Renames all npm package scopes from @composio/* to @aoagents/* and
updates GitHub repo references from ComposioHQ/agent-orchestrator
to aoagents/ao throughout the codebase.
- All package.json names and dependencies
- README badges, links, and install instructions
- Documentation references
- Changeset config
- Source code imports and test files
* fix: reduce dashboard JS bundle from 1.7MB to 170KB (gzipped) (#792)
Switch `ao start` default from `next dev` (7.6MB uncompressed) to optimized
production builds (128KB per route). Add `--dev` flag for HMR when editing
dashboard UI. Add bundle analyzer, server-only guards, and lazy-load
DirectTerminal via next/dynamic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip dashboard rebuild when assets exist, fix CI timeout
Skip the production build step when .next/BUILD_ID and dist-server/
already exist (e.g. after pnpm build in CI). Add c8 ignore for
untestable process-spawning startup code to fix diff coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: adopt PR #903 patterns — centralize rebuild logic and add preflight web artifact checks
Extract rebuildDashboardProductionArtifacts into dashboard-rebuild.ts, add
isInstalledUnderNodeModules/assertDashboardRebuildSupported guards, remove
findProcessWebDir, and verify .next/BUILD_ID + dist-server/start-all.js in
preflight. Dashboard command now always uses production server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip production preflight in --dev mode, add coverage for rebuild helpers
- Skip preflight.checkBuilt() when --dev is passed (dev mode uses HMR,
doesn't need .next/BUILD_ID or dist-server/start-all.js)
- Add c8 ignore to dashboard.ts process-spawning code (matches start.ts pattern)
- Add tests for rebuildDashboardProductionArtifacts (success, failure, npm guard)
- Add preflight tests for npm-install web artifact hint paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: align preflight skip with actual dev-server condition
Only skip production artifact preflight when both --dev is passed AND
we're in the monorepo (where dev mode actually works). For npm global
installs, --dev is silently ignored and production server runs, so
preflight must still validate .next/BUILD_ID and dist-server/start-all.js.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: match @next/bundle-analyzer version to next@^15.1.0
The @next/* packages follow the Next.js release train. Pin the bundle
analyzer to ^15.1.0 to match the project's next dependency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct recovery command for npm installs and remove redundant guard
- Change stale-build recovery suggestion from "ao update" (which only
works in source checkouts) to "npm install -g @composio/ao@latest"
for npm global installs
- Remove redundant assertDashboardRebuildSupported call in dashboard.ts
since rebuildDashboardProductionArtifacts already calls it internally
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): remove CLI instructions from empty state, clean up misleading UI text (#955)
- Replace "No sessions running. Start one with ao start" with neutral
"No active sessions" — the dashboard has no context for CLI commands
- Fix PullRequestsPage subtitle that referenced internal "board chrome" jargon
- Simplify Dashboard hero subtitle to plain language
- Simplify Attention Board subtitle from opaque triage jargon
- Rename "Blocked on system state" caption to "Waiting on external state"
- Update test assertion to match new empty state text
Fixes#955
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web): improve empty state copy per review feedback
Use more informative message that points users to the in-dashboard
orchestrator button rather than a generic "no sessions" label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web): use neutral empty state copy; fix stale test assertions
- Revert instructional empty state copy to neutral "No active sessions"
because OrchestratorControl is hidden on mobile and when no orchestrator
is running — exactly the zero-session scenario where EmptyState appears.
Pointing users to a button that may not be visible is misleading.
- Update Dashboard.doneBar.test.tsx regex from stale /No sessions running/i
to /No active sessions/i so the not.toBeInTheDocument() assertion
meaningfully guards the regression rather than passing trivially.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): pin first quality summary for stable session titles
When a session's first non-fallback summary is obtained, persist it to
metadata as pinnedSummary. This prevents the dashboard title from drifting
as the agent generates new summaries each turn.
Changes:
- Add pinnedSummary to SessionMetadata type
- Serialize/deserialize in writeMetadata/readMetadata
- Pin in lifecycle manager's checkSession (correct write path)
- Propagate through restore path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(web): stable session titles via unified getSessionTitle
Reorder title fallback: PR > issue > branch > summary (was: summary > issue > branch).
Replace inline getSessionHeadline with shared getSessionTitle in both
SessionDetail and page.tsx for consistent behavior across all components.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: coverage for pinnedSummary and title fallback chain
9 new tests:
- 3 serialize tests: pinnedSummary priority over agent/metadata summary
- 5 lifecycle-manager tests: pinning logic guards and error handling
- 1 metadata test: pinnedSummary serialization in writeMetadata
Updated existing tests to match new title fallback order.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: fix mobile title expectations
* fix(web): use pinnedSummary only for title selection, not dashboard.summary
Previously, pinnedSummary was assigned to dashboard.summary in
sessionToDashboard(), causing two problems:
1. enrichSessionAgentSummary() never fetched newer summaries because
dashboard.summary was already set
2. Every UI surface rendering session.summary showed the stale pinned
value instead of live agent output
Fix: pinnedSummary is now read only by getSessionTitle() via
session.metadata["pinnedSummary"], keeping title stable without
freezing the live summary field. dashboard.summary always reflects the
current agentInfo or metadata summary.
Addresses review comment on PR #946.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): ao start navigates to session page instead of orchestrator selection
When ao start finds existing orchestrators, it now auto-selects the most
recently active one and opens /sessions/{id} directly, rather than the
/orchestrators selection page. This matches the behavior users expect:
landing on the agent terminal immediately on startup.
Closes#954
* fix(cli): navigate to session page for single orchestrator, selection page for multiple
When ao start finds existing orchestrators:
- 1 orchestrator: navigate directly to /sessions/{id} (fix for #954)
- 2+ orchestrators: keep /orchestrators?project={id} selection page so users
can choose among sessions or spawn a new one — the main dashboard only
links one orchestrator per project, making the selection page the only
startup path for multi-orchestrator projects.
Addresses review feedback on #958.
* fix(notifier): remove desktop notifications from all default configs (#960)
Desktop notifications via osascript open blank Finder windows on macOS
without providing useful information. Remove 'desktop' from all default
notifier lists so new users are not affected. Existing configs that
explicitly list 'desktop' continue to work unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(cli): add coverage for autoCreateConfig default notifiers
Line 585 in start.ts (`notifiers: []`) was not covered by the diff
coverage check. Added a test that exercises `autoCreateConfig` via
`createConfigOnly()`, verifying that the generated config does not
include 'desktop' in `defaults.notifiers`.
Required infrastructure additions:
- Mock `node:process` so `import { cwd } from "node:process"` in
start.ts can be overridden per-test via `mockProcessCwd`
- Set `detectProjectType` mock to return `{ languages: [], frameworks: [] }`
instead of null to avoid crash in autoCreateConfig
Closes#960
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(config): disable all notifications by default — fully opt-in
Change DefaultPluginsSchema.notifiers default from ["composio"] to []
and notificationRouting defaults to all empty arrays, so no notifications
are sent unless the user explicitly configures them. Addresses reviewer
feedback that composio notifier should not be active out of the box.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(config): use empty object as notificationRouting default
Empty array entries like { urgent: [] } block the ?? fallback in
notifyHuman (lifecycle-manager.ts:1169) because [] is not null/undefined.
Using {} lets unlisted priorities fall back to defaults.notifiers, so
explicit per-user notifier config is respected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lifecycle): reduce GitHub API rate limiting from batch enrichment bypass
Three optimizations to prevent API storms in the lifecycle manager poll cycle:
1. **CRITICAL - maybeDispatchMergeConflicts**: Gate the getMergeability()
fallback to only run when batch enrichment didn't run at all. Previously
it called getMergeability() (3 REST calls) whenever hasConflicts was
undefined, even when the batch had already fetched PR data. Now uses
cachedData.hasConflicts ?? false when the batch ran.
2. **HIGH - maybeDispatchCIFailureDetails**: Use batch enrichment ciChecks
when available instead of calling getCIChecks() (separate REST call)
on every poll. The GraphQL batch query now fetches statusCheckRollup
contexts (individual check names, statuses, URLs) alongside the rollup
state. Falls back to getCIChecks() only when batch didn't run.
3. **MEDIUM - maybeDispatchReviewBacklog**: Throttle getPendingComments +
getAutomatedComments API calls to at most once per 2 minutes per session.
These were called every 30s even when nothing had changed.
Impact: ~8-10 API calls/PR/poll reduced to ~2-4, enabling 3-4x more
concurrent sessions before hitting GitHub's 5,000/hr REST limit.
Also extends PREnrichmentData with ciChecks?: CICheck[] and adds
parseCheckContexts() helper to graphql-batch.ts for parsing CheckRun
and StatusContext nodes from the GraphQL statusCheckRollup.contexts field.
* fix(scm-github): fall back to getCIChecks() when contexts list is truncated
When a PR has >20 CI checks, contexts(first: 20) silently truncates the
list. Setting ciChecks to undefined when pageInfo.hasNextPage is true
ensures maybeDispatchCIFailureDetails falls back to the getCIChecks()
REST call, which returns all checks without truncation.
Also adds pageInfo { hasNextPage } to the contexts GraphQL query so
truncation can be detected.
* fix(lifecycle): prune lastReviewBacklogCheckAt in pollAll cleanup loop
Add the new throttle map to the existing pruning loop that removes stale
entries for sessions no longer in the session list. Previously the map
was only cleared on terminal status transitions, leaving orphaned entries
for sessions removed externally (killed + cleaned up without transition).
* fix(lifecycle): bypass throttle on review transition; fix StatusContext conclusion
Two fixes for automated review findings:
1. Bypass review backlog throttle when a transition reaction just fired for
humanReactionKey or automatedReactionKey. The transitionReaction branch
needs to read the current fingerprint via the API to record
lastPendingReviewDispatchHash. Without bypassing, the throttle prevents
this write and the next unthrottled poll sees a stale (empty) hash,
clears the reaction tracker, and fires a duplicate dispatch.
2. Set conclusion on StatusContext nodes in parseCheckContexts() to match
the REST getCIChecksFromStatusRollup() format (rawState.toUpperCase()).
The CI failure fingerprint includes c.conclusion ?? '', so inconsistent
conclusion values between GraphQL and REST paths caused phantom fingerprint
changes when switching sources, triggering duplicate dispatches.
* fix(scm-github): normalize CheckRun conclusion and map NEUTRAL to skipped
Two consistency fixes in parseCheckContexts() vs the REST path:
1. NEUTRAL conclusion: was mapped to 'passed' (with SUCCESS), but
mapRawCheckStateToStatus() in the REST path maps NEUTRAL to 'skipped'.
Changed to treat NEUTRAL the same as SKIPPED.
2. CheckRun conclusion: was stored as the raw GraphQL string (may be
lowercase). REST getCIChecks/getCIChecksFromStatusRollup always store
conclusion as rawState.toUpperCase(). Now stores rawConclusion which
is already uppercased during the status branching logic.
Both fixes prevent phantom fingerprint changes when maybeDispatchCIFailureDetails
switches between GraphQL batch and REST fallback across poll cycles.
* fix(scm-github): map STALE/NOT_REQUIRED/NONE conclusions to skipped
parseCheckContexts() was mapping these conclusions to 'failed' via the
else fallback, while mapRawCheckStateToStatus() in the REST path
explicitly maps all of them to 'skipped'. Added them to the skipped
branch alongside SKIPPED and NEUTRAL to fully mirror the REST mapping.
* fix(scm-github): map QUEUED/WAITING to pending not running
parseCheckContexts() mapped QUEUED and WAITING CheckRun statuses to
'running', but mapRawCheckStateToStatus() in the REST path maps both
to 'pending'. Only IN_PROGRESS maps to 'running' in the REST path.
Fixes fingerprint inconsistency when switching between GraphQL batch
and REST fallback across poll cycles.
* fix(scm-github): map STARTUP_FAILURE to skipped; guard null pageInfo
- STARTUP_FAILURE conclusion now falls through to the "skipped" branch
(matching mapRawCheckStateToStatus() REST default) instead of the
explicit failure enumeration catch-all
- Null pageInfo guard prevents TypeError from typeof null === "object"
JavaScript quirk when accessing hasNextPage on a null pageInfo field
- Tests added for both cases
* fix(scm-github): map COMPLETED+null conclusion to skipped not passed
When a CheckRun has status COMPLETED and conclusion null, the REST path's
mapRawCheckStateToStatus() converts it to "" which maps to "skipped".
The GraphQL path was incorrectly mapping it to "passed" via !rawConclusion.
Fix: only map rawConclusion === "SUCCESS" to "passed"; null falls through
to the else branch → "skipped", matching the REST path exactly.
* Revert "fix: pause workers on model limits and stabilize session visibility (#367)"
This reverts commit 003eb78adb.
* fix: resolve post-revert type errors and broken test references
- Fix dangling import { in session-manager.ts (leftover from removing globalPause imports)
- Fix duplicate registerStop body in start.ts (leftover parent-version code after catch block)
- Remove --keep-session flag from stop command (was added by #367)
- Fix PullRequestsPage.tsx useSessionEvents call removing dropped initialGlobalPause arg
- Remove globalPause test cases from spawn.test.ts, communication.test.ts, api-routes.test.ts
- Remove unused updateMetadata imports from test files
* fix: remove unused allSessions variable after globalPause removal
Match font-size (12px), font-weight (700), and letter-spacing (0.16em)
to the existing board-section-head__title pattern for visual consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Done sessions should only render in the collapsible done-bar, not also
in the kanban columns and mobile accordion. Reverts KANBAN_LEVELS,
MOBILE_KANBAN_ORDER, and MOBILE_FILTERS to exclude "done", and restores
the handler guards that prevent accordion/pill interaction for done level.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Dashboard.doneBar.test.tsx covering the collapsible done/terminated
bar: renders when done sessions exist, expands on click to show session
cards, and suppresses the empty state when only done sessions are present.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a collapsible "Done / Terminated" bar below the kanban board that
expands to show completed session cards in a grid. Sessions with terminal
states (merged, killed, done, terminated) appear here with their full
card UI including restore button. Also fixes the empty state to not show
when only done sessions exist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Transient 404 during polling no longer kicks user off an active session
view. Also added missing next/link mock in session error test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Polling errors no longer throw to the error boundary when a session is
already loaded, preventing transient network blips from unmounting the
view. Added missing next/link mock in error.test.tsx.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update lastRefreshAtRef on non-OK /api/sessions responses to prevent
infinite 5-second retry loops when the API is unhealthy
- Reset lastRefreshAtRef when project changes so each project gets its
own immediate first refresh instead of inheriting the prior project's
cooldown timestamp
- Add test for project-switch refresh behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Treat unenriched PRs as a first-class state in the dashboard so SSR can
return fast-path session data without showing misleading PR status, size,
or alert states. Add shimmer placeholders for partial PR data, avoid
classifying unenriched PRs as merge/review/pending work, and ensure the
first SSE snapshot triggers an immediate refresh without failure loops.
- Remove phantom `projects` prop from test setup (was dead code)
- Deduplicate test coverage: consolidate component tests into
OrchestratorSelector.test.tsx, keep only page route tests in
orchestrators.test.tsx
- Fix useRouter mock to capture and assert router.push calls
- Add finally block to handleSpawnNew for resilience (ensures
isSpawning is reset even if router.push fails)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix tmux target to use session ID when runtimeHandle is missing,
preventing references to non-existent sessions
- Extract mapSessionsToOrchestrators helper to eliminate duplicate
orchestrator listing logic between page.tsx and API route
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Export Orchestrator interface from OrchestratorSelector component
and import it in page.tsx to avoid silent divergence risk.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
When --no-dashboard is used and existing orchestrators are found,
auto-select the most recently active one instead of deferring to
dashboard selection (which isn't available). This ensures the CLI
remains usable without the dashboard.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The sm.list() call was outside the existing try/catch that handles
orchestrator setup failures. If listing sessions throws, the spinner
could be left running and the dashboard process could leak.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The projects prop was declared and passed but never used, causing
unnecessary data fetching and serialization. Removed the prop from
the component interface, page, and tests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Guard against NaN timestamps from invalid ISO date strings and handle
future timestamps gracefully by returning "Just now" instead of
negative values.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>