feat: add wacli agent — JSON-RPC 2.0 mode for AI agents#49
feat: add wacli agent — JSON-RPC 2.0 mode for AI agents#49delltrak wants to merge 1 commit intosteipete:mainfrom
wacli agent — JSON-RPC 2.0 mode for AI agents#49Conversation
Add a new `wacli agent` subcommand that runs as a long-lived JSON-RPC 2.0 server over NDJSON (stdin/stdout), enabling AI agents to receive and send WhatsApp messages through a single process. This solves the problem of `sync --follow` and `send text` competing for the exclusive flock — the agent holds one connection and handles both directions simultaneously. Changes: - internal/wa/client.go: add SendPresence, SendChatPresence, MarkRead, SetForceActiveDeliveryReceipts wrapper methods - internal/app/app.go: extend WAClient interface with the 4 new methods - internal/app/jsonrpc.go: JSON-RPC 2.0 protocol types and mutex writer - internal/app/agent.go: agent loop, dispatcher, 9 RPC handlers (send_text, send_file, mark_read, set_typing, set_presence, list_chats, list_messages, search, get_message), event notifications (message, receipt, presence, chat_presence, connection), and optional auto-presence with typing simulation - internal/app/sync.go: refactor storeParsedMessage to return StreamMessage; add OnMessage callback to SyncOptions - cmd/wacli/sync.go: add --stream flag for NDJSON message output - cmd/wacli/agent.go: cobra command with --auto-presence flag - cmd/wacli/root.go: register agent command - internal/app/agent_test.go: 9 tests covering the full RPC surface - internal/app/fake_wa_test.go: stubs for the 4 new interface methods Ref: steipete#48 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Line 82 in b62bd28
When --stream is set, messages are emitted as JSON lines on stdout, but this unconditional summary print appends a plain-text line (Messages stored: ...) to the same stdout stream. In --once mode this breaks NDJSON consumers that parse stdout as JSON-only message events, so the summary should be suppressed or redirected (e.g., stderr) while streaming.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| writer.respondError(req.ID, errCodeParse, "invalid jsonrpc version") | ||
| continue | ||
| } | ||
| a.dispatchRPC(ctx, writer, req, opts) |
There was a problem hiding this comment.
Skip replies for notification requests without id
JSON-RPC 2.0 notifications are requests that omit id and must not produce a response, but this loop dispatches every request and downstream handlers always call respond/respondError, which emits frames with "id":null. Clients that send fire-and-forget notifications (for example set_presence) will receive unexpected responses and may treat them as protocol violations or mis-correlate them with real request/response traffic.
Useful? React with 👍 / 👎.
| } | ||
| a.dispatchRPC(ctx, writer, req, opts) | ||
| } | ||
| return nil |
There was a problem hiding this comment.
Propagate scanner errors from agent input loop
The input loop returns nil immediately after scanner.Scan() ends, without checking scanner.Err(). If stdin hits a read failure or a request line exceeds the configured 1 MiB scanner limit, the agent exits silently and drops subsequent requests, leaving callers without a detectable failure signal.
Useful? React with 👍 / 👎.
Summary
wacli agentsubcommand: a long-lived JSON-RPC 2.0 server over NDJSON (stdin/stdout)sync --followandsend text— one process handles both directionssend_text,send_file,mark_read,set_typing,set_presence,list_chats,list_messages,search,get_messagemessage,receipt,presence,chat_presence,connection,agent.ready--auto-presence: simulates human-like behavior (online → typing → send → offline)mark_readmethod) — the consumer decides when to show blue ticksFiles changed
internal/wa/client.gointernal/app/app.gointernal/app/jsonrpc.gointernal/app/agent.gointernal/app/sync.gostoreParsedMessageto returnStreamMessage; addOnMessagecallbackcmd/wacli/sync.go--streamflagcmd/wacli/agent.gocmd/wacli/root.gointernal/app/agent_test.gointernal/app/fake_wa_test.goTest plan
go test -tags sqlite_fts5 ./...— all passgo test ./...(without FTS5) — all passgo vet ./...— cleangofmt -l .— cleango build -tags sqlite_fts5 -o ./dist/wacli ./cmd/wacliCloses #48
🤖 Generated with Claude Code