feat(cli): add minimal ao send#83
Conversation
Replace the daemon's noopMessenger stub with a composite AgentMessenger
that writes a durable inbox file (primary) and types a live pointer into
the running zellij pane (best-effort secondary), plus the `ao send` CLI
that drives the existing POST /api/v1/sessions/{id}/send route.
- composite: fans Send to inbox then panep, pinning one timestamp so both
derive the same filename; a secondary failure is logged at WARN and
swallowed (the file is on disk), a primary failure aborts the call.
- inbox: writes <workspace>/.ao/inbox/<rfc3339nano>_<hash>.md.
- panep: types "new message at .ao/inbox/<file>" + Enter via a new narrow
zellij WriteChars seam (RuntimePaneWriter), kept off ports.Runtime.
- wiring: newSessionMessenger composes inbox+panep over the shared store;
startSession takes the messenger instead of the noop stub.
Carries across @Aa-43's work from PR #74 (staging), adapted to main's
post-#65/#77 daemon wiring shape.
Closes #79
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Greptile SummaryThis PR wires the
Confidence Score: 5/5Safe to merge; the change is a self-contained, well-tested addition with no modifications to existing logic paths. The new messenger replaces a no-op stub with a straightforward store-lookup-then-runtime-call, all sentinel errors are properly chained for existing HTTP error mapping, CLI validation is correct and covered by tests, and the daemon wiring is additive. No existing behavior is altered. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant CLI as ao send (cli/send.go)
participant Daemon as HTTP Daemon
participant Ctrl as SessionsController
participant Svc as sessionsvc.Service
participant Mgr as sessionmanager.Manager
participant Msng as runtimeMessenger
participant DB as sqlite.Store
participant RT as zellij.Runtime
User->>CLI: ao send --session X --message ...
CLI->>CLI: validate flags (usageError exit 2 if blank)
CLI->>Daemon: POST /api/v1/sessions/X/send
Daemon->>Ctrl: send(w, r)
Ctrl->>Ctrl: decodeJSON, validate, stripUnsafeControlChars
Ctrl->>Svc: Send(ctx, id, message)
Svc->>Mgr: Send(ctx, id, message)
Mgr->>Msng: messenger.Send(ctx, id, message)
Msng->>DB: GetSession(ctx, id)
DB-->>Msng: rec, ok
alt not found
Msng-->>Ctrl: "ErrNotFound -> 404"
else terminated
Msng-->>Ctrl: "ErrTerminated -> 409"
else no handle
Msng-->>Ctrl: "ErrIncompleteHandle -> 409"
else ok
Msng->>RT: SendMessage(ctx, handle, message)
RT-->>Msng: nil
Ctrl-->>CLI: 200 ok
CLI-->>User: exit 0
end
Reviews (7): Last reviewed commit: "fix(send): reject terminated sessions" | Re-trigger Greptile |
…bering
os.WriteFile opens with O_CREATE|O_WRONLY|O_TRUNC, which silently overwrites
an existing file. The doc comment already stated the intent ("we do not retry
on EEXIST"), but O_TRUNC never yields EEXIST — two identical messages sent on
the same composite-pinned nanosecond would produce the same filename and the
second Send would silently lose the first message. Switch to
O_CREATE|O_EXCL|O_WRONLY so a collision surfaces as an error; O_EXCL also
refuses to follow a symlink at the final path component. Add a regression test.
Addresses greptile review on PR #83.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The O_EXCL switch creates the inbox file before writing its body; if WriteString or Close then fails, the empty/partial .md was left on disk and the agent's next inbox scan would pick up a truncated ghost message. Remove the file on those error paths. O_EXCL guarantees the file did not exist before this call, so the cleanup can only delete our own partial write, never a legitimate earlier message. Addresses greptile review on PR #83. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@greptile-apps review. |
Summary
Adds the minimal
ao sendpath for sending a message to a running session through the existing daemon API.ao send --session <id> --message "..."now:{ "message": "..." }to/api/v1/sessions/{id}/sendSendMessage, which pastes the message into the agent pane and presses EnterIntentionally not included:
.ao/inboxfilesWriteCharsseamCloses #79.
Test plan
go test ./internal/cli ./internal/daemon ./internal/session_manager ./internal/httpd/controllersFocused tests cover CLI success/validation/error handling, preserving message whitespace, daemon messenger runtime-handle lookup, and sentinel error wrapping for HTTP error mapping.