feat(web): extract /agents facet sub-routes #1031

Merged
reviewer merged 5 commits from dev/1017 into main 2026-05-09 23:22:10 +00:00
Collaborator

feat(web): extract /agents live, roster, config, sessions, and json sub-routes.

Closes #1017

  • agents.tsx is a layout route (shared header + repo selector + <Outlet />); /agents redirects to /agents/live.
  • New facet routes: /agents/live (shared FleetBoard with /fleet), /agents/roster (types + instances), /agents/config (globals), /agents/sessions (moved from settings), /agents/json.
  • CustomizeDropdown extracted to features/agents/customize-dropdown.tsx for leaner browser tests.
  • settings/agents/sessions redirects to /agents/sessions.
  • Paraglide-backed placeholders + smoke tests (including agents.facets.test.tsx).
feat(web): extract `/agents` live, roster, config, sessions, and json sub-routes. Closes #1017 - `agents.tsx` is a layout route (shared header + repo selector + `<Outlet />`); `/agents` redirects to `/agents/live`. - New facet routes: `/agents/live` (shared `FleetBoard` with `/fleet`), `/agents/roster` (types + instances), `/agents/config` (globals), `/agents/sessions` (moved from settings), `/agents/json`. - `CustomizeDropdown` extracted to `features/agents/customize-dropdown.tsx` for leaner browser tests. - `settings/agents/sessions` redirects to `/agents/sessions`. - Paraglide-backed placeholders + smoke tests (including `agents.facets.test.tsx`).
Add /agents/live (shared fleet board), /agents/roster, /agents/config,
/agents/sessions, /agents/json; redirect /agents to live and settings
sessions to the new path. Lift AppShell + SSE into the /agents layout and
share the sessions panel + fleet board across routes (#1017).

Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary

Cursor SDK 1.0.12 indexes the entire `local.cwd` tree on `Agent.send()` and stuffs the file list into the initial HTTP/2 frame. A Bun monorepo with `node_modules/` (~30k paths) overflows Node's default 16 KB `SETTINGS_MAX_FRAME_SIZE` → `NGHTTP2_FRAME_SIZE_ERROR` on every dispatch that picked the cursor provider. `.cursorignore` is **not** honoured for this code path in 1.0.12.

**Fix:** build a per-task wrapper directory of symlinks to the worktree's top-level entries minus `{node_modules, .git, dist, build, .turbo, .next, .cache, coverage}`. The SDK does not traverse symlinks, so the indexed tree stays small. Tools (Read/Bash/Grep) still resolve real source paths through the symlinks. Wrapper torn down in the runTask `finally`.

## What this also fixes

Adjacent failure modes uncovered while diagnosing:

- **`replayConversationForAgent` skips for non-`bc-` ids.** SDK 1.0.12 strict-rejects forced `runtime: "cloud"` `listRuns` on local-runtime ids with `Agent ID must be in the format 'bc-<uuid>'`. Replay only fires for cloud agents now.
- **`agent.send()` recovers from `UnknownAgentError: already has active run`.** Stale `active_run_id` in the local SDK store after a prior crashed run made every subsequent kick fail. Recovery now disposes the resumed agent, mints a fresh one via `Agent.create`, retries once. `cursor_init` is yielded **after** recovery so the agent-runner persists the FRESH session id — breaks the otherwise-permanent recovery loop.
- **`agent.send()` is now raced against a 90s timeout + abort signal.** A wedged pre-stream phase no longer pins the worker. Emits `cursor_send_failed` with `elapsed_ms` + reason on timeout/abort.
- **Process-level `unhandledRejection` / `uncaughtException` handlers** in `main.ts`. Cursor SDK throws unhandled HTTP/2 stream errors from connectrpc that previously took down the whole service (every worker), not just the offending one.

## Diagnostic flow

1. First kick → `validation_error` on replay.
2. Fixed replay path → second kick → `UnknownAgentError: already has active run` from a stale `active_run_id`.
3. Fixed recovery path → third kick → `NGHTTP2_FRAME_SIZE_ERROR` from connectrpc.
4. Standalone reproducer (`apps/server/src/repro/cursor-repro.ts`) confirmed: bare cwd works, real worktree crashes, symlinked worktree (no `node_modules`) works.

## Repro

`apps/server/src/repro/cursor-repro.ts` — standalone script that isolates `Agent.create` + `agent.send` against the real cursor cloud (decrypts the API key from the `secret` table). Pulled out of the runner stack so future SDK regressions can be triaged in isolation.

```sh
CLAUDE_HOOKS_SECRET_KEY=… bun run apps/server/src/repro/cursor-repro.ts
# REPRO_CWD=/path/to/repo to test against a real worktree
```

## Test plan

- [x] `bun test apps/server/src/infrastructure/agent/cursor-sdk-adapter.test.ts` (43/43 pass)
- [x] `just typecheck` clean
- [x] Reproducer with `REPRO_CWD=/home/charles/Workspace/claude-hooks` succeeds (was crashing pre-fix, equivalent flow now goes through `buildCursorCwd`)
- [x] Live kick #1017 streamed events end-to-end after restart

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Charles Jacquin <charles@jacquin.app>
Reviewed-on: #1027
feat(web): extract /agents facet sub-routes
All checks were successful
qa / sql-layer-check (pull_request) Successful in 19s
qa / dockerfile (pull_request) Successful in 20s
qa / db-schema (pull_request) Successful in 23s
qa / i18n-string-check (pull_request) Successful in 26s
qa / qa-1 (pull_request) Successful in 3m56s
qa / qa (pull_request) Successful in 0s
f5d89095fa
Co-authored-by: Cursor <cursoragent@cursor.com>
dev self-assigned this 2026-05-09 23:07:54 +00:00
dev requested review from reviewer 2026-05-09 23:18:43 +00:00
reviewer approved these changes 2026-05-09 23:22:03 +00:00
reviewer left a comment

All AC from #1017 met: /agents/{live,roster,config,sessions,json} routes added, agents.tsx is a layout route with shared header+<Outlet>, internal Tabs gone, /agents//agents/live redirect, /settings/agents/sessions/agents/sessions redirect, smoke tests for all facets, CI green.

Nit (not blocking): cursor-sdk-adapter fixes, main.ts uncaught-exception handler, cursor-repro.ts, NavSections, and shortcut pre-work are bundled into a web-only PR — worth splitting next time, but they're all correct and tested.

All AC from #1017 met: `/agents/{live,roster,config,sessions,json}` routes added, `agents.tsx` is a layout route with shared header+`<Outlet>`, internal Tabs gone, `/agents/` → `/agents/live` redirect, `/settings/agents/sessions` → `/agents/sessions` redirect, smoke tests for all facets, CI green. Nit (not blocking): cursor-sdk-adapter fixes, `main.ts` uncaught-exception handler, `cursor-repro.ts`, `NavSections`, and shortcut pre-work are bundled into a web-only PR — worth splitting next time, but they're all correct and tested.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!1031
No description provided.