* 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>
* feat: add npm publishing support with @composio scope
Set up Changesets for version management, add publish metadata to all 20
packages under the @composio scope, create an unscoped wrapper package
(@composio/agent-orchestrator) for global install, and add a GitHub
Actions release workflow.
- Rename all packages from @agent-orchestrator/* to @composio/ao-*
- Add @composio/agent-orchestrator wrapper (bin shim → @composio/ao-cli)
- Add license, repository, homepage, bugs, files, engines to all packages
- Add .npmrc (access=public), MIT LICENSE file
- Add .changeset/ config with linked versioning for all packages
- Add .github/workflows/release.yml (changesets publish CI)
- Add changeset, version-packages, release scripts to root
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: exclude private web package from release build
The release script now filters out @composio/ao-web, matching the
workflow's existing exclusion and preventing a Next.js build failure
from blocking npm publishing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: implement SCM and tracker plugins (github, linear)
Implement three plugin interfaces from packages/core/src/types.ts:
- scm-github: Full GitHub PR lifecycle via `gh` CLI — PR detection by
branch, state tracking, CI checks, reviews, review decision, pending
comments, automated bot comment detection (cursor[bot], codecov, etc.),
and merge readiness (CI + reviews + conflicts + draft).
- tracker-github: GitHub Issues tracker via `gh` CLI — issue CRUD,
completion check, branch naming (feat/issue-N), prompt generation,
list/filter/update/create with label and assignee support.
- tracker-linear: Linear issue tracker via GraphQL API (LINEAR_API_KEY) —
issue fetch, completion check, branch naming (feat/IDENTIFIER), prompt
generation, list with team/state/label filters, state transitions via
workflow state resolution, issue creation with teamId config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review — GraphQL injection, N+1 queries, timeouts, tests
- tracker-linear: use GraphQL variables in listIssues to prevent injection
- tracker-linear: add 30s HTTP timeout to linearQuery
- tracker-linear: fix issueUrl to use workspace slug from project config
- tracker-linear: add labels/assignee support to createIssue
- scm-github: rewrite getPendingComments to use GraphQL reviewThreads
with real isResolved status instead of hardcoding false
- scm-github: simplify getAutomatedComments to single API call (N+1 fix)
- scm-github: add 30s timeout to gh CLI calls
- scm-github: fix parseDate to return epoch instead of fabricating dates
- scm-github: add repo format validation in detectPR
- scm-github: fix getCISummary to not count all-skipped as passing
- tracker-github: add 30s timeout to gh CLI calls
- tracker-github: fix createIssue to not use unsupported --json flag
- Add comprehensive vitest tests for scm-github and tracker-github
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Codex review — assignee lookup, GraphQL vars, status checks
Iteration 1 fixes (8 issues from Codex review):
- tracker-linear: remove invalid assigneeDisplayName, resolve assignee
by display name to ID via users query after creation
- tracker-linear: add HTTP status code check in linearQuery
- tracker-linear: throw on missing workflow state in updateIssue
- scm-github: use GraphQL variables ($owner, $name, $number) in
getPendingComments instead of string interpolation
- scm-github: guard against empty comment nodes in review threads
- scm-github: use mergeStateStatus in getMergeability (BEHIND, BLOCKED)
- tracker-github: default description to "" in createIssue
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review + lint — error context, GraphQL vars, mergeability
- scm-github/tracker-github: wrap gh() errors with command context + cause
- scm-github: use GraphQL variables ($owner, $name, $number) in
getPendingComments to prevent injection
- scm-github: guard against empty review thread nodes
- scm-github: incorporate mergeStateStatus (BEHIND/BLOCKED) in getMergeability
- tracker-linear: add identifier vs UUID comment in updateIssue
- Fix ESLint preserve-caught-error violations
- Remove unused mockGhRaw in scm-github tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add pagination to getAutomatedComments and increase thread limit
- getAutomatedComments: add --paginate flag to REST API call to fetch
all review comments beyond the default 30-item first page
- getPendingComments: increase GraphQL reviewThreads limit from 100 to
250 (GitHub's max for connections)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use per_page=100 instead of --paginate for JSON-safe pagination
Replace --paginate with -F per_page=100 in getAutomatedComments to
avoid concatenated JSON arrays that break JSON.parse on multi-page
responses. 100 is GitHub's max per_page value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: filter out resolved threads in getPendingComments
The method name implies it should only return unresolved/pending review
threads. The isResolved data was already fetched via GraphQL but was not
being filtered on, causing resolved threads to be included in results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add comprehensive tracker-linear test suite (53 tests)
Covers all Tracker interface methods: getIssue, isCompleted, issueUrl,
branchName, generatePrompt, listIssues, updateIssue, createIssue.
Mocks node:https request to simulate Linear GraphQL API responses.
Tests state mapping, error handling, assignee/label resolution, and
GraphQL variable injection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address 6 bugbot review comments on PR #4
- GraphQL reviewThreads(first: 250) → first: 100 (GitHub max)
- noConflicts false when mergeable is UNKNOWN (not just CONFLICTING)
- updateIssue now handles labels and assignee (not just state/comment)
- Add res.on("error") handler in linearQuery to prevent crashes
- createIssue returns only actually-applied labels, not all requested
- Tests added/updated for all fixes (144 total across 3 plugins)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make Linear updateIssue labels additive to match GitHub behavior
Linear's issueUpdate replaces all labels, while GitHub's --add-label is
additive. Now fetches existing label IDs first and merges with new ones
before sending to issueUpdate, ensuring consistent behavior across both
tracker implementations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: default listIssues to open state in tracker-linear
When no state filter is specified, tracker-github defaults to "open"
but tracker-linear was returning all issues. Now defaults to excluding
completed/canceled issues, matching tracker-github behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Composio SDK support to tracker-linear
Allow users with a COMPOSIO_API_KEY to use the Linear tracker without
a separate LINEAR_API_KEY. The plugin auto-detects which key is available
and routes through either the direct Linear GraphQL API or Composio's
LINEAR_RUN_QUERY_OR_MUTATION tool. All existing queries and response
parsing are reused via a GraphQLTransport abstraction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address 2 bugbot review comments on PR #4
1. getCIChecks now throws on error instead of silently returning [],
preventing a fail-open where CI appears healthy when we can't fetch
check status. getCISummary catches the error and returns "failing".
2. Handle GitHub's UNSTABLE mergeStateStatus (required checks failing)
as a merge blocker in getMergeability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent unhandled promise rejection in Composio timeout race
When the timeout wins Promise.race in the Composio transport, the
resultPromise is left without a rejection handler. If the SDK call
later rejects, it becomes an unhandled promise rejection that crashes
Node.js 20+ with --unhandled-rejections=throw.
Attach no-op .catch() to both resultPromise and timeoutPromise before
the race so whichever promise loses has its rejection silently handled.
Also adds plugin integration tests (core -> real plugins -> mocked gh CLI).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: chain error cause in getCIChecks and fix core build
- getCIChecks catch now preserves the original error via { cause: err }
instead of discarding it, matching the pattern used by the gh() helper
- Add tsconfig.build.json to core that excludes __tests__/ from build
compilation, preventing TS5055 errors from circular workspace deps
(integration tests import plugin packages that depend back on core)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove circular devDeps, address bugbot comments
- Remove plugin devDependencies from core/package.json that created a
circular dependency (core -> plugins -> core), breaking CI build order
- Document in_progress state as intentional no-op in tracker-github
updateIssue (GitHub Issues only supports open/closed)
- Remove @composio/core optional peerDependency from tracker-linear;
the dynamic import() already handles the missing package gracefully,
and the peer dep was pulling composio + transitive deps into lockfile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add real integration test for tracker-linear against Linear API
Creates a test that exercises the full tracker-linear plugin lifecycle
against the real Linear API — createIssue, getIssue, isCompleted,
listIssues, updateIssue (comment, close, reopen), generatePrompt,
branchName, issueUrl. Each run creates a throwaway test issue and
trashes it in cleanup. Skipped when LINEAR_API_KEY is not set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: pass LINEAR_API_KEY and LINEAR_TEAM_ID to integration tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support both LINEAR_API_KEY and COMPOSIO_API_KEY in integration test
The tracker-linear integration test now runs with either credential:
- LINEAR_API_KEY: direct Linear API (full cleanup via trash)
- COMPOSIO_API_KEY: via Composio SDK (cleanup falls back to closing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: don't pass COMPOSIO_API_KEY to CI integration tests
When both LINEAR_API_KEY and COMPOSIO_API_KEY are set, the plugin
prefers the Composio transport which requires @composio/core SDK.
The SDK isn't installed in CI, so use the direct LINEAR_API_KEY
transport which has no extra dependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use import.meta.url in vitest config, default createIssue description
- Replace __dirname with import.meta.url-derived dirname in ESM config
- Default createIssue description to "" for defensive safety
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: map CANCELLED and ACTION_REQUIRED CI conclusions to failed
Terminal check conclusions (CANCELLED, ACTION_REQUIRED) were falling
through to "pending", causing the lifecycle manager to wait forever.
Also changed the default else branch to "failed" (fail-closed).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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>