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>
* 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>