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.
- package/dist/ai-checker-base.d.ts.map +1 -1
- package/dist/ai-checker-base.js +3 -2
- package/dist/ai-checker-base.js.map +1 -1
- package/dist/bash-tool-parser.d.ts +6 -0
- package/dist/bash-tool-parser.d.ts.map +1 -1
- package/dist/bash-tool-parser.js +87 -101
- package/dist/bash-tool-parser.js.map +1 -1
- package/dist/file-stream-manager.d.ts.map +1 -1
- package/dist/file-stream-manager.js +2 -1
- package/dist/file-stream-manager.js.map +1 -1
- package/dist/hooks-config.d.ts +5 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +25 -0
- package/dist/hooks-config.js.map +1 -1
- package/dist/orchestrator-loop.d.ts +2 -0
- package/dist/orchestrator-loop.d.ts.map +1 -1
- package/dist/orchestrator-loop.js +27 -22
- package/dist/orchestrator-loop.js.map +1 -1
- package/dist/orchestrator-verifier.d.ts +1 -1
- package/dist/orchestrator-verifier.d.ts.map +1 -1
- package/dist/orchestrator-verifier.js +3 -2
- package/dist/orchestrator-verifier.js.map +1 -1
- package/dist/plan-orchestrator.d.ts +4 -1
- package/dist/plan-orchestrator.d.ts.map +1 -1
- package/dist/plan-orchestrator.js +66 -88
- package/dist/plan-orchestrator.js.map +1 -1
- package/dist/ralph-status-parser.d.ts +2 -0
- package/dist/ralph-status-parser.d.ts.map +1 -1
- package/dist/ralph-status-parser.js +98 -102
- package/dist/ralph-status-parser.js.map +1 -1
- package/dist/respawn-controller.d.ts +4 -1
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +165 -131
- package/dist/respawn-controller.js.map +1 -1
- package/dist/session.d.ts +18 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +129 -117
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +2 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +22 -28
- package/dist/state-store.js.map +1 -1
- package/dist/subagent-watcher.d.ts +6 -0
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +145 -139
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/tunnel-manager.d.ts.map +1 -1
- package/dist/tunnel-manager.js +2 -1
- package/dist/tunnel-manager.js.map +1 -1
- package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
- package/dist/web/public/{app.3b7ff137.js → app.16290ae3.js} +2 -2
- package/dist/web/public/app.16290ae3.js.br +0 -0
- package/dist/web/public/app.16290ae3.js.gz +0 -0
- package/dist/web/public/constants.64161167.js.gz +0 -0
- package/dist/web/public/index.html +26 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.88082175.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.9fb81db6.js.gz +0 -0
- package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
- package/dist/web/public/mobile.9a61290c.css +1 -0
- package/dist/web/public/mobile.9a61290c.css.br +0 -0
- package/dist/web/public/mobile.9a61290c.css.gz +0 -0
- package/dist/web/public/notification-manager.2d5ea8ec.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.8204db1e.js → panels-ui.2d5b9703.js} +1 -1
- package/dist/web/public/panels-ui.2d5b9703.js.br +0 -0
- package/dist/web/public/panels-ui.2d5b9703.js.gz +0 -0
- package/dist/web/public/{ralph-panel.a2733fd5.js → ralph-panel.61076370.js} +1 -1
- package/dist/web/public/ralph-panel.61076370.js.br +0 -0
- package/dist/web/public/ralph-panel.61076370.js.gz +0 -0
- package/dist/web/public/ralph-wizard.f31ab90e.js.gz +0 -0
- package/dist/web/public/{respawn-ui.372c6ea7.js → respawn-ui.60be6ef5.js} +1 -1
- package/dist/web/public/respawn-ui.60be6ef5.js.br +0 -0
- package/dist/web/public/respawn-ui.60be6ef5.js.gz +0 -0
- package/dist/web/public/session-ui.ab189b7f.js +16 -0
- package/dist/web/public/session-ui.ab189b7f.js.br +0 -0
- package/dist/web/public/session-ui.ab189b7f.js.gz +0 -0
- package/dist/web/public/settings-ui.50a8018e.js +55 -0
- package/dist/web/public/settings-ui.50a8018e.js.br +0 -0
- package/dist/web/public/settings-ui.50a8018e.js.gz +0 -0
- package/dist/web/public/styles.111ff326.css.gz +0 -0
- package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.474f79df.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/respawn-event-wiring.d.ts +51 -0
- package/dist/web/respawn-event-wiring.d.ts.map +1 -0
- package/dist/web/respawn-event-wiring.js +280 -0
- package/dist/web/respawn-event-wiring.js.map +1 -0
- package/dist/web/route-helpers.d.ts +9 -0
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +15 -0
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/orchestrator-routes.d.ts.map +1 -1
- package/dist/web/routes/orchestrator-routes.js +23 -30
- package/dist/web/routes/orchestrator-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +5 -1
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +12 -30
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +1 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +2 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +4 -51
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +98 -941
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +89 -0
- package/dist/web/session-listener-wiring.d.ts.map +1 -0
- package/dist/web/session-listener-wiring.js +290 -0
- package/dist/web/session-listener-wiring.js.map +1 -0
- package/dist/web/sse-stream-manager.d.ts +91 -0
- package/dist/web/sse-stream-manager.d.ts.map +1 -0
- package/dist/web/sse-stream-manager.js +426 -0
- package/dist/web/sse-stream-manager.js.map +1 -0
- package/package.json +1 -1
- package/dist/web/public/app.3b7ff137.js.br +0 -0
- package/dist/web/public/app.3b7ff137.js.gz +0 -0
- package/dist/web/public/mobile.0b213796.css +0 -1
- package/dist/web/public/mobile.0b213796.css.br +0 -0
- package/dist/web/public/mobile.0b213796.css.gz +0 -0
- package/dist/web/public/panels-ui.8204db1e.js.br +0 -0
- package/dist/web/public/panels-ui.8204db1e.js.gz +0 -0
- package/dist/web/public/ralph-panel.a2733fd5.js.br +0 -0
- package/dist/web/public/ralph-panel.a2733fd5.js.gz +0 -0
- package/dist/web/public/respawn-ui.372c6ea7.js.br +0 -0
- package/dist/web/public/respawn-ui.372c6ea7.js.gz +0 -0
- package/dist/web/public/session-ui.554092ae.js +0 -16
- package/dist/web/public/session-ui.554092ae.js.br +0 -0
- package/dist/web/public/session-ui.554092ae.js.gz +0 -0
- package/dist/web/public/settings-ui.bd3eaadb.js +0 -55
- package/dist/web/public/settings-ui.bd3eaadb.js.br +0 -0
- 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
|
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}}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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)}});
|
|
Binary file
|
|
Binary file
|