* feat(core): auto-terminate sessions on PR merge (#1309)
When a session's PR was detected as merged, the session transitioned
to status "merged" but its tmux runtime, worktree, and metadata were
never cleaned up — leaving zombie tmux sessions and stale entries in
`ao status` / `ao session ls`. Users worked around this with an
external watchdog. Close the loop in AO itself.
Changes:
- `kill()` gains an optional `reason` and returns `KillResult`
(`cleaned` / `alreadyTerminated`), with short-circuit paths so
repeated calls on archived sessions are safe no-ops instead of
throwing `SessionNotFoundError`.
- New `LifecycleConfig` (`autoCleanupOnMerge: true` default,
`mergeCleanupIdleGraceMs: 5 min`) so operators can opt out when
they need merged worktrees preserved for inspection.
- `lifecycle-manager` runs `maybeAutoCleanupOnMerge` at the end of
each `checkSession`. Reactions and notifications observe the live
session first; cleanup runs last. If the agent is still `active` /
`waiting_input` / `blocked`, cleanup is deferred and retried on
the next poll until the agent idles or the grace window elapses
(prevents killing an agent mid-task).
- New `CanonicalSessionReason` / `CanonicalRuntimeReason` variants
(`pr_merged`, `auto_cleanup`) so observability distinguishes
automated teardown from manual kills.
Scope is deliberately narrow to `merged`: `done` / `errored` often
need the worktree preserved for debugging; `killed` would self-recurse.
Follows Codex review feedback (conditional pass): scope narrowed,
reactions-before-cleanup ordering, idleness safety gate, real
idempotency guards, config opt-in.
6 new unit tests cover: idle agent cleanup, active agent deferral,
grace-window force-cleanup, config opt-out, terminated/killed no
self-recursion, kill() failure retry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(core): clean up lifecycle config access per review
Address review comment on PR #1311. The `config.lifecycle` field is
typed optional but always populated by Zod — the old guard chain
(`if (lifecycleConfig && lifecycleConfig.autoCleanupOnMerge === false)`)
obscured that duality. Destructure with defaults at the call site so
the contract is visible in one place, and document why the field stays
optional (hand-constructed test configs) on the interface.
Matches the existing `power?: PowerConfig` pattern — keeps churn to
zero across 60 test config literals while removing the ambiguous guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(core): surface auto-cleanup-on-merge in config, docs, and UI
Followup to DX audit on #1311. The lifecycle cleanup behavior was
operational but invisible — config key only in TS types, no changeset
for downstream consumers, missing observability spec, and a dashboard
summary that actively contradicted the new default-on behavior.
- agent-orchestrator.yaml.example: add commented `lifecycle:` block
with both keys so operators discover the knob in the primary
config reference.
- .changeset/auto-cleanup-on-merge.md: minor bump for @aoagents/ao-core
with migration note (default-on, opt-out via config).
- docs/observability.md: document the three new lifecycle_poll
operations (merge_cleanup.completed / deferred / failed) so
dashboard/alert authors have a spec.
- packages/web/src/lib/serialize.ts: replace stale summary
"PR merged; worker is still available for a keep-or-kill decision"
with "PR merged; worker session will be cleaned up automatically".
The old copy is wrong under default-on auto-cleanup.
Deferred to follow-up issues:
- Health surface degradation on repeated cleanup failure (the
operator-facing gap Codex flagged — failures emit a metric but
don't downgrade /api/observability health).
- Dashboard "cleaning up in Nm" indicator for the deferred state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(core,web): address PR review feedback for auto-cleanup on merge
- serialize.ts: only claim "will be cleaned up automatically" when
mergedPendingCleanupSince marker is present; otherwise show neutral
"PR merged". Avoids lying when autoCleanupOnMerge is opted out.
- lifecycle-manager.ts: use ACTIVITY_STATE constants instead of
hardcoded strings, matching the existing SESSION_STATUS.MERGED usage.
- config.ts: keep mergeCleanupIdleGraceMs=0 as a valid escape hatch
(immediate cleanup), but reject 1..9999 with a units-mistake error
so users typing `5` (intending seconds) get a clear message.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add end-to-end observability across core, web, and terminal
Instrument lifecycle/API/websocket flows with correlation-aware metrics and operator health surfaces so the system can self-diagnose and escalate failures.
* fix: realign session restore set and unblock claim PR typecheck
* fix(web): restore project-filtered sessions route after main merge
* fix(core): remove unused orchestrator import to unblock lint
* fix(core): always record lifecycle poll failures and remove dead review branches
* fix(web): harden websocket metrics and reuse SSE observers
* fix(web): preserve primary session API errors when services bootstrap fails
* fix(web): use full orchestrator config type in SSE observer helper
* fix(web): restore project-scoped SSE and guard observability error paths
* fix(web): address remaining Bugbot review gaps
* fix(web): align SSE project attribution and share session project resolver
* fix(web): require request arg for SSE route and align tests
* fix(web): record websocket error disconnects as failures
* fix(web): use path alias for session project resolver import
* fix(web): include active connection count in disconnect metrics