ai-agent-session-center 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +484 -429
  2. package/docs/3D/ADAPTATION_GUIDE.md +592 -0
  3. package/docs/3D/index.html +754 -0
  4. package/docs/AGENT_TEAM_TASKS.md +716 -0
  5. package/docs/CYBERDROME_V2_SPEC.md +531 -0
  6. package/docs/ERROR_185_ANALYSIS.md +263 -0
  7. package/docs/PLATFORM_FEATURES_PROMPT.md +296 -0
  8. package/docs/SESSION_DETAIL_FEATURES.md +98 -0
  9. package/docs/_3d_multimedia_features.md +1080 -0
  10. package/docs/_frontend_features.md +1057 -0
  11. package/docs/_server_features.md +1077 -0
  12. package/docs/session-duplication-fixes.md +271 -0
  13. package/docs/session-terminal-linkage.md +412 -0
  14. package/package.json +63 -5
  15. package/public/apple-touch-icon.svg +21 -0
  16. package/public/css/dashboard.css +0 -161
  17. package/public/css/detail-panel.css +25 -0
  18. package/public/css/layout.css +18 -1
  19. package/public/css/modals.css +0 -26
  20. package/public/css/settings.css +0 -150
  21. package/public/css/terminal.css +34 -0
  22. package/public/favicon.svg +18 -0
  23. package/public/index.html +6 -26
  24. package/public/js/alarmManager.js +0 -21
  25. package/public/js/app.js +21 -7
  26. package/public/js/detailPanel.js +63 -64
  27. package/public/js/historyPanel.js +61 -55
  28. package/public/js/quickActions.js +132 -48
  29. package/public/js/sessionCard.js +5 -20
  30. package/public/js/sessionControls.js +8 -0
  31. package/public/js/settingsManager.js +0 -142
  32. package/server/apiRouter.js +60 -15
  33. package/server/apiRouter.ts +774 -0
  34. package/server/approvalDetector.ts +94 -0
  35. package/server/authManager.ts +144 -0
  36. package/server/autoIdleManager.ts +110 -0
  37. package/server/config.ts +121 -0
  38. package/server/constants.ts +150 -0
  39. package/server/db.ts +475 -0
  40. package/server/hookInstaller.d.ts +3 -0
  41. package/server/hookProcessor.ts +108 -0
  42. package/server/hookRouter.ts +18 -0
  43. package/server/hookStats.ts +116 -0
  44. package/server/index.js +15 -1
  45. package/server/index.ts +230 -0
  46. package/server/logger.ts +75 -0
  47. package/server/mqReader.ts +349 -0
  48. package/server/portManager.ts +55 -0
  49. package/server/processMonitor.ts +239 -0
  50. package/server/serverConfig.ts +29 -0
  51. package/server/sessionMatcher.js +17 -6
  52. package/server/sessionMatcher.ts +403 -0
  53. package/server/sessionStore.js +109 -3
  54. package/server/sessionStore.ts +1145 -0
  55. package/server/sshManager.js +167 -24
  56. package/server/sshManager.ts +671 -0
  57. package/server/teamManager.ts +289 -0
  58. package/server/wsManager.ts +200 -0
@@ -0,0 +1,263 @@
1
+ # React Error #185: Complete Architecture Analysis
2
+
3
+ > **Error**: "Maximum update depth exceeded" when clicking a 3D robot to open session detail panel.
4
+ > **Impact**: Crashes the WebGL context (`THREE.WebGLRenderer: Context Lost`), requires page reload.
5
+
6
+ ---
7
+
8
+ ## 1. What is React Error #185?
9
+
10
+ React Error #185 fires when a component triggers a state update during rendering, which triggers another render, which triggers another update — an infinite `setState → render → setState` loop. React detects this after ~50 nested updates and throws.
11
+
12
+ ---
13
+
14
+ ## 2. Current Click-to-Detail Architecture
15
+
16
+ ### Component Tree
17
+
18
+ ```
19
+ App.tsx (React DOM root)
20
+ ├── AppLayout
21
+ │ ├── Header, NavBar, ActivityFeed (React DOM)
22
+ │ ├── <LiveView /> → <CyberdromeScene />
23
+ │ │ ├── <Canvas> ← R3F RECONCILER BOUNDARY
24
+ │ │ │ ├── SceneContent
25
+ │ │ │ │ ├── SessionRobot ×N (memo'd)
26
+ │ │ │ │ │ ├── Robot3DModel (pure Three.js meshes)
27
+ │ │ │ │ │ ├── RobotLabel (drei <Text> + <Billboard>)
28
+ │ │ │ │ │ ├── RobotDialogue (drei <Text> + <Billboard>)
29
+ │ │ │ │ │ └── StatusParticles (Three.js Points)
30
+ │ │ │ │ ├── SubagentConnections (Three.js Lines)
31
+ │ │ │ │ └── CyberdromeEnvironment (Three.js meshes)
32
+ │ │ │ ├── CameraController
33
+ │ │ │ └── OrbitControls
34
+ │ │ ├── MapControls (DOM overlay, outside Canvas)
35
+ │ │ ├── SceneOverlay (DOM overlay, outside Canvas)
36
+ │ │ └── RobotListSidebar (DOM overlay, outside Canvas)
37
+ │ ├── DetailPanel ← OUTSIDE Canvas, subscribes to selectedSessionId
38
+ │ ├── SettingsPanel
39
+ │ └── Modals (Kill, Alert, Summarize)
40
+ ```
41
+
42
+ ### The Click Flow
43
+
44
+ ```
45
+ 1. User clicks robot mesh
46
+
47
+ 2. R3F raycasts and fires onClick on <group> in SessionRobot
48
+
49
+ 3. handleClick: setTimeout(() => onSelect(session.sessionId), 0)
50
+ ↓ (deferred to next event loop tick)
51
+ 4. onSelect = handleSelect in SceneContent:
52
+ a. selectSession(sessionId) → updates sessionStore.selectedSessionId
53
+ b. flyTo(pos, lookAt) → updates cameraStore.animation
54
+
55
+ 5. Zustand notifies ALL subscribers synchronously:
56
+
57
+ SUBSCRIBERS TO selectedSessionId (sessionStore):
58
+ a. DetailPanel (React DOM) → re-renders, shows slide-in panel
59
+ b. RobotListSidebar (React DOM) → re-renders, highlights selected entry
60
+ c. SummarizeModal (React DOM) → conditional re-render
61
+ d. KillConfirmModal (React DOM) → conditional re-render
62
+ e. AlertModal (React DOM) → conditional re-render
63
+ f. useKeyboardShortcuts hook → no visual re-render
64
+
65
+ SUBSCRIBERS TO sessions (sessionStore):
66
+ a. SceneContent (R3F) → NOT triggered (sessions Map unchanged)
67
+ b. SubagentConnections (R3F) → NOT triggered (sessions Map unchanged)
68
+ c. RobotListSidebar (React DOM) → also subscribes to sessions
69
+
70
+ SUBSCRIBERS TO animation (cameraStore):
71
+ a. CameraController (R3F) → triggers useFrame lerp animation
72
+ ```
73
+
74
+ ### The Dual-Reconciler Problem
75
+
76
+ React Three Fiber maintains its OWN React reconciler separate from React DOM's reconciler. Both share the same React runtime. When a Zustand store update fires:
77
+
78
+ 1. **React DOM reconciler** processes subscribers (DetailPanel, RobotListSidebar, modals)
79
+ 2. **R3F reconciler** processes subscribers (SceneContent, CameraController)
80
+
81
+ These two reconcilers can **interleave** — one reconciler's flush can trigger effects that cause the other to flush, creating a ping-pong cascade.
82
+
83
+ ---
84
+
85
+ ## 3. Why Previous Fixes Failed
86
+
87
+ ### Fix Attempt 1: `startTransition(() => onSelect(...))`
88
+ **Why it failed**: `startTransition` defers the update within ONE reconciler, but doesn't prevent cross-reconciler cascades. React DOM's deferred update can still trigger R3F's reconciler to flush synchronously.
89
+
90
+ ### Fix Attempt 2: `setTimeout(() => onSelect(...), 0)`
91
+ **Why it partially works**: Moves the store update completely out of R3F's render loop. But the problem persists because:
92
+ - The `setTimeout` fires and calls `selectSession(sessionId)` + `flyTo()`
93
+ - `selectSession` synchronously notifies Zustand subscribers
94
+ - DetailPanel (React DOM) re-renders → mounts heavy child components
95
+ - R3F's animation frame may be processing simultaneously
96
+ - If any R3F component also triggers a state update (even indirectly), cascade begins
97
+
98
+ ### Fix Attempt 3: `memo(SessionRobot)` with granular comparator
99
+ **Why it helps but isn't sufficient**: Prevents SessionRobots from re-rendering when `sessions` Map gets a new reference but their specific session data hasn't changed. However, the `sessions` Map reference doesn't change on `selectSession` (only `selectedSessionId` changes), so this memo was never the actual trigger.
100
+
101
+ ### Fix Attempt 4: `seatedRef` / `isHoveredRef` / `dialogueRef`
102
+ **Why it helps but isn't the root cause**: Eliminates all `useState` from SessionRobot's render cycle. No React state updates happen inside R3F's tree from these refs. Good defensive measure, but the crash still happens because the problem is in the cross-reconciler interaction triggered by Zustand, not by local state.
103
+
104
+ ---
105
+
106
+ ## 4. Root Cause: The Zustand Cross-Reconciler Bridge
107
+
108
+ The **fundamental** problem is:
109
+
110
+ ```
111
+ sessionStore.selectSession(id)
112
+ → Zustand.setState({ selectedSessionId: id })
113
+ → Zustand notifies ALL subscribers SYNCHRONOUSLY
114
+ → React DOM subscribers (DetailPanel) flush
115
+ → React DOM flush may trigger layout effects
116
+ → R3F's next frame picks up any pending work
117
+ → If any R3F component has pending effects → cascade
118
+ ```
119
+
120
+ Specifically, when Zustand notifies subscribers, it calls `useSyncExternalStore` in both reconcilers. React 19's concurrent features mean the DOM reconciler may start a render that the R3F reconciler then sees as a pending update.
121
+
122
+ ### The Dangerous Components Inside R3F's Canvas
123
+
124
+ Even after all fixes, these components inside `<Canvas>` still subscribe to Zustand stores:
125
+
126
+ | Component | Store | Selector | Risk |
127
+ |-----------|-------|----------|------|
128
+ | SceneContent | sessionStore | `s.sessions` | Medium — triggers on any session update |
129
+ | SceneContent | sessionStore | `s.selectSession` | Low — stable fn ref |
130
+ | SceneContent | cameraStore | `s.flyTo` | Low — stable fn ref |
131
+ | SubagentConnections | sessionStore | `s.sessions` | Medium — triggers on any session update |
132
+ | Robot3DModel | settingsStore | `s.animationIntensity` | Low |
133
+ | Robot3DModel | settingsStore | `s.animationSpeed` | Low |
134
+ | SessionRobot | roomStore | `s.getRoomForSession` | Low — stable fn ref |
135
+ | SessionRobot | settingsStore | `s.characterModel` | Low |
136
+ | SessionRobot | settingsStore | `s.labelSettings` | Low |
137
+ | CameraController | cameraStore | `s.animation` | **HIGH — triggers on flyTo()** |
138
+ | SceneThemeSync | settingsStore | `s.themeName` | Low |
139
+
140
+ **CameraController** is the likely remaining trigger. When `handleSelect` calls `flyTo()`, CameraController re-renders because `s.animation` changes. This re-render happens **inside R3F's reconciler** at the same time that DetailPanel is re-rendering in React DOM's reconciler.
141
+
142
+ ---
143
+
144
+ ## 5. The Remaining Hazard: `SubagentConnections`
145
+
146
+ `SubagentConnections` subscribes to `useSessionStore((s) => s.sessions)`. While `selectSession` doesn't change `sessions`, the `updateSession` action (from WebSocket) creates a new `Map` reference every time. If a WebSocket update arrives at the exact moment the click handler fires, SubagentConnections re-renders inside R3F while DetailPanel re-renders in DOM — cross-reconciler cascade.
147
+
148
+ ---
149
+
150
+ ## 6. Solution: Decouple the Two Reconcilers Completely
151
+
152
+ The only reliable fix is to ensure **NO shared reactive state** bridges the R3F and DOM reconcilers. The R3F tree should read data imperatively (via refs or callbacks), never via Zustand subscriptions that can trigger simultaneous re-renders.
153
+
154
+ ### Option A: Event-Based Communication (Recommended)
155
+
156
+ Replace the Zustand `selectedSessionId` bridge with a custom event:
157
+
158
+ ```
159
+ Robot click → dispatch CustomEvent('select-session', { sessionId })
160
+
161
+ DOM listener (outside Canvas) → updates local React state
162
+
163
+ DetailPanel renders from local state (never touches R3F)
164
+ ```
165
+
166
+ R3F components never subscribe to `selectedSessionId`. The click handler dispatches a DOM event. A DOM-only listener (in CyberdromeScene's wrapper or App) catches it and calls `selectSession`. This ensures:
167
+
168
+ - R3F's reconciler has ZERO work to do when a session is selected
169
+ - React DOM's reconciler handles DetailPanel independently
170
+ - No cross-reconciler flush interleaving
171
+
172
+ ### Option B: Ref-Based Bridge
173
+
174
+ Store `selectedSessionId` in a `useRef` at the Canvas boundary. R3F reads it imperatively in `useFrame`. DOM components use the normal Zustand subscription. No R3F component subscribes to `selectedSessionId`.
175
+
176
+ ### Option C: Move ALL Zustand Subscriptions Outside Canvas
177
+
178
+ Move `SceneContent` and `SubagentConnections` to read sessions via a ref that's updated outside Canvas. Pass session data as props computed in the DOM layer. This eliminates all Zustand subscriptions from inside `<Canvas>`.
179
+
180
+ ---
181
+
182
+ ## 7. Additional Hazards to Fix
183
+
184
+ ### A. `SubagentConnections` sessions subscription
185
+ Subscribes to `s.sessions` inside Canvas. Every WebSocket `updateSession` creates a new Map, causing re-render. Should use a ref or compute connections outside Canvas.
186
+
187
+ ### B. `SceneContent` sessions subscription
188
+ Same issue — `useSessionStore((s) => s.sessions)` re-renders SceneContent on every session update. Should receive sessions as a prop from outside Canvas, or use a ref.
189
+
190
+ ### C. `Robot3DModel` settingsStore subscriptions
191
+ `animationIntensity` and `animationSpeed` subscriptions cause Robot3DModel to re-render when settings change. Should read via `useSettingsStore.getState()` inside useFrame instead of subscribing.
192
+
193
+ ### D. `CameraController` cameraStore subscription
194
+ Re-renders when `flyTo()` is called. The animation data should be read via ref in useFrame, not via subscription.
195
+
196
+ ### E. `RobotListSidebar` calls `selectSession` directly
197
+ Line 202: `selectSession(sessionId)` — this DOM component calls the same function that triggers the cross-reconciler cascade. Should use the same decoupled mechanism as the robot click.
198
+
199
+ ---
200
+
201
+ ## 8. Files Involved
202
+
203
+ | File | Role | Issues |
204
+ |------|------|--------|
205
+ | `src/App.tsx:53` | Mounts `<DetailPanel />` | None — correct placement outside Canvas |
206
+ | `src/routes/LiveView.tsx` | Wraps `<CyberdromeScene />` | None |
207
+ | `src/components/3d/CyberdromeScene.tsx:89-103` | `SceneContent` subscribes to `sessions` + calls `selectSession` + `flyTo` | **PRIMARY ISSUE**: Zustand subscriptions inside Canvas |
208
+ | `src/components/3d/CyberdromeScene.tsx:74-130` | `SceneContent` function | Sessions subscription triggers re-render |
209
+ | `src/components/3d/SessionRobot.tsx:431-438` | `handleClick` with setTimeout | Good fix but insufficient |
210
+ | `src/components/3d/SessionRobot.tsx:668-692` | `memo` wrapper | Good but sessions Map ref unchanged on select |
211
+ | `src/components/3d/Robot3DModel.tsx:100-102` | Settings subscriptions | Should use getState() in useFrame |
212
+ | `src/components/3d/RobotLabel.tsx` | Pure WebGL (drei Text) | **SAFE** — no Html portals, no store subscriptions |
213
+ | `src/components/3d/RobotDialogue.tsx` | Pure WebGL (drei Text) | **SAFE** — ref-based, no store subscriptions |
214
+ | `src/components/3d/SubagentConnections.tsx:89` | `sessions` subscription | **DANGEROUS** — re-renders inside Canvas on every session update |
215
+ | `src/components/3d/CameraController.tsx` | `animation` subscription | **DANGEROUS** — re-renders inside Canvas on flyTo |
216
+ | `src/components/session/DetailPanel.tsx:68` | `selectedSessionId` subscription | Fine — outside Canvas |
217
+ | `src/components/3d/RobotListSidebar.tsx:180-183` | Multiple store subscriptions | Fine — outside Canvas (DOM overlay) |
218
+ | `src/stores/sessionStore.ts:56` | `selectSession` action | Triggers all subscribers synchronously |
219
+ | `src/stores/cameraStore.ts` | `flyTo` action | Triggers CameraController re-render inside Canvas |
220
+
221
+ ---
222
+
223
+ ## 9. Recommended Redevelopment Plan
224
+
225
+ ### Phase 1: Eliminate ALL Zustand subscriptions from inside `<Canvas>`
226
+
227
+ 1. **SceneContent**: Remove `useSessionStore` and `useCameraStore` subscriptions. Receive `sessions`, `selectSession`, `flyTo` as props from CyberdromeScene (DOM layer).
228
+
229
+ 2. **SubagentConnections**: Remove `useSessionStore` subscription. Receive `sessions` or precomputed connections as props.
230
+
231
+ 3. **Robot3DModel**: Replace `useSettingsStore` subscriptions with `useSettingsStore.getState()` reads inside useFrame.
232
+
233
+ 4. **CameraController**: Replace `useCameraStore((s) => s.animation)` subscription with polling in useFrame via `useCameraStore.getState()`.
234
+
235
+ 5. **SceneThemeSync**: Replace `useSettingsStore` subscription with receiving theme as a prop.
236
+
237
+ ### Phase 2: Replace cross-reconciler selection bridge
238
+
239
+ 1. Robot click dispatches `window.dispatchEvent(new CustomEvent('robot-select', { detail: { sessionId } }))`.
240
+
241
+ 2. CyberdromeScene (DOM wrapper) listens for 'robot-select' and calls `selectSession()` + `flyTo()`.
242
+
243
+ 3. RobotListSidebar dispatches same event instead of calling `selectSession()` directly.
244
+
245
+ 4. DetailPanel remains unchanged — subscribes to `selectedSessionId` in React DOM, which is safe.
246
+
247
+ ### Phase 3: Verify no cross-reconciler state bridges remain
248
+
249
+ - Grep for `useSessionStore`, `useCameraStore`, `useSettingsStore`, `useRoomStore` inside any component rendered within `<Canvas>`.
250
+ - Each must use either `.getState()` in useFrame/callbacks, or receive data as props from the DOM layer.
251
+
252
+ ---
253
+
254
+ ## 10. Summary
255
+
256
+ The error persists because **Zustand store updates synchronously notify subscribers in BOTH React reconcilers** (R3F and DOM). When the user clicks a robot:
257
+
258
+ 1. `selectSession()` notifies DOM subscribers (DetailPanel) → heavy re-render
259
+ 2. `flyTo()` notifies R3F subscribers (CameraController) → re-render inside Canvas
260
+ 3. If a WebSocket `updateSession` arrives simultaneously, SceneContent + SubagentConnections re-render
261
+ 4. Both reconcilers flush work, potentially interleaving, causing the infinite loop
262
+
263
+ **The fix is architectural**: no component inside `<Canvas>` should subscribe to Zustand stores. All data flows into the Canvas via props or is read imperatively via `.getState()` in useFrame callbacks.
@@ -0,0 +1,296 @@
1
+ # Prompt: Write Comprehensive Platform Features Document
2
+
3
+ ## Context
4
+
5
+ You are documenting a full-featured **AI Agent Session Center** — a localhost dashboard (port 3333) that monitors all active AI coding agent sessions (Claude Code, Gemini CLI, Codex) in real-time via hook scripts. The platform has both a **legacy vanilla JS + CSS animations frontend** and a **React Three Fiber 3D Cyberdrome scene** built with TypeScript + Vite. The document must serve as a complete specification for rebuilding the platform from scratch.
6
+
7
+ ## Deliverable
8
+
9
+ Write a single Markdown file `docs/PLATFORM_FEATURES.md` that exhaustively documents every feature. The document must be detailed enough that a developer who has never seen the codebase can rebuild the entire platform feature-for-feature.
10
+
11
+ ## Required Sections
12
+
13
+ ### 1. Platform Overview
14
+ - Purpose, target users, supported AI CLIs (Claude Code, Gemini CLI, Codex, OpenClaw)
15
+ - Tech stack: Node.js 18+ ESM, Express 5, ws 8, node-pty, better-sqlite3, React 19 + TypeScript + Vite, React Three Fiber, Zustand, IndexedDB, xterm.js
16
+ - Architecture diagram (ASCII): Hook script → File MQ → Server → WebSocket → Browser
17
+ - Latency expectations per stage (jq 2-5ms, file append 0.1ms, fs.watch 0-10ms, processing 0.5ms, total 3-17ms)
18
+
19
+ ### 2. Hook Delivery Pipeline
20
+ - **Primary path**: bash hook script → jq enrichment (PID, TTY, terminal detection, tab_id, tmux, agent metadata) → atomic POSIX append to `/tmp/claude-session-center/queue.jsonl` → background subshell + disown
21
+ - **Fallback**: curl HTTP POST to `localhost:PORT/api/hooks` (1s connect, 3s total timeout)
22
+ - **MQ Reader**: fs.watch() + 10ms debounce, 500ms fallback poll, 5s health check, byte-offset tracking, partial line handling, 1MB truncation threshold
23
+ - **Hook validation**: session_id required (max 256), event name in known set, optional claude_pid/timestamp validation
24
+ - **Enriched fields**: claude_pid, hook_sent_at, tty_path, term_program/version, vscode_pid, tab_id (iTerm/kitty/Warp/WezTerm/Apple Terminal), tmux session/pane, terminal env vars, agent metadata (parent_session_id, team_name, agent_name/type/id/color)
25
+ - **TTY detection cache**: per-PPID in `/tmp/claude-tty-cache/` to avoid repeated ps calls
26
+ - **Tab title management**: escape sequences to terminal for state-changing events
27
+
28
+ ### 3. Hook Events and Density Levels
29
+ - All 14 Claude events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, Stop, Notification, SubagentStart, SubagentStop, TeammateIdle, TaskCompleted, PreCompact, SessionEnd
30
+ - 7 Gemini events: SessionStart, BeforeAgent, BeforeTool, AfterTool, AfterAgent, SessionEnd, Notification
31
+ - 1 Codex event: agent-turn-complete
32
+ - 3 density levels (high=all 14, medium=12 excl TeammateIdle/PreCompact, low=5 Start/Prompt/Permission/Stop/End)
33
+ - Hook installation: copies scripts to `~/.claude/hooks/`, atomic JSON merge into `~/.claude/settings.json`, Gemini `~/.gemini/settings.json`, Codex `~/.codex/config.toml`, Windows PowerShell variant
34
+
35
+ ### 4. Session State Machine
36
+ - Full state diagram: idle → prompting → working → approval/input → waiting → idle → ended
37
+ - All transitions with trigger events
38
+ - Session object: 50+ fields (sessionId, projectPath, projectName, label, title, status, animationState, emote, timestamps, promptHistory[50], toolUsage Map, totalToolCalls, model, subagentCount, toolLog[200], responseLog[50], events[50], archived, source, pendingTool, waitingDetail, cachedPid, queueCount, terminalId, sshHost/Command/Config, teamId/Role, agentName/Type/Color, characterModel, accentColor, summary, previousSessions[5], tmuxPaneId, backendType)
39
+ - Event ring buffer (500 events) for WebSocket replay on reconnect
40
+ - Snapshot persistence: atomic write every 10s to `/tmp/claude-session-center/sessions-snapshot.json`, loaded on restart
41
+ - Debounced broadcast: 50ms window per sessionId
42
+
43
+ ### 5. Session Matching (5-Priority System)
44
+ - Priority 0: pendingResume + terminal ID or workDir match
45
+ - Priority 0.5: auto-link to snapshot-restored ended session by projectPath (within 30 min, single candidate)
46
+ - Priority 1: agent_terminal_id env var direct match → re-key session
47
+ - Priority 2: tryLinkByWorkDir() pending link from sshManager
48
+ - Priority 3: scan CONNECTING sessions by normalized path (exactly 1 match)
49
+ - Priority 4: PID parent check via getTerminalByPtyChild()
50
+ - Fallback: display-only card with detected terminal source (VS Code, JetBrains IDEs, iTerm, Warp, Kitty, Ghostty, Alacritty, WezTerm, Hyper, Apple Terminal, tmux)
51
+ - Session re-keying: transfers old→new sessionId, resets state, preserves previousSessions chain, migrates SQLite FK
52
+
53
+ ### 6. Approval Detection
54
+ - Tool category timeouts: fast(3s: Read/Write/Edit/Grep/Glob/NotebookEdit), userInput(3s: AskUser/EnterPlan/ExitPlan), medium(15s: WebFetch/WebSearch), slow(8s: Bash/Task)
55
+ - hasChildProcesses check for slow tools (pgrep -P)
56
+ - PermissionRequest event as reliable signal (medium+ density)
57
+ - Auto-idle timeouts: prompting→30s→waiting, waiting→120s→idle, working→180s→idle, approval/input→600s→idle
58
+
59
+ ### 7. Team and Subagent Tracking
60
+ - Auto-detection by matching cwd paths (parent/child within 10s window)
61
+ - Explicit via CLAUDE_CODE_PARENT_SESSION_ID env var
62
+ - Team config reader: `~/.claude/teams/{name}/config.json` with member metadata
63
+ - Team terminal: attach to member's tmux pane
64
+ - Cleanup: 15s delay after parent ends if all children also ended
65
+ - Parent tracks subagentCount
66
+
67
+ ### 8. SSH/PTY Terminal Management
68
+ - 4 creation modes: direct SSH, tmux attach, tmux wrap, resume with fallback (`claude --resume ID || claude --continue`)
69
+ - node-pty for PTY multiplexing, max 10 concurrent
70
+ - Shell ready detection: watches for prompt patterns ($/%/#/>), 100ms settle, 5s/15s timeouts
71
+ - 128KB ring buffer per terminal, replayed on (re)subscribe
72
+ - Pending workDir links for session matching (60s expiry)
73
+ - API key injection via PTY env vars
74
+ - AGENT_MANAGER_TERMINAL_ID env var exported on remote shell
75
+ - Input validation: rejects shell metacharacters
76
+
77
+ ### 9. WebSocket Protocol
78
+ - Server→Client: snapshot, session_update, session_removed, team_update, hook_stats, terminal_output (base64), terminal_ready, terminal_closed, clearBrowserDb, replay
79
+ - Client→Server: terminal_input, terminal_resize, terminal_disconnect, terminal_subscribe, update_queue_count, replay (sinceSeq)
80
+ - Heartbeat: ping 30s, pong timeout 10s
81
+ - Backpressure: skip non-critical if send buffer >1MB
82
+ - hook_stats throttled to 1/s
83
+
84
+ ### 10. REST API (All Endpoints)
85
+ - Hook/Stats: GET/POST hook-stats, GET mq-stats
86
+ - Sessions: GET source, POST resume, POST kill, DELETE, PUT title, PUT label, PUT accent-color, POST summarize (rate limited 2 concurrent)
87
+ - Terminals: POST create (max 10), GET list, DELETE close
88
+ - SSH: GET ssh-keys, POST tmux-sessions
89
+ - Teams: GET config, POST member terminal
90
+ - SQLite DB: GET/DELETE sessions (search/filter/paginate), GET projects, GET search (full-text), GET/POST/DELETE notes
91
+ - Analytics: GET summary, tools, projects, heatmap (7×24)
92
+ - Admin: POST reset, GET/POST/POST hooks status/install/uninstall
93
+ - Auth: GET status, POST login, POST logout
94
+
95
+ ### 11. Authentication
96
+ - Optional password protection via server-config.json passwordHash
97
+ - scrypt-based hashing with timing-safe comparison
98
+ - 24-hour in-memory tokens, hourly cleanup
99
+ - Token sources: HTTP cookie, Authorization Bearer header, ?token= query (for WS)
100
+ - Hook endpoints bypass auth
101
+
102
+ ### 12. SQLite Persistence
103
+ - better-sqlite3, WAL mode, `data/sessions.db`
104
+ - Tables: sessions, prompts, responses, tool_calls, events, notes (all with UNIQUE constraints)
105
+ - Upsert pattern, batch insert transactions, cascade delete
106
+ - Session ID migration for resume re-keying
107
+ - Full-text LIKE search across prompts/responses
108
+ - Analytics: prepared statements for summary, tools, projects, heatmap
109
+
110
+ ### 13. IndexedDB Client Persistence
111
+ - Database: claude-dashboard v2
112
+ - 12 object stores: sessions, prompts, responses, toolCalls, events, notes, promptQueue, alerts, sshProfiles, settings, summaryPrompts, teams
113
+ - Batched writes: 200ms flush or 20-item threshold
114
+ - Session dedup by timestamp on persist
115
+ - Queue CRUD with reorder/move-between-sessions
116
+ - Full-text search with `<mark>` highlighting
117
+ - Timeline queries with granularity (hour/day/week/month)
118
+ - Default seeding: 6 settings + 5 summary prompt templates
119
+
120
+ ### 14. CSS Character System (2D View)
121
+ - 20 pure-CSS characters: robot, cat, alien, ghost, orb, dragon, penguin, octopus, mushroom, fox, unicorn, jellyfish, owl, bat, cactus, slime, pumpkin, yeti, crystal, bee
122
+ - 8-color accent palette, round-robin assignment
123
+ - Per-session character override + global setting
124
+ - Status→animation mapping: idle→Idle, prompting→Wave+Walking, working→Running, approval/input→Waiting, waiting→ThumbsUp+Waiting, ended→Death
125
+ - Character lifecycle: create, update (data-status attr), switch, markChecked, remove
126
+
127
+ ### 15. 3D Cyberdrome Scene
128
+ - React Three Fiber + Three.js, zero useState inside Canvas (all refs), zero Zustand subscriptions inside Canvas
129
+ - CustomEvent pattern: robot click → window.dispatchEvent('robot-select') → DOM handler → Zustand update
130
+ - Canvas: PCFSoftShadowMap, ACESFilmicToneMapping, FOV 50, OrbitControls with damping
131
+ - Map controls overlay: zoom in/out, top-down, reset view
132
+ - Dynamic room system: 4 rooms/row, ROOM_SIZE=12, ROOM_GAP=5, 8 desks/room (3 north + 2 west + 3 east)
133
+ - Two doors per room (north + south, DOOR_GAP=4)
134
+ - Corridor workstations: 10 desks south of rooms
135
+ - Casual areas north: Coffee Lounge (6 seats, zone -2), Gym (10 stations, zone -3)
136
+ - Wall collision rects for navigation
137
+ - Door waypoints with inside/outside vectors, nearest-door pathfinding
138
+ - Robot navigation AI: 4 modes (WALK/GOTO/SIT/IDLE), status-based desk seeking, cross-room pathfinding via doors, position persistence to sessionStorage
139
+ - Robot dialogue bubbles: status-persistent + tool-transient, zero useState (ref-based)
140
+ - Robot labels: Billboard+Text WebGL rendering, fontSize from settings, title+status dot+alert banner
141
+ - Status particles per robot
142
+ - Subagent connection beams between parent/child robots
143
+ - Scene themes synced from UI themeName setting
144
+ - Camera controller: flyTo with smooth interpolation
145
+ - Room labels in 3D space
146
+ - RobotListSidebar: DOM overlay with close button, status sorting, fly-to-robot on click
147
+
148
+ ### 16. Session Cards (2D View)
149
+ - Card creation with 100ms debounce
150
+ - Pinned sessions (localStorage), muted sessions (per-session + global)
151
+ - Top-5 tool bars with proportional fill
152
+ - Toast notifications (error always, info configurable)
153
+ - Label badge, team indicator, editable title
154
+ - Status border glow pulsing
155
+ - Drag-and-drop reorder within groups
156
+
157
+ ### 17. Session Detail Panel
158
+ - Slide-in right overlay, resizable (min 320px, max 95vw), width persisted
159
+ - Header: project name, status, model, duration, character selector, title input, label input with chips
160
+ - 6 tabs: Conversation (prompts numbered + timestamped + COPY, previous sessions collapsible), Activity (merged events+tools+responses color-coded), Terminal (xterm.js + reconnect + theme), Notes (CRUD), Queue (per-session), Summary (AI-generated + re-summarize)
161
+ - Tab/selection persistence in localStorage
162
+ - Auto-attach terminal on tab switch
163
+ - History view mode: loads from SQLite for ended sessions
164
+ - Search integration with highlight
165
+
166
+ ### 18. Session Controls
167
+ - Resume (ended sessions → terminal + `claude --resume`)
168
+ - Kill (SIGTERM → 3s → SIGKILL, confirm modal)
169
+ - Archive (marks archived, removes from live, plays sound)
170
+ - Delete (permanent, cascade SQLite delete, confirm dialog)
171
+ - Summarize (prompt template selector modal with CRUD, calls Claude haiku, rate limited)
172
+ - Alert (timed notification modal)
173
+ - Notes CRUD
174
+ - Label system: 3 built-in (ONEOFF/HEAVY/IMPORTANT) + custom, chip quick-select, autocomplete history (30 max)
175
+ - Title system: free-text, saves on blur/Enter
176
+
177
+ ### 19. Quick Actions
178
+ - + NEW SESSION: full SSH modal (host/port/user/auth/key/workdir/command/tmux mode/API key/theme/title/label)
179
+ - QUICK SESSION: minimal modal reusing last SSH config
180
+ - ONEOFF/HEAVY/IMPORTANT: quick-launch with label pre-filled (HEAVY/IMPORTANT auto-pin)
181
+ - MUTE ALL toggle, ARCHIVE ENDED bulk action
182
+ - Working directory history (20 max, dropdown with delete), label history (30 max MRU)
183
+ - Mobile FAB with slide-up panel
184
+ - Shortcuts panel (? key)
185
+
186
+ ### 20. Prompt Queue
187
+ - Per-session numbered list with SEND/expand/EDIT/MOVE/DEL per item
188
+ - Drag-to-reorder (HTML5 drag)
189
+ - Move mode: multi-select + choose destination session
190
+ - Global queue view: all sessions grouped with headers
191
+ - Auto-send: when autoSendQueue on + session→waiting, pop first item to terminal
192
+ - IndexedDB persistence with orderIndex
193
+
194
+ ### 21. Session Groups
195
+ - Default 4 groups: Priority, Active, Background, Review
196
+ - CSS Grid 12-column layout, configurable colSpan per group
197
+ - 5 layout presets: 1-col, 2-col, 3-col, 1/3+2/3, 2/3+1/3
198
+ - Group CRUD, drag-reorder, resize handles
199
+ - Session-to-group assignment via detail panel dropdown
200
+ - Persistence in localStorage
201
+
202
+ ### 22. Sound System
203
+ - 16 synthesized Web Audio API sounds: chirp, ping, chime, ding, blip, swoosh, click, beep, warble, buzz, cascade, fanfare, alarm, thud, urgentAlarm, none
204
+ - 20 action-sound mappings (configurable per-action)
205
+ - Per-CLI sound profiles (claude/gemini/codex/openclaw) with default overrides
206
+ - Volume 0-1, global enable/disable
207
+ - AudioContext unlock on first interaction
208
+ - Ambient presets with room sounds option
209
+
210
+ ### 23. Movement Effects
211
+ - 18 CSS effects: none, sweat, energy-ring, sparks, steam, eye-cycle, think-pulse, head-tilt, float, breathe, sway, sparkle, bounce, flash, shake, fade, shrink, dissolve, questions
212
+ - Applied via data-movement attribute, auto-clear after 3.5s
213
+ - Per-action configurable mappings
214
+
215
+ ### 24. Alarm System
216
+ - Approval alarm: urgentAlarm + shake, repeats every 10s while in approval
217
+ - Input notification: single sound on entering input state
218
+ - Event-based: maps hook events to configured sounds/movements
219
+ - Label completion alerts: per-label sound + movement + frame effect (fire/electric/chains/liquid/plasma)
220
+ - Duration alerts: timed, stored in IndexedDB, checked periodically
221
+
222
+ ### 25. Settings
223
+ - Appearance: 9 themes (command-center/cyberpunk/warm/dracula/solarized/nord/monokai/light/blonde), font size, card size, scanlines, activity feed, toasts
224
+ - Sound: global enable/volume, per-action sound grid, per-CLI profiles
225
+ - Animation: character model, intensity %, speed %, per-action movement grid
226
+ - Hooks: density selector, install/uninstall buttons with status
227
+ - API Keys: Anthropic/OpenAI/Gemini with show/hide
228
+ - Label settings: per-label frame effect + sound + movement
229
+ - Summary prompts: CRUD templates, star default, 5 built-in templates
230
+ - Import/Export: JSON download/upload, reset to defaults
231
+ - All settings persisted to IndexedDB with 200ms batching
232
+
233
+ ### 26. Terminal Manager (Frontend)
234
+ - xterm.js with Canvas renderer, FitAddon, Unicode11Addon, WebLinksAddon
235
+ - 10000 line scrollback, JetBrains Mono font, responsive font size
236
+ - 8 terminal themes + auto theme (reads CSS variables)
237
+ - Canvas repaint workaround (resize cols-1 → restore in 2 frames)
238
+ - Fullscreen mode with Alt+F11
239
+ - WS reconnect: clear + re-subscribe (128KB replay)
240
+ - Pending output buffer (500 chunks max)
241
+ - Per-terminal theme persistence
242
+ - Resize observer with 50ms debounce
243
+ - Team member terminal: attach to tmux pane
244
+
245
+ ### 27. History Panel
246
+ - Server-side SQLite queries (not IndexedDB)
247
+ - Filters: search (300ms debounce), project dropdown, status, date range, archived flag
248
+ - Sort: date/duration/prompts/tools + direction toggle
249
+ - Pagination: 50/page with numbered buttons
250
+ - Row click → detail panel from SQLite
251
+ - Delete with cascade fade-out
252
+
253
+ ### 28. Analytics Panel
254
+ - Custom SVG charts (no library)
255
+ - 5 sections: summary stats (6 cards), tool usage bar chart (top 15), duration trends area chart, active projects bar chart, daily heatmap (7×24 Mon-first)
256
+ - All data from server SQLite analytics endpoints
257
+
258
+ ### 29. Timeline Panel
259
+ - Grouped bar chart: sessions/prompts/tool calls per time bucket
260
+ - Filters: granularity (hour/day/week/month), project, date range (default 30 days)
261
+ - Data from IndexedDB getTimeline()
262
+
263
+ ### 30. Keyboard Shortcuts
264
+ - `/` focus search, Escape close/deselect, `?` shortcuts panel, `S` settings, `K` kill, `A` archive, `T` new terminal, `M` mute all
265
+
266
+ ### 31. Server Infrastructure
267
+ - Port conflict auto-resolution (kills occupying process)
268
+ - Graceful shutdown (SIGTERM/SIGINT → save snapshot → close SQLite → server.close → 5s force exit)
269
+ - Process monitor: PID liveness check every 15s
270
+ - Auto-idle manager: per-session timers
271
+ - Hook stats: per-event counts + latency/processing histograms
272
+ - Rate limiting: in-memory sliding window
273
+ - Logger: debug-aware with pretty JSON
274
+ - Server config: port, enabledClis, hookDensity, debug, sessionHistoryHours, passwordHash
275
+
276
+ ### 32. CLI and Setup
277
+ - `npx` entry: auto-setup wizard on first run, `--setup` flag to re-run
278
+ - 6-step wizard: port, CLIs, density, debug, retention, password
279
+ - Saves to `data/server-config.json`
280
+ - Auto-install hooks on every server startup (quiet mode)
281
+
282
+ ### 33. Testing
283
+ - Vitest unit tests (407+ tests)
284
+ - Playwright E2E tests: groups, kill-session, navigation, session-lifecycle, settings, smoke, terminal
285
+
286
+ ## Instructions for Agent Team
287
+
288
+ Split the work across agents:
289
+
290
+ 1. **Agent 1 (Server Features)**: Sections 1-12, 31-32 — everything server-side
291
+ 2. **Agent 2 (Frontend Features)**: Sections 13-21, 27-30 — UI components, panels, views
292
+ 3. **Agent 3 (3D Scene + Multimedia)**: Sections 15, 22-26, 33 — Cyberdrome, sound, animations, settings, testing
293
+
294
+ Each agent should read the actual source files to verify details and add any missing specifics. The final document should be merged into a single `docs/PLATFORM_FEATURES.md` file.
295
+
296
+ Write in precise technical language. Include exact values (timeouts, buffer sizes, limits). Use tables for mappings and enums. Use code blocks for data structures. Use ASCII diagrams for architecture flows.
@@ -0,0 +1,98 @@
1
+ # Session Detail Features
2
+
3
+ ## Overview
4
+
5
+ When a user clicks on an agent robot in the 3D Cyberdrome scene, a detail panel slides in from the right showing comprehensive session information. This document describes the features, architecture, and data flow.
6
+
7
+ ## Detail Panel Contents
8
+
9
+ ### Header
10
+ - **Mini robot icon**: Shows first letter of model type with status-colored border
11
+ - **Project name**: `session.projectName`
12
+ - **Session title**: `session.title` (italic, smaller)
13
+ - **Status badge**: Color-coded label (idle/prompting/working/waiting/approval/input/ended/connecting)
14
+ - **Model name**: `session.model` (e.g., "claude-opus-4-6")
15
+ - **Duration**: Time since `session.startedAt`
16
+
17
+ ### Session Control Bar
18
+ - Kill, Archive, Resume buttons
19
+ - Labels (ONEOFF, HEAVY, IMPORTANT)
20
+ - Notes toggle
21
+ - Summarize trigger
22
+
23
+ ### Tabs
24
+ | Tab | Content |
25
+ |-----|---------|
26
+ | **Terminal** | xterm.js terminal connected via WebSocket relay. Shows reconnect button for SSH sessions. |
27
+ | **Prompts** | Scrollable prompt history (`session.promptHistory`). Includes previous sessions if resumed. |
28
+ | **Activity** | Interleaved events, tool calls, and response excerpts from `session.events`, `session.toolLog`, `session.responseLog`. |
29
+ | **Notes** | Per-session markdown notes stored in IndexedDB. |
30
+ | **Summary** | AI-generated session summary (`session.summary`). |
31
+ | **Queue** | Prompt queue management — compose, reorder, send, move between sessions. |
32
+
33
+ ### Modals (lazy-mounted)
34
+ - **KillConfirmModal**: Confirms session termination
35
+ - **AlertModal**: Displays alarm notifications
36
+ - **SummarizeModal**: Triggers AI summarization
37
+
38
+ ## Selection Architecture
39
+
40
+ ### Data Flow (Click → Detail Panel)
41
+
42
+ ```
43
+ [R3F Canvas] [DOM Layer]
44
+ Robot <group onClick> ──→ onSelect() │
45
+ └─ SceneContent.handleSelect() │
46
+ └─ setTimeout(() => { │
47
+ dispatchEvent('robot-select') ──→ CyberdromeScene useEffect handler
48
+ }) ├─ selectSession(sessionId) [sessionStore]
49
+ └─ flyTo(pos + offset) [cameraStore]
50
+
51
+ ┌───────────┘
52
+
53
+ DetailPanel (DOM)
54
+ └─ useSessionStore(selectedSessionId)
55
+ └─ Renders session details
56
+ ```
57
+
58
+ ### Key Principles
59
+
60
+ 1. **ZERO Zustand subscriptions inside `<Canvas>`**: All store reads happen in the DOM-side `CyberdromeScene` wrapper. Data flows into Canvas via props only.
61
+
62
+ 2. **CustomEvent bridge**: Robot clicks dispatch a `CustomEvent('robot-select')` which is caught by a DOM-side `useEffect`. This ensures Zustand store updates happen exclusively in the DOM React reconciler, never in R3F's reconciler.
63
+
64
+ 3. **setTimeout deferral**: The CustomEvent dispatch is wrapped in `setTimeout(0)` to ensure it fires after R3F's pointer event processing completes.
65
+
66
+ 4. **Imperative store reads**: Components inside Canvas that need store data (CameraController, Robot3DModel) use `useStore.getState()` inside `useFrame` instead of subscriptions.
67
+
68
+ 5. **Ref-based state**: Interactive state inside Canvas (hover, seated, dialogue) uses `useRef` instead of `useState` to prevent React re-renders in the R3F tree.
69
+
70
+ ## Store Dependencies
71
+
72
+ | Component | Location | Store Subscriptions |
73
+ |-----------|----------|-------------------|
74
+ | CyberdromeScene | DOM wrapper | sessionStore, roomStore, settingsStore, cameraStore |
75
+ | DetailPanel | DOM (App.tsx) | sessionStore, uiStore, wsStore |
76
+ | RobotListSidebar | DOM overlay | sessionStore |
77
+ | SceneOverlay | DOM overlay | roomStore, sessionStore, cameraStore, settingsStore |
78
+ | MapControls | DOM overlay | cameraStore |
79
+ | SceneContent | Canvas | NONE (props only) |
80
+ | SessionRobot | Canvas | NONE (props only) |
81
+ | CameraController | Canvas | NONE (imperative reads) |
82
+ | Robot3DModel | Canvas | NONE (imperative reads) |
83
+ | SubagentConnections | Canvas | NONE (props only) |
84
+ | RoomLabels | Canvas | NONE (props only) |
85
+ | CyberdromeEnvironment | Canvas | NONE (props only) |
86
+
87
+ ## Status Colors
88
+
89
+ ```
90
+ idle: #00ff88 (green)
91
+ prompting: #00e5ff (cyan)
92
+ working: #ff9100 (orange)
93
+ waiting: #00e5ff (cyan)
94
+ approval: #ffdd00 (yellow)
95
+ input: #aa66ff (purple)
96
+ ended: #ff4444 (red)
97
+ connecting: #666 (gray)
98
+ ```