Update existing test to verify the new --no-dashboard auto-select
behavior and add a separate test for dashboard-enabled selection
message.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: support multiple concurrent orchestrators with isolated worktrees
- Each orchestrator session gets a numbered ID ({prefix}-orchestrator-N)
and an isolated git worktree, replacing the single shared orchestrator
- reserveNextOrchestratorIdentity atomically reserves the next available
number and detects conflicting project prefix configurations early
- isOrchestratorSession / isOrchestratorSessionName accept optional
allSessionPrefixes for cross-project false-positive prevention
- getProjectPause and resolveGlobalPause track the longest active pause
across all concurrent orchestrators instead of returning the first found
- cleanupWorktreeAndMetadata helper used consistently on all failure paths
including post-launch; system prompt file included in cleanup
- pollBacklog, status.ts, ProjectSidebar, sessions page, global-pause,
project-utils, serialize, and sessions API route all pass
allSessionPrefixes to isOrchestratorSession
- Web sessions/[id]/page.tsx fetches project prefix map and passes it
through prefixByProjectRef to avoid stale closure in fetchProjectSessions
* fix: seed sseAttentionLevels from fresh sessions on full refresh
When scheduleRefresh dispatches a reset action, derive sseAttentionLevels
from the freshly fetched sessions using getAttentionLevel. This clears
phantom entries for removed sessions and seeds correct levels for new
sessions, preventing stale favicon color and attention counts until the
next SSE snapshot.
* fix: merge duplicate @/lib/types imports into single import statement
Combining the separate import type and value import from @/lib/types
into one import to satisfy the no-duplicate-imports lint rule.
The --interval flag is only relevant when --watch is enabled.
Previously, passing `--interval 0` without `--watch` threw an error,
whereas `--interval 5` was silently ignored. Now, invalid intervals
are ignored when `--watch` is omitted, ensuring consistent behavior.
Also adds a test case to prevent regression.
Cover the following previously-uncovered lines in status.ts:
- 65-69: maybeClearScreen() isTTY branch
- 255-256: maybeClearScreen() call on watch refresh (refreshing=true)
- 262-266: loadConfig() throw → fallback to tmux session discovery
- 269-271: unknown --project flag → process.exit(1)
- 388, 390-396, 398: tracker block (registry.get, listIssues, error catch)
- 402-405: unverified issues warning banner
- 424-436: setInterval guard skips render when already in progress
Also hoists mockGetPluginRegistry as a vi.fn() so individual tests
can override the plugin registry returned by getPluginRegistry.
- Remove unused `resolve` import from plugin.ts
- Use strict equality (`!== null && !== undefined`) instead of `!=` in start.ts
- Attach `cause` to re-thrown errors in plugin install/update rollback
- Fix forbidden `import()` type annotation in plugin test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Export runSetupAction from setup.ts (fixes TS2459 build error)
- Use rollbackErr as cause in re-thrown errors (preserve-caught-error)
- Remove import() type annotation from test mock (consistent-type-imports)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused `resolve` import from node:path
- Add `{ cause: err }` to re-thrown errors (preserve-caught-error rule)
- Fix import() type annotation in plugin test (consistent-type-imports)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
Fixes all 12 issues identified in the Cursor Bugbot review:
#4 – Setup tests now assert non-interactive mode skips validation and
auto-generates tokens; removed incorrect validateToken call expectations.
#5 – Replaced module-level mutable `tsFailures` in doctor.ts with a
`makeFailCounter()` closure that is local to each command invocation,
eliminating potential state bleed between invocations.
#6 – Both `notify`, `notifyWithActions`, and `post` in notifier-discord
now consistently guard on `effectiveUrl` (which includes thread_id),
not on the raw `webhookUrl`. Removes non-null assertions.
#7/#12 – setup.ts now writes `${OPENCLAW_HOOKS_TOKEN}` as the token
value in the YAML config instead of the raw token, so credentials are
never committed to version control. setup.test.ts already expected this
placeholder; the test was correct, the code was not.
#8 – `ao_batch_spawn` follow-up setTimeout handles are tracked in
`batchSpawnFollowUpTimeouts[]` and cleared when the health service stops,
preventing timer leaks after plugin shutdown.
#11 – Discord 429 Retry-After handling no longer double-delays: a
`skipNextBackoff` flag is set after waiting for Retry-After so the
following iteration skips the standard exponential backoff.
Also removes the unused `yamlStringify` import from setup.ts.
Issues #1/#2/#3/#9/#10 were already correctly addressed in previous commits.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Refactor ensureTmux() to use askYesNo() + tryInstallWithAttempts()
matching the existing ensureGit() pattern — no silent sudo
- Add log line before each auto-install attempt in preflight.checkTmux()
- Update config warning: "will prompt to install at startup"
- Update design doc to reflect interactive consent flow
- Include prior uncommitted changes: interactive install prompts for
git/gh/agent-runtime, ensureGit(), canPromptForInstall(), askYesNo()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Store board scanner initial setTimeout and clear on stop
- Fix deliver assertion in openclaw-probe test (true -> false)
- Fix Discord thread_id test to check URL query param, not body
- Use yaml Document API to preserve comments in config writes
- Fix setup tests to match actual nonInteractiveSetup behavior
(skips validation, auto-generates missing tokens)
- checkTmux() now attempts auto-install (brew/apt/dnf) before erroring
- Config generation uses runtime: "process" when tmux unavailable
- start.ts tries auto-install during config creation, falls back gracefully
- Fix restart bookkeeping in start-all.ts: slot-based tracking prevents
duplicate children entries that broke cleanup countdown
- Updated design doc reflecting all fixes and review feedback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix shell injection in writeShellExport: use single-quoted token with
escaped embedded single quotes instead of double-quoted interpolation
- Fix board scanner initial setTimeout not cleared on stop: store the
timeout handle and clear it in the stop handler
- Fix openclaw-probe test asserting deliver:true when code sends false
- Fix Discord notifier thread_id test to check URL query param instead
of body, and remove redundant thread_id from post() body payload
- Remove redundant project-existence check after autoDetectProject
- Wrap SIGTERM in try/catch for restart flow (dead process case)
- Remove unused setCallerContext export from caller-context.ts
- Fix unused vi import lint error in init.test.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add vi.mock() for running-state, caller-context, detect-env,
detect-agent, and project-detection modules imported by start.ts
- Without these mocks, vitest threads never exit (open handles)
- Remove init.test.ts test that triggered dynamic import of start.js
- Relax waitForPortAndOpen assertion (port resolution depends on
full dashboard startup which is mocked out)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When users run `ao spawn <project> <issue>`, show a yellow warning
explaining the new syntax (`ao spawn <issue>`) instead of Commander's
raw "too many arguments" error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- spawn now takes only `[issue]` — project is always auto-detected
- batch-spawn now takes only `<issues...>` — no project prefix
- Commander rejects extra positional args automatically
- Update orchestrator prompt to remove projectId from spawn example
- Rewrite spawn tests to use auto-detected project (single project in config)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite init.test.ts to match simplified deprecated init command
(old tests referenced removed --output, --auto, --smart flags)
- Add eslint-disable for consistent-type-imports in direct-terminal-ws.ts
(node-pty is optional, must use import() type annotations)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep orchestrator control sessions visible in CLI status without counting them as worker sessions, and preserve explicit roles in JSON output for multi-project tooling.
* fix(core): enforce single-owner PR claim consolidation
* refactor: remove dead `takeover` option from claimPR
Since PR consolidation is now automatic (single-owner enforcement),
the `--takeover` flag became dead code. Users passing it got no
error but it had zero effect.
This commit:
- Removes takeover from ClaimPROptions interface
- Removes --takeover flag from spawn and session CLI commands
- Updates orchestrator-prompt.ts examples
- Updates tests to reflect automatic consolidation
Tests: ao-core 403 passing, ao-cli 189 passing (spawn+session)
* fix: remove stale --takeover from Quick Start example
Remove remaining --takeover reference in orchestrator prompt Quick Start section.
* feat(core): document and test asymmetric PR ownership model
- Add JSDoc to claimPR documenting RULE A (exclusive PR->Agent) and
RULE B (Agent->Many PRs) ownership contract
- Add tests for:
- Same session claiming multiple PRs sequentially (RULE B)
- Idempotent re-claim by same owner
- Stale/dead prior owner handoff
- Exclusive PR ownership enforcement (RULE A)
139 tests passing
---------
Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
* fix: pause workers on model limits and stabilize session visibility
Detect model limit exhaustion, pause project worker operations until reset, and expose pause state in dashboard/API. Also harden killed-session cleanup and SSE session reconciliation so sessions do not appear ghost-active or disappear until reload.
* fix: satisfy lint in rate-limit pause probe
* fix: address bugbot feedback for pause handling
* fix(lifecycle): prevent infinite re-pause loop for duration-based rate limits
Duration-based rate limits (e.g., 'usage limit reached for N hours') were
causing infinite re-pause loops because they always calculate reset time as
Date.now() + duration, which extends the pause on every poll cycle if the
message remains in terminal output.
Now checks for existing active pause before setting a new one:
- Skips override if same session already has active pause
- Preserves longer pauses from other sessions
Fixes infinite loop described in PR #367 review comment.
* fix(core): export global pause constants to prevent duplication
Export GLOBAL_PAUSE_*_KEY constants and parsePauseUntil utility from
@composio/ao-core so web package can import them instead of hardcoding.
This prevents silent breakage if key values ever change - now there's
a single source of truth.
Addresses review comment on PR #367.
* fix(web): globalPause as first-class state in SSE event flow
globalPause is now part of the same reducer/event flow as sessions,
not derived from provider-specific output text in the UI.
Changes:
- useSessionEvents: manage globalPause alongside sessions in reducer state
- Dashboard: consume globalPause from hook instead of SSR-only prop
- types: re-export GlobalPauseState from shared lib (provider-agnostic contract)
- Tests: 15 new tests proving banner appears/disappears from state updates
alone, regardless of agent model/plugin (Claude Code, OpenCode, Codex)
Design requirements satisfied:
- First-class state in same reducer/event flow as sessions
- Key names sourced from shared core contract via export/import
- Provider-neutral: no Anthropic/OpenAI string coupling in control logic
- State-driven: banner visibility from reducer state updates via SSE
Addresses Bugbot finding: Dashboard pause banner never updates after
initial render (eba24a0b-9e4c-47e3-91c9-7d10be01e3cf)
* fix: remove unused import in test file
* fix(cli): consistent purge default for session kill and stop commands
The kill method's default changed from opt-in (=== true) to opt-out (!== false).
Both session kill and stop commands now use the same logic:
purgeOpenCode = opts.purgeSession === true ? true : opts.keepSession !== true
This ensures consistent behavior across all kill paths.
Adds --keep-session flag to both commands.
Adds regression tests for provider-agnostic behavior verified.
Addresses Bugbot finding: orchestrator start cleanup inconsistent
with new purge default (2bc2535f-b2d1-4f64-96d6-150f97ed8564)