@supen-ai/cli 1.4.0 → 1.4.2

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 (205) hide show
  1. package/README.md +1 -1
  2. package/daemon/dist/agent-sdk/app-server-stream.js +5 -5
  3. package/daemon/dist/agent-sdk/app-server-stream.js.map +1 -1
  4. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts +3 -2
  5. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -1
  6. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +72 -49
  7. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -1
  8. package/daemon/dist/agent-sdk/drivers/driver.d.ts +8 -8
  9. package/daemon/dist/agent-sdk/drivers/driver.d.ts.map +1 -1
  10. package/daemon/dist/agent-sdk/index.d.ts +4 -4
  11. package/daemon/dist/agent-sdk/index.d.ts.map +1 -1
  12. package/daemon/dist/agent-sdk/index.js +2 -2
  13. package/daemon/dist/agent-sdk/index.js.map +1 -1
  14. package/daemon/dist/agent-sdk/intelligence/contracts.d.ts +2 -2
  15. package/daemon/dist/agent-sdk/intelligence/contracts.d.ts.map +1 -1
  16. package/daemon/dist/agent-sdk/memory/subsystem.d.ts +1 -1
  17. package/daemon/dist/agent-sdk/memory/subsystem.d.ts.map +1 -1
  18. package/daemon/dist/agent-sdk/{session-events.d.ts → thread-events.d.ts} +18 -18
  19. package/daemon/dist/agent-sdk/thread-events.d.ts.map +1 -0
  20. package/daemon/dist/agent-sdk/{session-events.js → thread-events.js} +15 -15
  21. package/daemon/dist/agent-sdk/thread-events.js.map +1 -0
  22. package/daemon/dist/agent-sdk/{session-manager.d.ts → thread-manager.d.ts} +9 -9
  23. package/daemon/dist/agent-sdk/thread-manager.d.ts.map +1 -0
  24. package/daemon/dist/agent-sdk/{session-manager.js → thread-manager.js} +7 -7
  25. package/daemon/dist/agent-sdk/thread-manager.js.map +1 -0
  26. package/daemon/dist/agent-sdk/types.d.ts +18 -18
  27. package/daemon/dist/agent-sdk/types.d.ts.map +1 -1
  28. package/daemon/dist/automation-event-listener.js +6 -6
  29. package/daemon/dist/automation-event-listener.js.map +1 -1
  30. package/daemon/dist/automation-runner.d.ts +1 -1
  31. package/daemon/dist/automation-runner.d.ts.map +1 -1
  32. package/daemon/dist/automation-runner.js +24 -24
  33. package/daemon/dist/automation-runner.js.map +1 -1
  34. package/daemon/dist/autonomy/memory-rules.d.ts +2 -2
  35. package/daemon/dist/autonomy/memory-rules.d.ts.map +1 -1
  36. package/daemon/dist/autonomy/memory-rules.js +1 -1
  37. package/daemon/dist/autonomy/memory-rules.js.map +1 -1
  38. package/daemon/dist/autonomy/session-autonomy.d.ts +4 -4
  39. package/daemon/dist/autonomy/session-autonomy.d.ts.map +1 -1
  40. package/daemon/dist/autonomy/session-autonomy.js +5 -5
  41. package/daemon/dist/autonomy/session-autonomy.js.map +1 -1
  42. package/daemon/dist/bin/mcp-os.js +1 -1
  43. package/daemon/dist/bin/mcp-os.js.map +1 -1
  44. package/daemon/dist/bin/mcp-scheduler.js +1 -1
  45. package/daemon/dist/bin/mcp-scheduler.js.map +1 -1
  46. package/daemon/dist/bin/supen-sys.js +1 -1
  47. package/daemon/dist/bin/supen-sys.js.map +1 -1
  48. package/daemon/dist/bootstrap/hub-bootstrap.js +1 -1
  49. package/daemon/dist/bootstrap/hub-bootstrap.js.map +1 -1
  50. package/daemon/dist/channels/http-routes.d.ts.map +1 -1
  51. package/daemon/dist/channels/http-routes.js +54 -60
  52. package/daemon/dist/channels/http-routes.js.map +1 -1
  53. package/daemon/dist/channels/http.js +2 -2
  54. package/daemon/dist/channels/http.js.map +1 -1
  55. package/daemon/dist/channels/index.d.ts +0 -1
  56. package/daemon/dist/channels/index.d.ts.map +1 -1
  57. package/daemon/dist/channels/index.js +0 -2
  58. package/daemon/dist/channels/index.js.map +1 -1
  59. package/daemon/dist/channels/registry.d.ts +1 -1
  60. package/daemon/dist/channels/registry.d.ts.map +1 -1
  61. package/daemon/dist/commands/builtin.d.ts +1 -1
  62. package/daemon/dist/commands/builtin.d.ts.map +1 -1
  63. package/daemon/dist/commands/builtin.js +2 -2
  64. package/daemon/dist/commands/builtin.js.map +1 -1
  65. package/daemon/dist/commands/catalog.js +3 -3
  66. package/daemon/dist/commands/catalog.js.map +1 -1
  67. package/daemon/dist/core/config.d.ts +0 -2
  68. package/daemon/dist/core/config.d.ts.map +1 -1
  69. package/daemon/dist/core/config.js +0 -5
  70. package/daemon/dist/core/config.js.map +1 -1
  71. package/daemon/dist/core/control-commands.d.ts +3 -3
  72. package/daemon/dist/core/control-commands.d.ts.map +1 -1
  73. package/daemon/dist/core/control-commands.js +6 -6
  74. package/daemon/dist/core/control-commands.js.map +1 -1
  75. package/daemon/dist/core/control-log.d.ts +4 -4
  76. package/daemon/dist/core/control-log.d.ts.map +1 -1
  77. package/daemon/dist/core/control-log.js +12 -12
  78. package/daemon/dist/core/control-log.js.map +1 -1
  79. package/daemon/dist/core/cortex.d.ts +4 -4
  80. package/daemon/dist/core/cortex.d.ts.map +1 -1
  81. package/daemon/dist/core/cortex.js +98 -117
  82. package/daemon/dist/core/cortex.js.map +1 -1
  83. package/daemon/dist/core/dispatcher.d.ts +3 -3
  84. package/daemon/dist/core/dispatcher.js +18 -18
  85. package/daemon/dist/core/dispatcher.js.map +1 -1
  86. package/daemon/dist/core/gateway-protocol.d.ts +0 -1
  87. package/daemon/dist/core/gateway-protocol.d.ts.map +1 -1
  88. package/daemon/dist/core/gateway.d.ts +3 -3
  89. package/daemon/dist/core/gateway.d.ts.map +1 -1
  90. package/daemon/dist/core/gateway.js +26 -26
  91. package/daemon/dist/core/gateway.js.map +1 -1
  92. package/daemon/dist/core/hub-snapshot.d.ts +1 -1
  93. package/daemon/dist/core/hub-snapshot.d.ts.map +1 -1
  94. package/daemon/dist/core/hub-snapshot.js +1 -1
  95. package/daemon/dist/core/hub-snapshot.js.map +1 -1
  96. package/daemon/dist/core/pairing.d.ts +2 -2
  97. package/daemon/dist/core/pairing.js +3 -3
  98. package/daemon/dist/core/pairing.js.map +1 -1
  99. package/daemon/dist/core/store.d.ts +38 -38
  100. package/daemon/dist/core/store.d.ts.map +1 -1
  101. package/daemon/dist/core/store.js +285 -289
  102. package/daemon/dist/core/store.js.map +1 -1
  103. package/daemon/dist/core/task-artifacts.d.ts +4 -4
  104. package/daemon/dist/core/task-artifacts.d.ts.map +1 -1
  105. package/daemon/dist/core/task-artifacts.js +10 -10
  106. package/daemon/dist/core/task-artifacts.js.map +1 -1
  107. package/daemon/dist/core/thread-context.d.ts +76 -0
  108. package/daemon/dist/core/thread-context.d.ts.map +1 -0
  109. package/daemon/dist/core/thread-context.js +308 -0
  110. package/daemon/dist/core/thread-context.js.map +1 -0
  111. package/daemon/dist/core/types.d.ts +28 -28
  112. package/daemon/dist/core/types.d.ts.map +1 -1
  113. package/daemon/dist/core/utils.js +1 -1
  114. package/daemon/dist/core/utils.js.map +1 -1
  115. package/daemon/dist/http/router.d.ts +2 -2
  116. package/daemon/dist/http/router.d.ts.map +1 -1
  117. package/daemon/dist/http/router.js +5 -5
  118. package/daemon/dist/http/router.js.map +1 -1
  119. package/daemon/dist/http/routes/agents.js +3 -3
  120. package/daemon/dist/http/routes/agents.js.map +1 -1
  121. package/daemon/dist/http/routes/automations.d.ts +2 -2
  122. package/daemon/dist/http/routes/automations.d.ts.map +1 -1
  123. package/daemon/dist/http/routes/automations.js +23 -23
  124. package/daemon/dist/http/routes/automations.js.map +1 -1
  125. package/daemon/dist/http/routes/chat-input.d.ts +1 -1
  126. package/daemon/dist/http/routes/chat-input.d.ts.map +1 -1
  127. package/daemon/dist/http/routes/chat-input.js +2 -2
  128. package/daemon/dist/http/routes/chat-input.js.map +1 -1
  129. package/daemon/dist/http/routes/plugins.d.ts.map +1 -1
  130. package/daemon/dist/http/routes/plugins.js +6 -74
  131. package/daemon/dist/http/routes/plugins.js.map +1 -1
  132. package/daemon/dist/http/routes/rpc.d.ts +3 -3
  133. package/daemon/dist/http/routes/rpc.d.ts.map +1 -1
  134. package/daemon/dist/http/routes/rpc.js +93 -92
  135. package/daemon/dist/http/routes/rpc.js.map +1 -1
  136. package/daemon/dist/http/routes/system.d.ts +8 -7
  137. package/daemon/dist/http/routes/system.d.ts.map +1 -1
  138. package/daemon/dist/http/routes/system.js +225 -111
  139. package/daemon/dist/http/routes/system.js.map +1 -1
  140. package/daemon/dist/http/routes/threads.d.ts +11 -0
  141. package/daemon/dist/http/routes/threads.d.ts.map +1 -0
  142. package/daemon/dist/http/routes/{sessions.js → threads.js} +158 -158
  143. package/daemon/dist/http/routes/threads.js.map +1 -0
  144. package/daemon/dist/http/stream.d.ts +2 -2
  145. package/daemon/dist/http/stream.d.ts.map +1 -1
  146. package/daemon/dist/http/stream.js +3 -3
  147. package/daemon/dist/http/stream.js.map +1 -1
  148. package/daemon/dist/http/thread-title.d.ts +1 -1
  149. package/daemon/dist/http/thread-title.d.ts.map +1 -1
  150. package/daemon/dist/http/thread-title.js +6 -6
  151. package/daemon/dist/http/thread-title.js.map +1 -1
  152. package/daemon/dist/http/websocket.d.ts +2 -2
  153. package/daemon/dist/http/websocket.d.ts.map +1 -1
  154. package/daemon/dist/http/websocket.js +11 -11
  155. package/daemon/dist/http/websocket.js.map +1 -1
  156. package/daemon/dist/index.d.ts +3 -3
  157. package/daemon/dist/index.d.ts.map +1 -1
  158. package/daemon/dist/index.js +65 -81
  159. package/daemon/dist/index.js.map +1 -1
  160. package/daemon/dist/mcp/aggregate-config.d.ts +1 -1
  161. package/daemon/dist/mcp/index.js +1 -1
  162. package/daemon/dist/mcp/index.js.map +1 -1
  163. package/daemon/dist/mcp/tools.d.ts +1 -1
  164. package/daemon/dist/mcp/tools.js +1 -1
  165. package/daemon/dist/plugins/hub.d.ts +2 -8
  166. package/daemon/dist/plugins/hub.d.ts.map +1 -1
  167. package/daemon/dist/plugins/hub.js +63 -214
  168. package/daemon/dist/plugins/hub.js.map +1 -1
  169. package/daemon/dist/plugins/types.d.ts +10 -0
  170. package/daemon/dist/plugins/types.d.ts.map +1 -1
  171. package/daemon/dist/sub-agent.d.ts +3 -3
  172. package/daemon/dist/sub-agent.d.ts.map +1 -1
  173. package/daemon/dist/sub-agent.js +8 -8
  174. package/daemon/dist/sub-agent.js.map +1 -1
  175. package/daemon/dist/sync/supabase-sync.js +18 -18
  176. package/daemon/dist/sync/supabase-sync.js.map +1 -1
  177. package/daemon/dist/task-executor.js +1 -1
  178. package/daemon/dist/task-executor.js.map +1 -1
  179. package/daemon/dist/tools/shell.js +1 -1
  180. package/daemon/dist/tools/shell.js.map +1 -1
  181. package/daemon/dist/tools/types.d.ts +1 -1
  182. package/daemon/dist/tools/types.d.ts.map +1 -1
  183. package/daemon/package.json +1 -1
  184. package/dist/computer.js +1 -1
  185. package/dist/index.js +1 -1
  186. package/package.json +1 -1
  187. package/daemon/dist/acp-client.d.ts +0 -42
  188. package/daemon/dist/acp-client.d.ts.map +0 -1
  189. package/daemon/dist/acp-client.js +0 -149
  190. package/daemon/dist/acp-client.js.map +0 -1
  191. package/daemon/dist/acp-types.d.ts +0 -98
  192. package/daemon/dist/acp-types.d.ts.map +0 -1
  193. package/daemon/dist/acp-types.js +0 -2
  194. package/daemon/dist/acp-types.js.map +0 -1
  195. package/daemon/dist/agent-sdk/session-events.d.ts.map +0 -1
  196. package/daemon/dist/agent-sdk/session-events.js.map +0 -1
  197. package/daemon/dist/agent-sdk/session-manager.d.ts.map +0 -1
  198. package/daemon/dist/agent-sdk/session-manager.js.map +0 -1
  199. package/daemon/dist/channels/acp.d.ts +0 -23
  200. package/daemon/dist/channels/acp.d.ts.map +0 -1
  201. package/daemon/dist/channels/acp.js +0 -915
  202. package/daemon/dist/channels/acp.js.map +0 -1
  203. package/daemon/dist/http/routes/sessions.d.ts +0 -11
  204. package/daemon/dist/http/routes/sessions.d.ts.map +0 -1
  205. package/daemon/dist/http/routes/sessions.js.map +0 -1
@@ -14,7 +14,7 @@ import { collectOsTelemetryFields } from '../../core/os-info.js';
14
14
  import { getCodingCliStatusResponse, startCodingCliStatusCache, } from '../../core/coding-cli-status-cache.js';
15
15
  import { getGatewayInstance, getLlmToken } from '../../core/gateway.js';
16
16
  import { normalizeGatewayUplinkUrl, readGatewayConfig, writeGatewayConfig, } from '../../core/gateway-config.js';
17
- import { ensureSession, getAllAgents, getDailyUsage, getGlobalUsage, getSessionsForAgent, getSessionUiEvents, updateSessionBackendDriverId, updateSessionSdkId, } from '../../core/store.js';
17
+ import { ensureThread, getAllAgents, getDailyUsage, getGlobalUsage, getThreadsForAgent, getThreadUiEvents, updateThreadBackendDriverId, updateThreadSdkSessionId, } from '../../core/store.js';
18
18
  import { listMcpEnvKeys, listMcpServers } from '../../mcp/default-servers.js';
19
19
  import { getMcpEnvOverrides, updateMcpEnvOverrides } from '../../mcp/settings.js';
20
20
  import { getMcpManager } from '../../mcp/index.js';
@@ -23,14 +23,18 @@ import { buildDaemonOpenApiSpec } from '../../channels/http-routes.js';
23
23
  import { writeJson, writeProtocolError, readJsonBody } from '../response.js';
24
24
  import { listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
25
25
  import { projectThreadRuntimeState } from '../../core/thread-runtime-state.js';
26
+ import { attachThreadContextToChunk, readThreadContext, } from '../../core/thread-context.js';
26
27
  import { addThreadStreamClient, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
27
28
  const DEFAULT_TOKEN_TTL_MINUTES = 20;
28
29
  const SPACE_LOG_EVENT_LIMIT = 200;
29
30
  const SPACE_LOG_STREAM_POLL_MS = 1000;
30
31
  const SPACE_LOG_STREAM_PING_MS = 15000;
31
32
  const MIRRORED_THREAD_LIMIT = 80;
32
- const MIRRORED_THREAD_HISTORY_LIMIT = 100;
33
+ const MIRRORED_THREAD_HISTORY_LIMIT = 200;
33
34
  const MIRRORED_THREAD_HISTORY_MAX_BYTES = 256 * 1024 * 1024;
35
+ const MIRRORED_THREAD_HISTORY_FAST_MAX_BYTES = 8 * 1024 * 1024;
36
+ const MIRRORED_THREAD_HISTORY_FAST_MAX_LINES = 2_000;
37
+ const MIRRORED_THREAD_JSONL_CACHE_LIMIT = 64;
34
38
  const MIRRORED_THREAD_PROJECTION_VERSION = 2;
35
39
  const MIRRORED_THREAD_TEXT_LIMIT = 48_000;
36
40
  const MIRRORED_THREAD_INLINE_DATA_URL_LIMIT = 120_000;
@@ -113,6 +117,9 @@ function truncateStreamDelta(value, max = 160) {
113
117
  function currentSpaceId() {
114
118
  return (process.env.SUPEN_SPACE_ID || '').trim() || 'local';
115
119
  }
120
+ const recentJsonlLineCache = new Map();
121
+ const recentCodexHistoryLineCache = new Map();
122
+ const mirroredFileTextCache = new Map();
116
123
  function summarizeStreamEvent(raw) {
117
124
  const inner = raw.event && typeof raw.event === 'object'
118
125
  ? raw.event
@@ -383,13 +390,13 @@ function extractQuotaEvent(event) {
383
390
  };
384
391
  }
385
392
  function readLatestSpaceQuotaStatus() {
386
- const sessions = getAllAgents()
387
- .flatMap((agent) => getSessionsForAgent(agent.agent_id).map((session) => ({
393
+ const threads = getAllAgents()
394
+ .flatMap((agent) => getThreadsForAgent(agent.agent_id).map((thread) => ({
388
395
  agent_id: agent.agent_id,
389
- session_id: session.session_id,
396
+ thread_id: thread.thread_id,
390
397
  })));
391
- const latest = sessions
392
- .flatMap((session) => getSessionUiEvents(session.agent_id, session.session_id, 200)
398
+ const latest = threads
399
+ .flatMap((thread) => getThreadUiEvents(thread.agent_id, thread.thread_id, 200)
393
400
  .map((event) => extractQuotaEvent(event)))
394
401
  .filter((entry) => Boolean(entry))
395
402
  .sort((a, b) => a.updated_at.localeCompare(b.updated_at))
@@ -413,17 +420,84 @@ function parseJsonLine(line) {
413
420
  return null;
414
421
  }
415
422
  }
416
- export function readRecentJsonlLines(filePath, maxBytes = MIRRORED_THREAD_HISTORY_MAX_BYTES) {
417
- const safeMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), MIRRORED_THREAD_HISTORY_MAX_BYTES));
418
- const size = fs.statSync(filePath).size;
419
- if (size <= safeMaxBytes) {
420
- return fs.readFileSync(filePath, 'utf-8').split(/\r?\n/).filter(Boolean);
423
+ function jsonlCacheKey(parts) {
424
+ return parts.map((part) => part === undefined ? '' : String(part)).join('\u0000');
425
+ }
426
+ function getCachedJsonlLines(cache, key, stat) {
427
+ const cached = cache.get(key);
428
+ if (!cached)
429
+ return null;
430
+ if (cached.size !== stat.size || cached.mtimeMs !== stat.mtimeMs) {
431
+ cache.delete(key);
432
+ return null;
433
+ }
434
+ cached.accessedAt = Date.now();
435
+ return cached.lines;
436
+ }
437
+ function setCachedJsonlLines(cache, key, entry) {
438
+ if (cache.size >= MIRRORED_THREAD_JSONL_CACHE_LIMIT && !cache.has(key)) {
439
+ let oldestKey = null;
440
+ let oldestAccess = Number.POSITIVE_INFINITY;
441
+ for (const [candidateKey, candidate] of cache) {
442
+ if (candidate.accessedAt < oldestAccess) {
443
+ oldestAccess = candidate.accessedAt;
444
+ oldestKey = candidateKey;
445
+ }
446
+ }
447
+ if (oldestKey)
448
+ cache.delete(oldestKey);
421
449
  }
450
+ cache.set(key, {
451
+ ...entry,
452
+ accessedAt: Date.now(),
453
+ });
454
+ return entry.lines;
455
+ }
456
+ function getCachedFileText(key, stat) {
457
+ const cached = mirroredFileTextCache.get(key);
458
+ if (!cached)
459
+ return null;
460
+ if (cached.size !== stat.size || cached.mtimeMs !== stat.mtimeMs) {
461
+ mirroredFileTextCache.delete(key);
462
+ return null;
463
+ }
464
+ cached.accessedAt = Date.now();
465
+ return cached.text;
466
+ }
467
+ function setCachedFileText(key, stat, text) {
468
+ if (mirroredFileTextCache.size >= MIRRORED_THREAD_JSONL_CACHE_LIMIT && !mirroredFileTextCache.has(key)) {
469
+ let oldestKey = null;
470
+ let oldestAccess = Number.POSITIVE_INFINITY;
471
+ for (const [candidateKey, candidate] of mirroredFileTextCache) {
472
+ if (candidate.accessedAt < oldestAccess) {
473
+ oldestAccess = candidate.accessedAt;
474
+ oldestKey = candidateKey;
475
+ }
476
+ }
477
+ if (oldestKey)
478
+ mirroredFileTextCache.delete(oldestKey);
479
+ }
480
+ mirroredFileTextCache.set(key, {
481
+ size: stat.size,
482
+ mtimeMs: stat.mtimeMs,
483
+ text,
484
+ accessedAt: Date.now(),
485
+ });
486
+ return text;
487
+ }
488
+ export function readRecentJsonlLines(filePath, maxBytes = MIRRORED_THREAD_HISTORY_FAST_MAX_BYTES) {
489
+ const safeMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), MIRRORED_THREAD_HISTORY_MAX_BYTES));
490
+ const stat = fs.statSync(filePath);
491
+ const cachedKey = jsonlCacheKey(['recent', filePath, safeMaxBytes]);
492
+ const cachedLines = getCachedJsonlLines(recentJsonlLineCache, cachedKey, stat);
493
+ if (cachedLines)
494
+ return cachedLines;
495
+ const size = stat.size;
422
496
  const readLength = Math.min(size, safeMaxBytes);
423
497
  const start = size - readLength;
424
498
  const fd = fs.openSync(filePath, 'r');
425
499
  try {
426
- const buffer = Buffer.alloc(readLength);
500
+ const buffer = Buffer.allocUnsafe(readLength);
427
501
  const bytesRead = fs.readSync(fd, buffer, 0, readLength, start);
428
502
  let text = buffer.subarray(0, bytesRead).toString('utf-8');
429
503
  if (start > 0) {
@@ -432,7 +506,14 @@ export function readRecentJsonlLines(filePath, maxBytes = MIRRORED_THREAD_HISTOR
432
506
  return [];
433
507
  text = text.slice(firstLineEnd + 1);
434
508
  }
435
- return text.split(/\r?\n/).filter(Boolean);
509
+ const lines = text.split(/\r?\n/).filter(Boolean);
510
+ return setCachedJsonlLines(recentJsonlLineCache, cachedKey, {
511
+ filePath,
512
+ size: stat.size,
513
+ mtimeMs: stat.mtimeMs,
514
+ maxBytes: safeMaxBytes,
515
+ lines,
516
+ });
436
517
  }
437
518
  finally {
438
519
  fs.closeSync(fd);
@@ -519,10 +600,15 @@ function codexAppServerEventChunk(method, params = {}) {
519
600
  },
520
601
  };
521
602
  }
522
- function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRRORED_THREAD_HISTORY_MAX_BYTES) {
603
+ function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRRORED_THREAD_HISTORY_FAST_MAX_BYTES) {
523
604
  const safeTargetMessages = Math.max(1, Math.min(Math.floor(targetMessages), 5_000));
524
605
  const safeMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), MIRRORED_THREAD_HISTORY_MAX_BYTES));
525
- const size = fs.statSync(filePath).size;
606
+ const stat = fs.statSync(filePath);
607
+ const cachedKey = jsonlCacheKey(['codex-history', filePath, safeTargetMessages, safeMaxBytes]);
608
+ const cachedLines = getCachedJsonlLines(recentCodexHistoryLineCache, cachedKey, stat);
609
+ if (cachedLines)
610
+ return cachedLines;
611
+ const size = stat.size;
526
612
  const fd = fs.openSync(filePath, 'r');
527
613
  try {
528
614
  let position = size;
@@ -530,7 +616,10 @@ function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRROR
530
616
  let leadingPartial = '';
531
617
  let messageCount = 0;
532
618
  let collected = [];
533
- while (position > 0 && scannedBytes < safeMaxBytes && messageCount < safeTargetMessages) {
619
+ while (position > 0 &&
620
+ scannedBytes < safeMaxBytes &&
621
+ messageCount < safeTargetMessages &&
622
+ collected.length < MIRRORED_THREAD_HISTORY_FAST_MAX_LINES) {
534
623
  const readLength = Math.min(MIRRORED_THREAD_HISTORY_CHUNK_BYTES, position, safeMaxBytes - scannedBytes);
535
624
  position -= readLength;
536
625
  scannedBytes += readLength;
@@ -547,17 +636,25 @@ function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRROR
547
636
  messageCount += historyLines.filter(isCodexVisibleUserHistoryLine).length;
548
637
  collected = [...historyLines, ...collected];
549
638
  }
550
- return collected.filter(Boolean);
639
+ const lines = collected.filter(Boolean);
640
+ return setCachedJsonlLines(recentCodexHistoryLineCache, cachedKey, {
641
+ filePath,
642
+ size: stat.size,
643
+ mtimeMs: stat.mtimeMs,
644
+ maxBytes: safeMaxBytes,
645
+ targetMessages: safeTargetMessages,
646
+ lines,
647
+ });
551
648
  }
552
649
  finally {
553
650
  fs.closeSync(fd);
554
651
  }
555
652
  }
556
653
  function readThreadIndex(limit) {
557
- const indexPath = path.join(localAgentHome(), 'session_index.jsonl');
654
+ const indexPath = path.join(localAgentHome(), 'thread_index.jsonl');
558
655
  if (!fs.existsSync(indexPath))
559
656
  return [];
560
- const lines = fs.readFileSync(indexPath, 'utf-8').split(/\r?\n/).filter(Boolean);
657
+ const lines = readRecentJsonlLines(indexPath);
561
658
  const byId = new Map();
562
659
  for (const line of lines) {
563
660
  const parsed = parseJsonLine(line);
@@ -650,8 +747,8 @@ function readThreadStateEntries(limit) {
650
747
  return [];
651
748
  }
652
749
  }
653
- let mirroredSessionFileIndexCache = null;
654
- function walkSessionFilesUncached(dir, out = new Map()) {
750
+ let mirroredThreadFileIndexCache = null;
751
+ function walkThreadFilesUncached(dir, out = new Map()) {
655
752
  if (!fs.existsSync(dir))
656
753
  return out;
657
754
  let entries;
@@ -664,7 +761,7 @@ function walkSessionFilesUncached(dir, out = new Map()) {
664
761
  for (const entry of entries) {
665
762
  const fullPath = path.join(dir, entry.name);
666
763
  if (entry.isDirectory()) {
667
- walkSessionFilesUncached(fullPath, out);
764
+ walkThreadFilesUncached(fullPath, out);
668
765
  continue;
669
766
  }
670
767
  if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
@@ -675,15 +772,15 @@ function walkSessionFilesUncached(dir, out = new Map()) {
675
772
  }
676
773
  return out;
677
774
  }
678
- function walkSessionFiles(dir) {
775
+ function walkThreadFiles(dir) {
679
776
  const now = Date.now();
680
- if (mirroredSessionFileIndexCache &&
681
- mirroredSessionFileIndexCache.root === dir &&
682
- now - mirroredSessionFileIndexCache.indexedAt <= MIRRORED_THREAD_SESSION_FILE_INDEX_TTL_MS) {
683
- return new Map(mirroredSessionFileIndexCache.files);
777
+ if (mirroredThreadFileIndexCache &&
778
+ mirroredThreadFileIndexCache.root === dir &&
779
+ now - mirroredThreadFileIndexCache.indexedAt <= MIRRORED_THREAD_SESSION_FILE_INDEX_TTL_MS) {
780
+ return new Map(mirroredThreadFileIndexCache.files);
684
781
  }
685
- const files = walkSessionFilesUncached(dir);
686
- mirroredSessionFileIndexCache = {
782
+ const files = walkThreadFilesUncached(dir);
783
+ mirroredThreadFileIndexCache = {
687
784
  root: dir,
688
785
  indexedAt: now,
689
786
  files: new Map(files),
@@ -691,24 +788,33 @@ function walkSessionFiles(dir) {
691
788
  return files;
692
789
  }
693
790
  function readFileHead(filePath, maxBytes = 64 * 1024) {
791
+ const stat = fs.statSync(filePath);
792
+ const cacheKey = jsonlCacheKey(['head', filePath, maxBytes]);
793
+ const cached = getCachedFileText(cacheKey, stat);
794
+ if (cached !== null)
795
+ return cached;
694
796
  const fd = fs.openSync(filePath, 'r');
695
797
  try {
696
798
  const buffer = Buffer.alloc(maxBytes);
697
799
  const bytesRead = fs.readSync(fd, buffer, 0, maxBytes, 0);
698
- return buffer.subarray(0, bytesRead).toString('utf-8');
800
+ return setCachedFileText(cacheKey, stat, buffer.subarray(0, bytesRead).toString('utf-8'));
699
801
  }
700
802
  finally {
701
803
  fs.closeSync(fd);
702
804
  }
703
805
  }
704
806
  function readFileTail(filePath, maxBytes = 256 * 1024) {
807
+ const stat = fs.statSync(filePath);
808
+ const cacheKey = jsonlCacheKey(['tail', filePath, maxBytes]);
809
+ const cached = getCachedFileText(cacheKey, stat);
810
+ if (cached !== null)
811
+ return cached;
705
812
  const fd = fs.openSync(filePath, 'r');
706
813
  try {
707
- const stat = fs.fstatSync(fd);
708
814
  const length = Math.min(maxBytes, stat.size);
709
815
  const buffer = Buffer.alloc(length);
710
816
  fs.readSync(fd, buffer, 0, length, Math.max(0, stat.size - length));
711
- return buffer.toString('utf-8');
817
+ return setCachedFileText(cacheKey, stat, buffer.toString('utf-8'));
712
818
  }
713
819
  finally {
714
820
  fs.closeSync(fd);
@@ -751,7 +857,7 @@ function readThreadWorkspacePath(filePath) {
751
857
  if (!line.trim())
752
858
  continue;
753
859
  const parsed = parseJsonLine(line);
754
- if (parsed?.type !== 'session_meta')
860
+ if (parsed?.type !== 'thread_meta')
755
861
  continue;
756
862
  const payload = parsed.payload && typeof parsed.payload === 'object'
757
863
  ? parsed.payload
@@ -1008,7 +1114,7 @@ function readMirroredThreadStatus(threadId, filePath) {
1008
1114
  return 'running';
1009
1115
  }
1010
1116
  catch {
1011
- // Fall back to the Codex session file status below.
1117
+ // Fall back to the Codex thread file status below.
1012
1118
  }
1013
1119
  return readThreadStatus(filePath);
1014
1120
  }
@@ -1105,18 +1211,19 @@ function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
1105
1211
  const safeLimit = Math.max(1, Math.min(limit, 200));
1106
1212
  const stateEntries = readThreadStateEntries(safeLimit);
1107
1213
  const indexEntries = readThreadIndex(safeLimit);
1108
- const sessionFiles = walkSessionFiles(path.join(localAgentHome(), 'sessions'));
1214
+ const indexEntriesById = new Map(indexEntries.map((entry) => [entry.id, entry]));
1215
+ const threadFiles = walkThreadFiles(path.join(localAgentHome(), 'threads'));
1109
1216
  const projects = new Map();
1110
1217
  if (stateEntries.length > 0) {
1111
1218
  for (const entry of stateEntries) {
1112
- const sessionPath = sessionFiles.get(entry.id) || null;
1113
- if (!sessionPath)
1219
+ const threadPath = threadFiles.get(entry.id) || null;
1220
+ if (!threadPath)
1114
1221
  continue;
1115
- const workspacePath = entry.cwd || readThreadWorkspacePath(sessionPath);
1222
+ const workspacePath = entry.cwd || readThreadWorkspacePath(threadPath);
1116
1223
  if (!isMirrorableCodexWorkspace(workspacePath))
1117
1224
  continue;
1118
1225
  const projectId = workspacePath || 'no-workspace';
1119
- const indexEntry = readThreadIndexEntry(entry.id);
1226
+ const indexEntry = indexEntriesById.get(entry.id) || null;
1120
1227
  const title = indexEntry?.thread_name || entry.title;
1121
1228
  if (isAutomationRunThreadTitle(title))
1122
1229
  continue;
@@ -1130,8 +1237,8 @@ function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
1130
1237
  id: entry.id,
1131
1238
  title,
1132
1239
  updated_at: entry.updated_at,
1133
- status: readMirroredThreadStatus(entry.id, sessionPath),
1134
- session_path: sessionPath,
1240
+ status: readMirroredThreadStatus(entry.id, threadPath),
1241
+ thread_path: threadPath,
1135
1242
  });
1136
1243
  projects.set(projectId, project);
1137
1244
  }
@@ -1144,8 +1251,8 @@ function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
1144
1251
  };
1145
1252
  }
1146
1253
  for (const entry of indexEntries) {
1147
- const sessionPath = sessionFiles.get(entry.id) || null;
1148
- const workspacePath = readThreadWorkspacePath(sessionPath);
1254
+ const threadPath = threadFiles.get(entry.id) || null;
1255
+ const workspacePath = readThreadWorkspacePath(threadPath);
1149
1256
  if (!isMirrorableCodexWorkspace(workspacePath))
1150
1257
  continue;
1151
1258
  if (isAutomationRunThreadTitle(entry.thread_name))
@@ -1161,8 +1268,8 @@ function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
1161
1268
  id: entry.id,
1162
1269
  title: entry.thread_name,
1163
1270
  updated_at: entry.updated_at,
1164
- status: readMirroredThreadStatus(entry.id, sessionPath),
1165
- session_path: sessionPath,
1271
+ status: readMirroredThreadStatus(entry.id, threadPath),
1272
+ thread_path: threadPath,
1166
1273
  });
1167
1274
  projects.set(projectId, project);
1168
1275
  }
@@ -1297,10 +1404,10 @@ function isMirroredGoalContextMessage(text) {
1297
1404
  const trimmed = text.trim();
1298
1405
  return trimmed.startsWith('<goal_context>') && trimmed.endsWith('</goal_context>');
1299
1406
  }
1300
- function mirroredThreadWorkspacePath(sessionPath, stateEntry) {
1407
+ function mirroredThreadWorkspacePath(threadPath, stateEntry) {
1301
1408
  const stateCwd = typeof stateEntry?.cwd === 'string' ? stateEntry.cwd : null;
1302
- const sessionWorkspace = readThreadWorkspacePath(sessionPath);
1303
- for (const candidate of [stateCwd, sessionWorkspace]) {
1409
+ const threadWorkspace = readThreadWorkspacePath(threadPath);
1410
+ for (const candidate of [stateCwd, threadWorkspace]) {
1304
1411
  if (candidate && isMirrorableCodexWorkspace(candidate))
1305
1412
  return candidate;
1306
1413
  }
@@ -1354,17 +1461,17 @@ function collapseCompletedAssistantProgressMessages(input) {
1354
1461
  .map(({ phase: _phase, internal: _internal, ...message }) => message);
1355
1462
  }
1356
1463
  export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY_LIMIT, options = {}) {
1357
- const safeLimit = Math.max(1, Math.min(limit, 500));
1358
- const sessionPath = walkSessionFiles(path.join(localAgentHome(), 'sessions')).get(threadId);
1359
- if (!sessionPath)
1464
+ const safeLimit = Math.max(1, Math.min(limit, 1000));
1465
+ const threadPath = walkThreadFiles(path.join(localAgentHome(), 'threads')).get(threadId);
1466
+ if (!threadPath)
1360
1467
  return null;
1361
1468
  const eventLogThreadId = options.eventLogThreadId || threadId;
1362
1469
  const stateEntry = readThreadStateEntry(threadId);
1363
- const workspacePath = mirroredThreadWorkspacePath(sessionPath, stateEntry);
1470
+ const workspacePath = mirroredThreadWorkspacePath(threadPath, stateEntry);
1364
1471
  const indexEntry = readThreadIndexEntry(threadId);
1365
1472
  const eventLogHead = readThreadEventLogHead(eventLogThreadId);
1366
- const historyLineTarget = Math.min(120, Math.max(3, Math.ceil(safeLimit / 4)));
1367
- const lines = readRecentCodexHistoryLines(sessionPath, historyLineTarget);
1473
+ const historyLineTarget = Math.min(250, Math.max(3, Math.ceil(safeLimit / 4)));
1474
+ const lines = readRecentCodexHistoryLines(threadPath, historyLineTarget);
1368
1475
  const messages = [];
1369
1476
  const events = [];
1370
1477
  let messageIndex = 0;
@@ -1572,10 +1679,15 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
1572
1679
  const collapsedMessages = collapseCompletedAssistantProgressMessages(messages);
1573
1680
  const slicedMessages = collapsedMessages.slice(-safeLimit);
1574
1681
  const slicedEvents = events.slice(-safeLimit * 8);
1575
- const status = readMirroredThreadStatus(eventLogThreadId, sessionPath);
1682
+ const status = readMirroredThreadStatus(eventLogThreadId, threadPath);
1576
1683
  const latestGoalContext = recentGoalContext;
1684
+ const context = readThreadContext({
1685
+ threadId,
1686
+ eventLogThreadId,
1687
+ workspace: workspacePath,
1688
+ });
1577
1689
  return {
1578
- session: {
1690
+ thread: {
1579
1691
  task_id: threadId,
1580
1692
  agent_id: 'codex',
1581
1693
  channel: 'local-agent',
@@ -1600,23 +1712,24 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
1600
1712
  updated_at: latestTimestamp || undefined,
1601
1713
  },
1602
1714
  },
1715
+ context,
1603
1716
  has_more: collapsedMessages.length > safeLimit || lines.length >= historyLineTarget,
1604
1717
  ...(latestGoalContext ? { goal_context: latestGoalContext } : {}),
1605
1718
  };
1606
1719
  }
1607
- function serializeAdoptedMirroredThread(session) {
1720
+ function serializeAdoptedMirroredThread(thread) {
1608
1721
  return {
1609
- id: session.session_id,
1610
- taskId: session.session_id,
1611
- agentId: session.agent_id,
1612
- status: session.status || 'idle',
1613
- title: session.title,
1614
- createdAt: session.created_at,
1615
- updatedAt: session.updated_at,
1722
+ id: thread.thread_id,
1723
+ taskId: thread.thread_id,
1724
+ agentId: thread.agent_id,
1725
+ status: thread.status || 'idle',
1726
+ title: thread.title,
1727
+ createdAt: thread.created_at,
1728
+ updatedAt: thread.updated_at,
1616
1729
  supen: {
1617
- channel: session.channel,
1618
- sourceRef: session.source_ref,
1619
- taskWorkspaceFolder: session.task_workspace_folder,
1730
+ channel: thread.channel,
1731
+ sourceRef: thread.source_ref,
1732
+ taskWorkspaceFolder: thread.task_workspace_folder,
1620
1733
  },
1621
1734
  };
1622
1735
  }
@@ -1624,23 +1737,23 @@ function adoptMirroredThread(threadId, agentId) {
1624
1737
  const history = readCodexThreadHistory(threadId, 1);
1625
1738
  if (!history)
1626
1739
  return null;
1627
- const historySession = history.session;
1628
- const session = ensureSession({
1740
+ const historyThread = history.thread;
1741
+ const thread = ensureThread({
1629
1742
  agent_id: agentId,
1630
- session_id: threadId,
1743
+ thread_id: threadId,
1631
1744
  channel: 'http',
1632
1745
  agent_name: agentId,
1633
1746
  source_ref: `local-agent:${threadId}`,
1634
- title: typeof historySession.title === 'string' ? historySession.title : undefined,
1635
- status: historySession.status === 'running' ? 'running' : 'idle',
1747
+ title: typeof historyThread.title === 'string' ? historyThread.title : undefined,
1748
+ status: historyThread.status === 'running' ? 'running' : 'idle',
1636
1749
  backend_driver_id: 'codex-app-server',
1637
- task_workspace_folder: typeof historySession.task_workspace_folder === 'string'
1638
- ? historySession.task_workspace_folder
1750
+ task_workspace_folder: typeof historyThread.task_workspace_folder === 'string'
1751
+ ? historyThread.task_workspace_folder
1639
1752
  : null,
1640
1753
  });
1641
- updateSessionSdkId(agentId, threadId, threadId);
1642
- updateSessionBackendDriverId(agentId, threadId, 'codex-app-server');
1643
- return session;
1754
+ updateThreadSdkSessionId(agentId, threadId, threadId);
1755
+ updateThreadBackendDriverId(agentId, threadId, 'codex-app-server');
1756
+ return thread;
1644
1757
  }
1645
1758
  function coerceSingleQueryParam(value) {
1646
1759
  if (Array.isArray(value))
@@ -1663,24 +1776,24 @@ function collectSpaceLogEntries(filters = {}) {
1663
1776
  const agents = getAllAgents()
1664
1777
  // Single-Computer runtime: include all agents in this mounted volume.
1665
1778
  .sort((a, b) => (a.name || a.agent_id).localeCompare(b.name || b.agent_id));
1666
- const sessions = agents.flatMap((agent) => getSessionsForAgent(agent.agent_id)
1667
- .map((session) => ({
1779
+ const threads = agents.flatMap((agent) => getThreadsForAgent(agent.agent_id)
1780
+ .map((thread) => ({
1668
1781
  agent_id: agent.agent_id,
1669
1782
  agent_name: agent.name || null,
1670
- session_id: session.session_id,
1671
- status: session.status,
1672
- updated_at: session.updated_at,
1673
- title: session.title || null,
1783
+ thread_id: thread.thread_id,
1784
+ status: thread.status,
1785
+ updated_at: thread.updated_at,
1786
+ title: thread.title || null,
1674
1787
  })));
1675
- const filteredSessions = sessions.filter((session) => {
1676
- if (filters.agentId && session.agent_id !== filters.agentId)
1788
+ const filteredThreads = threads.filter((thread) => {
1789
+ if (filters.agentId && thread.agent_id !== filters.agentId)
1677
1790
  return false;
1678
- if (filters.sessionId && session.session_id !== filters.sessionId)
1791
+ if (filters.threadId && thread.thread_id !== filters.threadId)
1679
1792
  return false;
1680
1793
  return true;
1681
1794
  });
1682
- const entries = filteredSessions
1683
- .flatMap((session) => getSessionUiEvents(session.agent_id, session.session_id, limit).map((event) => {
1795
+ const entries = filteredThreads
1796
+ .flatMap((thread) => getThreadUiEvents(thread.agent_id, thread.thread_id, limit).map((event) => {
1684
1797
  const logSummary = event.chunk && typeof event.chunk === 'object'
1685
1798
  ? summarizeUiChunk(event.chunk)
1686
1799
  : null;
@@ -1688,10 +1801,10 @@ function collectSpaceLogEntries(filters = {}) {
1688
1801
  ? {
1689
1802
  id: event.id,
1690
1803
  timestamp: event.timestamp,
1691
- agent_id: session.agent_id,
1692
- agent_name: session.agent_name,
1693
- session_id: session.session_id,
1694
- session_title: session.title,
1804
+ agent_id: thread.agent_id,
1805
+ agent_name: thread.agent_name,
1806
+ thread_id: thread.thread_id,
1807
+ thread_title: thread.title,
1695
1808
  task_id: event.task_id || null,
1696
1809
  category: logSummary.category,
1697
1810
  summary: logSummary.summary,
@@ -1706,11 +1819,11 @@ function collectSpaceLogEntries(filters = {}) {
1706
1819
  generated_at: new Date().toISOString(),
1707
1820
  timezone: resolveHostTimeZone(),
1708
1821
  agents,
1709
- sessions,
1822
+ threads,
1710
1823
  entries,
1711
1824
  filters: {
1712
1825
  agent_id: filters.agentId || null,
1713
- session_id: filters.sessionId || null,
1826
+ thread_id: filters.threadId || null,
1714
1827
  },
1715
1828
  };
1716
1829
  }
@@ -2574,24 +2687,24 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2574
2687
  }
2575
2688
  if (pathname === '/api/computers/{computer_id}/logs' && method === 'GET') {
2576
2689
  const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
2577
- const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
2690
+ const threadId = coerceSingleQueryParam(url.searchParams.get('thread_id'));
2578
2691
  const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
2579
2692
  const limit = limitRaw ? Number.parseInt(limitRaw, 10) : SPACE_LOG_EVENT_LIMIT;
2580
2693
  writeJson(res, 200, collectSpaceLogEntries({
2581
2694
  agentId: agentId?.trim() || null,
2582
- sessionId: sessionId?.trim() || null,
2695
+ threadId: threadId?.trim() || null,
2583
2696
  limit: Number.isFinite(limit) ? limit : SPACE_LOG_EVENT_LIMIT,
2584
2697
  }));
2585
2698
  return true;
2586
2699
  }
2587
2700
  if (pathname === '/api/computers/{computer_id}/logs/stream' && method === 'GET') {
2588
2701
  const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
2589
- const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
2702
+ const threadId = coerceSingleQueryParam(url.searchParams.get('thread_id'));
2590
2703
  const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
2591
2704
  const limit = limitRaw ? Number.parseInt(limitRaw, 10) : SPACE_LOG_EVENT_LIMIT;
2592
2705
  const filters = {
2593
2706
  agentId: agentId?.trim() || null,
2594
- sessionId: sessionId?.trim() || null,
2707
+ threadId: threadId?.trim() || null,
2595
2708
  limit: Number.isFinite(limit) ? limit : SPACE_LOG_EVENT_LIMIT,
2596
2709
  };
2597
2710
  res.writeHead(200, {
@@ -2874,7 +2987,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2874
2987
  });
2875
2988
  return true;
2876
2989
  }
2877
- if (pathname === '/api/computers/{computer_id}/agents/codex/session' && method === 'DELETE') {
2990
+ if (pathname === '/api/computers/{computer_id}/agents/codex/thread' && method === 'DELETE') {
2878
2991
  const cmd = spawnSync('codex', ['logout'], {
2879
2992
  encoding: 'utf8',
2880
2993
  timeout: 15_000,
@@ -2969,25 +3082,26 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2969
3082
  numericQueryParam(coerceSingleQueryParam(url.searchParams.get('after')));
2970
3083
  const replayAfter = afterSequence ?? readThreadEventLogHead(threadId);
2971
3084
  addThreadStreamClient(threadId, res);
2972
- const sessionPath = walkSessionFiles(path.join(localAgentHome(), 'sessions')).get(threadId);
2973
- let jsonlOffset = sessionPath && fs.existsSync(sessionPath) ? fs.statSync(sessionPath).size : 0;
3085
+ const threadPath = walkThreadFiles(path.join(localAgentHome(), 'threads')).get(threadId);
3086
+ let jsonlOffset = threadPath && fs.existsSync(threadPath) ? fs.statSync(threadPath).size : 0;
2974
3087
  let jsonlEventIndex = 0;
2975
- const workspacePath = readThreadWorkspacePath(sessionPath) || process.cwd();
3088
+ const workspacePath = readThreadWorkspacePath(threadPath) || process.cwd();
3089
+ const withThreadContext = (chunk) => attachThreadContextToChunk(chunk, readThreadContext({ threadId, workspace: workspacePath }));
2976
3090
  const stopCodexObserver = process.env.NODE_ENV === 'test'
2977
3091
  ? () => { }
2978
3092
  : startCodexThreadObserver({
2979
3093
  threadId,
2980
3094
  cwd: workspacePath,
2981
- write: (chunk, id) => writeThreadStreamEvent(res, chunk, id ? { id } : {}),
3095
+ write: (chunk, id) => writeThreadStreamEvent(res, withThreadContext(chunk), id ? { id } : {}),
2982
3096
  });
2983
3097
  const pingTimer = setInterval(() => {
2984
3098
  writeSse(res);
2985
3099
  res.flush?.();
2986
3100
  }, SPACE_LOG_STREAM_PING_MS);
2987
3101
  pingTimer.unref?.();
2988
- const jsonlPollTimer = sessionPath
3102
+ const jsonlPollTimer = threadPath
2989
3103
  ? setInterval(() => {
2990
- const read = readAppendedJsonlLines(sessionPath, jsonlOffset);
3104
+ const read = readAppendedJsonlLines(threadPath, jsonlOffset);
2991
3105
  jsonlOffset = read.offset;
2992
3106
  for (const line of read.lines) {
2993
3107
  if (!isCodexHistoryTranscriptLine(line))
@@ -2998,7 +3112,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2998
3112
  if (isCodexJsonlMessageRecord(parsed))
2999
3113
  continue;
3000
3114
  jsonlEventIndex += 1;
3001
- writeThreadStreamEvent(res, codexJsonlEventChunk(parsed), {
3115
+ writeThreadStreamEvent(res, withThreadContext(codexJsonlEventChunk(parsed)), {
3002
3116
  id: `jsonl-${jsonlOffset}-${jsonlEventIndex}`,
3003
3117
  });
3004
3118
  }
@@ -3025,7 +3139,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
3025
3139
  const chunk = threadStreamChunkForEvent(event);
3026
3140
  if (!chunk)
3027
3141
  continue;
3028
- writeThreadStreamEvent(res, chunk, { id: event.sequence });
3142
+ writeThreadStreamEvent(res, withThreadContext(chunk), { id: event.sequence });
3029
3143
  }
3030
3144
  res.flush?.();
3031
3145
  return true;
@@ -3054,12 +3168,12 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
3054
3168
  : '';
3055
3169
  const fallbackAgentId = getAllAgents()[0]?.agent_id || 'codex';
3056
3170
  const agentId = requestedAgentId || fallbackAgentId;
3057
- const session = threadId ? adoptMirroredThread(threadId, agentId) : null;
3058
- if (!session) {
3171
+ const thread = threadId ? adoptMirroredThread(threadId, agentId) : null;
3172
+ if (!thread) {
3059
3173
  writeProtocolError(res, 404, 'not_found', 'mirrored_thread_not_found', 'Mirrored task history was not found.');
3060
3174
  return true;
3061
3175
  }
3062
- writeJson(res, 200, { thread: serializeAdoptedMirroredThread(session) });
3176
+ writeJson(res, 200, { thread: serializeAdoptedMirroredThread(thread) });
3063
3177
  return true;
3064
3178
  }
3065
3179
  if (codexProjectsOpenRoute(pathname) && method === 'POST') {