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.
Files changed (103) hide show
  1. package/README.md +4 -0
  2. package/dist/ai-checker-base.d.ts.map +1 -1
  3. package/dist/ai-checker-base.js +2 -2
  4. package/dist/ai-checker-base.js.map +1 -1
  5. package/dist/ai-idle-checker.d.ts.map +1 -1
  6. package/dist/ai-idle-checker.js +5 -5
  7. package/dist/ai-idle-checker.js.map +1 -1
  8. package/dist/ai-plan-checker.d.ts.map +1 -1
  9. package/dist/ai-plan-checker.js +5 -5
  10. package/dist/ai-plan-checker.js.map +1 -1
  11. package/dist/config/ai-defaults.d.ts +20 -4
  12. package/dist/config/ai-defaults.d.ts.map +1 -1
  13. package/dist/config/ai-defaults.js +32 -4
  14. package/dist/config/ai-defaults.js.map +1 -1
  15. package/dist/config/server-timing.d.ts +6 -0
  16. package/dist/config/server-timing.d.ts.map +1 -1
  17. package/dist/config/server-timing.js +9 -0
  18. package/dist/config/server-timing.js.map +1 -1
  19. package/dist/file-stream-manager.d.ts.map +1 -1
  20. package/dist/file-stream-manager.js +3 -2
  21. package/dist/file-stream-manager.js.map +1 -1
  22. package/dist/ralph-config.d.ts.map +1 -1
  23. package/dist/ralph-config.js +3 -4
  24. package/dist/ralph-config.js.map +1 -1
  25. package/dist/ralph-stall-detector.d.ts.map +1 -1
  26. package/dist/ralph-stall-detector.js +2 -1
  27. package/dist/ralph-stall-detector.js.map +1 -1
  28. package/dist/ralph-tracker.d.ts.map +1 -1
  29. package/dist/ralph-tracker.js +17 -22
  30. package/dist/ralph-tracker.js.map +1 -1
  31. package/dist/respawn-controller.d.ts.map +1 -1
  32. package/dist/respawn-controller.js +5 -5
  33. package/dist/respawn-controller.js.map +1 -1
  34. package/dist/run-summary.d.ts.map +1 -1
  35. package/dist/run-summary.js +2 -1
  36. package/dist/run-summary.js.map +1 -1
  37. package/dist/session.d.ts.map +1 -1
  38. package/dist/session.js +3 -6
  39. package/dist/session.js.map +1 -1
  40. package/dist/subagent-watcher.d.ts.map +1 -1
  41. package/dist/subagent-watcher.js +2 -1
  42. package/dist/subagent-watcher.js.map +1 -1
  43. package/dist/task-tracker.d.ts.map +1 -1
  44. package/dist/task-tracker.js +2 -1
  45. package/dist/task-tracker.js.map +1 -1
  46. package/dist/types/plan.d.ts +2 -4
  47. package/dist/types/plan.d.ts.map +1 -1
  48. package/dist/types/plan.js +1 -1
  49. package/dist/utils/index.d.ts +2 -2
  50. package/dist/utils/index.d.ts.map +1 -1
  51. package/dist/utils/index.js +2 -2
  52. package/dist/utils/index.js.map +1 -1
  53. package/dist/utils/regex-patterns.d.ts +5 -0
  54. package/dist/utils/regex-patterns.d.ts.map +1 -1
  55. package/dist/utils/regex-patterns.js +11 -0
  56. package/dist/utils/regex-patterns.js.map +1 -1
  57. package/dist/utils/token-validation.d.ts +1 -27
  58. package/dist/utils/token-validation.d.ts.map +1 -1
  59. package/dist/utils/token-validation.js +1 -47
  60. package/dist/utils/token-validation.js.map +1 -1
  61. package/dist/web/ports/event-port.d.ts +1 -0
  62. package/dist/web/ports/event-port.d.ts.map +1 -1
  63. package/dist/web/public/api-client.js.gz +0 -0
  64. package/dist/web/public/app.js +63 -28
  65. package/dist/web/public/app.js.br +0 -0
  66. package/dist/web/public/app.js.gz +0 -0
  67. package/dist/web/public/constants.js +2 -2
  68. package/dist/web/public/constants.js.br +0 -0
  69. package/dist/web/public/constants.js.gz +0 -0
  70. package/dist/web/public/index.html +3 -0
  71. package/dist/web/public/index.html.br +0 -0
  72. package/dist/web/public/index.html.gz +0 -0
  73. package/dist/web/public/keyboard-accessory.js.gz +0 -0
  74. package/dist/web/public/mobile-handlers.js.gz +0 -0
  75. package/dist/web/public/mobile.css.gz +0 -0
  76. package/dist/web/public/notification-manager.js.gz +0 -0
  77. package/dist/web/public/ralph-wizard.js.gz +0 -0
  78. package/dist/web/public/styles.css +1 -1
  79. package/dist/web/public/styles.css.br +0 -0
  80. package/dist/web/public/styles.css.gz +0 -0
  81. package/dist/web/public/subagent-windows.js +118 -18
  82. package/dist/web/public/subagent-windows.js.br +0 -0
  83. package/dist/web/public/subagent-windows.js.gz +0 -0
  84. package/dist/web/public/sw.js.gz +0 -0
  85. package/dist/web/public/upload.html.gz +0 -0
  86. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  87. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  88. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-zerolag-input.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm.css.gz +0 -0
  91. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  92. package/dist/web/public/voice-input.js.gz +0 -0
  93. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  94. package/dist/web/routes/respawn-routes.js +6 -5
  95. package/dist/web/routes/respawn-routes.js.map +1 -1
  96. package/dist/web/routes/system-routes.d.ts.map +1 -1
  97. package/dist/web/routes/system-routes.js +16 -0
  98. package/dist/web/routes/system-routes.js.map +1 -1
  99. package/dist/web/server.d.ts +12 -0
  100. package/dist/web/server.d.ts.map +1 -1
  101. package/dist/web/server.js +79 -14
  102. package/dist/web/server.js.map +1 -1
  103. package/package.json +5 -3
@@ -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
- sseClients = new Set();
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: 5 * 60 * 1000, // 5 minutes - auto-expire stale session timing data
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.add(reply);
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
- reply.raw.write(`event: ${SseEvent.SessionNeedsRefresh}\ndata: {}\n\n`);
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 when tunnel is actually active
1675
- // direct/Tailscale clients don't need 8KB padding on every event.
1676
- const padding = this._isTunnelActive ? SSE_PADDING : '';
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
- for (const client of this.sseClients) {
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
- const message = `event: session:terminal\ndata: {"id":"${sessionId}","data":${escapedData}}\n\n`;
1763
- for (const client of this.sseClients) {
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
- }, 5 * 60 * 1000, { description: 'periodic token recording' });
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()) {