Commit Graph

30 Commits

Author SHA1 Message Date
i-trytoohard f22d2d9ccc
fix(web): terminal button deep-links to terminal section (#1218)
Previously both the 'terminal' button and 'View current context →' link
on the kanban card navigated to the same session detail page without
distinction. The terminal button now links to #session-terminal-section
anchor, which already exists in SessionDetail.tsx.

Fixes #1217

Co-authored-by: AO Bot <ao-bot@composio.dev>
2026-04-13 23:40:41 +05:30
Dhruv Sharma 663c61e16d fix: merge main and address review findings for prompt-driven spawn
- Merge main to pick up @composio/ao-core → @aoagents/ao-core rename
  and decomposer removal (#1104)
- Fix @composio/ao-core import in prompt-spawn.test.ts → @aoagents/ao-core
- Remove unreachable dead code in CLI spawn (empty-string guard after || undefined)
- Update format.ts JSDoc to document 8-item fallback chain including userPrompt
- Add clarifying comment on validation/sanitization separation in spawn route
- Integrate userPrompt display into redesigned SessionCard footer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:44:57 +05:30
Ashish Huddar af2af115bd
style(design): design review fixes + fresh Warm Terminal design system (#927)
* style(design): FINDING-001 — add prefers-reduced-motion support

All animations and transitions are disabled when the user's system
requests reduced motion, per DESIGN.md accessibility requirements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-003 — remove concurrent breathe animations

Status pills had two animations: a breathe animation on the pill
and a dot-pulse on the child dot. DESIGN.md says "one animation per
element, one purpose" and "keep dot pulse, remove border heartbeat."

Removed all three breathe keyframes, kept dot-pulse only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-004 — fix dashboard title weight and tracking

DESIGN.md specifies display headings at weight 680 and letter-spacing
-0.035em. The dashboard title was using 600 / -0.05em.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-005 — fix detail-card text to blue-tinted graphite

Detail cards overrode text-secondary and text-tertiary with neutral
grays (#9898a0, #5c5c66). DESIGN.md specifies blue-tinted graphite
palette (#a5afc4, #6f7c94) for dark mode text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-008 — add text-wrap: balance on headings

Dashboard title and kanban column titles now use text-wrap: balance
for more even line breaks on narrow viewports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-006/009 — fix section label semantics and spacing

Changed "Attention Board" from <h2> to <div role="heading"> since it's
styled as a 12px uppercase label, not a heading. Also fixed letter-spacing
from 0.16em to 0.06em per DESIGN.md UI/Labels spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-007 — contextual empty state messages

Empty kanban columns now show context-specific messages instead of
generic "No sessions" text. Each column's empty state reflects its
purpose: "No agents need your input" (Respond), "No code waiting
for review" (Review), etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): fresh design system — Warm Terminal

Complete redesign from Industrial Precision (blue-tinted) to Warm Terminal
(brown-tinted). Key changes:

- Warm charcoal surfaces (#121110, #1a1918, #222120) replace blue-gray
- Cream text (#f0ece8) replaces blue-white (#eef3ff)
- Warm periwinkle accent (#8b9cf7) replaces cool blue (#5B7EF8)
- Berkeley Mono for display headlines (mono cohesion)
- Added: Accessibility section (44px touch targets, WCAG AA, focus-visible)
- Added: Component anatomy (button states, card structure, input fields)
- Added: Light mode design rationale (warm parchment, not clinical white)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): swap Berkeley Mono for JetBrains Mono (free)

Berkeley Mono is a paid font ($75). JetBrains Mono is free, open source,
already loaded in the project, and the mono-for-headlines concept works
the same way with it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): fix light mode contrast failures

Light mode text-tertiary #a8a29e failed WCAG AA at 2.5:1 on white.
Darkened to #736e6b (5.0:1). Light mode accent #6b73c4 was borderline
at 4.3:1, darkened to #5c64b5 (5.3:1). All pairs now pass AA.

Added verified contrast ratios for both modes to accessibility section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: gitignore .gstack/ directory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(design): add design audit report and screenshots

Design review audit report with before/after screenshots for all
dashboard pages (kanban, session detail, PRs) across desktop, tablet,
and mobile viewports in both light and dark mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(design): address PR review comments

- Fix mobile test expecting removed "No sessions" text. The merge zone
  emptyMessage is now "Nothing cleared to land yet." (Bugbot comment #1)
- Remove no-op .dark .detail-card override that duplicated global dark
  values after FINDING-005 fix aligned them (Bugbot comment #2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gitignore .gstack-report/ and remove from tracking

The .gstack-report/ directory contains local audit artifacts with
filesystem paths. Should not be tracked in the repository.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(design): align dashboard title CSS to new DESIGN.md spec

Dashboard title was using old Geist Sans values (weight 680, -0.035em).
New spec is JetBrains Mono, weight 500, letter-spacing -0.02em.
Added font-family: var(--font-mono) to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use native h2 element for Attention Board section heading

Replace ARIA role="heading" div with semantic h2 per ARIA first rule — native elements are preferred over ARIA roles for actual headings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pre-landing review fixes — a11y, dead code, test coverage

- Add aria-controls + id to accordion button/body pair in AttentionZone
- Wrap empty-state messages in aria-live="polite" regions for AT announcements
- Remove dead message prop and isDefault from EmptyState (Skeleton.tsx)
- Add parameterized test covering all 6 zone-specific empty messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: revert aria-live on empty states — causes false AT announcements

Codex review identified that role="status" aria-live on static empty-state
text causes burst announcements on page load (all empty columns fire) and
announces in collapsed mobile sections that aren't visible. Empty states are
static text, not dynamic transitions. The aria-controls fix is kept.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: bump version and changelog (v0.0.1.0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove .gstack-report/ from .gitignore

* chore: remove VERSION and CHANGELOG (not used in this project)

* style(design): warm terminal color migration + inline style removal

Migrate all CSS tokens from cool blue-tinted graphite to warm
brown-tinted terminal aesthetic per DESIGN.md spec. Replace inline
style color mappings in ActivityDot, AttentionZone, Dashboard,
ProjectSidebar, and SessionCard with data-attribute CSS selectors.
Fix duplicate className bug on SessionCard done-title element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pre-landing review fixes — activity dot fallback + review stat color

Add base CSS fallback for activity-dot, activity-pill, and
activity-pill__text so null/unknown activity states render visibly
(gray) instead of invisible. Fix review stat card to use accent-orange
(matching kanban/sidebar/mobile review indicators) instead of cyan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: checkpoint current design branch state

* design changes

* feat(web): redesign session detail page — compact PR card, identity strip, layout reorder

- Redesign SessionTopStrip with simplified breadcrumbs, action buttons (Message/Kill)
- Replace stacked PR card with compact inline layout: title row + blocker/CI chips + collapsible comments
- Move PR card above terminal for better information hierarchy
- Replace vertical IssuesList with inline buildBlockerChips helper
- Add ~200 lines of new CSS classes for compact PR card design system
- Add changedFiles field to DashboardPR type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): design system tokens, sidebar redesign, and component primitives

- Align all color tokens (status, bg, border, text) across three HTML mockups
- Rewrite ProjectSidebar to match finalized.html: rotation chevron, session status text, border-bottom project separators, 224px width
- Add packages/web/DESIGN.md: agent-readable reference for tokens, typography, component patterns, anti-patterns
- Add Badge.tsx: generic badge/chip/pill primitive with status/outline/default variants
- Add Button.tsx: ghost/primary/danger button primitive
- Update CLAUDE.md to reference DESIGN.md as required pre-read for web UI work

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style(design): FINDING-001 — card border-radius 0 → 6px to match mockup

* style(design): FINDING-002 — column border-radius 0 → 7px, border subtle to match mockup

* style(design): FINDING-003 — column header mono font, 500 weight, muted color to match mockup

* style(design): FINDING-004/005/006 — fix accent-blue/yellow/purple tokens to match mockup

* style(design): FINDING-007 — add --color-bg-card token (light #fff, dark #1c1b19)

* Refine dashboard design system and remove fixture flow

* Fix respond status colors in dashboard indicators

* Align tests with updated dashboard and metadata behavior

* fix(core): register notifier aliases consistently

* chore(web): drop uncovered showcase routes

* Add desktop PullRequestsPage coverage tests

* Remove generated coverage artifact

* Consolidate web design guidance into the root design system

* Fix working and ready status color tokens

* Fix sidebar collapse and inline kill confirmation

* fix(qa): ISSUE-001 - show all mobile filter chips

* Restore full title contrast in session cards

* Fix review feedback in dashboard state styling

* style: implement mobile responsive designs (feed, terminal-first, dense PRs)

Dashboard: replace accordion with urgency-sorted priority feed, horizontal scroll filter pills.
Session Detail: terminal-first layout with floating header, status pill, PR bottom sheet.
PRs: dense rows with CI dots, grouped sections, muted merged/closed rows.
Update tests to match new mobile layouts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix mobile terminal padding with PR sheet layout

* Polish mobile feed and session detail styling

* Align mobile dashboard layouts with gstack designs

* Fix mobile terminal actions and PR review labels

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 16:30:03 +05:30
Dhruv Sharma aa2177698a feat(spawn): support prompt-driven sessions without a tracker issue (#974)
- Add --prompt <text> flag to `ao spawn` CLI
- Accept prompt field in POST /api/spawn web route
- Persist userPrompt to session metadata on spawn
- Add userPrompt to DashboardSession type and serialize.ts mapping
- Show userPrompt in SessionCard footer for prompt-only sessions
- Include userPrompt in SessionDetail headline fallback chain
- Update orchestrator-prompt.ts with prompt-driven spawn examples

Closes #974
2026-04-09 18:43:55 +05:30
Ashish Huddar 2ce2f484a0 fix: polish shimmer states, terminal PR inference, and gitignore .gstack
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:52:00 +05:30
Ashish Huddar db8c69da72 Merge origin/main into ashish921998/fix-session-cleanup 2026-04-05 17:24:38 +05:30
Ashish Huddar 661e654901 Improve dashboard fast-path session hydration
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.
2026-04-05 17:18:36 +05:30
Harsh Batheja 53ef778357
feat: add CI failure detail notifications in lifecycle manager (#850)
* feat: add CI failure detail notifications in lifecycle manager

When CI fails on a PR, the lifecycle manager now fetches individual check
details (names, statuses, URLs) and sends them to the worker session.
This complements the existing static reaction message with actionable
debugging information.

Flow:
- On first transition to ci_failed: static reaction message fires (existing)
- On next poll: detailed CI failure info with check names and URLs dispatched
- Fingerprinting prevents re-sending the same failure set
- New/changed failures trigger fresh detailed notifications
- Tracking metadata cleared when PR is merged/closed or CI passes

Follows the same deduplication pattern as maybeDispatchReviewBacklog().

* fix: send CI details directly to avoid consuming reaction retry budget

The detailed CI failure dispatch now uses sessionManager.send() directly
instead of executeReaction(), so it doesn't increment the ci-failed
reaction tracker. This prevents low retries/escalateAfter settings from
causing premature escalation before the agent receives failure details.

The transition reaction still owns escalation; the detailed dispatch is
purely informational follow-up delivery.

* feat: add merge conflict notifications in lifecycle manager

Adds maybeDispatchMergeConflicts() that detects merge conflicts from
the PR enrichment cache or getMergeability() and notifies the worker
session. Conflicts are dispatched independently of session status since
they can coexist with ci_failed, changes_requested, etc.

- Uses the existing merge-conflicts reaction config
- Dispatches once per conflict occurrence (tracks lastMergeConflictDispatched)
- Clears tracking when conflicts resolve, allowing re-dispatch if they recur
- Sends directly via sessionManager.send() (same pattern as CI details)

* fix: use CICheck type instead of inline type declaration

Replace inline Array<{ name, status, url, conclusion }> with the
existing CICheck type from ./types.js for formatCIFailureMessage and
the checks variable in maybeDispatchCIFailureDetails.

* fix: resolve no-useless-assignment lint error in merge conflict check

Declare hasConflicts without initial value since both branches of the
if/else assign to it before it's read.

* feat: show agent notification state in session page blockers

The blockers section on both SessionDetail (IssuesList) and SessionCard
(alert pills) now shows whether the agent has been notified about each
blocker. Reads lifecycle manager dispatch metadata:
- lastCIFailureDispatchHash for CI failures
- lastMergeConflictDispatched for merge conflicts
- lastPendingReviewDispatchHash for review comments

Displays "agent notified" indicator next to blockers where the lifecycle
manager has already forwarded the issue to the worker session.

* fix: use lifecycle status as fallback for blockers when PR data is stale

The blockers section now uses the lifecycle manager's session status
metadata as a source of truth when PR enrichment data hasn't caught up.
PR enrichment uses a 5-min cache and can timeout or be rate-limited,
causing blockers to show stale/incorrect state.

Changes:
- IssuesList and getAlerts now check metadata["status"] (lifecycle
  manager state) alongside PR enrichment data
- CI failing: shown when pr.ciStatus is "failing" OR lifecycle status
  is "ci_failed"
- Changes requested: shown when pr.reviewDecision matches OR lifecycle
  status is "changes_requested"
- Merge conflicts: shown when pr.mergeability.noConflicts is false OR
  lifecycle dispatch metadata indicates conflicts were detected

* fix: fall back to getMergeability when cached hasConflicts is undefined

When PREnrichmentData has hasConflicts as undefined (the field is typed
as boolean | undefined), the previous check treated it as no conflicts.
Now falls through to the getMergeability() call instead.

* test: add coverage for CI/conflict notify action and recovery paths

- Test CI tracking clears when CI recovers to passing
- Test notify action for CI failure details (human notification path)
- Test notify action for merge conflicts (human notification path)

These cover the previously uncovered notify action branches and the
CI recovery cleanup path in the lifecycle manager.

* fix: resolve typecheck error in CI recovery test

writeMetadata requires SessionMetadata type which doesn't include
custom keys like lastCIFailureFingerprint. Rewrote the test to use
setupCheck and let the lifecycle manager set tracking metadata
naturally through the CI failure flow, then verify cleanup on recovery.

* fix: don't use dispatch metadata for conflict detection in UI

lastMergeConflictDispatched lingers after conflicts resolve until the
lifecycle manager's next poll clears it. Using it as a conflict signal
caused stale "merge conflict" alerts. Now only pr.mergeability.noConflicts
drives conflict detection; the metadata is only used for the "agent
notified" badge.

* fix: use Promise.allSettled for dispatch functions to avoid orphaned rejections

Promise.all rejects immediately on first failure, leaving in-flight
promises unmonitored. Promise.allSettled waits for all to complete.
2026-04-02 11:23:08 +05:30
Ashish Huddar 128c9101c8 Handle dashboard send failures and optimize session polling 2026-03-26 22:28:31 +05:30
Ashish Huddar b69cb9aac2 Guard alert action sends and reset on failure 2026-03-26 11:34:13 +05:30
Ashish Huddar 16c2bf23d1 Fix quick reply success state on send failures 2026-03-26 11:22:50 +05:30
Ashish Huddar 771453e088 Prevent stale sheet state and duplicate quick replies 2026-03-26 01:47:14 +05:30
Ashish Huddar f4518398b1 fix(web): add aria-label to quick-reply textarea and use consistent border token
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:55:03 +05:30
Ashish Huddar 3e678f7f43 feat(web): add quick-reply section on respond cards (mobile)
On mobile (<=767px), session cards with attention level "respond" now
show an inline quick-reply panel with the agent's last message summary
(2-line clamp), Continue/Abort/Skip preset buttons, and an expandable
textarea. Enter submits; all touch targets are >=44px. Hidden on
desktop via CSS-only media query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 22:45:57 +05:30
Ashish Huddar 6b7a8bd20c chore(web): design audit fixes — rename column, refine styles, fix terminal
- Rename "Merge Ready" kanban column to "Ready"
- Refine card design system and globals.css theme tokens
- Fix terminal scrollbar and theme handling in DirectTerminal
- Update SessionDetail layout
- Update tests to match component changes
- Add .gstack/ to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:58:24 +05:30
Ashish Huddar 9ee1660dfa chore(web): refine card design, terminal scrollbar, and add card showcase
Flatten card shadows, add subtle borders, simplify SessionCard markup,
apply macOS-style auto-hide scrollbar to terminal, and add dev card
showcase page for visual testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:32:43 +05:30
Ashish Huddar f9feb4bbf8 chore(web): kanban dashboard redesign — Linear-inspired design system
Redesign the kanban dashboard with a Linear-inspired aesthetic:
- New design tokens (neutral charcoal darks, white-alpha borders, muted status colors, indigo accent)
- Tighter spacing, visible column backgrounds, dark mode weight reduction
- Redesigned SessionCard, ProjectSidebar, and AttentionZone components
- Removed list view toggle, reordered columns (respond → merge → done)
- Added ThemeToggle, Skeleton/EmptyState components, and showcase page
- Exported CI_STATUS and getSizeLabel for component consumption

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:01:31 +05:30
Harsh Batheja 4e93bc1458
perf: reduce dashboard SSE refresh and render churn (#451)
* 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>
2026-03-13 09:26:07 +05:30
prateek 40c1906d41
feat(web): redesign dashboard, session detail, and orchestrator terminal (#125)
* docs: add design research artifacts — briefs, token reference, screenshots

Comprehensive design research package for the ao dashboard, session
detail page, and orchestrator terminal. Produced via competitive analysis
of 14 products (Linear, Vercel, Railway, Fly.io, Inngest, WandB, LangSmith,
Supabase, and more) + Playwright CSS extraction from live sites + full
codebase audit.

Artifacts:
- docs/design/design-brief.md            Main design brief (v2, Playwright-updated)
- docs/design/session-detail-design-brief.md   /sessions/[id] design spec
- docs/design/orchestrator-terminal-design-brief.md  Orchestrator page spec
- docs/design/token-reference.css        Drop-in CSS replacement for globals.css
- docs/design/competitive-analysis-raw.md  Raw research notes, all 14 sites
- docs/design/design-brief-v1.md         Original text-only brief (pre-Playwright)
- docs/design/README.md                  Index + research methods summary
- docs/design/screenshots/linear-homepage.png   Playwright-captured screenshot
- docs/design/screenshots/railway-homepage.png  Playwright-captured screenshot

Key findings:
- Linear CSS token values verified via Playwright (body bg #08090A, accent
  #7070FF, Berkeley Mono monospace, type scale, radius, transitions)
- Recommended palette: #0C0C11 base (blue-cast dark vs current GitHub #0d1117)
- Highest-impact change: load Inter Variable via next/font/google
- Orchestrator terminal needs visual differentiation (violet accent, status strip)
- token-reference.css is ready to drop into packages/web/src/app/globals.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): redesign dashboard, session detail, and orchestrator terminal

Implements a cohesive dense dark-mode design system across all three main views.

- New color token palette: #0c0c11 base, #141419 surface, #1c1c25 elevated
- Accent blue #5b7ef8, status semantics (ready/error/attention/working/idle/done)
- Violet accent #a371f7 reserved for orchestrator
- Inter Variable + JetBrains Mono loaded via next/font with CSS variables
- activity-pulse keyframe for live agent dots

- AttentionZone header: dot + label + flex divider + count pill + chevron
- Sessions laid out in responsive 1→2→3 column grid
- Solid green merge button (translateY hover), no confirm() dialog

- Breadcrumb nav: ← Agent Orchestrator / {session-id} [orchestrator badge]
- CSS 8×8px activity dot with pulse animation replaces emoji labels
- Merge-ready state: green-bordered banner with checkmark icon
- Orchestrator sessions show zone counts strip (merge/respond/review counts)

- xterm.js dark theme (#0a0a0f bg, #d4d4d8 fg, full 16-color ANSI palette)
- variant prop: "agent" (blue cursor) vs "orchestrator" (violet cursor)
- Dynamic height prop instead of fixed 600px; fullscreen toggle with SVG icons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(web): strip rainbow stats, clean header, IBM Plex Sans typography

- Replace Inter with IBM Plex Sans (technical tool aesthetic, distinctive numerics)
- Replace 4-color big-number stats bar with a single compact inline status line
  in the header: "35 sessions · 1 working · 9 PRs" — no decorative colors
- Remove the two-tone "Agent (blue) Orchestrator (white)" title — just "Orchestrator"
- Remove ClientTimestamp (useless) — replaced by orchestrator nav link
- Zone headers: colored dot only (semantic), neutral uppercase label, plain count
  — removes the rainbow-colored label text that read as a widget template
- Add subtle radial gradient glow at top of page for depth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): kanban layout, amber accent, full-width, bigger stats

- Switch accent from blue (#5b7ef8) to amber/gold (#d18616) throughout
- Replace grid layout with horizontal Kanban columns for active zones
  (merge, respond, review, pending, working), Done stays full-width below
- Remove max-w-[1100px] constraint — full viewport width
- Header stats numbers 20px bold (was 12px), orchestrator link is now a
  visible bordered button
- AttentionZone gains variant="column" for Kanban mode (compact header
  with count pill, vertical card stack)
- Update all hardcoded rgba(91,126,248,...) in SessionCard to amber

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): layout, alert sizing, column order, button feel

- Kanban column order: working→pending→review→respond→merge
  (left = in progress, right = ready to ship)
- Columns use flex-1 min-w-[200px] to fill available width
  instead of fixed 260px leaving half the page empty
- Alert badges: inline-flex wrapper prevents stretching to full
  row width when wrapping
- Terminal button: add bg-subtle fill so it reads as a button
- PR number (#91): remove opaque pill background, now plain
  amber text link — clearly a hyperlink
- Merge PR button: pt-0.5 spacer above the action area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): don't cache rate-limited partial PR data

When GitHub rate limits fire, enrichSessionPR was caching the
bad partial data (0 additions, CI failing) for 60 seconds, causing
the dashboard to show incorrect data for the full TTL window.

- Skip cache write when majority of API calls failed
- Downgrade console.error → console.warn (this is handled/expected)

The next page refresh will retry live API calls, so data recovers
as soon as the rate limit window resets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): graceful GitHub API rate limit handling in UI

When the GitHub plugin hits rate limits, the dashboard now:

- Shows a single amber banner: "GitHub API rate limited — PR data
  (CI status, review state, sizes) may be stale. Will retry on
  next refresh."
- Hides CI badge, review decision, and size pill on PR cards
  (they'd show wrong values: +0 -0 XS, CI failing)
- Shows a subtle "⚠ PR data rate limited" note on affected cards
  instead of misleading alert badges
- Skips CI/review/conflict-based attention zone classification
  for rate-limited PRs (prevents sessions moving to Review due
  to phantom "CI failing" from the fallback value)
- Doesn't cache partial rate-limited data so next refresh retries
  live API calls as soon as the rate limit window resets

What still works when rate limited:
- Session ID, title, branch, PR number/link
- Session activity status (working/spawning/etc.)
- Merge button if mergeability was already cached
- Restore/terminate/send actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): dashboard redesign — glassmorphism, Kanban, rate-limit handling, perf fix

Design:
- Kanban layout: active zones as flex columns (working→pending→review→respond→merge),
  Done as full-width grid below
- GitHub dark color palette (main's tokens) with glassmorphic card surfaces
  (rgba bg + backdrop-blur) and subtle blue/violet body gradient
- Activity state shown as labeled pill (● active / ● idle etc.) instead of bare dot
- Session card: title on its own row, larger font, inline-flex alert badges (no stretch)
- PR number rendered as plain accent link, not a blue pill badge
- Terminal button has background fill to feel like a button
- Info circle icon replaces alarming warning triangle for rate-limit indicators
- "1 working" → "1 active" in header stats
- PR table constrained to max-w-[900px] and centered
- Orchestrator session no longer uses purple accent

Rate limiting:
- isPRRateLimited() helper; getAttentionLevel() skips PR classification when limited
- Rate-limited banner in Dashboard; suppressed CI/size/review badges in PRStatus
- SessionCard shows subtle "PR data rate limited" indicator; getAlerts() returns []
- serialize.ts: rate-limited enrichment results cached for 5 min (not 60s) to stop
  retrying 168 failing API calls every minute

Performance:
- page.tsx: 4s hard timeout on PR enrichment — serves stale data fast instead of
  blocking SSR for 75s under rate limiting
- cache.ts: TTLCache.set() accepts optional ttl override for per-entry control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress stale size/CI/review in PR table when rate limited

PRTableRow now shows "—" for size, CI, and review columns when GitHub
API is rate limited, matching the card view which already hides these.
Prevents misleading "+0 -0 XS" size and "needs review" labels from the
default fallback values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: 3D card effect with depth shadow and top-edge shine

Cards now clearly pop against the dark background:
- Solid gradient bg (rgba(28,36,47) → rgba(18,23,31)) instead of
  near-invisible rgba(22,27,34,0.8) surface
- Layered box-shadow: contact shadow + diffuse depth + inset top highlight
  that simulates light hitting the card's top edge (the "shine")
- Hover: card lifts 2px with deeper shadow
- Merge-ready: green-tinted bg with green ambient glow + stronger lift on hover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore text legibility inside session cards

The darker solid card gradient made muted/secondary text nearly
invisible — #484f58 (text-muted) had only ~2:1 contrast on the
new card bg. Override the color tokens locally within .session-card
to GitHub's established dark-mode legibility values:

  --color-text-muted:     #484f58 → #656d76  (3.8:1 on card bg)
  --color-text-secondary: #7d8590 → #8b949e  (6.2:1 on card bg)
  --color-text-tertiary:  #484f58 → #656d76

Scoped to .session-card so the rest of the UI is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address bugbot comments — fonts, review zone, ActivityDot, orchestrator btn

- layout.tsx: add IBM Plex Sans weight 700 (was missing, font-bold falling
  back to 600)
- DirectTerminal.tsx: use "IBM Plex Mono" instead of unloaded "JetBrains Mono"
- SessionDetail.tsx: add review zone to OrchestratorStatusStrip (was omitted,
  sessions with CI failures were invisible in the strip)
- ActivityDot.tsx: extract shared component, remove duplicate implementations
  in SessionCard.tsx and SessionDetail.tsx
- Dashboard.tsx: redesign orchestrator button with 3D glass style matching
  card aesthetic (blue-tinted bg, depth shadow, hover lift)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): lint — eqeqeq, duplicate import, unused var

- ActivityDot.tsx: != → !== (eqeqeq rule)
- PRStatus.tsx: merge duplicate @/lib/types imports into one
- SessionCard.tsx: remove unused activityIcon import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): elevate session detail + orchestrator page design

- Nav: glass backdrop-blur effect with chevron back link
- Header: detail-card 3D treatment with left-border accent keyed to activity color
- Meta chips: bordered pill style with subtle bg instead of flat text
- Status tag: pill badge for status instead of plain text
- PR card: detail-card 3D treatment, border-color reflects PR state
- PR merged badge: purple pill instead of gray text
- Unresolved count: red pill badge in section header
- Blockers section: renamed "Issues" → "Blockers"
- Terminal section: colored bar indicator instead of plain label
- Orchestrator status strip: total agent count + per-zone colored pills
- globals.css: add .nav-glass and .detail-card classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): fetchZoneCounts parses body.sessions, delayed 2s to avoid contention

The /api/sessions endpoint returns `{ sessions: [...] }` not a bare array.
fetchZoneCounts was treating the whole response object as an array, so
zone counts were always zero on the orchestrator detail page.

Also delays the initial fetchZoneCounts call by 2s so it doesn't contend
with the session fetch on page load (both hit /api/sessions which is slow
when GitHub enrichment is running).

Also includes: Playwright kill-Chrome-for-Testing tip in CLAUDE.md,
toned-down detail-card shadow in globals.css.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf+test(web): cache-first PR enrichment, skip exited sessions, fix component tests

Performance improvements:
- enrichSessionPR() now accepts cacheOnly option and returns boolean
- /api/sessions/[id]: serve from cache immediately, only block on first load
- /api/sessions: skip PR enrichment for EXITED sessions (no longer changing)
- cache: increase default TTL from 60s to 5 minutes

Test fixes (match redesigned SessionCard + AttentionZone):
- "restore session" (header) → "restore"; expanded panel still shows "restore session"
- "merge PR #N" → "Merge PR #N" (capital M)
- "CI status unknown" → "CI unknown"
- "ask to fix CI" / "ask to fix CI" → "ask to fix"
- "terminate session" → "terminate"
- Zone labels: RESPOND/WORKING/DONE → Respond/Working/Done (CSS uppercase is visual only)
- "working" zone no longer collapsed by default; collapse tests now use "done" zone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(core): ActivityDetection with timestamp propagation

- Add ActivityDetection interface { state, timestamp? } to types.ts
- Agent getActivityState() returns ActivityDetection | null instead of
  ActivityState | null, allowing timestamp from JSONL mtime to propagate
- session-manager updates session.lastActivityAt when detected.timestamp
  is more recent — fixes "active 22h ago" showing stale timestamps
- Update all agent plugins (claude-code, aider, codex, opencode) to
  return ActivityDetection objects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): dismissible rate limit banner + 60min rate-limit cache TTL

- Add X dismiss button to GitHub API rate limit banner in Dashboard.tsx
  so it can be closed during demos
- Extend rate-limited PR cache TTL from 5min to 60min — GitHub GraphQL
  rate limits reset hourly, no point retrying every 5 minutes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): address Cursor Bugbot review comments on PR #125

- Dashboard StatusLine: active sessions count now uses var(--color-status-working)
  (blue) instead of neutral text color, matching the design system semantics
- SessionCard: isReadyToMerge now guards against rate-limited state — a card
  with stale cached mergeability data won't show green merge-ready styling
- DirectTerminal: add `variant` to useEffect dependency array (was missing,
  causing stale cursor/selection colors if variant changed after mount)
- agent-aider: include `timestamp: chatMtime` in all ActivityDetection returns,
  matching the pattern used by agent-claude-code (enables accurate lastActivityAt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): resolve lint, typecheck, and test failures

Lint:
- Remove unused parseJsonlFile function (superseded by parseJsonlFileTail)
- Remove dead lastLogModified stat() call in getSessionInfo (field was
  removed from AgentSessionInfo but the filesystem read was left behind)

Typecheck + Tests (ActivityDetection):
- session-manager.test.ts: update mocks to return { state: "active" } /
  { state: "idle" } instead of bare strings — getActivityState() returns
  ActivityDetection | null, not ActivityState | null
- integration tests (aider, claude-code, codex, opencode): update imports
  from ActivityState → ActivityDetection, variable types, comparisons
  (activityState?.state !== "exited"), and assertions (?.state).toBe()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: parseJsonlFileTail uses readFile for small files; enrich exited sessions with PRs

- parseJsonlFileTail now calls stat() then readFile() for files smaller than
  maxBytes, falling back to open()/handle.read() only for large files. This
  fixes the test infrastructure (which mocks readFile but not open) and also
  fixes a scope bug where `offset` was declared inside an inner try block but
  referenced outside both try blocks.
- Math.max(0, NaN) returns NaN not 0, so size must default to 0 when stat
  returns a mock without a size field: `const { size = 0 } = await stat(...)`.
- Update activity-detection.test.ts: getActivityState() now returns
  ActivityDetection objects, so tests use (await ...)?.state comparisons.
- Remove stale lastLogModified test (field was removed from AgentSessionInfo).
- Remove EXITED skip guard from api/sessions/route.ts: exited sessions can
  still have open, merge-ready PRs that need enrichment on the dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: comprehensive code review fixes — tests, timestamps, UI correctness

Address gaps identified in code review of the ActivityDetection PR:

Core / Session Manager:
- Add `timestamp` to all `{ state: "exited" }` returns in all 4 agent plugins
  (claude-code, aider, codex, opencode) using consistent `exitedAt = new Date()` pattern
- Add 2 new session-manager tests: timestamp propagation when detection timestamp
  is newer, and no-downgrade when detection timestamp is older
- Fix `parseJsonlFileTail` lint error: remove useless `= 0` initializer (value was
  always overwritten before use; catch block returns early)

Web package — tests:
- Fix 3 `api-routes.test.ts` failures: `sessionsGET()` needs a Request object since
  the route reads `request.url` for `?active=true` query param
- Fix `serialize.test.ts` rate-limit test: spy on `console.warn` (what the code uses)
  not `console.error`
- Add 5 `ActivityDot` component tests covering all activity states, unknown states,
  null activity, and dotOnly mode

Web package — UI correctness:
- Fix `relativeTime()` in SessionDetail to guard against invalid/empty ISO strings
- Fix timer Map leak: add `timersRef.current.clear()` in cleanup effect after forEach
- Add `encodeURIComponent` to sessionId in message fetch URL

Server — race condition fix:
- Guard `activeSessions.delete` in pty.onExit, ws.on("close"), and ws.on("error")
  against stale handlers deleting a newly-registered session with the same ID.
  Fixes flaky integration test where afterEach's pty.kill() fired asynchronously
  after the next test had already set up a new session with the same session ID.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): narrow PREnrichmentData types to eliminate unsafe casts in serialize

PREnrichmentData.ciStatus and .reviewDecision were typed as string,
requiring unsafe `as` casts when reading from cache into DashboardPR.
Narrow them to the same literal union types used by DashboardPR, making
the casts unnecessary. Also narrow ciChecks[].status to match CoreCICheck.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 18:43:57 +05:30
prateek 0e2ca70b0b
feat: session title fallback chain for PR-less sessions (#105)
* feat: session title fallback chain — PR title → summary → issue title → branch

Sessions without PRs now always show a meaningful title on the dashboard
instead of just the status text. The fallback chain is:

1. PR title (already worked)
2. Agent summary (now fetched from JSONL via getSessionInfo())
3. Issue title (now fetched via tracker.getIssue())
4. Humanized branch name (e.g., "feat/infer-project-id" → "Infer Project ID")

Key changes:
- Enrich agent summaries by calling getSessionInfo() for sessions
  without summaries (local file I/O, not API calls)
- Enrich issue titles via tracker.getIssue() with 5-min TTL cache
- Add humanizeBranch() utility for last-resort branch name display
- Add issueTitle field to DashboardSession type
- Show issue title in expanded detail panel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: extract humanizeBranch to separate module to avoid client-side timer leaks

Moves humanizeBranch() from serialize.ts to format.ts — a pure utility
module with no side effects. This prevents the client bundle from pulling
in TTLCache instantiations (which create setInterval timers) when
SessionCard.tsx imports the function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead re-export of humanizeBranch from serialize.ts

No consumer imports humanizeBranch from serialize — SessionCard imports
directly from format.ts. The re-export was unused surface area.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing first-project fallback in summary enrichment block

Matches the pattern used by all other enrichment blocks in page.tsx
(issue labels, issue titles, PR enrichment) which fall back to the
first configured project when projectId and sessionPrefix both miss.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: smarter title heuristic — skip prompt excerpts, prefer issue titles

The agent summary fallback from extractSummary() often returns truncated
spawn prompts ("You are working on GitHub issue #42: Add auth...") which
make poor titles. The new heuristic detects these prompt excerpts and
prefers the issue title when available.

Updated fallback chain:
  PR title → quality summary → issue title → any summary → humanized branch → status

Changes:
- Add looksLikePromptExcerpt() to detect spawn prompt patterns
- Add getSessionTitle() to encapsulate the smart fallback logic
- Expand humanizeBranch() with more prefix patterns (release, hotfix, etc.)
- SessionCard now uses getSessionTitle() instead of inline ?? chain
- Add 25 unit tests covering all functions and edge cases
- Fix missing issueTitle field in serialize.test.ts fixture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shared resolveProject() to eliminate duplication

Moves resolveProject() from route.ts into serialize.ts as a shared
export. Both page.tsx and route.ts now use the same function instead
of duplicating the 3-step project resolution logic (projectId →
sessionPrefix → first project fallback) inline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: replace looksLikePromptExcerpt heuristic with summaryIsFallback metadata

Instead of fragile string matching to detect truncated spawn prompts,
the agent plugin now sets summaryIsFallback: true when the summary is
a first-message fallback rather than a real agent-generated summary.

- Add summaryIsFallback to AgentSessionInfo (core/types.ts)
- extractSummary() returns { summary, isFallback } in claude-code plugin
- Add summaryIsFallback to DashboardSession, propagate in serialize.ts
- Replace looksLikePromptExcerpt() with !session.summaryIsFallback
- Fix .js extension in format.ts import (review feedback)
- Add thorough tests for all layers of propagation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add resolveProject and enrichSessionIssueTitle coverage

- resolveProject: 5 tests covering direct match, prefix fallback,
  first-project fallback, empty projects, and priority ordering
- enrichSessionIssueTitle: 7 tests covering enrichment, # prefix
  stripping, Linear-style labels, skip conditions, error handling,
  and cross-call caching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: extract shared enrichSessionsMetadata, fix session detail route

- Extract duplicated enrichment orchestration (issue labels, agent
  summaries, issue titles) from page.tsx and route.ts into a single
  enrichSessionsMetadata() function in serialize.ts
- Fix /api/sessions/[id] route: was missing agent summary and issue
  title enrichment, and had hand-rolled project resolution instead of
  using resolveProject() (also missing the first-project fallback)
- Optimize: resolve projects once per session instead of 3x
- Add 8 tests for enrichSessionsMetadata covering full pipeline, skip
  conditions, missing plugins, no-tracker config, multiple sessions,
  and default agent fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove dead getAgent and getTracker exports from services.ts

These helpers became unused when enrichSessionsMetadata was extracted
to serialize.ts with inline registry.get() calls (to avoid coupling
serialize.ts to services.ts and pulling plugin packages into webpack).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:02:02 +05:30
prateek b605ee8ed4
feat: dynamic tab titles and health-aware favicons (#111)
* feat: dynamic browser tab titles and health-aware favicons

Tabs now show contextual titles so multiple dashboard instances are
distinguishable at a glance. Favicons reflect system health (green/
yellow/red) and display the project initial for visual identification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review — dedupe project name, handle merge level, add activity emoji

- Extract getProjectName() to shared lib/project-name.ts (used by
  layout, page, icon) — fixes bugbot duplication comment
- computeHealth now treats "merge" attention level as yellow (needs
  human action) instead of silently mapping to green — fixes bugbot
  merge-ignored comment
- Add activity status emoji to session tab titles (🟢💤🚧💀)
  that updates live as session state changes
- Special-case orchestrator sessions: "ao-orchestrator | Orchestrator Terminal"
- Extract activityIcon map to shared lib/activity-icons.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use absolute title to avoid layout template duplication

The layout template `%s | project` was wrapping the page title
`project | Agent Orchestrator`, producing `project | Agent Orchestrator | project`.
Use `title.absolute` to opt out of the template on the root page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use "ao | <project>" format for dashboard title

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dedupe config reads with React.cache() on getProjectName

Wraps getProjectName with React.cache() so layout, page, and icon
share a single loadConfig() call per server render pass instead of
reading the YAML file three times.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:21:49 +05:30
prateek 65fa811b3b
feat: implement session restore for crashed/exited agents (#104)
* feat: implement session restore for crashed/exited agents

Add true in-place session restore: same session ID, same worktree, same
metadata — optionally resuming the Claude Code conversation via --resume.

Core changes:
- Add TERMINAL_STATUSES, TERMINAL_ACTIVITIES, NON_RESTORABLE_STATUSES sets
  and isTerminalSession/isRestorable helpers to types.ts
- Add SessionNotRestorableError and WorkspaceMissingError error classes
- Add restore() to SessionManager with 9-step flow: find metadata →
  validate restorability → check/recreate workspace → get restore or
  launch command → create runtime → update metadata
- Add restoredAt field to Session and SessionMetadata

Plugin extensions:
- workspace-worktree: exists() + restore() (git worktree prune + re-add)
- workspace-clone: exists() + restore() (git clone + checkout)
- scm-github: branchExists() via git rev-parse
- agent-claude-code: getRestoreCommand() finds latest JSONL session file
  and builds claude --resume command

CLI + Web:
- Add `ao session restore <id>` subcommand
- Web restore API route uses sessionManager.restore() instead of spawn()
- SessionCard uses centralized TERMINAL_STATUSES/TERMINAL_ACTIVITIES
- Web types re-export core constants with sync tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add "merged" to TERMINAL_STATUSES

The old inline isTerminal check included "merged" but when refactored
to use the TERMINAL_STATUSES set, "merged" was omitted. This caused
merged sessions (whose activity is not "exited") to incorrectly show
the "terminal" link and "terminate session" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enrich runtime state before restore check, remove dead branchExists

- Add enrichSessionWithRuntimeState() call before isRestorable() in
  restore() so crashed sessions (status "working", agent exited) are
  correctly detected as terminal and eligible for restore.
- Remove dead branchExists from SCM interface and scm-github plugin
  (defined but never called anywhere in the codebase).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: allow restore of crashed working sessions

Remove "working" from NON_RESTORABLE_STATUSES. The isTerminalSession()
gate already prevents restoring truly active sessions (activity is not
"exited"). This fix allows crashed agents (status "working", activity
"exited") to be restored, aligning core behavior with the UI which
already shows the restore button for this case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: distinguish missing branch from missing restore support

Split the compound condition so workspace restore gives an accurate
error message when branch metadata is null ("branch metadata is
missing") vs when the workspace plugin lacks a restore method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 01:12:57 +05:30
prateek 73957182f7
fix: activity detection — fix path encoding bug, add ready state (#71)
* fix: activity detection — fix path encoding, use tail -1 for JSONL

Two tightly coupled infrastructure fixes:

- Fix toClaudeProjectPath(): leading `/` becomes `-` (not stripped),
  matching Claude Code's actual project directory naming convention.
- Replace manual 4KB buffer read in readLastJsonlEntry() with
  `tail -1` + JSON.parse — handles any file size, any line length,
  and eliminates the truncated-line edge case entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add "ready" state, return null when unknown, remove dead code

Behavioral changes to activity detection:

- Add "ready" to ActivityState — separates "alive at prompt" from
  "idle/stale". Configurable via readyThresholdMs (default 5 min).
- Agent plugins return null when they can't determine activity
  (no workspace, no JSONL, no per-session tracking). Session manager
  preserves existing activity instead of overwriting with a guess.
- Remove isProcessing() from Agent interface — zero callers in
  production code, fully superseded by getActivityState().
- Remove extractLastMessageType() from claude-code — the field it
  populated (lastMessageType) was only consumed by the old inline
  CLI mapping, which is now replaced by plugin delegation.
- CLI status delegates to agent.getActivityState() (single source
  of truth) with metadata fallback when plugin returns null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: comprehensive activity detection coverage

- activity-detection.test.ts: 42+ tests covering path encoding,
  getActivityState edge cases (exited/null/fallback), real Claude Code
  JSONL types, agent interface spec types, staleness thresholds,
  JSONL file selection, and realistic session sequences.
- status.test.ts: plugin delegation tests — verifies CLI uses
  agent.getActivityState() as single source of truth, passes
  readyThresholdMs from config, falls back to metadata on null/throw.
- Integration tests: updated type expectations for null returns from
  codex, opencode, and aider; added "ready" to valid state lists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: flaky Linear integration test + missing ready label in SessionDetail

Linear API has eventual consistency — updateIssue state changes don't
propagate instantly. Poll with retries instead of asserting immediately.

Also adds "ready" entry to SessionDetail activityLabel map (was missing,
causing fallback to dim/unstyled rendering).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pure Node.js readLastJsonlEntry, use pollUntilEqual for Linear test

Replace `tail -1` with pure Node.js implementation that reads backwards
from end of file in 4KB chunks. No external binary dependency — works
on any platform.

Fix flaky Linear integration test by using the existing pollUntilEqual
helper instead of an inline retry loop. Linear API has eventual
consistency; pollUntilEqual retries for up to 5s with 500ms intervals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use tail -1 for readLastJsonlEntry, add real-data integration test

Replace over-engineered pure Node.js backward-reading implementation with
simple `tail -1` via execFile. The codebase already shells out to tmux,
git, and ps everywhere — tail is no different.

Add integration test that validates toClaudeProjectPath() and
readLastJsonlEntry() against real ~/.claude/projects/ data on disk.
No API key needed — just requires Claude to have been run once.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 03:48:19 +05:30
prateek eaea131af9
feat: seamless onboarding with enhanced documentation (#66)
* feat: implement seamless onboarding with enhanced documentation

- Add comprehensive README.md (18KB) with quick start, core concepts, and FAQ
- Add detailed SETUP.md (16.5KB) with prerequisites, integration guides, and troubleshooting
- Add examples/ directory with 5 ready-to-use config templates:
  - simple-github.yaml: Minimal GitHub setup
  - linear-team.yaml: Linear integration
  - multi-project.yaml: Multiple repos
  - auto-merge.yaml: Aggressive automation
  - codex-integration.yaml: Using Codex agent

- Add environment detection (git repo, remote, branch, auth status)
- Auto-fill prompts with smart defaults from detected environment
- Add prerequisite validation (git, tmux, gh CLI)
- Show actionable next steps and warnings
- Parse owner/repo from git remote automatically
- Detect LINEAR_API_KEY and SLACK_WEBHOOK_URL in environment
- Prompt for Linear team ID when Linear tracker selected

- Format all files with Prettier for consistency

Reduces onboarding time from 30+ minutes to ~5 minutes:
1. Install CLI: `npm install -g @composio/ao-cli`
2. Run init: `ao init` (auto-detects everything)
3. Spawn agent: `ao spawn my-project ISSUE-123`

Users no longer need to:
- Manually parse git remote URLs
- Look up current branch names
- Remember YAML syntax
- Search for Linear team IDs
- Debug missing prerequisites

-  pnpm build - All packages compile
-  pnpm typecheck - No TypeScript errors
-  pnpm lint - No new linting issues
-  pnpm format - All files formatted

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: update installation instructions to reflect npm not yet published

Package is not published to npm yet, so users must build from source.
Updated README.md and SETUP.md to:
- Make 'build from source' the primary installation method
- Add note that npm publishing is coming soon
- Include pnpm as a prerequisite

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: add ao init --auto --smart for zero-config setup

Implements intelligent config generation with project type detection.

## What's New

### ao init --auto
- Zero prompts - auto-generates config with smart defaults
- Detects: git repo, remote, branch, languages, frameworks, tools
- Generates project-specific agentRules based on detected tech stack

### Project Detection
- Languages: TypeScript, JavaScript, Python, Go, Rust
- Frameworks: React, Next.js, Vue, Express, FastAPI, Django, Flask
- Tools: pnpm workspaces, test frameworks
- Package managers: pnpm, yarn, npm

### Rule Templates
Created templates for:
- base.md - Universal best practices
- typescript.md - TS strict mode, ESM, type imports
- javascript.md - Modern ES6+ patterns
- react.md - Hooks, composition, best practices
- nextjs.md - App Router, Server Components
- python.md - Type hints, PEP 8
- go.md - Error handling, defer patterns
- pnpm-workspaces.md - Monorepo commands

### Example Output

```bash
ao init --auto

# Detects:
# ✓ TypeScript + pnpm workspaces
# ✓ React + Next.js
# ✓ Vitest

# Generates:
agentRules: |
  Always run tests before pushing.
  Use TypeScript strict mode.
  Use ESM modules with .js extensions.
  Use React best practices (hooks, composition).
  Before pushing: pnpm build && pnpm typecheck && pnpm lint && pnpm test
```

## Benefits

- **5 seconds** instead of 5 minutes
- **Zero config knowledge** required
- **Context-aware rules** tailored to your stack
- **Still customizable** - edit the generated config

## Future: --smart (AI-powered)

Flag added but not yet implemented. Will use Claude Code to:
- Analyze CLAUDE.md, CONTRIBUTING.md
- Read CI/CD config
- Generate custom rules based on project patterns

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: detect repo default branch instead of current branch

Fixes Bugbot issue: "Current branch wrongly suggested as default base branch"

## Problem

detectEnvironment was using `git branch --show-current` to suggest
defaultBranch in the config. If a user ran `ao init` while on a feature
branch like `feat/my-work`, the wizard would suggest that feature branch
as the default, causing agents to branch from the wrong base.

## Solution

Added detectDefaultBranch() function with 3 fallback methods:
1. git symbolic-ref refs/remotes/origin/HEAD (most reliable)
2. GitHub API via gh CLI (if ownerRepo known)
3. Check common branch names: main, master, next, develop

Now EnvironmentInfo tracks both:
- currentBranch: The checked-out branch (for display only)
- defaultBranch: The repo's base branch (for config)

## Testing

Tested on feat/seamless-onboarding branch:
- Current branch: feat/seamless-onboarding (displayed)
- Default branch: main (correctly detected for config)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: prevent duplicate framework detection in Python projects

Fixes Bugbot issue: "Duplicate frameworks when multiple Python config files exist"

## Problem

When both requirements.txt and pyproject.toml exist and mention the same
framework (e.g., FastAPI), the detection loop added it to the frameworks
array twice, causing duplicate rules in the generated config.

## Solution

Added addFramework() helper that checks if framework already exists before
adding to the array. Also prevents pytest from being set multiple times as
testFramework.

## Testing

Verified with test repo containing both files with FastAPI:
- Before: Would add 'fastapi' twice
- After: Only adds 'fastapi' once ✓

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: address Bugbot review comments

- Remove redundant conditional in --smart flag (both branches were identical)
- Include templates directory in npm package files

* fix: add existence check for base.md template file

Add existsSync guard before reading base.md to handle missing templates gracefully, consistent with other template file reads.

* fix: use direct tool invocation instead of which command

Replace 'which' with direct tool invocation (tmux -V, gh --version)
for better portability on minimal Linux systems where 'which' may
not be installed.

* fix: address Bugbot review comments

- Simplify gh auth status check to rely on exit code instead of output string
- Remove async from synchronous functions (detectProjectType, generateRulesFromTemplates)

* feat: add setup script for one-command installation

Add scripts/setup.sh that:
- Installs pnpm if not present
- Installs dependencies
- Builds all packages
- Links CLI globally

Updated README with simplified setup instructions using the script.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: correct npm link command in setup script

Remove incorrect -g flag from npm link command. The correct syntax is to cd into the package directory and run npm link without flags.

* fix: address Bugbot review comments on init command

- Validate --smart flag requires --auto (prevents silent ignore)
- Fix path validation to check user-specified path (not CWD)

These fixes address medium and low severity issues found by Cursor Bugbot
in PR #66 review.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: add DirectTerminal troubleshooting and fix setup script

- Add TROUBLESHOOTING.md documenting node-pty posix_spawnp error
- Update setup.sh to rebuild node-pty from source (fixes DirectTerminal)
- Ensures seamless onboarding with working terminal out-of-the-box

Resolves DirectTerminal WebSocket failures from incompatible prebuilt binaries.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: resolve variable scope issue in init command validation

- Move path variable outside if block to fix TypeScript scope error
- Only validate path existence if projectId is provided
- Use inline tilde expansion instead of missing expandHome import

Fixes build error that prevented setup.sh from completing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: automate node-pty rebuild to eliminate terminal issues

- Add postinstall hook to automatically rebuild node-pty after pnpm install
- Create scripts/rebuild-node-pty.js for automatic rebuild with error handling
- Remove manual node-pty rebuild from setup.sh (now automatic)

This ensures DirectTerminal works correctly on every installation without
manual intervention. Fixes posix_spawnp errors from incompatible prebuilt
binaries across different systems and installations.

Resolves issue where users would encounter blank terminals after setup.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: update TROUBLESHOOTING with automatic node-pty rebuild

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: add comprehensive README with quick start guide

- 3-line magical setup: clone → setup → init → start
- Architecture overview with plugin slots table
- Usage examples and auto-reaction configuration
- Links to detailed docs (SETUP.md, TROUBLESHOOTING.md, examples/)
- Philosophy: push not pull, amplify judgment

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: resolve ESLint errors in rebuild-node-pty script

- Add scripts directory configuration to eslint.config.js
- Configure Node.js globals (console, process) for scripts
- Remove unused error variable from catch block

Fixes lint CI failure.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: warn when auto mode uses placeholder repo value

- Detect when 'owner/repo' placeholder is used in --auto mode
- Show warning: 'Could not detect GitHub repository'
- Update next steps to emphasize editing config when placeholder used
- Prevents silent failures when spawning agents with invalid repo

Addresses Bugbot review comment about silent placeholder values.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:22:13 +05:30
prateek 1cb52c122d
refactor: replace magic strings with constants for status enums (#64)
Replaces hardcoded string literals with typed constants for:
- SESSION_STATUS (needs_input, stuck, errored, etc.)
- PR_STATE (merged, closed, open)
- CI_STATUS (passing, failing, pending, none)

Benefits:
- Type safety - IDE catches typos at compile time
- Autocomplete - Better developer experience
- Single source of truth - Easy to add/remove states
- Easier refactoring - Change constant value in one place
- Better git grep - Search for SESSION_STATUS.STUCK vs all "stuck" strings

Files updated:
- packages/core/src/types.ts - Added constants
- packages/core/src/lifecycle-manager.ts - SessionStatus, PRState, CIStatus
- packages/core/src/session-manager.ts - PRState
- packages/web/src/lib/types.ts - SessionStatus, CIStatus
- packages/web/src/components/SessionCard.tsx - CIStatus
- packages/web/src/components/SessionDetail.tsx - CIStatus
- packages/web/src/components/Dashboard.tsx - CIStatus
- packages/plugins/scm-github/src/index.ts - CIStatus
- packages/plugins/notifier-slack/src/index.ts - CIStatus

Follows pattern established in PR #55 for ACTIVITY_STATE constants.

Resolves INT-1368

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 08:53:17 +05:30
prateek 7ce8dc3480
fix: remove redundant attention level badge from session detail (#41)
* fix: remove redundant attention level badge from session detail page

The detail page was showing both activity ("Idle") and attention level
("Working") as badges, which looked contradictory. Attention level is
for the dashboard overview zones, not the detail page. Keep only the
activity badge.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: terminal button should link to session detail page, not raw xterm URL

The terminal button was fetching from the terminal-web plugin and opening
a raw xterm.js URL (localhost:7801). Changed to a simple link to the
session detail page which has an embedded terminal.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 13:36:16 +05:30
prateek de662dc042
fix: recognize terminated/done session states and hide terminal for dead sessions (#40)
* fix: recognize terminated/done session states and hide terminal for dead sessions

- Add "done" and "terminated" to VALID_STATUSES in session-manager so
  validateStatus() doesn't fall back to "spawning" for these states
- Hide terminal button for terminal-state sessions (no tmux to connect to)
- Hide "terminate session" button for already-terminated sessions
- Show "restore session" button for terminated/done sessions

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: restore activity=exited check for crashed sessions

Bugbot caught that the refactor dropped the activity === "exited"
condition. When an agent crashes, status stays non-terminal (e.g.
"working") but activity becomes "exited" — these need the restore
button and should not show terminal/terminate buttons.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add terminated/done to backend RESTORABLE_STATUSES

Frontend shows restore button for terminated/done sessions but
the backend restore endpoint only accepted killed/cleanup, returning
409 "Session is not in a terminal state" for the new statuses.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add type annotations to fix implicit any errors in integration tests

Pre-existing issue from package rename — callback parameters in
.find() lost type inference. Add explicit type annotations.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: filter orchestrator session from SSR page

The API route filtered it but the SSR path in page.tsx did not,
causing the orchestrator to appear as a session card.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: make orchestrator session name dynamic using prefix convention

Use endsWith("-orchestrator") instead of hardcoded "orchestrator" to
support project-prefixed names like "ao-orchestrator". Pass orchestratorId
from SSR to Dashboard so the terminal button links to the correct session.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: update stale @agent-orchestrator/core imports to @composio/ao-core

Package was renamed in PR #32 but these two files were missed.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 05:15:41 +05:30
prateek c2a0aaeebb
fix: resolve dashboard GitHub API rate limiting and PR enrichment (#37)
* 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>
2026-02-15 04:14:54 +05:30
prateek 620bad9053
Wire xterm.js terminal embed into web dashboard (#29)
* feat: wire xterm.js terminal embed into web dashboard

- Add xterm.js dependencies (@xterm/xterm, @xterm/addon-fit)
- Create SSE streaming endpoint at /api/sessions/:id/terminal
  - Polls tmux capture-pane every 2 seconds
  - Streams ANSI-aware output with colors/formatting
  - Handles session exit gracefully
- Implement Terminal component with xterm.js
  - Live output streaming from tmux pane
  - Fullscreen mode toggle
  - Optional input mode to send messages to agent
  - Read-only by default
- Import xterm.js CSS in globals.css

The terminal shows live agent activity in the browser with full
ANSI color support. Users can optionally enable input mode to
send messages to the running agent.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: improve terminal rendering and remove clunky input interface

- Fix rendering issues:
  - Use term.reset() instead of clear() for proper clearing
  - Only update when content changes (prevents flickering)
  - Add scrollToBottom() to show latest output
  - Increase scrollback buffer to 10000 lines
  - Add convertEol for proper line endings
  - Increase default height to 600px
  - Add padding around terminal content

- Simplify interface:
  - Remove separate input box (was clunky)
  - Make it clearly "Read-only" by default
  - Clean up header UI
  - Better fullscreen sizing calculation

Next step: Consider WebSocket-based bidirectional terminal for
true interactive sessions (like tmux attach).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: implement proper interactive terminal with WebSocket

Replace hacky SSE polling with real-time WebSocket for bidirectional
terminal communication. This is a proper interactive terminal - type
directly, like tmux attach in the browser.

Architecture:
- WebSocket server on port 3001 alongside Next.js
- Uses tmux pipe-pane for real-time output streaming
- Sends input character-by-character via tmux send-keys
- Handles terminal resize events
- Connection status indicator

Implementation:
- packages/web/src/server/terminal-websocket.ts: WebSocket server
- Terminal component now fully interactive (not read-only)
- Runs both servers via concurrently in dev mode
- Green dot = connected, red dot = disconnected
- Proper cursor, no more clunky input box

Benefits:
- Real-time streaming (not 2-second polling)
- Type directly into terminal
- Proper terminal control sequences
- Handles resize
- Like native tmux attach

Dependencies added:
- ws (WebSocket server)
- @types/ws
- concurrently (run multiple servers)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: improve terminal rendering - hide extra cursor, faster polling

- Hide xterm cursor (tmux output has its own)
- Increase polling from 500ms to 100ms (5x faster, less lag)
- Add -J flag to join wrapped lines (reduce truncation)
- Increase scrollback to 200 lines

Note: Current polling approach has limitations:
- Still some lag when typing (replacing full content)
- Not true real-time streaming
- For interactive use, prefer 'tmux attach' directly

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: improve terminal auto-sizing - multiple fit attempts

- Fit terminal multiple times (0ms, 100ms, 250ms, 500ms) to catch layout changes
- Add w-full class to ensure terminal takes full width
- Better error handling for fit operations
- Should eliminate need to manually zoom out

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: use const for pollInterval, expand WORKING zone by default

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: import WebSocket as value, not type-only

WebSocket.OPEN is used as a runtime value, so it cannot be a type-only import.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: implement proper tmux control mode streaming

Replace hacky polling approach with professional tmux control mode:

- Use 'tmux -C attach-session' for true incremental streaming
- Parse control mode protocol (%output, %exit, %layout-change)
- Send commands via stdin (not spawning processes)
- Unescape octal sequences from tmux output
- Event-driven (not polling) - lower latency, less CPU
- Only sends new output (not full snapshots)

Benefits:
- 10x less bandwidth (no repeated snapshots)
- Lower latency (~10ms vs 100ms)
- No missed output (event-driven)
- Proper professional solution (how iTerm2 does it)

Based on research of VS Code, tmux control mode documentation,
and industry best practices for terminal streaming.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: replace custom WebSocket terminal with ttyd

- Replace broken custom tmux control mode + xterm.js with ttyd (iframe)
- ttyd handles all terminal rendering, ANSI, resize, input correctly
- Terminal server now manages ttyd instances per session on dynamic ports
- Enable mouse mode on tmux sessions for proper scroll behavior
- Remove dead code: @xterm/xterm, @xterm/addon-fit, ws deps
- Remove dead SSE terminal API route
- Remove xterm.css import
- Clean up Terminal component: single status dot, no decorative dots
- Make Linear issue link clickable in SessionDetail
- Extract issue label from URL for display (INT-1327 from full URL)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: add tracker plugin integration for issue label extraction

Replaces hardcoded URL parsing with proper tracker plugin abstraction.
Now the dashboard uses tracker.issueLabel() to extract human-readable
labels from issue URLs (e.g., "INT-1327", "#42") in a plugin-agnostic way.

Changes:
- Core: Add optional issueLabel() method to Tracker interface
- Plugins: Implement issueLabel() in tracker-github and tracker-linear
- Web: Add issueUrl and issueLabel fields to DashboardSession
- Web: Add enrichSessionIssue() to populate labels via tracker plugin
- Web: Update SessionDetail and SessionCard to use new fields
- Web: Add getTracker() helper to services.ts

This is fully generic - any tracker plugin can implement issueLabel()
and the dashboard will automatically use it.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add delay before Enter in tmux sendMessage to ensure text delivery

The dashboard "ask to resolve" button was putting messages in the input
buffer without submitting them. The tmux send-keys Enter was arriving
before the pasted text was fully processed. Match the bash send-to-session
script behavior with a 300ms delay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use node:timers/promises for async setTimeout

node:util does not export setTimeout — the async sleep function
lives in node:timers/promises.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: hide tmux status bar in terminal for cleaner appearance

Added 'status off' option to remove the green tmux bar at the bottom
of the terminal for a cleaner, less cluttered interface.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add health check to wait for ttyd before returning URL

Fixes race condition where iframe loads before ttyd is ready,
causing 'localhost refused to connect' on direct page loads.
Now waits up to 3s for ttyd to be listening before responding.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: enable hot reloading for terminal server with tsx watch

Both frontend (Next.js) and backend (terminal server) now have
hot reloading enabled for faster development iteration.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add PR enrichment to session detail page

The session detail page was not enriching PR data with live stats
from GitHub, causing it to show +0 -0. Now calls enrichSessionPR()
to fetch additions, deletions, CI status, and review data.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: clean up and collapse unresolved PR comments

- Extract title and description from Bugbot comments
- Strip out HTML comments, metadata, and image links
- Make comments collapsible (collapsed by default)
- Show clean summary with expand for details

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: convert session detail page to client-side with live updates

- Changed from SSR to client-side component
- Added polling every 5 seconds for real-time data
- Created /api/sessions/[id] endpoint for single session fetch
- Faster navigation with client-side routing
- No page refresh needed to see updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: remove machine-specific symlinks from repository

- Remove .claude and packages/web/agent-orchestrator.yaml symlinks
- Add them to .gitignore to prevent re-committing
- These are development convenience links created per-worktree

Fixes Bugbot comment about environment-dependent paths that break
on other machines.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: improve session detail UI and fix activity detection

- Fix session activity detection and timestamps
  - session-manager now checks if runtime is alive in get()
  - Use file birthtime/mtime for createdAt/lastActivityAt
  - Fixes "Idle" status and "Created just now" issues

- Improve session detail UI
  - Hide empty projectId chip
  - Add PR# chip to header
  - Fix "0 checks failing" logic
  - Remove duplicate status display
  - Humanize attention level labels ("review" → "Pending Review")

- Add Linear tracker support
  - Register Linear tracker plugin in web services
  - Issue labels now show "INT-1354" instead of full URL

- Add "Ask Agent to Fix" feature
  - Button for each unresolved comment
  - API endpoint to send messages to agent via tmux
  - /api/sessions/[id]/message endpoint

- Fix waitForTtyd timeout handling
  - Add timeout event handler to prevent hanging requests
  - Properly abort timed-out requests

- Fix lint errors
  - Remove duplicate imports
  - Fix unused variables
  - Use type-only imports where appropriate

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: address production issues in terminal implementation

- Use dynamic hostname instead of hardcoded localhost
  - Terminal.tsx uses window.location.hostname
  - terminal-websocket.ts derives URL from request host
  - Supports remote access and reverse proxy scenarios
  - Fixes high-severity Bugbot comments

- Add SIGTERM handling for graceful shutdown
  - Previously only handled SIGINT
  - Now cleans up ttyd processes on SIGTERM too
  - Prevents orphan processes after restarts
  - Adds 5s timeout to prevent hanging

Fixes Bugbot comments:
- r2807572056: Terminal embed hardcodes localhost endpoints
- r2807630014: Terminal URLs are hardcoded to localhost
- r2807604002: ttyd children survive non-interrupt shutdowns

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: properly validate message delivery to tmux sessions

Use execFile with promisify instead of spawn to:
- Wait for tmux commands to complete
- Check exit codes for failures
- Return proper error if send-keys fails
- Add 5s timeout to prevent hanging

Previously the endpoint returned success immediately without
verifying if the message was actually delivered to the session.

Fixes Bugbot comment r2807674035

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: use runtime plugin sendMessage for proper message delivery

Address Bugbot review comments:
- Use session.runtimeHandle instead of raw session id
- Use Runtime plugin's sendMessage method for proper sanitization
- Remove direct tmux command execution

The Runtime plugin's sendMessage handles:
- Proper runtime handle resolution
- Input sanitization and control character stripping
- Safe message delivery via load-buffer for long messages

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: sanitize message input and support runtime defaults

Address Bugbot review comments:
- Add stripControlChars sanitization to prevent control character injection
- Fall back to config.defaults.runtime when project.runtime is not set
- Validate that message is not empty after sanitization

This aligns the message endpoint with the existing send endpoint's
security model and ensures proper runtime resolution.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: address all Bugbot review comments

Comprehensive fixes for all remaining issues:

**message/route.ts:**
- Add session ID validation with validateIdentifier
- Add JSON parse error handling with try/catch
- Add message length validation with MAX_MESSAGE_LENGTH
- Add type guard for non-string messages
- Add URL encoding for session IDs

**terminal-websocket.ts:**
- Fix memory leak in waitForTtyd by tracking and canceling timeouts
- Add cleanup() function to cancel pending requests and timers
- Add MAX_PORT limit to prevent port exhaustion
- Add error handlers for spawned tmux processes
- Use once() instead of on() for exit/error to prevent race condition
- Add unref() to shutdown timeout to allow graceful exit

**page.tsx:**
- Use useCallback to memoize fetchSession
- Add fetchSession to useEffect dependency arrays
- Add URL encoding for session ID in fetch

**Terminal.tsx:**
- Add URL encoding for session ID in terminal fetch URL

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: remove unused err variable in JSON parse catch block

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add security improvements for terminal and message endpoints

Address remaining Bugbot security concerns:

**terminal-websocket.ts:**
- Add TODO comments about authentication requirements
- Restrict CORS to localhost origins only (was allowing any origin)
- Add session existence validation before spawning ttyd
- Import fs and path modules for session validation

**Authentication:**
Full authentication with session ownership validation is tracked
separately and requires architectural decisions about auth middleware.
These changes provide defense-in-depth for the current implementation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: remove unused readFileSync import

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: terminal button opens ttyd directly in new tab

Instead of navigating to the session detail page, the terminal button
now fetches the ttyd URL from the terminal server and opens it directly
in a new browser tab. Falls back to the session detail page if the
terminal server is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address final 4 Bugbot review comments

**Issue 1: Terminal lookup ignores configured data directory (HIGH)**
- Load config using loadConfig() from @agent-orchestrator/core
- Use config.dataDir instead of hardcoded path for session validation
- Ensures terminal works with custom dataDir configurations

**Issue 2: Terminal ports exhaust without reuse (MEDIUM)**
- Implement port recycling with availablePorts Set
- Recycle ports when ttyd instances exit or error
- Prevents port exhaustion after 100 allocations

**Issue 3: Remote dashboard blocked by terminal CORS (MEDIUM)**
- Replace hardcoded localhost whitelist with dynamic origin validation
- Allow CORS if origin hostname matches request host
- Supports remote deployments while maintaining security

**Issue 4: Message endpoint can pick wrong runtime plugin (MEDIUM)**
- Use session.runtimeHandle.runtimeName instead of project config
- Ensures message delivery uses the runtime that created the session
- Handles sessions created with different runtime than current config

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 01:37:07 +05:30
prateek 343c8a65b5
feat: implement web dashboard with attention-zone UI and API routes (#1)
* feat: implement web dashboard with attention-zone UI, API routes, and SSE

Implements the Next.js 15 web dashboard for INT-1332 with:
- Attention-prioritized session cards (urgent/action/warning/ok/done zones)
- 6 API routes: sessions, spawn, send, kill, merge, SSE events
- 5 components: SessionCard, AttentionZone, PRStatus, CIBadge, Terminal
- Session detail page with PR merge readiness, CI checks, unresolved comments
- Tailwind CSS 4 dark theme matching the reference bash dashboard
- Mock data layer covering all attention states for development
- SSE endpoint for real-time lifecycle event streaming

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add vitest test suite for web dashboard (77 tests)

Add comprehensive tests covering API routes, component rendering, and
attention-level classification. Fix merge button visibility when no alerts present.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address all PR review feedback

- Fix SSE memory leak: add cancel() handler to clear intervals on disconnect
- Add X-Accel-Buffering header for reverse proxy compatibility
- Add input validation on all API routes (validateString, validateIdentifier)
- Import core types (SessionStatus, ActivityState, CIStatus, etc.) from
  @agent-orchestrator/core instead of redeclaring them
- Fix hydration mismatch: render timestamp client-side only via useEffect
- Add error handling on fetch calls in Dashboard (check response.ok)
- Add cn() utility for conditional class composition
- Fix setTimeout leak: use useRef + useEffect cleanup in SessionCard
- Fix getAttentionLevel edge case: status-based checks outside PR block
- Add NaN check on parseInt in merge route
- Extract duplicated sizeLabel logic into shared getSizeLabel()
- Add TODO guard comment on mock-data.ts for production removal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Codex review feedback (iteration 1)

- Use strict /^\d+$/ validation on PR merge route (reject "432foo")
- Treat exited agents with non-terminal status as urgent (crashed agents)
- Add explicit getAttentionLevel mappings for review_pending, approved, cleanup
- Guard SSE update loop against empty sessions array
- Use encodeURIComponent on session details link
- Tighten mergeScore types to Pick<DashboardPR, ...>
- Add stripControlChars() and apply to send route for shell safety

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply ESLint and Prettier formatting after rebase on main

- Add next-env.d.ts to ESLint ignores (triple-slash reference)
- Merge duplicate imports using inline type syntax (no-duplicate-imports)
- Replace non-null assertions with proper null checks
- Apply Prettier formatting across all packages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Cursor Bugbot review findings

- Add reviewDecision "none" to warning zone in getAttentionLevel
- Change ACTION zone color from green to orange for proper priority signaling
- Fix SessionDetail date hydration mismatch with ClientDateCard pattern
- Add export const dynamic = "force-dynamic" to SSE route
- Add --color-accent-orange CSS variable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Codex review iteration 2 findings

- Gate merge route on PR state (409 if not open) and draft status (422)
- Handle pr.state === "closed" in getAttentionLevel → done zone
- Reject messages that become empty after control char stripping
- Align SSEEvent types with actual emitted events (snapshot + activity)
- Add 6 new tests for edge cases (85 total)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve typecheck error in test helper (NextRequest init type)

Cast RequestInit to NextRequest's ConstructorParameters to fix
type incompatibility between global RequestInit.signal (null allowed)
and Next.js RequestInit.signal (null not allowed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve remaining review threads (zone colors, draft PRs, ternary)

- WORKING zone uses green (not blue) per design spec
- ACTION zone uses orange, consistent across all components
- Draft PRs with reviewDecision "none" fall to ok (not warning)
- Remove redundant ternary in getAttentionLevel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Playwright screenshot tooling for visual verification

Agents and developers can now capture headless Chromium screenshots of
the running dashboard. Includes dev server auto-start, default page
specs, and CLI arg parsing. Screenshots committed to branch for PR
visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add session detail screenshot after compact metadata redesign

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add screenshots for session detail redesign + zone reclassification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bugbot findings — unused type, kill route validation, draft PR awareness

- Remove unused SSEEvent type alias from types.ts
- Add validateIdentifier() to kill route for session ID validation
- Skip "needs review" alert for draft PRs in SessionCard getAlerts()
- Show "draft" instead of "needs review" in PRTableRow for draft PRs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: session detail redesign + attention zone reclassification

Session Detail:
- Nav bar replacing standalone back link
- Meta chips (project/branch/issue) with GitHub links
- Humanized status labels and relative timestamps
- Unified PR card with stats row, issues list, inline CI checks
- Clickable file paths in unresolved comments

Attention Zones (reordered by human action urgency):
- merge: PRs ready to merge (highest ROI per second)
- respond: agents waiting for input (quick unblock)
- review: CI failures, changes requested, conflicts
- pending: waiting on reviewer or CI
- working: agents doing their thing
- done: merged or terminated

Also fixes:
- Add validateIdentifier() to send route for session ID
- Update all tests for new zone names

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve 3 remaining review threads

- Remove duplicate eslint ignore entry for next-env.d.ts
- Export checkStatusIcon and ciCheckSortOrder from CIBadge, import in
  SessionDetail instead of duplicating
- Add restore API route (was untracked)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore route validation, merged session guards

- Add validateIdentifier() to restore route (matches kill/send routes)
- Block restoring merged sessions with 409 response
- Hide kill button for merged sessions in top-row and expanded panel
- Expanded panel now shows restore OR terminate (not ternary fallback)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: exclude e2e from tsconfig include (fixes next build)

The e2e directory imports playwright which isn't resolvable during
next build. Since e2e files are standalone tooling (not app code),
exclude them from the main tsconfig include.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: unify CI check rendering via CICheckList layout prop

Add layout prop ("vertical"|"inline"|"expanded") to CICheckList,
remove duplicated InlineCIChecks from SessionDetail in favor of
reusing CICheckList with the appropriate layout mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add isDraft guard to unresolvedThreads pending check

Draft PRs with unresolved threads should fall through to "working",
not "pending". Adds the missing !pr.isDraft guard to match the
reviewDecision check on the next line.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address 9 bugbot findings across e2e, SSE, and mock-data

- server.ts: kill child process on waitForServer failure, drain stdout
  to prevent backpressure
- screenshot.ts: validate Number() args for NaN with clear error messages
- events/route.ts: initialize interval variables as undefined
- mock-data.ts: exclude draft PRs from needsReview stat count

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent simultaneous restore and kill buttons in SessionCard

Add !isRestorable guard to kill button condition so the two buttons
are mutually exclusive. Restorable sessions show restore; non-restorable
exited sessions show kill.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove unreachable kill button from SessionCard top row

The kill button in the top row was dead code — isRestorable already
covers all activity === "exited" cases, so the !isRestorable && exited
condition could never be true.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update tests to match SessionCard after kill button removal

Tests referenced "kill session" text that no longer exists. Exited
sessions show "restore session", not "kill session". Updated 4 tests
to use onRestore/restore session instead of onKill/kill session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 14:26:59 +05:30