* feat(plugin): add agents plugin (first iteration)
Faithful copy of the agents plugin implementation from yyovil/better-ao
(internal/plugin/ -> backend/internal/plugin/) plus its PRD
(prds/plugins/agents/PRD.md), as a first-iteration proposal for review.
Imports are left at their original github.com/yyovil/better-ao/... paths and
are NOT yet reconciled to this repo's module; see PR description for the
integration deltas (module path, missing internal/utils dependency).
Co-authored-by: Claude <noreply@anthropic.com>
* Move agent adapters under backend adapters
* Keep daemon ports and session out of adapter move
* Remove Better-AO naming from flake
* Keep flake as dev shell only
* Use goimports for local formatting
* Wire session manager to per-session agent adapters
Move the Agent port into internal/ports and have the claude-code and
codex adapters implement it directly, alongside their workspace-local
activity hooks and a manifest-keyed adapter registry. Rename
RuntimeConfig.LaunchCommand to Argv and update the tmux and zellij
runtimes to match.
The session Manager now resolves a real agent adapter per session via a
new ports.AgentResolver: from cfg.Harness on Spawn and the stored harness
on Restore, so one daemon runs claude-code and codex sessions side by
side. The daemon backs the resolver with the registry; AO_AGENT selects
the default harness (default claude-code), validated at startup. Removes
the temporary noopAgent stub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(agent): point the agent contract at internal/ports/agent.go
The Agent interface moved from internal/adapters/agent to internal/ports;
update the PRD's Goal and Agent Contract sections (and the SessionInfo
references) to match the code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Wire the session service into the daemon
daemon.Run now builds the controller-facing session service — a session
manager over the zellij runtime, a gitworktree workspace, the shared
store + LCM, and the per-session agent resolver (AO_AGENT default,
validated at startup) — and mounts it at httpd APIDeps.Sessions, so the
session REST routes are backed by a real service. startLifecycle moves
ahead of the HTTP server so both share one LCM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address Greptile review: complete the live spawn path
- Spawn and Restore now install workspace-local activity hooks
(GetAgentHooks) and run the adapter's optional PreLaunch step before
launch, via a shared prepareWorkspace helper. PreLaunch is how Claude
Code records workspace trust, so its interactive "trust this folder?"
dialog can't hang the headless pane; the spawned env now also carries
AO_DATA_DIR so the installed hook commands find the store.
- claudecode and codex hook/config writes are now atomic (temp + rename)
instead of os.WriteFile, so a crash mid-write can't leave a partial
file the agent fails to parse.
- ensureWorkspaceTrusted serializes its read-modify-write under a package
mutex, so concurrent spawns to different workspaces don't drop each
other's ~/.claude.json trust entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ports): pin MetadataKeyAgentSessionID to domain.SessionMetadata json tag
The equality between ports.MetadataKeyAgentSessionID and the json tag on
domain.SessionMetadata.AgentSessionID is a hand-maintained invariant; this
test fails loudly if either side drifts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(adapters): use ports.MetadataKeyAgentSessionID in claudecode + codex
The native session id metadata key is defined in ports for cross-package
consumption; drop the duplicated literals in each adapter so the constant
has one home.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(codex): cover ensureCodexHooksFeatureEnabled TOML edge cases
The helper is a string editor over config.toml; pin its content
transformation for missing/empty files, existing [features] blocks,
the no-op case, and the legacy codex_hooks=true migration paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(adapters): document Registry concurrency contract
Registry registration runs at daemon boot before any goroutine calls Get,
so the underlying map needs no lock; pin that contract in the doc comment
so a future change doesn't quietly introduce a race.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* style(codex): gofmt codex_test.go after constant rename
The previous commit (7c5b2a9) replaced codexAgentSessionIDMetadataKey with
ports.MetadataKeyAgentSessionID inside a map literal; the longer key threw
off gofmt's column alignment on the adjacent codexTitleMetadataKey /
codexSummaryMetadataKey lines. Caught by agent-ci's Check formatting step.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: harshitsinghbhandari <dev@theharshitsingh.com>
Add internal/terminal: a transport-agnostic feature package that attaches
a PTY to a session's tmux pane and multiplexes the byte stream to WebSocket
clients, plus a session-state channel fed by the CDC broadcaster.
The PTY is reached through a small PTYSource interface (satisfied by the
tmux runtime adapter) and spawned via an injectable spawnFunc, so fan-out,
the 50KB replay ring, and re-attach resilience all test without a real
process, tmux, or network. A tmux-guarded integration test exercises the
real creack/pty path end-to-end.
Raw PTY bytes never touch the CDC change_log; only the sessions channel is
CDC-fed. Windows PTY spawning is stubbed pending a ConPTY path.