aicodeman 0.5.2 → 0.5.4
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/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/ralph-tracker.d.ts +9 -0
- package/dist/ralph-tracker.d.ts.map +1 -1
- package/dist/ralph-tracker.js +52 -60
- package/dist/ralph-tracker.js.map +1 -1
- package/dist/respawn-controller.d.ts +18 -1
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +215 -181
- package/dist/respawn-controller.js.map +1 -1
- package/dist/session-auto-ops.d.ts.map +1 -1
- package/dist/session-auto-ops.js +57 -55
- package/dist/session-auto-ops.js.map +1 -1
- package/dist/session.d.ts +5 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +182 -218
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +6 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +67 -79
- package/dist/state-store.js.map +1 -1
- package/dist/subagent-watcher.d.ts +24 -0
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +215 -220
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/tmux-manager.d.ts +17 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +57 -66
- package/dist/tmux-manager.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.16290ae3.js +26 -0
- 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 +7 -7
- 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.0b213796.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.72f2f538.js → session-ui.554092ae.js} +1 -1
- 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 → settings-ui.c58b0b9b.js} +7 -7
- package/dist/web/public/settings-ui.c58b0b9b.js.br +0 -0
- package/dist/web/public/settings-ui.c58b0b9b.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 +23 -0
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +53 -0
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +2 -11
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +8 -24
- package/dist/web/routes/file-routes.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/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +17 -71
- package/dist/web/routes/system-routes.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.e09fd4a6.js +0 -26
- package/dist/web/public/app.e09fd4a6.js.br +0 -0
- package/dist/web/public/app.e09fd4a6.js.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.72f2f538.js.br +0 -0
- package/dist/web/public/session-ui.72f2f538.js.gz +0 -0
- 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
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";const _crashDiag={_entries:[],_maxEntries:50,log(l){const e=`${new Date().toISOString().slice(11,23)} ${l}`;this._entries.push(e),this._entries.length>this._maxEntries&&this._entries.shift();try{localStorage.setItem("codeman-crash-diag",this._entries.join(`
|
|
2
|
-
`))}catch{}}};try{const l=localStorage.getItem("codeman-crash-diag");l&&console.log(`[CRASH-DIAG] Previous session breadcrumbs:
|
|
3
|
-
`+l)}catch{}if(_crashDiag.log("PAGE LOAD"),setInterval(()=>{try{localStorage.setItem("codeman-crash-heartbeat",String(Date.now())),_crashDiag._entries.length>0&&navigator.sendBeacon("/api/crash-diag",JSON.stringify({data:_crashDiag._entries.join(`
|
|
4
|
-
`)}))}catch{}},2e3),window.addEventListener("error",l=>{_crashDiag.log(`ERROR: ${l.message} at ${l.filename}:${l.lineno}`),console.error("[CRASH-DIAG] Uncaught error:",l.message,`
|
|
5
|
-
File:`,l.filename,":",l.lineno,":",l.colno,`
|
|
6
|
-
Stack:`,l.error?.stack)}),window.addEventListener("unhandledrejection",l=>{_crashDiag.log(`UNHANDLED: ${l.reason?.message||l.reason}`),console.error("[CRASH-DIAG] Unhandled promise rejection:",l.reason?.message||l.reason,`
|
|
7
|
-
Stack:`,l.reason?.stack)}),typeof PerformanceObserver<"u")try{new PerformanceObserver(e=>{for(const t of e.getEntries())t.duration>200&&(_crashDiag.log(`LONG_TASK: ${t.duration.toFixed(0)}ms`),console.warn(`[CRASH-DIAG] Long task: ${t.duration.toFixed(0)}ms (type: ${t.entryType}, name: ${t.name})`))}).observe({type:"longtask",buffered:!0})}catch{}const _origGetContext=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(l,...e){const t=_origGetContext.call(this,l,...e);return(l==="webgl2"||l==="webgl")&&(this.addEventListener("webglcontextlost",s=>{_crashDiag.log(`WEBGL_LOST: ${this.width}x${this.height}`),console.error("[CRASH-DIAG] WebGL context LOST on canvas",this.width,"x",this.height,"\u2014 prevented:",s.defaultPrevented)}),this.addEventListener("webglcontextrestored",()=>{_crashDiag.log("WEBGL_RESTORED"),console.warn("[CRASH-DIAG] WebGL context restored")})),t};const _SSE_HANDLER_MAP=[[SSE_EVENTS.INIT,"_onInit"],[SSE_EVENTS.SESSION_CREATED,"_onSessionCreated"],[SSE_EVENTS.SESSION_UPDATED,"_onSessionUpdated"],[SSE_EVENTS.SESSION_DELETED,"_onSessionDeleted"],[SSE_EVENTS.SESSION_TERMINAL,"_onSSETerminal"],[SSE_EVENTS.SESSION_NEEDS_REFRESH,"_onSSENeedsRefresh"],[SSE_EVENTS.SESSION_CLEAR_TERMINAL,"_onSSEClearTerminal"],[SSE_EVENTS.SESSION_COMPLETION,"_onSessionCompletion"],[SSE_EVENTS.SESSION_ERROR,"_onSessionError"],[SSE_EVENTS.SESSION_EXIT,"_onSessionExit"],[SSE_EVENTS.SESSION_IDLE,"_onSessionIdle"],[SSE_EVENTS.SESSION_WORKING,"_onSessionWorking"],[SSE_EVENTS.SESSION_AUTO_CLEAR,"_onSessionAutoClear"],[SSE_EVENTS.SESSION_CLI_INFO,"_onSessionCliInfo"],[SSE_EVENTS.SCHEDULED_CREATED,"_onScheduledCreated"],[SSE_EVENTS.SCHEDULED_UPDATED,"_onScheduledUpdated"],[SSE_EVENTS.SCHEDULED_COMPLETED,"_onScheduledCompleted"],[SSE_EVENTS.SCHEDULED_STOPPED,"_onScheduledStopped"],[SSE_EVENTS.RESPAWN_STARTED,"_onRespawnStarted"],[SSE_EVENTS.RESPAWN_STOPPED,"_onRespawnStopped"],[SSE_EVENTS.RESPAWN_STATE_CHANGED,"_onRespawnStateChanged"],[SSE_EVENTS.RESPAWN_CYCLE_STARTED,"_onRespawnCycleStarted"],[SSE_EVENTS.RESPAWN_BLOCKED,"_onRespawnBlocked"],[SSE_EVENTS.RESPAWN_AUTO_ACCEPT_SENT,"_onRespawnAutoAcceptSent"],[SSE_EVENTS.RESPAWN_DETECTION_UPDATE,"_onRespawnDetectionUpdate"],[SSE_EVENTS.RESPAWN_TIMER_STARTED,"_onRespawnTimerStarted"],[SSE_EVENTS.RESPAWN_TIMER_CANCELLED,"_onRespawnTimerCancelled"],[SSE_EVENTS.RESPAWN_TIMER_COMPLETED,"_onRespawnTimerCompleted"],[SSE_EVENTS.RESPAWN_ERROR,"_onRespawnError"],[SSE_EVENTS.RESPAWN_ACTION_LOG,"_onRespawnActionLog"],[SSE_EVENTS.TASK_CREATED,"_onTaskCreated"],[SSE_EVENTS.TASK_COMPLETED,"_onTaskCompleted"],[SSE_EVENTS.TASK_FAILED,"_onTaskFailed"],[SSE_EVENTS.TASK_UPDATED,"_onTaskUpdated"],[SSE_EVENTS.MUX_CREATED,"_onMuxCreated"],[SSE_EVENTS.MUX_KILLED,"_onMuxKilled"],[SSE_EVENTS.MUX_DIED,"_onMuxDied"],[SSE_EVENTS.MUX_STATS_UPDATED,"_onMuxStatsUpdated"],[SSE_EVENTS.SESSION_RALPH_LOOP_UPDATE,"_onRalphLoopUpdate"],[SSE_EVENTS.SESSION_RALPH_TODO_UPDATE,"_onRalphTodoUpdate"],[SSE_EVENTS.SESSION_RALPH_COMPLETION_DETECTED,"_onRalphCompletionDetected"],[SSE_EVENTS.SESSION_RALPH_STATUS_UPDATE,"_onRalphStatusUpdate"],[SSE_EVENTS.SESSION_CIRCUIT_BREAKER_UPDATE,"_onCircuitBreakerUpdate"],[SSE_EVENTS.SESSION_EXIT_GATE_MET,"_onExitGateMet"],[SSE_EVENTS.SESSION_BASH_TOOL_START,"_onBashToolStart"],[SSE_EVENTS.SESSION_BASH_TOOL_END,"_onBashToolEnd"],[SSE_EVENTS.SESSION_BASH_TOOLS_UPDATE,"_onBashToolsUpdate"],[SSE_EVENTS.HOOK_IDLE_PROMPT,"_onHookIdlePrompt"],[SSE_EVENTS.HOOK_PERMISSION_PROMPT,"_onHookPermissionPrompt"],[SSE_EVENTS.HOOK_ELICITATION_DIALOG,"_onHookElicitationDialog"],[SSE_EVENTS.HOOK_STOP,"_onHookStop"],[SSE_EVENTS.HOOK_TEAMMATE_IDLE,"_onHookTeammateIdle"],[SSE_EVENTS.HOOK_TASK_COMPLETED,"_onHookTaskCompleted"],[SSE_EVENTS.SUBAGENT_DISCOVERED,"_onSubagentDiscovered"],[SSE_EVENTS.SUBAGENT_UPDATED,"_onSubagentUpdated"],[SSE_EVENTS.SUBAGENT_TOOL_CALL,"_onSubagentToolCall"],[SSE_EVENTS.SUBAGENT_PROGRESS,"_onSubagentProgress"],[SSE_EVENTS.SUBAGENT_MESSAGE,"_onSubagentMessage"],[SSE_EVENTS.SUBAGENT_TOOL_RESULT,"_onSubagentToolResult"],[SSE_EVENTS.SUBAGENT_COMPLETED,"_onSubagentCompleted"],[SSE_EVENTS.IMAGE_DETECTED,"_onImageDetected"],[SSE_EVENTS.TUNNEL_STARTED,"_onTunnelStarted"],[SSE_EVENTS.TUNNEL_STOPPED,"_onTunnelStopped"],[SSE_EVENTS.TUNNEL_PROGRESS,"_onTunnelProgress"],[SSE_EVENTS.TUNNEL_ERROR,"_onTunnelError"],[SSE_EVENTS.TUNNEL_QR_ROTATED,"_onTunnelQrRotated"],[SSE_EVENTS.TUNNEL_QR_REGENERATED,"_onTunnelQrRegenerated"],[SSE_EVENTS.TUNNEL_QR_AUTH_USED,"_onTunnelQrAuthUsed"],[SSE_EVENTS.PLAN_SUBAGENT,"_onPlanSubagent"],[SSE_EVENTS.PLAN_PROGRESS,"_onPlanProgress"],[SSE_EVENTS.PLAN_STARTED,"_onPlanStarted"],[SSE_EVENTS.PLAN_CANCELLED,"_onPlanCancelled"],[SSE_EVENTS.PLAN_COMPLETED,"_onPlanCompleted"],[SSE_EVENTS.ORCHESTRATOR_STATE_CHANGED,"_onOrchestratorStateChanged"],[SSE_EVENTS.ORCHESTRATOR_PLAN_PROGRESS,"_onOrchestratorPlanProgress"],[SSE_EVENTS.ORCHESTRATOR_PLAN_READY,"_onOrchestratorPlanReady"],[SSE_EVENTS.ORCHESTRATOR_PHASE_STARTED,"_onOrchestratorPhaseStarted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_COMPLETED,"_onOrchestratorPhaseCompleted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_FAILED,"_onOrchestratorPhaseFailed"],[SSE_EVENTS.ORCHESTRATOR_VERIFICATION,"_onOrchestratorVerification"],[SSE_EVENTS.ORCHESTRATOR_TASK_ASSIGNED,"_onOrchestratorTaskAssigned"],[SSE_EVENTS.ORCHESTRATOR_TASK_COMPLETED,"_onOrchestratorTaskCompleted"],[SSE_EVENTS.ORCHESTRATOR_TASK_FAILED,"_onOrchestratorTaskFailed"],[SSE_EVENTS.ORCHESTRATOR_COMPLETED,"_onOrchestratorCompleted"],[SSE_EVENTS.ORCHESTRATOR_ERROR,"_onOrchestratorError"]];class CodemanApp{constructor(){this.sessions=new Map,this._shortIdCache=new Map,this.sessionOrder=[],this.draggedTabId=null,this.cases=[],this.currentRun=null,this.totalTokens=0,this.globalStats=null,this.eventSource=null,this.terminal=null,this.fitAddon=null,this.activeSessionId=null,this._initGeneration=0,this._initFallbackTimer=null,this._selectGeneration=0,this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.timerCountdownInterval=null,this.terminalBuffers=new Map,this.editingSessionId=null,this.pendingCloseSessionId=null,this.muxSessions=[],this.ralphStates=new Map,this.subagents=new Map,this.subagentActivity=new Map,this.subagentToolResults=new Map,this.activeSubagentId=null,this.subagentPanelVisible=!1,this.subagentWindows=new Map,this.subagentWindowZIndex=ZINDEX_SUBAGENT_BASE,this.minimizedSubagents=new Map,this._subagentHideTimeout=null,this.subagentParentMap=new Map,this.teams=new Map,this.teamTasks=new Map,this.teammateMap=new Map,this.teammatePanesByName=new Map,this.teammateTerminals=new Map,this.terminalBufferCache=new Map,this.ralphStatePanelCollapsed=!0,this.ralphClosedSessions=new Set,this.planSubagents=new Map,this.planSubagentWindowZIndex=ZINDEX_PLAN_SUBAGENT_BASE,this.planGenerationStopped=!1,this.planAgentsMinimized=!1,this.wizardDragState=null,this.wizardDragListeners=null,this.wizardPosition=null,this.projectInsights=new Map,this.logViewerWindows=new Map,this.logViewerWindowZIndex=ZINDEX_LOG_VIEWER_BASE,this.projectInsightsPanelVisible=!1,this.orchestratorState=null,this.orchestratorPanelVisible=!1,this.currentSessionWorkingDir=null,this.imagePopups=new Map,this.imagePopupZIndex=ZINDEX_IMAGE_POPUP_BASE,this.fileBrowserData=null,this.fileBrowserExpandedDirs=new Set,this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1,this.fileBrowserDragListeners=null,this.filePreviewContent="",this._toastContainer=null,this._tunnelUrl=null,this.tabAlerts=new Map,this.pendingHooks=new Map,this._ws=null,this._wsSessionId=null,this._wsReady=!1,this.pendingWrites=[],this.writeFrameScheduled=!1,this._wasAtBottomBeforeWrite=!0,this.syncWaitTimeout=null,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this.flickerFilterBuffer="",this.flickerFilterActive=!1,this.flickerFilterTimeout=null,this._debounceTimers=Object.create(null),this.systemStatsInterval=null,this.sseReconnectTimeout=null,this._sseListenerCleanup=null,this.reconnectAttempts=0,this.maxReconnectAttempts=10,this.isOnline=navigator.onLine,this._inputQueue=new Map,this._inputQueueMaxBytes=64*1024,this._connectionStatus="connected",this._inputSendChain=Promise.resolve(),this._localEchoOverlay=null,this._localEchoEnabled=!1,this._restoringFlushedState=!1,this.activeFocusTrap=null,this.notificationManager=new NotificationManager(this),this.idleTimers=new Map,this._elemCache={},this.init()}$(e){return this._elemCache[e]||(this._elemCache[e]=document.getElementById(e)),this._elemCache[e]}formatTokens(e){if(e>=1e6){const t=e/1e6;return t>=10?`${t.toFixed(1)}m`:`${t.toFixed(2)}m`}else if(e>=1e3){const t=e/1e3;return t>=100?`${t.toFixed(0)}k`:`${t.toFixed(1)}k`}return String(e)}estimateCost(e,t){const s=e/1e6*15,i=t/1e6*75;return s+i}setPendingHook(e,t){this.pendingHooks.has(e)||this.pendingHooks.set(e,new Set),this.pendingHooks.get(e).add(t),this.updateTabAlertFromHooks(e)}clearPendingHooks(e,t=null){const s=this.pendingHooks.get(e);s&&(t?s.delete(t):s.clear(),s.size===0&&this.pendingHooks.delete(e),this.updateTabAlertFromHooks(e))}updateTabAlertFromHooks(e){const t=this.pendingHooks.get(e);!t||t.size===0?this.tabAlerts.delete(e):t.has("permission_prompt")||t.has("elicitation_dialog")?this.tabAlerts.set(e,"action"):t.has("idle_prompt")&&this.tabAlerts.set(e,"idle"),this.renderSessionTabs()}init(){MobileDetection.init(),KeyboardHandler.init(),SwipeHandler.init(),VoiceInput.init(),KeyboardAccessoryBar.init(),this.applyHeaderVisibilitySettings(),this.applyTabWrapSettings(),this.applyMonitorVisibility(),document.documentElement.classList.remove("mobile-init"),requestAnimationFrame(()=>{this.initTerminal(),this.loadFontSize(),this.connectSSE(),this._initFallbackTimer=setTimeout(()=>{this._initGeneration===0&&this.loadState()},3e3)}),this.registerServiceWorker(),this.loadTunnelStatus();const e=fetch("/api/settings").then(t=>t.ok?t.json():null).catch(()=>null);if(this.loadQuickStartCases(null,e),this._initRunMode(),this.setupEventListeners(),MobileDetection.isTouchDevice()){const t=s=>{s&&s.addEventListener("touchstart",i=>{if(!KeyboardHandler.keyboardVisible)return;const n=i.target.closest("button");n&&(i.preventDefault(),n.click(),typeof app<"u"&&app.terminal&&app.terminal.focus())},{passive:!1})};t(document.querySelector(".toolbar")),t(document.querySelector(".welcome-overlay"))}this.setupOnlineDetection(),this.loadAppSettingsFromServer(e).then(()=>{this.applyHeaderVisibilitySettings(),this.applyTabWrapSettings(),this.applyMonitorVisibility()}),document.body.classList.add("app-loaded")}_initWebGL(){if(!(typeof WebglAddon>"u"))try{this._webglAddon=new WebglAddon.WebglAddon,this._webglAddon.onContextLoss(()=>{console.error("[CRASH-DIAG] WebGL context LOST \u2014 falling back to canvas renderer"),this._webglAddon.dispose(),this._webglAddon=null}),this.terminal.loadAddon(this._webglAddon),console.log("[CRASH-DIAG] WebGL renderer enabled")}catch{}}setupEventListeners(){document.addEventListener("keydown",t=>{t.isComposing||t.keyCode===229||(t.key==="Escape"&&(this.closeAllPanels(),this.closeHelp()),(t.ctrlKey||t.metaKey)&&(t.key==="?"||t.key==="/")&&(t.preventDefault(),this.showHelp()),(t.ctrlKey||t.metaKey)&&t.key==="Enter"&&(t.preventDefault(),this.quickStart()),(t.ctrlKey||t.metaKey)&&t.key==="w"&&(t.preventDefault(),this.killActiveSession()),(t.ctrlKey||t.metaKey)&&t.key==="Tab"&&(t.preventDefault(),this.nextSession()),(t.ctrlKey||t.metaKey)&&t.key==="k"&&(t.preventDefault(),this.killAllSessions()),(t.ctrlKey||t.metaKey)&&t.key==="l"&&(t.preventDefault(),this.clearTerminal()),(t.ctrlKey||t.metaKey)&&t.shiftKey&&t.key==="R"&&(t.preventDefault(),this.restoreTerminalSize()),(t.ctrlKey||t.metaKey)&&(t.key==="="||t.key==="+")&&(t.preventDefault(),this.increaseFontSize()),(t.ctrlKey||t.metaKey)&&t.key==="-"&&(t.preventDefault(),this.decreaseFontSize()),(t.ctrlKey||t.metaKey)&&t.shiftKey&&t.key==="V"&&(t.preventDefault(),VoiceInput.toggle()))},!0);const e=this.$("headerTokens");e&&!e._statsHandlerAttached&&(e.classList.add("clickable"),e._statsHandlerAttached=!0,e.addEventListener("click",()=>this.openTokenStats())),this.setupColorPicker()}connectSSE(){if(!navigator.onLine){this.setConnectionStatus("offline");return}this.sseReconnectTimeout&&(clearTimeout(this.sseReconnectTimeout),this.sseReconnectTimeout=null),this._sseListenerCleanup&&(this._sseListenerCleanup(),this._sseListenerCleanup=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectAttempts===0?this.setConnectionStatus("connecting"):this.setConnectionStatus("reconnecting"),this.eventSource=new EventSource("/api/events");const e=[],t=(s,i)=>{this.eventSource.addEventListener(s,i),e.push({event:s,handler:i})};if(this._sseListenerCleanup=()=>{for(const{event:s,handler:i}of e)this.eventSource&&this.eventSource.removeEventListener(s,i);e.length=0},this.eventSource.onopen=()=>{this.reconnectAttempts=0,this.setConnectionStatus("connected")},this.eventSource.onerror=()=>{this.reconnectAttempts++,this.reconnectAttempts>=this.maxReconnectAttempts?this.setConnectionStatus("disconnected"):this.setConnectionStatus("reconnecting"),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.sseReconnectTimeout&&clearTimeout(this.sseReconnectTimeout);const s=this.reconnectAttempts<=1?200:Math.min(500*Math.pow(2,this.reconnectAttempts-2),3e4);this.sseReconnectTimeout=setTimeout(()=>this.connectSSE(),s)},!this._sseHandlerWrappers){this._sseHandlerWrappers=new Map;for(const[s,i]of _SSE_HANDLER_MAP){const n=this[i];this._sseHandlerWrappers.set(s,o=>{try{n.call(this,o.data?JSON.parse(o.data):{})}catch(r){console.error(`[SSE] Error handling ${s}:`,r)}})}}for(const[s]of _SSE_HANDLER_MAP)t(s,this._sseHandlerWrappers.get(s))}_onInit(e){_crashDiag.log(`INIT: ${e.sessions?.length||0} sessions`),this.handleInit(e)}_onSessionCreated(e){this.sessions.set(e.id,e),this.sessionOrder.includes(e.id)||(this.sessionOrder.push(e.id),this.saveSessionOrder()),this.renderSessionTabs(),this.updateCost(),this.sessions.size===1&&this.startSystemStatsPolling()}_onSessionUpdated(e){const t=e.session||e,s=this.sessions.get(t.id),i=t.claudeSessionId&&(!s||!s.claudeSessionId);this.sessions.set(t.id,t),this.renderSessionTabs(),this.updateCost(),t.id===this.activeSessionId&&t.tokens&&this.updateRespawnTokens(t.tokens),this.updateSubagentParentNames(t.id),i&&(this.recheckOrphanSubagents(),requestAnimationFrame(()=>{this.updateConnectionLines()}))}_onSessionDeleted(e){if(this._wsSessionId===e.id&&this._disconnectWs(),this._cleanupSessionData(e.id),this.activeSessionId===e.id){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome()}this.renderSessionTabs(),this.renderRalphStatePanel(),this.renderProjectInsightsPanel(),this.sessions.size===0&&this.stopSystemStatsPolling()}_onSSETerminal(e){this._wsReady&&this._wsSessionId===e.id||this._onSessionTerminal(e)}_onSSENeedsRefresh(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionNeedsRefresh(e)}_onSSEClearTerminal(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionClearTerminal(e)}_onSessionTerminal(e){if(e.id===this.activeSessionId){if(e.data.length>32768&&_crashDiag.log(`TERMINAL: ${(e.data.length/1024).toFixed(0)}KB`),(this.pendingWrites?.reduce((s,i)=>s+i.length,0)||0)+(this.flickerFilterBuffer?.length||0)>131072){this._clientDropRecoveryTimer||(this._clientDropRecoveryTimer=setTimeout(()=>{this._clientDropRecoveryTimer=null,this._onSessionNeedsRefresh()},2e3));return}this.batchTerminalWrite(e.data)}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json();t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=await(await fetch(`/api/sessions/${e.id}/terminal`)).json();if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const i=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(i)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`);const t=this.sessions.get(e.id);this.notificationManager?.notify({urgency:"critical",category:"session-error",sessionId:e.id,sessionName:t?.name||this.getShortId(e.id),title:"Session Error",message:e.error||"Unknown error"})}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this.notificationManager?.notify({urgency:"critical",category:"session-crash",sessionId:e.id,sessionName:t?.name||this.getShortId(e.id),title:"Session Crashed",message:`Exited with code ${e.code}`})}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{const i=this.sessions.get(e.id);this.notificationManager?.notify({urgency:"warning",category:"session-stuck",sessionId:e.id,sessionName:i?.name||this.getShortId(e.id),title:"Session Idle",message:`Idle for ${Math.round(s/6e4)}+ minutes`}),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0));const t=this.sessions.get(e.sessionId);this.notificationManager?.notify({urgency:"info",category:"auto-clear",sessionId:e.sessionId,sessionName:t?.name||this.getShortId(e.sessionId),title:"Auto-Cleared",message:`Context reset at ${(e.tokens||0).toLocaleString()} tokens`})}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,this._updateConnectionIndicator(),e==="connected"&&this._inputQueue.size>0&&this._drainInputQueues()}_connectWs(e){this._disconnectWs();const s=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws/sessions/${e}/terminal`,i=new WebSocket(s);this._ws=i,this._wsSessionId=e,i.onopen=()=>{this._ws===i&&(this._wsReady=!0,this._wsReconnectAttempts=0)},i.onmessage=n=>{if(this._ws===i)try{const o=JSON.parse(n.data);o.t==="o"?this._onSessionTerminal({id:e,data:o.d}):o.t==="c"?this._onSessionClearTerminal({id:e}):o.t==="r"&&this._onSessionNeedsRefresh({id:e})}catch{}},i.onclose=n=>{if(this._ws===i&&(this._ws=null,this._wsSessionId=null,this._wsReady=!1,n.code<4004&&this.activeSessionId===e)){const o=Math.min(1e3*Math.pow(2,this._wsReconnectAttempts||0),1e4);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},o)}},i.onerror=()=>{}}_disconnectWs(){this._wsReconnectTimer&&(clearTimeout(this._wsReconnectTimer),this._wsReconnectTimer=null),this._wsReconnectAttempts=0,this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_sendInputAsync(e,t){if(!this.isOnline||this._connectionStatus==="disconnected"){this._enqueueInput(e,t);return}if(this._wsReady&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t})),this.clearPendingHooks(e);return}catch{}this._inputSendChain=this._inputSendChain.then(()=>{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:t.length<65536}).then(i=>{i.ok?this.clearPendingHooks(e):this._enqueueInput(e,t)}).catch(()=>{this._enqueueInput(e,t)})})}_enqueueInput(e,t){let i=(this._inputQueue.get(e)||"")+t;i.length>this._inputQueueMaxBytes&&(i=i.slice(i.length-this._inputQueueMaxBytes)),this._inputQueue.set(e,i),this._updateConnectionIndicator()}async _drainInputQueues(){if(this._inputQueue.size===0)return;const e=new Map(this._inputQueue);this._inputQueue.clear(),this._updateConnectionIndicator();for(const[t,s]of e)(await this._apiPost(`/api/sessions/${t}/input`,{input:s}))?.ok||this._enqueueInput(t,s);this._updateConnectionIndicator()}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;let i=0;for(const a of this._inputQueue.values())i+=a.length;const n=this._connectionStatus,o=i>0;if((n==="connected"||n==="connecting")&&!o){e.style.display="none";return}e.style.display="flex",t.className="connection-dot";const r=a=>a<1024?`${a}B`:`${(a/1024).toFixed(1)}KB`;n==="connected"&&o?(t.classList.add("draining"),s.textContent=`Sending ${r(i)}...`):n==="reconnecting"?(t.classList.add("reconnecting"),s.textContent=o?`Reconnecting (${r(i)} queued)`:"Reconnecting..."):(t.classList.add("offline"),s.textContent=o?`Offline (${r(i)} queued)`:"Offline")}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this.connectSSE()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this._serverCjkOverride||t.cjkInputEnabled||!1;e.style.display=s?"block":"none",s||(window.cjkActive=!1)}handleInit(e){this._initFallbackTimer&&(clearTimeout(this._initFallbackTimer),this._initFallbackTimer=null);const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.version){const n=this.$("versionDisplay"),o=this.$("headerVersion");n&&(n.textContent=`v${e.version}`,n.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const n of this.idleTimers.values())clearTimeout(n);if(this.idleTimers.clear(),this.flickerFilterTimeout&&(clearTimeout(this.flickerFilterTimeout),this.flickerFilterTimeout=null),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this.syncWaitTimeout&&(clearTimeout(this.syncWaitTimeout),this.syncWaitTimeout=null),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this.pendingHooks.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:n}of this.notificationManager.groupingMap.values())clearTimeout(n);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null),e.sessions.forEach(n=>{this.sessions.set(n.id,n),(n.ralphLoop||n.ralphTodos)&&!this.ralphClosedSessions.has(n.id)&&this.ralphStates.set(n.id,{loop:n.ralphLoop||null,todos:n.ralphTodos||[]})}),this._restoreEndedTabs(),this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((n,o)=>n+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((n,o)=>n+(o.totalCost||0),0);const s=e.scheduledRuns.find(n=>n.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(n=>{this.subagents.set(n.agentId,n)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[n,o]of this.subagentParentMap){const r=this.subagents.get(n);if(r&&this.sessions.has(o)){r.parentSessionId=o;const a=this.sessions.get(o);a&&(r.parentSessionName=this.getSessionName(a)),this.subagents.set(n,r)}}for(const[n]of this.subagents)this.subagentParentMap.has(n)||this.findParentSessionForSubagent(n);this.restoreSubagentWindowStates()})),t!==this._initGeneration)return;const i=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let n=i;if(!n||!this.sessions.has(n))try{n=localStorage.getItem("codeman-active-session")}catch{}n&&this.sessions.has(n)?this.selectSession(n):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t)}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const i of s)i.dataset.id===e?i.classList.add("active"):i.classList.remove("active")}_renderSessionTabsImmediate(){const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(o=>o.dataset.id)),i=new Set(this.sessions.keys());if(s.size===i.size&&[...s].every(o=>i.has(o)))for(const[o,r]of this.sessions){const a=e.querySelector(`.session-tab[data-id="${o}"]`);if(!a)continue;const h=o===this.activeSessionId,f=r.status||"idle",c=this.getSessionName(r),p=r.taskStats||{running:0,total:0},d=p.running>0;h&&!a.classList.contains("active")?a.classList.add("active"):!h&&a.classList.contains("active")&&a.classList.remove("active");const u=this.tabAlerts.get(o),w=u==="action",S=u==="idle",m=a.classList.contains("tab-alert-action"),_=a.classList.contains("tab-alert-idle");w&&!m?(a.classList.add("tab-alert-action"),a.classList.remove("tab-alert-idle")):S&&!_?(a.classList.add("tab-alert-idle"),a.classList.remove("tab-alert-action")):!u&&(m||_)&&a.classList.remove("tab-alert-action","tab-alert-idle");const E=a.querySelector(".tab-status");E&&!E.classList.contains(f)&&(E.className=`tab-status ${f}`);const b=a.querySelector(".tab-name");b&&b.textContent!==c&&(b.textContent=c);const T=a.querySelector(".tab-badge");if(d)if(T)T.textContent!==String(p.running)&&(T.textContent=p.running);else{this._fullRenderSessionTabs();return}else if(T){this._fullRenderSessionTabs();return}const g=a.querySelector(".tab-subagent-badge"),A=this.minimizedSubagents.get(o),v=A?.size||0;if(v>0&&g){const C=g.querySelector(".subagent-label"),y=v===1?"AGENT":`AGENTS (${v})`;C&&C.textContent!==y&&(C.textContent=y);const R=g.querySelector(".subagent-dropdown");if(R){const N=this.renderSubagentTabBadge(o,A),O=document.createElement("div");O.innerHTML=N;const I=O.querySelector(".subagent-dropdown");I&&(R.innerHTML=I.innerHTML)}}else if(v>0&&!g){const C=this.renderSubagentTabBadge(o,A),y=a.querySelector(".tab-gear");y&&y.insertAdjacentHTML("beforebegin",C)}else v===0&&g&&g.remove()}else this._fullRenderSessionTabs()}_fullRenderSessionTabs(){const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(i=>i.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(i=>i!==this.activeSessionId)]);for(const i of s){const n=this.sessions.get(i);if(!n)continue;const o=i===this.activeSessionId,r=n.status||"idle",a=this.getSessionName(n),h=n.mode||"claude",f=n.color||"default",c=n.taskStats||{running:0,total:0},p=c.running>0,d=this.tabAlerts.get(i),u=d==="action"?" tab-alert-action":d==="idle"?" tab-alert-idle":"",w=this.minimizedSubagents.get(i),m=(w?.size||0)>0?this.renderSubagentTabBadge(i,w):"",_=n.workingDir&&n.workingDir.split("/").pop()||"",b=(this._tallTabsEnabled??!1)&&n.name&&_&&_!==a,T=n._ended?' data-ended="1"':"";t.push(`<div class="session-tab ${o?"active":""}${u}" data-id="${i}" data-color="${f}"${T} onclick="app.selectSession('${escapeHtml(i)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(i)}')" tabindex="0" role="tab" aria-selected="${o?"true":"false"}" aria-label="${escapeHtml(a)} session" ${n.workingDir?`title="${escapeHtml(n.workingDir)}"`:""}>
|
|
8
|
-
<span class="tab-status ${r}" aria-hidden="true"></span>
|
|
9
|
-
<span class="tab-info">
|
|
10
|
-
<span class="tab-name-row">
|
|
11
|
-
${h==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':h==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':""}
|
|
12
|
-
<span class="tab-name" data-session-id="${i}">${escapeHtml(a)}</span>
|
|
13
|
-
</span>
|
|
14
|
-
${b?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(_)}</span>`:""}
|
|
15
|
-
</span>
|
|
16
|
-
${p?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${c.running} running tasks">${c.running}</span>`:""}
|
|
17
|
-
${m}
|
|
18
|
-
<span class="tab-gear" onclick="event.stopPropagation(); app.openSessionOptions('${escapeHtml(i)}')" title="Session options" aria-label="Session options" tabindex="0">⚙</span>
|
|
19
|
-
<span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession('${escapeHtml(i)}')" title="Close session" aria-label="Close session" tabindex="0">×</span>
|
|
20
|
-
</div>`)}e.innerHTML=t.join(""),this._saveTabMetadata(),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o);return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}_saveTabMetadata(){try{const e={};for(const[t,s]of this.sessions)s._ended||(e[t]={id:t,name:s.name||"",workingDir:s.workingDir||"",mode:s.mode||"claude",color:s.color||"default"});localStorage.setItem("codeman-tab-meta",JSON.stringify(e))}catch{}}_restoreEndedTabs(){try{const e=localStorage.getItem("codeman-tab-meta");if(!e)return;const t=JSON.parse(e);for(const[s,i]of Object.entries(t))this.sessions.has(s)||this.sessions.set(s,{id:s,name:i.name,workingDir:i.workingDir,mode:i.mode,color:i.color,status:"ended",_ended:!0})}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,r=i.clientX<o;s.classList.toggle("drag-over-left",r),s.classList.toggle("drag-over-right",!r)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,r=s.getBoundingClientRect(),a=r.left+r.width/2,h=i.clientX<a,f=this.sessionOrder.indexOf(o);let c=this.sessionOrder.indexOf(n);f===-1||c===-1||(this.sessionOrder.splice(f,1),c=this.sessionOrder.indexOf(n),c!==-1&&(h?this.sessionOrder.splice(c,0,o):this.sessionOrder.splice(c+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}async selectSession(e){if(this.activeSessionId===e)return;this.terminal&&this.terminal.focus();const t=performance.now(),s=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${s}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const i=++this._selectGeneration;if(i!==this._selectGeneration)return;this._disconnectWs();const n=document.getElementById("cjkInput");n&&(n.value=""),this.flickerFilterTimeout&&(clearTimeout(this.flickerFilterTimeout),this.flickerFilterTimeout=null),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._tabCompletionFallback&&(clearTimeout(this._tabCompletionFallback),this._tabCompletionFallback=null),this._clientDropRecoveryTimer&&(clearTimeout(this._clientDropRecoveryTimer),this._clientDropRecoveryTimer=null),this.syncWaitTimeout&&(clearTimeout(this.syncWaitTimeout),this.syncWaitTimeout=null),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const a=this.terminal?._core?._compositionHelper;if(a?._isComposing){a._isComposing=!1;const h=this.terminal?.element?.querySelector(".xterm-helper-textarea");h&&h.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const a=this._localEchoOverlay?.pendingText||"",h=this._localEchoOverlay?.getFlushed()?.count||0,f=this._localEchoOverlay?.getFlushed()?.text||"";a&&this._sendInputAsync(this.activeSessionId,a);const c=h+a.length;c>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,c),this._flushedTexts.set(this.activeSessionId,f+a))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection(),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const o=document.querySelector(`.session-tab.active[data-id="${e}"]`);o&&(o.classList.add("tab-glow"),o.addEventListener("animationend",()=>o.classList.remove("tab-glow"),{once:!0}));const r=this.sessions.get(e);if(r?._ended){this.terminal.clear(),this.terminal.write(`\r
|
|
21
|
-
\x1B[2mSession ended. Close tab or click to reopen.\x1B[0m\r
|
|
22
|
-
`);return}if(this.currentSessionWorkingDir=r?.workingDir||null,r&&r.pid===null&&r.status==="idle")try{const a=r.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(a,{method:"POST"}),r.status="busy"}catch(a){console.error("Failed to attach to restored session:",a)}this._restoringFlushedState=!0,this._isLoadingBuffer=!0,this._loadBufferQueue=[];try{this.fitAddon&&this.fitAddon.fit();const a=this.terminalBufferCache.get(e),h=r&&(r.status==="busy"||r.status==="working");if(a&&!h){if(_crashDiag.log(`CACHE_WRITE: ${(a.length/1024).toFixed(0)}KB`),this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(a),i!==this._selectGeneration){this._isLoadingBuffer&&this._finishBufferLoad(),this._restoringFlushedState=!1;return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else h&&(this.terminal.clear(),this.terminal.reset(),_crashDiag.log("CACHE_SKIP_BUSY"));_crashDiag.log("FETCH_START");const f=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(i!==this._selectGeneration){this._isLoadingBuffer&&this._finishBufferLoad(),this._restoringFlushedState=!1;return}const c=await f.json();if(_crashDiag.log(`FETCH_DONE: ${c.terminalBuffer?(c.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${c.truncated}`),c.terminalBuffer){if(c.terminalBuffer!==a){if(_crashDiag.log(`REWRITE: ${(c.terminalBuffer.length/1024).toFixed(0)}KB`),this.terminal.clear(),this.terminal.reset(),c.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
|
|
23
|
-
\r
|
|
24
|
-
`),await this.chunkedTerminalWrite(c.terminalBuffer),i!==this._selectGeneration){this._isLoadingBuffer&&this._finishBufferLoad(),this._restoringFlushedState=!1;return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,c.terminalBuffer),this.terminalBufferCache.size>20){const u=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(u)}}else a||(this.terminal.clear(),this.terminal.reset());if(this._isLoadingBuffer&&this._finishBufferLoad(),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const d=this._localEchoOverlay;this.terminal.write("",()=>{d.hasPending&&d.rerender()})}this.sendResize(e).then(()=>{i===this._selectGeneration&&fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:"\f"})}).catch(()=>{})}),(typeof requestIdleCallback=="function"?requestIdleCallback:d=>setTimeout(d,16))(()=>{if(i!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const d=document.getElementById("taskPanel");d&&d.classList.contains("open")&&this.renderTaskPanel();const u=this.sessions.get(e);if(u&&(u.ralphLoop||u.ralphTodos)&&this.updateRalphState(e,{loop:u.ralphLoop,todos:u.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const S=this.$("fileBrowserPanel");if(S&&(S.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const m=S.querySelector(".file-browser-header");if(m){const _=()=>{if(!S.style.left){const E=S.getBoundingClientRect();S.style.left=`${E.left}px`,S.style.top=`${E.top}px`,S.style.right="auto"}};m.addEventListener("mousedown",_),m.addEventListener("touchstart",_,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(S,m),this.fileBrowserDragListeners._onFirstDrag=_}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),this.terminal.focus(),this.terminal.scrollToBottom(),_crashDiag.log(`SELECT_DONE: ${(performance.now()-t).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-t).toFixed(0)}ms`)}catch(a){this._isLoadingBuffer&&this._finishBufferLoad(),this._restoringFlushedState=!1,console.error("Failed to load session terminal:",a)}}_cleanupSessionData(e){this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._updateTokensTimeout&&clearTimeout(this._updateTokensTimeout),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(r=>{r.tokens&&(e+=r.tokens.input||0,t+=r.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const a=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?a?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${a?`
|
|
25
|
-
Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${a?`
|
|
26
|
-
Estimated cost based on Claude Opus pricing`:""}`}}}try{for(let l=0;l<localStorage.length;l++){const e=localStorage.key(l);if(e&&(e.startsWith("claudeman-")||e.startsWith("claudeman_"))){const t=e.replace(/^claudeman[-_]/,s=>"codeman"+s.charAt(s.length-1));localStorage.getItem(t)===null&&localStorage.setItem(t,localStorage.getItem(e))}}}catch{}let app;document.addEventListener("DOMContentLoaded",()=>{app=new CodemanApp,window.app=app}),window.MobileDetection=MobileDetection;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|