aicodeman 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/ai-checker-base.d.ts.map +1 -1
- package/dist/ai-checker-base.js +2 -2
- package/dist/ai-checker-base.js.map +1 -1
- package/dist/ai-idle-checker.d.ts.map +1 -1
- package/dist/ai-idle-checker.js +5 -5
- package/dist/ai-idle-checker.js.map +1 -1
- package/dist/ai-plan-checker.d.ts.map +1 -1
- package/dist/ai-plan-checker.js +5 -5
- package/dist/ai-plan-checker.js.map +1 -1
- package/dist/config/ai-defaults.d.ts +20 -4
- package/dist/config/ai-defaults.d.ts.map +1 -1
- package/dist/config/ai-defaults.js +32 -4
- package/dist/config/ai-defaults.js.map +1 -1
- package/dist/config/server-timing.d.ts +6 -0
- package/dist/config/server-timing.d.ts.map +1 -1
- package/dist/config/server-timing.js +9 -0
- package/dist/config/server-timing.js.map +1 -1
- package/dist/file-stream-manager.d.ts.map +1 -1
- package/dist/file-stream-manager.js +3 -2
- package/dist/file-stream-manager.js.map +1 -1
- package/dist/ralph-config.d.ts.map +1 -1
- package/dist/ralph-config.js +3 -4
- package/dist/ralph-config.js.map +1 -1
- package/dist/ralph-stall-detector.d.ts.map +1 -1
- package/dist/ralph-stall-detector.js +2 -1
- package/dist/ralph-stall-detector.js.map +1 -1
- package/dist/ralph-tracker.d.ts.map +1 -1
- package/dist/ralph-tracker.js +17 -22
- package/dist/ralph-tracker.js.map +1 -1
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +5 -5
- package/dist/respawn-controller.js.map +1 -1
- package/dist/run-summary.d.ts.map +1 -1
- package/dist/run-summary.js +2 -1
- package/dist/run-summary.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +3 -6
- package/dist/session.js.map +1 -1
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +2 -1
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/task-tracker.d.ts.map +1 -1
- package/dist/task-tracker.js +2 -1
- package/dist/task-tracker.js.map +1 -1
- package/dist/types/plan.d.ts +2 -4
- package/dist/types/plan.d.ts.map +1 -1
- package/dist/types/plan.js +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/regex-patterns.d.ts +5 -0
- package/dist/utils/regex-patterns.d.ts.map +1 -1
- package/dist/utils/regex-patterns.js +11 -0
- package/dist/utils/regex-patterns.js.map +1 -1
- package/dist/utils/token-validation.d.ts +1 -27
- package/dist/utils/token-validation.d.ts.map +1 -1
- package/dist/utils/token-validation.js +1 -47
- package/dist/utils/token-validation.js.map +1 -1
- package/dist/web/ports/event-port.d.ts +1 -0
- package/dist/web/ports/event-port.d.ts.map +1 -1
- package/dist/web/public/api-client.js.gz +0 -0
- package/dist/web/public/app.js +63 -28
- package/dist/web/public/app.js.br +0 -0
- package/dist/web/public/app.js.gz +0 -0
- package/dist/web/public/constants.js +2 -2
- package/dist/web/public/constants.js.br +0 -0
- package/dist/web/public/constants.js.gz +0 -0
- package/dist/web/public/index.html +3 -0
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/keyboard-accessory.js.gz +0 -0
- package/dist/web/public/mobile-handlers.js.gz +0 -0
- package/dist/web/public/mobile.css.gz +0 -0
- package/dist/web/public/notification-manager.js.gz +0 -0
- package/dist/web/public/ralph-wizard.js.gz +0 -0
- package/dist/web/public/styles.css +1 -1
- package/dist/web/public/styles.css.br +0 -0
- package/dist/web/public/styles.css.gz +0 -0
- package/dist/web/public/subagent-windows.js +118 -18
- package/dist/web/public/subagent-windows.js.br +0 -0
- package/dist/web/public/subagent-windows.js.gz +0 -0
- package/dist/web/public/sw.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.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.js.gz +0 -0
- package/dist/web/routes/respawn-routes.d.ts.map +1 -1
- package/dist/web/routes/respawn-routes.js +6 -5
- package/dist/web/routes/respawn-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +16 -0
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/server.d.ts +12 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +79 -14
- package/dist/web/server.js.map +1 -1
- package/package.json +5 -3
package/dist/web/server.js
CHANGED
|
@@ -64,7 +64,7 @@ import { SseEvent } from './sse-events.js';
|
|
|
64
64
|
import { registerAuthMiddleware, registerSecurityHeaders } from './middleware/auth.js';
|
|
65
65
|
import { registerPushRoutes, registerTeamRoutes, registerMuxRoutes, registerFileRoutes, registerScheduledRoutes, registerHookEventRoutes, registerSystemRoutes, registerCaseRoutes, registerSessionRoutes, registerRespawnRoutes, registerRalphRoutes, registerPlanRoutes, } from './routes/index.js';
|
|
66
66
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
67
|
-
import { TERMINAL_BATCH_INTERVAL, TASK_UPDATE_BATCH_INTERVAL, STATE_UPDATE_DEBOUNCE_INTERVAL, SESSIONS_LIST_CACHE_TTL, SCHEDULED_CLEANUP_INTERVAL, SCHEDULED_RUN_MAX_AGE, SSE_HEARTBEAT_INTERVAL, SSE_PADDING_SIZE, SESSION_LIMIT_WAIT_MS, ITERATION_PAUSE_MS, BATCH_FLUSH_THRESHOLD, STATS_COLLECTION_INTERVAL_MS, } from '../config/server-timing.js';
|
|
67
|
+
import { TERMINAL_BATCH_INTERVAL, TASK_UPDATE_BATCH_INTERVAL, STATE_UPDATE_DEBOUNCE_INTERVAL, SESSIONS_LIST_CACHE_TTL, SCHEDULED_CLEANUP_INTERVAL, SCHEDULED_RUN_MAX_AGE, SSE_HEARTBEAT_INTERVAL, SSE_PADDING_SIZE, SESSION_LIMIT_WAIT_MS, ITERATION_PAUSE_MS, BATCH_FLUSH_THRESHOLD, STATS_COLLECTION_INTERVAL_MS, INACTIVITY_TIMEOUT_MS, } from '../config/server-timing.js';
|
|
68
68
|
// DEC mode 2026 - Synchronized Output
|
|
69
69
|
// When terminal supports this, it buffers all output between start/end markers
|
|
70
70
|
// and renders atomically, eliminating partial-frame flicker from Ink redraws.
|
|
@@ -113,7 +113,14 @@ export class WebServer extends EventEmitter {
|
|
|
113
113
|
// Store session listener references for explicit cleanup (prevents memory leaks)
|
|
114
114
|
sessionListenerRefs = new Map();
|
|
115
115
|
scheduledRuns = new Map();
|
|
116
|
-
|
|
116
|
+
/**
|
|
117
|
+
* SSE clients mapped to their session subscription filter.
|
|
118
|
+
* Value is a Set of session IDs the client wants events for,
|
|
119
|
+
* or `null` meaning "receive all events" (backwards-compatible default).
|
|
120
|
+
*/
|
|
121
|
+
sseClients = new Map();
|
|
122
|
+
/** SSE clients connecting from non-localhost (i.e. through tunnel) */
|
|
123
|
+
remoteSseClients = new Set();
|
|
117
124
|
/** Clients with backpressure — skip writes until 'drain' fires */
|
|
118
125
|
backpressuredClients = new Set();
|
|
119
126
|
store = getStore();
|
|
@@ -128,7 +135,7 @@ export class WebServer extends EventEmitter {
|
|
|
128
135
|
// Adaptive batching: track rapid events to extend batch window (per-session)
|
|
129
136
|
// StaleExpirationMap auto-cleans entries for sessions that stop generating output
|
|
130
137
|
lastTerminalEventTime = new StaleExpirationMap({
|
|
131
|
-
ttlMs:
|
|
138
|
+
ttlMs: INACTIVITY_TIMEOUT_MS, // 5 minutes - auto-expire stale session timing data
|
|
132
139
|
refreshOnGet: false, // Don't refresh on reads, only on explicit sets
|
|
133
140
|
});
|
|
134
141
|
// Centralized cleanup for standalone timers (intervals + resettable timeouts)
|
|
@@ -370,6 +377,7 @@ export class WebServer extends EventEmitter {
|
|
|
370
377
|
batchTerminalData: this.batchTerminalData.bind(this),
|
|
371
378
|
broadcastSessionStateDebounced: this.broadcastSessionStateDebounced.bind(this),
|
|
372
379
|
batchTaskUpdate: this.batchTaskUpdate.bind(this),
|
|
380
|
+
getSseClientCount: () => this.remoteSseClients.size,
|
|
373
381
|
// RespawnPort
|
|
374
382
|
respawnControllers: this.respawnControllers,
|
|
375
383
|
respawnTimers: this.respawnTimers,
|
|
@@ -453,13 +461,32 @@ export class WebServer extends EventEmitter {
|
|
|
453
461
|
reply.code(503).send('Too many SSE connections');
|
|
454
462
|
return;
|
|
455
463
|
}
|
|
464
|
+
// Parse optional session subscription filter from query parameter.
|
|
465
|
+
// /api/events?sessions=id1,id2 — client only receives events for those sessions.
|
|
466
|
+
// /api/events (no param) — client receives all events (backwards-compatible).
|
|
467
|
+
const query = req.query;
|
|
468
|
+
let sessionFilter = null;
|
|
469
|
+
if (query.sessions) {
|
|
470
|
+
const ids = query.sessions
|
|
471
|
+
.split(',')
|
|
472
|
+
.map((s) => s.trim())
|
|
473
|
+
.filter(Boolean);
|
|
474
|
+
if (ids.length > 0) {
|
|
475
|
+
sessionFilter = new Set(ids);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
456
478
|
reply.raw.writeHead(200, {
|
|
457
479
|
'Content-Type': 'text/event-stream',
|
|
458
480
|
'Cache-Control': 'no-cache',
|
|
459
481
|
Connection: 'keep-alive',
|
|
460
482
|
'X-Accel-Buffering': 'no', // Disable nginx buffering
|
|
461
483
|
});
|
|
462
|
-
this.sseClients.
|
|
484
|
+
this.sseClients.set(reply, sessionFilter);
|
|
485
|
+
// Track tunnel clients — cloudflared proxies locally so req.ip is always
|
|
486
|
+
// 127.0.0.1; detect tunnel traffic via Cf-Connecting-Ip header instead.
|
|
487
|
+
if (req.headers['cf-connecting-ip']) {
|
|
488
|
+
this.remoteSseClients.add(reply);
|
|
489
|
+
}
|
|
463
490
|
// Send initial state
|
|
464
491
|
// Use light state for SSE init to avoid sending 2MB+ terminal buffers
|
|
465
492
|
// Buffers are fetched on-demand when switching tabs
|
|
@@ -476,6 +503,7 @@ export class WebServer extends EventEmitter {
|
|
|
476
503
|
}
|
|
477
504
|
req.raw.on('close', () => {
|
|
478
505
|
this.sseClients.delete(reply);
|
|
506
|
+
this.remoteSseClients.delete(reply);
|
|
479
507
|
this.backpressuredClients.delete(reply);
|
|
480
508
|
});
|
|
481
509
|
});
|
|
@@ -1625,6 +1653,7 @@ export class WebServer extends EventEmitter {
|
|
|
1625
1653
|
}
|
|
1626
1654
|
catch {
|
|
1627
1655
|
this.sseClients.delete(reply);
|
|
1656
|
+
this.remoteSseClients.delete(reply);
|
|
1628
1657
|
}
|
|
1629
1658
|
}
|
|
1630
1659
|
// Optimized: send pre-formatted SSE message to a client
|
|
@@ -1644,7 +1673,8 @@ export class WebServer extends EventEmitter {
|
|
|
1644
1673
|
// Client may have missed terminal data during backpressure.
|
|
1645
1674
|
// Tell it to reload the active session's buffer to recover.
|
|
1646
1675
|
try {
|
|
1647
|
-
|
|
1676
|
+
const drainPadding = this._isTunnelActive ? SSE_PADDING : '';
|
|
1677
|
+
reply.raw.write(`event: ${SseEvent.SessionNeedsRefresh}\ndata: {}\n\n${drainPadding}`);
|
|
1648
1678
|
}
|
|
1649
1679
|
catch {
|
|
1650
1680
|
/* client gone */
|
|
@@ -1654,6 +1684,7 @@ export class WebServer extends EventEmitter {
|
|
|
1654
1684
|
}
|
|
1655
1685
|
catch {
|
|
1656
1686
|
this.sseClients.delete(reply);
|
|
1687
|
+
this.remoteSseClients.delete(reply);
|
|
1657
1688
|
this.backpressuredClients.delete(reply);
|
|
1658
1689
|
}
|
|
1659
1690
|
}
|
|
@@ -1671,9 +1702,12 @@ export class WebServer extends EventEmitter {
|
|
|
1671
1702
|
this.cachedSessionsList = null;
|
|
1672
1703
|
}
|
|
1673
1704
|
// Performance optimization: serialize JSON once for all clients.
|
|
1674
|
-
// Only append Cloudflare tunnel padding
|
|
1675
|
-
//
|
|
1676
|
-
|
|
1705
|
+
// Only append Cloudflare tunnel padding for latency-sensitive events —
|
|
1706
|
+
// Recovery events need immediate proxy flush; low-frequency metadata events
|
|
1707
|
+
// (session:created, ralph:*, respawn:*, etc.) don't need padding.
|
|
1708
|
+
// Note: session:terminal has its own padding in flushSessionTerminalBatch().
|
|
1709
|
+
const needsPadding = this._isTunnelActive && event === SseEvent.SessionNeedsRefresh;
|
|
1710
|
+
const padding = needsPadding ? SSE_PADDING : '';
|
|
1677
1711
|
let message;
|
|
1678
1712
|
try {
|
|
1679
1713
|
message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n` + padding;
|
|
@@ -1683,10 +1717,33 @@ export class WebServer extends EventEmitter {
|
|
|
1683
1717
|
console.error(`[Server] Failed to serialize SSE event "${event}":`, err);
|
|
1684
1718
|
return;
|
|
1685
1719
|
}
|
|
1686
|
-
|
|
1720
|
+
// Extract sessionId from event data for subscription filtering.
|
|
1721
|
+
const eventSessionId = this.extractSessionId(event, data);
|
|
1722
|
+
for (const [client, filter] of this.sseClients) {
|
|
1723
|
+
// No filter (null) = receive everything. Otherwise, skip if event is
|
|
1724
|
+
// session-scoped and the session isn't in the client's subscription set.
|
|
1725
|
+
if (filter && eventSessionId && !filter.has(eventSessionId))
|
|
1726
|
+
continue;
|
|
1687
1727
|
this.sendSSEPreformatted(client, message);
|
|
1688
1728
|
}
|
|
1689
1729
|
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Extract the session ID from an event's data payload for subscription filtering.
|
|
1732
|
+
* Returns the sessionId string if the event is session-scoped, or null for global events.
|
|
1733
|
+
*/
|
|
1734
|
+
extractSessionId(event, data) {
|
|
1735
|
+
if (data == null || typeof data !== 'object')
|
|
1736
|
+
return null;
|
|
1737
|
+
const record = data;
|
|
1738
|
+
// Most session-scoped events use `sessionId`
|
|
1739
|
+
if (typeof record.sessionId === 'string')
|
|
1740
|
+
return record.sessionId;
|
|
1741
|
+
// Session lifecycle events (session:*) use `id` from the session state object
|
|
1742
|
+
if (typeof record.id === 'string' && event.startsWith('session:'))
|
|
1743
|
+
return record.id;
|
|
1744
|
+
// No session ID found — treat as global event (sent to all clients)
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1690
1747
|
// Batch terminal data for better performance (60fps)
|
|
1691
1748
|
// Uses per-session timers with adaptive intervals to prevent thundering herd:
|
|
1692
1749
|
// each session flushes independently rather than all sessions flushing in one burst.
|
|
@@ -1759,8 +1816,14 @@ export class WebServer extends EventEmitter {
|
|
|
1759
1816
|
// Fast path: build SSE message directly without JSON.stringify on wrapper object.
|
|
1760
1817
|
// Only the terminal data string needs escaping; sessionId is a UUID (safe to template).
|
|
1761
1818
|
const escapedData = JSON.stringify(syncData);
|
|
1762
|
-
|
|
1763
|
-
|
|
1819
|
+
// Append tunnel padding for immediate Cloudflare proxy flush —
|
|
1820
|
+
// terminal data is high-frequency and latency-sensitive.
|
|
1821
|
+
const padding = this._isTunnelActive ? SSE_PADDING : '';
|
|
1822
|
+
const message = `event: session:terminal\ndata: {"id":"${sessionId}","data":${escapedData}}\n\n` + padding;
|
|
1823
|
+
for (const [client, filter] of this.sseClients) {
|
|
1824
|
+
// Skip clients that have a session filter and aren't subscribed to this session
|
|
1825
|
+
if (filter && !filter.has(sessionId))
|
|
1826
|
+
continue;
|
|
1764
1827
|
this.sendSSEPreformatted(client, message);
|
|
1765
1828
|
}
|
|
1766
1829
|
}
|
|
@@ -1909,7 +1972,7 @@ export class WebServer extends EventEmitter {
|
|
|
1909
1972
|
*/
|
|
1910
1973
|
cleanupDeadSSEClients() {
|
|
1911
1974
|
const deadClients = [];
|
|
1912
|
-
for (const client of this.sseClients) {
|
|
1975
|
+
for (const [client] of this.sseClients) {
|
|
1913
1976
|
try {
|
|
1914
1977
|
// Check if the underlying socket is still writable
|
|
1915
1978
|
const socket = client.raw.socket;
|
|
@@ -1932,6 +1995,7 @@ export class WebServer extends EventEmitter {
|
|
|
1932
1995
|
// Remove dead clients
|
|
1933
1996
|
for (const client of deadClients) {
|
|
1934
1997
|
this.sseClients.delete(client);
|
|
1998
|
+
this.remoteSseClients.delete(client);
|
|
1935
1999
|
this.backpressuredClients.delete(client);
|
|
1936
2000
|
}
|
|
1937
2001
|
if (deadClients.length > 0) {
|
|
@@ -1991,7 +2055,7 @@ export class WebServer extends EventEmitter {
|
|
|
1991
2055
|
// Start token recording timer (every 5 minutes for long-running sessions)
|
|
1992
2056
|
this.cleanup.setInterval(() => {
|
|
1993
2057
|
this.recordPeriodicTokenUsage();
|
|
1994
|
-
},
|
|
2058
|
+
}, INACTIVITY_TIMEOUT_MS, { description: 'periodic token recording' });
|
|
1995
2059
|
// Start subagent watcher for Claude Code background agent visibility (if enabled)
|
|
1996
2060
|
if (await this.isSubagentTrackingEnabled()) {
|
|
1997
2061
|
subagentWatcher.start();
|
|
@@ -2247,7 +2311,7 @@ export class WebServer extends EventEmitter {
|
|
|
2247
2311
|
// Dispose all managed timers (intervals + resettable timeouts)
|
|
2248
2312
|
this.cleanup.dispose();
|
|
2249
2313
|
// Gracefully close all SSE connections before clearing
|
|
2250
|
-
for (const client of this.sseClients) {
|
|
2314
|
+
for (const [client] of this.sseClients) {
|
|
2251
2315
|
try {
|
|
2252
2316
|
// Send a final event to notify clients of shutdown
|
|
2253
2317
|
this.sendSSE(client, 'server:shutdown', { reason: 'Server stopping' });
|
|
@@ -2258,6 +2322,7 @@ export class WebServer extends EventEmitter {
|
|
|
2258
2322
|
}
|
|
2259
2323
|
}
|
|
2260
2324
|
this.sseClients.clear();
|
|
2325
|
+
this.remoteSseClients.clear();
|
|
2261
2326
|
this.backpressuredClients.clear();
|
|
2262
2327
|
// Clear per-session batch timers
|
|
2263
2328
|
for (const timer of this.terminalBatchTimers.values()) {
|