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.
- package/README.md +484 -429
- package/docs/3D/ADAPTATION_GUIDE.md +592 -0
- package/docs/3D/index.html +754 -0
- package/docs/AGENT_TEAM_TASKS.md +716 -0
- package/docs/CYBERDROME_V2_SPEC.md +531 -0
- package/docs/ERROR_185_ANALYSIS.md +263 -0
- package/docs/PLATFORM_FEATURES_PROMPT.md +296 -0
- package/docs/SESSION_DETAIL_FEATURES.md +98 -0
- package/docs/_3d_multimedia_features.md +1080 -0
- package/docs/_frontend_features.md +1057 -0
- package/docs/_server_features.md +1077 -0
- package/docs/session-duplication-fixes.md +271 -0
- package/docs/session-terminal-linkage.md +412 -0
- package/package.json +63 -5
- package/public/apple-touch-icon.svg +21 -0
- package/public/css/dashboard.css +0 -161
- package/public/css/detail-panel.css +25 -0
- package/public/css/layout.css +18 -1
- package/public/css/modals.css +0 -26
- package/public/css/settings.css +0 -150
- package/public/css/terminal.css +34 -0
- package/public/favicon.svg +18 -0
- package/public/index.html +6 -26
- package/public/js/alarmManager.js +0 -21
- package/public/js/app.js +21 -7
- package/public/js/detailPanel.js +63 -64
- package/public/js/historyPanel.js +61 -55
- package/public/js/quickActions.js +132 -48
- package/public/js/sessionCard.js +5 -20
- package/public/js/sessionControls.js +8 -0
- package/public/js/settingsManager.js +0 -142
- package/server/apiRouter.js +60 -15
- package/server/apiRouter.ts +774 -0
- package/server/approvalDetector.ts +94 -0
- package/server/authManager.ts +144 -0
- package/server/autoIdleManager.ts +110 -0
- package/server/config.ts +121 -0
- package/server/constants.ts +150 -0
- package/server/db.ts +475 -0
- package/server/hookInstaller.d.ts +3 -0
- package/server/hookProcessor.ts +108 -0
- package/server/hookRouter.ts +18 -0
- package/server/hookStats.ts +116 -0
- package/server/index.js +15 -1
- package/server/index.ts +230 -0
- package/server/logger.ts +75 -0
- package/server/mqReader.ts +349 -0
- package/server/portManager.ts +55 -0
- package/server/processMonitor.ts +239 -0
- package/server/serverConfig.ts +29 -0
- package/server/sessionMatcher.js +17 -6
- package/server/sessionMatcher.ts +403 -0
- package/server/sessionStore.js +109 -3
- package/server/sessionStore.ts +1145 -0
- package/server/sshManager.js +167 -24
- package/server/sshManager.ts +671 -0
- package/server/teamManager.ts +289 -0
- package/server/wsManager.ts +200 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
# AI Agent Session Center — Server Features Reference
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. Platform Overview
|
|
6
|
+
|
|
7
|
+
The AI Agent Session Center is a localhost dashboard (default port **3333**) that monitors active AI coding agent sessions in real time. Hook scripts installed into the AI CLI capture lifecycle events, relay them via a file-based message queue, and the server pushes updates to all connected browsers over WebSocket.
|
|
8
|
+
|
|
9
|
+
### Tech Stack
|
|
10
|
+
|
|
11
|
+
| Layer | Technology | Version / Notes |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Runtime | Node.js | 18+ (ESM modules) |
|
|
14
|
+
| HTTP framework | Express | 5 |
|
|
15
|
+
| WebSocket | ws | 8 |
|
|
16
|
+
| PTY / terminal | node-pty | native bindings |
|
|
17
|
+
| Database | better-sqlite3 | WAL mode |
|
|
18
|
+
| Hook delivery | Bash → JSONL queue file | POSIX atomic append |
|
|
19
|
+
| Frontend (legacy) | Vanilla JS + CSS | Import maps, no build step |
|
|
20
|
+
| Frontend (new) | React 19 + TypeScript + Vite | Served from dist/client |
|
|
21
|
+
| Port | 3333 | Configurable |
|
|
22
|
+
|
|
23
|
+
### Top-Level Architecture
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
AI CLI (Claude / Gemini / Codex)
|
|
27
|
+
│ Hook script fires on each event
|
|
28
|
+
▼
|
|
29
|
+
dashboard-hook.sh
|
|
30
|
+
├── Reads stdin JSON
|
|
31
|
+
├── Enriches with: PID, TTY, TERM_PROGRAM, tab IDs, team env vars
|
|
32
|
+
├── Single jq pass (~2-5ms)
|
|
33
|
+
└── Appends to /tmp/claude-session-center/queue.jsonl (~0.1ms)
|
|
34
|
+
│ (HTTP POST fallback if MQ dir absent)
|
|
35
|
+
▼
|
|
36
|
+
mqReader.js
|
|
37
|
+
├── fs.watch() + 10ms debounce (instant notification)
|
|
38
|
+
├── 500ms fallback poll
|
|
39
|
+
├── 5s health check (detects silent fs.watch failures)
|
|
40
|
+
└── Reads from last byte offset (no re-reading)
|
|
41
|
+
│
|
|
42
|
+
▼
|
|
43
|
+
hookProcessor.js
|
|
44
|
+
├── Validates payload (session_id, event type, PID)
|
|
45
|
+
├── Calls sessionStore.handleEvent()
|
|
46
|
+
├── Records stats (latency, processing time)
|
|
47
|
+
└── Broadcasts to WebSocket clients
|
|
48
|
+
│
|
|
49
|
+
▼
|
|
50
|
+
sessionStore.js (coordinator)
|
|
51
|
+
├── sessionMatcher → link hook to session (5-priority system)
|
|
52
|
+
├── approvalDetector → tool approval timeout timers
|
|
53
|
+
├── teamManager → subagent team tracking
|
|
54
|
+
├── processMonitor → PID liveness checks
|
|
55
|
+
└── autoIdleManager → idle transition timers
|
|
56
|
+
│
|
|
57
|
+
▼
|
|
58
|
+
wsManager.js
|
|
59
|
+
└── Broadcasts session_update to all connected browsers
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
Browser (React / Vanilla JS)
|
|
63
|
+
└── IndexedDB + UI rendering
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Latency Budget
|
|
67
|
+
|
|
68
|
+
| Stage | Typical | Notes |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| jq enrichment | 2-5 ms | Single jq invocation |
|
|
71
|
+
| File append | ~0.1 ms | POSIX atomic for writes < 4096 bytes |
|
|
72
|
+
| fs.watch + debounce | 0-10 ms | Instant on macOS/Linux |
|
|
73
|
+
| Server processing | ~0.5 ms | handleEvent + broadcast |
|
|
74
|
+
| **Total end-to-end** | **3-17 ms** | Hook fired → browser updated |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 2. Hook Delivery Pipeline
|
|
79
|
+
|
|
80
|
+
### 2.1 Bash Hook Script (`hooks/dashboard-hook.sh`)
|
|
81
|
+
|
|
82
|
+
The hook script is copied to `~/.claude/hooks/dashboard-hook.sh` and registered in `~/.claude/settings.json`. It runs synchronously to read stdin, then forks a background subshell immediately (`} &>/dev/null &`) so the Claude process is never blocked.
|
|
83
|
+
|
|
84
|
+
**Execution flow:**
|
|
85
|
+
|
|
86
|
+
1. Capture `SENT_AT=$(date +%s)` and `INPUT=$(cat)` synchronously
|
|
87
|
+
2. Fork background subshell (`{ ... } &>/dev/null &; disown`)
|
|
88
|
+
3. In background: TTY detection (cached per PID in `/tmp/claude-tty-cache/$PPID`)
|
|
89
|
+
4. Single `jq -c` pass to enrich and extract fields
|
|
90
|
+
5. Tab title update (only on state-changing events: `SessionStart`, `UserPromptSubmit`, `PermissionRequest`, `Stop`, `Notification`, `SessionEnd`)
|
|
91
|
+
6. Deliver: append to `/tmp/claude-session-center/queue.jsonl` if MQ dir exists; otherwise HTTP POST to `http://localhost:3333/api/hooks`
|
|
92
|
+
|
|
93
|
+
**Fields enriched by jq:**
|
|
94
|
+
|
|
95
|
+
| Field | Source | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `claude_pid` | `$PPID` | Claude process PID |
|
|
98
|
+
| `hook_sent_at` | `date +%s * 1000` | Timestamp in ms for latency tracking |
|
|
99
|
+
| `tty_path` | `ps -o tty= -p $PPID` | Full TTY path (e.g. `/dev/ttys003`) |
|
|
100
|
+
| `term_program` | `$TERM_PROGRAM` | Terminal app name |
|
|
101
|
+
| `term_program_version` | `$TERM_PROGRAM_VERSION` | Terminal version |
|
|
102
|
+
| `vscode_pid` | `$VSCODE_PID` | VS Code extension host PID |
|
|
103
|
+
| `term` | `$TERM` | TERM env variable |
|
|
104
|
+
| `tab_id` | `$ITERM_SESSION_ID`, `$KITTY_WINDOW_ID`, `$WARP_SESSION_ID`, `$WEZTERM_PANE`, `$TERM_SESSION_ID` | Tab/session identifier |
|
|
105
|
+
| `window_id` | `$WINDOWID` | X11 window ID |
|
|
106
|
+
| `tmux` | `$TMUX`, `$TMUX_PANE` | `{session, pane}` or null |
|
|
107
|
+
| `is_ghostty` | `$GHOSTTY_RESOURCES_DIR` | Boolean flag |
|
|
108
|
+
| `kitty_pid` | `$KITTY_PID` | Kitty process PID |
|
|
109
|
+
| `agent_terminal_id` | `$AGENT_MANAGER_TERMINAL_ID` | PTY terminal ID injected by sshManager |
|
|
110
|
+
| `claude_project_dir` | `$CLAUDE_PROJECT_DIR` | Claude's project directory |
|
|
111
|
+
| `parent_session_id` | `$CLAUDE_CODE_PARENT_SESSION_ID` | Parent session for subagent linking |
|
|
112
|
+
| `team_name` | `$CLAUDE_CODE_TEAM_NAME` | Team name for multi-agent |
|
|
113
|
+
| `agent_name` | `$CLAUDE_CODE_AGENT_NAME` | Agent name (e.g. `backend-engineer`) |
|
|
114
|
+
| `agent_type` | `$CLAUDE_CODE_AGENT_TYPE` | Agent type (e.g. `task`) |
|
|
115
|
+
| `agent_id` | `$CLAUDE_CODE_AGENT_ID` | Agent UUID |
|
|
116
|
+
| `agent_color` | `$CLAUDE_CODE_AGENT_COLOR` | Agent accent color |
|
|
117
|
+
|
|
118
|
+
**TTY caching:** Cached per PPID in `/tmp/claude-tty-cache/$PPID` to avoid running `ps` on every hook event.
|
|
119
|
+
|
|
120
|
+
### 2.2 MQ Reader (`server/mqReader.js`)
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Queue file: /tmp/claude-session-center/queue.jsonl
|
|
124
|
+
(Windows: %TEMP%\claude-session-center\queue.jsonl)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Parameter | Value |
|
|
128
|
+
|---|---|
|
|
129
|
+
| Poll interval (fallback) | 500 ms |
|
|
130
|
+
| fs.watch debounce | 10 ms |
|
|
131
|
+
| Health check interval | 5000 ms |
|
|
132
|
+
| Truncation threshold | 1 MB |
|
|
133
|
+
|
|
134
|
+
**Read algorithm:**
|
|
135
|
+
1. `fs.watch()` fires → `scheduleRead()` (debounced 10ms)
|
|
136
|
+
2. Open file, get `fstat` size
|
|
137
|
+
3. If `fileSize < lastByteOffset`: external truncation detected, reset offset to 0
|
|
138
|
+
4. `readSync()` from `lastByteOffset` to `fileSize`
|
|
139
|
+
5. Split on `\n`, retain partial trailing line in `partialLine` buffer
|
|
140
|
+
6. Parse each complete JSON line → `processHookEvent()`
|
|
141
|
+
7. Advance `lastByteOffset` by bytes consumed (minus partial)
|
|
142
|
+
8. If `lastByteOffset > 1MB` and no partial: truncate file (write remaining partial back, reset offset)
|
|
143
|
+
|
|
144
|
+
**Snapshot resume:** On startup the reader accepts `resumeOffset` from a saved snapshot to continue from where it left off without reprocessing events.
|
|
145
|
+
|
|
146
|
+
**Stats tracked:** `linesProcessed`, `linesErrored`, `truncations`, `lastProcessedAt`, `startedAt`, `currentOffset`, `hasPartialLine`.
|
|
147
|
+
|
|
148
|
+
### 2.3 Hook Validation (`server/hookProcessor.js`)
|
|
149
|
+
|
|
150
|
+
Every hook (from HTTP or MQ) passes through `validateHookPayload()`:
|
|
151
|
+
|
|
152
|
+
| Field | Requirement |
|
|
153
|
+
|---|---|
|
|
154
|
+
| `session_id` | Required, string, max 256 chars |
|
|
155
|
+
| `hook_event_name` | Required, must be in `KNOWN_EVENTS` set |
|
|
156
|
+
| `claude_pid` | Optional, must be positive integer if present |
|
|
157
|
+
| `timestamp` | Optional, must be valid number if present |
|
|
158
|
+
|
|
159
|
+
Unknown event types are rejected with `"unknown event type: ..."`. Invalid payloads are logged and not processed.
|
|
160
|
+
|
|
161
|
+
### 2.4 HTTP Fallback (`server/hookRouter.js`)
|
|
162
|
+
|
|
163
|
+
When the MQ directory does not exist (server not yet started), the hook script falls back to:
|
|
164
|
+
```
|
|
165
|
+
POST http://localhost:3333/api/hooks
|
|
166
|
+
Content-Type: application/json
|
|
167
|
+
--connect-timeout 1 -m 3
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Rate limit: **100 requests/second per IP** (enforced by `hookRateLimitMiddleware`).
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 3. Hook Events and Density Levels
|
|
175
|
+
|
|
176
|
+
### 3.1 Claude Code Events (14 total)
|
|
177
|
+
|
|
178
|
+
| Event | Constant | When It Fires |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `SessionStart` | `EVENT_TYPES.SESSION_START` | Claude process starts |
|
|
181
|
+
| `UserPromptSubmit` | `EVENT_TYPES.USER_PROMPT_SUBMIT` | User submits a prompt |
|
|
182
|
+
| `PreToolUse` | `EVENT_TYPES.PRE_TOOL_USE` | Before a tool call executes |
|
|
183
|
+
| `PostToolUse` | `EVENT_TYPES.POST_TOOL_USE` | After a tool call succeeds |
|
|
184
|
+
| `PostToolUseFailure` | `EVENT_TYPES.POST_TOOL_USE_FAILURE` | After a tool call fails |
|
|
185
|
+
| `PermissionRequest` | `EVENT_TYPES.PERMISSION_REQUEST` | Claude needs user approval |
|
|
186
|
+
| `Stop` | `EVENT_TYPES.STOP` | Claude finishes its turn |
|
|
187
|
+
| `Notification` | `EVENT_TYPES.NOTIFICATION` | System notification |
|
|
188
|
+
| `SubagentStart` | `EVENT_TYPES.SUBAGENT_START` | Subagent spawned |
|
|
189
|
+
| `SubagentStop` | `EVENT_TYPES.SUBAGENT_STOP` | Subagent finished |
|
|
190
|
+
| `TeammateIdle` | `EVENT_TYPES.TEAMMATE_IDLE` | A teammate is idle (high density only) |
|
|
191
|
+
| `TaskCompleted` | `EVENT_TYPES.TASK_COMPLETED` | A task was completed |
|
|
192
|
+
| `PreCompact` | `EVENT_TYPES.PRE_COMPACT` | Context compaction about to start (high only) |
|
|
193
|
+
| `SessionEnd` | `EVENT_TYPES.SESSION_END` | Claude process exits |
|
|
194
|
+
|
|
195
|
+
### 3.2 Gemini CLI Events (7 total)
|
|
196
|
+
|
|
197
|
+
| Event | When It Fires |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `BeforeAgent` | Before agent turn |
|
|
200
|
+
| `BeforeTool` | Before tool call |
|
|
201
|
+
| `AfterTool` | After tool call |
|
|
202
|
+
| `AfterAgent` | After agent turn |
|
|
203
|
+
| (plus `SessionStart`, `SessionEnd`, `Notification` from Claude mapping) |
|
|
204
|
+
|
|
205
|
+
### 3.3 Codex Events (1 event)
|
|
206
|
+
|
|
207
|
+
| Event | When It Fires |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `agent-turn-complete` | After Codex completes a turn |
|
|
210
|
+
|
|
211
|
+
### 3.4 Density Levels
|
|
212
|
+
|
|
213
|
+
| Level | Claude Events | Gemini Events | Notes |
|
|
214
|
+
|---|---|---|---|
|
|
215
|
+
| `high` | All 14 | 7 (`SessionStart`, `BeforeAgent`, `BeforeTool`, `AfterTool`, `AfterAgent`, `SessionEnd`, `Notification`) | Full monitoring |
|
|
216
|
+
| `medium` | 12 (excludes `TeammateIdle`, `PreCompact`) | 5 (`SessionStart`, `BeforeAgent`, `AfterAgent`, `SessionEnd`, `Notification`) | Default — good balance |
|
|
217
|
+
| `low` | 5 (`SessionStart`, `UserPromptSubmit`, `PermissionRequest`, `Stop`, `SessionEnd`) | 3 (`SessionStart`, `AfterAgent`, `SessionEnd`) | Minimal overhead |
|
|
218
|
+
|
|
219
|
+
### 3.5 Hook Registration
|
|
220
|
+
|
|
221
|
+
Hooks are registered in `~/.claude/settings.json` under the `hooks` key. Each event gets a group entry:
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"_source": "ai-agent-session-center",
|
|
225
|
+
"hooks": [{ "type": "command", "command": "~/.claude/hooks/dashboard-hook.sh", "async": true }]
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Registration uses **atomic writes** (write to `.tmp.XXXX`, then `rename()`) to prevent corrupting `settings.json`. The installer checks for existing hooks before adding to avoid duplicates. Hooks are re-synced on every server startup if the script content has changed.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 4. Session State Machine
|
|
234
|
+
|
|
235
|
+
### 4.1 States
|
|
236
|
+
|
|
237
|
+
| Status | Constant | Animation | Description |
|
|
238
|
+
|---|---|---|---|
|
|
239
|
+
| `idle` | `SESSION_STATUS.IDLE` | `Idle` | No activity |
|
|
240
|
+
| `prompting` | `SESSION_STATUS.PROMPTING` | `Walking` + `Wave` emote | User prompt submitted |
|
|
241
|
+
| `working` | `SESSION_STATUS.WORKING` | `Running` | Tool in progress |
|
|
242
|
+
| `approval` | `SESSION_STATUS.APPROVAL` | `Waiting` | Needs user approval for a tool |
|
|
243
|
+
| `input` | `SESSION_STATUS.INPUT` | `Waiting` | Waiting for user answer (AskUserQuestion etc.) |
|
|
244
|
+
| `waiting` | `SESSION_STATUS.WAITING` | `Waiting` + `ThumbsUp` emote | Turn finished, ready for next prompt |
|
|
245
|
+
| `ended` | `SESSION_STATUS.ENDED` | `Death` | Session ended |
|
|
246
|
+
| `connecting` | `SESSION_STATUS.CONNECTING` | `Walking` + `Wave` emote | SSH terminal connecting |
|
|
247
|
+
|
|
248
|
+
**Heavy work variant:** If `totalToolCalls > 10` at `Stop` time, the animation switches to `Dance` instead of `Waiting+ThumbsUp`.
|
|
249
|
+
|
|
250
|
+
### 4.2 State Transitions
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
SessionStart → idle
|
|
254
|
+
UserPromptSubmit → prompting (Walking + Wave)
|
|
255
|
+
PreToolUse → working (Running)
|
|
256
|
+
PostToolUse → working (stays Running)
|
|
257
|
+
[timer expires] → approval (Waiting) — tool approval heuristic
|
|
258
|
+
PermissionRequest → approval (Waiting) — direct signal
|
|
259
|
+
[timer expires for userInput tools] → input (Waiting)
|
|
260
|
+
Stop → waiting (Waiting + ThumbsUp / Dance)
|
|
261
|
+
[2 min idle] → idle (auto-idle)
|
|
262
|
+
SessionEnd → ended (Death)
|
|
263
|
+
[10s after ended] → deleted from memory (non-SSH)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 4.3 Auto-Idle Timeouts
|
|
267
|
+
|
|
268
|
+
| Status | Timeout | Transitions To |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `prompting` | 30,000 ms (30 s) | `waiting` |
|
|
271
|
+
| `waiting` | 120,000 ms (2 min) | `idle` |
|
|
272
|
+
| `working` | 180,000 ms (3 min) | `idle` |
|
|
273
|
+
| `approval` | 600,000 ms (10 min) | `idle` (safety net) |
|
|
274
|
+
| `input` | 600,000 ms (10 min) | `idle` (safety net) |
|
|
275
|
+
|
|
276
|
+
Auto-idle is checked every **10 seconds** by `autoIdleManager.js`.
|
|
277
|
+
|
|
278
|
+
### 4.4 Session Object Fields
|
|
279
|
+
|
|
280
|
+
Every session in memory (`Map<string, Session>`) contains:
|
|
281
|
+
|
|
282
|
+
| Field | Type | Description |
|
|
283
|
+
|---|---|---|
|
|
284
|
+
| `sessionId` | string | Claude-assigned UUID or terminal ID |
|
|
285
|
+
| `projectPath` | string | Working directory (full path) |
|
|
286
|
+
| `projectName` | string | Last segment of projectPath |
|
|
287
|
+
| `title` | string | Auto-generated or user-set title |
|
|
288
|
+
| `status` | string | Current status (see table above) |
|
|
289
|
+
| `animationState` | string | `Idle`, `Walking`, `Running`, `Waiting`, `Death`, `Dance` |
|
|
290
|
+
| `emote` | string\|null | `Wave`, `ThumbsUp`, `Jump`, `Yes` or null |
|
|
291
|
+
| `startedAt` | number | Unix ms timestamp |
|
|
292
|
+
| `lastActivityAt` | number | Unix ms timestamp of last event |
|
|
293
|
+
| `endedAt` | number\|null | Unix ms timestamp or null |
|
|
294
|
+
| `currentPrompt` | string | Current/last prompt text |
|
|
295
|
+
| `promptHistory` | array | Last 50 prompts: `{text, timestamp}` |
|
|
296
|
+
| `toolUsage` | object | `{toolName: count}` map |
|
|
297
|
+
| `totalToolCalls` | number | Total tool calls this turn (reset at `Stop`) |
|
|
298
|
+
| `toolLog` | array | Last 200 tool entries: `{tool, input, timestamp, failed?, error?}` |
|
|
299
|
+
| `responseLog` | array | Last 50 response excerpts: `{text, timestamp}` (first 2000 chars each) |
|
|
300
|
+
| `events` | array | Last 50 lifecycle events: `{type, detail, timestamp}` |
|
|
301
|
+
| `model` | string | AI model name (e.g. `claude-opus-4-6`) |
|
|
302
|
+
| `subagentCount` | number | Currently active subagents |
|
|
303
|
+
| `archived` | number | 0 or 1 |
|
|
304
|
+
| `source` | string | `ssh`, `vscode`, `iterm`, `warp`, `terminal`, etc. |
|
|
305
|
+
| `pendingTool` | string\|null | Tool awaiting approval |
|
|
306
|
+
| `pendingToolDetail` | string\|null | Summary of pending tool input |
|
|
307
|
+
| `waitingDetail` | string\|null | Human-readable approval message |
|
|
308
|
+
| `cachedPid` | number\|null | Claude process PID |
|
|
309
|
+
| `queueCount` | number | Pending prompt queue count |
|
|
310
|
+
| `terminalId` | string\|null | Active PTY terminal ID |
|
|
311
|
+
| `lastTerminalId` | string\|null | Previous terminal ID (for resume) |
|
|
312
|
+
| `sshHost` | string | SSH host |
|
|
313
|
+
| `sshCommand` | string | Command run in terminal |
|
|
314
|
+
| `sshConfig` | object | `{host, port, username, authMethod, privateKeyPath, workingDir, command}` |
|
|
315
|
+
| `transcriptPath` | string | Claude transcript file path |
|
|
316
|
+
| `permissionMode` | string\|null | Claude permission mode |
|
|
317
|
+
| `teamId` | string\|null | Team this session belongs to |
|
|
318
|
+
| `teamRole` | string\|null | `leader` or `member` |
|
|
319
|
+
| `agentName` | string\|null | Agent name (multi-agent) |
|
|
320
|
+
| `agentType` | string\|null | Agent type |
|
|
321
|
+
| `agentColor` | string\|null | Agent accent color |
|
|
322
|
+
| `tmuxPaneId` | string\|null | Tmux pane ID (e.g. `%5`) |
|
|
323
|
+
| `isHistorical` | boolean | SSH session archived after end |
|
|
324
|
+
| `previousSessions` | array | Up to 5 previous session snapshots (for resume history) |
|
|
325
|
+
| `replacesId` | string | One-time field set during session re-key |
|
|
326
|
+
| `label` | string | User-assigned label |
|
|
327
|
+
| `summary` | string | AI-generated summary |
|
|
328
|
+
| `accentColor` | string | Custom accent color |
|
|
329
|
+
| `characterModel` | string | 3D character model override |
|
|
330
|
+
|
|
331
|
+
### 4.5 Event Ring Buffer
|
|
332
|
+
|
|
333
|
+
The server maintains a ring buffer of the last **500 events** for WebSocket reconnect replay:
|
|
334
|
+
|
|
335
|
+
```js
|
|
336
|
+
const EVENT_BUFFER_MAX = 500;
|
|
337
|
+
// Each entry: { seq: number, type: string, data: any, timestamp: number }
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
On reconnect, the client sends `{ type: "replay", sinceSeq: N }` and the server replays all events with `seq > N`.
|
|
341
|
+
|
|
342
|
+
### 4.6 Snapshot Persistence
|
|
343
|
+
|
|
344
|
+
Sessions are saved to `/tmp/claude-session-center/sessions-snapshot.json` every **10 seconds** using atomic write (tmp file + rename). The snapshot includes:
|
|
345
|
+
|
|
346
|
+
- All session objects
|
|
347
|
+
- `projectSessionCounters` Map
|
|
348
|
+
- `pidToSession` Map
|
|
349
|
+
- `pendingResume` Map
|
|
350
|
+
- `eventSeq` (ring buffer sequence number)
|
|
351
|
+
- `mqOffset` (byte offset in queue file)
|
|
352
|
+
|
|
353
|
+
On startup, the snapshot is loaded and PID liveness is checked. Dead PIDs result in sessions marked `ended`. SSH sessions with orphaned processes (alive but unreachable after server restart) are sent `SIGTERM`. Non-SSH ended sessions are kept for 30 minutes to allow auto-linking on `claude --resume`.
|
|
354
|
+
|
|
355
|
+
### 4.7 Broadcast Debounce
|
|
356
|
+
|
|
357
|
+
Session updates are debounced within a **50ms window** to batch rapid state changes. Within a batch, only the latest `session_update` per `sessionId` is sent (deduplication).
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 5. Session Matching (5-Priority System)
|
|
362
|
+
|
|
363
|
+
When a hook event arrives with an unknown `session_id`, the matcher (`server/sessionMatcher.js`) tries the following priorities:
|
|
364
|
+
|
|
365
|
+
| Priority | Strategy | Match Condition | Risk |
|
|
366
|
+
|---|---|---|---|
|
|
367
|
+
| 0 | `pendingResume` + terminal ID | `agent_terminal_id` matches a pending resume entry | Low — explicit user action |
|
|
368
|
+
| 0 (fallback) | `pendingResume` + workDir | Exactly one pending resume has matching `projectPath` | Medium — ambiguous if multiple |
|
|
369
|
+
| 0.5 | Snapshot-restored ended session | One ended session with `ServerRestart` event matches `cwd` (within 30 min) | Low — post-restart linking |
|
|
370
|
+
| 1 | `agent_terminal_id` env var | SSH terminal injected `AGENT_MANAGER_TERMINAL_ID` into PTY env | Low — direct match |
|
|
371
|
+
| 2 | `tryLinkByWorkDir` | `pendingLinks` Map has entry for the hook's `cwd` | Medium — two sessions in same dir |
|
|
372
|
+
| 3 | Path scan (connecting sessions) | Exactly one `connecting` session has matching `projectPath` | Medium — ambiguous if multiple |
|
|
373
|
+
| 4 | PID parent check | Claude's PID is a child of a known PTY process (`ps -o ppid=`) | High — unreliable across shells |
|
|
374
|
+
|
|
375
|
+
If no match is found, a **display-only card** is created with the detected terminal source.
|
|
376
|
+
|
|
377
|
+
### Session Source Detection
|
|
378
|
+
|
|
379
|
+
The `detectHookSource()` function maps environment variables to source labels:
|
|
380
|
+
|
|
381
|
+
| Source | Detection |
|
|
382
|
+
|---|---|
|
|
383
|
+
| `vscode` | `$VSCODE_PID` present, or `TERM_PROGRAM` contains `vscode`/`code` |
|
|
384
|
+
| `jetbrains` | `TERM_PROGRAM` contains `jetbrains`, `intellij`, `idea`, `webstorm`, etc. |
|
|
385
|
+
| `iterm` | `TERM_PROGRAM` contains `iterm` |
|
|
386
|
+
| `warp` | `TERM_PROGRAM` contains `warp` |
|
|
387
|
+
| `kitty` | `TERM_PROGRAM` contains `kitty` |
|
|
388
|
+
| `ghostty` | `TERM_PROGRAM` contains `ghostty` or `$GHOSTTY_RESOURCES_DIR` set |
|
|
389
|
+
| `alacritty` | `TERM_PROGRAM` contains `alacritty` |
|
|
390
|
+
| `wezterm` | `TERM_PROGRAM` contains `wezterm` or `$WEZTERM_PANE` set |
|
|
391
|
+
| `hyper` | `TERM_PROGRAM` contains `hyper` |
|
|
392
|
+
| `terminal` | `TERM_PROGRAM` is `apple_terminal` |
|
|
393
|
+
| `tmux` | `$TMUX` is set |
|
|
394
|
+
| `unknown` | No matching env var |
|
|
395
|
+
|
|
396
|
+
### Session Re-keying
|
|
397
|
+
|
|
398
|
+
When a resumed session is matched, `reKeyResumedSession()` transfers data from the old session key to the new `session_id`:
|
|
399
|
+
- Deletes old Map entry
|
|
400
|
+
- Resets `status`, `animationState`, `emote`, `startedAt`, `totalToolCalls`, `toolUsage`, `promptHistory`, `toolLog`, `responseLog`, `events`
|
|
401
|
+
- Preserves `previousSessions` array (history chain)
|
|
402
|
+
- Sets `replacesId` for DB migration
|
|
403
|
+
- Inserts under new `session_id`
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## 6. Approval Detection
|
|
408
|
+
|
|
409
|
+
### 6.1 Timeout Heuristic
|
|
410
|
+
|
|
411
|
+
When `PreToolUse` fires, `startApprovalTimer()` sets a category-based timer:
|
|
412
|
+
|
|
413
|
+
| Category | Tools | Timeout | Status Set |
|
|
414
|
+
|---|---|---|---|
|
|
415
|
+
| `fast` | `Read`, `Write`, `Edit`, `Grep`, `Glob`, `NotebookEdit` | 3,000 ms | `approval` |
|
|
416
|
+
| `userInput` | `AskUserQuestion`, `EnterPlanMode`, `ExitPlanMode` | 3,000 ms | `input` |
|
|
417
|
+
| `medium` | `WebFetch`, `WebSearch` | 15,000 ms | `approval` |
|
|
418
|
+
| `slow` | `Bash`, `Task` | 8,000 ms | `approval` |
|
|
419
|
+
|
|
420
|
+
Tools not in any category get no timer. The timer is cleared immediately when `PostToolUse` or `PostToolUseFailure` arrives.
|
|
421
|
+
|
|
422
|
+
**Waiting detail labels:**
|
|
423
|
+
|
|
424
|
+
| Status | Label format |
|
|
425
|
+
|---|---|
|
|
426
|
+
| `approval` | `"Approve {toolName}: {inputSummary}"` or `"Approve {toolName}"` |
|
|
427
|
+
| `input` (`AskUserQuestion`) | `"Waiting for your answer"` |
|
|
428
|
+
| `input` (`EnterPlanMode`) | `"Review plan mode request"` |
|
|
429
|
+
| `input` (`ExitPlanMode`) | `"Review plan"` |
|
|
430
|
+
|
|
431
|
+
### 6.2 `hasChildProcesses` Check
|
|
432
|
+
|
|
433
|
+
For `slow` category tools (Bash, Task), before setting `approval` status, the server checks if the cached PID still has child processes via:
|
|
434
|
+
```bash
|
|
435
|
+
pgrep -P {pid}
|
|
436
|
+
```
|
|
437
|
+
If child processes exist, the command is still running (not waiting for approval) and the status transition is skipped.
|
|
438
|
+
|
|
439
|
+
### 6.3 `PermissionRequest` Direct Signal
|
|
440
|
+
|
|
441
|
+
When the `PermissionRequest` hook event fires (at medium+ density), the heuristic timer is immediately cleared and the session transitions directly to `approval` status. This is more reliable than the timeout approach.
|
|
442
|
+
|
|
443
|
+
### 6.4 Timer Management
|
|
444
|
+
|
|
445
|
+
All pending timers are stored in a `Map<sessionId, timeoutHandle>`. A new timer for the same session replaces any existing one. All timers are cleared on:
|
|
446
|
+
- `PostToolUse`
|
|
447
|
+
- `PostToolUseFailure`
|
|
448
|
+
- `PermissionRequest`
|
|
449
|
+
- `Stop`
|
|
450
|
+
- `SessionEnd`
|
|
451
|
+
- Process liveness check (dead process)
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 7. Team and Subagent Tracking
|
|
456
|
+
|
|
457
|
+
### 7.1 Auto-Detection (Path-Based)
|
|
458
|
+
|
|
459
|
+
When `SubagentStart` fires on a parent session, `addPendingSubagent()` records `{parentSessionId, parentCwd, agentType, timestamp}`. When a new `SessionStart` arrives within **10 seconds** from a child session whose `cwd` matches (exact or parent/child path relationship), the sessions are linked into a team.
|
|
460
|
+
|
|
461
|
+
Stale entries older than **30 seconds** are pruned from the pending list.
|
|
462
|
+
|
|
463
|
+
### 7.2 Direct Linking (Priority 0)
|
|
464
|
+
|
|
465
|
+
When the `CLAUDE_CODE_PARENT_SESSION_ID` env var is set, `linkByParentSessionId()` directly links the child to its parent without path guessing. This is the preferred mechanism.
|
|
466
|
+
|
|
467
|
+
### 7.3 Team Object
|
|
468
|
+
|
|
469
|
+
```js
|
|
470
|
+
{
|
|
471
|
+
teamId: "team-{parentSessionId}",
|
|
472
|
+
parentSessionId: string,
|
|
473
|
+
childSessionIds: Set<string>,
|
|
474
|
+
teamName: string, // "{projectName} Team" or from env var
|
|
475
|
+
createdAt: number
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**Serialized form** (for WebSocket) converts `childSessionIds` Set to array.
|
|
480
|
+
|
|
481
|
+
### 7.4 Team Config Reader
|
|
482
|
+
|
|
483
|
+
Team configurations can be stored in `~/.claude/teams/{teamName}/config.json`:
|
|
484
|
+
```json
|
|
485
|
+
{
|
|
486
|
+
"members": {
|
|
487
|
+
"backend-engineer": {
|
|
488
|
+
"tmuxPaneId": "%3",
|
|
489
|
+
"backendType": "node",
|
|
490
|
+
"color": "#00ff88"
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
The team name is sanitized (only `a-zA-Z0-9_-. `) before constructing the file path (path traversal prevention). If the config is found, the member's `tmuxPaneId`, `backendType`, and `agentColor` are applied to the child session.
|
|
497
|
+
|
|
498
|
+
### 7.5 Team Cleanup
|
|
499
|
+
|
|
500
|
+
When a team member session ends, `handleTeamMemberEnd()` removes it from the team's `childSessionIds`. If the **parent** ends and all children are also ended, the team is deleted after a **15-second** delay.
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## 8. SSH/PTY Terminal Management
|
|
505
|
+
|
|
506
|
+
### 8.1 Terminal Modes
|
|
507
|
+
|
|
508
|
+
| Mode | How | When |
|
|
509
|
+
|---|---|---|
|
|
510
|
+
| Local direct | `node-pty` spawns `$SHELL` | `host` is `localhost`/`127.0.0.1`/`::1` |
|
|
511
|
+
| Remote SSH | `node-pty` spawns `ssh -t -i keyfile user@host` | Remote host |
|
|
512
|
+
| Tmux attach | Shell runs `tmux attach -t '{session}'` | `tmuxSession` parameter provided |
|
|
513
|
+
| Tmux new | Shell runs `tmux new-session -s 'claude-{id}' '{command}'` | `useTmux: true` |
|
|
514
|
+
|
|
515
|
+
### 8.2 PTY Spawn Parameters
|
|
516
|
+
|
|
517
|
+
```js
|
|
518
|
+
pty.spawn(shell, args, {
|
|
519
|
+
name: 'xterm-256color',
|
|
520
|
+
cols: 120,
|
|
521
|
+
rows: 40,
|
|
522
|
+
cwd, // workDir for local; homedir() for remote
|
|
523
|
+
env: {
|
|
524
|
+
...process.env,
|
|
525
|
+
AGENT_MANAGER_TERMINAL_ID: terminalId,
|
|
526
|
+
// ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY if apiKey provided
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Terminal IDs use the format: `term-{Date.now()}-{random6chars}`
|
|
532
|
+
Tmux terminal IDs: `term-tmux-{Date.now()}-{random6chars}`
|
|
533
|
+
|
|
534
|
+
### 8.3 Shell-Ready Detection
|
|
535
|
+
|
|
536
|
+
Before sending the launch command, the server waits for the shell to display a prompt. The detection algorithm:
|
|
537
|
+
|
|
538
|
+
1. Buffer PTY output (capped at 4096 bytes)
|
|
539
|
+
2. Strip ANSI escape sequences (`CSI` + `OSC` patterns)
|
|
540
|
+
3. After 100ms of silence (settle timer), check if the last non-empty line ends with `[#$%>]\s*$` and is shorter than 200 chars
|
|
541
|
+
4. Resolve `true` (prompt detected) or `false` (timeout)
|
|
542
|
+
|
|
543
|
+
| Timeout | Local | Remote SSH |
|
|
544
|
+
|---|---|---|
|
|
545
|
+
| Shell-ready wait | 5,000 ms | 15,000 ms |
|
|
546
|
+
|
|
547
|
+
On timeout, the command is sent anyway with a warning log.
|
|
548
|
+
|
|
549
|
+
### 8.4 Output Ring Buffer
|
|
550
|
+
|
|
551
|
+
Each terminal maintains an output ring buffer capped at **128 KB** (128 × 1024 bytes). When a new WebSocket client subscribes, the full buffer is replayed so the client sees previous terminal output.
|
|
552
|
+
|
|
553
|
+
### 8.5 Pending Links
|
|
554
|
+
|
|
555
|
+
When `createTerminal()` is called, a `pendingLinks` entry is registered: `workDir → {terminalId, host, createdAt}`. This is used by the session matcher (Priority 2) to link the first `SessionStart` hook from that directory to the correct terminal session.
|
|
556
|
+
|
|
557
|
+
Pending links expire after **60 seconds** (cleaned up every 30s).
|
|
558
|
+
|
|
559
|
+
### 8.6 Input Validation
|
|
560
|
+
|
|
561
|
+
All inputs to `createTerminal()` are validated against injection patterns. The shell metacharacter regex is: `/[;|&$\`\\!><()\n\r{}[\]]/`
|
|
562
|
+
|
|
563
|
+
| Parameter | Validation |
|
|
564
|
+
|---|---|
|
|
565
|
+
| `workingDir` | Max 1024 chars, no shell metacharacters (after stripping leading `~`) |
|
|
566
|
+
| `command` | Max 512 chars, no shell metacharacters |
|
|
567
|
+
| `tmuxSession` | Max 128 chars, only `[a-zA-Z0-9_.\-]` |
|
|
568
|
+
| `host` | Max 255 chars, no shell metacharacters |
|
|
569
|
+
| `username` | Max 128 chars, only `[a-zA-Z0-9_.\-]` |
|
|
570
|
+
| `port` | Integer 1–65535 |
|
|
571
|
+
|
|
572
|
+
API keys are passed via the PTY `env` object, never interpolated into shell command strings.
|
|
573
|
+
|
|
574
|
+
### 8.7 Terminal Limits
|
|
575
|
+
|
|
576
|
+
Maximum **10 terminals** open simultaneously (enforced at `POST /api/terminals` and `POST /api/teams/:id/members/:sessionId/terminal`).
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## 9. WebSocket Protocol
|
|
581
|
+
|
|
582
|
+
### 9.1 Connection Lifecycle
|
|
583
|
+
|
|
584
|
+
1. Client connects to `ws://localhost:3333`
|
|
585
|
+
2. If password enabled: token validated via cookie, Authorization header, or `?token=` query param; rejected with code `4001` if invalid
|
|
586
|
+
3. Server sends `snapshot` message with all current sessions, teams, and event sequence number
|
|
587
|
+
4. Client sends `replay` request if it has a previous sequence number (reconnect case)
|
|
588
|
+
5. Heartbeat: server pings every **30 seconds**, terminates unresponsive clients (no pong within **10 seconds**)
|
|
589
|
+
|
|
590
|
+
### 9.2 Server → Client Messages
|
|
591
|
+
|
|
592
|
+
| Type | Payload | When Sent |
|
|
593
|
+
|---|---|---|
|
|
594
|
+
| `snapshot` | `{sessions, teams, seq}` | On initial connection |
|
|
595
|
+
| `session_update` | `{session, team?}` | Any session state change |
|
|
596
|
+
| `session_removed` | `{sessionId}` | Session deleted via API |
|
|
597
|
+
| `team_update` | `{team}` | Team membership change |
|
|
598
|
+
| `hook_stats` | `{stats}` | After each hook event (throttled to 1/sec) |
|
|
599
|
+
| `terminal_output` | `{terminalId, data: base64}` | PTY output |
|
|
600
|
+
| `terminal_ready` | `{terminalId}` | PTY spawned and ready |
|
|
601
|
+
| `terminal_closed` | `{terminalId, reason}` | PTY process exited |
|
|
602
|
+
| `clearBrowserDb` | — | Full reset requested via API |
|
|
603
|
+
| `replay` | event objects | Missed events on reconnect |
|
|
604
|
+
|
|
605
|
+
### 9.3 Client → Server Messages
|
|
606
|
+
|
|
607
|
+
| Type | Payload | Action |
|
|
608
|
+
|---|---|---|
|
|
609
|
+
| `terminal_input` | `{terminalId, data}` | Write to PTY stdin |
|
|
610
|
+
| `terminal_resize` | `{terminalId, cols, rows}` | Resize PTY |
|
|
611
|
+
| `terminal_disconnect` | `{terminalId}` | Close PTY |
|
|
612
|
+
| `terminal_subscribe` | `{terminalId}` | Register WebSocket for terminal output relay + replay buffer |
|
|
613
|
+
| `update_queue_count` | `{sessionId, count}` | Update session queue count |
|
|
614
|
+
| `replay` | `{sinceSeq}` | Request missed events since sequence number |
|
|
615
|
+
|
|
616
|
+
### 9.4 Backpressure
|
|
617
|
+
|
|
618
|
+
For non-critical messages (only `hook_stats`), the server checks `client.bufferedAmount`. If it exceeds **1 MB**, the message is dropped for that client.
|
|
619
|
+
|
|
620
|
+
### 9.5 Hook Stats Throttle
|
|
621
|
+
|
|
622
|
+
`hook_stats` broadcasts are throttled to a maximum of **once per second** per client. If a stats update arrives while the throttle window is active, it is stored as `pendingHookStats` and sent when the window expires.
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## 10. REST API
|
|
627
|
+
|
|
628
|
+
All endpoints except auth and hook ingestion require authentication when `passwordHash` is configured. The auth middleware checks: cookie `auth_token`, then `Authorization: Bearer {token}`, then `?token=` query param.
|
|
629
|
+
|
|
630
|
+
### 10.1 Auth Endpoints (No Auth Required)
|
|
631
|
+
|
|
632
|
+
| Method | Path | Body | Description |
|
|
633
|
+
|---|---|---|---|
|
|
634
|
+
| `GET` | `/api/auth/status` | — | Returns `{passwordRequired, authenticated}` |
|
|
635
|
+
| `POST` | `/api/auth/login` | `{password}` | Returns `{success, token}`, sets `auth_token` cookie |
|
|
636
|
+
| `POST` | `/api/auth/logout` | — | Removes token, clears cookie |
|
|
637
|
+
|
|
638
|
+
### 10.2 Hook Ingestion (No Auth — Rate Limited)
|
|
639
|
+
|
|
640
|
+
| Method | Path | Body | Description |
|
|
641
|
+
|---|---|---|---|
|
|
642
|
+
| `POST` | `/api/hooks` | Hook JSON | Processes a hook event; rate limit 100/sec per IP |
|
|
643
|
+
|
|
644
|
+
### 10.3 Session Endpoints
|
|
645
|
+
|
|
646
|
+
| Method | Path | Body | Description |
|
|
647
|
+
|---|---|---|---|
|
|
648
|
+
| `GET` | `/api/sessions` | — | Returns all in-memory sessions |
|
|
649
|
+
| `GET` | `/api/sessions/:id/source` | — | Returns `{source}` (vscode/terminal/etc.) |
|
|
650
|
+
| `PUT` | `/api/sessions/:id/title` | `{title}` | Update session title (max 500 chars) |
|
|
651
|
+
| `PUT` | `/api/sessions/:id/label` | `{label}` | Update session label |
|
|
652
|
+
| `PUT` | `/api/sessions/:id/accent-color` | `{color}` | Update accent color (max 50 chars) |
|
|
653
|
+
| `POST` | `/api/sessions/:id/kill` | `{confirm: true}` | Send SIGTERM (SIGKILL after 3s), archive session |
|
|
654
|
+
| `DELETE` | `/api/sessions/:id` | — | Permanently delete from memory, broadcast removal |
|
|
655
|
+
| `POST` | `/api/sessions/:id/resume` | — | Resume SSH session (`claude --resume {id} \|\| claude --continue`) |
|
|
656
|
+
| `POST` | `/api/sessions/:id/summarize` | `{context, promptTemplate?, custom_prompt?}` | Summarize via Claude CLI (`haiku` model), max 2 concurrent |
|
|
657
|
+
|
|
658
|
+
### 10.4 Terminal Endpoints
|
|
659
|
+
|
|
660
|
+
| Method | Path | Body | Description |
|
|
661
|
+
|---|---|---|---|
|
|
662
|
+
| `POST` | `/api/terminals` | Connection config | Create PTY terminal (max 10 total) |
|
|
663
|
+
| `GET` | `/api/terminals` | — | List all active terminals |
|
|
664
|
+
| `DELETE` | `/api/terminals/:id` | — | Close and cleanup terminal |
|
|
665
|
+
|
|
666
|
+
**`POST /api/terminals` body fields:**
|
|
667
|
+
`host`, `port` (default 22), `username` (required), `password`, `privateKeyPath`, `authMethod` (default `key`), `workingDir` (default `~`), `command` (default `claude`), `apiKey`, `tmuxSession`, `useTmux`, `sessionTitle`, `label`
|
|
668
|
+
|
|
669
|
+
### 10.5 SSH Key and Tmux Endpoints
|
|
670
|
+
|
|
671
|
+
| Method | Path | Body | Description |
|
|
672
|
+
|---|---|---|---|
|
|
673
|
+
| `GET` | `/api/ssh-keys` | — | List private keys from `~/.ssh/` |
|
|
674
|
+
| `POST` | `/api/tmux-sessions` | Connection config | List tmux sessions on host |
|
|
675
|
+
|
|
676
|
+
### 10.6 Team Endpoints
|
|
677
|
+
|
|
678
|
+
| Method | Path | Body | Description |
|
|
679
|
+
|---|---|---|---|
|
|
680
|
+
| `GET` | `/api/teams/:teamId/config` | — | Read team config from `~/.claude/teams/{name}/config.json` |
|
|
681
|
+
| `POST` | `/api/teams/:teamId/members/:sessionId/terminal` | — | Attach to member's tmux pane (requires `tmuxPaneId` on session) |
|
|
682
|
+
|
|
683
|
+
### 10.7 Hook Management Endpoints
|
|
684
|
+
|
|
685
|
+
| Method | Path | Body | Description |
|
|
686
|
+
|---|---|---|---|
|
|
687
|
+
| `GET` | `/api/hooks/status` | — | Current density and installed events |
|
|
688
|
+
| `POST` | `/api/hooks/install` | `{density}` | Install hooks at specified density |
|
|
689
|
+
| `POST` | `/api/hooks/uninstall` | — | Remove all dashboard hooks |
|
|
690
|
+
|
|
691
|
+
### 10.8 Database / History Endpoints
|
|
692
|
+
|
|
693
|
+
| Method | Path | Query Params | Description |
|
|
694
|
+
|---|---|---|---|
|
|
695
|
+
| `GET` | `/api/db/sessions` | `query`, `project`, `status`, `dateFrom`, `dateTo`, `archived`, `sortBy`, `sortDir`, `page`, `pageSize` | Search/list sessions from SQLite |
|
|
696
|
+
| `GET` | `/api/db/sessions/:id` | — | Full session detail with prompts, responses, tool_calls, events, notes |
|
|
697
|
+
| `DELETE` | `/api/db/sessions/:id` | — | Cascade delete session and all child records |
|
|
698
|
+
| `GET` | `/api/db/projects` | — | Distinct projects |
|
|
699
|
+
| `GET` | `/api/db/search` | `query`, `type` (`all`/`prompts`/`responses`), `page`, `pageSize` | Full-text search across prompts and responses |
|
|
700
|
+
| `GET` | `/api/sessions/history` | `projectPath?` | Legacy endpoint; returns all or project sessions |
|
|
701
|
+
|
|
702
|
+
### 10.9 Notes Endpoints
|
|
703
|
+
|
|
704
|
+
| Method | Path | Body | Description |
|
|
705
|
+
|---|---|---|---|
|
|
706
|
+
| `GET` | `/api/db/sessions/:id/notes` | — | Get notes for session |
|
|
707
|
+
| `POST` | `/api/db/sessions/:id/notes` | `{text}` | Add note (max 10,000 chars) |
|
|
708
|
+
| `DELETE` | `/api/db/notes/:id` | — | Delete note by ID |
|
|
709
|
+
|
|
710
|
+
### 10.10 Analytics Endpoints
|
|
711
|
+
|
|
712
|
+
| Method | Path | Description |
|
|
713
|
+
|---|---|---|
|
|
714
|
+
| `GET` | `/api/db/analytics/summary` | Total sessions, active sessions, total prompts, total tool calls, most used tool, busiest project |
|
|
715
|
+
| `GET` | `/api/db/analytics/tools` | Tool breakdown with counts and percentages |
|
|
716
|
+
| `GET` | `/api/db/analytics/projects` | Active projects with session count and last activity |
|
|
717
|
+
| `GET` | `/api/db/analytics/heatmap` | Activity heatmap by `{day_of_week, hour, count}` |
|
|
718
|
+
|
|
719
|
+
### 10.11 Stats and Admin Endpoints
|
|
720
|
+
|
|
721
|
+
| Method | Path | Description |
|
|
722
|
+
|---|---|---|
|
|
723
|
+
| `GET` | `/api/hook-stats` | Hook performance statistics |
|
|
724
|
+
| `POST` | `/api/hook-stats/reset` | Reset hook stats |
|
|
725
|
+
| `GET` | `/api/mq-stats` | MQ reader stats (offset, linesProcessed, etc.) |
|
|
726
|
+
| `POST` | `/api/reset` | Broadcast `clearBrowserDb` to all clients |
|
|
727
|
+
|
|
728
|
+
### 10.12 Rate Limiting
|
|
729
|
+
|
|
730
|
+
In-memory sliding-window rate limiter (no external dependencies):
|
|
731
|
+
|
|
732
|
+
| Endpoint | Limit |
|
|
733
|
+
|---|---|
|
|
734
|
+
| `/api/hooks` | 100 requests/second per IP |
|
|
735
|
+
| `/api/sessions/:id/summarize` | 2 concurrent requests |
|
|
736
|
+
| `/api/terminals` (POST) | 10 terminals total |
|
|
737
|
+
|
|
738
|
+
Stale rate limit buckets are cleaned up every **30 seconds** (entries older than 5s).
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## 11. Authentication
|
|
743
|
+
|
|
744
|
+
Authentication is optional. It is enabled when `passwordHash` is set in `data/server-config.json`.
|
|
745
|
+
|
|
746
|
+
### 11.1 Password Hashing
|
|
747
|
+
|
|
748
|
+
Uses Node.js `crypto.scryptSync`:
|
|
749
|
+
|
|
750
|
+
```
|
|
751
|
+
salt = randomBytes(16).toString('hex') // 32 hex chars
|
|
752
|
+
hash = scryptSync(password, salt, 64).toString('hex') // 128 hex chars
|
|
753
|
+
stored = "${salt}:${hash}"
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
Verification uses `crypto.timingSafeEqual` to prevent timing attacks. Passwords must be at least 4 characters (enforced by setup wizard).
|
|
757
|
+
|
|
758
|
+
### 11.2 Token Management
|
|
759
|
+
|
|
760
|
+
- Tokens are 32 random bytes encoded as hex (64 hex chars)
|
|
761
|
+
- TTL: **24 hours** (`TOKEN_TTL_MS = 24 * 60 * 60 * 1000`)
|
|
762
|
+
- Stored in-memory: `Map<token, {createdAt: number}>`
|
|
763
|
+
- Expired tokens are removed lazily on `validateToken()` check
|
|
764
|
+
- Periodic cleanup runs every **1 hour** to sweep all expired tokens
|
|
765
|
+
|
|
766
|
+
### 11.3 Token Extraction Priority
|
|
767
|
+
|
|
768
|
+
1. Cookie: `auth_token={token}`
|
|
769
|
+
2. HTTP header: `Authorization: Bearer {token}`
|
|
770
|
+
3. Query string: `?token={token}` (used for WebSocket connections)
|
|
771
|
+
|
|
772
|
+
### 11.4 Protected vs Unprotected Routes
|
|
773
|
+
|
|
774
|
+
| Routes | Auth Required |
|
|
775
|
+
|---|---|
|
|
776
|
+
| `/api/auth/*` | No |
|
|
777
|
+
| `/api/hooks` | No (hooks must work without login) |
|
|
778
|
+
| Static files | No (login page is part of SPA) |
|
|
779
|
+
| All other `/api/*` | Yes (if password enabled) |
|
|
780
|
+
| WebSocket | Yes (if password enabled); rejected with WS code `4001` |
|
|
781
|
+
|
|
782
|
+
Cookie is set with: `HttpOnly; SameSite=Strict; Path=/; Max-Age=86400`
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## 12. SQLite Persistence
|
|
787
|
+
|
|
788
|
+
### 12.1 Database Location
|
|
789
|
+
|
|
790
|
+
```
|
|
791
|
+
data/sessions.db
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
Opened with **WAL mode** (`PRAGMA journal_mode = WAL`) for concurrent reads and writes.
|
|
795
|
+
|
|
796
|
+
### 12.2 Schema
|
|
797
|
+
|
|
798
|
+
**Table: `sessions`**
|
|
799
|
+
|
|
800
|
+
| Column | Type | Notes |
|
|
801
|
+
|---|---|---|
|
|
802
|
+
| `id` | TEXT PRIMARY KEY | Claude session UUID |
|
|
803
|
+
| `project_path` | TEXT | Full working directory |
|
|
804
|
+
| `project_name` | TEXT | Project display name |
|
|
805
|
+
| `title` | TEXT | Session title |
|
|
806
|
+
| `model` | TEXT | AI model name |
|
|
807
|
+
| `status` | TEXT | Final status |
|
|
808
|
+
| `source` | TEXT DEFAULT 'hook' | `hook`, `ssh`, `vscode`, etc. |
|
|
809
|
+
| `label` | TEXT | User-assigned label |
|
|
810
|
+
| `summary` | TEXT | AI-generated summary |
|
|
811
|
+
| `team_id` | TEXT | Team reference |
|
|
812
|
+
| `team_role` | TEXT | `leader` or `member` |
|
|
813
|
+
| `character_model` | TEXT | 3D character model override |
|
|
814
|
+
| `accent_color` | TEXT | Custom accent color |
|
|
815
|
+
| `started_at` | INTEGER | Unix ms |
|
|
816
|
+
| `ended_at` | INTEGER | Unix ms or null |
|
|
817
|
+
| `last_activity_at` | INTEGER | Unix ms |
|
|
818
|
+
| `total_prompts` | INTEGER DEFAULT 0 | |
|
|
819
|
+
| `total_tool_calls` | INTEGER DEFAULT 0 | |
|
|
820
|
+
| `archived` | INTEGER DEFAULT 0 | 0 or 1 |
|
|
821
|
+
|
|
822
|
+
**Indexes:** `project_path`, `status`, `started_at`, `last_activity_at`
|
|
823
|
+
|
|
824
|
+
**Table: `prompts`**
|
|
825
|
+
|
|
826
|
+
| Column | Type | Notes |
|
|
827
|
+
|---|---|---|
|
|
828
|
+
| `id` | INTEGER PK AUTOINCREMENT | |
|
|
829
|
+
| `session_id` | TEXT NOT NULL | FK → sessions.id |
|
|
830
|
+
| `text` | TEXT | Prompt text |
|
|
831
|
+
| `timestamp` | INTEGER | Unix ms |
|
|
832
|
+
|
|
833
|
+
**Unique index:** `(session_id, timestamp)` — deduplication on upsert.
|
|
834
|
+
|
|
835
|
+
**Table: `responses`**
|
|
836
|
+
|
|
837
|
+
| Column | Type | Notes |
|
|
838
|
+
|---|---|---|
|
|
839
|
+
| `id` | INTEGER PK AUTOINCREMENT | |
|
|
840
|
+
| `session_id` | TEXT NOT NULL | FK → sessions.id |
|
|
841
|
+
| `text_excerpt` | TEXT | First 2000 chars of response |
|
|
842
|
+
| `timestamp` | INTEGER | Unix ms |
|
|
843
|
+
|
|
844
|
+
**Unique index:** `(session_id, timestamp)`
|
|
845
|
+
|
|
846
|
+
**Table: `tool_calls`**
|
|
847
|
+
|
|
848
|
+
| Column | Type | Notes |
|
|
849
|
+
|---|---|---|
|
|
850
|
+
| `id` | INTEGER PK AUTOINCREMENT | |
|
|
851
|
+
| `session_id` | TEXT NOT NULL | FK → sessions.id |
|
|
852
|
+
| `tool_name` | TEXT | Tool name |
|
|
853
|
+
| `tool_input_summary` | TEXT | Summarized input |
|
|
854
|
+
| `timestamp` | INTEGER | Unix ms |
|
|
855
|
+
|
|
856
|
+
**Unique index:** `(session_id, timestamp, tool_name)`
|
|
857
|
+
**Additional index:** `tool_name` (for analytics queries)
|
|
858
|
+
|
|
859
|
+
**Table: `events`**
|
|
860
|
+
|
|
861
|
+
| Column | Type | Notes |
|
|
862
|
+
|---|---|---|
|
|
863
|
+
| `id` | INTEGER PK AUTOINCREMENT | |
|
|
864
|
+
| `session_id` | TEXT NOT NULL | FK → sessions.id |
|
|
865
|
+
| `event_type` | TEXT | Hook event name |
|
|
866
|
+
| `detail` | TEXT | Human-readable description |
|
|
867
|
+
| `timestamp` | INTEGER | Unix ms |
|
|
868
|
+
|
|
869
|
+
**Table: `notes`**
|
|
870
|
+
|
|
871
|
+
| Column | Type | Notes |
|
|
872
|
+
|---|---|---|
|
|
873
|
+
| `id` | INTEGER PK AUTOINCREMENT | |
|
|
874
|
+
| `session_id` | TEXT NOT NULL | FK → sessions.id |
|
|
875
|
+
| `text` | TEXT | Note content |
|
|
876
|
+
| `created_at` | INTEGER | Unix ms |
|
|
877
|
+
| `updated_at` | INTEGER | Unix ms |
|
|
878
|
+
|
|
879
|
+
### 12.3 Upsert Strategy
|
|
880
|
+
|
|
881
|
+
All child records (prompts, responses, tool_calls) use `INSERT OR IGNORE` with unique indexes for deduplication. The `sessions` table uses `INSERT ... ON CONFLICT(id) DO UPDATE SET ...`, updating all mutable fields. A single `db.transaction()` wraps the full upsert for atomicity.
|
|
882
|
+
|
|
883
|
+
### 12.4 Persist-on-Events
|
|
884
|
+
|
|
885
|
+
Only key state transitions trigger a DB write:
|
|
886
|
+
|
|
887
|
+
| Event | Triggers DB Upsert |
|
|
888
|
+
|---|---|
|
|
889
|
+
| `SessionStart` | Yes |
|
|
890
|
+
| `UserPromptSubmit` | Yes |
|
|
891
|
+
| `Stop` | Yes |
|
|
892
|
+
| `SessionEnd` | Yes |
|
|
893
|
+
|
|
894
|
+
Other events (PreToolUse, PostToolUse, etc.) are not persisted to DB individually.
|
|
895
|
+
|
|
896
|
+
### 12.5 Cascade Delete
|
|
897
|
+
|
|
898
|
+
`deleteSessionCascade()` is a transaction that deletes from: `prompts`, `responses`, `tool_calls`, `events`, `notes`, then `sessions` — in that order.
|
|
899
|
+
|
|
900
|
+
### 12.6 Session ID Migration
|
|
901
|
+
|
|
902
|
+
When a session is re-keyed (resume), `migrateSessionId(oldId, newId)` updates `session_id` in all child tables in a single transaction.
|
|
903
|
+
|
|
904
|
+
### 12.7 Search
|
|
905
|
+
|
|
906
|
+
`searchSessions()` supports: text search (via `prompts` subquery with `LIKE`), project filter, status filter, date range, archived filter, sorting by `started_at`/`last_activity_at`/`project_name`/`status`, and pagination.
|
|
907
|
+
|
|
908
|
+
`fullTextSearch()` searches both `prompts.text` and `responses.text_excerpt` via `LIKE` patterns, merging and sorting results by timestamp.
|
|
909
|
+
|
|
910
|
+
### 12.8 Analytics Queries
|
|
911
|
+
|
|
912
|
+
| Query | What It Returns |
|
|
913
|
+
|---|---|
|
|
914
|
+
| `getSummaryStats()` | Total/active sessions, total prompts, total tool calls, most-used tool, busiest project |
|
|
915
|
+
| `getToolBreakdown()` | Per-tool count and percentage of total |
|
|
916
|
+
| `getActiveProjects()` | Projects with session count and last activity |
|
|
917
|
+
| `getHeatmap()` | Activity grid by `{day_of_week (0=Mon), hour, count}` |
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## 31. Server Infrastructure
|
|
922
|
+
|
|
923
|
+
### 31.1 Port Resolution
|
|
924
|
+
|
|
925
|
+
Port is resolved in order: `--port` CLI flag → `PORT` environment variable → `config.port` → default **3333**.
|
|
926
|
+
|
|
927
|
+
Port conflict handling: on `EADDRINUSE`, the server calls `killPortProcess(port)` which uses `lsof -ti:{port}` (macOS/Linux) or `netstat -ano | findstr :port` (Windows) to find and SIGTERM the occupying process, then retries binding after **1 second**.
|
|
928
|
+
|
|
929
|
+
### 31.2 Graceful Shutdown
|
|
930
|
+
|
|
931
|
+
Signals handled: `SIGTERM`, `SIGINT`
|
|
932
|
+
|
|
933
|
+
Shutdown sequence:
|
|
934
|
+
1. Stop periodic snapshot save
|
|
935
|
+
2. Stop WebSocket heartbeat
|
|
936
|
+
3. Stop MQ reader (performs final read to flush)
|
|
937
|
+
4. Stop auth token cleanup
|
|
938
|
+
5. Save final snapshot with current MQ offset
|
|
939
|
+
6. Close SQLite database
|
|
940
|
+
7. Close HTTP server
|
|
941
|
+
8. Exit 0 (forced exit 1 after **5 seconds** if not clean)
|
|
942
|
+
|
|
943
|
+
### 31.3 Global Error Handlers
|
|
944
|
+
|
|
945
|
+
| Handler | Behavior |
|
|
946
|
+
|---|---|
|
|
947
|
+
| `uncaughtException` | Log error; exit 1 if `out of memory` or `ENOMEM` |
|
|
948
|
+
| `unhandledRejection` | Log error and stack; continue |
|
|
949
|
+
|
|
950
|
+
### 31.4 Process Monitor
|
|
951
|
+
|
|
952
|
+
`processMonitor.js` runs every **15,000 ms** (configurable via `serverConfig.processCheckInterval`) and checks `process.kill(pid, 0)` for each non-ended session with a `cachedPid`. Dead processes trigger:
|
|
953
|
+
- Session marked `ended` + `Death` animation
|
|
954
|
+
- PID released from `pidToSession` Map
|
|
955
|
+
- Approval timer cleared
|
|
956
|
+
- Team cleanup
|
|
957
|
+
- Broadcast to browsers
|
|
958
|
+
- SSH sessions: marked `isHistorical`, terminal link cleared
|
|
959
|
+
- Non-SSH sessions: scheduled for deletion after 10 seconds
|
|
960
|
+
|
|
961
|
+
Sessions with an active PTY terminal are skipped (terminal is the source of truth).
|
|
962
|
+
|
|
963
|
+
**`findClaudeProcess()` fallback chain:**
|
|
964
|
+
1. Cached PID (validated with signal 0)
|
|
965
|
+
2. `pgrep -f claude` → match by `cwd` via `lsof -a -d cwd` (macOS) or `/proc/{pid}/cwd` (Linux)
|
|
966
|
+
3. TTY fallback: first unclaimed PID with a TTY attached
|
|
967
|
+
4. Last resort: first unclaimed PID
|
|
968
|
+
|
|
969
|
+
### 31.5 Rate Limiting
|
|
970
|
+
|
|
971
|
+
In-memory sliding-window rate limiter using `Map<key, {count, windowStart}>`. Window is **1 second**. Stale buckets (older than 5s) are cleaned every **30 seconds**.
|
|
972
|
+
|
|
973
|
+
### 31.6 Logger
|
|
974
|
+
|
|
975
|
+
Levels:
|
|
976
|
+
- `info` — always shown
|
|
977
|
+
- `warn` — always shown (yellow `WARN` prefix)
|
|
978
|
+
- `error` — always shown (red `ERROR` prefix)
|
|
979
|
+
- `debug` — only in debug mode (magenta `DEBUG` prefix)
|
|
980
|
+
- `debugJson` — only in debug mode (full JSON.stringify with indent 2)
|
|
981
|
+
|
|
982
|
+
Format: `[ISO timestamp] [tag] message`
|
|
983
|
+
|
|
984
|
+
Debug mode is activated by `--debug` CLI flag or `"debug": true` in `data/server-config.json`.
|
|
985
|
+
|
|
986
|
+
### 31.7 Server Config (`data/server-config.json`)
|
|
987
|
+
|
|
988
|
+
| Field | Type | Default | Description |
|
|
989
|
+
|---|---|---|---|
|
|
990
|
+
| `port` | number | `3333` | HTTP/WS listen port |
|
|
991
|
+
| `hookDensity` | string | `"medium"` | `high`, `medium`, or `low` |
|
|
992
|
+
| `debug` | boolean | `false` | Verbose logging |
|
|
993
|
+
| `processCheckInterval` | number | `15000` | PID liveness check interval (ms) |
|
|
994
|
+
| `sessionHistoryHours` | number | `24` | History retention (used by setup wizard) |
|
|
995
|
+
| `enabledClis` | string[] | `["claude"]` | Which CLIs to install hooks for |
|
|
996
|
+
| `passwordHash` | string\|null | `null` | `salt:hash` for dashboard login |
|
|
997
|
+
|
|
998
|
+
### 31.8 Network Interface Detection
|
|
999
|
+
|
|
1000
|
+
On startup, `getLocalIP()` probes network interfaces in preferred order: `en0`, `en1`, `eth0`, `wlan0`, then falls back to any non-internal IPv4. The LAN IP is displayed in the startup log.
|
|
1001
|
+
|
|
1002
|
+
### 31.9 Static File Serving
|
|
1003
|
+
|
|
1004
|
+
If `dist/client/` directory exists (Vite build output), it is served from there. Otherwise, `public/` is served. The React SPA fallback (`GET /*`) serves `dist/client/index.html` for client-side routing.
|
|
1005
|
+
|
|
1006
|
+
---
|
|
1007
|
+
|
|
1008
|
+
## 32. CLI and Setup
|
|
1009
|
+
|
|
1010
|
+
### 32.1 npx Entry Point (`bin/cli.js`)
|
|
1011
|
+
|
|
1012
|
+
When invoked as `npx ai-agent-session-center` or after global install:
|
|
1013
|
+
|
|
1014
|
+
1. Check if `data/server-config.json` exists
|
|
1015
|
+
2. If missing (`isFirstRun`) or `--setup` flag passed: run `hooks/setup-wizard.js`
|
|
1016
|
+
3. On wizard exit 0: start `server/index.js` with remaining args
|
|
1017
|
+
4. On wizard exit non-0: exit with same code
|
|
1018
|
+
5. If config exists and no `--setup`: start server directly
|
|
1019
|
+
|
|
1020
|
+
### 32.2 Setup Wizard (`hooks/setup-wizard.js`)
|
|
1021
|
+
|
|
1022
|
+
Interactive 6-step wizard:
|
|
1023
|
+
|
|
1024
|
+
| Step | Question | Options |
|
|
1025
|
+
|---|---|---|
|
|
1026
|
+
| 1 | Server port | Free-form number (default: `3333`) |
|
|
1027
|
+
| 2 | AI CLIs to hook | Claude only / Claude+Gemini / Claude+Codex / All three |
|
|
1028
|
+
| 3 | Hook density | `high` (14 events) / `medium` (12 events, default) / `low` (5 events) |
|
|
1029
|
+
| 4 | Debug mode | Off (default) / On |
|
|
1030
|
+
| 5 | Session history retention | 12h / 24h (default) / 48h / 7 days |
|
|
1031
|
+
| 6 | Dashboard password | No password (default) / Set password (min 4 chars, confirmed) |
|
|
1032
|
+
|
|
1033
|
+
Password input uses raw mode TTY (`process.stdin.setRawMode(true)`) with character-by-character echo masking (`*`). Re-running the wizard shows existing config as defaults and allows keeping/changing/removing the password.
|
|
1034
|
+
|
|
1035
|
+
After wizard completes:
|
|
1036
|
+
1. Saves `data/server-config.json`
|
|
1037
|
+
2. Runs `hooks/install-hooks.js --density {density} --clis {clis}` to install hooks
|
|
1038
|
+
|
|
1039
|
+
### 32.3 Auto-Install on Startup
|
|
1040
|
+
|
|
1041
|
+
`ensureHooksInstalled(config)` runs on every server startup. It:
|
|
1042
|
+
1. Reads `data/server-config.json` for `hookDensity` and `enabledClis`
|
|
1043
|
+
2. For each enabled CLI: copies the hook script to the CLI's hooks directory (if content changed)
|
|
1044
|
+
3. Reads the CLI's settings file
|
|
1045
|
+
4. Adds missing event registrations (checks for `dashboard-hook` in existing commands)
|
|
1046
|
+
5. Writes settings atomically if any changes were made
|
|
1047
|
+
|
|
1048
|
+
**Hook registration markers:** Each added entry includes `"_source": "ai-agent-session-center"` so the reset tool can identify and remove them.
|
|
1049
|
+
|
|
1050
|
+
### 32.4 Available npm Scripts
|
|
1051
|
+
|
|
1052
|
+
| Script | Command | Description |
|
|
1053
|
+
|---|---|---|
|
|
1054
|
+
| `npm start` | `node server/index.js` | Start server, auto-open browser |
|
|
1055
|
+
| `npm run start:no-open` | `node server/index.js --no-open` | Start without opening browser |
|
|
1056
|
+
| `npm run debug` | `node server/index.js --debug` | Start with verbose logging |
|
|
1057
|
+
| `npm run setup` | `node hooks/setup-wizard.js` | Interactive setup wizard |
|
|
1058
|
+
| `npm run install-hooks` | `node hooks/install-hooks.js` | Install hooks into CLI settings |
|
|
1059
|
+
| `npm run uninstall-hooks` | `node hooks/install-hooks.js --uninstall` | Remove all dashboard hooks |
|
|
1060
|
+
| `npm run reset` | `node hooks/reset.js` | Remove hooks, clean config, create backup |
|
|
1061
|
+
| `npm test` | Test suite | Run tests |
|
|
1062
|
+
| `npm run test:watch` | Test suite watch | Run tests in watch mode |
|
|
1063
|
+
|
|
1064
|
+
### 32.5 Hook Performance Stats
|
|
1065
|
+
|
|
1066
|
+
`hookStats.js` maintains rolling statistics per event type (last **200 samples**) and globally (last **1 minute** for rate):
|
|
1067
|
+
|
|
1068
|
+
| Metric | Description |
|
|
1069
|
+
|---|---|
|
|
1070
|
+
| `count` | Total hooks received of this type |
|
|
1071
|
+
| `rate` | Hooks received in last 60 seconds |
|
|
1072
|
+
| `latency.avg/min/max/p95` | Delivery latency (hook_sent_at → server received) in ms |
|
|
1073
|
+
| `processing.avg/min/max/p95` | Server `handleEvent()` duration in ms |
|
|
1074
|
+
| `totalHooks` | Global total |
|
|
1075
|
+
| `hooksPerMin` | Global rate (hooks in last 60s) |
|
|
1076
|
+
|
|
1077
|
+
Stats are broadcast as `hook_stats` WebSocket messages after each hook event (throttled to 1/sec per client).
|