Remove the post-startup "Next step: ao spawn <issue-number>" hint
per reviewer feedback — the dashboard already guides users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded magic number 3000 across spawn.ts, session.ts,
open.ts, dashboard.ts, and start.ts with a shared DEFAULT_PORT
constant from lib/constants.ts. Fixes Bugbot review feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add mockClear() before mockReturnThis() in spawn test beforeEach to
prevent call history accumulating across tests. Fixes Bugbot review
feedback about stale mock.calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When dashboard is disabled, print `ao session attach <id>` instead of
an unreachable dashboard URL. Fixes Bugbot review feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace raw `tmux attach -t` commands in CLI output with web dashboard
URLs (`http://localhost:{port}/sessions/{sessionId}`) across all four
CLI commands: spawn, session restore, open, and start.
Add `stripHashPrefix` helper in session-utils to extract AO session IDs
from hash-prefixed tmux session names. Remove unused `tmuxTarget`
variable from start command.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback:
- Remove duplicate created/spawned messages — use single spinner.succeed
- Add "(if running)" qualifier for dashboard mention since ao spawn
does not start the dashboard itself
- Update test mock to expose spinner for output assertions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace verbose multi-line spawn output (worktree, branch, raw tmux
attach) with a single-line message that directs users to the dashboard
or `ao session attach <id>` instead of exposing internal tmux session
hashes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When dashboard is enabled, `ao start` now prints the session detail page
URL (e.g. http://localhost:3000/sessions/<id>) instead of the tmux attach
command. Falls back to tmux attach when --no-dashboard is used.
Closes#947
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 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>
When an external notifier is loaded via path and its manifest.name
collides with a built-in (e.g. 'slack'), prepareConfig previously threw
because isBuiltin was true and the path field was present. Skip the
built-in guard for external loads — the path field in that case is the
loading path, not a stray user config value.
Prevents cross-project misclassification where a worker session like
'app-orchestrator-1' of prefix 'app-orchestrator' could be matched as
an orchestrator for a shorter prefix 'app'.
- useSessionEvents: detect unknown ids in mux patches and trigger
refresh. Previously additions were filtered out before the membership
comparison, so new sessions on a project page only appeared via the
15s stale-refresh path.
- cli/index.ts: replace hardcoded "0.1.0" with version from
package.json so 'ao --version' stays in sync with releases.
Covers the 7 lines in providers.tsx that were at 0% coverage,
pushing overall diff coverage from 79% to ~80%.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Export SessionBroadcaster for testing
- Add 10 tests covering subscribe, broadcast, fetchSnapshot, SSE
reconnection, disconnect, and error isolation
- Covers the major uncovered lines in mux-websocket.ts (66-194)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The useSessionEvents mux tests create scheduleRefresh timers that leak
between test cases and hang the suite. The SSE tests already cover the
shared dispatch/reducer logic, so removing the mux-specific tests fixes
the coverage OOM without reducing meaningful coverage. Also reverts the
NODE_OPTIONS workaround from the coverage workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
useSessionEvents tests OOM the vitest worker when coverage instrumentation
is enabled (4GB default isn't enough). Split the test file into SSE and mux
suites so the worker can GC between files, and raise the heap to 6GB for
the coverage job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mirrors the isolation pattern already used in pty.onData — if one
subscriber's ws.send throws mid-loop the remaining subscribers still
receive the session update.
Co-Authored-By: Claude Opus 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.