@getpaseo/server 0.1.63 → 0.1.66

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 (265) hide show
  1. package/dist/server/client/daemon-client-transport-types.d.ts +2 -0
  2. package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client-websocket-transport.d.ts +2 -1
  4. package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -1
  5. package/dist/server/client/daemon-client-websocket-transport.js +4 -4
  6. package/dist/server/client/daemon-client-websocket-transport.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +30 -20
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +206 -98
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/client/terminal-stream-router.d.ts +24 -0
  12. package/dist/server/client/terminal-stream-router.d.ts.map +1 -0
  13. package/dist/server/client/terminal-stream-router.js +100 -0
  14. package/dist/server/client/terminal-stream-router.js.map +1 -0
  15. package/dist/server/server/agent/activity-curator.d.ts +6 -3
  16. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  17. package/dist/server/server/agent/activity-curator.js +45 -138
  18. package/dist/server/server/agent/activity-curator.js.map +1 -1
  19. package/dist/server/server/agent/agent-manager.d.ts +3 -14
  20. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  21. package/dist/server/server/agent/agent-manager.js +75 -140
  22. package/dist/server/server/agent/agent-manager.js.map +1 -1
  23. package/dist/server/server/agent/agent-metadata-generator.d.ts +0 -5
  24. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  25. package/dist/server/server/agent/agent-metadata-generator.js +3 -93
  26. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  27. package/dist/server/server/agent/agent-sdk-types.d.ts +15 -1
  28. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  29. package/dist/server/server/agent/agent-stream-coalescer.d.ts +1 -1
  30. package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -1
  31. package/dist/server/server/agent/agent-stream-coalescer.js +1 -1
  32. package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -1
  33. package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -1
  34. package/dist/server/server/agent/agent-timeline-store.js +29 -32
  35. package/dist/server/server/agent/agent-timeline-store.js.map +1 -1
  36. package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
  37. package/dist/server/server/agent/foreground-run-state.d.ts.map +1 -0
  38. package/dist/server/server/agent/foreground-run-state.js +162 -0
  39. package/dist/server/server/agent/foreground-run-state.js.map +1 -0
  40. package/dist/server/server/agent/mcp-server.d.ts +5 -3
  41. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  42. package/dist/server/server/agent/mcp-server.js +110 -99
  43. package/dist/server/server/agent/mcp-server.js.map +1 -1
  44. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  45. package/dist/server/server/agent/mcp-shared.js +7 -1
  46. package/dist/server/server/agent/mcp-shared.js.map +1 -1
  47. package/dist/server/server/agent/prompt-attachments.d.ts +4 -3
  48. package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -1
  49. package/dist/server/server/agent/prompt-attachments.js +43 -4
  50. package/dist/server/server/agent/prompt-attachments.js.map +1 -1
  51. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  52. package/dist/server/server/agent/provider-manifest.js +7 -0
  53. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  54. package/dist/server/server/agent/providers/acp-agent.d.ts +38 -1
  55. package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
  56. package/dist/server/server/agent/providers/acp-agent.js +257 -140
  57. package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
  58. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts.map +1 -1
  59. package/dist/server/server/agent/providers/claude/sidechain-tracker.js +1 -1
  60. package/dist/server/server/agent/providers/claude/sidechain-tracker.js.map +1 -1
  61. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  62. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +68 -198
  63. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  64. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  65. package/dist/server/server/agent/providers/claude-agent.js +52 -102
  66. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  67. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  68. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +125 -141
  69. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  70. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +36 -6
  71. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  72. package/dist/server/server/agent/providers/codex-app-server-agent.js +374 -219
  73. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  74. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +6 -2
  75. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -1
  76. package/dist/server/server/agent/providers/mock-load-test-agent.js +294 -113
  77. package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -1
  78. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +1 -1
  79. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -1
  80. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +94 -2
  81. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -1
  82. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -1
  83. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +24 -115
  84. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -1
  85. package/dist/server/server/agent/providers/opencode-agent.d.ts +102 -1
  86. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  87. package/dist/server/server/agent/providers/opencode-agent.js +416 -158
  88. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  89. package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
  90. package/dist/server/server/agent/providers/provider-runner.d.ts.map +1 -0
  91. package/dist/server/server/agent/providers/provider-runner.js +80 -0
  92. package/dist/server/server/agent/providers/provider-runner.js.map +1 -0
  93. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +6 -3
  94. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  95. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +2 -0
  96. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -1
  97. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +31 -0
  98. package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -1
  99. package/dist/server/server/agent/timeline-projection.d.ts +21 -5
  100. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  101. package/dist/server/server/agent/timeline-projection.js +59 -9
  102. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  103. package/dist/server/server/auth.d.ts +25 -0
  104. package/dist/server/server/auth.d.ts.map +1 -0
  105. package/dist/server/server/auth.js +93 -0
  106. package/dist/server/server/auth.js.map +1 -0
  107. package/dist/server/server/bootstrap.d.ts +3 -1
  108. package/dist/server/server/bootstrap.d.ts.map +1 -1
  109. package/dist/server/server/bootstrap.js +87 -30
  110. package/dist/server/server/bootstrap.js.map +1 -1
  111. package/dist/server/server/config.d.ts.map +1 -1
  112. package/dist/server/server/config.js +11 -0
  113. package/dist/server/server/config.js.map +1 -1
  114. package/dist/server/server/exports.d.ts +7 -1
  115. package/dist/server/server/exports.d.ts.map +1 -1
  116. package/dist/server/server/exports.js +6 -1
  117. package/dist/server/server/exports.js.map +1 -1
  118. package/dist/server/server/file-explorer/service.d.ts +10 -0
  119. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  120. package/dist/server/server/file-explorer/service.js +38 -4
  121. package/dist/server/server/file-explorer/service.js.map +1 -1
  122. package/dist/server/server/index.js +9 -6
  123. package/dist/server/server/index.js.map +1 -1
  124. package/dist/server/server/logger.d.ts.map +1 -1
  125. package/dist/server/server/logger.js +15 -1
  126. package/dist/server/server/logger.js.map +1 -1
  127. package/dist/server/server/pagination/cursor.d.ts +16 -0
  128. package/dist/server/server/pagination/cursor.d.ts.map +1 -0
  129. package/dist/server/server/pagination/cursor.js +62 -0
  130. package/dist/server/server/pagination/cursor.js.map +1 -0
  131. package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
  132. package/dist/server/server/pagination/sortable-pager.d.ts.map +1 -0
  133. package/dist/server/server/pagination/sortable-pager.js +68 -0
  134. package/dist/server/server/pagination/sortable-pager.js.map +1 -0
  135. package/dist/server/server/paseo-worktree-archive-service.d.ts +3 -1
  136. package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -1
  137. package/dist/server/server/paseo-worktree-archive-service.js +61 -53
  138. package/dist/server/server/paseo-worktree-archive-service.js.map +1 -1
  139. package/dist/server/server/paseo-worktree-service.d.ts +13 -0
  140. package/dist/server/server/paseo-worktree-service.d.ts.map +1 -1
  141. package/dist/server/server/paseo-worktree-service.js +72 -3
  142. package/dist/server/server/paseo-worktree-service.js.map +1 -1
  143. package/dist/server/server/persisted-config.d.ts +25 -0
  144. package/dist/server/server/persisted-config.d.ts.map +1 -1
  145. package/dist/server/server/persisted-config.js +9 -0
  146. package/dist/server/server/persisted-config.js.map +1 -1
  147. package/dist/server/server/relay-transport.d.ts.map +1 -1
  148. package/dist/server/server/relay-transport.js +16 -4
  149. package/dist/server/server/relay-transport.js.map +1 -1
  150. package/dist/server/server/resolve-worktree-creation-intent.d.ts +0 -10
  151. package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -1
  152. package/dist/server/server/resolve-worktree-creation-intent.js +1 -45
  153. package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -1
  154. package/dist/server/server/script-status-projection.d.ts +6 -1
  155. package/dist/server/server/script-status-projection.d.ts.map +1 -1
  156. package/dist/server/server/script-status-projection.js +12 -3
  157. package/dist/server/server/script-status-projection.js.map +1 -1
  158. package/dist/server/server/session.d.ts +19 -51
  159. package/dist/server/server/session.d.ts.map +1 -1
  160. package/dist/server/server/session.js +354 -1069
  161. package/dist/server/server/session.js.map +1 -1
  162. package/dist/server/server/websocket-server.d.ts +4 -2
  163. package/dist/server/server/websocket-server.d.ts.map +1 -1
  164. package/dist/server/server/websocket-server.js +65 -12
  165. package/dist/server/server/websocket-server.js.map +1 -1
  166. package/dist/server/server/workspace-directory.d.ts +69 -0
  167. package/dist/server/server/workspace-directory.d.ts.map +1 -0
  168. package/dist/server/server/workspace-directory.js +229 -0
  169. package/dist/server/server/workspace-directory.js.map +1 -0
  170. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  171. package/dist/server/server/worktree-bootstrap.js +6 -2
  172. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  173. package/dist/server/server/worktree-core.d.ts +2 -0
  174. package/dist/server/server/worktree-core.d.ts.map +1 -1
  175. package/dist/server/server/worktree-core.js.map +1 -1
  176. package/dist/server/server/worktree-errors.d.ts +1 -1
  177. package/dist/server/server/worktree-errors.d.ts.map +1 -1
  178. package/dist/server/server/worktree-errors.js +1 -4
  179. package/dist/server/server/worktree-errors.js.map +1 -1
  180. package/dist/server/server/worktree-session.d.ts +47 -20
  181. package/dist/server/server/worktree-session.d.ts.map +1 -1
  182. package/dist/server/server/worktree-session.js +68 -25
  183. package/dist/server/server/worktree-session.js.map +1 -1
  184. package/dist/server/shared/binary-frames/file-transfer.d.ts +56 -0
  185. package/dist/server/shared/binary-frames/file-transfer.d.ts.map +1 -0
  186. package/dist/server/shared/binary-frames/file-transfer.js +108 -0
  187. package/dist/server/shared/binary-frames/file-transfer.js.map +1 -0
  188. package/dist/server/shared/binary-frames/index.d.ts +3 -0
  189. package/dist/server/shared/binary-frames/index.d.ts.map +1 -0
  190. package/dist/server/shared/binary-frames/index.js +3 -0
  191. package/dist/server/shared/binary-frames/index.js.map +1 -0
  192. package/dist/server/shared/{terminal-stream-protocol.d.ts → binary-frames/terminal.d.ts} +2 -2
  193. package/dist/server/shared/binary-frames/terminal.d.ts.map +1 -0
  194. package/dist/server/shared/{terminal-stream-protocol.js → binary-frames/terminal.js} +2 -2
  195. package/dist/server/shared/binary-frames/terminal.js.map +1 -0
  196. package/dist/server/shared/client-capabilities.d.ts +5 -0
  197. package/dist/server/shared/client-capabilities.d.ts.map +1 -0
  198. package/dist/server/shared/client-capabilities.js +4 -0
  199. package/dist/server/shared/client-capabilities.js.map +1 -0
  200. package/dist/server/shared/connection-offer.d.ts +8 -0
  201. package/dist/server/shared/connection-offer.d.ts.map +1 -1
  202. package/dist/server/shared/connection-offer.js +35 -0
  203. package/dist/server/shared/connection-offer.js.map +1 -1
  204. package/dist/server/shared/daemon-endpoints.d.ts +16 -1
  205. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  206. package/dist/server/shared/daemon-endpoints.js +65 -6
  207. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  208. package/dist/server/shared/host-connection-schema.d.ts +23 -0
  209. package/dist/server/shared/host-connection-schema.d.ts.map +1 -0
  210. package/dist/server/shared/host-connection-schema.js +9 -0
  211. package/dist/server/shared/host-connection-schema.js.map +1 -0
  212. package/dist/server/shared/messages.d.ts +3242 -535
  213. package/dist/server/shared/messages.d.ts.map +1 -1
  214. package/dist/server/shared/messages.js +73 -34
  215. package/dist/server/shared/messages.js.map +1 -1
  216. package/dist/server/terminal/terminal-manager-factory.d.ts +7 -0
  217. package/dist/server/terminal/terminal-manager-factory.d.ts.map +1 -0
  218. package/dist/server/terminal/terminal-manager-factory.js +13 -0
  219. package/dist/server/terminal/terminal-manager-factory.js.map +1 -0
  220. package/dist/server/terminal/terminal-manager.d.ts +7 -1
  221. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  222. package/dist/server/terminal/terminal-manager.js +14 -1
  223. package/dist/server/terminal/terminal-manager.js.map +1 -1
  224. package/dist/server/terminal/terminal-session-controller.d.ts +63 -0
  225. package/dist/server/terminal/terminal-session-controller.d.ts.map +1 -0
  226. package/dist/server/terminal/terminal-session-controller.js +615 -0
  227. package/dist/server/terminal/terminal-session-controller.js.map +1 -0
  228. package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
  229. package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
  230. package/dist/server/terminal/terminal-worker-process.d.ts.map +1 -0
  231. package/dist/server/terminal/terminal-worker-process.js +221 -0
  232. package/dist/server/terminal/terminal-worker-process.js.map +1 -0
  233. package/dist/server/terminal/terminal-worker-protocol.d.ts +113 -0
  234. package/dist/server/terminal/terminal-worker-protocol.d.ts.map +1 -0
  235. package/dist/server/terminal/terminal-worker-protocol.js +2 -0
  236. package/dist/server/terminal/terminal-worker-protocol.js.map +1 -0
  237. package/dist/server/terminal/terminal.d.ts +7 -0
  238. package/dist/server/terminal/terminal.d.ts.map +1 -1
  239. package/dist/server/terminal/terminal.js +45 -9
  240. package/dist/server/terminal/terminal.js.map +1 -1
  241. package/dist/server/terminal/worker-terminal-manager.d.ts +19 -0
  242. package/dist/server/terminal/worker-terminal-manager.d.ts.map +1 -0
  243. package/dist/server/terminal/worker-terminal-manager.js +466 -0
  244. package/dist/server/terminal/worker-terminal-manager.js.map +1 -0
  245. package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
  246. package/dist/server/utils/directory-suggestions.js +10 -1
  247. package/dist/server/utils/directory-suggestions.js.map +1 -1
  248. package/dist/server/utils/process-tree.d.ts +25 -0
  249. package/dist/server/utils/process-tree.d.ts.map +1 -0
  250. package/dist/server/utils/process-tree.js +96 -0
  251. package/dist/server/utils/process-tree.js.map +1 -0
  252. package/dist/server/utils/windows-command.d.ts.map +1 -1
  253. package/dist/server/utils/windows-command.js +5 -1
  254. package/dist/server/utils/windows-command.js.map +1 -1
  255. package/dist/server/utils/worktree-metadata.d.ts +44 -0
  256. package/dist/server/utils/worktree-metadata.d.ts.map +1 -1
  257. package/dist/server/utils/worktree-metadata.js +58 -0
  258. package/dist/server/utils/worktree-metadata.js.map +1 -1
  259. package/dist/server/utils/worktree.d.ts +14 -2
  260. package/dist/server/utils/worktree.d.ts.map +1 -1
  261. package/dist/server/utils/worktree.js +22 -13
  262. package/dist/server/utils/worktree.js.map +1 -1
  263. package/package.json +5 -4
  264. package/dist/server/shared/terminal-stream-protocol.d.ts.map +0 -1
  265. package/dist/server/shared/terminal-stream-protocol.js.map +0 -1
@@ -5,15 +5,17 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import readline from "node:readline";
7
7
  import { z } from "zod";
8
- import { loadCodexPersistedTimeline } from "./codex-rollout-timeline.js";
9
8
  import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
9
+ import { curateAgentActivity } from "../activity-curator.js";
10
10
  import { mapCodexRolloutToolCall, mapCodexToolCallFromThreadItem, } from "./codex/tool-call-mapper.js";
11
11
  import { createProviderEnv, createProviderEnvSpec, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
12
12
  import { findExecutable, isCommandAvailable } from "../../../utils/executable.js";
13
+ import { terminateProcessTree } from "../../../utils/process-tree.js";
13
14
  import { spawnProcess } from "../../../utils/spawn.js";
14
15
  import { extractCodexTerminalSessionId, nonEmptyString } from "./tool-call-mapper-utils.js";
15
16
  import { buildCodexFeatures, codexModelSupportsFastMode } from "./codex-feature-definitions.js";
16
17
  import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, resolveBinaryVersion, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
18
+ import { runProviderTurn } from "./provider-runner.js";
17
19
  const DEFAULT_TIMEOUT_MS = 14 * 24 * 60 * 60 * 1000;
18
20
  const TURN_START_TIMEOUT_MS = 90 * 1000;
19
21
  const INTERRUPT_TIMEOUT_MS = 2000;
@@ -21,6 +23,7 @@ const APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS = 2000;
21
23
  const APP_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS = 1000;
22
24
  const CODEX_PROVIDER = "codex";
23
25
  const CODEX_IMAGE_ATTACHMENT_DIR = "paseo-attachments";
26
+ const ASSISTANT_MESSAGE_BOUNDARY_MARKDOWN = "\n\n---\n\n";
24
27
  const CODEX_PLAN_IMPLEMENTATION_PROMPT_PREFIX = "The user approved the plan. Implement it now. Do not restate or revise the plan unless blocked.";
25
28
  const CODEX_APP_SERVER_CAPABILITIES = {
26
29
  supportsStreaming: true,
@@ -406,11 +409,7 @@ class CodexAppServerClient {
406
409
  this.nextId = 1;
407
410
  this.disposed = false;
408
411
  this.stderrBuffer = "";
409
- this.resolveExitPromise = null;
410
412
  this.rl = readline.createInterface({ input: child.stdout });
411
- this.exitPromise = new Promise((resolve) => {
412
- this.resolveExitPromise = resolve;
413
- });
414
413
  this.rl.on("line", (line) => this.handleLine(line));
415
414
  child.stderr.on("data", (chunk) => {
416
415
  this.stderrBuffer += chunk.toString();
@@ -426,8 +425,6 @@ class CodexAppServerClient {
426
425
  }
427
426
  this.pending.clear();
428
427
  this.disposed = true;
429
- this.resolveExitPromise?.();
430
- this.resolveExitPromise = null;
431
428
  });
432
429
  child.on("exit", (code, signal) => {
433
430
  const message = code === 0 && !signal
@@ -440,8 +437,6 @@ class CodexAppServerClient {
440
437
  }
441
438
  this.pending.clear();
442
439
  this.disposed = true;
443
- this.resolveExitPromise?.();
444
- this.resolveExitPromise = null;
445
440
  });
446
441
  }
447
442
  setNotificationHandler(handler) {
@@ -495,31 +490,15 @@ class CodexAppServerClient {
495
490
  catch {
496
491
  // ignore
497
492
  }
498
- signalChildProcessTree(this.child, "SIGTERM");
499
- if (await this.waitForExit(APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS)) {
500
- return;
501
- }
502
- this.logger.warn({ timeoutMs: APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS }, "Codex app-server did not exit after SIGTERM; sending SIGKILL");
503
- signalChildProcessTree(this.child, "SIGKILL");
504
- if (await this.waitForExit(APP_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS)) {
505
- return;
506
- }
507
- this.logger.warn({ timeoutMs: APP_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS }, "Codex app-server did not report exit after SIGKILL");
508
- }
509
- async waitForExit(timeoutMs) {
510
- let timer = null;
511
- try {
512
- return await Promise.race([
513
- this.exitPromise.then(() => true),
514
- new Promise((resolve) => {
515
- timer = setTimeout(() => resolve(false), timeoutMs);
516
- }),
517
- ]);
518
- }
519
- finally {
520
- if (timer) {
521
- clearTimeout(timer);
522
- }
493
+ const result = await terminateProcessTree(this.child, {
494
+ gracefulTimeoutMs: APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS,
495
+ forceTimeoutMs: APP_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS,
496
+ onForceSignal: () => {
497
+ this.logger.warn({ timeoutMs: APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS }, "Codex app-server did not exit after SIGTERM; sending SIGKILL");
498
+ },
499
+ });
500
+ if (result === "kill-timeout") {
501
+ this.logger.warn({ timeoutMs: APP_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS }, "Codex app-server did not report exit after SIGKILL");
523
502
  }
524
503
  }
525
504
  async handleLine(line) {
@@ -572,26 +551,6 @@ class CodexAppServerClient {
572
551
  }
573
552
  }
574
553
  }
575
- function signalChildProcessTree(child, signal) {
576
- if (child.exitCode !== null || child.signalCode !== null) {
577
- return;
578
- }
579
- if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
580
- try {
581
- process.kill(-child.pid, signal);
582
- return;
583
- }
584
- catch {
585
- // Fall back to the direct child when no separate process group exists.
586
- }
587
- }
588
- try {
589
- child.kill(signal);
590
- }
591
- catch {
592
- // ignore
593
- }
594
- }
595
554
  function toAgentUsage(tokenUsage) {
596
555
  if (!tokenUsage || typeof tokenUsage !== "object")
597
556
  return undefined;
@@ -881,6 +840,8 @@ function normalizeCodexThreadItemType(rawType) {
881
840
  return "mcpToolCall";
882
841
  case "WebSearch":
883
842
  return "webSearch";
843
+ case "CollabAgentToolCall":
844
+ return "collabAgentToolCall";
884
845
  default:
885
846
  return rawType;
886
847
  }
@@ -1183,11 +1144,51 @@ function threadItemToTimeline(item, options) {
1183
1144
  case "fileChange":
1184
1145
  case "mcpToolCall":
1185
1146
  case "webSearch":
1147
+ case "collabAgentToolCall":
1186
1148
  return mapCodexToolCallFromThreadItem(normalizedItem, { cwd });
1187
1149
  default:
1188
1150
  return null;
1189
1151
  }
1190
1152
  }
1153
+ const CodexThreadReadResponseSchema = z
1154
+ .object({
1155
+ thread: z
1156
+ .object({
1157
+ turns: z
1158
+ .array(z
1159
+ .object({
1160
+ items: z.array(z.unknown()).default([]),
1161
+ })
1162
+ .passthrough())
1163
+ .default([]),
1164
+ })
1165
+ .passthrough()
1166
+ .default({ turns: [] }),
1167
+ })
1168
+ .passthrough();
1169
+ async function requestCodexThreadHistory(requestThread, threadId) {
1170
+ const response = await requestThread(threadId);
1171
+ return CodexThreadReadResponseSchema.parse(response);
1172
+ }
1173
+ async function loadCodexThreadHistoryTimeline(params) {
1174
+ const response = await requestCodexThreadHistory(params.requestThread, params.threadId);
1175
+ const timeline = [];
1176
+ for (const turn of response.thread.turns) {
1177
+ for (const item of turn.items) {
1178
+ const timelineItem = threadItemToTimeline(item, { cwd: params.cwd });
1179
+ if (timelineItem) {
1180
+ timeline.push(timelineItem);
1181
+ }
1182
+ }
1183
+ }
1184
+ return timeline;
1185
+ }
1186
+ function readCodexThread(client, threadId) {
1187
+ return client.request("thread/read", {
1188
+ threadId,
1189
+ includeTurns: true,
1190
+ });
1191
+ }
1191
1192
  function toSandboxPolicy(type, networkAccess) {
1192
1193
  switch (type) {
1193
1194
  case "read-only":
@@ -1234,11 +1235,13 @@ const ThreadStartedNotificationSchema = z
1234
1235
  .passthrough();
1235
1236
  const TurnStartedNotificationSchema = z
1236
1237
  .object({
1238
+ threadId: z.string().optional(),
1237
1239
  turn: z.object({ id: z.string() }).passthrough(),
1238
1240
  })
1239
1241
  .passthrough();
1240
1242
  const TurnCompletedNotificationSchema = z
1241
1243
  .object({
1244
+ threadId: z.string().optional(),
1242
1245
  turn: z
1243
1246
  .object({
1244
1247
  status: z.string(),
@@ -1275,12 +1278,14 @@ const ThreadTokenUsageUpdatedNotificationSchema = z
1275
1278
  .passthrough();
1276
1279
  const ItemTextDeltaNotificationSchema = z
1277
1280
  .object({
1281
+ threadId: z.string().optional(),
1278
1282
  itemId: z.string(),
1279
1283
  delta: z.string(),
1280
1284
  })
1281
1285
  .passthrough();
1282
1286
  const ItemLifecycleNotificationSchema = z
1283
1287
  .object({
1288
+ threadId: z.string().optional(),
1284
1289
  item: z
1285
1290
  .object({
1286
1291
  id: z.string().optional(),
@@ -1310,9 +1315,12 @@ const CodexEventTaskCompleteNotificationSchema = z
1310
1315
  .passthrough();
1311
1316
  const CodexEventItemLifecycleNotificationSchema = z
1312
1317
  .object({
1318
+ threadId: z.string().optional(),
1313
1319
  msg: z
1314
1320
  .object({
1315
1321
  type: z.enum(["item_started", "item_completed"]),
1322
+ threadId: z.string().optional(),
1323
+ thread_id: z.string().optional(),
1316
1324
  item: z
1317
1325
  .object({
1318
1326
  id: z.string().optional(),
@@ -1442,9 +1450,11 @@ const CodexNotificationSchema = z.union([
1442
1450
  method,
1443
1451
  params,
1444
1452
  })),
1445
- z
1446
- .object({ method: z.literal("turn/started"), params: TurnStartedNotificationSchema })
1447
- .transform(({ params }) => ({ kind: "turn_started", turnId: params.turn.id })),
1453
+ z.object({ method: z.literal("turn/started"), params: TurnStartedNotificationSchema }).transform(({ params }) => ({
1454
+ kind: "turn_started",
1455
+ turnId: params.turn.id,
1456
+ threadId: params.threadId ?? null,
1457
+ })),
1448
1458
  z.object({ method: z.literal("turn/started"), params: z.unknown() }).transform(({ method, params }) => ({
1449
1459
  kind: "invalid_payload",
1450
1460
  method,
@@ -1456,6 +1466,7 @@ const CodexNotificationSchema = z.union([
1456
1466
  kind: "turn_completed",
1457
1467
  status: params.turn.status,
1458
1468
  errorMessage: params.turn.error?.message ?? null,
1469
+ threadId: params.threadId ?? null,
1459
1470
  })),
1460
1471
  z.object({ method: z.literal("turn/completed"), params: z.unknown() }).transform(({ method, params }) => ({
1461
1472
  kind: "invalid_payload",
@@ -1507,6 +1518,7 @@ const CodexNotificationSchema = z.union([
1507
1518
  kind: "agent_message_delta",
1508
1519
  itemId: params.itemId,
1509
1520
  delta: params.delta,
1521
+ threadId: params.threadId ?? null,
1510
1522
  })),
1511
1523
  z.object({ method: z.literal("item/agentMessage/delta"), params: z.unknown() }).transform(({ method, params }) => ({
1512
1524
  kind: "invalid_payload",
@@ -1522,6 +1534,7 @@ const CodexNotificationSchema = z.union([
1522
1534
  kind: "reasoning_delta",
1523
1535
  itemId: params.itemId,
1524
1536
  delta: params.delta,
1537
+ threadId: params.threadId ?? null,
1525
1538
  })),
1526
1539
  z.object({ method: z.literal("item/reasoning/summaryTextDelta"), params: z.unknown() }).transform(({ method, params }) => ({
1527
1540
  kind: "invalid_payload",
@@ -1533,6 +1546,7 @@ const CodexNotificationSchema = z.union([
1533
1546
  .transform(({ params }) => ({
1534
1547
  kind: "item_completed",
1535
1548
  source: "item",
1549
+ threadId: params.threadId ?? null,
1536
1550
  item: params.item,
1537
1551
  })),
1538
1552
  z.object({ method: z.literal("item/completed"), params: z.unknown() }).transform(({ method, params }) => ({
@@ -1545,6 +1559,7 @@ const CodexNotificationSchema = z.union([
1545
1559
  .transform(({ params }) => ({
1546
1560
  kind: "item_started",
1547
1561
  source: "item",
1562
+ threadId: params.threadId ?? null,
1548
1563
  item: params.item,
1549
1564
  })),
1550
1565
  z.object({ method: z.literal("item/started"), params: z.unknown() }).transform(({ method, params }) => ({
@@ -1560,6 +1575,7 @@ const CodexNotificationSchema = z.union([
1560
1575
  .transform(({ params }) => ({
1561
1576
  kind: "item_started",
1562
1577
  source: "codex_event",
1578
+ threadId: params.threadId ?? params.msg.threadId ?? params.msg.thread_id ?? null,
1563
1579
  item: params.msg.item,
1564
1580
  })),
1565
1581
  z.object({ method: z.literal("codex/event/item_started"), params: z.unknown() }).transform(({ method, params }) => ({
@@ -1575,6 +1591,7 @@ const CodexNotificationSchema = z.union([
1575
1591
  .transform(({ params }) => ({
1576
1592
  kind: "item_completed",
1577
1593
  source: "codex_event",
1594
+ threadId: params.threadId ?? params.msg.threadId ?? params.msg.thread_id ?? null,
1578
1595
  item: params.msg.item,
1579
1596
  })),
1580
1597
  z.object({ method: z.literal("codex/event/item_completed"), params: z.unknown() }).transform(({ method, params }) => ({
@@ -1759,6 +1776,7 @@ const CodexNotificationSchema = z.union([
1759
1776
  kind: "turn_completed",
1760
1777
  status: "interrupted",
1761
1778
  errorMessage: null,
1779
+ threadId: null,
1762
1780
  })),
1763
1781
  z.object({ method: z.literal("codex/event/turn_aborted"), params: z.unknown() }).transform(({ method, params }) => ({
1764
1782
  kind: "invalid_payload",
@@ -1774,6 +1792,7 @@ const CodexNotificationSchema = z.union([
1774
1792
  kind: "turn_completed",
1775
1793
  status: "completed",
1776
1794
  errorMessage: null,
1795
+ threadId: null,
1777
1796
  })),
1778
1797
  z.object({ method: z.literal("codex/event/task_complete"), params: z.unknown() }).transform(({ method, params }) => ({
1779
1798
  kind: "invalid_payload",
@@ -1824,40 +1843,49 @@ async function readCodexConfiguredDefaults(client, logger) {
1824
1843
  }
1825
1844
  export async function codexAppServerTurnInputFromPrompt(prompt, logger) {
1826
1845
  if (typeof prompt === "string") {
1827
- return [{ type: "text", text: prompt }];
1828
- }
1829
- const blocks = prompt;
1830
- const output = await Promise.all(blocks.map(async (block) => {
1831
- if (!block || typeof block !== "object") {
1832
- return block;
1846
+ return [toCodexTextInput(prompt)];
1847
+ }
1848
+ const output = [];
1849
+ let previousTextBlock = false;
1850
+ for (const block of prompt) {
1851
+ if (block.type === "text") {
1852
+ output.push(toCodexTextInput(block.text));
1853
+ previousTextBlock = block.text.length > 0;
1854
+ continue;
1833
1855
  }
1834
- const record = block;
1835
- if (record.type === "image" &&
1836
- typeof record.mimeType === "string" &&
1837
- typeof record.data === "string") {
1856
+ if (block.type === "skill") {
1857
+ output.push(block);
1858
+ previousTextBlock = false;
1859
+ continue;
1860
+ }
1861
+ if (block.type === "image") {
1838
1862
  try {
1839
- const filePath = await writeImageAttachment(record.mimeType, record.data);
1840
- return { type: "localImage", path: filePath };
1863
+ const filePath = await writeImageAttachment(block.mimeType, block.data);
1864
+ output.push({ type: "localImage", path: filePath });
1841
1865
  }
1842
1866
  catch (error) {
1843
1867
  const message = error instanceof Error ? error.message : String(error);
1844
1868
  logger.warn({ message }, "Failed to write Codex image attachment");
1845
- return {
1846
- type: "text",
1847
- text: `User attached image (failed to write temp file): ${message}`,
1848
- };
1869
+ output.push({
1870
+ ...toCodexTextInput(`User attached image (failed to write temp file): ${message}`),
1871
+ });
1849
1872
  }
1873
+ previousTextBlock = false;
1874
+ continue;
1850
1875
  }
1851
- if (record.type === "github_pr" || record.type === "github_issue") {
1852
- return {
1853
- type: "text",
1854
- text: renderPromptAttachmentAsText(record),
1855
- };
1856
- }
1857
- return block;
1858
- }));
1876
+ const attachmentText = renderPromptAttachmentAsText(block);
1877
+ output.push(toCodexTextInput(previousTextBlock ? `\n\n${attachmentText}` : attachmentText));
1878
+ previousTextBlock = true;
1879
+ }
1859
1880
  return output;
1860
1881
  }
1882
+ function toCodexTextInput(text) {
1883
+ return {
1884
+ type: "text",
1885
+ text,
1886
+ text_elements: [],
1887
+ };
1888
+ }
1861
1889
  function buildCodexAppServerEnv(runtimeSettings, launchEnv) {
1862
1890
  return createProviderEnv({
1863
1891
  runtimeSettings,
@@ -1901,6 +1929,7 @@ class CodexAppServerAgentSession {
1901
1929
  this.pendingReasoning = new Map();
1902
1930
  this.pendingCommandOutputDeltas = new Map();
1903
1931
  this.pendingFileChangeOutputDeltas = new Map();
1932
+ this.pendingAssistantMessageBoundary = false;
1904
1933
  this.terminalCommandByProcessId = new Map();
1905
1934
  this.pendingUnlabeledTerminalInteractions = new Set();
1906
1935
  this.emittedTerminalInteractionKeys = new Set();
@@ -1908,6 +1937,8 @@ class CodexAppServerAgentSession {
1908
1937
  this.emittedExecCommandCompletedCallIds = new Set();
1909
1938
  this.emittedItemStartedIds = new Set();
1910
1939
  this.emittedItemCompletedIds = new Set();
1940
+ this.subAgentCallsByCallId = new Map();
1941
+ this.subAgentCallIdByChildThreadId = new Map();
1911
1942
  this.warnedUnknownNotificationMethods = new Set();
1912
1943
  this.warnedInvalidNotificationPayloads = new Set();
1913
1944
  this.warnedIncompleteEditToolCallIds = new Set();
@@ -1958,8 +1989,8 @@ class CodexAppServerAgentSession {
1958
1989
  await this.loadCollaborationModes();
1959
1990
  await this.loadSkills();
1960
1991
  if (this.currentThreadId) {
1961
- await this.loadPersistedHistory();
1962
1992
  await this.ensureThreadLoaded();
1993
+ await this.loadPersistedHistory();
1963
1994
  }
1964
1995
  this.connected = true;
1965
1996
  }
@@ -2128,27 +2159,16 @@ class CodexAppServerAgentSession {
2128
2159
  async loadPersistedHistory() {
2129
2160
  if (!this.client || !this.currentThreadId)
2130
2161
  return;
2162
+ const client = this.client;
2163
+ const threadId = this.currentThreadId;
2131
2164
  try {
2132
- let rolloutTimeline = [];
2133
- try {
2134
- rolloutTimeline = await loadCodexPersistedTimeline(this.currentThreadId, undefined, this.logger);
2135
- }
2136
- catch {
2137
- rolloutTimeline = [];
2138
- }
2139
- const response = (await this.client.request("thread/read", {
2140
- threadId: this.currentThreadId,
2141
- includeTurns: true,
2142
- }));
2143
- const thread = response?.thread;
2144
- const threadTimeline = [];
2145
- if (thread && Array.isArray(thread.turns)) {
2146
- for (const turn of thread.turns) {
2147
- const items = Array.isArray(turn.items) ? turn.items : [];
2148
- this.collectThreadTurnTimelineItems(items, threadTimeline);
2149
- }
2150
- }
2151
- const timeline = rolloutTimeline.length > 0 ? rolloutTimeline : threadTimeline;
2165
+ const timeline = await loadCodexThreadHistoryTimeline({
2166
+ threadId,
2167
+ cwd: this.config.cwd ?? null,
2168
+ requestThread: (threadIdToRead) => {
2169
+ return readCodexThread(client, threadIdToRead);
2170
+ },
2171
+ });
2152
2172
  if (timeline.length > 0) {
2153
2173
  this.persistedHistory = timeline;
2154
2174
  this.historyPending = true;
@@ -2245,79 +2265,22 @@ class CodexAppServerAgentSession {
2245
2265
  return args ? `$${commandName} ${args}` : `$${commandName}`;
2246
2266
  }
2247
2267
  async run(prompt, options) {
2248
- const timeline = [];
2249
- let finalText = "";
2250
- let usage;
2251
- let turnId = null;
2252
- const bufferedEvents = [];
2253
- let settled = false;
2254
- let resolveCompletion;
2255
- let rejectCompletion;
2256
- const processEvent = (event) => {
2257
- if (settled) {
2258
- return;
2259
- }
2260
- const eventTurnId = event.turnId;
2261
- if (turnId && eventTurnId && eventTurnId !== turnId) {
2262
- return;
2263
- }
2264
- if (event.type === "timeline") {
2265
- timeline.push(event.item);
2266
- if (event.item.type === "assistant_message") {
2267
- finalText = event.item.text;
2268
+ return runProviderTurn({
2269
+ prompt,
2270
+ runOptions: options,
2271
+ startTurn: (p, o) => this.startTurn(p, o),
2272
+ subscribe: (callback) => this.subscribe(callback),
2273
+ getSessionId: async () => (await this.getRuntimeInfo()).sessionId ?? "",
2274
+ reduceFinalText: ({ current, item }) => {
2275
+ if (item.type === "assistant_message") {
2276
+ return item.text;
2268
2277
  }
2269
- else if (event.item.type === "tool_call" && event.item.detail.type === "plan") {
2270
- finalText = event.item.detail.text;
2278
+ if (item.type === "tool_call" && item.detail.type === "plan") {
2279
+ return item.detail.text;
2271
2280
  }
2272
- return;
2273
- }
2274
- if (event.type === "turn_completed") {
2275
- usage = event.usage;
2276
- settled = true;
2277
- resolveCompletion();
2278
- return;
2279
- }
2280
- if (event.type === "turn_failed") {
2281
- settled = true;
2282
- rejectCompletion(new Error(event.error));
2283
- return;
2284
- }
2285
- if (event.type === "turn_canceled") {
2286
- settled = true;
2287
- resolveCompletion();
2288
- }
2289
- };
2290
- const completion = new Promise((resolve, reject) => {
2291
- resolveCompletion = resolve;
2292
- rejectCompletion = reject;
2293
- });
2294
- const unsubscribe = this.subscribe((event) => {
2295
- if (!turnId) {
2296
- bufferedEvents.push(event);
2297
- return;
2298
- }
2299
- processEvent(event);
2281
+ return current;
2282
+ },
2300
2283
  });
2301
- try {
2302
- const result = await this.startTurn(prompt, options);
2303
- turnId = result.turnId;
2304
- for (const event of bufferedEvents) {
2305
- processEvent(event);
2306
- }
2307
- if (!settled) {
2308
- await completion;
2309
- }
2310
- }
2311
- finally {
2312
- unsubscribe();
2313
- }
2314
- const info = await this.getRuntimeInfo();
2315
- return {
2316
- sessionId: info.sessionId ?? "",
2317
- finalText,
2318
- usage,
2319
- timeline,
2320
- };
2321
2284
  }
2322
2285
  async startTurn(prompt, options) {
2323
2286
  if (this.activeForegroundTurnId) {
@@ -2740,17 +2703,11 @@ class CodexAppServerAgentSession {
2740
2703
  }
2741
2704
  async buildUserInput(prompt) {
2742
2705
  if (typeof prompt === "string") {
2743
- return [{ type: "text", text: prompt }];
2706
+ return [toCodexTextInput(prompt)];
2744
2707
  }
2745
- const blocks = prompt;
2746
- return await codexAppServerTurnInputFromPrompt(blocks, this.logger);
2708
+ return await codexAppServerTurnInputFromPrompt(prompt, this.logger);
2747
2709
  }
2748
2710
  emitEvent(event) {
2749
- if (event.type === "timeline") {
2750
- if (event.item.type === "assistant_message") {
2751
- this.pendingAgentMessages.clear();
2752
- }
2753
- }
2754
2711
  this.notifySubscribers(event);
2755
2712
  }
2756
2713
  notifySubscribers(event) {
@@ -2825,16 +2782,153 @@ class CodexAppServerAgentSession {
2825
2782
  this.warnUnknownNotificationMethod(parsed.method, parsed.params);
2826
2783
  }
2827
2784
  }
2785
+ getSubAgentCallIdForThread(threadId) {
2786
+ if (!threadId || threadId === this.currentThreadId) {
2787
+ return null;
2788
+ }
2789
+ return this.subAgentCallIdByChildThreadId.get(threadId) ?? null;
2790
+ }
2791
+ registerSubAgentToolCall(timelineItem, rawItem) {
2792
+ if (timelineItem.detail.type !== "sub_agent") {
2793
+ return;
2794
+ }
2795
+ const existing = this.subAgentCallsByCallId.get(timelineItem.callId);
2796
+ const state = existing ??
2797
+ {
2798
+ callId: timelineItem.callId,
2799
+ toolCall: timelineItem,
2800
+ childItemOrder: [],
2801
+ childItems: new Map(),
2802
+ };
2803
+ state.toolCall = {
2804
+ ...timelineItem,
2805
+ detail: {
2806
+ ...timelineItem.detail,
2807
+ log: timelineItem.detail.log ||
2808
+ (state.toolCall.detail.type === "sub_agent" ? state.toolCall.detail.log : ""),
2809
+ },
2810
+ };
2811
+ this.subAgentCallsByCallId.set(timelineItem.callId, state);
2812
+ const receiverThreadIds = Array.isArray(rawItem.receiverThreadIds)
2813
+ ? rawItem.receiverThreadIds.filter((value) => typeof value === "string")
2814
+ : [];
2815
+ for (const receiverThreadId of receiverThreadIds) {
2816
+ this.subAgentCallIdByChildThreadId.set(receiverThreadId, timelineItem.callId);
2817
+ }
2818
+ }
2819
+ upsertSubAgentChildItem(callId, itemId, item) {
2820
+ const state = this.subAgentCallsByCallId.get(callId);
2821
+ if (!state) {
2822
+ return;
2823
+ }
2824
+ if (!state.childItems.has(itemId)) {
2825
+ state.childItemOrder.push(itemId);
2826
+ }
2827
+ state.childItems.set(itemId, item);
2828
+ }
2829
+ getSubAgentChildTimeline(state) {
2830
+ return state.childItemOrder
2831
+ .map((itemId) => state.childItems.get(itemId))
2832
+ .filter((item) => Boolean(item));
2833
+ }
2834
+ emitSubAgentActivityUpdate(callId, status) {
2835
+ const state = this.subAgentCallsByCallId.get(callId);
2836
+ if (!state || state.toolCall.detail.type !== "sub_agent") {
2837
+ return;
2838
+ }
2839
+ const childTimeline = this.getSubAgentChildTimeline(state);
2840
+ const log = childTimeline.length > 0
2841
+ ? curateAgentActivity(childTimeline, { labelAssistantMessages: true })
2842
+ : "";
2843
+ const resolvedStatus = status ?? state.toolCall.status;
2844
+ const baseToolCall = {
2845
+ ...state.toolCall,
2846
+ detail: {
2847
+ ...state.toolCall.detail,
2848
+ log,
2849
+ },
2850
+ };
2851
+ const nextToolCall = resolvedStatus === "failed"
2852
+ ? {
2853
+ ...baseToolCall,
2854
+ status: "failed",
2855
+ error: state.toolCall.error ?? { message: "Sub-agent failed" },
2856
+ }
2857
+ : {
2858
+ ...baseToolCall,
2859
+ status: resolvedStatus,
2860
+ error: null,
2861
+ };
2862
+ state.toolCall = nextToolCall;
2863
+ this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: nextToolCall });
2864
+ }
2865
+ handleSubAgentChildItemCompleted(callId, itemId, timelineItem) {
2866
+ this.applyBufferedDeltaTextToTimelineItem(timelineItem, itemId);
2867
+ if (itemId) {
2868
+ this.upsertSubAgentChildItem(callId, itemId, timelineItem);
2869
+ this.pendingAgentMessages.delete(itemId);
2870
+ this.pendingReasoning.delete(itemId);
2871
+ this.pendingCommandOutputDeltas.delete(itemId);
2872
+ this.pendingFileChangeOutputDeltas.delete(itemId);
2873
+ }
2874
+ this.emitSubAgentActivityUpdate(callId, timelineItem.type === "tool_call" && timelineItem.status === "failed" ? "failed" : "running");
2875
+ }
2876
+ shouldSkipCompletedThreadItem(timelineItem, normalizedItemType, itemId) {
2877
+ // For commandExecution items, codex/event/exec_command_* is authoritative.
2878
+ if (timelineItem.type === "tool_call" && normalizedItemType === "commandExecution") {
2879
+ const callId = timelineItem.callId || itemId;
2880
+ return Boolean(callId && this.emittedExecCommandCompletedCallIds.has(callId));
2881
+ }
2882
+ return Boolean(itemId && this.emittedItemCompletedIds.has(itemId));
2883
+ }
2828
2884
  handleCodexDeltaNotification(parsed) {
2829
2885
  if (parsed.kind === "agent_message_delta") {
2830
2886
  const prev = this.pendingAgentMessages.get(parsed.itemId) ?? "";
2831
- this.pendingAgentMessages.set(parsed.itemId, prev + parsed.delta);
2887
+ const text = prev + parsed.delta;
2888
+ this.pendingAgentMessages.set(parsed.itemId, text);
2889
+ const subAgentCallId = this.getSubAgentCallIdForThread(parsed.threadId);
2890
+ if (subAgentCallId) {
2891
+ this.upsertSubAgentChildItem(subAgentCallId, parsed.itemId, {
2892
+ type: "assistant_message",
2893
+ text,
2894
+ });
2895
+ this.emitSubAgentActivityUpdate(subAgentCallId, "running");
2896
+ return;
2897
+ }
2898
+ const isFirstDeltaForItem = prev.length === 0;
2899
+ this.emitEvent({
2900
+ type: "timeline",
2901
+ provider: CODEX_PROVIDER,
2902
+ item: {
2903
+ type: "assistant_message",
2904
+ text: isFirstDeltaForItem && this.pendingAssistantMessageBoundary
2905
+ ? `${ASSISTANT_MESSAGE_BOUNDARY_MARKDOWN}${parsed.delta}`
2906
+ : parsed.delta,
2907
+ },
2908
+ });
2909
+ if (isFirstDeltaForItem) {
2910
+ this.pendingAssistantMessageBoundary = false;
2911
+ }
2832
2912
  return;
2833
2913
  }
2834
2914
  if (parsed.kind === "reasoning_delta") {
2835
2915
  const prev = this.pendingReasoning.get(parsed.itemId) ?? [];
2836
2916
  prev.push(parsed.delta);
2837
2917
  this.pendingReasoning.set(parsed.itemId, prev);
2918
+ const subAgentCallId = this.getSubAgentCallIdForThread(parsed.threadId);
2919
+ if (subAgentCallId) {
2920
+ this.upsertSubAgentChildItem(subAgentCallId, parsed.itemId, {
2921
+ type: "reasoning",
2922
+ text: prev.join(""),
2923
+ });
2924
+ this.emitSubAgentActivityUpdate(subAgentCallId, "running");
2925
+ return;
2926
+ }
2927
+ this.emitEvent({
2928
+ type: "timeline",
2929
+ provider: CODEX_PROVIDER,
2930
+ item: { type: "reasoning", text: parsed.delta },
2931
+ });
2838
2932
  return;
2839
2933
  }
2840
2934
  if (parsed.kind === "exec_command_output_delta") {
@@ -2854,11 +2948,28 @@ class CodexAppServerAgentSession {
2854
2948
  });
2855
2949
  }
2856
2950
  handleTurnStartedNotification(parsed) {
2951
+ const subAgentCallId = this.getSubAgentCallIdForThread(parsed.threadId);
2952
+ if (subAgentCallId) {
2953
+ this.emitSubAgentActivityUpdate(subAgentCallId, "running");
2954
+ return;
2955
+ }
2857
2956
  this.currentTurnId = parsed.turnId;
2858
2957
  this.resetTurnTrackingState();
2859
2958
  this.emitEvent({ type: "turn_started", provider: CODEX_PROVIDER });
2860
2959
  }
2861
2960
  handleTurnCompletedNotification(parsed) {
2961
+ const subAgentCallId = this.getSubAgentCallIdForThread(parsed.threadId);
2962
+ if (subAgentCallId) {
2963
+ let status = "completed";
2964
+ if (parsed.status === "failed") {
2965
+ status = "failed";
2966
+ }
2967
+ else if (parsed.status === "interrupted") {
2968
+ status = "canceled";
2969
+ }
2970
+ this.emitSubAgentActivityUpdate(subAgentCallId, status);
2971
+ return;
2972
+ }
2862
2973
  if (parsed.status === "failed") {
2863
2974
  this.emitEvent({
2864
2975
  type: "turn_failed",
@@ -2888,8 +2999,11 @@ class CodexAppServerAgentSession {
2888
2999
  this.emittedItemCompletedIds.clear();
2889
3000
  this.emittedExecCommandStartedCallIds.clear();
2890
3001
  this.emittedExecCommandCompletedCallIds.clear();
3002
+ this.pendingAgentMessages.clear();
3003
+ this.pendingReasoning.clear();
2891
3004
  this.pendingCommandOutputDeltas.clear();
2892
3005
  this.pendingFileChangeOutputDeltas.clear();
3006
+ this.pendingAssistantMessageBoundary = false;
2893
3007
  this.warnedIncompleteEditToolCallIds.clear();
2894
3008
  }
2895
3009
  handlePlanUpdatedNotification(parsed) {
@@ -2902,6 +3016,12 @@ class CodexAppServerAgentSession {
2902
3016
  });
2903
3017
  if (timelineItem) {
2904
3018
  this.rememberPlanResult(timelineItem);
3019
+ // In plan mode, the same plan is rendered through the synthetic approval
3020
+ // permission. Keep the remembered text for that card, but do not also
3021
+ // emit a static timeline plan panel.
3022
+ if (this.planModeEnabled) {
3023
+ return;
3024
+ }
2905
3025
  this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
2906
3026
  }
2907
3027
  }
@@ -3018,26 +3138,43 @@ class CodexAppServerAgentSession {
3018
3138
  if (!timelineItem) {
3019
3139
  return;
3020
3140
  }
3141
+ const childSubAgentCallId = this.getSubAgentCallIdForThread(parsed.threadId);
3142
+ if (childSubAgentCallId) {
3143
+ this.handleSubAgentChildItemCompleted(childSubAgentCallId, parsed.item.id, timelineItem);
3144
+ return;
3145
+ }
3021
3146
  const normalizedItemType = normalizeCodexThreadItemType(typeof parsed.item.type === "string" ? parsed.item.type : undefined);
3022
3147
  const itemId = parsed.item.id;
3023
- // For commandExecution items, codex/event/exec_command_* is authoritative.
3024
- if (timelineItem.type === "tool_call" && normalizedItemType === "commandExecution") {
3025
- const callId = timelineItem.callId || itemId;
3026
- if (callId && this.emittedExecCommandCompletedCallIds.has(callId)) {
3027
- return;
3028
- }
3148
+ if (this.shouldSkipCompletedThreadItem(timelineItem, normalizedItemType, itemId)) {
3149
+ return;
3029
3150
  }
3030
- if (itemId && this.emittedItemCompletedIds.has(itemId)) {
3151
+ if (this.consumeStreamedTextCompletion(timelineItem, itemId)) {
3152
+ if (timelineItem.type === "assistant_message") {
3153
+ this.pendingAssistantMessageBoundary = true;
3154
+ }
3155
+ if (itemId) {
3156
+ this.emittedItemCompletedIds.add(itemId);
3157
+ this.emittedItemStartedIds.delete(itemId);
3158
+ }
3031
3159
  return;
3032
3160
  }
3033
3161
  this.applyBufferedDeltaTextToTimelineItem(timelineItem, itemId);
3034
3162
  if (timelineItem.type === "tool_call") {
3163
+ this.registerSubAgentToolCall(timelineItem, parsed.item);
3035
3164
  if (timelineItem.detail.type === "plan") {
3036
3165
  this.rememberPlanResult(timelineItem);
3166
+ // Codex can surface plans both as turn/plan updates and as completed
3167
+ // thread items. In plan mode, approval owns the visible plan card.
3168
+ if (this.planModeEnabled) {
3169
+ return;
3170
+ }
3037
3171
  }
3038
3172
  this.warnOnIncompleteEditToolCall(timelineItem, "item_completed", parsed.item);
3039
3173
  }
3040
3174
  this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
3175
+ if (timelineItem.type === "assistant_message") {
3176
+ this.pendingAssistantMessageBoundary = true;
3177
+ }
3041
3178
  if (itemId) {
3042
3179
  this.emittedItemCompletedIds.add(itemId);
3043
3180
  this.emittedItemStartedIds.delete(itemId);
@@ -3045,6 +3182,39 @@ class CodexAppServerAgentSession {
3045
3182
  this.pendingFileChangeOutputDeltas.delete(itemId);
3046
3183
  }
3047
3184
  }
3185
+ consumeStreamedTextCompletion(timelineItem, itemId) {
3186
+ if (!itemId) {
3187
+ return false;
3188
+ }
3189
+ if (timelineItem.type === "assistant_message" && this.pendingAgentMessages.has(itemId)) {
3190
+ const streamedText = this.pendingAgentMessages.get(itemId) ?? "";
3191
+ this.pendingAgentMessages.delete(itemId);
3192
+ this.emitMissingFinalTextSuffix(timelineItem, streamedText);
3193
+ return true;
3194
+ }
3195
+ if (timelineItem.type === "reasoning" && this.pendingReasoning.has(itemId)) {
3196
+ const streamedText = this.pendingReasoning.get(itemId)?.join("") ?? "";
3197
+ this.pendingReasoning.delete(itemId);
3198
+ this.emitMissingFinalTextSuffix(timelineItem, streamedText);
3199
+ return true;
3200
+ }
3201
+ return false;
3202
+ }
3203
+ emitMissingFinalTextSuffix(timelineItem, streamedText) {
3204
+ if (!timelineItem.text.startsWith(streamedText)) {
3205
+ this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
3206
+ return;
3207
+ }
3208
+ const suffix = timelineItem.text.slice(streamedText.length);
3209
+ if (!suffix) {
3210
+ return;
3211
+ }
3212
+ this.emitEvent({
3213
+ type: "timeline",
3214
+ provider: CODEX_PROVIDER,
3215
+ item: { type: timelineItem.type, text: suffix },
3216
+ });
3217
+ }
3048
3218
  applyBufferedDeltaTextToTimelineItem(timelineItem, itemId) {
3049
3219
  if (!itemId) {
3050
3220
  return;
@@ -3074,6 +3244,14 @@ class CodexAppServerAgentSession {
3074
3244
  if (!timelineItem || timelineItem.type !== "tool_call") {
3075
3245
  return;
3076
3246
  }
3247
+ const childSubAgentCallId = this.getSubAgentCallIdForThread(parsed.threadId);
3248
+ if (childSubAgentCallId) {
3249
+ if (parsed.item.id) {
3250
+ this.upsertSubAgentChildItem(childSubAgentCallId, parsed.item.id, timelineItem);
3251
+ }
3252
+ this.emitSubAgentActivityUpdate(childSubAgentCallId, "running");
3253
+ return;
3254
+ }
3077
3255
  const normalizedItemType = normalizeCodexThreadItemType(typeof parsed.item.type === "string" ? parsed.item.type : undefined);
3078
3256
  const itemId = parsed.item.id;
3079
3257
  if (normalizedItemType === "commandExecution") {
@@ -3086,6 +3264,7 @@ class CodexAppServerAgentSession {
3086
3264
  return;
3087
3265
  }
3088
3266
  this.warnOnIncompleteEditToolCall(timelineItem, "item_started", parsed.item);
3267
+ this.registerSubAgentToolCall(timelineItem, parsed.item);
3089
3268
  this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
3090
3269
  if (itemId) {
3091
3270
  this.emittedItemStartedIds.add(itemId);
@@ -3167,20 +3346,6 @@ class CodexAppServerAgentSession {
3167
3346
  this.emittedTerminalInteractionKeys.add(key);
3168
3347
  return true;
3169
3348
  }
3170
- collectThreadTurnTimelineItems(items, target) {
3171
- for (const item of items) {
3172
- const timelineItem = threadItemToTimeline(item, {
3173
- cwd: this.config.cwd ?? null,
3174
- });
3175
- if (!timelineItem) {
3176
- continue;
3177
- }
3178
- if (timelineItem.type === "tool_call") {
3179
- this.warnOnIncompleteEditToolCall(timelineItem, "thread_read", item);
3180
- }
3181
- target.push(timelineItem);
3182
- }
3183
- }
3184
3349
  warnOnIncompleteEditToolCall(item, source, payload) {
3185
3350
  if (!isEditToolCallWithoutContent(item)) {
3186
3351
  return;
@@ -3368,23 +3533,13 @@ export class CodexAppServerAgentClient {
3368
3533
  const title = typeof thread.preview === "string" ? thread.preview : null;
3369
3534
  let timeline = [];
3370
3535
  try {
3371
- const [rolloutTimeline, read] = await Promise.all([
3372
- loadCodexPersistedTimeline(threadId, undefined, this.logger),
3373
- client.request("thread/read", {
3374
- threadId,
3375
- includeTurns: true,
3376
- }),
3377
- ]);
3378
- const turns = read.thread?.turns ?? [];
3379
- const itemsFromThreadRead = [];
3380
- for (const turn of turns) {
3381
- for (const item of turn.items ?? []) {
3382
- const timelineItem = threadItemToTimeline(item, { cwd });
3383
- if (timelineItem)
3384
- itemsFromThreadRead.push(timelineItem);
3385
- }
3386
- }
3387
- timeline = rolloutTimeline.length > 0 ? rolloutTimeline : itemsFromThreadRead;
3536
+ timeline = await loadCodexThreadHistoryTimeline({
3537
+ threadId,
3538
+ cwd,
3539
+ requestThread: (threadIdToRead) => {
3540
+ return readCodexThread(client, threadIdToRead);
3541
+ },
3542
+ });
3388
3543
  }
3389
3544
  catch {
3390
3545
  timeline = [];