## Approach
The orchestrator polling loop previously made individual API calls for each PR's
state, CI status, and review decision - 3 separate calls per PR per poll.
With multiple PRs being monitored, this quickly exhausted GitHub's 5,000-point
hourly rate limit.
This PR implements GraphQL batching using aliases, which allows fetching data
for up to 25 PRs in a single GraphQL query. Additionally, a 2-Guard ETag
strategy is used to skip queries entirely when nothing has changed.
## Implementation
### GraphQL Batching
- `generateBatchQuery()` creates a single GraphQL query with unique aliases (pr0, pr1, pr2...)
- Each PR gets the same set of fields: state, CI status, review decision, mergeability
- Uses inline fragments for union types (CheckRun/StatusContext)
- Variable types: String! for owner/repo, Int! for PR numbers
### 2-Guard ETag Strategy
Before running expensive GraphQL queries, two lightweight REST ETag checks detect if
anything changed:
**Guard 1 (PR List ETag):**
- Checks `/repos/{owner}/{repo}/pulls` with If-None-Match header
- Returns 304 if no changes → skips GraphQL (0 points)
- Detects: New commits, title/body edits, labels, reviews, state changes
**Guard 2 (Commit Status ETag):**
- Checks `/repos/{owner}/{repo}/commits/{sha}/status` per cached PR
- Returns 304 if no changes → skips GraphQL (0 points)
- Detects: CI status transitions (failing → passing, passing → failing, etc.)
### Caching
- LRU caches for PR metadata (max 200 entries), ETags (100/500 entries)
- Cache misses trigger individual API fallback via lifecycle-manager
- No placeholder caching on errors - allows proper fallback behavior
## Impact
- **API reduction:** ~88% fewer REST calls (216 vs 1,800 calls/hour for 5 PRs)
- **GraphQL efficiency:** Batch query fetches 25 PRs for ~40 points vs ~400 for individual calls
- **Polling interval:** Still 30s, but most polls return cached data (0 cost)
- **Fallback:** Individual SCM calls still work for edge cases (permissions, cache misses)
## Testing
- Unit tests for query generation and parsing helpers
- Integration tests for real GraphQL API calls (skipped by default)
- Covers batch failures, partial success, empty arrays, edge cases
Without this, the catch block's `err === lastError` identity check fails and
the error is absorbed into the retry loop instead of propagating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When rateLimitRetries was exhausted, a 429 fell through to the normal
error path where isRetryableHttpStatus(429)=true caused it to also
consume the error retry budget — giving 2×retries total attempts.
Now throws immediately when rate-limit budget is exhausted so the two
counters remain independent: up to `retries` rate-limit waits, then
up to `retries+1` attempts for genuine errors (5xx), never compounding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, a 429 with no Retry-After header fell through to response.text()
with no explicit wait, relying on the generic exponential backoff. Now all 429
responses are handled uniformly via rateLimitRetries: use the Retry-After
value when present, otherwise fall back to retryDelayMs as the minimum wait.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each 429 with a Retry-After header was consuming one slot from the error
retry budget. With retries=3, three sustained rate-limits would exhaust
retries before any real error retry could fire.
Track rate-limit waits with a separate rateLimitRetries counter (capped at
retries to prevent infinite loops) and decrement attempt before continue so
the for-loop increment cancels out and the error retry budget is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix YAML project key detection for inline comments: trimmed.endsWith(":")
fails on valid YAML like "my-app: # description" — strip inline comments
before the check so getConfiguredRepos works with commented project keys
- Fix Discord retry backoff skipping after 429+5xx sequence: the skipNextBackoff
flag caused exponential backoff to be skipped on the attempt AFTER a 429,
even when that attempt failed with an unrelated 5xx error. Removed the flag
entirely — continue already skips backoff naturally for the 429 iteration
Co-Authored-By: Claude Sonnet 4.6 <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>
- 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
- Fix autoDetectProject path matching: expand ~ before comparing project
paths to cwd, so `path: ~/my-repo` matches `/Users/user/my-repo`
- Fix addProjectToConfig: detect duplicate directory names and auto-suffix
instead of silently overwriting existing project entries
- Fix agent detect() in all 4 plugins: replace `which` (Unix-only) with
direct `--version` invocation for cross-platform compatibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change != null to !== undefined && !== null in caller-context.ts and
session-manager.ts (3 locations) to satisfy eqeqeq lint rule
- Add displayName field to all 4 agent plugin test manifest assertions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace require() with execFileSync in all 4 agent plugin detect()
functions — fixes ReferenceError in ESM-only environments
- Remove non-existent -p flag from orchestrator prompt spawn/batch-spawn
docs — prevents orchestrator agent from generating broken commands
- Return actual port from runStartup() and pass to register() — fixes
running.json storing wrong port when auto-escalation picks a free port
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduces onboarding to `npm install -g @composio/ao && ao start`.
- Absorb init and add-project logic into `ao start` with auto-config
creation, environment detection, and project type detection
- Add single-instance tracking via running.json with already-running
interactive menu (human) and info+exit (agent)
- Add caller context detection (human/orchestrator/agent) via TTY and
AO_CALLER_TYPE env var
- Add plugin-based agent runtime detection — no hardcoded binary paths,
each plugin exports detect() and displayName
- Simplify `ao spawn` to just `ao spawn <issue>` with auto-detected
project (backward compat: `ao spawn <project> <issue>` still works)
- Set AO_CALLER_TYPE, AO_PROJECT_ID, AO_CONFIG_PATH, AO_PORT env vars
on all spawned sessions (worker, orchestrator, restore)
- Add `ao config-help` subcommand for config schema reference
- Deprecate `ao init` to thin wrapper, fully remove `ao add-project`
- Register/unregister in running.json on start/stop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updates the cd-prefix stripping regex to require a space before the
delimiter (&& or ;). This allows paths containing & or ; characters
to be matched correctly while still finding the command separator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store the regex pattern in a variable and remove confusing single quotes
from the character class. The pattern now uses [^&;]* to explicitly
exclude & and ; without mixing shell quoting into the regex pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(lifecycle): implement stuck detection using agent-stuck threshold
The agent-stuck reaction config supported a threshold field (e.g.
"10m"), but determineStatus() never returned "stuck" — there was no
code path that consumed the threshold or transitioned sessions based
on idle time. Sessions would stay parked at pr_open/working forever
even when the agent had been idle for hours.
Added idle-time check in determineStatus(): when getActivityState()
reports "idle" or "blocked" with a timestamp, compare the idle
duration against the agent-stuck.threshold config. If exceeded,
return "stuck" so the reaction system can fire notifications.
Also removed the priority !== "info" guard on transition
notifications, so all priority levels (including info) are routed
through notificationRouting. This lets the config control which
notifiers receive each priority level, rather than silently dropping
info-level transition events.
* fix(lifecycle): add post-PR stuck detection as safety net
The original stuck check in step 2 (before PR checks) can be bypassed
when getActivityState() returns null (session file not found, cache miss,
I/O failure). When this happens, the code falls through to the PR path
which returns 'pr_open' without ever checking idle duration.
Fix: extract isIdleBeyondThreshold() helper and call it in three places:
1. Step 2: before PR checks (fast path, catches most cases)
2. Step 4b: after PR checks return 'pr_open' (safety net)
3. Step 5: after all checks, for agents that finish without a PR
This ensures stuck detection fires even when the JSONL activity detection
fails to return idle state. Sessions can no longer get permanently stuck
at 'pr_open' when the agent has been idle beyond the threshold.
Also removes the debug console.error calls from the previous commit.
* fix(lifecycle): treat reviewDecision 'none' as approved for merge readiness
PRs with no required reviewers never reached 'mergeable' status because
getReviewDecision returned 'none', which was not handled. The lifecycle
poll fell through to 'review_pending' or the default, so merge.ready
never fired and the approved-and-green reaction never triggered.
Also: skip stuck short-circuit when session has an open PR so merge
readiness checks in step 4 can still run. Without this, idle agents
with open PRs get stuck status and never transition to mergeable.
Closes composio#0 (internal fix)
* fix(opencode): classify activity state from session timestamps
* test(lifecycle): cover opencode idle-threshold stuck transition
* fix(lifecycle): preserve global stuck threshold with project overrides
* fix(lifecycle): run PR auto-detection before stuck transitions
---------
Co-authored-by: Harsh <harshb012@gmail.com>
* 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
* feat: wire lifecycle manager, backlog auto-claim, and dashboard overhaul
- Start LifecycleManager in dashboard server (30s polling) so reactions
actually fire: CI failures, review comments, merge conflicts are now
auto-forwarded to agents
- Add backlog auto-claim poller (60s interval) that watches for issues
labeled `agent:backlog` and auto-spawns agent sessions up to max
concurrent limit (5)
- Add tabbed dashboard UI: Board (kanban), Backlog (issue queue), PRs
- Add issue creation form in dashboard — creates GitHub issues with
`agent:backlog` label for immediate agent pickup
- Add API routes: /api/backlog, /api/issues, /api/setup-labels
- Pass notifier config through plugin registry (slack webhook fix)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add task decomposition layer (classify → decompose → recurse)
Adds LLM-driven recursive task decomposition upstream of session spawning.
Complex issues are broken into atomic subtasks before agents start working.
Each agent receives lineage context (where it fits in the hierarchy) and
sibling awareness (what parallel agents are doing).
Core changes:
- New decomposer module (core/src/decomposer.ts) — classify, decompose,
plan tree, lineage formatting, using Claude API
- Extended SessionSpawnConfig with lineage/siblings fields
- Prompt builder Layer 4: decomposition context (hierarchy + siblings)
- ProjectConfig.decomposer config section with Zod validation
- Tracker plugin: added removeLabels support for label management
CLI:
- `ao spawn <project> <issue> --decompose` flag
- `--max-depth <n>` option for decomposition depth
- Spawns multiple sessions with lineage context for composite tasks
Backlog poller:
- Respects project.decomposer.enabled for auto-decomposition
- Posts plan as issue comment when requireApproval=true
- Auto-spawns subtasks with lineage when requireApproval=false
Config example:
projects:
my-app:
decomposer:
enabled: true
maxDepth: 3
requireApproval: true
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add verification gate — issues stay open until human confirms fix
PR merge no longer auto-closes GitHub issues. Instead:
1. On PR merge: issue labeled `merged-unverified`, stays open
2. Human checks staging, then runs `ao verify <issue>` to close
3. Or `ao verify <issue> --fail` to flag verification failure
Changes:
- services.ts: labelIssuesForVerification() replaces closeIssuesForMergedSessions()
- New CLI command: `ao verify` (verify/fail/list modes)
- New API route: GET/POST /api/verify
- Dashboard: new Verify tab with one-click verify/fail buttons
- ao status: shows count of issues awaiting verification
- Idle session detection + auto-nudge reaction
- Use TERMINAL_STATUSES in batch-spawn dedup check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rename decomposerConfig to avoid variable shadowing
Addresses Bugbot medium severity issue where inner variable
shadowed outer from getServices().
* fix: update pnpm-lock.yaml for new @anthropic-ai/sdk dependency
* fix: resolve remaining merge conflicts and syntax errors
- Remove leftover conflict markers in types.ts
- Remove orphaned code in services.ts
- Fix semicolon to comma in config.ts
- Remove unused import in verify.ts
* fix: address final Bugbot issues
- requireApproval path now exits early with continue to prevent
fall-through to in-progress label and session spawned comment
- remove packages/core/package-lock.json (pnpm workspace should only
use root pnpm-lock.yaml)
* fix: idle sessions now transition back to working
When agent resumes activity after being idle, the status correctly
transitions to 'working' instead of remaining stuck in 'idle' state.
* fix(backlog): remove agent:backlog label when claiming issues
When claiming issues from the backlog, the poller now removes the
agent:backlog label in addition to adding agent:in-progress. This
prevents duplicate work if all spawned sessions reach terminal status
and the poller rediscovers the issue.
* fix(test): use Set for TERMINAL_STATUSES mock
The mock for TERMINAL_STATUSES was an array, but the real export is a
ReadonlySet. Changed to use a Set so tests with non-empty sessions won't
crash when calling .has().
* fix(web): resolve backlog/dashboard regressions after branch sync
* fix(web): align dashboard events hook and SSE test mocks
* fix(notifier-openclaw): apply exponential delay from retry index
* fix(integration-tests): align openclaw retry delay expectation
* fix(web): keep dashboard header stats in sync
* fix(openclaw): keep first retry at base delay
---------
Co-authored-by: Agent <agent@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
Co-authored-by: Harsh <harsh@example.com>
* fix: capture exact OpenCode session ID from JSON stream to prevent orphan sessions
- Primary: Extract session_id from opencode run --format json step_start event
- Fallback: Title-based match with newest-first sorting for delayed visibility
- Core: Add atomic reserveSessionId check in spawnOrchestrator to prevent race conditions
- Prevents orphan sessions when multiple spawns use same title or delayed discovery
(cherry picked from commit 42cc0cfa1a)
* fix: capture OpenCode session id from stream output
(cherry picked from commit 42b3bf3ba6)
* fix: respawn orchestrators when stale metadata is left behind
(cherry picked from commit e519628017)
* fix: align respawn guard and opencode test expectation
* fix: remove unused helper from opencode launch path
* fix: remove unused function and align tests with session capture format
- Remove unused buildSessionLookupScript function from opencode agent
- Update integration tests to expect --format json flag in launch commands
- Fix assertions for exec opencode --session wrapper format
* fix: address Bugbot findings on PR #366
- Fix orphaned runtime leak when reuse strategy finds alive runtime but
get() returns null (now destroys the orphaned runtime)
- Restore session ID format validation in fallback script with
isValidId regex check
- Add regression tests for both fixes
* fix: address additional Bugbot findings on PR #366
- Add timestamp helper to fallback sort to avoid NaN from invalid dates
- Add session ID type/format validation to primary capture script
- Add tests for both robustness improvements
* 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
* feat: wire lifecycle manager, backlog auto-claim, and dashboard overhaul
- Start LifecycleManager in dashboard server (30s polling) so reactions
actually fire: CI failures, review comments, merge conflicts are now
auto-forwarded to agents
- Add backlog auto-claim poller (60s interval) that watches for issues
labeled `agent:backlog` and auto-spawns agent sessions up to max
concurrent limit (5)
- Add tabbed dashboard UI: Board (kanban), Backlog (issue queue), PRs
- Add issue creation form in dashboard — creates GitHub issues with
`agent:backlog` label for immediate agent pickup
- Add API routes: /api/backlog, /api/issues, /api/setup-labels
- Pass notifier config through plugin registry (slack webhook fix)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add task decomposition layer (classify → decompose → recurse)
Adds LLM-driven recursive task decomposition upstream of session spawning.
Complex issues are broken into atomic subtasks before agents start working.
Each agent receives lineage context (where it fits in the hierarchy) and
sibling awareness (what parallel agents are doing).
Core changes:
- New decomposer module (core/src/decomposer.ts) — classify, decompose,
plan tree, lineage formatting, using Claude API
- Extended SessionSpawnConfig with lineage/siblings fields
- Prompt builder Layer 4: decomposition context (hierarchy + siblings)
- ProjectConfig.decomposer config section with Zod validation
- Tracker plugin: added removeLabels support for label management
CLI:
- `ao spawn <project> <issue> --decompose` flag
- `--max-depth <n>` option for decomposition depth
- Spawns multiple sessions with lineage context for composite tasks
Backlog poller:
- Respects project.decomposer.enabled for auto-decomposition
- Posts plan as issue comment when requireApproval=true
- Auto-spawns subtasks with lineage when requireApproval=false
Config example:
projects:
my-app:
decomposer:
enabled: true
maxDepth: 3
requireApproval: true
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add verification gate — issues stay open until human confirms fix
PR merge no longer auto-closes GitHub issues. Instead:
1. On PR merge: issue labeled `merged-unverified`, stays open
2. Human checks staging, then runs `ao verify <issue>` to close
3. Or `ao verify <issue> --fail` to flag verification failure
Changes:
- services.ts: labelIssuesForVerification() replaces closeIssuesForMergedSessions()
- New CLI command: `ao verify` (verify/fail/list modes)
- New API route: GET/POST /api/verify
- Dashboard: new Verify tab with one-click verify/fail buttons
- ao status: shows count of issues awaiting verification
- Idle session detection + auto-nudge reaction
- Use TERMINAL_STATUSES in batch-spawn dedup check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rename decomposerConfig to avoid variable shadowing
Addresses Bugbot medium severity issue where inner variable
shadowed outer from getServices().
* fix: update pnpm-lock.yaml for new @anthropic-ai/sdk dependency
* fix: resolve remaining merge conflicts and syntax errors
- Remove leftover conflict markers in types.ts
- Remove orphaned code in services.ts
- Fix semicolon to comma in config.ts
- Remove unused import in verify.ts
* fix: address final Bugbot issues
- requireApproval path now exits early with continue to prevent
fall-through to in-progress label and session spawned comment
- remove packages/core/package-lock.json (pnpm workspace should only
use root pnpm-lock.yaml)
* fix: idle sessions now transition back to working
When agent resumes activity after being idle, the status correctly
transitions to 'working' instead of remaining stuck in 'idle' state.
* fix(backlog): remove agent:backlog label when claiming issues
When claiming issues from the backlog, the poller now removes the
agent:backlog label in addition to adding agent:in-progress. This
prevents duplicate work if all spawned sessions reach terminal status
and the poller rediscovers the issue.
* fix(test): use Set for TERMINAL_STATUSES mock
The mock for TERMINAL_STATUSES was an array, but the real export is a
ReadonlySet. Changed to use a Set so tests with non-empty sessions won't
crash when calling .has().
* fix(web): resolve backlog/dashboard regressions after branch sync
* fix(web): align dashboard events hook and SSE test mocks
* fix(notifier-openclaw): apply exponential delay from retry index
* fix(integration-tests): align openclaw retry delay expectation
* fix(web): keep dashboard header stats in sync
* fix(openclaw): keep first retry at base delay
---------
Co-authored-by: Agent <agent@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
Co-authored-by: Harsh <harsh@example.com>
* fix: capture exact OpenCode session ID from JSON stream to prevent orphan sessions
- Primary: Extract session_id from opencode run --format json step_start event
- Fallback: Title-based match with newest-first sorting for delayed visibility
- Core: Add atomic reserveSessionId check in spawnOrchestrator to prevent race conditions
- Prevents orphan sessions when multiple spawns use same title or delayed discovery
(cherry picked from commit 42cc0cfa1a)
* fix: capture OpenCode session id from stream output
(cherry picked from commit 42b3bf3ba6)
* fix: respawn orchestrators when stale metadata is left behind
(cherry picked from commit e519628017)
* fix: align respawn guard and opencode test expectation
* fix: remove unused helper from opencode launch path
* fix: remove unused function and align tests with session capture format
- Remove unused buildSessionLookupScript function from opencode agent
- Update integration tests to expect --format json flag in launch commands
- Fix assertions for exec opencode --session wrapper format
* fix: address Bugbot findings on PR #366
- Fix orphaned runtime leak when reuse strategy finds alive runtime but
get() returns null (now destroys the orphaned runtime)
- Restore session ID format validation in fallback script with
isValidId regex check
- Add regression tests for both fixes
* fix: address additional Bugbot findings on PR #366
- Add timestamp helper to fallback sort to avoid NaN from invalid dates
- Add session ID type/format validation to primary capture script
- Add tests for both robustness improvements
* fix: address bugbot follow-ups in send and opencode discovery
* fix(test): update stale integration test expectation for opencode bootstrap
The getLaunchCommand implementation now uses a robust bootstrap pattern
that captures the session ID between 'opencode run' and 'exec opencode --session'.
Updated test to check both parts separately instead of expecting a contiguous
string that no longer exists.
* fix: avoid NaN in sort comparator when both timestamps are missing
The comparator (b.updatedAt ?? -Infinity) - (a.updatedAt ?? -Infinity)
produces NaN when both timestamps are missing because -Infinity - (-Infinity)
is NaN in IEEE 754. A comparator returning NaN violates ECMA-262's
'consistent comparison function' requirement, making sort results
implementation-defined.
Fixed by adding equality check before subtraction:
- If both timestamps are equal (including both -Infinity), return 0
- Otherwise return the difference
Address two Bugbot review comments from PR #191:
1. Closed MRs are now correctly reported as non-mergeable in
getMergeability, returning a "MR is closed" blocker instead of
falling through to produce an empty blockers list.
2. Extract shared glab, parseJSON, extractHost, and stripHost helpers
into scm-gitlab/src/glab-utils.ts. The tracker-gitlab plugin now
imports these from @composio/ao-plugin-scm-gitlab/glab-utils instead
of maintaining duplicate copies.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>