Commit Graph

12 Commits

Author SHA1 Message Date
Ashish Huddar faaddb15df
feat(core): auto-terminate sessions on PR merge (#1309) (#1311)
* 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>
2026-04-20 12:56:07 +05:30
harshitsinghbhandari 5e1414e7ad feat(power): prevent macOS idle sleep while AO is running (#1072)
Add idle sleep prevention on macOS using `caffeinate -i -w <pid>` to keep
the Mac awake while AO is running, enabling remote dashboard access (e.g.,
via Tailscale) without the machine going to sleep.

Changes:
- Add `preventIdleSleep()` helper in packages/cli/src/lib/prevent-sleep.ts
- Add `power.preventIdleSleep` config option (defaults to true on macOS)
- Wire sleep prevention into `ao start` command
- Add tests for the helper function and config validation
- Document the feature in README and example config

Note: Lid-close sleep is enforced by macOS hardware and cannot be prevented
by userspace assertions. Use clamshell mode for that use case.

Closes #1072

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-11 00:11:45 +05:30
Dhruv Sharma 7445e134c4 add plugin spec and update docs 2026-03-31 04:02:25 +05:30
Dhruv Sharma 570f3b588f fix: address CodeRabbit review comments
- Use consistent regex for OPENCLAW_HOOKS_TOKEN detection and replacement
  in shell profile (prevents silent no-ops for non-exported lines)
- Broaden token detection regex to match lines with/without export prefix
  and leading whitespace
- Fix misleading --non-interactive help text (token is auto-generated)
- Fix doctor.ts catch block to say "Notifier checks failed" not "load config"
- Fix 204 mock in Discord notifier test (ok: true, not ok: false)
- Fix weak no-duplicate assertion in setup.test.ts (actually count list items)
- Add discord to notifier options comment in config-instruction.ts
- URL-encode threadId in Discord webhook URL construction
- Add aoCwd to required[] in openclaw.plugin.json configSchema
- Add HTTPS recommendation comment to agent-orchestrator.yaml.example
- Add rimraf for cross-platform clean script in notifier-discord
- Rename "Recommended Settings" to "Required: Disable Conflicting Built-in Skills"
  with explicit warning in docs
- Add /ao setup post-setup reminder to manually disable coding-agent skill
- Fix misleading README non-interactive example wording

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 21:58:19 +05:30
Dhruv Sharma 36d354e0c2 feat: OpenClaw plugin, AO skill, Discord notifier, and setup wizard
Adds bidirectional integration between Agent Orchestrator and OpenClaw,
enabling AI bots on Discord/Telegram/WhatsApp to manage coding agent
fleets through natural conversation.

OpenClaw Plugin (openclaw-plugin/):
- 14 AI tools: ao_spawn, ao_issues, ao_sessions, ao_status, ao_batch_spawn,
  ao_send, ao_kill, ao_session_restore, ao_session_cleanup,
  ao_session_claim_pr, ao_review_check, ao_verify, ao_doctor, ao_session_list
- Hooks: message_received + before_prompt_build for live data injection
- /ao slash command with subcommands (sessions, spawn, issues, doctor, setup)
- Background services: health monitor + issue board scanner
- Security: execFileSync with arg arrays (no shell injection)

AO Skill (skills/agent-orchestrator/):
- Natural language intent → AO tool mapping
- Decision heuristic: quick fix → direct, multi-issue → AO
- Designed for ClawHub publishing (blocked on ClawHub server bug)

CLI: ao setup openclaw (packages/cli/src/commands/setup.ts):
- Interactive wizard + non-interactive mode
- Auto-detects OpenClaw gateway on localhost
- Auto-generates secure token
- Writes both configs (agent-orchestrator.yaml + openclaw.json)
- Appends OPENCLAW_HOOKS_TOKEN to shell profile
- Validates connection end-to-end

CLI: ao doctor notifier checks:
- Probes OpenClaw gateway reachability + token validity
- --test-notify flag sends test event through all configured notifiers
- Failure count propagated to exit code

Discord Notifier (packages/plugins/notifier-discord/):
- Rich webhook embeds with colors, fields, action links
- Retry with exponential backoff + Discord Retry-After header
- Request timeouts, embed truncation, webhook URL validation
- thread_id via URL query string (Discord API requirement)

OpenClaw Notifier improvements:
- Added request timeouts via AbortController
- Improved README

Reviewed through 5 rounds of Codex code review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:52:28 +05:30
Harsh Batheja 3deea32bda
feat: support distinct worker and orchestrator agents (#439)
* feat: add role-specific agent resolution

* test: cover role-specific agent precedence

* docs: show role-specific agent config

* fix: preserve shared agent config fallbacks

* fix: avoid role-config permission clobbering

* fix: keep orchestrator launches permissionless

* fix: remove stale lifecycle imports
2026-03-12 20:58:55 +05:30
Harsh Batheja 2064595633
feat: add SCM webhook lifecycle triggers (#394)
* feat(core): add scm webhook contract

Defines a provider-agnostic SCM webhook contract in core types and
config so SCM plugins can verify and normalize inbound webhook events
without reshaping project config later.

* feat(scm): trigger lifecycle checks from github webhooks

Adds GitHub webhook verification and event parsing, exposes a web
webhook endpoint, and routes matching PR/branch events through the
existing lifecycle manager so CI and review reactions update immediately.

* fix(scm): verify github signatures with raw webhook bytes

Preserves the original webhook bytes alongside the decoded payload so
GitHub HMAC verification uses the exact request body while the route
continues to drive lifecycle checks through the existing manager.

* fix(web): wire scm webhook route into main branch services

Restores the main-branch service and route-test wiring while keeping the
new webhook route coverage and scoped lifecycle helper in place.

* fix(webhooks): use singleton lifecycle manager and fail closed on scm API errors

Reuses the existing services lifecycle manager for webhook-triggered checks
so reactions and state transitions don't replay from a fresh instance, and
restores fail-closed behavior for GitHub review comment fetch failures.

* fix(webhooks): tighten project matching and restore scm compatibility methods

Prevents repository-less webhook events from matching all projects, restores
GitHub SCM PR utility methods and CI status rollup fallback, and adds tests
covering the compatibility paths and safer project matching behavior.

* fix(webhooks): pre-check content length and continue on parse errors

Adds an early content-length guard against configured maxBodyBytes before
reading the body and changes candidate parse failures to fail-forward so
one malformed payload path does not abort other valid candidate handling.

* fix(scm-github): parse review-comment timestamps from comment payload

Use comment.updated_at/created_at for pull_request_review_comment webhook
timestamps so normalized events retain temporal data for comment events.

* fix(webhooks): apply early size guard only when all candidates are bounded

Uses the broadest candidate limit for pre-read content-length checks and
skips early rejection when any matching project has no configured limit,
while retaining per-candidate verification limits.

* fix(webhooks): normalize repo matching and skip terminal sessions

Match webhook repository names case-insensitively against configured project
repos and avoid lifecycle checks for terminal sessions when resolving
webhook-affected sessions.

* fix(webhooks): fail forward when lifecycle checks throw

* fix(scm-github): parse push webhook branch and sha

* refactor(scm-github): dedupe cli exec helper wrappers

* fix(scm-github): tighten exec helper type and comment timestamps

* fix(scm-github): prefer head_commit timestamp for push events

* fix(webhooks): tighten repository parsing and helper visibility

* fix(scm-github): ignore non-head refs for push branch

* chore: trigger bugbot rerun

* feat(scm-gitlab): add webhook verification and event parsing

* fix(webhooks): share parser utils and handle check_run branch

* fix(webhooks): ignore gitlab tag refs in ci branch mapping

* fix(scm-gitlab): harden token and tag ref handling

* chore(scm-gitlab): update webhook helpers around bugbot threads
2026-03-11 10:34:41 +05:30
Harsh Batheja 4e2144d99e
feat: OpenCode session lifecycle and CLI controls (#315)
* feat: refine OpenCode session reuse strategy and cleanup

* fix: harden OpenCode session selection and lint errors

* refactor: centralize OpenCode reuse resolution flow

* fix: return 404 for missing session in message route

* feat: replace force remap with terminal reload control

* fix: protect project path from session kill cleanup

* fix: preserve fullscreen alignment without reload action

* fix: harden OpenCode session id handling and title reuse selection

* fix: show OpenCode reload control and remap before restart

* fix: preserve title-only OpenCode reuse with fallback mapping persistence

* fix: resolve remaining PR315 Bugbot findings

* fix: keep OpenCode discovery title-based without timestamp sorting

* fix: avoid enrichment race fallout in session listing

* fix: guard OpenCode discovery parse with array check

* fix: stabilize Linear comment integration check

* fix: harden OpenCode discovery and prompt option flow

* ux: clarify OpenCode terminal restart action

* fix: remap OpenCode session fresh on each restart

* fix: validate remap session ids before reuse

* fix: clean archived metadata only after purge

* docs: align OpenCode remap selection with title-based behavior

* fix: harden opencode cleanup and ignore local sisyphus state

* test: add timeout cleanup coverage for session enrichment

* fix: harden Linear integration helper against transient non-JSON errors

* fix: make linear integration assertions resilient to eventual consistency

* fix: remove unused fs import after rebase

* fix: address remaining Bugbot blockers for opencode session handling

* fix: avoid stale metadata overwrite during restore post-launch

* fix: forward subagent in orchestrator flows and defer reuse lookup

* fix: apply configured subagent fallback for session spawn

* fix: scope archived cleanup by project and delay archive restore write

* fix: normalize orchestrator strategy aliases in start display logic

* fix: centralize orchestrator strategy normalization in core

* fix: derive orchestrator reuse display from spawn result

* fix: keep cleanup results consistent across project-id collisions

* fix: namespace cleanup results when session IDs collide

* fix: harden GitHub issue stateReason fallback

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: avoid false failing CI state mapping

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: add tmux command timeouts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: bound session API enrichment latency

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: repair scm-github merge resolution

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: repair lifecycle-manager test merge

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: delay archive metadata recreation until restore passes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test: restore claim-pr session mocks

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: address review findings for OpenCode lifecycle PR

- Fix stripControlChars to preserve newlines for reload commands
- Add SessionNotFoundError and use instanceof checks in API routes
- Document orchestratorSessionStrategy in YAML example
- Validate existingSessionId with asValidOpenCodeSessionId()
- Extract inline Node script to buildSessionLookupScript helper
- Create OpenCodeSessionManager interface for remap capability
- Create OpenCodeAgentConfig type for agent-specific config
- Change default orchestratorSessionStrategy from delete to reuse

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

* fix: guard reused session display without metadata

* chore: add agent config files to .gitignore

Agent configuration files (CLAUDE.md, AGENTS.md, IMPROVEMENTS.md, etc.) are personal and project-specific. They should not be committed to the repository.

Changes:
- Remove CLAUDE.md from git tracking
- Add agent config files to .gitignore
- Create .gitignore-template for reference

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

* chore: update gitignore for agent config folder structure

Reorganized agent configuration files:
- CLAUDE.md and AGENTS.md stay in root (agents read them there)
- Tracking files move to .opencode/ (IMPROVEMENTS.md, etc.)
- Optional Claude files in .claude/

Updated .gitignore to ignore folders instead of individual files.

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

* fix: tighten opencode remap and session discovery safeguards

* fix: address Bugbot findings in session manager

* fix: scope opencode discovery to opencode sessions

* fix: restore concurrent listing and strict permission literals

* fix: throw SessionNotFoundError, parallelize list enrichment, fix permissions type

- Session manager now throws SessionNotFoundError instead of plain Error
  for missing sessions, so web API routes correctly return 404 (not 500)
- Parallelize session enrichment in list() — was sequential, causing O(N)
  latency for N sessions with subprocess enrichment
- Fix AgentLaunchConfig.permissions type to accept legacy "skip" value
  (AgentPermissionInput instead of AgentPermissionMode)
- Add happy-path and validation tests for /api/sessions/:id/message route
- Update all test mocks to use SessionNotFoundError

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

* fix: route ao send through session manager

* feat: add purge option to orchestrator stop

* fix: register opencode agent in web services

* test: align web API missing-session coverage

* fix: match notifier config by plugin name

* refactor: dedupe session lookup and tmux buffer send flow

* test: update send lifecycle wait expectation

* fix: harden send routing and cleanup purge controls

---------

Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Prateek <karnalprateek@gmail.com>
2026-03-08 09:55:44 +05:30
prateek ef40c529f8
fix: handle permissions=skip correctly in codex plugin (#337)
* fix: handle permissions=skip correctly in codex plugin

* refactor: rename permission mode to permissionless and align agent mappings

---------

Co-authored-by: Jayesh Sharma <wjayesh@outlook.com>
2026-03-07 20:27:01 +05:30
prateek 520010d5a2
feat: configurable terminal server ports for multi-dashboard support (#113)
* feat: make terminal server ports configurable to fix multi-dashboard EADDRINUSE

When multiple ao dashboards run simultaneously (e.g., ao on port 3000,
integrator on port 3002), both try to start terminal WebSocket servers
on hardcoded ports 3001/3003, causing EADDRINUSE. Add terminalPort and
directTerminalPort to config schema so each instance can use unique ports.

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

* fix: use optional() instead of default() for terminal port schema

Zod .default() always fills in the value, making config.terminalPort
never undefined and the env var fallback in buildDashboardEnv dead code.
Switch to .optional() so the priority chain works correctly:
config value > TERMINAL_PORT env var > hardcoded default.

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

* fix: move terminal server defaults from 3001/3003 to 14800/14801

The 3000-3009 range is the most contested in dev tooling (Next.js
auto-increments, BrowserSync, Grafana, Rails, Express all default to
3000+). Port 14800-14899 has zero IANA registrations, zero known dev
tool conflicts, and is safely below OS ephemeral ranges.

Updated all hardcoded fallbacks, .env.local.example, docker-compose
port mappings, and documentation.

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

* feat: auto-detect available terminal ports for zero-config multi-dashboard

When no terminal ports are configured (no config, no env vars),
buildDashboardEnv now probes for an available port pair starting at
14800. The second `ao start` automatically gets 14802/14803 (or the
next free pair), eliminating EADDRINUSE without any user configuration.

Port detection scans in steps of 2 to keep the pair consecutive.
Explicit config/env values bypass auto-detection entirely.

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

* fix: update onboarding test to use new default terminal port (14801)

The onboarding integration test had port 3003 hardcoded for the
WebSocket health check. Updated to read from DIRECT_TERMINAL_PORT
env var with 14801 as the default, matching the new port defaults.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:00:19 +05:30
prateek 90111da18d
feat: layered prompt system for agent sessions (#27)
* feat: implement layered prompt system for agent sessions

Replace hardcoded spawn prompts with a 3-layer composition system:
- Layer 1: BASE_AGENT_PROMPT constant with session lifecycle, git workflow, PR handling
- Layer 2: Config-derived context (project, repo, tracker, issue details via generatePrompt())
- Layer 3: User-customizable rules via agentRules (inline) and agentRulesFile (path)

The session-manager path fetches issue context from the tracker plugin and passes
the composed prompt via AgentLaunchConfig.prompt. The CLI spawn path delivers it
via tmux send-keys to keep agents interactive for follow-up messages.

Returns null when nothing to compose (no issue, no rules), preserving backward
compatibility for bare launches.

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

* fix: use tmuxSendKeys for multi-line prompt delivery in CLI spawn

The buildPrompt() output contains newlines which tmux send-keys -l treats
as Enter keypresses, splitting the prompt into separate submissions. Use
the core tmuxSendKeys() helper which handles multi-line text via
load-buffer/paste-buffer.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: update spawn test to verify tmuxSendKeys usage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 20:07:13 +05:30
Prateek 5058c409d5 feat: scaffold TypeScript monorepo with all plugin interfaces
Phase 0 complete. Establishes:
- pnpm workspace with 18 packages (core, cli, web, 15 plugins)
- Complete type definitions in packages/core/src/types.ts defining
  all 8 plugin slot interfaces (Runtime, Agent, Workspace, Tracker,
  SCM, Notifier, Terminal) + core service interfaces
- YAML config loader with Zod validation and sensible defaults
- Plugin registry with built-in discovery
- CLAUDE.md with conventions for spawned agents

All agents can now branch from main and implement their assigned
packages against the interfaces defined in types.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 17:02:42 +05:30