Skip to content

[codex] add ao hooks activity command#113

Merged
harshitsinghbhandari merged 2 commits into
aoagents:mainfrom
yyovil:codex/ao-hooks-command
Jun 6, 2026
Merged

[codex] add ao hooks activity command#113
harshitsinghbhandari merged 2 commits into
aoagents:mainfrom
yyovil:codex/ao-hooks-command

Conversation

@yyovil
Copy link
Copy Markdown
Collaborator

@yyovil yyovil commented Jun 5, 2026

Summary

This adds the hidden ao hooks <agent> <event> callback command and wires it to daemon-backed session activity updates.

AO already installs workspace-local activity hooks for agents, but the receiving CLI command did not exist yet. That meant native hook callbacks could shell out to ao hooks ... without a daemon route that translated the event into durable session activity. The user-visible effect is that sessions can remain stale or rely on runtime observation instead of agent-owned activity signals.

The root cause is a missing boundary between installed agent hook callbacks and the lifecycle reducer. The CLI needed a small, best-effort callback receiver, and the daemon needed a route that records normalized activity through the existing lifecycle manager.

Changes

  • Adds a hidden ao hooks <agent> <event> Cobra command that reads hook JSON from stdin, uses AO_SESSION_ID to identify the AO session, derives an activity state, and posts to /api/v1/sessions/{id}/activity.
  • Adds activitydispatch plus Claude Code, Codex, and opencode derivers for the hook events currently installed by those adapters.
  • Extends Claude Code hooks to report Notification and SessionEnd, and Codex hooks to report PermissionRequest.
  • Adds the Codex --dangerously-bypass-hook-trust launch/restore flag so AO-installed workspace hooks run in fresh per-session worktrees. I verified the option with codex --help; it runs enabled hooks without requiring persisted hook trust for the invocation.
  • Adds the daemon activity route and wires it directly to the lifecycle manager, plus regenerated OpenAPI YAML and frontend TypeScript schema.
  • Documents the shared CLI HTTP client as fixed-loopback for gosec and checks the best-effort diagnostic write in ao hooks.

Validation

  • codex --help | rg -n "hook|trust|bypass|danger" -C 2
  • npm run api
  • go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 run --path-mode=abs
  • go test ./internal/adapters/agent/claudecode ./internal/adapters/agent/codex ./internal/adapters/agent/opencode ./internal/cli ./internal/httpd/controllers ./internal/httpd/apispec
  • go test ./internal/terminal -run 'TestNilLoggerFallsBackToDefault|TestCreackPTYCloseIsIdempotent|TestRingBuffer|TestClientMsgRoundTrip|TestServerMsg|TestServe|TestEnqueueOverflowCancelsConn|TestSessionFansOutLiveOutputToSubscribers|TestSessionReplaysRingBufferOnSubscribe|TestSessionWriteAndResizeReachPTY|TestSessionSkipsReattachOnCleanExit|TestSessionReattachesWhileSessionAlive|TestSessionFailsWhenAttachCommandErrors'
  • go test ./... -skip TestSessionStreamsRealZellijPane

Notes

A plain go test ./... fails in backend/internal/terminal on this machine because TestSessionStreamsRealZellijPane constructs a zellij IPC socket path longer than zellij's 103-byte limit. Even TMPDIR=/tmp leaves the generated path at 118 bytes because the test name is repeated in the socket path.

npm run frontend:typecheck also fails on pre-existing frontend dependency/type issues unrelated to this schema change: missing fumadocs-* and .source modules, plus a Next-specific fetch({ next: ... }) option not present in the current RequestInit type.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 5, 2026

Greptile Summary

This PR adds the ao hooks <agent> <event> hidden CLI command and wires it to a new daemon activity endpoint, closing the gap between installed agent hook callbacks and durable session activity state. The hook command reads native payloads from stdin, derives an AO activity state via per-agent derivers, and posts to the lifecycle manager via a new POST /api/v1/sessions/{sessionId}/activity route.

  • Adds activitydispatch plus per-agent derivers (claudecode, codex, opencode) that map native hook event names and payloads to domain.ActivityState values; all parsing errors are treated as best-effort no-ops.
  • Extends Claude Code hooks with Notification and SessionEnd events and adds a PermissionRequest hook for Codex, along with the --dangerously-bypass-hook-trust flag needed for per-session worktree hook execution.
  • Introduces the daemon-side activity route, OpenAPI spec, and regenerated TypeScript schema.

Confidence Score: 5/5

Safe to merge; the change adds a narrow, best-effort callback receiver and a guarded activity endpoint with no side effects on existing session flows.

All error paths in the new hook command exit 0 by design, the activity endpoint validates the state enum before touching the lifecycle manager, and the nil-guard on the recorder returns 501 cleanly when the dependency is absent. The omitempty fix on the Codex matcher field is a correctness improvement. No existing behaviour is altered.

No files require special attention.

Important Files Changed

Filename Overview
backend/internal/cli/hooks.go New hidden ao hooks command; correctly implements best-effort semantics (exit 0 on all error paths), URL-encodes the session ID, and logs failures to stderr.
backend/internal/adapters/agent/claudecode/activity.go Activity state deriver for Claude Code; correctly treats malformed payloads as no-ops and maps clear/resume session-end reasons to no-signal.
backend/internal/adapters/agent/codex/codex.go Adds --dangerously-bypass-hook-trust to both launch and restore commands; the trade-off (globally bypassing Codex hook trust) is acknowledged in comments and was previously flagged as a P2 concern.
backend/internal/httpd/controllers/sessions.go Adds ActivityRecorder interface and activity handler; state enum validation is exhaustive and the nil-guard returns 501 cleanly when the recorder is absent.
backend/internal/adapters/agent/activitydispatch/dispatch.go Clean registry mapping agent tokens to DeriveFunc; returns ok=false for unknown agents.
backend/internal/adapters/agent/codex/hooks.go Adds PermissionRequest hook and fixes the matcher field to omitempty so a nil pointer serializes as absent JSON rather than null.

Sequence Diagram

sequenceDiagram
    participant Agent as AgentCLI
    participant Hook as AoHooksCmd
    participant Dispatch as ActivityDispatch
    participant Daemon as DaemonRoute
    participant LCM as LifecycleMgr

    Agent->>Hook: exec with stdin payload and AO_SESSION_ID
    Hook->>Hook: read AO_SESSION_ID from env
    Hook->>Hook: io.ReadAll stdin
    Hook->>Dispatch: Derive(agent, event, payload)
    alt event carries activity signal
        Dispatch-->>Hook: "state, ok=true"
        Hook->>Daemon: "POST /api/v1/sessions/{id}/activity"
        Daemon->>Daemon: validate state enum
        Daemon->>LCM: ApplyActivitySignal(ctx, id, signal)
        LCM-->>Daemon: nil or error
        Daemon-->>Hook: 200 SetActivityResponse
        Hook-->>Agent: exit 0
    else unknown agent or no-signal event
        Dispatch-->>Hook: "empty, ok=false"
        Hook-->>Agent: exit 0 no-op
    end
    Note over Hook,Daemon: Any daemon error logs to stderr and exits 0
Loading

Reviews (2): Last reviewed commit: "feat: add ao hooks activity command" | Re-trigger Greptile

Comment thread backend/internal/adapters/agent/opencode/activity.go
@yyovil yyovil force-pushed the codex/ao-hooks-command branch from 313bb17 to 2ee5b21 Compare June 5, 2026 17:29
@harshitsinghbhandari harshitsinghbhandari added this to the rewrite milestone Jun 6, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yyovil has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yyovil has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator

Took over to fix CI failures after recent main merges (#103, #108/#114, #115, #101, #106).

Root cause was a botched merge commit (7f909d1) that left a missing } in backend/internal/httpd/controllers/dto.go:188, which cascaded into all 7 failing checks (build-test, lint, container, native ×3, api-drift). Replaced the merge with a clean rebase onto current main.

Changes:

  • Rebased onto origin/main (drops the broken merge commit; preserves yyovil's authorship on the single feat commit).
  • dto.go: kept both blocks — SessionPRFacts/ClaimPR* (from feat: ao session claim-pr + spawn --claim-pr wiring #101) and the new SetActivityRequest/SetActivityResponse.
  • daemon.go httpd.APIDeps{} literal: kept CDC/Events (from feat(cdc): add SSE event stream replay #106) alongside the new Activity: lcStack.LCM.
  • apispec/specgen/build.go: kept both sets of Controllers* rename entries (PR claim + activity).
  • Regenerated OpenAPI spec (go generate ./internal/httpd/apispec/...) and frontend TS (npx openapi-typescript) — both already in sync with the rebased code, no extra changes needed.

Validation (local, after rebase):

  • cd backend && go build ./... clean.
  • cd backend && go test -race -skip TestSessionStreamsRealZellijPane ./... — 608 passed in 39 packages.
  • cd backend && go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 run --path-mode=abs — 0 issues.
  • Skipped TestSessionStreamsRealZellijPane per PR description (pre-existing macOS path-length quirk; not regression).

Pushed with --force-with-lease to yyovil:codex/ao-hooks-command. No design changes. Original feat commit authored by yyovil is preserved.

- lcm: sameActivity ignores LastActivityAt so same-state repeats no-op
  and don't churn UpdatedAt / CDC events.
- cli/hooks: surface stdin read errors to stderr for parity with the
  daemon-error path; still exit 0 so a failed hook can't break the agent.
- claudecode: GetAgentHooks docstring covers Notification + SessionEnd
  (the slice already included them; only the comment was stale).
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yyovil has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@harshitsinghbhandari harshitsinghbhandari merged commit 3c7344b into aoagents:main Jun 6, 2026
8 checks passed
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.

2 participants