* refactor(adapters): add shared helpers for adapter dedup (#2349)
Introduce the shared building blocks the per-adapter cleanup will use:
- ports.NormalizePermissionMode (finding 3)
- hookutil.FileExists (finding 10)
- binaryutil.ResolveBinary + BinarySpec (finding 2)
- activitystate.StandardDeriveActivityState (finding 4)
- agentbase.Base embed + StandardSessionInfo (findings 6-9)
No adapters wired up yet; behavior unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(goose): convert to shared adapter helpers (reference) (#2349)
Reference conversion proving the shared packages against real tests:
- hooks.go collapses onto hooksjson.Manager (finding 1)
- ResolveGooseBinary via binaryutil.BinarySpec (finding 2)
- gooseMode uses ports.NormalizePermissionMode (finding 3)
- activity.go deleted; dispatch points at activitystate (findings 4, 5)
- SessionInfo via agentbase.StandardSessionInfo; Base embed drops the
GetConfigSpec/GetPromptDeliveryStrategy no-ops (findings 6-8)
- fileExists/atomicWriteFile copies removed (findings 5, 10)
Also adds hooksjson package + activitystate test. goose: 327->~90 LOC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(adapters): dedup remaining 22 agent adapters onto shared helpers (#2349)
Applies the shared helpers across every remaining adapter:
- hooksjson.Manager for the matcher-group cohort (claudecode, qwen, droid)
- binaryutil.BinarySpec for 19 binary resolvers (aider/cursor/opencode/codex
keep their special resolvers; all now use hookutil.FileExists)
- ports.NormalizePermissionMode replaces 15 private copies
- agentbase.Base embed drops the GetConfigSpec/GetPromptDeliveryStrategy no-ops
(and GetAgentHooks/GetRestoreCommand/SessionInfo no-ops on hookless adapters)
- agentbase.StandardSessionInfo replaces the per-adapter metadata readers
- activitystate.StandardDeriveActivityState via dispatch for the 8 name-only
derivers; their activity.go files removed (claudecode/codex/droid/agy/opencode
keep payload-parsing derivers)
- 4 private atomicWriteFile copies missing fsync now use hookutil.AtomicWriteFile
- 23 private fileExists copies removed
Full backend build + vet + test suite green (1647 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: format with prettier [skip ci]
* fix(binaryutil): preserve per-adapter Windows candidate order (#2349)
Review feedback: the shared resolver hardcoded Windows candidate order as
APPDATA then LOCALAPPDATA, which flipped Kiro's lookup so the npm shim
(%APPDATA%\npm\kiro-cli.*) was probed before the native install
(%LOCALAPPDATA%\Programs\kiro\kiro-cli.exe). A fixed order can't preserve every
adapter's original order (goose/vibe want APPDATA first, kiro wants LOCALAPPDATA
first), so BinarySpec now takes an ordered WinPaths []WinPath list where each
entry names its base (WinAppData/WinLocalAppData/WinHome). Every adapter's list
reproduces its pre-refactor order exactly; kiro's native path is restored to
first.
go build / vet / test all green (1647 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(adapters): drop unused resolvedBinary writes flagged by govet (#2349)
Embedding agentbase.Base (value receiver) lets govet's unusedwrite analyzer
prove that setting resolvedBinary in tests that only call Base-promoted methods
(GetConfigSpec/GetPromptDeliveryStrategy/SessionInfo/cancellation) is a dead
write. Drop the field from those 23 constructions; GetLaunchCommand/GetRestore
tests that actually read it keep it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: gofmt manager_test.go struct alignment (#2349)
Inherited via the merge of main: a SessionRecord literal aligned its Metadata
field across a multi-key line, which gofmt/goimports rejects. One-line reformat
to unblock CI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(adapters): fix stale resolver fallback comments (#2349)
Review nit: 6 Resolve*Binary functions now delegate to binaryutil.ResolveBinary,
which returns a wrapped ports.ErrAgentBinaryNotFound rather than the bare binary
name. Update their doc comments (kiro, vibe, amp, agy, crush, cline) to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* feat: add agent catalog API and integrate with project settings
- Implemented AgentsController to handle /agents endpoint, returning a list of supported and installed agents.
- Created agent inventory service to manage agent data and detect installed agents.
- Updated ProjectSettingsForm to fetch and display agent information, including installed and supported agents.
- Enhanced error handling for agent detection and orchestrator restarts.
- Added tests for agent catalog and service to ensure correct functionality and error handling.
* Implement agent authorization status checks and update frontend to reflect changes
- Added `AuthStatus` method to various agent plugins to check authorization status using CLI probes.
- Introduced `authprobe` package to handle common CLI command checks for agent authorization.
- Updated backend tests to include scenarios for authorized and unauthorized agents.
- Modified frontend API schema to include `authorized` counts and `authStatus` for agents.
- Enhanced `ProjectSettingsForm` to display authorized agents and their statuses, including prompts for login when necessary.
- Adjusted agent selection logic to prioritize authorized agents and provide feedback for unauthorized or uninstalled agents.
* fix: simplify orchestrator replacement retry flow
* refactor: cache agent catalog ,remove AgentCounts schema and related references from API and frontend
* fix: clarify orchestrator replacement recovery state
* feat: enhance project settings and agent management
- Updated NewTaskDialog tests to increase timeout for async operations.
- Modified ProjectSettingsForm tests to improve agent handling and validation messages.
- Refactored ProjectSettingsForm component to streamline agent selection and validation logic.
- Introduced new agent service to manage agent inventory and authentication status.
- Improved Sidebar tests to ensure proper agent options are loaded and handled.
- Enhanced SessionsBoard component by removing unused imports and optimizing state management.
- Fixed Select component styling for better consistency in UI.
- Added error handling for AO daemon readiness in ShellLayout.
* chore: format with prettier [skip ci]
* feat: add AuthStatus method documentation and improve error handling in session manager
* feat: enhance agent authentication and configuration management
- Implement local authentication status checks for PI, Qwen, and Vibe agents.
- Introduce JSON-based authentication status retrieval for PI agents.
- Add environment variable checks for Qwen agents and improve settings file parsing.
- Enhance Vibe agent authentication with support for environment variables and session logs.
- Update agent service to handle asynchronous probing for installed and authorized agents.
- Modify session manager to support prompt delivery strategies based on agent capabilities.
- Improve frontend agent selection UI with loading states and error handling.
- Add tests for new authentication logic and session management features.
* chore: format with prettier [skip ci]
* test: fix session manager fake after rebase
* feat: enhance agent authentication status checks
- Implement local authentication status checks for the Devin, Droid, Kiro, and other agents.
- Add support for reading credentials from specific configuration files and environment variables.
- Introduce new tests for various agents to ensure proper authentication status reporting.
- Refactor existing authentication logic to improve clarity and maintainability.
- Remove deprecated agent setup warnings from the SessionsBoard component in the frontend.
* feat: clear environment variables in auth status tests for Aider and OpenCode
* feat: enhance error handling in authentication status functions and update component props
* feat: integrate fallback agents in RequiredAgentField and streamline props handling
* fix: refactor Sidebar test imports and parameters for clarity
* refactor: remove orchestrator retirement logic and related tests
- Deleted the RetireOrchestrator function and its associated error handling.
- Removed tests related to orchestrator retirement and state management.
- Simplified ProjectSettingsForm by eliminating orchestrator restart logic and related UI elements.
- Updated API client mocks to reflect the removal of orchestrator-related functionality.
* feat: enhance agent management and error handling
- Added agent refresh functionality in ProjectSettingsForm with UI updates for agent availability.
- Implemented `refreshAgents` API call to fetch the latest agent catalog.
- Updated agent selection logic to disable unavailable agents and show appropriate error messages.
- Enhanced error handling in `apiErrorMessage` to include daemon error codes alongside messages.
- Created new test cases for agent availability and error handling in Sidebar component.
- Introduced `ResolveBinary` method for multiple agent adapters to standardize binary resolution.
- Added new agent adapter files for various agents (e.g., Aider, Claude Code, etc.) to support binary resolution.
* chore: format with prettier [skip ci]
* fix: satisfy backend lint checks
* refactor: clean up authentication logic and improve error handling across agents
* chore: format with prettier [skip ci]
* feat(authprobe): enhance status classification for authentication outputs and add tests for explicit false/true keys
chore(docs): update README to remove outdated agent adapter contract references
fix(components): improve agent selection logic to handle unknown auth status and update related tests
* chore: format with prettier [skip ci]
* refactor: remove shell environment authentication logic and update related tests
* feat(tests): integrate QueryClient for agent data in CreateProjectAgentSheet tests
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix(sessions): stop AO hook files from making every worktree permanently dirty
Agent adapters write hook files (.codex/hooks.json, .opencode/plugins/
ao-activity.ts, .claude/settings.local.json, ...) into fresh session
worktrees as untracked files. `git worktree remove` (deliberately run
without --force) refuses on any untracked file, so Workspace.Destroy
failed for every session of the 12 workspace-writing harnesses:
POST /sessions/{id}/kill returned an unlogged 500 INTERNAL_ERROR and
`ao session cleanup` reported 'Would clean N' then '0 sessions cleaned'
with no reason, leaking workspaces forever.
Three coordinated fixes, none of which force-deletes user/agent work:
- Root cause: every adapter now writes a sentinel-guarded, self-ignoring
.gitignore next to its hook files (hookutil.EnsureWorkspaceGitignore),
so AO's own files no longer count as dirt while anything an agent
drops — even in the same directory — still blocks teardown. A
registry-wide conformance test enforces the contract for all current
and future adapters. (Per-worktree .git/worktrees/<name>/info/exclude
was evaluated first but git does not honor it.)
- Typed refusal: gitworktree.Destroy classifies a still-dirty refusal as
ports.ErrWorkspaceDirty (git status probe). Kill maps it to success
with freed=false (session terminated, worktree preserved); Cleanup
reports it per-session as skipped-with-reason through the API
(CleanupSessionsResponse.skipped), and the CLI prints
'Skipped: <id> (workspace has uncommitted changes)' plus a summary.
- Observability: envelope.WriteError records the raw service error into
a request-scoped slot and the access log attaches it to 5xx lines, so
any remaining internal error is diagnosable server-side.
Worktrees created before this fix gain the .gitignore on restore (hook
install re-runs); their cleanup is otherwise reported as skipped instead
of erroring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cleanup): address Greptile P2s — surface dirty-probe failures, stop leaking raw errors
Two review findings on this PR:
- gitworktree.Destroy: when the isDirty probe itself failed, the error was
silently discarded and the refusal looked identical to "registered but not
dirty". The probe failure now rides the returned error (dirty probe: ...),
so it reaches the access log via the 5xx error capture.
- Cleanup skip reasons: a non-dirty teardown failure put the raw error —
including internal filesystem paths — into the public skipped[].reason
field. The public reason is now the fixed string "workspace teardown
failed"; the full cause goes to the daemon log (warn, with sessionID and
path). The dirty-refusal reason is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(gitworktree): wrap the dirty-probe error with %w per errorlint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Introduces the shared platform that per-agent adapters plug into, wired for the
three shipped harnesses (claude-code, codex, opencode):
- adapters/agent/registry: single source of truth for shipped adapters
(Constructors), consumed by the daemon to resolve a session's harness.
- adapters/agent/activitydispatch + 'ao hooks' command: maps an agent's native
hook callbacks onto AO activity states (active/idle/waiting/...).
- claudecode/codex/opencode: emit SessionStart/UserPromptSubmit/Stop activity.
- HTTP + OpenAPI: report session activity state.
- db: single migration widening sessions.harness to all shipped harnesses, so
adding an adapter needs no further migration.
- domain: harness constants + --agent alias for 'ao spawn'.
Adding a new agent is now one adapter package plus a line in Constructors().
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Harshit Singh Bhandari <claudeagain@pkarnal.com>
* feat: add ao hooks activity command
* fix(activity): address review nits
- 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).
---------
Co-authored-by: harshitsinghbhandari <dev@theharshitsingh.com>
* feat(agent): add opencode adapter + activity plugin hooks
Add an opencode (sst/opencode) agent adapter implementing the 6-method
ports.Agent interface and register it in the daemon's agent resolver, so a
session with harness "opencode" spawns and restores a real opencode worker.
opencode diverges from the claude-code/codex adapters in two ways the adapter
bridges:
- No native command-hook config. Unlike Claude Code (.claude/settings.local.json)
and Codex (.codex/hooks.json), opencode has no "run this command on event"
config (see sst/opencode#5409). Its only lifecycle surface is a JS/TS plugin
loaded from .opencode/plugins/. GetAgentHooks therefore //go:embeds an
AO-owned plugin (assets/ao-activity.ts) and writes it atomically; install is an
idempotent overwrite and uninstall is a sentinel-guarded delete, so
user-authored plugins are never touched. The plugin maps opencode events onto
AO's three normalized activity events: session.created -> session-start,
message.updated/message.part.updated -> user-prompt-submit, and
session.status(idle) -> stop (NOT the deprecated session.idle, which is
unreliable under `opencode run`). It shells `ao hooks opencode <event>` via a
guarded sh -c so a missing `ao` binary is a silent no-op.
- A single approval flag. opencode exposes only --dangerously-skip-permissions
(no graduated accept-edits/auto) and no system-prompt flag, so those map to a
bypass-only permission flag and a documented no-op for the system prompt
(deferred to opencode's own config).
Launch uses the interactive TUI (`opencode --prompt <p>`); restore continues a
captured native session via `opencode --session <id>`.
opencode_test.go mirrors codex_test.go (12 tests) and the daemon wiring test now
asserts the opencode harness resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent): address Greptile review on opencode adapter (#80)
- Dispatch all opencode plugin hooks synchronously (Bun.spawnSync). The
session.created handler previously fired session-start via an async
Bun.spawn; if opencode does not await the event handler, a following
message.updated -> user-prompt-submit (sync) could complete before the
in-flight async session-start, so AO would see the prompt before the session
was registered. A sync spawn blocks opencode's single-threaded event loop, so
events are now reported strictly in dispatch order. Removes the now-unused
async callHook helper.
- Fix package/doc comments that said .opencode/plugin/ (singular) to match the
plural .opencode/plugins/ the adapter actually writes to.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent): surface opencode hook failures instead of swallowing them
The activity plugin previously discarded every failure: callHookSync ignored
the subprocess exit code/stderr and both catch blocks were empty, so a failing
`ao hooks` invocation or a malformed event payload was completely invisible.
Now failures are reported through opencode's structured logger (client.app.log)
while still never crashing opencode:
- callHookSync pipes stderr and checks result.success; a non-zero exit (a real
`ao hooks` failure — the `command -v ao` guard makes a missing binary exit 0)
is logged with its exit code and stderr.
- spawn exceptions (e.g. no `sh`) are caught and logged.
- the event-handler catch logs the offending event type instead of swallowing.
- logHookFailure is itself best-effort (optional-chained, rejection swallowed),
so logging can never throw back into opencode.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent): address opencode adapter review — install guard, prompt dedup, hook timeout
Maintainer review on #80 surfaced three pre-merge issues:
- GetAgentHooks could clobber a user file: install overwrote
.opencode/plugins/ao-activity.ts unconditionally while uninstall was
sentinel-guarded. Install now refuses (loud error) to overwrite a file that
isn't AO-managed; absent/AO-managed targets still write idempotently.
- Empty-prompt report poisoned the dedup: message.updated fired
user-prompt-submit with an empty prompt AND marked the message seen, so the
text from the following message.part.updated was deduped away and never
reached AO — breaking title-from-prompt. reportUserPrompt now reports at most
twice: an optional early empty report (keeps run-mode flows active) that does
NOT block a later text report, and a text report that is terminal.
- Bun.spawnSync had no timeout, so a hung `ao hooks` could block opencode
indefinitely. Each spawn is now time-boxed at 30s, matching the claude-code
and codex hook timeouts.
Adds TestGetAgentHooksRefusesToClobberForeignFile and a spawn-timeout assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: harden opencode activity hooks
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in>