Skip to content

feat(interactions): generic agent interaction-request bridge (Claude + Codex permission capture) - #112

Merged
TraderSamwise merged 5 commits into
masterfrom
feat/agent-interaction-requests
Jun 9, 2026
Merged

feat(interactions): generic agent interaction-request bridge (Claude + Codex permission capture)#112
TraderSamwise merged 5 commits into
masterfrom
feat/agent-interaction-requests

Conversation

@TraderSamwise

Copy link
Copy Markdown
Owner

What

Builds the aimux side of the cmux⇄aimux bridge: a generic, first-party agent interaction-request primitive (approvals being the first type), with Claude and Codex PermissionRequest capture routed through it. External clients (cmux today; the aimux app/ later) consume one HTTP API to see and resolve "ask"-gated prompts. Design spec: ~/cs/docs/aimux-cmux-bridge.md.

How it fits together

  1. Generic primitivesrc/interaction-requests.ts: an in-memory InteractionRegistry of {id, sessionId, type, payload, status, response} where type ∈ {permission, exit_plan, question, input}, with register/resolve/cancel/wait (timeout + abort). permission is implemented; the others drop into the same shape.
  2. HTTP surface (per-project metadata server): POST /agents/interaction/{register,respond,request}, GET /agents/interaction/{wait,pending,stream}; an actionable interaction_request alert (carries {id,type,summary}); pendingInteractions on /desktop-state. New attention state needs_response.
  3. Claude capture — a PermissionRequest hook (120s) injected via the existing --settings path; the claude-hook permission-request action registers + long-polls /agents/interaction/request and prints the allow/deny decision Claude honors, or {} to defer.
  4. Codex capture — ported codex-hook subsystem + PermissionRequest; writes a merge-preserving ~/.codex/hooks.json (only replaces aimux-owned entries, so it coexists with cmux) and enables hooks per-launch via -c features.hooks=true --dangerously-bypass-hook-trust (no config.toml mutation). Shares one resolvePermissionRequestOutput helper with Claude.

No default-path regression (watcher-gating)

The PermissionRequest hook is blocking. To avoid every gated prompt hanging ~110s when no approval client is running, /agents/interaction/request blocks only while a client is connected to /agents/interaction/stream; otherwise it returns instantly and the hook defers to the native tmux prompt. Any error/timeout also degrades to {} (native prompt) — nothing regresses when no client is watching.

Tests / verification

  • Registry unit tests; real-HTTP integration tests (register→wait→respond, the combined /request, watcher gate, timeout, 400/409); codex-hooks tests (merge preserves foreign entries, PermissionRequest@120s).
  • End-to-end with the built CLI for both Claude and Codex: no-watcher defers in ~200ms; a watched request resolves to behavior:allow on approve.
  • yarn typecheck, yarn lint, yarn build, full suite (1094) all green.

Out of scope (follow-ups)

  • The cmux client (Swift) that consumes /agents/interaction/* (poll/stream → Feed, reply, Deny/Once/Always).
  • aimux app/ consumption + updating the app/ TS mirrors of AlertKind/DesktopState.
  • Other interaction types (exit_plan/question/input) — structurally ready, not yet wired.

🤖 Generated with Claude Code

test and others added 4 commits June 9, 2026 12:42
Introduce a pure, in-memory InteractionRegistry and request model
(permission/exit_plan/question/input) with register/resolve/cancel/wait
(timeout + abort). Extend AgentAttentionState with needs_response and
AlertKind with interaction_request plus an actionable `interaction`
payload on AlertEvent. No server/hook wiring yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add register/wait(long-poll)/respond/pending HTTP endpoints under
/agents/interaction/*, an actionable interaction_request alert (forwarded
through publishAlert), and pendingInteractions on /desktop-state. The
registry is a per-server instance. Validate object payload/response and
only clear attention when still needs_response. Covered by HTTP
integration tests (register -> wait -> respond, pending, timeout, 400/409).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…equest hook

Inject a PermissionRequest hook (120s) that routes "ask"-gated permission
prompts through the interaction registry: the claude-hook permission-request
action registers + long-polls POST /agents/interaction/request and prints the
allow/deny decision Claude honors, or {} to defer to the native tmux prompt.

Watcher-gating: a GET /agents/interaction/stream SSE endpoint tracks connected
interaction clients; /request blocks only when a watcher is present, otherwise
returns watching:false so the hook defers instantly (no ~110s block in the
default no-client case). Clear lingering needs_response on non-resolved settle.

Verified end-to-end with the built CLI: no-watcher defers in ~200ms; a watched
request blocks and resolves to behavior:allow on approve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ude)

Port the codex hook subsystem onto this branch and add PermissionRequest
capture. codex-hooks.ts writes a merge-preserving ~/.codex/hooks.json (only
replaces aimux-owned entries, so it coexists with cmux) and enables hooks
per-launch via -c features.hooks=true --dangerously-bypass-hook-trust (no
config.toml mutation). The codex-hook command's permission-request action and
the claude one now share resolvePermissionRequestOutput, routing both through
POST /agents/interaction/request. session-launch wires installCodexHooks +
launch args + AIMUX_SESSION_ID/AIMUX_PROJECT_ROOT env for codex.

Verified end-to-end with the built CLI: codex-hook defers in ~200ms with no
watcher and resolves to behavior:allow when a watcher approves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jun 9, 2026 7:08am

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@TraderSamwise, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 46 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 213d5b93-b1fe-49b3-bd22-8872ff01fc7e

📥 Commits

Reviewing files that changed from the base of the PR and between b3b70e6 and 1577a4b.

📒 Files selected for processing (12)
  • src/agent-events.ts
  • src/claude-hooks.test.ts
  • src/claude-hooks.ts
  • src/codex-hooks.test.ts
  • src/codex-hooks.ts
  • src/interaction-requests.test.ts
  • src/interaction-requests.ts
  • src/main.ts
  • src/metadata-server.interaction.test.ts
  • src/metadata-server.ts
  • src/multiplexer/session-launch.ts
  • src/project-events.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-interaction-requests

Comment @coderabbitai help to get the list of available commands and usage tips.

…growth, watcher safety)

- /request: reset needs_response on client disconnect, not only on the
  normal-return path, so a killed hook doesn't strand the attention state.
- /respond: clear attention via listPending (handles concurrent interactions)
  instead of a stale loadMetadataState disk read.
- InteractionRegistry: prune settled entries past a TTL on register so the
  registry can't grow unbounded; constructor-configurable for tests.
- /agents/interaction/stream: wrap setup so a send failure runs cleanup and
  never strands the watcher counter.
- Truncate overlong permission detail before it enters the alert/notification.
- Document the codex hook trust-bypass scope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@TraderSamwise

Copy link
Copy Markdown
Owner Author

Sub-agent review (CodeRabbit was rate-limited this round)

CodeRabbit couldn't review (PR-review rate limit). A sub-agent review ran instead; findings + resolutions, fixed in 1577a4b:

  • [HIGH] Attention stuck at needs_response on /request client disconnect — Fixed: the attention reset now runs before the if (closed) return, so a killed/disconnected hook no longer strands the session in needs_response.
  • [MED] /respond cleared attention via a stale loadMetadataState() disk read and ignored concurrent interactions — Fixed: now resets only when listPending(session) is empty (same guard as /request), so one resolved prompt won't clear attention while another is still pending.
  • [MED] InteractionRegistry grew unbounded (no GC of settled entries) — Fixed: register() prunes settled entries past a TTL (default 5m, constructor-configurable for tests).
  • [MED] /agents/interaction/stream leaked the watcher counter if sendSseEvent("ready") threw — Fixed: setup wrapped so a failure runs cleanup() (decrements the counter).
  • [LOW-MED] Untruncated tool_input flowed into the persisted alert message — Fixed: permission detail truncated to 200 chars.
  • [LOW] --dangerously-bypass-hook-trust rationale — Documented: applies to the per-user hooks.json aimux owns/writes (same model as the existing cmux integration).
  • [LOW] cancelSession never called on session-end — Covered by the disconnect fix (a killed hook's socket close cancels its pending /request) + the TTL prune; no separate wiring added.
  • [LOW] Codex permission-request early-exit skips backend-session recording — By design: the permission path is kept lean; SessionStart/prompt-submit record the backend id, and the permission flow uses the aimux session id (not the backend id).

yarn typecheck, yarn lint, full suite (1097) green.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@TraderSamwise
TraderSamwise merged commit b22ac97 into master Jun 9, 2026
3 checks passed
@TraderSamwise
TraderSamwise deleted the feat/agent-interaction-requests branch June 9, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant