aicodeman 0.5.3 → 0.5.5

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 (144) hide show
  1. package/dist/ai-checker-base.d.ts.map +1 -1
  2. package/dist/ai-checker-base.js +3 -2
  3. package/dist/ai-checker-base.js.map +1 -1
  4. package/dist/bash-tool-parser.d.ts +6 -0
  5. package/dist/bash-tool-parser.d.ts.map +1 -1
  6. package/dist/bash-tool-parser.js +87 -101
  7. package/dist/bash-tool-parser.js.map +1 -1
  8. package/dist/file-stream-manager.d.ts.map +1 -1
  9. package/dist/file-stream-manager.js +2 -1
  10. package/dist/file-stream-manager.js.map +1 -1
  11. package/dist/hooks-config.d.ts +5 -0
  12. package/dist/hooks-config.d.ts.map +1 -1
  13. package/dist/hooks-config.js +25 -0
  14. package/dist/hooks-config.js.map +1 -1
  15. package/dist/orchestrator-loop.d.ts +2 -0
  16. package/dist/orchestrator-loop.d.ts.map +1 -1
  17. package/dist/orchestrator-loop.js +27 -22
  18. package/dist/orchestrator-loop.js.map +1 -1
  19. package/dist/orchestrator-verifier.d.ts +1 -1
  20. package/dist/orchestrator-verifier.d.ts.map +1 -1
  21. package/dist/orchestrator-verifier.js +3 -2
  22. package/dist/orchestrator-verifier.js.map +1 -1
  23. package/dist/plan-orchestrator.d.ts +4 -1
  24. package/dist/plan-orchestrator.d.ts.map +1 -1
  25. package/dist/plan-orchestrator.js +66 -88
  26. package/dist/plan-orchestrator.js.map +1 -1
  27. package/dist/ralph-status-parser.d.ts +2 -0
  28. package/dist/ralph-status-parser.d.ts.map +1 -1
  29. package/dist/ralph-status-parser.js +98 -102
  30. package/dist/ralph-status-parser.js.map +1 -1
  31. package/dist/respawn-controller.d.ts +4 -1
  32. package/dist/respawn-controller.d.ts.map +1 -1
  33. package/dist/respawn-controller.js +165 -131
  34. package/dist/respawn-controller.js.map +1 -1
  35. package/dist/session.d.ts +18 -0
  36. package/dist/session.d.ts.map +1 -1
  37. package/dist/session.js +129 -117
  38. package/dist/session.js.map +1 -1
  39. package/dist/state-store.d.ts +2 -0
  40. package/dist/state-store.d.ts.map +1 -1
  41. package/dist/state-store.js +22 -28
  42. package/dist/state-store.js.map +1 -1
  43. package/dist/subagent-watcher.d.ts +6 -0
  44. package/dist/subagent-watcher.d.ts.map +1 -1
  45. package/dist/subagent-watcher.js +145 -139
  46. package/dist/subagent-watcher.js.map +1 -1
  47. package/dist/tunnel-manager.d.ts.map +1 -1
  48. package/dist/tunnel-manager.js +2 -1
  49. package/dist/tunnel-manager.js.map +1 -1
  50. package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
  51. package/dist/web/public/{app.3b7ff137.js → app.16290ae3.js} +2 -2
  52. package/dist/web/public/app.16290ae3.js.br +0 -0
  53. package/dist/web/public/app.16290ae3.js.gz +0 -0
  54. package/dist/web/public/constants.64161167.js.gz +0 -0
  55. package/dist/web/public/index.html +26 -8
  56. package/dist/web/public/index.html.br +0 -0
  57. package/dist/web/public/index.html.gz +0 -0
  58. package/dist/web/public/input-cjk.88082175.js.gz +0 -0
  59. package/dist/web/public/keyboard-accessory.9fb81db6.js.gz +0 -0
  60. package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
  61. package/dist/web/public/mobile.9a61290c.css +1 -0
  62. package/dist/web/public/mobile.9a61290c.css.br +0 -0
  63. package/dist/web/public/mobile.9a61290c.css.gz +0 -0
  64. package/dist/web/public/notification-manager.2d5ea8ec.js.gz +0 -0
  65. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  66. package/dist/web/public/{panels-ui.8204db1e.js → panels-ui.2d5b9703.js} +1 -1
  67. package/dist/web/public/panels-ui.2d5b9703.js.br +0 -0
  68. package/dist/web/public/panels-ui.2d5b9703.js.gz +0 -0
  69. package/dist/web/public/{ralph-panel.a2733fd5.js → ralph-panel.61076370.js} +1 -1
  70. package/dist/web/public/ralph-panel.61076370.js.br +0 -0
  71. package/dist/web/public/ralph-panel.61076370.js.gz +0 -0
  72. package/dist/web/public/ralph-wizard.f31ab90e.js.gz +0 -0
  73. package/dist/web/public/{respawn-ui.372c6ea7.js → respawn-ui.60be6ef5.js} +1 -1
  74. package/dist/web/public/respawn-ui.60be6ef5.js.br +0 -0
  75. package/dist/web/public/respawn-ui.60be6ef5.js.gz +0 -0
  76. package/dist/web/public/session-ui.ab189b7f.js +16 -0
  77. package/dist/web/public/session-ui.ab189b7f.js.br +0 -0
  78. package/dist/web/public/session-ui.ab189b7f.js.gz +0 -0
  79. package/dist/web/public/settings-ui.50a8018e.js +55 -0
  80. package/dist/web/public/settings-ui.50a8018e.js.br +0 -0
  81. package/dist/web/public/settings-ui.50a8018e.js.gz +0 -0
  82. package/dist/web/public/styles.111ff326.css.gz +0 -0
  83. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  84. package/dist/web/public/sw.js.gz +0 -0
  85. package/dist/web/public/terminal-ui.474f79df.js.gz +0 -0
  86. package/dist/web/public/upload.html.gz +0 -0
  87. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  88. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  91. package/dist/web/public/vendor/xterm.css.gz +0 -0
  92. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  93. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  94. package/dist/web/respawn-event-wiring.d.ts +51 -0
  95. package/dist/web/respawn-event-wiring.d.ts.map +1 -0
  96. package/dist/web/respawn-event-wiring.js +280 -0
  97. package/dist/web/respawn-event-wiring.js.map +1 -0
  98. package/dist/web/route-helpers.d.ts +9 -0
  99. package/dist/web/route-helpers.d.ts.map +1 -1
  100. package/dist/web/route-helpers.js +15 -0
  101. package/dist/web/route-helpers.js.map +1 -1
  102. package/dist/web/routes/orchestrator-routes.d.ts.map +1 -1
  103. package/dist/web/routes/orchestrator-routes.js +23 -30
  104. package/dist/web/routes/orchestrator-routes.js.map +1 -1
  105. package/dist/web/routes/session-routes.d.ts.map +1 -1
  106. package/dist/web/routes/session-routes.js +5 -1
  107. package/dist/web/routes/session-routes.js.map +1 -1
  108. package/dist/web/routes/system-routes.d.ts.map +1 -1
  109. package/dist/web/routes/system-routes.js +12 -30
  110. package/dist/web/routes/system-routes.js.map +1 -1
  111. package/dist/web/schemas.d.ts +1 -0
  112. package/dist/web/schemas.d.ts.map +1 -1
  113. package/dist/web/schemas.js +2 -0
  114. package/dist/web/schemas.js.map +1 -1
  115. package/dist/web/server.d.ts +4 -51
  116. package/dist/web/server.d.ts.map +1 -1
  117. package/dist/web/server.js +98 -941
  118. package/dist/web/server.js.map +1 -1
  119. package/dist/web/session-listener-wiring.d.ts +89 -0
  120. package/dist/web/session-listener-wiring.d.ts.map +1 -0
  121. package/dist/web/session-listener-wiring.js +290 -0
  122. package/dist/web/session-listener-wiring.js.map +1 -0
  123. package/dist/web/sse-stream-manager.d.ts +91 -0
  124. package/dist/web/sse-stream-manager.d.ts.map +1 -0
  125. package/dist/web/sse-stream-manager.js +426 -0
  126. package/dist/web/sse-stream-manager.js.map +1 -0
  127. package/package.json +1 -1
  128. package/dist/web/public/app.3b7ff137.js.br +0 -0
  129. package/dist/web/public/app.3b7ff137.js.gz +0 -0
  130. package/dist/web/public/mobile.0b213796.css +0 -1
  131. package/dist/web/public/mobile.0b213796.css.br +0 -0
  132. package/dist/web/public/mobile.0b213796.css.gz +0 -0
  133. package/dist/web/public/panels-ui.8204db1e.js.br +0 -0
  134. package/dist/web/public/panels-ui.8204db1e.js.gz +0 -0
  135. package/dist/web/public/ralph-panel.a2733fd5.js.br +0 -0
  136. package/dist/web/public/ralph-panel.a2733fd5.js.gz +0 -0
  137. package/dist/web/public/respawn-ui.372c6ea7.js.br +0 -0
  138. package/dist/web/public/respawn-ui.372c6ea7.js.gz +0 -0
  139. package/dist/web/public/session-ui.554092ae.js +0 -16
  140. package/dist/web/public/session-ui.554092ae.js.br +0 -0
  141. package/dist/web/public/session-ui.554092ae.js.gz +0 -0
  142. package/dist/web/public/settings-ui.bd3eaadb.js +0 -55
  143. package/dist/web/public/settings-ui.bd3eaadb.js.br +0 -0
  144. package/dist/web/public/settings-ui.bd3eaadb.js.gz +0 -0
@@ -0,0 +1,426 @@
1
+ /**
2
+ * @fileoverview SSE stream manager — owns all SSE client state, broadcasting, and event batching.
3
+ *
4
+ * Extracted from server.ts for modularity. Handles:
5
+ * - SSE client connection tracking with subscription filtering
6
+ * - Backpressure-aware message delivery
7
+ * - Terminal data batching with adaptive intervals (16-50ms for 60fps)
8
+ * - Task update and session state batching
9
+ * - Dead client cleanup and keepalive
10
+ * - Cloudflare tunnel padding for proxy buffer flushing
11
+ *
12
+ * @dependencies CleanupManager (managed timers), config/server-timing (constants)
13
+ * @consumedby web/server.ts (WebServer delegates all SSE operations here)
14
+ *
15
+ * @module web/sse-stream-manager
16
+ */
17
+ import { StaleExpirationMap } from '../utils/index.js';
18
+ import { SseEvent } from './sse-events.js';
19
+ import { TERMINAL_BATCH_INTERVAL, TASK_UPDATE_BATCH_INTERVAL, STATE_UPDATE_DEBOUNCE_INTERVAL, BATCH_FLUSH_THRESHOLD, SSE_PADDING_SIZE, INACTIVITY_TIMEOUT_MS, } from '../config/server-timing.js';
20
+ // SSE padding for Cloudflare tunnel buffer flushing.
21
+ // Cloudflare quick tunnels buffer small SSE responses, causing lag for real-time events.
22
+ // Appending SSE comment padding (ignored by EventSource) forces the proxy to flush.
23
+ // Pre-computed once at startup to avoid repeated string allocation.
24
+ const SSE_PADDING = ':' + 'p'.repeat(SSE_PADDING_SIZE) + '\n';
25
+ export class SseStreamManager {
26
+ deps;
27
+ cleanup;
28
+ // ─── SSE Client Tracking ────────────────────────────────
29
+ /**
30
+ * SSE clients mapped to their session subscription filter.
31
+ * Value is a Set of session IDs the client wants events for,
32
+ * or `null` meaning "receive all events" (backwards-compatible default).
33
+ */
34
+ sseClients = new Map();
35
+ /** SSE clients connecting from non-localhost (i.e. through tunnel) */
36
+ remoteSseClients = new Set();
37
+ /** Clients with backpressure — skip writes until 'drain' fires */
38
+ backpressuredClients = new Set();
39
+ // ─── Tunnel State ───────────────────────────────────────
40
+ /** Cached tunnel active state — updated on TunnelStarted/TunnelStopped to avoid getUrl() on every broadcast */
41
+ _isTunnelActive = false;
42
+ // ─── Terminal Batching ──────────────────────────────────
43
+ terminalBatches = new Map();
44
+ terminalBatchSizes = new Map(); // Running total avoids O(n) reduce per push
45
+ terminalBatchTimers = new Map(); // Per-session timers (staggered flushes)
46
+ // Adaptive batching: track rapid events to extend batch window (per-session)
47
+ // StaleExpirationMap auto-cleans entries for sessions that stop generating output
48
+ lastTerminalEventTime;
49
+ // ─── Event Batching ─────────────────────────────────────
50
+ taskUpdateBatches = new Map();
51
+ taskUpdateBatchTimerId = null;
52
+ // State update batching (reduce expensive toDetailedState() serialization)
53
+ stateUpdatePending = new Set();
54
+ stateUpdateTimerId = null;
55
+ // ─── Lifecycle ──────────────────────────────────────────
56
+ _isStopping = false;
57
+ constructor(deps, cleanup) {
58
+ this.deps = deps;
59
+ this.cleanup = cleanup;
60
+ this.lastTerminalEventTime = new StaleExpirationMap({
61
+ ttlMs: INACTIVITY_TIMEOUT_MS, // 5 minutes - auto-expire stale session timing data
62
+ refreshOnGet: false, // Don't refresh on reads, only on explicit sets
63
+ });
64
+ }
65
+ // ========== SSE Connection Management ==========
66
+ get clientCount() {
67
+ return this.sseClients.size;
68
+ }
69
+ get remoteClientCount() {
70
+ return this.remoteSseClients.size;
71
+ }
72
+ get isTunnelActive() {
73
+ return this._isTunnelActive;
74
+ }
75
+ setTunnelActive(active) {
76
+ this._isTunnelActive = active;
77
+ }
78
+ addClient(reply, sessionFilter, isRemote) {
79
+ this.sseClients.set(reply, sessionFilter);
80
+ if (isRemote) {
81
+ this.remoteSseClients.add(reply);
82
+ }
83
+ }
84
+ removeClient(reply) {
85
+ this.sseClients.delete(reply);
86
+ this.remoteSseClients.delete(reply);
87
+ this.backpressuredClients.delete(reply);
88
+ }
89
+ /** Send a single SSE event to a specific client. */
90
+ sendSSE(reply, event, data) {
91
+ try {
92
+ reply.raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
93
+ }
94
+ catch {
95
+ this.sseClients.delete(reply);
96
+ this.remoteSseClients.delete(reply);
97
+ }
98
+ }
99
+ /** Send pre-formatted tunnel padding to a specific client. */
100
+ sendPadding(reply) {
101
+ if (!this._isTunnelActive)
102
+ return;
103
+ try {
104
+ reply.raw.write(SSE_PADDING);
105
+ }
106
+ catch {
107
+ /* client gone */
108
+ }
109
+ }
110
+ // Optimized: send pre-formatted SSE message to a client
111
+ // Returns false if client is backpressured or dead
112
+ sendSSEPreformatted(reply, message) {
113
+ // Skip backpressured clients to prevent unbounded memory growth.
114
+ // Terminal data dropped here is recovered via session:needsRefresh on drain.
115
+ if (this.backpressuredClients.has(reply))
116
+ return;
117
+ try {
118
+ const ok = reply.raw.write(message);
119
+ if (!ok) {
120
+ // Buffer is full — mark as backpressured, resume on drain
121
+ this.backpressuredClients.add(reply);
122
+ reply.raw.once('drain', () => {
123
+ this.backpressuredClients.delete(reply);
124
+ // Client may have missed terminal data during backpressure.
125
+ // Tell it to reload the active session's buffer to recover.
126
+ try {
127
+ const drainPadding = this._isTunnelActive ? SSE_PADDING : '';
128
+ reply.raw.write(`event: ${SseEvent.SessionNeedsRefresh}\ndata: {}\n\n${drainPadding}`);
129
+ }
130
+ catch {
131
+ /* client gone */
132
+ }
133
+ });
134
+ }
135
+ }
136
+ catch {
137
+ this.sseClients.delete(reply);
138
+ this.remoteSseClients.delete(reply);
139
+ this.backpressuredClients.delete(reply);
140
+ }
141
+ }
142
+ // ========== Broadcasting ==========
143
+ broadcast(event, data) {
144
+ // Skip serialization entirely when no clients are listening
145
+ if (this.sseClients.size === 0)
146
+ return;
147
+ // Performance optimization: serialize JSON once for all clients.
148
+ // Only append Cloudflare tunnel padding for latency-sensitive events —
149
+ // Recovery events need immediate proxy flush; low-frequency metadata events
150
+ // (session:created, ralph:*, respawn:*, etc.) don't need padding.
151
+ // Note: session:terminal has its own padding in flushSessionTerminalBatch().
152
+ const needsPadding = this._isTunnelActive && event === SseEvent.SessionNeedsRefresh;
153
+ const padding = needsPadding ? SSE_PADDING : '';
154
+ let message;
155
+ try {
156
+ message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n` + padding;
157
+ }
158
+ catch (err) {
159
+ // Handle circular references or non-serializable values
160
+ console.error(`[Server] Failed to serialize SSE event "${event}":`, err);
161
+ return;
162
+ }
163
+ // Extract sessionId from event data for subscription filtering.
164
+ const eventSessionId = this.extractSessionId(event, data);
165
+ for (const [client, filter] of this.sseClients) {
166
+ // No filter (null) = receive everything. Otherwise, skip if event is
167
+ // session-scoped and the session isn't in the client's subscription set.
168
+ if (filter && eventSessionId && !filter.has(eventSessionId))
169
+ continue;
170
+ this.sendSSEPreformatted(client, message);
171
+ }
172
+ }
173
+ /**
174
+ * Extract the session ID from an event's data payload for subscription filtering.
175
+ * Returns the sessionId string if the event is session-scoped, or null for global events.
176
+ */
177
+ extractSessionId(event, data) {
178
+ if (data == null || typeof data !== 'object')
179
+ return null;
180
+ const record = data;
181
+ // Most session-scoped events use `sessionId`
182
+ if (typeof record.sessionId === 'string')
183
+ return record.sessionId;
184
+ // Session lifecycle events (session:*) use `id` from the session state object
185
+ if (typeof record.id === 'string' && event.startsWith('session:'))
186
+ return record.id;
187
+ // No session ID found — treat as global event (sent to all clients)
188
+ return null;
189
+ }
190
+ // ========== Terminal Data Batching ==========
191
+ // Batch terminal data for better performance (60fps)
192
+ // Uses per-session timers with adaptive intervals to prevent thundering herd:
193
+ // each session flushes independently rather than all sessions flushing in one burst.
194
+ batchTerminalData(sessionId, data) {
195
+ // Skip if server is stopping
196
+ if (this._isStopping)
197
+ return;
198
+ let chunks = this.terminalBatches.get(sessionId);
199
+ if (!chunks) {
200
+ chunks = [];
201
+ this.terminalBatches.set(sessionId, chunks);
202
+ }
203
+ chunks.push(data);
204
+ const prevSize = this.terminalBatchSizes.get(sessionId) ?? 0;
205
+ const totalLength = prevSize + data.length;
206
+ this.terminalBatchSizes.set(sessionId, totalLength);
207
+ // Adaptive batching: detect rapid events and extend batch window (per-session)
208
+ const now = Date.now();
209
+ const lastEvent = this.lastTerminalEventTime.get(sessionId) ?? 0;
210
+ const eventGap = now - lastEvent;
211
+ this.lastTerminalEventTime.set(sessionId, now);
212
+ // Adjust batch interval based on event frequency (per-session)
213
+ // Rapid events (<10ms gap) = 50ms batch, moderate (<20ms) = 32ms, else 16ms
214
+ let sessionInterval;
215
+ if (eventGap > 0 && eventGap < 10) {
216
+ sessionInterval = 50;
217
+ }
218
+ else if (eventGap > 0 && eventGap < 20) {
219
+ sessionInterval = 32;
220
+ }
221
+ else {
222
+ sessionInterval = TERMINAL_BATCH_INTERVAL;
223
+ }
224
+ // Flush immediately if batch is large for responsiveness
225
+ if (totalLength > BATCH_FLUSH_THRESHOLD) {
226
+ const existingTimer = this.terminalBatchTimers.get(sessionId);
227
+ if (existingTimer) {
228
+ clearTimeout(existingTimer);
229
+ this.terminalBatchTimers.delete(sessionId);
230
+ }
231
+ this.flushSessionTerminalBatch(sessionId);
232
+ return;
233
+ }
234
+ // Start per-session batch timer if not already running
235
+ // Each session flushes independently — prevents one busy session from
236
+ // forcing all sessions to flush at its rate (thundering herd)
237
+ if (!this.terminalBatchTimers.has(sessionId)) {
238
+ this.terminalBatchTimers.set(sessionId, setTimeout(() => {
239
+ this.terminalBatchTimers.delete(sessionId);
240
+ this.flushSessionTerminalBatch(sessionId);
241
+ }, sessionInterval));
242
+ }
243
+ }
244
+ /** Flush a single session's batched terminal data */
245
+ flushSessionTerminalBatch(sessionId) {
246
+ if (this._isStopping) {
247
+ this.terminalBatches.delete(sessionId);
248
+ this.terminalBatchSizes.delete(sessionId);
249
+ return;
250
+ }
251
+ const chunks = this.terminalBatches.get(sessionId);
252
+ if (chunks && chunks.length > 0) {
253
+ // Join chunks only at flush time (avoids O(n^2) string concatenation in batchTerminalData)
254
+ const data = chunks.join('');
255
+ // Wrap batched output in DEC 2026 synchronized output markers so xterm.js
256
+ // renders the entire batch atomically. Ink spinner frames (cursor-up + redraw)
257
+ // do NOT emit their own 2026 markers, so without this wrapper each partial
258
+ // cursor update renders individually, causing visible flicker.
259
+ // xterm.js 6.0+ handles DEC 2026 natively: it buffers everything between
260
+ // 2026h/2026l and renders in one pass.
261
+ const syncData = '\x1b[?2026h' + data + '\x1b[?2026l';
262
+ // Fast path: build SSE message directly without JSON.stringify on wrapper object.
263
+ // Only the terminal data string needs escaping; sessionId is a UUID (safe to template).
264
+ const escapedData = JSON.stringify(syncData);
265
+ // Append tunnel padding for immediate Cloudflare proxy flush —
266
+ // terminal data is high-frequency and latency-sensitive.
267
+ const padding = this._isTunnelActive ? SSE_PADDING : '';
268
+ const message = `event: session:terminal\ndata: {"id":"${sessionId}","data":${escapedData}}\n\n` + padding;
269
+ for (const [client, filter] of this.sseClients) {
270
+ // Skip clients that have a session filter and aren't subscribed to this session
271
+ if (filter && !filter.has(sessionId))
272
+ continue;
273
+ this.sendSSEPreformatted(client, message);
274
+ }
275
+ }
276
+ this.terminalBatches.delete(sessionId);
277
+ this.terminalBatchSizes.delete(sessionId);
278
+ }
279
+ // ========== Task Update Batching ==========
280
+ // Batch task:updated events at 100ms - only send latest update per task
281
+ // Key is sessionId:taskId to avoid collisions when multiple tasks update concurrently
282
+ batchTaskUpdate(sessionId, task) {
283
+ // Skip if server is stopping
284
+ if (this._isStopping)
285
+ return;
286
+ // Use composite key to avoid losing updates when multiple tasks update in same batch window
287
+ const key = `${sessionId}:${task.id}`;
288
+ this.taskUpdateBatches.set(key, { sessionId, task });
289
+ if (!this.taskUpdateBatchTimerId) {
290
+ this.taskUpdateBatchTimerId = this.cleanup.setTimeout(() => {
291
+ this.taskUpdateBatchTimerId = null;
292
+ this.flushTaskUpdateBatches();
293
+ }, TASK_UPDATE_BATCH_INTERVAL, { description: 'task update batch flush' });
294
+ }
295
+ }
296
+ flushTaskUpdateBatches() {
297
+ // Skip if server is stopping (timer may have been queued before stop() was called)
298
+ if (this._isStopping) {
299
+ this.taskUpdateBatches.clear();
300
+ return;
301
+ }
302
+ for (const [, { sessionId, task }] of this.taskUpdateBatches) {
303
+ this.broadcast(SseEvent.TaskUpdated, { sessionId, task });
304
+ }
305
+ this.taskUpdateBatches.clear();
306
+ }
307
+ // ========== Session State Batching ==========
308
+ /**
309
+ * Debounce expensive session:updated broadcasts.
310
+ * Instead of calling toDetailedState() on every event, batch requests
311
+ * and only serialize once per STATE_UPDATE_DEBOUNCE_INTERVAL.
312
+ */
313
+ broadcastSessionStateDebounced(sessionId) {
314
+ // Skip if server is stopping
315
+ if (this._isStopping)
316
+ return;
317
+ this.stateUpdatePending.add(sessionId);
318
+ if (!this.stateUpdateTimerId) {
319
+ this.stateUpdateTimerId = this.cleanup.setTimeout(() => {
320
+ this.stateUpdateTimerId = null;
321
+ this.flushStateUpdates();
322
+ }, STATE_UPDATE_DEBOUNCE_INTERVAL, { description: 'state update debounce flush' });
323
+ }
324
+ }
325
+ flushStateUpdates() {
326
+ // Skip if server is stopping (timer may have been queued before stop() was called)
327
+ if (this._isStopping) {
328
+ this.stateUpdatePending.clear();
329
+ return;
330
+ }
331
+ for (const sessionId of this.stateUpdatePending) {
332
+ // Single expensive serialization per batch interval
333
+ const state = this.deps.getSessionStateWithRespawn(sessionId);
334
+ if (state) {
335
+ this.broadcast(SseEvent.SessionUpdated, state);
336
+ }
337
+ }
338
+ this.stateUpdatePending.clear();
339
+ }
340
+ // ========== Client Health ==========
341
+ /**
342
+ * Clean up dead SSE clients and send keep-alive comments.
343
+ * Keep-alive prevents proxy/load-balancer timeouts on idle connections.
344
+ * Dead client cleanup prevents memory leaks from abruptly terminated connections.
345
+ */
346
+ cleanupDeadClients() {
347
+ const deadClients = [];
348
+ for (const [client] of this.sseClients) {
349
+ try {
350
+ // Check if the underlying socket is still writable
351
+ const socket = client.raw.socket;
352
+ if (!socket || socket.destroyed || !socket.writable) {
353
+ deadClients.push(client);
354
+ }
355
+ else {
356
+ // Send SSE comment as keep-alive. Only add padding when tunnel is
357
+ // active — it flushes Cloudflare proxy buffers but wastes bandwidth
358
+ // for direct/Tailscale connections.
359
+ const ka = this._isTunnelActive ? ':keepalive\n' + SSE_PADDING : ':keepalive\n\n';
360
+ client.raw.write(ka);
361
+ }
362
+ }
363
+ catch {
364
+ // Error accessing socket means client is dead
365
+ deadClients.push(client);
366
+ }
367
+ }
368
+ // Remove dead clients
369
+ for (const client of deadClients) {
370
+ this.sseClients.delete(client);
371
+ this.remoteSseClients.delete(client);
372
+ this.backpressuredClients.delete(client);
373
+ }
374
+ if (deadClients.length > 0) {
375
+ console.log(`[Server] Cleaned up ${deadClients.length} dead SSE client(s)`);
376
+ }
377
+ }
378
+ // ========== Session Cleanup ==========
379
+ /** Clean up all batching state for a session (call on session exit or deletion). */
380
+ cleanupSessionBatches(sessionId) {
381
+ this.terminalBatches.delete(sessionId);
382
+ this.terminalBatchSizes.delete(sessionId);
383
+ const batchTimer = this.terminalBatchTimers.get(sessionId);
384
+ if (batchTimer) {
385
+ clearTimeout(batchTimer);
386
+ this.terminalBatchTimers.delete(sessionId);
387
+ }
388
+ this.taskUpdateBatches.delete(sessionId);
389
+ this.stateUpdatePending.delete(sessionId);
390
+ this.lastTerminalEventTime.delete(sessionId);
391
+ }
392
+ // ========== Lifecycle ==========
393
+ setStopping() {
394
+ this._isStopping = true;
395
+ }
396
+ /** Graceful shutdown: notify clients, close connections, clear all state. */
397
+ stop() {
398
+ this._isStopping = true;
399
+ // Gracefully close all SSE connections before clearing
400
+ for (const [client] of this.sseClients) {
401
+ try {
402
+ // Send a final event to notify clients of shutdown
403
+ this.sendSSE(client, 'server:shutdown', { reason: 'Server stopping' });
404
+ client.raw.end();
405
+ }
406
+ catch {
407
+ // Client may already be disconnected
408
+ }
409
+ }
410
+ this.sseClients.clear();
411
+ this.remoteSseClients.clear();
412
+ this.backpressuredClients.clear();
413
+ // Clear per-session batch timers
414
+ for (const timer of this.terminalBatchTimers.values()) {
415
+ clearTimeout(timer);
416
+ }
417
+ this.terminalBatchTimers.clear();
418
+ this.terminalBatches.clear();
419
+ this.terminalBatchSizes.clear();
420
+ this.taskUpdateBatches.clear();
421
+ this.stateUpdatePending.clear();
422
+ // Dispose StaleExpirationMap (stops internal cleanup timer)
423
+ this.lastTerminalEventTime.dispose();
424
+ }
425
+ }
426
+ //# sourceMappingURL=sse-stream-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-stream-manager.js","sourceRoot":"","sources":["../../src/web/sse-stream-manager.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicodeman",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
Binary file
Binary file
@@ -1 +0,0 @@
1
- html.mobile-init .header-font-controls,html.mobile-init .header-system-stats,html.mobile-init .header-tokens,html.mobile-init .monitor-panel,html.mobile-init .subagents-panel,html.mobile-init .project-insights-panel,html.mobile-init .file-browser-panel{display:none!important}@media(max-width:768px){input,textarea,select{font-size:16px!important}html{touch-action:manipulation}}@media(max-width:768px)and (min-width:430px){.header{position:fixed;top:0;left:0;right:0;min-height:48px;max-height:48px;padding:.35rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));background:#0a0a0a;border-bottom:1px solid rgba(255,255,255,.08);z-index:200}.ios-device .header{padding-top:calc(.35rem + var(--safe-area-top));min-height:calc(48px + var(--safe-area-top));max-height:calc(48px + var(--safe-area-top))}.main{margin-top:54px}.ios-device .main{margin-top:calc(54px + var(--safe-area-top))}.header-font-controls{gap:.15rem;padding:.15rem .25rem}.header-font-controls .btn-icon-sm{width:18px;height:18px}.header-system-stats{font-size:.6rem;gap:.25rem}.header-right{gap:.35rem}.session-tabs,.session-tabs.tabs-two-rows{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;max-height:52px;gap:3px}.session-tabs::-webkit-scrollbar{display:none}.session-tab{padding:.4rem .6rem;font-size:.75rem;min-height:40px}.session-tab .tab-name{max-width:80px}.toolbar{padding:.4rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));gap:.4rem}.tab-count-group{display:none}.btn-toolbar{padding:.4rem .8rem;font-size:.8rem;min-height:40px}.case-select-group{max-width:150px}.toolbar-select{font-size:.75rem}.monitor-panel,.subagents-panel{width:100%;max-width:100%;left:0;right:0;border-radius:8px 8px 0 0;max-height:40vh}.project-insights-panel{max-width:280px;font-size:.7rem}.modal-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex-wrap:nowrap}.modal-tabs::-webkit-scrollbar{display:none}.modal-tab-btn{padding:.4rem .75rem;font-size:.7rem;white-space:nowrap;flex-shrink:0}.settings-grid{gap:.4rem .75rem}.settings-item{font-size:.7rem}.event-type-grid{grid-template-columns:1fr 40px 40px 40px;gap:5px 6px}.event-label{font-size:.7rem}.subagent-window{width:320px;height:280px;min-width:240px;min-height:160px}.subagent-window-header{padding:.35rem .5rem}.subagent-window-title .id{font-size:.7rem;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.subagent-window-title .status{font-size:.55rem}.subagent-window-body{font-size:.7rem}.toolbar-center .btn-toolbar.btn-voice{display:flex!important}.toolbar{padding:0 .5rem;gap:.5rem}.toolbar-left,.toolbar-right,.toolbar-group{gap:.5rem}.btn-toolbar{padding:.4rem .75rem;font-size:.75rem;min-height:unset}}@media(max-width:430px){.header-brand{display:none}.header-font-controls{gap:.1rem;padding:.1rem .2rem}.header-font-controls .btn-icon-sm{width:16px;height:16px}.header-font-controls .font-size-display{font-size:.6rem;min-width:14px}.header-system-stats{font-size:.55rem;gap:.15rem}.header-tokens{font-size:.6rem;padding:.1rem .3rem}.header{position:fixed;top:0;left:0;right:0;min-height:36px;max-height:36px;padding:.15rem .3rem;padding-left:calc(.3rem + var(--safe-area-left));padding-right:calc(.3rem + var(--safe-area-right));gap:.15rem;overflow:hidden;background:#0a0a0a;border-bottom:1px solid rgba(255,255,255,.08);z-index:200}.ios-device .header{padding-top:calc(.15rem + var(--safe-area-top));min-height:calc(36px + var(--safe-area-top));max-height:calc(36px + var(--safe-area-top))}.main{margin-top:42px;padding-bottom:calc(40px + var(--safe-area-bottom))}.ios-device .main{margin-top:calc(42px + var(--safe-area-top))}.ios-device.safari-browser .main{padding-bottom:calc(40px + var(--safe-area-bottom) + (100vh - var(--app-height, 100vh)))}.header-right{padding-left:.2rem;gap:.1rem;flex-shrink:0;border-left:none}.btn-icon-header{width:26px;height:26px;padding:0}.btn-icon-header svg{width:12px;height:12px}.btn-icon-header.btn-settings,.btn-icon-header.btn-lifecycle-log{display:none!important}.btn-voice-mobile{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;background:#1a2a3f;border:1px solid rgba(59,130,246,.3);border-radius:4px;color:#93c5fd;cursor:pointer;flex-shrink:0;order:5}.btn-voice-mobile svg{width:13px;height:13px}.btn-voice-mobile:active{background:#2a3a5f}.btn-voice-mobile.recording{background:#ef444440;border-color:#ef444499;color:#ef4444;animation:voice-pulse 1.2s ease-in-out infinite}.btn-settings-mobile{display:inline-flex!important;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;color:#9ca3af;font-size:.85rem;cursor:pointer;flex-shrink:0;order:6;position:relative}.btn-settings-mobile svg{width:13px;height:13px}.btn-settings-mobile:active{background:#ffffff1a;color:#fff}.app{overflow:hidden;position:relative}.keyboard-visible .app{position:fixed;inset:0}.session-tabs,.session-tabs.tabs-two-rows{flex:1;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;max-height:36px;gap:2px;padding:0}.session-tabs::-webkit-scrollbar{display:none}.session-tab{flex-shrink:0;min-height:32px;max-height:32px;padding:.35rem .5rem;font-size:.7rem;gap:.25rem;border-radius:4px}.session-tab .tab-status{width:4px;height:4px}.session-tab .tab-name{max-width:50px;overflow:hidden;text-overflow:ellipsis}.session-tab .tab-close,.session-tab .tab-gear{display:none}.session-tab.active .tab-gear{display:inline-flex;align-items:center;justify-content:center;font-size:.5rem;line-height:1;width:12px;height:12px;margin-left:auto;opacity:.6}.session-tab.active .tab-close{display:inline-flex;align-items:center;justify-content:center;font-size:1.1rem;line-height:1;width:20px;height:18px;margin-left:-2px}.toolbar{position:fixed;bottom:var(--safe-area-bottom);left:0;right:0;height:40px;min-height:40px;max-height:40px;padding:.3rem .4rem;padding-left:calc(.4rem + var(--safe-area-left));padding-right:calc(.4rem + var(--safe-area-right));flex-wrap:nowrap;gap:.3rem;align-items:center;justify-content:space-between;background:#111;border-top:1px solid rgba(255,255,255,.1);z-index:50;transition:transform .15s ease-out;will-change:transform}.ios-device.safari-browser .toolbar{bottom:calc(var(--safe-area-bottom) + (100vh - var(--app-height, 100vh)))}.keyboard-visible.ios-device.safari-browser .toolbar{bottom:var(--safe-area-bottom)}.keyboard-visible.ios-device.safari-browser .keyboard-accessory-bar{bottom:calc(var(--safe-area-bottom) + 40px)}.toolbar-center{display:flex!important;align-items:center}.toolbar-right,.version-display{display:none!important}.toolbar-left{flex:1 1 0;min-width:0;align-items:center;gap:.3rem}.toolbar-left .toolbar-group{display:flex;flex:1 1 0;min-width:0;align-items:center;gap:.3rem}.toolbar-left .toolbar-group:first-child{flex:1 1 0;min-width:0;justify-content:space-between;gap:.3rem;flex-wrap:nowrap;align-items:center}.tab-count-group{display:none!important}.btn-toolbar{min-height:26px!important;max-height:26px!important;height:26px!important;padding:0 .5rem!important;font-size:.65rem!important;border-radius:4px;line-height:26px;display:inline-flex;align-items:center;justify-content:center;font-weight:500;letter-spacing:.01em;color:#fff}.run-btn-group{display:flex;flex:0 0 auto}.btn-toolbar.btn-run{flex:0 0 auto;padding:0 .6rem!important;font-weight:500;border-radius:4px 0 0 4px!important;border-right:none!important}.btn-toolbar.btn-run svg{width:10px;height:10px;margin-right:3px}.btn-toolbar.btn-run-gear{flex:0 0 auto;padding:0 .35rem!important;min-width:unset!important;border-radius:0 4px 4px 0!important;border-left:1px solid rgba(255,255,255,.15)!important}.btn-toolbar.btn-run-gear svg{width:10px;height:10px}.btn-toolbar.btn-run.mode-claude,.btn-toolbar.btn-run-gear.mode-claude{background:#1e3a5f;border-color:#3b82f64d;color:#93c5fd}.btn-toolbar.btn-run.mode-claude:active,.btn-toolbar.btn-run-gear.mode-claude:active{background:#2563eb;border-color:#3b82f680}.btn-toolbar.btn-run.mode-opencode,.btn-toolbar.btn-run-gear.mode-opencode{background:#0a2e2a;border-color:#10b9814d;color:#6ee7b7}.btn-toolbar.btn-run.mode-opencode:active,.btn-toolbar.btn-run-gear.mode-opencode:active{background:#0d4a40;border-color:#10b98180}.run-mode-menu{bottom:100%;left:0;margin-bottom:6px;min-width:160px;max-width:80vw}.run-mode-option{padding:10px 12px;font-size:.8rem;cursor:pointer;-webkit-tap-highlight-color:rgba(255,255,255,.1)}.run-mode-history{-webkit-overflow-scrolling:touch;touch-action:manipulation}.btn-toolbar.btn-stop{display:flex!important;flex:0 0 auto;padding:0 8px!important;min-width:unset;order:3}.btn-toolbar.btn-stop svg{width:10px;height:10px;margin-right:0}.btn-toolbar.btn-shell{flex:0 0 auto;background:transparent;border:1px solid rgba(255,255,255,.2);color:#9ca3af;order:4}.btn-toolbar.btn-shell:hover,.btn-toolbar.btn-shell:active{background:#ffffff1a;color:#fff}.case-select-group{display:none!important}.toolbar-left .toolbar-group:first-child{width:100%;gap:8px}.btn-toolbar.btn-shell{flex:0 0 auto;min-width:fit-content;white-space:nowrap;padding:0 10px!important}.btn-toolbar.btn-case-mobile{display:flex!important;flex:1 1 0!important;min-width:0;padding:0 8px!important;order:2;overflow:hidden;justify-content:center;gap:4px}.btn-toolbar.btn-case-mobile #mobileCaseName{max-width:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn-case-settings-mobile{display:inline-flex!important;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;color:#9ca3af;cursor:pointer;flex-shrink:0;order:1}.btn-case-settings-mobile:active{background:#ffffff1a;color:#fff}.case-settings-popover-mobile{position:fixed;bottom:calc(var(--safe-area-bottom) + 46px);left:8px;right:8px;background:#1e1e1e;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:.6rem .75rem;z-index:1000;box-shadow:0 4px 16px #00000080;display:block}.case-settings-popover-mobile.hidden{display:none!important}.case-settings-popover-mobile .checkbox-inline{display:flex;align-items:center;gap:6px;font-size:.75rem;color:#e5e7eb}.case-settings-popover-mobile .form-hint{display:block;margin-top:.2rem;font-size:.6rem;color:#6b7280}.keyboard-accessory-bar{display:none;position:fixed;bottom:calc(var(--safe-area-bottom) + 40px);left:0;right:0;height:44px;background:#1a1a1a;border-top:1px solid rgba(255,255,255,.1);padding:6px 8px;padding-left:calc(8px + var(--safe-area-left));padding-right:calc(8px + var(--safe-area-right));gap:8px;align-items:center;justify-content:center;z-index:51;transition:transform .15s ease-out;will-change:transform}.keyboard-accessory-bar.visible{display:flex}.accessory-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:6px 12px;background:#2a2a2a;border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#e5e5e5;font-size:.65rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s}.accessory-btn.confirming{background:#6b4f00;border-color:#b8860b;color:#ffd54f}.accessory-btn:active{background:#3a3a3a}.accessory-btn svg{width:14px;height:14px}.accessory-btn-arrow{padding:6px 10px;background:#1e3a5f;border-color:#3b82f64d;color:#93c5fd}.accessory-btn-arrow:active{background:#2563eb}.accessory-btn-dismiss{padding:8px 14px;background:#2a2a2a;border:1.5px solid rgba(255,255,255,.25);border-radius:6px;color:#e5e5e5}.accessory-btn-dismiss svg{width:22px;height:22px;stroke-width:3}.accessory-btn-dismiss:active{background:#3a3a3a}.voice-preview{bottom:calc(var(--safe-area-bottom) + 94px);font-size:.8rem;max-width:90%;padding:6px 14px}.btn-case-add,.btn-case-settings{display:none!important}.paste-overlay{position:fixed;inset:0;background:#0009;z-index:10000;display:flex;align-items:flex-end;justify-content:center;padding-bottom:env(safe-area-inset-bottom,12px)}.paste-dialog{background:var(--bg-secondary, #1e1e2e);border:1px solid var(--border-color, #444);border-radius:12px;padding:12px;width:calc(100% - 24px);max-width:400px;margin-bottom:8px}.paste-textarea{width:100%;min-height:60px;max-height:120px;background:var(--bg-primary, #0d0d14);color:var(--text-primary, #e0e0e0);border:1px solid var(--border-color, #444);border-radius:8px;padding:8px;font-family:inherit;font-size:16px;resize:none;box-sizing:border-box}.paste-textarea:focus{outline:none;border-color:var(--accent-color, #7aa2f7)}.paste-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.paste-cancel,.paste-new,.paste-send{padding:8px 18px;border:none;border-radius:8px;font-size:14px;cursor:pointer}.paste-cancel{background:var(--bg-tertiary, #333);color:var(--text-secondary, #aaa)}.paste-new{background:var(--bg-tertiary, #333);color:var(--accent-color, #7aa2f7);border:1px solid var(--accent-color, #7aa2f7)}.paste-send{background:var(--accent-color, #7aa2f7);color:#fff;font-weight:600}.toolbar-select{display:none!important}.toolbar .btn-case-add{min-width:26px!important;max-width:26px!important;width:26px!important;min-height:26px!important;max-height:26px!important;height:26px!important;padding:0!important;font-size:.9rem;font-weight:700;display:inline-flex!important;align-items:center;justify-content:center;line-height:1;border-radius:4px;background:transparent;border:1px solid rgba(255,255,255,.15);color:#9ca3af}.btn-case-add:hover,.btn-case-add:active{background:#ffffff1a;color:#fff}.monitor-panel,.subagents-panel{width:100%;max-width:100%;left:0;right:0;border-radius:8px 8px 0 0;bottom:calc(44px + 2rem + var(--safe-area-bottom));max-height:35vh}.modal-content{width:100%;max-width:100%;height:100%;max-height:100%;border-radius:0;margin:0;display:flex;flex-direction:column}.modal-content.modal-sm{height:auto;max-height:85vh;border-radius:12px;margin:1rem;width:calc(100% - 2rem)}.ios-device .modal-content{padding-top:var(--safe-area-top);padding-bottom:var(--safe-area-bottom);padding-left:var(--safe-area-left);padding-right:var(--safe-area-right)}.modal-header{padding:.75rem 1rem}.modal-header h3{font-size:1rem}.modal-body{padding:.75rem 1rem;flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch}.modal-footer,.form-actions{padding:.75rem 1rem;padding-bottom:calc(.75rem + var(--safe-area-bottom))}.ios-device .terminal-container{padding-bottom:var(--safe-area-bottom)}.main{flex:1;min-height:0}.cli-info-bar{display:block}.terminal-container{height:100%;min-height:0;position:relative;overflow:visible;touch-action:none}.terminal-container .xterm,.terminal-container .xterm-viewport,.terminal-container .xterm-screen{touch-action:none}.welcome-content{max-width:calc(100vw - 1.5rem);padding:1rem .75rem}.welcome-title{font-size:1.2rem;margin-bottom:.5rem}.welcome-desc{font-size:.8rem;margin-bottom:.4rem}.welcome-actions{flex-direction:column;gap:.5rem;margin-top:1rem}.welcome-btn{width:100%;justify-content:center;min-height:44px;padding:.75rem 1rem;font-size:.85rem}.welcome-hint{font-size:.7rem;margin-top:.75rem}.modal-wizard{display:flex;flex-direction:column}.wizard-progress{padding:.5rem}.wizard-step{padding:.3rem}.wizard-step-number{width:24px;height:24px;font-size:.7rem}.wizard-step-label{display:none}.timer-banner{padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right))}.respawn-banner{padding:.4rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));font-size:.7rem}.respawn-compact-layout{flex-direction:column;gap:.25rem}.respawn-status-col{flex-shrink:1;min-width:0}.respawn-status-row1{flex-wrap:wrap;gap:.35rem}.respawn-banner .btn-icon-only{min-width:36px;min-height:36px;font-size:1rem;display:flex;align-items:center;justify-content:center;margin-left:auto;border-radius:6px}.respawn-action-log{border-left:none;border-top:1px solid rgba(34,197,94,.2);padding-left:0;padding-top:.25rem;max-height:2em;font-size:.6rem}.respawn-countdown-timers{gap:.2rem}.respawn-countdown-timer{font-size:.55rem;padding:.05rem .25rem}.respawn-header{flex-direction:row;gap:.4rem;align-items:center}.respawn-header .respawn-actions{display:flex;gap:.3rem;flex-shrink:0}.respawn-header .respawn-actions .btn-toolbar{min-height:28px!important;font-size:.7rem!important;padding:.2rem .5rem!important;border-radius:5px}.duration-presets{display:flex;flex-wrap:wrap;gap:.25rem}.duration-preset-btn{min-height:28px;padding:.2rem .4rem;font-size:.65rem;border-radius:5px;text-align:center;flex:1 1 auto;min-width:0}.duration-custom{flex-wrap:wrap;gap:.25rem;flex:1 1 100%}.duration-custom .duration-preset-btn{flex:0 0 auto;min-width:50px}.duration-custom-input.visible{flex:1}.duration-custom-input input{width:100%;min-height:28px;font-size:16px}.preset-selector{display:flex;flex-wrap:wrap;gap:.25rem}.preset-selector select{width:100%;min-height:28px;font-size:16px;border-radius:5px;padding:.2rem .4rem}.preset-selector .btn{flex:1;min-height:28px;font-size:.65rem;border-radius:5px;padding:.2rem .4rem}#sessionOptionsModal textarea{font-size:16px;min-height:32px!important;max-height:56px;height:auto!important;border-radius:6px;padding:.3rem .5rem}#modalRespawnPrompt{min-height:32px!important;max-height:56px}#modalRespawnKickstart{min-height:32px!important;max-height:48px}#sessionOptionsModal .checkbox-inline{min-height:30px;font-size:.75rem;padding:.15rem 0;gap:.4rem}#sessionOptionsModal .checkbox-inline input[type=checkbox]{width:18px;height:18px}#sessionOptionsModal .respawn-options-row{gap:.5rem}#sessionOptionsModal .form-section-header{margin-top:.75rem;padding-top:.5rem;font-size:.65rem}#sessionOptionsModal .form-row{margin-bottom:.5rem}#sessionOptionsModal .form-row label{font-size:.65rem;margin-bottom:.2rem}#sessionOptionsModal .form-hint{font-size:.6rem;margin-top:.2rem}.context-setting{padding:.625rem}.context-setting-header{flex-wrap:wrap;gap:.4rem}.context-setting-header .input-suffix-sm input{width:60px;font-size:16px;min-height:36px}.context-setting input[type=text]{font-size:16px;min-height:36px;padding:.4rem .6rem;border-radius:6px}.color-picker{gap:8px}.color-swatch{width:36px;height:36px;border-radius:6px}.form-row-switch{min-height:40px;gap:.4rem}.context-settings-grid input[type=number]{font-size:16px;min-height:36px}.ralph-limits-grid{grid-template-columns:1fr 1fr;gap:.5rem}.ralph-limits-grid .form-col input[type=number]{font-size:16px;min-height:36px;padding:.4rem .5rem;border-radius:6px}#modalRalphPhrase{font-size:16px;min-height:40px;border-radius:6px}.ralph-config-actions{margin-top:1rem}.ralph-config-actions .btn-toolbar{width:100%;min-height:44px;font-size:.85rem;border-radius:8px}.run-summary-filters{gap:.3rem;margin-bottom:.5rem}.filter-btn{padding:.35rem .75rem;font-size:.7rem;min-height:32px}.timeline-event{padding:.4rem .5rem;margin-bottom:.25rem;font-size:.7rem}.run-summary-footer{flex-direction:column;gap:.4rem;padding:.5rem 0}.run-summary-actions{flex-wrap:wrap;gap:.3rem}.run-summary-actions .btn-toolbar{flex:1;min-height:36px;font-size:.7rem;white-space:nowrap}.auto-refresh-label{font-size:.7rem}.ralph-panel{font-size:.75rem}.ralph-summary{padding:.4rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right))}.project-insights-panel{max-width:100%;font-size:.65rem;bottom:calc(44px + 2rem + var(--safe-area-bottom))}.file-browser-panel{max-width:100%;max-height:50vh;bottom:calc(44px + 2rem + var(--safe-area-bottom))}.notification-drawer{width:100%;max-width:100%;right:0;border-radius:0;padding-left:var(--safe-area-left);padding-right:var(--safe-area-right);padding-bottom:var(--safe-area-bottom)}input,textarea,[contenteditable]{scroll-margin-bottom:200px;scroll-margin-top:80px}.keyboard-visible .modal-body{max-height:40vh;overflow-y:auto}.keyboard-visible #createCaseModal .modal-body{max-height:60vh}.mobile-case-picker .modal-backdrop{background:#00000080}.mobile-case-picker-sheet{max-height:60vh;padding-bottom:var(--safe-area-bottom);animation:slideUp .2s ease-out}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.mobile-case-picker-header .modal-close{width:32px;height:32px;font-size:1.5rem}.mobile-case-picker-footer{padding-bottom:calc(12px + var(--safe-area-bottom))}.modal-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;gap:.25rem;padding:0 .75rem .5rem;flex-wrap:nowrap}.modal-tabs::-webkit-scrollbar{display:none}.modal-tab-btn{padding:.35rem .6rem;font-size:.65rem;white-space:nowrap;flex-shrink:0}#createCaseModal .modal-tabs{gap:.5rem;padding:.5rem 1rem .75rem}#createCaseModal .modal-tab-btn{flex:1;min-height:44px;padding:.6rem 1rem;font-size:.8rem;font-weight:500;border-radius:8px;justify-content:center;text-align:center}#createCaseModal .form-row{margin-bottom:1rem}#createCaseModal .form-row label{font-size:.8rem;margin-bottom:.4rem;font-weight:500;color:#e5e7eb}#createCaseModal .form-row input[type=text]{min-height:44px;font-size:16px;padding:.5rem .75rem;border-radius:8px}#createCaseModal .form-row .form-hint{font-size:.65rem;margin-top:.35rem;line-height:1.4}#createCaseModal .form-actions{padding:.75rem 1rem;padding-bottom:calc(.75rem + var(--safe-area-bottom));gap:.75rem}#createCaseModal .form-actions .btn-toolbar{min-height:44px!important;max-height:44px!important;height:44px!important;font-size:.85rem!important;font-weight:500;border-radius:8px}#caseModalSubmit.loading{opacity:.6;pointer-events:none}#createCaseModal.from-mobile .modal-content{animation:caseModalSlideUp .25s ease-out}@keyframes caseModalSlideUp{0%{transform:translateY(30px);opacity:0}to{transform:translateY(0);opacity:1}}.btn-case-create-mobile{min-height:48px!important;max-height:48px!important;height:48px!important;font-size:.85rem!important;font-weight:500;border-radius:10px}.settings-grid{grid-template-columns:1fr;gap:.35rem}.settings-grid-3col{grid-template-columns:1fr 1fr 1fr}.settings-item{padding:.35rem .5rem;font-size:.7rem}.settings-item-label{font-size:.7rem}.settings-section-header{font-size:.6rem;padding:.35rem 0 .2rem;margin-top:.5rem}.form-row{margin-bottom:.5rem}.form-row label{font-size:.7rem;margin-bottom:.2rem}.form-row .form-hint{font-size:.6rem}.form-row input[type=text],.form-row input[type=number],.form-row textarea,.form-row .form-select,.form-row select{font-size:.75rem;padding:.35rem .5rem;min-height:32px}.form-row-switch{gap:.3rem}.form-row-switch>label:first-child{font-size:.7rem}.form-section-header{font-size:.6rem;margin-top:.75rem}.event-type-grid{grid-template-columns:1fr 36px 36px 36px;gap:4px 4px;padding:6px;margin-top:6px}.event-header{font-size:.55rem}.event-label{font-size:.65rem}.event-type-grid input[type=checkbox]{width:12px;height:12px}.form-actions{gap:.5rem}.form-actions .btn-toolbar{flex:1;min-height:36px!important;max-height:36px!important;height:36px!important;font-size:.75rem!important}.subagent-window{position:fixed;width:calc(100% - 8px);max-width:calc(100% - 8px);height:110px;min-height:80px;max-height:110px;min-width:200px;border-radius:6px;resize:none;box-shadow:0 2px 8px #0006}.subagent-window:after{display:none}.subagent-window-header{padding:.2rem .4rem;min-height:24px}.subagent-window-title{gap:.2rem;overflow:hidden}.subagent-window-title .icon{font-size:.7rem}.subagent-window-title .id{font-size:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:45vw}.subagent-window-title .status{font-size:.45rem;padding:.05rem .15rem}.subagent-model-badge{font-size:.45rem!important;padding:.05rem .15rem!important}.subagent-window-actions button{font-size:.7rem;padding:.15rem .35rem;min-width:26px;min-height:26px;display:flex;align-items:center;justify-content:center}.subagent-window-body{font-size:.6rem;padding:.2rem .35rem}.subagent-window-body .activity-line{gap:.15rem;padding:.05rem 0}.subagent-window-body .activity-line .time{font-size:.5rem}.subagent-window-body .activity-line .tool-name,.subagent-window-body .activity-line .tool-detail{font-size:.55rem}.subagent-window-parent{display:none}.session-tab .tab-subagent-badge{height:14px;padding:0 3px;border-radius:7px;margin-left:2px}.session-tab .tab-subagent-badge .subagent-label{font-size:.45rem}.subagent-dropdown{position:fixed!important;left:8px!important;right:8px!important;bottom:auto!important;min-width:auto!important;max-width:none!important;border-radius:8px;transform:none!important}}.keyboard-accessory-bar{display:none;height:44px;background:#1a1a1a;border-top:1px solid rgba(255,255,255,.1);padding:6px 8px;gap:8px;align-items:center;justify-content:center;z-index:51}.keyboard-accessory-bar.visible{display:flex}.accessory-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:6px 12px;background:#2a2a2a;border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#e5e5e5;font-size:.65rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s}.accessory-btn.confirming{background:#6b4f00;border-color:#b8860b;color:#ffd54f}.accessory-btn:active{background:#3a3a3a}.accessory-btn svg{width:14px;height:14px}.accessory-btn-arrow{padding:6px 10px;background:#1e3a5f;border-color:#3b82f64d;color:#93c5fd}.accessory-btn-arrow:active{background:#2563eb}.accessory-btn-dismiss{padding:8px 14px;background:#2a2a2a;border:1.5px solid rgba(255,255,255,.25);border-radius:6px;color:#e5e5e5}.accessory-btn-dismiss svg{width:22px;height:22px;stroke-width:3}.accessory-btn-dismiss:active{background:#3a3a3a}.ios-device.safari-browser{overscroll-behavior:none}@media(hover:none)and (pointer:coarse){.session-tab .tab-close,.session-tab .tab-gear{opacity:1;width:auto;padding:.15rem .25rem;align-items:center;justify-content:center}.btn-toolbar:hover,.btn-icon-header:hover{transform:none}.subagent-dropdown-trigger:active+.subagent-dropdown-menu,.subagent-dropdown-trigger:focus+.subagent-dropdown-menu{display:block}}
@@ -1,16 +0,0 @@
1
- "use strict";Object.assign(CodemanApp.prototype,{async loadQuickStartCases(e=null,t=null){try{let s=null;try{const c=t?await t:await fetch("/api/settings").then(i=>i.ok?i.json():null);c&&(s=c.lastUsedCase||null)}catch{}const n=await(await fetch("/api/cases")).json();this.cases=n,console.log("[loadQuickStartCases] Loaded cases:",n.map(c=>c.name),"lastUsedCase:",s);const o=document.getElementById("quickStartCase");let l="";const r=n.some(c=>c.name==="testcase"),u=MobileDetection.getDeviceType()==="mobile"?8:20;if(n.forEach(c=>{const i=c.name.length>u?c.name.substring(0,u)+"\u2026":c.name;l+=`<option value="${escapeHtml(c.name)}">${escapeHtml(i)}</option>`}),r||(l='<option value="testcase">testcase</option>'+l),o.innerHTML=l,console.log("[loadQuickStartCases] Set options:",o.innerHTML.substring(0,200)),e)o.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&n.some(c=>c.name===s))o.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(n.length>0){const c=n.find(i=>i.name==="testcase")||n[0];o.value=c.name,this.updateDirDisplayForCase(c.name),this.updateMobileCaseLabel(c.name)}else o.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");o.dataset.listenerAdded||(o.addEventListener("change",()=>{this.updateDirDisplayForCase(o.value),this.saveLastUsedCase(o.value),this.updateMobileCaseLabel(o.value)}),o.dataset.listenerAdded="true")}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=await(await fetch(`/api/cases/${e}`)).json();s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async run(){return(this._runMode||"claude")==="opencode"?this.runOpenCode():this.runClaude()},get runMode(){return this._runMode||"claude"},setRunMode(e){this._runMode=e;try{localStorage.setItem("codeman_runMode",e)}catch{}this._applyRunMode(),this._apiPut("/api/settings",{runMode:e}).catch(()=>{}),document.getElementById("runModeMenu")?.classList.remove("active")},toggleRunModeMenu(e){e?.stopPropagation();const t=document.getElementById("runModeMenu");if(t&&(t.classList.toggle("active"),t.querySelectorAll(".run-mode-option").forEach(s=>{s.classList.toggle("selected",s.dataset.mode===this.runMode)}),t.classList.contains("active"))){this._loadRunModeHistory();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const a=new Date(s.lastModified),n=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),o=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),l=document.createElement("button");l.className="run-mode-option",l.title=s.workingDir,l.dataset.sessionId=s.sessionId,l.dataset.workingDir=s.workingDir;const r=document.createElement("span");r.className="hist-dir",r.textContent=o;const d=document.createElement("span");d.className="hist-meta",d.textContent=n,l.append(r,d),l.addEventListener("click",u=>{u.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir)}),e.appendChild(l)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":"Run")},_initRunMode(){try{this._runMode=localStorage.getItem("codeman_runMode")||"claude"}catch{this._runMode="claude"}this._applyRunMode()},incrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},incrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=await(await fetch(`/api/cases/${e}`)).json();if(!a.path){const g=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!g.success)throw new Error(g.error||"Failed to create case");a=g.case}const n=a.path;if(!n)throw new Error("Case path not found");let o=null,l=1;for(const[,m]of this.sessions){const g=m.name&&m.name.match(/^w(\d+)-(.+)$/);if(g&&g[2]===e){const f=parseInt(g[1]);f>=l&&(l=f+1)}}const r=this.isRalphTrackerEnabledByDefault(),d=[];for(let m=0;m<t;m++)d.push(`w${l+m}-${e}`);const u=this.getCaseSettings(e),c=this.loadAppSettingsFromStorage(),i={};(u.agentTeams||c.agentTeamsEnabled)&&(i.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1");const p=Object.keys(i).length>0;this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const h=d.map(m=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,name:m,...p?{envOverrides:i}:{}})}).then(g=>g.json())),C=await Promise.all(h),y=[];for(const m of C){if(!m.success)throw new Error(m.error);y.push(m.session.id)}o=y[0],await Promise.all(y.map(m=>fetch(`/api/sessions/${m}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:r,disableAutoEnable:!r})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(y.map(m=>fetch(`/api/sessions/${m}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),o&&(await this.selectSession(o),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{const n=(await(await fetch(`/api/cases/${e}`)).json()).path;if(!n)throw new Error("Case path not found");let o=1;for(const[,i]of this.sessions){const p=i.name&&i.name.match(/^s(\d+)-(.+)$/);if(p&&p[2]===e){const h=parseInt(p[1]);h>=o&&(o=h+1)}}const l=[];for(let i=0;i<t;i++)l.push(`s${o+i}-${e}`);const r=l.map(i=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,mode:"shell",name:i})}).then(p=>p.json())),d=await Promise.all(r),u=[];for(const i of d){if(!i.success)throw new Error(i.error);u.push(i.session.id)}await Promise.all(u.map(i=>fetch(`/api/sessions/${i}/shell`,{method:"POST"})));const c=this.getTerminalDimensions();c&&await Promise.all(u.map(i=>fetch(`/api/sessions/${i}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)}))),u.length>0&&(this.activeSessionId=u[0],await this.selectSession(u[0])),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!(await(await fetch("/api/opencode/status")).json()).available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",openCodeConfig:{autoAllowTools:!0}})})).json();if(!n.success)throw new Error(n.error||"Failed to start OpenCode");n.sessionId&&await this.selectSession(n.sessionId),this.terminal.focus()}catch(t){this.terminal.writeln(`\x1B[1;31m Error: ${t.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e,this.switchOptionsTab(t.mode==="opencode"?"summary":"respawn");const s=document.getElementById("sessionRespawnStatus"),a=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(s.classList.add("active"),s.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",a.style.display="none",n.style.display=""):(s.classList.remove("active"),s.querySelector(".respawn-status-text").textContent="Not active",a.style.display="",n.style.display="none");const o=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?o.style.display="":o.style.display="none";const l=t.mode==="opencode";document.querySelectorAll("[data-claude-only]").forEach(h=>{h.style.display=l?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1,document.getElementById("modalSessionName").value=t.name||"";const d=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(h=>{h.classList.toggle("selected",h.dataset.color===d)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const c=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),i=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(l?(c&&(c.style.display="none"),i&&(i.style.display="none"),this.switchOptionsTab("context")):(c&&(c.style.display=""),i&&(i.style.display="")),!l){const h=this.ralphStates.get(e);this.populateRalphForm({enabled:h?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:h?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:h?.loop?.maxIterations||t.ralphLoop?.maxIterations||0})}const p=document.getElementById("sessionOptionsModal");p.classList.add("active"),this.activeFocusTrap=new FocusTrap(p),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=document.getElementById("modalSessionName").value.trim();try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:e})}catch(t){this.showToast("Failed to save session name: "+t.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.config){const a=s.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;const a=this.getSessionName(t),n=document.createElement("input");n.type="text",n.value=t.name||"",n.placeholder=a,n.className="tab-rename-input",n.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;";const o=s.textContent;s.textContent="",s.appendChild(n),n.focus(),n.select();const l=async()=>{const r=n.value.trim();if(s.textContent=r||o,r&&r!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:r})})}catch{s.textContent=o,this.showToast("Failed to rename","error")}};n.addEventListener("blur",l),n.addEventListener("keydown",r=>{r.key==="Enter"?(r.preventDefault(),n.blur()):r.key==="Escape"&&(n.value="",n.blur())})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const e=document.getElementById("createCaseModal");e.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(t=>{t.onclick=()=>this.switchCaseModalTab(t.dataset.tab)}),e.querySelectorAll('input[type="text"]').forEach(t=>{t._mobileScrollWired||(t._mobileScrollWired=!0,t.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>t.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),e.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");s.textContent=e==="case-create"?"Create":"Link",e==="case-create"?document.getElementById("newCaseName").focus():document.getElementById("linkCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}try{const a=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" created`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to create case","error")}catch(s){console.error("Failed to create case:",s),this.showToast("Failed to create case: "+s.message,"error")}},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let n="";const o=this.cases||[],r=o.some(d=>d.name==="testcase")?o:[{name:"testcase"},...o];for(const d of r){const u=d.name===a;n+=`
2
- <button class="mobile-case-item ${u?"selected":""}"
3
- onclick="app.selectMobileCase('${escapeHtml(d.name)}')">
4
- <span class="mobile-case-item-icon">
5
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6
- <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
7
- </svg>
8
- </span>
9
- <span class="mobile-case-item-name">${escapeHtml(d.name)}</span>
10
- <span class="mobile-case-item-check">
11
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
12
- <polyline points="20 6 9 17 4 12"/>
13
- </svg>
14
- </span>
15
- </button>
16
- `}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}});