* 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>
* fix: resolve dashboard GitHub API rate limiting and PR enrichment issues
This commit addresses critical dashboard performance and reliability issues:
**Core Issues Fixed:**
1. GitHub API rate exhaustion (~84 calls/refresh → ~7-10 calls/refresh)
2. Silent failures showing misleading PR data when rate-limited
3. Missing SessionStatus values ("done", "terminated")
4. Unnecessary enrichment of merged/closed PRs
5. No caching of API responses
**Key Changes:**
- Add "done" and "terminated" to SessionStatus type
- Update getAttentionLevel to correctly classify terminal sessions
- Skip PR enrichment for terminal sessions (merged, done, terminated)
- Implement 60-second TTL cache for PR enrichment data
- Handle rate limit errors gracefully with explicit "unavailable" messages
- Improve default values in basicPRToDashboard (no longer misleading)
- Add orchestrator terminal button to Dashboard header
**Test Coverage:**
- 54 new test cases across 3 test files
- Tests for cache behavior, attention level classification, and serialization
- All tests passing (cache: 9/9, types: 29/29, serialize: 16/16)
**Performance Impact:**
- 10× reduction in API calls (84 → 7-10 per refresh)
- 10× improvement in rate limit exhaustion time
- 60s cache prevents redundant API calls on page refresh
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: address bugbot comments (cache leak, PR skip, CI alert)
Fixes three issues identified by bugbot:
1. **TTL cache memory leak (Medium)**: Cache only evicted expired entries
on get(), causing unread keys to accumulate indefinitely. Added periodic
cleanup via setInterval (runs every TTL period) with unref() to prevent
blocking process exit.
2. **PR skip condition never triggers (Low)**: Check for merged/closed PRs
was using sessions[i].pr.state which is always "open" (default from
basicPRToDashboard). Fixed by checking cache for merged/closed state
before enrichment, avoiding unnecessary API calls.
3. **SessionCard "0 CI check failing" bug**: When GitHub API fails,
ciStatus is "failing" but ciChecks is empty, showing nonsensical
"0 CI check failing" alert. Fixed to show "CI status unknown" instead
when failCount is 0.
**Tests Added:**
- Cache cleanup interval test (async real timer)
- SessionCard CI status unknown test (verifies no "0 failing" or "ask to fix")
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: CRITICAL - fix field name mismatch in getCIChecks causing all checks to fail
Root cause of "CI failing" everywhere: scm-github plugin was requesting
non-existent fields from gh CLI, causing all checks to map to "failed".
**The Bug:**
- Requesting: `conclusion` and `detailsUrl` (don't exist in gh pr checks)
- Since `conclusion` was always undefined, every check hit the else clause
and was marked as "failed"
**The Fix:**
- Use correct field names: `state` (contains SUCCESS/FAILURE/PENDING directly)
and `link` (replaces detailsUrl)
- Parse `state` directly instead of looking for non-existent `conclusion`
- Map state values: SUCCESS → passed, FAILURE → failed, PENDING → pending, etc.
**Impact:**
This was the #1 bug causing false "CI failing" status everywhere, not rate
limiting. All PRs with passing CI were incorrectly shown as failing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: update plugin-integration tests for getCIChecks field name changes
The getCIChecks fix changed field names from `conclusion`/`detailsUrl`
to `state`/`link`. Updated test mocks to match:
- Changed `conclusion: "SUCCESS"` → `state: "SUCCESS"`
- Changed `conclusion: "FAILURE"` → `state: "FAILURE"`
- Changed `detailsUrl` → `link`
Tests now pass with correct field names.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: update scm-github plugin tests for correct field names
Updated all test mocks to use correct gh pr checks field names:
- Changed `conclusion: "SUCCESS"/"FAILURE"/etc` → `state: "SUCCESS"/"FAILURE"/etc`
- Changed `detailsUrl` → `link`
- Removed redundant `state: "COMPLETED"` prefix (state contains result directly)
All 52 scm-github plugin tests now pass.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: apply cached data when skipping enrichment + improve rate-limit detection
Fixes two issues identified in bugbot comments:
1. **Cached terminal PR state never applied** (issue #2807979137):
- When skipping enrichment for merged/closed PRs, we now copy all cached
fields to the session before returning
- Previously the session kept default basicPRToDashboard() values (e.g.,
state: "open"), causing terminal PRs to render with stale data
2. **Rate-limit detection cannot trigger reliably** (issue #2807979141):
- Changed from "all failed" to "majority failed" detection (>= 50%)
- Some SCM methods (like getCISummary) return fallback values instead of
throwing, so allFailed was too strict
- Now detects rate limiting even when some methods return defaults
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(web): apply partial enrichment data when rate-limited + fix type errors
Addresses bugbot comment #2807998258: Rate-limit detection should not
discard partial successful enrichment data.
**Changes:**
1. Remove early return when mostFailed - continue to apply any fulfilled results
2. Add rate-limit blocker message to mergeability after applying partial data
3. Fix cached data application - use correct field names (unresolvedThreads/unresolvedComments)
4. Add proper type casts for cached ciChecks status field
5. Fix tsconfig to exclude test files from type-checking (jest-dom type extensions
don't work with tsc, but tests run fine with vitest)
**Behavior change:**
- Before: 3+ failed API calls → skip enrichment entirely, show "API rate limited"
- After: 3+ failed API calls → apply any successful results + add blocker message
This allows partial data (e.g., PR state, title, passing CI checks) to be displayed
even when some API calls fail, providing better UX during rate limiting.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: apply cached data to terminal sessions + always cache partial enrichment
Addresses two new bugbot comments:
1. **Terminal sessions keep stale open PR state** (#2808037050):
- Problem: page.tsx returned early for terminal sessions before checking cache
- Result: Terminal sessions kept basicPRToDashboard() defaults (pr.state="open")
- Fix: Check cache FIRST, apply cached data, THEN skip enrichment for terminal sessions
2. **Partial rate-limit results are never cached** (#2808037054):
- Problem: Caching was gated by `if (!mostFailed)`, so partial data wasn't cached
- Result: During rate-limits, sessions repeatedly re-hit SCM APIs every refresh
- Fix: Always cache enrichment results (including partial data from rate-limited requests)
**Behavior changes:**
- Terminal sessions now show correct cached PR state (merged/closed) instead of "open"
- Partial enrichment data is cached for 60s, reducing API pressure during rate-limit periods
- Updated test expectations to reflect new caching behavior
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: apply all cached fields + allow terminal sessions to enrich once
Addresses two new bugbot comments:
1. **Cached terminal data applied incompletely** (#2808048773):
- Problem: Only copied some fields (state, ciStatus, etc.) but omitted title, additions, deletions
- Fix: Added missing fields when applying cached data
2. **Terminal PRs remain permanently unenriched** (#2808048771):
- Problem: Terminal sessions with no cache never got enriched → kept stale defaults forever
- Fix: Removed the "skip enrichment for terminal with no cache" logic
- Behavior: Terminal sessions now enrich at least once (or when cache expires), then skip subsequent enrichments
**Behavior change:**
- Before: Terminal session without cache → skip enrichment forever → stale data
- After: Terminal session without cache → enrich once → cache for 60s → skip while cached
This ensures terminal sessions get accurate PR data at least once, while still avoiding
unnecessary API calls for sessions that already have fresh cached data.
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>