* 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
- 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.
* feat: support multiple concurrent orchestrators with isolated worktrees
- Each orchestrator session gets a numbered ID ({prefix}-orchestrator-N)
and an isolated git worktree, replacing the single shared orchestrator
- reserveNextOrchestratorIdentity atomically reserves the next available
number and detects conflicting project prefix configurations early
- isOrchestratorSession / isOrchestratorSessionName accept optional
allSessionPrefixes for cross-project false-positive prevention
- getProjectPause and resolveGlobalPause track the longest active pause
across all concurrent orchestrators instead of returning the first found
- cleanupWorktreeAndMetadata helper used consistently on all failure paths
including post-launch; system prompt file included in cleanup
- pollBacklog, status.ts, ProjectSidebar, sessions page, global-pause,
project-utils, serialize, and sessions API route all pass
allSessionPrefixes to isOrchestratorSession
- Web sessions/[id]/page.tsx fetches project prefix map and passes it
through prefixByProjectRef to avoid stale closure in fetchProjectSessions
* fix: seed sseAttentionLevels from fresh sessions on full refresh
When scheduleRefresh dispatches a reset action, derive sseAttentionLevels
from the freshly fetched sessions using getAttentionLevel. This clears
phantom entries for removed sessions and seeds correct levels for new
sessions, preventing stale favicon color and attention counts until the
next SSE snapshot.
* fix: merge duplicate @/lib/types imports into single import statement
Combining the separate import type and value import from @/lib/types
into one import to satisfy the no-duplicate-imports lint rule.
* feat: event-driven live tab titles and favicons via SSE
Switch tab titles and favicons from polling to real-time SSE updates:
- Extend useSessionEvents to expose sseAttentionLevels map from SSE
snapshots (server-computed, includes full PR state)
- Refactor DynamicFavicon to use SSE attention levels instead of
recomputing from the full sessions array (which has stale PR data
between refreshes)
- Add useSSESessionActivity hook for session detail page to update
document.title emoji immediately on activity change
- Add live dashboard title showing count of sessions needing attention
- Update PullRequestsPage to use new DynamicFavicon API
Closes#115
* fix: seed initial attention levels to avoid stale favicon/title on first render
Accept initialAttentionLevels parameter in useSessionEvents so callers
can seed the attention map from initialSessions via getAttentionLevel().
This prevents the favicon and dashboard title from briefly showing
"all clear" before the first SSE snapshot arrives.
* fix: add EventSource mock to session page test for SSE hook compatibility
* fix: reset stale activity state when sessionId changes in useSSESessionActivity
Add sessionId to the effect dependency array and reset state to null at
the start of each effect run so callers that reuse the hook with a
different sessionId don't see the previous session's activity.
* fix: reset sseAttentionLevels on initialSessions change to prevent stale data on project switch
The reset action now accepts an optional sseAttentionLevels field. When
initialSessions changes (e.g., project switch via sidebar), the dispatch
passes the current initialAttentionLevels via ref so the favicon and
dashboard title reflect the new project immediately rather than showing
stale attention data until the first SSE snapshot.
Exposes connectionStatus ("connected" | "reconnecting" | "disconnected")
from useSessionEvents via onopen/onerror EventSource handlers, and renders
a fixed ConnectionBar at the top of the viewport that shows a pulsing amber
bar while reconnecting and a red 32px "Offline · tap to retry" bar when
disconnected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the lazy useState initializer that read window.matchMedia on
first render (causing SSR/client hydration mismatch) with a plain
false default; the existing useEffect already syncs the correct value
after mount. Adds a full vitest test suite covering all six specified
cases (SSR-safe default, match/no-match, number-to-query conversion,
change-event reactivity, and listener cleanup on unmount).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* perf: coalesce dashboard session refreshes
Batch membership-driven refreshes behind a short cadence and keep low-frequency steady-state refreshes so pause and PR data stay current without hammering /api/sessions during SSE churn.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* perf: memoize dashboard session views
Keep unchanged zones and cards cold during same-membership SSE updates by stabilizing dashboard callbacks, caching project groupings, and memoizing the session view tree with a render-cadence regression test.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix: prevent stale refresh scheduling after project switch
Abort in-flight refreshes and ignore late completions from disposed effects so old project fetches cannot re-arm timers or reset the new project state.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(web): add project-based dashboard architecture
- Add project query parameter to API routes
- Filter sessions by project in both SSR and SSE
- Update useSessionEvents hook to accept project param
- Update Dashboard and pass project to hook
- Add unit tests for project filtering
- Add architecture spec document
Implements project-based architecture as defined in docs/specs/project-based-dashboard-architecture.md:
- GET /api/sessions?project=X returns sessions for project X
- GET /api/events?project=X streams only sessions for project X
- Dashboard uses project filter from config
- SSE URL includes project param when provided
- Project matching uses projectId and sessionPrefix
- Full backward compatibility maintained (no param = all sessions)
* fix: remove duplicate export default in page.tsx
* fix(web): clean project-scoped dashboard verification
* fix(web): scope dashboard using project id
* fix(web): address Bugbot findings in project-scoped dashboard
- Consolidate triplicated matchesProject into shared lib/project-utils.ts
- Fix Dashboard to receive both projectId (for SSE filtering) and projectName (for display)
- Remove inline matchesProject definitions from page.tsx, sessions/route.ts, events/route.ts
* fix(web): resolve Bugbot issues in project-scoped dashboard
- Add ?project=all query param support in SSR page to show all sessions
(previously getPrimaryProjectId() always returned non-empty, making
else branches unreachable)
- Exclude orchestrator sessions from SSE stream to match SSR/API behavior
(orchestrator sessions get their own button, not a card)
- Add tests for SSE stream orchestrator exclusion and project filtering
Addresses Bugbot issues:
- #2903595986: Dead else branches when project filter always applied
- #2903595995: SSE stream includes orchestrator sessions unlike SSR/API
* feat(web): add project navigation sidebar
Add visible left sidebar with project navigation for multi-project setups:
- ProjectSidebar component with active state styling
- /api/projects endpoint to fetch configured projects
- getAllProjects() helper in project-name.ts
- Sidebar appears only when 2+ projects configured
- Click navigation updates ?project= query param
- Active project highlighted with accent color
Tests:
- ProjectSidebar component tests (8 tests)
- API routes tests with proper mocking
- All 386 web tests passing
Manual test steps:
1. Configure 2+ projects in agent-orchestrator.yaml
2. Start dashboard - sidebar should appear on left
3. Click different projects - URL updates, sessions filter
4. Click "All Projects" - shows all sessions across projects
5. Active project highlighted in sidebar
* fix(web): remove duplicate import in test file
* fix(web): consolidate ProjectInfo type to shared source
Remove duplicated ProjectInfo interface definitions from Dashboard.tsx and
ProjectSidebar.tsx. Both now import the type from @/lib/project-name where it is exported as the single source of truth.
This addresses Bugbot issue #2906835895: Triplicated ProjectInfo type instead of shared import.
* fix(web): integrate globalPause state from main
* fix(web): consolidate duplicate @/lib/types import
* feat(web): add project-based dashboard architecture
- Add project query parameter to API routes
- Filter sessions by project in both SSR and SSE
- Update useSessionEvents hook to accept project param
- Update Dashboard and pass project to hook
- Add unit tests for project filtering
- Add architecture spec document
Implements project-based architecture as defined in docs/specs/project-based-dashboard-architecture.md:
- GET /api/sessions?project=X returns sessions for project X
- GET /api/events?project=X streams only sessions for project X
- Dashboard uses project filter from config
- SSE URL includes project param when provided
- Project matching uses projectId and sessionPrefix
- Full backward compatibility maintained (no param = all sessions)
* fix: remove duplicate export default in page.tsx
* fix(web): clean project-scoped dashboard verification
* fix(web): scope dashboard using project id
* fix(web): address Bugbot findings in project-scoped dashboard
- Consolidate triplicated matchesProject into shared lib/project-utils.ts
- Fix Dashboard to receive both projectId (for SSE filtering) and projectName (for display)
- Remove inline matchesProject definitions from page.tsx, sessions/route.ts, events/route.ts
* fix(web): resolve Bugbot issues in project-scoped dashboard
- Add ?project=all query param support in SSR page to show all sessions
(previously getPrimaryProjectId() always returned non-empty, making
else branches unreachable)
- Exclude orchestrator sessions from SSE stream to match SSR/API behavior
(orchestrator sessions get their own button, not a card)
- Add tests for SSE stream orchestrator exclusion and project filtering
Addresses Bugbot issues:
- #2903595986: Dead else branches when project filter always applied
- #2903595995: SSE stream includes orchestrator sessions unlike SSR/API
* feat(web): add project navigation sidebar
Add visible left sidebar with project navigation for multi-project setups:
- ProjectSidebar component with active state styling
- /api/projects endpoint to fetch configured projects
- getAllProjects() helper in project-name.ts
- Sidebar appears only when 2+ projects configured
- Click navigation updates ?project= query param
- Active project highlighted with accent color
Tests:
- ProjectSidebar component tests (8 tests)
- API routes tests with proper mocking
- All 386 web tests passing
Manual test steps:
1. Configure 2+ projects in agent-orchestrator.yaml
2. Start dashboard - sidebar should appear on left
3. Click different projects - URL updates, sessions filter
4. Click "All Projects" - shows all sessions across projects
5. Active project highlighted in sidebar
* fix(web): remove duplicate import in test file
* fix(web): consolidate ProjectInfo type to shared source
Remove duplicated ProjectInfo interface definitions from Dashboard.tsx and
ProjectSidebar.tsx. Both now import the type from @/lib/project-name where it is exported as the single source of truth.
This addresses Bugbot issue #2906835895: Triplicated ProjectInfo type instead of shared import.
* fix(web): integrate globalPause state from main
* fix(web): consolidate duplicate @/lib/types import
* fix(web): restore global pause state and membership refresh
* fix(web): satisfy lint in project-scoped page defaults
* fix(web): remove dead global pause reducer action
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(web): restore global pause resume time
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* chore(web): retrigger bugbot after thread reset
* refactor(web): centralize project session filtering helpers
* fix(web): reset pause banner dismissal on new pause
* fix(web): remove useless orchestrator assignment
* fix(web): derive header stats from live session state
* fix(web): restore project name in dashboard header
* fix(web): restore backlog poller startup in events stream
* refactor(web): keep project-utils helpers internal
* chore: retrigger bugbot evaluation
* chore: retrigger stuck bugbot check
* fix: address latest bugbot findings for dashboard events
* test(web): add dashboard bugbot regression coverage
* refactor(web): simplify projectId selection in dashboard props
* fix(web): derive selected project name from project filter
* refactor(web): initialize dashboard defaults before service load
* fix(web): satisfy lint in dashboard page error path
* fix: pause workers on model limits and stabilize session visibility
Detect model limit exhaustion, pause project worker operations until reset, and expose pause state in dashboard/API. Also harden killed-session cleanup and SSE session reconciliation so sessions do not appear ghost-active or disappear until reload.
* fix: satisfy lint in rate-limit pause probe
* fix: address bugbot feedback for pause handling
* fix(lifecycle): prevent infinite re-pause loop for duration-based rate limits
Duration-based rate limits (e.g., 'usage limit reached for N hours') were
causing infinite re-pause loops because they always calculate reset time as
Date.now() + duration, which extends the pause on every poll cycle if the
message remains in terminal output.
Now checks for existing active pause before setting a new one:
- Skips override if same session already has active pause
- Preserves longer pauses from other sessions
Fixes infinite loop described in PR #367 review comment.
* fix(core): export global pause constants to prevent duplication
Export GLOBAL_PAUSE_*_KEY constants and parsePauseUntil utility from
@composio/ao-core so web package can import them instead of hardcoding.
This prevents silent breakage if key values ever change - now there's
a single source of truth.
Addresses review comment on PR #367.
* fix(web): globalPause as first-class state in SSE event flow
globalPause is now part of the same reducer/event flow as sessions,
not derived from provider-specific output text in the UI.
Changes:
- useSessionEvents: manage globalPause alongside sessions in reducer state
- Dashboard: consume globalPause from hook instead of SSR-only prop
- types: re-export GlobalPauseState from shared lib (provider-agnostic contract)
- Tests: 15 new tests proving banner appears/disappears from state updates
alone, regardless of agent model/plugin (Claude Code, OpenCode, Codex)
Design requirements satisfied:
- First-class state in same reducer/event flow as sessions
- Key names sourced from shared core contract via export/import
- Provider-neutral: no Anthropic/OpenAI string coupling in control logic
- State-driven: banner visibility from reducer state updates via SSE
Addresses Bugbot finding: Dashboard pause banner never updates after
initial render (eba24a0b-9e4c-47e3-91c9-7d10be01e3cf)
* fix: remove unused import in test file
* fix(cli): consistent purge default for session kill and stop commands
The kill method's default changed from opt-in (=== true) to opt-out (!== false).
Both session kill and stop commands now use the same logic:
purgeOpenCode = opts.purgeSession === true ? true : opts.keepSession !== true
This ensures consistent behavior across all kill paths.
Adds --keep-session flag to both commands.
Adds regression tests for provider-agnostic behavior verified.
Addresses Bugbot finding: orchestrator start cleanup inconsistent
with new purge default (2bc2535f-b2d1-4f64-96d6-150f97ed8564)
Wire the Dashboard component to the existing /api/events SSE endpoint
so session status, activity, and lastActivityAt update in real-time
without requiring a full page refresh. SSE snapshots are lightweight
patches merged into the full server-rendered session objects.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>