@getpaseo/server 0.1.15 → 0.1.17

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 (364) hide show
  1. package/dist/scripts/daemon-runner.js +53 -14
  2. package/dist/scripts/daemon-runner.js.map +1 -1
  3. package/dist/scripts/dev-runner.js +9 -16
  4. package/dist/scripts/dev-runner.js.map +1 -1
  5. package/dist/scripts/supervisor.js +40 -13
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +63 -6
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +436 -92
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +13 -1
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +404 -39
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-metadata-generator.js +13 -4
  17. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  18. package/dist/server/server/agent/agent-projections.d.ts +5 -0
  19. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  20. package/dist/server/server/agent/agent-projections.js +24 -0
  21. package/dist/server/server/agent/agent-projections.js.map +1 -1
  22. package/dist/server/server/agent/agent-response-loop.js +1 -1
  23. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.d.ts +20 -0
  25. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  26. package/dist/server/server/agent/agent-sdk-types.js +11 -1
  27. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  28. package/dist/server/server/agent/agent-storage.d.ts +20 -6
  29. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  30. package/dist/server/server/agent/agent-storage.js +43 -72
  31. package/dist/server/server/agent/agent-storage.js.map +1 -1
  32. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  33. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  34. package/dist/server/server/agent/agent-title-limits.js +3 -0
  35. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  36. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  37. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  38. package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
  39. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  40. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  41. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  42. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  43. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  44. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +17 -0
  46. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  47. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +2 -0
  49. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  50. package/dist/server/server/agent/providers/claude-agent.d.ts +10 -3
  51. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  52. package/dist/server/server/agent/providers/claude-agent.js +1702 -330
  53. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  54. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
  56. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  57. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  58. package/dist/server/server/agent/providers/codex-app-server-agent.js +50 -9
  59. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  60. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
  61. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  62. package/dist/server/server/agent/providers/opencode-agent.js +207 -176
  63. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  64. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +55 -0
  65. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  66. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +1 -0
  67. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  68. package/dist/server/server/agent/timeline-projection.d.ts +20 -0
  69. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  70. package/dist/server/server/agent/timeline-projection.js +73 -0
  71. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  72. package/dist/server/server/bootstrap.d.ts +15 -0
  73. package/dist/server/server/bootstrap.d.ts.map +1 -1
  74. package/dist/server/server/bootstrap.js +27 -4
  75. package/dist/server/server/bootstrap.js.map +1 -1
  76. package/dist/server/server/client-message-id.d.ts +3 -0
  77. package/dist/server/server/client-message-id.d.ts.map +1 -0
  78. package/dist/server/server/client-message-id.js +12 -0
  79. package/dist/server/server/client-message-id.js.map +1 -0
  80. package/dist/server/server/file-download/token-store.d.ts +0 -1
  81. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  82. package/dist/server/server/file-download/token-store.js.map +1 -1
  83. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  84. package/dist/server/server/file-explorer/service.js +56 -36
  85. package/dist/server/server/file-explorer/service.js.map +1 -1
  86. package/dist/server/server/index.js +85 -29
  87. package/dist/server/server/index.js.map +1 -1
  88. package/dist/server/server/logger.d.ts +24 -3
  89. package/dist/server/server/logger.d.ts.map +1 -1
  90. package/dist/server/server/logger.js +157 -21
  91. package/dist/server/server/logger.js.map +1 -1
  92. package/dist/server/server/persisted-config.d.ts +94 -8
  93. package/dist/server/server/persisted-config.d.ts.map +1 -1
  94. package/dist/server/server/persisted-config.js +25 -3
  95. package/dist/server/server/persisted-config.js.map +1 -1
  96. package/dist/server/server/persistence-hooks.js +1 -1
  97. package/dist/server/server/persistence-hooks.js.map +1 -1
  98. package/dist/server/server/pid-lock.d.ts +6 -2
  99. package/dist/server/server/pid-lock.d.ts.map +1 -1
  100. package/dist/server/server/pid-lock.js +7 -10
  101. package/dist/server/server/pid-lock.js.map +1 -1
  102. package/dist/server/server/relay-transport.js +28 -28
  103. package/dist/server/server/relay-transport.js.map +1 -1
  104. package/dist/server/server/session.d.ts +60 -4
  105. package/dist/server/server/session.d.ts.map +1 -1
  106. package/dist/server/server/session.js +854 -190
  107. package/dist/server/server/session.js.map +1 -1
  108. package/dist/server/server/websocket-server.d.ts +24 -5
  109. package/dist/server/server/websocket-server.d.ts.map +1 -1
  110. package/dist/server/server/websocket-server.js +400 -77
  111. package/dist/server/server/websocket-server.js.map +1 -1
  112. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  113. package/dist/server/server/worktree-bootstrap.js +45 -2
  114. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  115. package/dist/server/shared/daemon-endpoints.d.ts +9 -1
  116. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  117. package/dist/server/shared/daemon-endpoints.js +18 -3
  118. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  119. package/dist/server/shared/messages.d.ts +4432 -380
  120. package/dist/server/shared/messages.d.ts.map +1 -1
  121. package/dist/server/shared/messages.js +139 -6
  122. package/dist/server/shared/messages.js.map +1 -1
  123. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  124. package/dist/server/shared/tool-call-display.js +7 -0
  125. package/dist/server/shared/tool-call-display.js.map +1 -1
  126. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  127. package/dist/server/terminal/terminal-manager.js +1 -13
  128. package/dist/server/terminal/terminal-manager.js.map +1 -1
  129. package/dist/server/terminal/terminal.d.ts.map +1 -1
  130. package/dist/server/terminal/terminal.js +29 -5
  131. package/dist/server/terminal/terminal.js.map +1 -1
  132. package/dist/server/utils/worktree.d.ts +1 -0
  133. package/dist/server/utils/worktree.d.ts.map +1 -1
  134. package/dist/server/utils/worktree.js +17 -2
  135. package/dist/server/utils/worktree.js.map +1 -1
  136. package/dist/src/server/agent/activity-curator.js +228 -0
  137. package/dist/src/server/agent/activity-curator.js.map +1 -0
  138. package/dist/src/server/agent/agent-manager.js +1712 -0
  139. package/dist/src/server/agent/agent-manager.js.map +1 -0
  140. package/dist/src/server/agent/agent-metadata-generator.js +163 -0
  141. package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
  142. package/dist/src/server/agent/agent-projections.js +262 -0
  143. package/dist/src/server/agent/agent-projections.js.map +1 -0
  144. package/dist/src/server/agent/agent-response-loop.js +304 -0
  145. package/dist/src/server/agent/agent-response-loop.js.map +1 -0
  146. package/dist/src/server/agent/agent-sdk-types.js +12 -0
  147. package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
  148. package/dist/src/server/agent/agent-storage.js +299 -0
  149. package/dist/src/server/agent/agent-storage.js.map +1 -0
  150. package/dist/src/server/agent/agent-title-limits.js +3 -0
  151. package/dist/src/server/agent/agent-title-limits.js.map +1 -0
  152. package/dist/src/server/agent/audio-utils.js +19 -0
  153. package/dist/src/server/agent/audio-utils.js.map +1 -0
  154. package/dist/src/server/agent/dictation-debug.js +50 -0
  155. package/dist/src/server/agent/dictation-debug.js.map +1 -0
  156. package/dist/src/server/agent/mcp-server.js +787 -0
  157. package/dist/src/server/agent/mcp-server.js.map +1 -0
  158. package/dist/src/server/agent/orchestrator-instructions.js +51 -0
  159. package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
  160. package/dist/src/server/agent/pcm16-resampler.js +63 -0
  161. package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
  162. package/dist/src/server/agent/provider-launch-config.js +83 -0
  163. package/dist/src/server/agent/provider-launch-config.js.map +1 -0
  164. package/dist/src/server/agent/provider-manifest.js +97 -0
  165. package/dist/src/server/agent/provider-manifest.js.map +1 -0
  166. package/dist/src/server/agent/provider-registry.js +45 -0
  167. package/dist/src/server/agent/provider-registry.js.map +1 -0
  168. package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
  169. package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
  170. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  171. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  172. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  173. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  174. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
  175. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  176. package/dist/src/server/agent/providers/claude-agent.js +3747 -0
  177. package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
  178. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  179. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  180. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
  181. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  182. package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
  183. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
  184. package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
  185. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  186. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  187. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  188. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  189. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  190. package/dist/src/server/agent/providers/opencode-agent.js +905 -0
  191. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
  192. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
  193. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  194. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
  195. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  196. package/dist/src/server/agent/recordings-debug.js +19 -0
  197. package/dist/src/server/agent/recordings-debug.js.map +1 -0
  198. package/dist/src/server/agent/stt-debug.js +33 -0
  199. package/dist/src/server/agent/stt-debug.js.map +1 -0
  200. package/dist/src/server/agent/stt-manager.js +233 -0
  201. package/dist/src/server/agent/stt-manager.js.map +1 -0
  202. package/dist/src/server/agent/timeline-append.js +27 -0
  203. package/dist/src/server/agent/timeline-append.js.map +1 -0
  204. package/dist/src/server/agent/timeline-projection.js +215 -0
  205. package/dist/src/server/agent/timeline-projection.js.map +1 -0
  206. package/dist/src/server/agent/tool-name-normalization.js +45 -0
  207. package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
  208. package/dist/src/server/agent/tts-debug.js +24 -0
  209. package/dist/src/server/agent/tts-debug.js.map +1 -0
  210. package/dist/src/server/agent/tts-manager.js +249 -0
  211. package/dist/src/server/agent/tts-manager.js.map +1 -0
  212. package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
  213. package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
  214. package/dist/src/server/agent-attention-policy.js +40 -0
  215. package/dist/src/server/agent-attention-policy.js.map +1 -0
  216. package/dist/src/server/allowed-hosts.js +94 -0
  217. package/dist/src/server/allowed-hosts.js.map +1 -0
  218. package/dist/src/server/bootstrap.js +498 -0
  219. package/dist/src/server/bootstrap.js.map +1 -0
  220. package/dist/src/server/client-message-id.js +12 -0
  221. package/dist/src/server/client-message-id.js.map +1 -0
  222. package/dist/src/server/config.js +84 -0
  223. package/dist/src/server/config.js.map +1 -0
  224. package/dist/src/server/connection-offer.js +60 -0
  225. package/dist/src/server/connection-offer.js.map +1 -0
  226. package/dist/src/server/daemon-keypair.js +40 -0
  227. package/dist/src/server/daemon-keypair.js.map +1 -0
  228. package/dist/src/server/daemon-version.js +22 -0
  229. package/dist/src/server/daemon-version.js.map +1 -0
  230. package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
  231. package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
  232. package/dist/src/server/file-download/token-store.js +40 -0
  233. package/dist/src/server/file-download/token-store.js.map +1 -0
  234. package/dist/src/server/file-explorer/service.js +183 -0
  235. package/dist/src/server/file-explorer/service.js.map +1 -0
  236. package/dist/src/server/json-utils.js +45 -0
  237. package/dist/src/server/json-utils.js.map +1 -0
  238. package/dist/src/server/messages.js +29 -0
  239. package/dist/src/server/messages.js.map +1 -0
  240. package/dist/src/server/package-version.js +47 -0
  241. package/dist/src/server/package-version.js.map +1 -0
  242. package/dist/src/server/paseo-home.js +19 -0
  243. package/dist/src/server/paseo-home.js.map +1 -0
  244. package/dist/src/server/path-utils.js +20 -0
  245. package/dist/src/server/path-utils.js.map +1 -0
  246. package/dist/src/server/persisted-config.js +259 -0
  247. package/dist/src/server/persisted-config.js.map +1 -0
  248. package/dist/src/server/persistence-hooks.js +60 -0
  249. package/dist/src/server/persistence-hooks.js.map +1 -0
  250. package/dist/src/server/pid-lock.js +126 -0
  251. package/dist/src/server/pid-lock.js.map +1 -0
  252. package/dist/src/server/push/push-service.js +68 -0
  253. package/dist/src/server/push/push-service.js.map +1 -0
  254. package/dist/src/server/push/token-store.js +70 -0
  255. package/dist/src/server/push/token-store.js.map +1 -0
  256. package/dist/src/server/relay-transport.js +457 -0
  257. package/dist/src/server/relay-transport.js.map +1 -0
  258. package/dist/src/server/server-id.js +63 -0
  259. package/dist/src/server/server-id.js.map +1 -0
  260. package/dist/src/server/session.js +5947 -0
  261. package/dist/src/server/session.js.map +1 -0
  262. package/dist/src/server/speech/audio.js +101 -0
  263. package/dist/src/server/speech/audio.js.map +1 -0
  264. package/dist/src/server/speech/provider-resolver.js +7 -0
  265. package/dist/src/server/speech/provider-resolver.js.map +1 -0
  266. package/dist/src/server/speech/providers/local/config.js +83 -0
  267. package/dist/src/server/speech/providers/local/config.js.map +1 -0
  268. package/dist/src/server/speech/providers/local/models.js +17 -0
  269. package/dist/src/server/speech/providers/local/models.js.map +1 -0
  270. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
  271. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
  272. package/dist/src/server/speech/providers/local/runtime.js +253 -0
  273. package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
  274. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
  275. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
  276. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
  277. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
  278. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
  279. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
  280. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
  281. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
  282. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
  283. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
  284. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
  285. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
  286. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
  287. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
  288. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
  289. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
  290. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
  291. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
  292. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
  293. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
  294. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
  295. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
  296. package/dist/src/server/speech/providers/openai/config.js +99 -0
  297. package/dist/src/server/speech/providers/openai/config.js.map +1 -0
  298. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
  299. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
  300. package/dist/src/server/speech/providers/openai/runtime.js +114 -0
  301. package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
  302. package/dist/src/server/speech/providers/openai/stt.js +208 -0
  303. package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
  304. package/dist/src/server/speech/providers/openai/tts.js +46 -0
  305. package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
  306. package/dist/src/server/speech/speech-config-resolver.js +85 -0
  307. package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
  308. package/dist/src/server/speech/speech-provider.js +2 -0
  309. package/dist/src/server/speech/speech-provider.js.map +1 -0
  310. package/dist/src/server/speech/speech-runtime.js +497 -0
  311. package/dist/src/server/speech/speech-runtime.js.map +1 -0
  312. package/dist/src/server/speech/speech-types.js +8 -0
  313. package/dist/src/server/speech/speech-types.js.map +1 -0
  314. package/dist/src/server/utils/diff-highlighter.js +244 -0
  315. package/dist/src/server/utils/diff-highlighter.js.map +1 -0
  316. package/dist/src/server/utils/syntax-highlighter.js +145 -0
  317. package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
  318. package/dist/src/server/voice-config.js +51 -0
  319. package/dist/src/server/voice-config.js.map +1 -0
  320. package/dist/src/server/voice-mcp-bridge-command.js +31 -0
  321. package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
  322. package/dist/src/server/voice-mcp-bridge.js +109 -0
  323. package/dist/src/server/voice-mcp-bridge.js.map +1 -0
  324. package/dist/src/server/voice-permission-policy.js +13 -0
  325. package/dist/src/server/voice-permission-policy.js.map +1 -0
  326. package/dist/src/server/voice-types.js +2 -0
  327. package/dist/src/server/voice-types.js.map +1 -0
  328. package/dist/src/server/websocket-server.js +967 -0
  329. package/dist/src/server/websocket-server.js.map +1 -0
  330. package/dist/src/server/worktree-bootstrap.js +497 -0
  331. package/dist/src/server/worktree-bootstrap.js.map +1 -0
  332. package/dist/src/shared/agent-attention-notification.js +130 -0
  333. package/dist/src/shared/agent-attention-notification.js.map +1 -0
  334. package/dist/src/shared/agent-lifecycle.js +8 -0
  335. package/dist/src/shared/agent-lifecycle.js.map +1 -0
  336. package/dist/src/shared/binary-mux.js +114 -0
  337. package/dist/src/shared/binary-mux.js.map +1 -0
  338. package/dist/src/shared/connection-offer.js +17 -0
  339. package/dist/src/shared/connection-offer.js.map +1 -0
  340. package/dist/src/shared/daemon-endpoints.js +113 -0
  341. package/dist/src/shared/daemon-endpoints.js.map +1 -0
  342. package/dist/src/shared/messages.js +2001 -0
  343. package/dist/src/shared/messages.js.map +1 -0
  344. package/dist/src/shared/path-utils.js +16 -0
  345. package/dist/src/shared/path-utils.js.map +1 -0
  346. package/dist/src/shared/tool-call-display.js +93 -0
  347. package/dist/src/shared/tool-call-display.js.map +1 -0
  348. package/dist/src/terminal/terminal-manager.js +136 -0
  349. package/dist/src/terminal/terminal-manager.js.map +1 -0
  350. package/dist/src/terminal/terminal.js +410 -0
  351. package/dist/src/terminal/terminal.js.map +1 -0
  352. package/dist/src/utils/checkout-git.js +1397 -0
  353. package/dist/src/utils/checkout-git.js.map +1 -0
  354. package/dist/src/utils/directory-suggestions.js +655 -0
  355. package/dist/src/utils/directory-suggestions.js.map +1 -0
  356. package/dist/src/utils/path.js +15 -0
  357. package/dist/src/utils/path.js.map +1 -0
  358. package/dist/src/utils/project-icon.js +391 -0
  359. package/dist/src/utils/project-icon.js.map +1 -0
  360. package/dist/src/utils/worktree-metadata.js +116 -0
  361. package/dist/src/utils/worktree-metadata.js.map +1 -0
  362. package/dist/src/utils/worktree.js +741 -0
  363. package/dist/src/utils/worktree.js.map +1 -0
  364. package/package.json +15 -7
@@ -6,22 +6,16 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { query, } from "@anthropic-ai/claude-agent-sdk";
8
8
  import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./claude/tool-call-mapper.js";
9
+ import { isTaskNotificationUserContent, mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./claude/task-notification-tool-call.js";
10
+ import { buildClaudeModelFamilyAliases, buildClaudeSelectableModelIds, listClaudeCatalogModels, } from "./claude/model-catalog.js";
11
+ import { buildToolCallDisplayModel } from "../../../shared/tool-call-display.js";
9
12
  import { applyProviderEnv, isProviderCommandAvailable, } from "../provider-launch-config.js";
10
13
  import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
11
14
  const fsPromises = promises;
12
- function normalizeClaudeModelLabel(model) {
13
- const fallback = model.displayName?.trim() || model.value;
14
- const prefix = model.description?.split(/[·•]/)[0]?.trim() || "";
15
- if (!prefix)
16
- return fallback;
17
- // Prefer concrete versioned labels from description (e.g. "Opus 4.6",
18
- // "Sonnet 4.5"), especially when displayName is generic like
19
- // "Default (recommended)".
20
- if (/\d/.test(prefix)) {
21
- return prefix;
22
- }
23
- return fallback;
24
- }
15
+ const CLAUDE_SETTING_SOURCES = [
16
+ "user",
17
+ "project",
18
+ ];
25
19
  function normalizeModelIdCandidate(modelId) {
26
20
  if (typeof modelId !== "string") {
27
21
  return null;
@@ -36,6 +30,28 @@ function pickSupportedModelId(supportedModelIds, candidate) {
36
30
  }
37
31
  return supportedModelIds.has(normalizedCandidate) ? normalizedCandidate : null;
38
32
  }
33
+ function inferClaudeModelFamilyFromText(text) {
34
+ if (typeof text !== "string") {
35
+ return null;
36
+ }
37
+ const lowerText = text.toLowerCase();
38
+ if (lowerText.includes("sonnet")) {
39
+ return "sonnet";
40
+ }
41
+ if (lowerText.includes("opus")) {
42
+ return "opus";
43
+ }
44
+ if (lowerText.includes("haiku")) {
45
+ return "haiku";
46
+ }
47
+ return null;
48
+ }
49
+ function pickFamilyAliasModelId(familyAliases, family) {
50
+ if (!familyAliases) {
51
+ return null;
52
+ }
53
+ return normalizeModelIdCandidate(familyAliases.get(family) ?? null);
54
+ }
39
55
  export function normalizeClaudeRuntimeModelId(options) {
40
56
  const runtimeModel = options.runtimeModelId.trim();
41
57
  if (!runtimeModel) {
@@ -45,31 +61,43 @@ export function normalizeClaudeRuntimeModelId(options) {
45
61
  if (!supportedModelIds || supportedModelIds.size === 0) {
46
62
  return runtimeModel;
47
63
  }
48
- const lowerRuntimeModel = runtimeModel.toLowerCase();
49
- if (lowerRuntimeModel.includes("sonnet")) {
64
+ if (supportedModelIds.has(runtimeModel)) {
65
+ return runtimeModel;
66
+ }
67
+ const runtimeFamily = inferClaudeModelFamilyFromText(runtimeModel);
68
+ const familyAlias = runtimeFamily
69
+ ? pickFamilyAliasModelId(options.supportedModelFamilyAliases, runtimeFamily)
70
+ : null;
71
+ if (runtimeFamily === "sonnet") {
50
72
  const explicitSonnet = pickSupportedModelId(supportedModelIds, "sonnet");
51
73
  if (explicitSonnet) {
52
74
  return explicitSonnet;
53
75
  }
76
+ if (familyAlias && supportedModelIds.has(familyAlias)) {
77
+ return familyAlias;
78
+ }
54
79
  const defaultAlias = pickSupportedModelId(supportedModelIds, "default");
55
80
  if (defaultAlias) {
56
81
  return defaultAlias;
57
82
  }
58
83
  }
59
- if (lowerRuntimeModel.includes("opus")) {
84
+ if (runtimeFamily === "opus") {
60
85
  const alias = pickSupportedModelId(supportedModelIds, "opus");
61
86
  if (alias) {
62
87
  return alias;
63
88
  }
89
+ if (familyAlias && supportedModelIds.has(familyAlias)) {
90
+ return familyAlias;
91
+ }
64
92
  }
65
- if (lowerRuntimeModel.includes("haiku")) {
93
+ if (runtimeFamily === "haiku") {
66
94
  const alias = pickSupportedModelId(supportedModelIds, "haiku");
67
95
  if (alias) {
68
96
  return alias;
69
97
  }
70
- }
71
- if (supportedModelIds.has(runtimeModel)) {
72
- return runtimeModel;
98
+ if (familyAlias && supportedModelIds.has(familyAlias)) {
99
+ return familyAlias;
100
+ }
73
101
  }
74
102
  const configuredModelId = pickSupportedModelId(supportedModelIds, options.configuredModelId);
75
103
  if (configuredModelId) {
@@ -79,6 +107,15 @@ export function normalizeClaudeRuntimeModelId(options) {
79
107
  if (currentModelId) {
80
108
  return currentModelId;
81
109
  }
110
+ // If Claude reports a concrete family ID we can't map directly, prefer the
111
+ // provider default alias for unconfigured sessions so UI model/thinking state
112
+ // can still reconcile against the current model catalog.
113
+ const defaultAlias = pickSupportedModelId(supportedModelIds, "default");
114
+ const hasConfiguredModel = normalizeModelIdCandidate(options.configuredModelId) !== null;
115
+ const hasCurrentModel = normalizeModelIdCandidate(options.currentModelId) !== null;
116
+ if (runtimeFamily && defaultAlias && !hasConfiguredModel && !hasCurrentModel) {
117
+ return defaultAlias;
118
+ }
82
119
  return runtimeModel;
83
120
  }
84
121
  const CLAUDE_CAPABILITIES = {
@@ -118,6 +155,7 @@ const REWIND_COMMAND = {
118
155
  description: "Rewind tracked files to a previous user message",
119
156
  argumentHint: "[user_message_uuid]",
120
157
  };
158
+ const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
121
159
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
122
160
  function resolveClaudeBinary() {
123
161
  try {
@@ -150,6 +188,149 @@ function resolveClaudeSpawnCommand(spawnOptions, runtimeSettings) {
150
188
  args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
151
189
  };
152
190
  }
191
+ function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings) {
192
+ const hasEnvOverrides = Object.keys(runtimeSettings?.env ?? {}).length > 0;
193
+ const commandMode = runtimeSettings?.command?.mode;
194
+ const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
195
+ if (!needsCustomSpawn) {
196
+ return options;
197
+ }
198
+ return {
199
+ ...options,
200
+ spawnClaudeCodeProcess: (spawnOptions) => {
201
+ const resolved = resolveClaudeSpawnCommand(spawnOptions, runtimeSettings);
202
+ return spawn(resolved.command, resolved.args, {
203
+ cwd: spawnOptions.cwd,
204
+ env: applyProviderEnv(spawnOptions.env, runtimeSettings),
205
+ signal: spawnOptions.signal,
206
+ stdio: ["pipe", "pipe", "pipe"],
207
+ });
208
+ },
209
+ };
210
+ }
211
+ const MAX_RECENT_STDERR_CHARS = 4000;
212
+ function summarizeClaudeOptionsForLog(options) {
213
+ const systemPromptRaw = options.systemPrompt;
214
+ const systemPromptSummary = (() => {
215
+ if (!systemPromptRaw) {
216
+ return { mode: "none", preset: null };
217
+ }
218
+ if (typeof systemPromptRaw === "string") {
219
+ return { mode: "string", preset: null };
220
+ }
221
+ const prompt = systemPromptRaw;
222
+ const promptType = typeof prompt.type === "string" ? prompt.type : "custom";
223
+ return {
224
+ mode: promptType === "preset"
225
+ ? "preset"
226
+ : "custom",
227
+ preset: typeof prompt.preset === "string" && prompt.preset.length > 0
228
+ ? prompt.preset
229
+ : null,
230
+ };
231
+ })();
232
+ const mcpServerNames = options.mcpServers
233
+ ? Object.keys(options.mcpServers).sort()
234
+ : [];
235
+ return {
236
+ cwd: typeof options.cwd === "string" ? options.cwd : null,
237
+ permissionMode: typeof options.permissionMode === "string"
238
+ ? options.permissionMode
239
+ : null,
240
+ model: typeof options.model === "string" ? options.model : null,
241
+ includePartialMessages: options.includePartialMessages === true,
242
+ settingSources: Array.isArray(options.settingSources)
243
+ ? options.settingSources
244
+ : [],
245
+ enableFileCheckpointing: options.enableFileCheckpointing === true,
246
+ hasResume: typeof options.resume === "string" && options.resume.length > 0,
247
+ maxThinkingTokens: typeof options.maxThinkingTokens === "number"
248
+ ? options.maxThinkingTokens
249
+ : null,
250
+ hasEnv: !!options.env,
251
+ envKeyCount: Object.keys(options.env ?? {}).length,
252
+ hasMcpServers: mcpServerNames.length > 0,
253
+ mcpServerNames,
254
+ systemPromptMode: systemPromptSummary.mode,
255
+ systemPromptPreset: systemPromptSummary.preset,
256
+ hasCanUseTool: typeof options.canUseTool === "function",
257
+ hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
258
+ hasStderrHandler: typeof options.stderr === "function",
259
+ };
260
+ }
261
+ function isToolResultTextBlock(value) {
262
+ return (!!value &&
263
+ typeof value === "object" &&
264
+ value.type === "text" &&
265
+ typeof value.text === "string");
266
+ }
267
+ function normalizeForDeterministicString(value, seen) {
268
+ if (value === null ||
269
+ typeof value === "string" ||
270
+ typeof value === "number" ||
271
+ typeof value === "boolean") {
272
+ return value;
273
+ }
274
+ if (typeof value === "bigint") {
275
+ return value.toString();
276
+ }
277
+ if (typeof value === "function") {
278
+ return "[function]";
279
+ }
280
+ if (typeof value === "symbol") {
281
+ return value.toString();
282
+ }
283
+ if (typeof value === "undefined") {
284
+ return "[undefined]";
285
+ }
286
+ if (Array.isArray(value)) {
287
+ return value.map((entry) => normalizeForDeterministicString(entry, seen));
288
+ }
289
+ if (typeof value === "object") {
290
+ const objectValue = value;
291
+ if (seen.has(objectValue)) {
292
+ return "[circular]";
293
+ }
294
+ seen.add(objectValue);
295
+ const record = value;
296
+ const normalized = {};
297
+ for (const key of Object.keys(record).sort()) {
298
+ normalized[key] = normalizeForDeterministicString(record[key], seen);
299
+ }
300
+ seen.delete(objectValue);
301
+ return normalized;
302
+ }
303
+ return String(value);
304
+ }
305
+ function deterministicStringify(value) {
306
+ if (typeof value === "undefined") {
307
+ return "";
308
+ }
309
+ try {
310
+ const normalized = normalizeForDeterministicString(value, new WeakSet());
311
+ if (typeof normalized === "string") {
312
+ return normalized;
313
+ }
314
+ return JSON.stringify(normalized);
315
+ }
316
+ catch {
317
+ try {
318
+ return String(value);
319
+ }
320
+ catch {
321
+ return "[unserializable]";
322
+ }
323
+ }
324
+ }
325
+ function coerceToolResultContentToString(content) {
326
+ if (typeof content === "string") {
327
+ return content;
328
+ }
329
+ if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
330
+ return content.map((block) => block.text).join("");
331
+ }
332
+ return deterministicStringify(content);
333
+ }
153
334
  export function extractUserMessageText(content) {
154
335
  if (typeof content === "string") {
155
336
  const normalized = content.trim();
@@ -179,10 +360,18 @@ export function extractUserMessageText(content) {
179
360
  const combined = parts.join("\n\n").trim();
180
361
  return combined.length > 0 ? combined : null;
181
362
  }
182
- const DEFAULT_PERMISSION_TIMEOUT_MS = 120000;
363
+ const MAX_SUB_AGENT_LOG_ENTRIES = 200;
364
+ const MAX_SUB_AGENT_SUMMARY_CHARS = 160;
183
365
  function isMetadata(value) {
184
366
  return typeof value === "object" && value !== null;
185
367
  }
368
+ function readTrimmedString(value) {
369
+ if (typeof value !== "string") {
370
+ return undefined;
371
+ }
372
+ const trimmed = value.trim();
373
+ return trimmed.length > 0 ? trimmed : undefined;
374
+ }
186
375
  function isMcpServerConfig(value) {
187
376
  if (!isMetadata(value)) {
188
377
  return false;
@@ -312,6 +501,440 @@ function resolvePermissionKind(toolName, input) {
312
501
  }
313
502
  return "tool";
314
503
  }
504
+ const ACTIVE_RUN_STATES = new Set([
505
+ "queued",
506
+ "awaiting_response",
507
+ "streaming",
508
+ "finalizing",
509
+ ]);
510
+ class RunTracker {
511
+ constructor() {
512
+ this.runs = new Map();
513
+ this.runByTaskId = new Map();
514
+ this.runByParentMessageId = new Map();
515
+ this.runByMessageId = new Map();
516
+ }
517
+ createRun(input) {
518
+ const run = {
519
+ id: input.id,
520
+ owner: input.owner,
521
+ queue: input.queue,
522
+ state: "queued",
523
+ promptReplaySeen: input.promptReplaySeen ?? true,
524
+ taskIds: new Set(),
525
+ parentMessageIds: new Set(),
526
+ messageIds: new Set(),
527
+ };
528
+ this.runs.set(run.id, run);
529
+ return run;
530
+ }
531
+ getRun(runId) {
532
+ return this.runs.get(runId) ?? null;
533
+ }
534
+ getForegroundRun() {
535
+ for (const run of this.runs.values()) {
536
+ if (run.owner === "foreground" && this.isActive(run.state)) {
537
+ return run;
538
+ }
539
+ }
540
+ return null;
541
+ }
542
+ listActiveRuns(owner) {
543
+ const runs = [];
544
+ for (const run of this.runs.values()) {
545
+ if (!this.isActive(run.state)) {
546
+ continue;
547
+ }
548
+ if (owner && run.owner !== owner) {
549
+ continue;
550
+ }
551
+ runs.push(run);
552
+ }
553
+ return runs;
554
+ }
555
+ hasActiveRuns(owner) {
556
+ for (const run of this.runs.values()) {
557
+ if (!this.isActive(run.state)) {
558
+ continue;
559
+ }
560
+ if (owner && run.owner !== owner) {
561
+ continue;
562
+ }
563
+ return true;
564
+ }
565
+ return false;
566
+ }
567
+ getLatestActiveRun(owner) {
568
+ let latest = null;
569
+ for (const run of this.runs.values()) {
570
+ if (!this.isActive(run.state)) {
571
+ continue;
572
+ }
573
+ if (owner && run.owner !== owner) {
574
+ continue;
575
+ }
576
+ latest = run;
577
+ }
578
+ return latest;
579
+ }
580
+ isRunActive(run) {
581
+ if (!run) {
582
+ return false;
583
+ }
584
+ return this.isActive(run.state);
585
+ }
586
+ resolveByIdentifiers(identifiers) {
587
+ if (identifiers.taskId) {
588
+ const run = this.resolveMappedRun(this.runByTaskId, identifiers.taskId);
589
+ if (run) {
590
+ return { run, reason: "task_id" };
591
+ }
592
+ }
593
+ if (identifiers.parentMessageId) {
594
+ const run = this.resolveMappedRun(this.runByParentMessageId, identifiers.parentMessageId);
595
+ if (run) {
596
+ return { run, reason: "parent_message_id" };
597
+ }
598
+ }
599
+ if (identifiers.messageId) {
600
+ const run = this.resolveMappedRun(this.runByMessageId, identifiers.messageId);
601
+ if (run) {
602
+ return { run, reason: "message_id" };
603
+ }
604
+ }
605
+ return { run: null, reason: "metadata" };
606
+ }
607
+ bindIdentifiers(run, identifiers) {
608
+ if (identifiers.taskId) {
609
+ run.taskIds.add(identifiers.taskId);
610
+ this.runByTaskId.set(identifiers.taskId, run.id);
611
+ }
612
+ if (identifiers.parentMessageId) {
613
+ run.parentMessageIds.add(identifiers.parentMessageId);
614
+ this.runByParentMessageId.set(identifiers.parentMessageId, run.id);
615
+ }
616
+ if (identifiers.messageId) {
617
+ run.messageIds.add(identifiers.messageId);
618
+ this.runByMessageId.set(identifiers.messageId, run.id);
619
+ }
620
+ }
621
+ transition(run, nextState) {
622
+ run.state = nextState;
623
+ }
624
+ complete(run, terminalState) {
625
+ run.state = terminalState;
626
+ this.clearRunIndex(run);
627
+ }
628
+ deriveLifecycle(pendingPermissionCount) {
629
+ for (const run of this.runs.values()) {
630
+ if (this.isActive(run.state)) {
631
+ return "running";
632
+ }
633
+ }
634
+ if (pendingPermissionCount > 0) {
635
+ return "permission";
636
+ }
637
+ for (const run of this.runs.values()) {
638
+ if (run.state === "error") {
639
+ return "error";
640
+ }
641
+ }
642
+ return "idle";
643
+ }
644
+ resolveMappedRun(mapping, identifier) {
645
+ const runId = mapping.get(identifier);
646
+ if (!runId) {
647
+ return null;
648
+ }
649
+ const run = this.runs.get(runId);
650
+ if (!run || !this.isActive(run.state)) {
651
+ mapping.delete(identifier);
652
+ return null;
653
+ }
654
+ return run;
655
+ }
656
+ clearRunIndex(run) {
657
+ for (const taskId of run.taskIds) {
658
+ this.runByTaskId.delete(taskId);
659
+ }
660
+ for (const parentMessageId of run.parentMessageIds) {
661
+ this.runByParentMessageId.delete(parentMessageId);
662
+ }
663
+ for (const messageId of run.messageIds) {
664
+ this.runByMessageId.delete(messageId);
665
+ }
666
+ run.taskIds.clear();
667
+ run.parentMessageIds.clear();
668
+ run.messageIds.clear();
669
+ }
670
+ isActive(state) {
671
+ return ACTIVE_RUN_STATES.has(state);
672
+ }
673
+ }
674
+ class TimelineAssembler {
675
+ constructor() {
676
+ this.messages = new Map();
677
+ this.activeMessageByRun = new Map();
678
+ this.syntheticMessageCounter = 0;
679
+ }
680
+ consume(input) {
681
+ if (input.message.type === "assistant") {
682
+ return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
683
+ }
684
+ if (input.message.type === "stream_event") {
685
+ return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
686
+ }
687
+ return [];
688
+ }
689
+ consumeAssistantMessage(message, runId, messageIdHint) {
690
+ const messageId = this.readMessageIdFromAssistantMessage(message) ??
691
+ messageIdHint ??
692
+ this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
693
+ if (!messageId) {
694
+ return [];
695
+ }
696
+ const state = this.ensureMessageState(messageId, runId);
697
+ const fragments = this.extractFragments(message.message?.content);
698
+ return this.applyAbsoluteFragments(state, fragments);
699
+ }
700
+ consumeStreamEvent(message, runId, messageIdHint) {
701
+ const event = message.event;
702
+ const eventType = readTrimmedString(event.type);
703
+ const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
704
+ if (eventType === "message_start") {
705
+ const messageId = this.resolveMessageId({
706
+ runId,
707
+ createIfMissing: true,
708
+ messageId: streamEventMessageId,
709
+ });
710
+ if (!messageId) {
711
+ return [];
712
+ }
713
+ this.ensureMessageState(messageId, runId);
714
+ return [];
715
+ }
716
+ if (eventType === "message_stop") {
717
+ const messageId = this.resolveMessageId({
718
+ runId,
719
+ createIfMissing: false,
720
+ messageId: streamEventMessageId,
721
+ });
722
+ if (!messageId) {
723
+ return [];
724
+ }
725
+ return this.finalizeMessage(messageId, runId);
726
+ }
727
+ if (eventType === "content_block_start") {
728
+ return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
729
+ }
730
+ if (eventType === "content_block_delta") {
731
+ return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
732
+ }
733
+ return [];
734
+ }
735
+ consumeDeltaContent(content, runId, messageIdHint) {
736
+ const fragments = this.extractFragments(content);
737
+ if (fragments.length === 0) {
738
+ return [];
739
+ }
740
+ const messageId = this.resolveMessageId({
741
+ runId,
742
+ createIfMissing: true,
743
+ messageId: messageIdHint,
744
+ });
745
+ if (!messageId) {
746
+ return [];
747
+ }
748
+ const state = this.ensureMessageState(messageId, runId);
749
+ return this.appendFragments(state, fragments);
750
+ }
751
+ appendFragments(state, fragments) {
752
+ for (const fragment of fragments) {
753
+ if (fragment.kind === "assistant") {
754
+ state.assistantText += fragment.text;
755
+ }
756
+ else {
757
+ state.reasoningText += fragment.text;
758
+ }
759
+ }
760
+ return this.emitNewContent(state);
761
+ }
762
+ applyAbsoluteFragments(state, fragments) {
763
+ const assistantText = fragments
764
+ .filter((fragment) => fragment.kind === "assistant")
765
+ .map((fragment) => fragment.text)
766
+ .join("");
767
+ const reasoningText = fragments
768
+ .filter((fragment) => fragment.kind === "reasoning")
769
+ .map((fragment) => fragment.text)
770
+ .join("");
771
+ if (assistantText.length > 0) {
772
+ if (!assistantText.startsWith(state.assistantText)) {
773
+ state.emittedAssistantLength = 0;
774
+ }
775
+ state.assistantText = assistantText;
776
+ }
777
+ if (reasoningText.length > 0) {
778
+ if (!reasoningText.startsWith(state.reasoningText)) {
779
+ state.emittedReasoningLength = 0;
780
+ }
781
+ state.reasoningText = reasoningText;
782
+ }
783
+ return this.emitNewContent(state);
784
+ }
785
+ finalizeMessage(messageId, runId) {
786
+ const state = this.messages.get(messageId);
787
+ if (!state) {
788
+ return [];
789
+ }
790
+ state.stopped = true;
791
+ const items = this.emitNewContent(state);
792
+ if (runId && this.activeMessageByRun.get(runId) === messageId) {
793
+ this.activeMessageByRun.delete(runId);
794
+ }
795
+ return items;
796
+ }
797
+ emitNewContent(state) {
798
+ const items = [];
799
+ const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
800
+ if (nextAssistantText.length > 0 &&
801
+ nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER) {
802
+ state.emittedAssistantLength = state.assistantText.length;
803
+ items.push({ type: "assistant_message", text: nextAssistantText });
804
+ }
805
+ const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
806
+ if (nextReasoningText.length > 0) {
807
+ state.emittedReasoningLength = state.reasoningText.length;
808
+ items.push({ type: "reasoning", text: nextReasoningText });
809
+ }
810
+ return items;
811
+ }
812
+ ensureMessageState(messageId, runId) {
813
+ const existing = this.messages.get(messageId);
814
+ if (existing) {
815
+ existing.stopped = false;
816
+ if (runId) {
817
+ this.activeMessageByRun.set(runId, messageId);
818
+ }
819
+ return existing;
820
+ }
821
+ const created = {
822
+ id: messageId,
823
+ assistantText: "",
824
+ reasoningText: "",
825
+ emittedAssistantLength: 0,
826
+ emittedReasoningLength: 0,
827
+ stopped: false,
828
+ };
829
+ this.messages.set(messageId, created);
830
+ if (runId) {
831
+ this.activeMessageByRun.set(runId, messageId);
832
+ }
833
+ return created;
834
+ }
835
+ resolveMessageId(input) {
836
+ if (input.messageId) {
837
+ return input.messageId;
838
+ }
839
+ if (input.runId) {
840
+ const active = this.activeMessageByRun.get(input.runId);
841
+ if (active) {
842
+ return active;
843
+ }
844
+ }
845
+ if (!input.createIfMissing) {
846
+ return null;
847
+ }
848
+ const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
849
+ if (input.runId) {
850
+ this.activeMessageByRun.set(input.runId, synthetic);
851
+ }
852
+ return synthetic;
853
+ }
854
+ extractFragments(content) {
855
+ if (typeof content === "string") {
856
+ if (content.length === 0) {
857
+ return [];
858
+ }
859
+ return [{ kind: "assistant", text: content }];
860
+ }
861
+ const blocks = Array.isArray(content) ? content : [content];
862
+ const fragments = [];
863
+ for (const rawBlock of blocks) {
864
+ if (!isClaudeContentChunk(rawBlock)) {
865
+ continue;
866
+ }
867
+ if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
868
+ typeof rawBlock.text === "string" &&
869
+ rawBlock.text.length > 0) {
870
+ fragments.push({ kind: "assistant", text: rawBlock.text });
871
+ }
872
+ if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
873
+ typeof rawBlock.thinking === "string" &&
874
+ rawBlock.thinking.length > 0) {
875
+ fragments.push({ kind: "reasoning", text: rawBlock.thinking });
876
+ }
877
+ }
878
+ return fragments;
879
+ }
880
+ readMessageIdFromAssistantMessage(message) {
881
+ const candidate = message;
882
+ return readTrimmedString(candidate.message_id) ??
883
+ readTrimmedString(candidate.message?.id) ??
884
+ null;
885
+ }
886
+ readMessageIdFromStreamEvent(event) {
887
+ const message = event.message;
888
+ return (readTrimmedString(event.message_id) ??
889
+ readTrimmedString(message?.id) ??
890
+ null);
891
+ }
892
+ }
893
+ function isMetadataOnlySdkMessage(message) {
894
+ if (message.type === "system") {
895
+ return true;
896
+ }
897
+ if (message.type !== "user") {
898
+ return false;
899
+ }
900
+ if (isSyntheticUserEntry(message)) {
901
+ return true;
902
+ }
903
+ return isTaskNotificationUserContent(message.message?.content);
904
+ }
905
+ function isSyntheticUserEntry(entry) {
906
+ if (!entry || typeof entry !== "object") {
907
+ return false;
908
+ }
909
+ return entry.isSynthetic === true;
910
+ }
911
+ export function readEventIdentifiers(message) {
912
+ const root = message;
913
+ const messageType = readTrimmedString(root.type);
914
+ const streamEvent = root.event;
915
+ const streamEventMessage = streamEvent?.message;
916
+ const messageContainer = root.message;
917
+ return {
918
+ taskId: readTrimmedString(root.task_id) ??
919
+ readTrimmedString(streamEvent?.task_id) ??
920
+ readTrimmedString(streamEventMessage?.task_id) ??
921
+ readTrimmedString(messageContainer?.task_id) ??
922
+ null,
923
+ parentMessageId: readTrimmedString(root.parent_message_id) ??
924
+ readTrimmedString(streamEvent?.parent_message_id) ??
925
+ readTrimmedString(streamEventMessage?.parent_message_id) ??
926
+ readTrimmedString(messageContainer?.parent_message_id) ??
927
+ null,
928
+ messageId: readTrimmedString(root.message_id) ??
929
+ readTrimmedString(streamEvent?.message_id) ??
930
+ readTrimmedString(streamEventMessage?.id) ??
931
+ readTrimmedString(streamEventMessage?.message_id) ??
932
+ readTrimmedString(messageContainer?.id) ??
933
+ readTrimmedString(messageContainer?.message_id) ??
934
+ (messageType === "user" ? readTrimmedString(root.uuid) : null) ??
935
+ null,
936
+ };
937
+ }
315
938
  export class ClaudeAgentClient {
316
939
  constructor(options) {
317
940
  this.provider = "claude";
@@ -326,26 +949,6 @@ export class ClaudeAgentClient {
326
949
  this.claudePath = null;
327
950
  }
328
951
  }
329
- applyRuntimeSettings(options) {
330
- const hasEnvOverrides = Object.keys(this.runtimeSettings?.env ?? {}).length > 0;
331
- const commandMode = this.runtimeSettings?.command?.mode;
332
- const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
333
- if (!needsCustomSpawn) {
334
- return options;
335
- }
336
- return {
337
- ...options,
338
- spawnClaudeCodeProcess: (spawnOptions) => {
339
- const resolved = resolveClaudeSpawnCommand(spawnOptions, this.runtimeSettings);
340
- return spawn(resolved.command, resolved.args, {
341
- cwd: spawnOptions.cwd,
342
- env: applyProviderEnv(spawnOptions.env, this.runtimeSettings),
343
- signal: spawnOptions.signal,
344
- stdio: ["pipe", "pipe", "pipe"],
345
- });
346
- },
347
- };
348
- }
349
952
  async createSession(config) {
350
953
  const claudeConfig = this.assertConfig(config);
351
954
  return new ClaudeAgentSession(claudeConfig, {
@@ -371,45 +974,8 @@ export class ClaudeAgentClient {
371
974
  logger: this.logger,
372
975
  });
373
976
  }
374
- async listModels(options) {
375
- const prompt = (async function* empty() { })();
376
- const claudeOptions = {
377
- cwd: options?.cwd ?? process.cwd(),
378
- permissionMode: "plan",
379
- includePartialMessages: false,
380
- ...(this.claudePath ? { pathToClaudeCodeExecutable: this.claudePath } : {}),
381
- };
382
- const claudeQuery = query({
383
- prompt,
384
- options: this.applyRuntimeSettings(claudeOptions),
385
- });
386
- try {
387
- const models = await claudeQuery.supportedModels();
388
- return models.map((model) => ({
389
- provider: "claude",
390
- id: model.value,
391
- label: normalizeClaudeModelLabel(model),
392
- description: model.description,
393
- thinkingOptions: [
394
- { id: "off", label: "Off", isDefault: true },
395
- { id: "on", label: "On" },
396
- ],
397
- defaultThinkingOptionId: "off",
398
- metadata: {
399
- description: model.description,
400
- },
401
- }));
402
- }
403
- finally {
404
- if (typeof claudeQuery.return === "function") {
405
- try {
406
- await claudeQuery.return();
407
- }
408
- catch {
409
- // ignore shutdown errors
410
- }
411
- }
412
- }
977
+ async listModels(_options) {
978
+ return listClaudeCatalogModels();
413
979
  }
414
980
  async listPersistedAgents(options) {
415
981
  const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
@@ -456,28 +1022,32 @@ class ClaudeAgentSession {
456
1022
  this.toolUseIndexToId = new Map();
457
1023
  this.toolUseInputBuffers = new Map();
458
1024
  this.pendingPermissions = new Map();
459
- this.eventQueue = null;
1025
+ this.activeForegroundTurn = null;
1026
+ this.liveEventQueue = new Pushable();
1027
+ this.runTracker = new RunTracker();
1028
+ this.timelineAssembler = new TimelineAssembler();
460
1029
  this.persistedHistory = [];
461
1030
  this.historyPending = false;
462
- this.turnCancelRequested = false;
463
- // NOTE: streamedAssistantTextThisTurn and streamedReasoningThisTurn were removed
464
- // These flags are now tracked per-turn via TurnContext to prevent race conditions
465
- // when multiple stream() calls overlap (e.g., interrupt + new message)
1031
+ this.turnState = "idle";
1032
+ this.preReplayMetadataSeen = false;
1033
+ this.pendingAutonomousWakeReservations = 0;
1034
+ this.nextRunOrdinal = 1;
466
1035
  this.cancelCurrentTurn = null;
467
- // Track the pending interrupt promise so we can await it in processPrompt
468
- // This ensures the interrupt's response is consumed before we call query.next()
469
1036
  this.pendingInterruptPromise = null;
470
- // Track the current turn ID and active turn promise to serialize concurrent stream() calls
471
- // and prevent race conditions where two processPrompt() loops run against the same query
472
- this.currentTurnId = 0;
473
1037
  this.activeTurnPromise = null;
474
1038
  this.cachedRuntimeInfo = null;
475
1039
  this.lastOptionsModel = null;
476
- this.selectableModelIds = null;
1040
+ this.selectableModelIds = buildClaudeSelectableModelIds();
1041
+ this.selectableModelFamilyAliases = buildClaudeModelFamilyAliases();
477
1042
  this.activeSidechains = new Map();
478
1043
  this.compacting = false;
1044
+ this.queryPumpPromise = null;
479
1045
  this.queryRestartNeeded = false;
480
1046
  this.userMessageIds = [];
1047
+ this.localUserMessageIds = new Set();
1048
+ this.suppressLocalReplayActivity = false;
1049
+ this.recentStderr = "";
1050
+ this.closed = false;
481
1051
  this.handlePermissionRequest = async (toolName, input, options) => {
482
1052
  const requestId = `permission-${randomUUID()}`;
483
1053
  const kind = resolvePermissionKind(toolName, input);
@@ -520,19 +1090,6 @@ class ClaudeAgentSession {
520
1090
  }
521
1091
  }
522
1092
  };
523
- const timeout = setTimeout(() => {
524
- this.pendingPermissions.delete(requestId);
525
- cleanup();
526
- const error = new Error("Permission request timed out");
527
- this.pushEvent({
528
- type: "permission_resolved",
529
- provider: "claude",
530
- requestId,
531
- resolution: { behavior: "deny", message: "timeout" },
532
- });
533
- reject(error);
534
- }, DEFAULT_PERMISSION_TIMEOUT_MS);
535
- cleanupFns.push(() => clearTimeout(timeout));
536
1093
  const abortHandler = () => {
537
1094
  this.pendingPermissions.delete(requestId);
538
1095
  cleanup();
@@ -640,81 +1197,125 @@ class ClaudeAgentSession {
640
1197
  }
641
1198
  async *stream(prompt, options) {
642
1199
  void options;
643
- // Increment turn ID to invalidate any in-flight processPrompt() loops from previous turns.
644
- // This prevents race conditions where an interrupted turn's events get mixed with the new turn.
645
- const turnId = ++this.currentTurnId;
646
- // Cancel the previous turn if one exists. The caller of interrupt() is responsible
647
- // for awaiting completion - the new turn just signals cancellation and proceeds.
648
1200
  if (this.cancelCurrentTurn) {
649
1201
  this.cancelCurrentTurn();
650
1202
  }
651
- // Reset cancel flag at the start of each turn to prevent stale state from previous turns
652
- this.turnCancelRequested = false;
1203
+ this.suppressLocalReplayActivity = false;
1204
+ this.pendingAutonomousWakeReservations = 0;
653
1205
  const slashCommand = this.resolveSlashCommandInvocation(prompt);
654
1206
  if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
655
1207
  yield* this.streamRewindCommand(slashCommand);
656
1208
  return;
657
1209
  }
1210
+ await this.awaitPendingInterruptPromise();
1211
+ if (this.turnState === "autonomous" &&
1212
+ this.runTracker.hasActiveRuns("autonomous")) {
1213
+ await this.transitionAutonomousToForeground();
1214
+ }
658
1215
  const sdkMessage = this.toSdkUserMessage(prompt);
659
1216
  const queue = new Pushable();
660
- this.eventQueue = queue;
1217
+ const run = this.createRun("foreground", queue);
1218
+ this.runTracker.bindIdentifiers(run, {
1219
+ taskId: null,
1220
+ parentMessageId: null,
1221
+ messageId: typeof sdkMessage.uuid === "string" ? sdkMessage.uuid : null,
1222
+ });
1223
+ const foregroundTurn = {
1224
+ runId: run.id,
1225
+ queue,
1226
+ };
1227
+ this.activeForegroundTurn = foregroundTurn;
1228
+ this.preReplayMetadataSeen = false;
1229
+ this.transitionTurnState("foreground", "foreground stream started");
1230
+ this.clearRecentStderr();
661
1231
  let finishedNaturally = false;
662
1232
  let cancelIssued = false;
1233
+ let queueDrainedWithoutTerminal = false;
1234
+ const turnPromise = Promise.resolve();
1235
+ this.activeTurnPromise = turnPromise;
663
1236
  const requestCancel = () => {
664
1237
  if (cancelIssued) {
665
1238
  return;
666
1239
  }
667
1240
  cancelIssued = true;
668
- this.turnCancelRequested = true;
669
- // Store the interrupt promise so processPrompt can await it before calling query.next()
670
- this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
671
- this.logger.warn({ err: error }, "Failed to interrupt during cancel");
672
- });
673
- this.flushPendingToolCalls();
674
- // Push turn_canceled before ending the queue so consumers get proper lifecycle signals
675
- queue.push({
1241
+ if (this.activeForegroundTurn?.runId === run.id) {
1242
+ this.activeForegroundTurn = null;
1243
+ }
1244
+ if (this.cancelCurrentTurn === requestCancel) {
1245
+ this.cancelCurrentTurn = null;
1246
+ }
1247
+ this.rejectAllPendingPermissions(new Error("Permission request aborted"));
1248
+ this.cancelRun(run, {
676
1249
  type: "turn_canceled",
677
1250
  provider: "claude",
678
1251
  reason: "Interrupted",
679
1252
  });
680
- queue.end();
1253
+ this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
1254
+ this.logger.warn({ err: error }, "Failed to interrupt during cancel");
1255
+ });
681
1256
  };
682
1257
  this.cancelCurrentTurn = requestCancel;
683
- // Start forwarding events and track the promise so future turns can wait for completion
684
- const forwardPromise = this.forwardPromptEvents(sdkMessage, queue, turnId);
685
- this.activeTurnPromise = forwardPromise;
686
- forwardPromise.catch((error) => {
687
- this.logger.error({ err: error }, "Unexpected error in forwardPromptEvents");
688
- });
1258
+ try {
1259
+ await this.ensureQuery();
1260
+ if (!this.input) {
1261
+ throw new Error("Claude session input stream not initialized");
1262
+ }
1263
+ this.startQueryPump();
1264
+ this.input.push(sdkMessage);
1265
+ }
1266
+ catch (error) {
1267
+ this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
1268
+ finishedNaturally = true;
1269
+ }
689
1270
  try {
690
1271
  for await (const event of queue) {
691
- yield event;
692
- if (event.type === "turn_completed" ||
1272
+ const isTerminalEvent = event.type === "turn_completed" ||
693
1273
  event.type === "turn_failed" ||
694
- event.type === "turn_canceled") {
1274
+ event.type === "turn_canceled";
1275
+ if (isTerminalEvent) {
695
1276
  finishedNaturally = true;
1277
+ }
1278
+ yield event;
1279
+ if (isTerminalEvent) {
696
1280
  break;
697
1281
  }
698
1282
  }
1283
+ if (!finishedNaturally && !cancelIssued) {
1284
+ queueDrainedWithoutTerminal = true;
1285
+ }
699
1286
  }
700
1287
  finally {
701
- if (!finishedNaturally && !cancelIssued) {
1288
+ if (!finishedNaturally && !cancelIssued && !queueDrainedWithoutTerminal) {
702
1289
  requestCancel();
703
1290
  }
704
- if (this.eventQueue === queue) {
705
- this.eventQueue = null;
1291
+ if (this.activeForegroundTurn === foregroundTurn) {
1292
+ this.activeForegroundTurn = null;
706
1293
  }
707
1294
  if (this.cancelCurrentTurn === requestCancel) {
708
1295
  this.cancelCurrentTurn = null;
709
1296
  }
710
- // Clear the active turn promise if it's still ours
711
- if (this.activeTurnPromise === forwardPromise) {
1297
+ if (this.activeTurnPromise === turnPromise) {
712
1298
  this.activeTurnPromise = null;
713
1299
  }
714
1300
  }
715
1301
  }
716
1302
  async interrupt() {
717
- this.cancelCurrentTurn?.();
1303
+ if (this.cancelCurrentTurn) {
1304
+ this.cancelCurrentTurn();
1305
+ return;
1306
+ }
1307
+ const autonomousRuns = this.runTracker.listActiveRuns("autonomous");
1308
+ if (autonomousRuns.length > 0) {
1309
+ this.flushPendingToolCalls();
1310
+ for (const run of autonomousRuns) {
1311
+ this.emitRunEvent(run, {
1312
+ type: "turn_canceled",
1313
+ provider: "claude",
1314
+ reason: "Interrupted",
1315
+ });
1316
+ }
1317
+ }
1318
+ await this.interruptActiveTurn();
718
1319
  }
719
1320
  async *streamHistory() {
720
1321
  if (!this.historyPending || this.persistedHistory.length === 0) {
@@ -727,6 +1328,14 @@ class ClaudeAgentSession {
727
1328
  yield { type: "timeline", item, provider: "claude" };
728
1329
  }
729
1330
  }
1331
+ async *streamLiveEvents() {
1332
+ if (this.claudeSessionId) {
1333
+ this.startQueryPump();
1334
+ }
1335
+ for await (const event of this.liveEventQueue) {
1336
+ yield event;
1337
+ }
1338
+ }
730
1339
  async getAvailableModes() {
731
1340
  return this.availableModes;
732
1341
  }
@@ -841,12 +1450,30 @@ class ClaudeAgentSession {
841
1450
  return this.persistence;
842
1451
  }
843
1452
  async close() {
1453
+ this.logger.trace({
1454
+ claudeSessionId: this.claudeSessionId,
1455
+ turnState: this.turnState,
1456
+ hasQuery: Boolean(this.query),
1457
+ hasInput: Boolean(this.input),
1458
+ hasActiveForegroundTurn: Boolean(this.activeForegroundTurn),
1459
+ }, "Claude session close: start");
1460
+ this.closed = true;
844
1461
  this.rejectAllPendingPermissions(new Error("Claude session closed"));
1462
+ this.cancelCurrentTurn?.();
1463
+ this.activeForegroundTurn?.queue.end();
1464
+ this.activeForegroundTurn = null;
1465
+ this.cancelCurrentTurn = null;
1466
+ this.turnState = "idle";
1467
+ this.suppressLocalReplayActivity = false;
1468
+ this.pendingAutonomousWakeReservations = 0;
1469
+ this.liveEventQueue.end();
1470
+ this.activeTurnPromise = null;
845
1471
  this.input?.end();
846
- await this.query?.interrupt?.();
847
- await this.query?.return?.();
1472
+ await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
1473
+ await this.awaitWithTimeout(this.query?.return?.(), "close query return");
848
1474
  this.query = null;
849
1475
  this.input = null;
1476
+ this.logger.trace({ claudeSessionId: this.claudeSessionId, turnState: this.turnState }, "Claude session close: completed");
850
1477
  }
851
1478
  async listCommands() {
852
1479
  const q = await this.ensureQuery();
@@ -1081,20 +1708,6 @@ class ClaudeAgentSession {
1081
1708
  }
1082
1709
  this.userMessageIds.push(messageId);
1083
1710
  }
1084
- async primeSelectableModelIds(query) {
1085
- try {
1086
- const models = await query.supportedModels();
1087
- const ids = models
1088
- .map((model) => model.value?.trim())
1089
- .filter((id) => typeof id === "string" && id.length > 0);
1090
- this.selectableModelIds = new Set(ids);
1091
- this.logger.debug({ modelIds: ids }, "Primed Claude selectable model IDs");
1092
- }
1093
- catch (error) {
1094
- this.selectableModelIds = null;
1095
- this.logger.warn({ err: error }, "Failed to prime Claude selectable model IDs");
1096
- }
1097
- }
1098
1711
  async ensureQuery() {
1099
1712
  if (this.query && !this.queryRestartNeeded) {
1100
1713
  return this.query;
@@ -1111,13 +1724,35 @@ class ClaudeAgentSession {
1111
1724
  }
1112
1725
  const input = new Pushable();
1113
1726
  const options = this.buildOptions();
1114
- this.logger.debug({ options }, "claude query");
1727
+ this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
1115
1728
  this.input = input;
1116
1729
  this.query = query({ prompt: input, options });
1117
- await this.primeSelectableModelIds(this.query);
1118
- await this.query.setPermissionMode(this.currentMode);
1730
+ // Do not kick off background control-plane queries here. Methods like
1731
+ // supportedCommands()/setPermissionMode() may execute immediately after
1732
+ // ensureQuery() (for listCommands()/setMode()), and sharing the same query
1733
+ // control plane can cause those calls to wait behind supportedModels().
1119
1734
  return this.query;
1120
1735
  }
1736
+ async awaitWithTimeout(promise, label) {
1737
+ if (!promise) {
1738
+ this.logger.trace({ label }, "Claude query operation skipped (no promise)");
1739
+ return;
1740
+ }
1741
+ const startedAt = Date.now();
1742
+ this.logger.trace({ label }, "Claude query operation wait start");
1743
+ try {
1744
+ await Promise.race([
1745
+ promise,
1746
+ new Promise((_, reject) => {
1747
+ setTimeout(() => reject(new Error("timeout")), 3000);
1748
+ }),
1749
+ ]);
1750
+ this.logger.trace({ label, durationMs: Date.now() - startedAt }, "Claude query operation settled");
1751
+ }
1752
+ catch (error) {
1753
+ this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
1754
+ }
1755
+ }
1121
1756
  buildOptions() {
1122
1757
  const configuredThinkingOptionId = this.config.thinkingOptionId;
1123
1758
  const thinkingOptionId = configuredThinkingOptionId && configuredThinkingOptionId !== "default"
@@ -1150,8 +1785,9 @@ class ClaudeAgentSession {
1150
1785
  preset: "claude_code",
1151
1786
  append: appendedSystemPrompt,
1152
1787
  },
1153
- settingSources: ["user", "project"],
1788
+ settingSources: CLAUDE_SETTING_SOURCES,
1154
1789
  stderr: (data) => {
1790
+ this.captureStderr(data);
1155
1791
  this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
1156
1792
  },
1157
1793
  env: {
@@ -1181,24 +1817,7 @@ class ClaudeAgentSession {
1181
1817
  return this.applyRuntimeSettings(base);
1182
1818
  }
1183
1819
  applyRuntimeSettings(options) {
1184
- const hasEnvOverrides = Object.keys(this.runtimeSettings?.env ?? {}).length > 0;
1185
- const commandMode = this.runtimeSettings?.command?.mode;
1186
- const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
1187
- if (!needsCustomSpawn) {
1188
- return options;
1189
- }
1190
- return {
1191
- ...options,
1192
- spawnClaudeCodeProcess: (spawnOptions) => {
1193
- const resolved = resolveClaudeSpawnCommand(spawnOptions, this.runtimeSettings);
1194
- return spawn(resolved.command, resolved.args, {
1195
- cwd: spawnOptions.cwd,
1196
- env: applyProviderEnv(spawnOptions.env, this.runtimeSettings),
1197
- signal: spawnOptions.signal,
1198
- stdio: ["pipe", "pipe", "pipe"],
1199
- });
1200
- },
1201
- };
1820
+ return applyRuntimeSettingsToClaudeOptions(options, this.runtimeSettings);
1202
1821
  }
1203
1822
  normalizeMcpServers(servers) {
1204
1823
  const result = {};
@@ -1226,191 +1845,828 @@ class ClaudeAgentSession {
1226
1845
  }
1227
1846
  }
1228
1847
  }
1229
- else {
1230
- content.push({ type: "text", text: prompt });
1848
+ else {
1849
+ content.push({ type: "text", text: prompt });
1850
+ }
1851
+ const messageId = randomUUID();
1852
+ this.rememberUserMessageId(messageId);
1853
+ this.localUserMessageIds.add(messageId);
1854
+ return {
1855
+ type: "user",
1856
+ message: {
1857
+ role: "user",
1858
+ content,
1859
+ },
1860
+ parent_tool_use_id: null,
1861
+ uuid: messageId,
1862
+ session_id: this.claudeSessionId ?? "",
1863
+ };
1864
+ }
1865
+ async awaitPendingInterruptPromise() {
1866
+ if (!this.pendingInterruptPromise) {
1867
+ return;
1868
+ }
1869
+ await this.pendingInterruptPromise;
1870
+ this.pendingInterruptPromise = null;
1871
+ }
1872
+ createRun(owner, queue) {
1873
+ const runId = `${owner}-run-${this.nextRunOrdinal++}`;
1874
+ const run = this.runTracker.createRun({
1875
+ id: runId,
1876
+ owner,
1877
+ queue,
1878
+ promptReplaySeen: owner === "autonomous",
1879
+ });
1880
+ this.logger.debug({ runId, owner, state: run.state }, "Created Claude run");
1881
+ return run;
1882
+ }
1883
+ transitionTurnState(next, reason) {
1884
+ if (this.turnState === next) {
1885
+ return;
1886
+ }
1887
+ this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
1888
+ this.turnState = next;
1889
+ }
1890
+ transitionTurnStateFromActiveRuns(reason) {
1891
+ if (this.runTracker.hasActiveRuns("foreground")) {
1892
+ this.transitionTurnState("foreground", reason);
1893
+ return;
1894
+ }
1895
+ if (this.runTracker.hasActiveRuns("autonomous")) {
1896
+ this.transitionTurnState("autonomous", reason);
1897
+ return;
1898
+ }
1899
+ this.transitionTurnState("idle", reason);
1900
+ }
1901
+ failRun(run, errorMessage) {
1902
+ this.emitRunEvent(run, this.buildTurnFailedEvent(errorMessage));
1903
+ }
1904
+ buildTurnFailedEvent(errorMessage) {
1905
+ const normalized = errorMessage.trim() || "Claude run failed";
1906
+ const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
1907
+ const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
1908
+ const diagnostic = this.getRecentStderrDiagnostic();
1909
+ return {
1910
+ type: "turn_failed",
1911
+ provider: "claude",
1912
+ error: normalized,
1913
+ ...(code ? { code } : {}),
1914
+ ...(diagnostic ? { diagnostic } : {}),
1915
+ };
1916
+ }
1917
+ captureStderr(data) {
1918
+ const text = data.trim();
1919
+ if (!text) {
1920
+ return;
1921
+ }
1922
+ const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
1923
+ this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
1924
+ }
1925
+ clearRecentStderr() {
1926
+ this.recentStderr = "";
1927
+ }
1928
+ getRecentStderrDiagnostic() {
1929
+ const text = this.recentStderr.trim();
1930
+ return text.length > 0 ? text : undefined;
1931
+ }
1932
+ cancelRun(run, event) {
1933
+ this.flushPendingToolCalls();
1934
+ this.emitRunEvent(run, event);
1935
+ }
1936
+ emitRunEvent(run, event) {
1937
+ if (event.type === "turn_started" ||
1938
+ event.type === "turn_completed" ||
1939
+ event.type === "turn_failed" ||
1940
+ event.type === "turn_canceled") {
1941
+ this.logger.trace({
1942
+ runId: run.id,
1943
+ owner: run.owner,
1944
+ runState: run.state,
1945
+ eventType: event.type,
1946
+ routedTo: run.owner === "foreground" && run.queue ? "foreground_queue" : "live_queue",
1947
+ }, "Claude run event emitted");
1948
+ }
1949
+ if (run.owner === "foreground" && run.queue) {
1950
+ run.queue.push(event);
1951
+ if (event.type === "turn_completed" ||
1952
+ event.type === "turn_failed" ||
1953
+ event.type === "turn_canceled") {
1954
+ run.queue.end();
1955
+ }
1956
+ }
1957
+ else {
1958
+ this.liveEventQueue.push(event);
1959
+ }
1960
+ this.handleRunTerminalEvent(run, event);
1961
+ }
1962
+ handleRunTerminalEvent(run, event) {
1963
+ if (event.type === "turn_completed") {
1964
+ this.runTracker.complete(run, "completed");
1965
+ }
1966
+ else if (event.type === "turn_failed") {
1967
+ this.runTracker.complete(run, "error");
1968
+ }
1969
+ else if (event.type === "turn_canceled") {
1970
+ this.runTracker.complete(run, "interrupted");
1971
+ }
1972
+ else {
1973
+ return;
1974
+ }
1975
+ if (this.activeForegroundTurn?.runId === run.id) {
1976
+ this.activeForegroundTurn = null;
1977
+ this.preReplayMetadataSeen = false;
1978
+ }
1979
+ this.logger.trace({
1980
+ runId: run.id,
1981
+ owner: run.owner,
1982
+ eventType: event.type,
1983
+ runState: run.state,
1984
+ hasActiveForegroundTurn: Boolean(this.activeForegroundTurn),
1985
+ }, "Claude run terminal event handled");
1986
+ this.transitionTurnStateFromActiveRuns(`run ${run.id} terminal`);
1987
+ }
1988
+ async transitionAutonomousToForeground() {
1989
+ const autonomousRuns = this.runTracker.listActiveRuns("autonomous");
1990
+ if (autonomousRuns.length === 0) {
1991
+ this.transitionTurnStateFromActiveRuns("no autonomous runs to transition");
1992
+ return;
1993
+ }
1994
+ this.logger.debug({ runIds: autonomousRuns.map((run) => run.id) }, "Transitioning autonomous runs to foreground ownership");
1995
+ this.flushPendingToolCalls();
1996
+ for (const run of autonomousRuns) {
1997
+ this.emitRunEvent(run, {
1998
+ type: "turn_canceled",
1999
+ provider: "claude",
2000
+ reason: "Interrupted by foreground prompt",
2001
+ });
2002
+ }
2003
+ this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
2004
+ this.logger.warn({ err: error }, "Failed to interrupt autonomous run during foreground transition");
2005
+ });
2006
+ await this.awaitPendingInterruptPromise();
2007
+ this.transitionTurnStateFromActiveRuns("autonomous interrupted for foreground");
2008
+ }
2009
+ routeMessage(normalized) {
2010
+ if (normalized.metadataOnly) {
2011
+ if (normalized.message.type === "user" &&
2012
+ isTaskNotificationUserContent(normalized.message.message?.content)) {
2013
+ this.reserveAutonomousWake("task_notification");
2014
+ }
2015
+ this.notePreReplayMetadata(normalized.message);
2016
+ return { run: null, reason: "metadata" };
2017
+ }
2018
+ const hasIdentifiers = Boolean(normalized.identifiers.taskId ||
2019
+ normalized.identifiers.parentMessageId ||
2020
+ normalized.identifiers.messageId);
2021
+ const byIdentifiers = this.runTracker.resolveByIdentifiers(normalized.identifiers);
2022
+ if (byIdentifiers.run) {
2023
+ return byIdentifiers;
2024
+ }
2025
+ if (this.turnState === "autonomous") {
2026
+ const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
2027
+ if (activeAutonomousRun) {
2028
+ return { run: activeAutonomousRun, reason: "unbound_autonomous" };
2029
+ }
2030
+ }
2031
+ const foregroundRun = this.activeForegroundTurn
2032
+ ? this.runTracker.getRun(this.activeForegroundTurn.runId)
2033
+ : null;
2034
+ // A previously unseen task_id during foreground ownership is deterministic
2035
+ // evidence of a distinct autonomous wake/run, not foreground response text.
2036
+ if (this.turnState === "foreground" &&
2037
+ foregroundRun &&
2038
+ normalized.identifiers.taskId) {
2039
+ const incomingTaskId = normalized.identifiers.taskId;
2040
+ // Foreground must claim its first task_id; otherwise early foreground
2041
+ // result events can be misrouted to autonomous fallback runs.
2042
+ if (foregroundRun.taskIds.size === 0) {
2043
+ if (foregroundRun.state !== "finalizing") {
2044
+ return { run: foregroundRun, reason: "foreground" };
2045
+ }
2046
+ }
2047
+ else if (foregroundRun.taskIds.has(incomingTaskId)) {
2048
+ return { run: foregroundRun, reason: "foreground" };
2049
+ }
2050
+ const autonomousRun = this.createRun("autonomous", null);
2051
+ this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
2052
+ return { run: autonomousRun, reason: "task_id_new" };
2053
+ }
2054
+ if (this.turnState === "foreground" &&
2055
+ foregroundRun &&
2056
+ this.shouldPreferForegroundRun({
2057
+ run: foregroundRun,
2058
+ message: normalized.message,
2059
+ })) {
2060
+ return { run: foregroundRun, reason: "foreground" };
1231
2061
  }
1232
- const messageId = randomUUID();
1233
- this.rememberUserMessageId(messageId);
1234
- return {
1235
- type: "user",
1236
- message: {
1237
- role: "user",
1238
- content,
1239
- },
1240
- parent_tool_use_id: null,
1241
- uuid: messageId,
1242
- session_id: this.claudeSessionId ?? "",
1243
- };
2062
+ if (!hasIdentifiers) {
2063
+ if (this.pendingAutonomousWakeReservations > 0) {
2064
+ const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_unbound");
2065
+ return {
2066
+ run: reservedAutonomousRun,
2067
+ reason: "unbound_autonomous",
2068
+ };
2069
+ }
2070
+ const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
2071
+ if (activeAutonomousRun) {
2072
+ return { run: activeAutonomousRun, reason: "unbound_autonomous" };
2073
+ }
2074
+ }
2075
+ if (this.pendingAutonomousWakeReservations > 0) {
2076
+ const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_fallback");
2077
+ return { run: reservedAutonomousRun, reason: "fallback" };
2078
+ }
2079
+ const autonomousRun = this.createRun("autonomous", null);
2080
+ this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
2081
+ return { run: autonomousRun, reason: "fallback" };
2082
+ }
2083
+ shouldPreferForegroundRun(input) {
2084
+ const { run, message } = input;
2085
+ if (run.state === "completed" ||
2086
+ run.state === "interrupted" ||
2087
+ run.state === "error") {
2088
+ return false;
2089
+ }
2090
+ // Before prompt replay is observed, prefer foreground by default so the
2091
+ // first turn cannot be stranded in autonomous fallback. If metadata churn
2092
+ // was observed pre-replay, stay conservative and wait for replay.
2093
+ if (!run.promptReplaySeen) {
2094
+ if (this.isToolUseBoundaryStreamEvent(input.message)) {
2095
+ return true;
2096
+ }
2097
+ // Keep pre-replay result events with the foreground run so stale result
2098
+ // bursts cannot consume autonomous wake reservations.
2099
+ if (message.type === "result") {
2100
+ return true;
2101
+ }
2102
+ if (message.type === "assistant" ||
2103
+ message.type === "stream_event" ||
2104
+ message.type === "tool_progress") {
2105
+ return !this.preReplayMetadataSeen;
2106
+ }
2107
+ return true;
2108
+ }
2109
+ if (run.state === "finalizing" &&
2110
+ (message.type === "assistant" || message.type === "stream_event")) {
2111
+ return false;
2112
+ }
2113
+ return true;
1244
2114
  }
1245
- async *processPrompt(sdkMessage, turnId) {
1246
- // If there's a pending interrupt, await it BEFORE calling ensureQuery().
1247
- // interruptActiveTurn() clears this.query after interrupt() returns,
1248
- // so we must wait for it to complete before we try to get the query.
1249
- if (this.pendingInterruptPromise) {
1250
- await this.pendingInterruptPromise;
1251
- this.pendingInterruptPromise = null;
2115
+ isToolUseBoundaryStreamEvent(message) {
2116
+ if (message.type !== "stream_event") {
2117
+ return false;
2118
+ }
2119
+ const event = message.event;
2120
+ if (!event || event.type !== "message_delta") {
2121
+ return false;
1252
2122
  }
1253
- // Check if we were superseded while waiting for the interrupt
1254
- if (this.currentTurnId !== turnId) {
2123
+ const delta = "delta" in event && event.delta && typeof event.delta === "object"
2124
+ ? event.delta
2125
+ : null;
2126
+ return delta?.stop_reason === "tool_use";
2127
+ }
2128
+ notePreReplayMetadata(message) {
2129
+ if (this.turnState !== "foreground") {
1255
2130
  return;
1256
2131
  }
1257
- const query = await this.ensureQuery();
1258
- if (!this.input) {
1259
- throw new Error("Claude session input stream not initialized");
2132
+ const foregroundRun = this.activeForegroundTurn
2133
+ ? this.runTracker.getRun(this.activeForegroundTurn.runId)
2134
+ : null;
2135
+ if (!foregroundRun || foregroundRun.promptReplaySeen) {
2136
+ return;
1260
2137
  }
1261
- this.input.push(sdkMessage);
1262
- while (true) {
1263
- // Check if this turn has been superseded by a new one.
1264
- if (this.currentTurnId !== turnId) {
1265
- break;
2138
+ if (message.type === "system" && message.subtype === "init") {
2139
+ return;
2140
+ }
2141
+ this.preReplayMetadataSeen = true;
2142
+ }
2143
+ reserveAutonomousWake(reason) {
2144
+ this.pendingAutonomousWakeReservations += 1;
2145
+ this.logger.debug({
2146
+ reason,
2147
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2148
+ }, "Reserved autonomous wake");
2149
+ }
2150
+ claimOrCreateAutonomousRun(reason) {
2151
+ const existing = this.runTracker.getLatestActiveRun("autonomous");
2152
+ if (existing) {
2153
+ if (this.pendingAutonomousWakeReservations > 0) {
2154
+ this.pendingAutonomousWakeReservations -= 1;
2155
+ }
2156
+ this.logger.debug({
2157
+ reason,
2158
+ runId: existing.id,
2159
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2160
+ }, "Claimed autonomous wake reservation on existing run");
2161
+ return existing;
2162
+ }
2163
+ const run = this.createRun("autonomous", null);
2164
+ this.emitRunEvent(run, { type: "turn_started", provider: "claude" });
2165
+ if (this.pendingAutonomousWakeReservations > 0) {
2166
+ this.pendingAutonomousWakeReservations -= 1;
2167
+ }
2168
+ this.logger.debug({
2169
+ reason,
2170
+ runId: run.id,
2171
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2172
+ }, "Claimed autonomous wake reservation with new run");
2173
+ return run;
2174
+ }
2175
+ startQueryPump() {
2176
+ if (this.closed || this.queryPumpPromise) {
2177
+ return;
2178
+ }
2179
+ const pump = this.runQueryPump().catch((error) => {
2180
+ this.logger.warn({ err: error }, "Claude query pump exited unexpectedly");
2181
+ });
2182
+ this.queryPumpPromise = pump;
2183
+ pump.finally(() => {
2184
+ if (this.queryPumpPromise === pump) {
2185
+ this.queryPumpPromise = null;
1266
2186
  }
1267
- const { value, done } = await query.next();
1268
- if (done) {
1269
- break;
2187
+ });
2188
+ }
2189
+ async runQueryPump() {
2190
+ while (!this.closed) {
2191
+ if (!this.claudeSessionId && !this.activeForegroundTurn && !this.query) {
2192
+ await this.waitForLiveHistoryPoll();
2193
+ continue;
1270
2194
  }
1271
- if (!value) {
2195
+ let q;
2196
+ try {
2197
+ q = await this.ensureQuery();
2198
+ }
2199
+ catch (error) {
2200
+ this.logger.warn({ err: error }, "Failed to initialize Claude query pump");
2201
+ await this.waitForLiveHistoryPoll();
1272
2202
  continue;
1273
2203
  }
1274
- // Double-check turn ID after awaiting, in case a new turn started while we waited
1275
- if (this.currentTurnId !== turnId) {
1276
- break;
2204
+ let next;
2205
+ try {
2206
+ next = await q.next();
2207
+ this.logger.info({ claudeSessionId: this.claudeSessionId, next }, "Claude query pump raw next()");
1277
2208
  }
1278
- yield value;
1279
- if (value.type === "result") {
1280
- break;
2209
+ catch (error) {
2210
+ this.logger.warn({ err: error }, "Claude query pump next() failed");
2211
+ for (const run of this.runTracker.listActiveRuns()) {
2212
+ this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
2213
+ }
2214
+ this.input?.end();
2215
+ await this.awaitWithTimeout(q.return?.(), "query pump return after failure");
2216
+ if (this.query === q) {
2217
+ this.query = null;
2218
+ this.input = null;
2219
+ }
2220
+ await this.waitForLiveHistoryPoll();
2221
+ continue;
1281
2222
  }
1282
- }
1283
- }
1284
- async forwardPromptEvents(message, queue, turnId) {
1285
- // Create a turn-local context to track streaming state.
1286
- // This prevents race conditions when a new stream() call interrupts a running one.
1287
- const turnContext = {
1288
- streamedAssistantTextThisTurn: false,
1289
- streamedReasoningThisTurn: false,
1290
- };
1291
- let completedNormally = false;
1292
- try {
1293
- for await (const sdkEvent of this.processPrompt(message, turnId)) {
1294
- // Check if this turn has been superseded before pushing events
1295
- if (this.currentTurnId !== turnId) {
1296
- break;
2223
+ if (next.done) {
2224
+ this.logger.trace({
2225
+ claudeSessionId: this.claudeSessionId,
2226
+ activeRunCount: this.runTracker.listActiveRuns().length,
2227
+ }, "Claude query pump next() returned done");
2228
+ this.input?.end();
2229
+ await this.awaitWithTimeout(q.return?.(), "query pump return on done");
2230
+ if (this.query === q) {
2231
+ this.query = null;
2232
+ this.input = null;
1297
2233
  }
1298
- const events = this.translateMessageToEvents(sdkEvent, turnContext);
1299
- for (const event of events) {
1300
- queue.push(event);
1301
- if (event.type === "turn_completed") {
1302
- completedNormally = true;
2234
+ const activeRuns = this.runTracker.listActiveRuns();
2235
+ if (activeRuns.length > 0) {
2236
+ for (const run of activeRuns) {
2237
+ this.failRun(run, "Claude stream ended before terminal result");
1303
2238
  }
1304
2239
  }
2240
+ await this.waitForLiveHistoryPoll();
2241
+ continue;
1305
2242
  }
1306
- }
1307
- catch (error) {
1308
- if (!this.turnCancelRequested && this.currentTurnId === turnId) {
1309
- queue.push({
1310
- type: "turn_failed",
1311
- provider: "claude",
1312
- error: error instanceof Error ? error.message : "Claude stream failed",
1313
- });
2243
+ const sdkMessage = next.value;
2244
+ if (!sdkMessage) {
2245
+ continue;
2246
+ }
2247
+ try {
2248
+ this.routeSdkMessageFromPump(sdkMessage);
2249
+ }
2250
+ catch (error) {
2251
+ this.logger.warn({ err: error }, "Failed to route Claude SDK message from query pump");
1314
2252
  }
1315
2253
  }
1316
- finally {
1317
- // Emit terminal event for superseded turns so consumers get proper lifecycle signals.
1318
- // Use turn_canceled (not turn_failed) to distinguish intentional interruption from errors.
1319
- // Only emit if not already emitted by requestCancel() (indicated by turnCancelRequested).
1320
- const wasSuperseded = this.currentTurnId !== turnId;
1321
- if (wasSuperseded && !completedNormally && !this.turnCancelRequested) {
1322
- this.flushPendingToolCalls();
1323
- queue.push({
1324
- type: "turn_canceled",
1325
- provider: "claude",
1326
- reason: "Interrupted by new message",
1327
- });
2254
+ }
2255
+ routeSdkMessageFromPump(message) {
2256
+ if (this.shouldSuppressLocalReplayActivity(message)) {
2257
+ return;
2258
+ }
2259
+ const identifiers = readEventIdentifiers(message);
2260
+ const metadataOnly = isMetadataOnlySdkMessage(message);
2261
+ const route = this.routeMessage({
2262
+ message,
2263
+ identifiers,
2264
+ metadataOnly,
2265
+ });
2266
+ const suppressTerminalEvents = this.shouldSuppressReplayResultTerminal({
2267
+ run: route.run,
2268
+ message,
2269
+ });
2270
+ this.logger.trace({
2271
+ claudeSessionId: this.claudeSessionId,
2272
+ messageType: message.type,
2273
+ routeReason: route.reason,
2274
+ runId: route.run?.id ?? null,
2275
+ runOwner: route.run?.owner ?? null,
2276
+ suppressTerminalEvents,
2277
+ metadataOnly,
2278
+ }, "Claude query pump routed SDK message");
2279
+ if (route.run) {
2280
+ this.transitionTurnStateFromActiveRuns(`routed via ${route.reason}`);
2281
+ this.runTracker.bindIdentifiers(route.run, identifiers);
2282
+ if (!suppressTerminalEvents) {
2283
+ this.updateRunLifecycleForMessage(route.run, message, identifiers);
2284
+ }
2285
+ }
2286
+ const messageEvents = this.translateMessageToEvents(message, {
2287
+ suppressAssistantText: true,
2288
+ suppressReasoning: true,
2289
+ suppressTerminalEvents,
2290
+ });
2291
+ const assistantTimelineItems = this.timelineAssembler.consume({
2292
+ message,
2293
+ runId: route.run?.id ?? null,
2294
+ messageIdHint: identifiers.messageId,
2295
+ });
2296
+ const assistantTimelineEvents = assistantTimelineItems.map((item) => ({
2297
+ type: "timeline",
2298
+ item,
2299
+ provider: "claude",
2300
+ }));
2301
+ const events = [...messageEvents, ...assistantTimelineEvents];
2302
+ if (events.length === 0) {
2303
+ return;
2304
+ }
2305
+ if (!route.run) {
2306
+ this.dispatchMetadataEvents(events);
2307
+ return;
2308
+ }
2309
+ for (const event of events) {
2310
+ this.emitRunEvent(route.run, event);
2311
+ }
2312
+ }
2313
+ shouldSuppressReplayResultTerminal(input) {
2314
+ const { run, message } = input;
2315
+ if (!run || run.owner !== "foreground" || message.type !== "result") {
2316
+ return false;
2317
+ }
2318
+ if (run.promptReplaySeen) {
2319
+ return false;
2320
+ }
2321
+ if (run.state === "streaming" || run.state === "finalizing") {
2322
+ return false;
2323
+ }
2324
+ const resultSubtype = "subtype" in message && typeof message.subtype === "string"
2325
+ ? message.subtype
2326
+ : null;
2327
+ // Pre-replay success results are stale in practice (leftover from an
2328
+ // earlier query segment) and must not end the current foreground run.
2329
+ if (resultSubtype === "success") {
2330
+ this.logger.trace({
2331
+ runId: run.id,
2332
+ runOwner: run.owner,
2333
+ runState: run.state,
2334
+ promptReplaySeen: run.promptReplaySeen,
2335
+ resultSubtype,
2336
+ }, "Suppressing pre-replay foreground success result terminal event");
2337
+ return true;
2338
+ }
2339
+ // For non-success results, keep the metadata-churn guard to avoid
2340
+ // suppressing legitimate hard failures.
2341
+ return this.preReplayMetadataSeen;
2342
+ }
2343
+ dispatchMetadataEvents(events) {
2344
+ for (const event of events) {
2345
+ this.pushEvent(event);
2346
+ }
2347
+ }
2348
+ updateRunLifecycleForMessage(run, message, identifiers) {
2349
+ const previousState = run.state;
2350
+ if (message.type === "user" &&
2351
+ identifiers.messageId &&
2352
+ run.messageIds.has(identifiers.messageId)) {
2353
+ run.promptReplaySeen = true;
2354
+ this.preReplayMetadataSeen = false;
2355
+ }
2356
+ if (run.state === "queued") {
2357
+ this.runTracker.transition(run, "awaiting_response");
2358
+ }
2359
+ if (message.type === "assistant" ||
2360
+ message.type === "stream_event" ||
2361
+ message.type === "tool_progress") {
2362
+ this.runTracker.transition(run, "streaming");
2363
+ return;
2364
+ }
2365
+ if (message.type === "result") {
2366
+ this.runTracker.transition(run, "finalizing");
2367
+ }
2368
+ else {
2369
+ return;
2370
+ }
2371
+ if (run.state !== previousState) {
2372
+ this.logger.trace({
2373
+ runId: run.id,
2374
+ owner: run.owner,
2375
+ messageType: message.type,
2376
+ previousState,
2377
+ nextState: run.state,
2378
+ taskId: identifiers.taskId,
2379
+ parentMessageId: identifiers.parentMessageId,
2380
+ messageId: identifiers.messageId,
2381
+ }, "Updated Claude run lifecycle from SDK message");
2382
+ }
2383
+ }
2384
+ shouldSuppressLocalReplayActivity(message) {
2385
+ const localReplay = this.isLocalReplayUserMessage(message);
2386
+ if (!this.activeForegroundTurn && localReplay) {
2387
+ this.suppressLocalReplayActivity = true;
2388
+ this.logger.debug({ uuid: message.uuid }, "Suppressing local replay user message from live pump");
2389
+ return true;
2390
+ }
2391
+ if (!this.suppressLocalReplayActivity) {
2392
+ return false;
2393
+ }
2394
+ // Suppress only replay scaffolding. Do not suppress autonomous
2395
+ // assistant/result events; otherwise task-notification replies can be dropped.
2396
+ if (localReplay) {
2397
+ return true;
2398
+ }
2399
+ if (message.type === "system") {
2400
+ return true;
2401
+ }
2402
+ const identifiers = readEventIdentifiers(message);
2403
+ const hasIdentifiers = Boolean(identifiers.taskId || identifiers.parentMessageId || identifiers.messageId);
2404
+ if (message.type !== "user" && !hasIdentifiers) {
2405
+ if (this.pendingAutonomousWakeReservations > 0) {
2406
+ this.suppressLocalReplayActivity = false;
2407
+ return false;
1328
2408
  }
1329
- this.turnCancelRequested = false;
1330
- queue.end();
2409
+ return true;
2410
+ }
2411
+ if (message.type === "user") {
2412
+ this.suppressLocalReplayActivity = false;
2413
+ return false;
1331
2414
  }
2415
+ this.suppressLocalReplayActivity = false;
2416
+ return false;
2417
+ }
2418
+ isLocalReplayUserMessage(message) {
2419
+ if (message.type !== "user") {
2420
+ return false;
2421
+ }
2422
+ const uuid = readTrimmedString(message.uuid);
2423
+ if (!uuid) {
2424
+ return false;
2425
+ }
2426
+ return this.localUserMessageIds.has(uuid);
1332
2427
  }
1333
2428
  async interruptActiveTurn() {
1334
2429
  const queryToInterrupt = this.query;
1335
2430
  if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
1336
- this.logger.info("interruptActiveTurn: no query to interrupt");
2431
+ this.logger.debug("interruptActiveTurn: no query to interrupt");
1337
2432
  return;
1338
2433
  }
1339
2434
  try {
1340
- this.logger.info("interruptActiveTurn: calling query.interrupt()...");
2435
+ this.logger.debug("interruptActiveTurn: calling query.interrupt()...");
1341
2436
  const t0 = Date.now();
1342
2437
  await queryToInterrupt.interrupt();
1343
- this.logger.info({ durationMs: Date.now() - t0 }, "interruptActiveTurn: query.interrupt() returned");
2438
+ this.logger.debug({ durationMs: Date.now() - t0 }, "interruptActiveTurn: query.interrupt() returned");
1344
2439
  // After interrupt(), the query iterator is done (returns done: true).
1345
2440
  // Clear it so ensureQuery() creates a fresh query for the next turn.
1346
2441
  // Also end the input stream and call return() to clean up the SDK process.
1347
2442
  this.input?.end();
1348
- this.logger.info("interruptActiveTurn: calling query.return()...");
2443
+ this.logger.debug("interruptActiveTurn: calling query.return()...");
1349
2444
  const t1 = Date.now();
1350
2445
  await queryToInterrupt.return?.();
1351
- this.logger.info({ durationMs: Date.now() - t1 }, "interruptActiveTurn: query.return() returned");
2446
+ this.logger.debug({ durationMs: Date.now() - t1 }, "interruptActiveTurn: query.return() returned");
1352
2447
  this.query = null;
1353
2448
  this.input = null;
1354
2449
  this.queryRestartNeeded = false;
1355
2450
  }
1356
2451
  catch (error) {
1357
2452
  this.logger.warn({ err: error }, "Failed to interrupt active turn");
2453
+ // If interrupt fails, the SDK iterator may remain in an indeterminate state.
2454
+ // Force a teardown/recreate path so the next turn cannot reuse stale query state.
2455
+ this.queryRestartNeeded = true;
1358
2456
  }
1359
2457
  }
1360
2458
  handleSidechainMessage(message, parentToolUseId) {
1361
- let toolName;
1362
- if (message.type === "assistant") {
1363
- const content = message.message?.content;
1364
- if (Array.isArray(content)) {
1365
- for (const block of content) {
1366
- if (isClaudeContentChunk(block) &&
1367
- (block.type === "tool_use" || block.type === "mcp_tool_use" || block.type === "server_tool_use") &&
1368
- typeof block.name === "string") {
1369
- toolName = block.name;
1370
- break;
1371
- }
1372
- }
1373
- }
1374
- }
1375
- else if (message.type === "stream_event") {
1376
- const event = message.event;
1377
- if (event.type === "content_block_start") {
1378
- const cb = isClaudeContentChunk(event.content_block) ? event.content_block : null;
1379
- if (cb?.type === "tool_use" && typeof cb.name === "string") {
1380
- toolName = cb.name;
1381
- }
2459
+ const state = this.activeSidechains.get(parentToolUseId) ??
2460
+ {
2461
+ actions: [],
2462
+ actionKeys: [],
2463
+ nextActionIndex: 1,
2464
+ actionIndexByKey: new Map(),
2465
+ };
2466
+ this.activeSidechains.set(parentToolUseId, state);
2467
+ const contextUpdated = this.updateSubAgentContextFromTaskInput(state, parentToolUseId);
2468
+ const actionCandidates = this.extractSubAgentActionCandidates(message);
2469
+ let actionUpdated = false;
2470
+ for (const action of actionCandidates) {
2471
+ if (this.appendSubAgentAction(state, action)) {
2472
+ actionUpdated = true;
1382
2473
  }
1383
2474
  }
1384
- else if (message.type === "tool_progress") {
1385
- toolName = message.tool_name;
1386
- }
1387
- if (!toolName) {
1388
- return [];
1389
- }
1390
- const prev = this.activeSidechains.get(parentToolUseId);
1391
- if (prev === toolName) {
2475
+ if (!contextUpdated && !actionUpdated) {
1392
2476
  return [];
1393
2477
  }
1394
- this.activeSidechains.set(parentToolUseId, toolName);
1395
2478
  const toolCall = mapClaudeRunningToolCall({
1396
2479
  name: "Task",
1397
2480
  callId: parentToolUseId,
1398
2481
  input: null,
1399
2482
  output: null,
1400
- metadata: { subAgentActivity: toolName },
1401
2483
  });
1402
2484
  if (!toolCall) {
1403
2485
  return [];
1404
2486
  }
2487
+ const detail = {
2488
+ type: "sub_agent",
2489
+ ...(state.subAgentType ? { subAgentType: state.subAgentType } : {}),
2490
+ ...(state.description ? { description: state.description } : {}),
2491
+ log: state.actions
2492
+ .map((action) => action.summary
2493
+ ? `[${action.toolName}] ${action.summary}`
2494
+ : `[${action.toolName}]`)
2495
+ .join("\n"),
2496
+ actions: state.actions.map((action) => ({
2497
+ index: action.index,
2498
+ toolName: action.toolName,
2499
+ ...(action.summary ? { summary: action.summary } : {}),
2500
+ })),
2501
+ };
1405
2502
  return [
1406
2503
  {
1407
2504
  type: "timeline",
1408
- item: toolCall,
2505
+ item: {
2506
+ ...toolCall,
2507
+ detail,
2508
+ },
1409
2509
  provider: "claude",
1410
2510
  },
1411
2511
  ];
1412
2512
  }
1413
- translateMessageToEvents(message, turnContext) {
2513
+ updateSubAgentContextFromTaskInput(state, parentToolUseId) {
2514
+ const taskInput = this.toolUseCache.get(parentToolUseId)?.input;
2515
+ const nextSubAgentType = this.normalizeSubAgentText(taskInput?.subagent_type);
2516
+ const nextDescription = this.normalizeSubAgentText(taskInput?.description);
2517
+ let changed = false;
2518
+ if (nextSubAgentType && nextSubAgentType !== state.subAgentType) {
2519
+ state.subAgentType = nextSubAgentType;
2520
+ changed = true;
2521
+ }
2522
+ if (nextDescription && nextDescription !== state.description) {
2523
+ state.description = nextDescription;
2524
+ changed = true;
2525
+ }
2526
+ return changed;
2527
+ }
2528
+ normalizeSubAgentText(value) {
2529
+ const normalized = readTrimmedString(value)?.replace(/\s+/g, " ");
2530
+ if (!normalized) {
2531
+ return undefined;
2532
+ }
2533
+ if (normalized.length <= MAX_SUB_AGENT_SUMMARY_CHARS) {
2534
+ return normalized;
2535
+ }
2536
+ return `${normalized.slice(0, MAX_SUB_AGENT_SUMMARY_CHARS)}...`;
2537
+ }
2538
+ extractSubAgentActionCandidates(message) {
2539
+ if (message.type === "assistant") {
2540
+ const content = message.message?.content;
2541
+ if (!Array.isArray(content)) {
2542
+ return [];
2543
+ }
2544
+ const actions = [];
2545
+ for (const block of content) {
2546
+ if (!isClaudeContentChunk(block) ||
2547
+ !(block.type === "tool_use" ||
2548
+ block.type === "mcp_tool_use" ||
2549
+ block.type === "server_tool_use") ||
2550
+ typeof block.name !== "string") {
2551
+ continue;
2552
+ }
2553
+ const key = readTrimmedString(block.id) ??
2554
+ `assistant:${block.name}:${actions.length}`;
2555
+ actions.push({
2556
+ key,
2557
+ toolName: block.name,
2558
+ input: block.input ?? null,
2559
+ });
2560
+ }
2561
+ return actions;
2562
+ }
2563
+ if (message.type === "stream_event") {
2564
+ const event = message.event;
2565
+ if (event.type !== "content_block_start") {
2566
+ return [];
2567
+ }
2568
+ const block = isClaudeContentChunk(event.content_block)
2569
+ ? event.content_block
2570
+ : null;
2571
+ if (!block ||
2572
+ !(block.type === "tool_use" ||
2573
+ block.type === "mcp_tool_use" ||
2574
+ block.type === "server_tool_use") ||
2575
+ typeof block.name !== "string") {
2576
+ return [];
2577
+ }
2578
+ const key = readTrimmedString(block.id) ??
2579
+ `stream:${block.name}:${typeof event.index === "number" ? event.index : 0}`;
2580
+ return [
2581
+ {
2582
+ key,
2583
+ toolName: block.name,
2584
+ input: block.input ?? null,
2585
+ },
2586
+ ];
2587
+ }
2588
+ if (message.type === "tool_progress") {
2589
+ const toolName = readTrimmedString(message.tool_name);
2590
+ if (!toolName) {
2591
+ return [];
2592
+ }
2593
+ const key = readTrimmedString(message.tool_use_id) ?? `progress:${toolName}`;
2594
+ return [{ key, toolName, input: null }];
2595
+ }
2596
+ return [];
2597
+ }
2598
+ appendSubAgentAction(state, candidate) {
2599
+ const normalizedToolName = readTrimmedString(candidate.toolName);
2600
+ if (!normalizedToolName) {
2601
+ return false;
2602
+ }
2603
+ const summary = this.deriveSubAgentActionSummary(normalizedToolName, candidate.input);
2604
+ const existingIndex = state.actionIndexByKey.get(candidate.key);
2605
+ if (existingIndex !== undefined) {
2606
+ const existing = state.actions[existingIndex];
2607
+ if (!existing) {
2608
+ return false;
2609
+ }
2610
+ const nextSummary = existing.summary ?? summary;
2611
+ const unchanged = existing.toolName === normalizedToolName &&
2612
+ existing.summary === nextSummary;
2613
+ if (unchanged) {
2614
+ return false;
2615
+ }
2616
+ state.actions[existingIndex] = {
2617
+ ...existing,
2618
+ toolName: normalizedToolName,
2619
+ ...(nextSummary ? { summary: nextSummary } : {}),
2620
+ };
2621
+ return true;
2622
+ }
2623
+ const nextEntry = {
2624
+ index: state.nextActionIndex,
2625
+ toolName: normalizedToolName,
2626
+ ...(summary ? { summary } : {}),
2627
+ };
2628
+ state.nextActionIndex += 1;
2629
+ state.actions.push(nextEntry);
2630
+ state.actionKeys.push(candidate.key);
2631
+ this.trimSubAgentTail(state);
2632
+ this.rebuildSubAgentActionIndex(state);
2633
+ return true;
2634
+ }
2635
+ trimSubAgentTail(state) {
2636
+ while (state.actions.length > MAX_SUB_AGENT_LOG_ENTRIES) {
2637
+ state.actions.shift();
2638
+ state.actionKeys.shift();
2639
+ }
2640
+ }
2641
+ rebuildSubAgentActionIndex(state) {
2642
+ state.actionIndexByKey.clear();
2643
+ for (let index = 0; index < state.actionKeys.length; index += 1) {
2644
+ const key = state.actionKeys[index];
2645
+ if (key) {
2646
+ state.actionIndexByKey.set(key, index);
2647
+ }
2648
+ }
2649
+ }
2650
+ deriveSubAgentActionSummary(toolName, input) {
2651
+ const runningToolCall = mapClaudeRunningToolCall({
2652
+ name: toolName,
2653
+ callId: `sub-agent-summary-${toolName}`,
2654
+ input,
2655
+ output: null,
2656
+ });
2657
+ if (!runningToolCall) {
2658
+ return undefined;
2659
+ }
2660
+ const display = buildToolCallDisplayModel({
2661
+ name: runningToolCall.name,
2662
+ status: runningToolCall.status,
2663
+ error: runningToolCall.error,
2664
+ detail: runningToolCall.detail,
2665
+ metadata: runningToolCall.metadata,
2666
+ });
2667
+ return this.normalizeSubAgentText(display.summary);
2668
+ }
2669
+ translateMessageToEvents(message, options) {
1414
2670
  const parentToolUseId = "parent_tool_use_id" in message
1415
2671
  ? message.parent_tool_use_id
1416
2672
  : null;
@@ -1418,6 +2674,14 @@ class ClaudeAgentSession {
1418
2674
  return this.handleSidechainMessage(message, parentToolUseId);
1419
2675
  }
1420
2676
  const events = [];
2677
+ const fallbackThreadSessionId = this.captureSessionIdFromMessage(message);
2678
+ if (fallbackThreadSessionId) {
2679
+ events.push({
2680
+ type: "thread_started",
2681
+ provider: "claude",
2682
+ sessionId: fallbackThreadSessionId,
2683
+ });
2684
+ }
1421
2685
  switch (message.type) {
1422
2686
  case "system":
1423
2687
  if (message.subtype === "init") {
@@ -1442,20 +2706,33 @@ class ClaudeAgentSession {
1442
2706
  }
1443
2707
  }
1444
2708
  else if (message.subtype === "compact_boundary") {
1445
- const meta = message.compact_metadata;
2709
+ const compactMetadata = readCompactionMetadata(message);
1446
2710
  events.push({
1447
2711
  type: "timeline",
1448
2712
  item: {
1449
2713
  type: "compaction",
1450
2714
  status: "completed",
1451
- trigger: meta?.trigger === "manual" ? "manual" : "auto",
1452
- preTokens: meta?.pre_tokens,
2715
+ trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
2716
+ preTokens: compactMetadata?.preTokens,
1453
2717
  },
1454
2718
  provider: "claude",
1455
2719
  });
1456
2720
  }
2721
+ else if (message.subtype === "task_notification") {
2722
+ const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
2723
+ if (taskNotificationItem) {
2724
+ events.push({
2725
+ type: "timeline",
2726
+ item: taskNotificationItem,
2727
+ provider: "claude",
2728
+ });
2729
+ }
2730
+ }
1457
2731
  break;
1458
2732
  case "user": {
2733
+ if (isSyntheticUserEntry(message)) {
2734
+ break;
2735
+ }
1459
2736
  if (this.compacting) {
1460
2737
  this.compacting = false;
1461
2738
  break;
@@ -1465,6 +2742,18 @@ class ClaudeAgentSession {
1465
2742
  : undefined;
1466
2743
  this.rememberUserMessageId(messageId);
1467
2744
  const content = message.message?.content;
2745
+ const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
2746
+ content,
2747
+ messageId,
2748
+ });
2749
+ if (taskNotificationItem) {
2750
+ events.push({
2751
+ type: "timeline",
2752
+ item: taskNotificationItem,
2753
+ provider: "claude",
2754
+ });
2755
+ break;
2756
+ }
1468
2757
  if (typeof content === "string" && content.length > 0) {
1469
2758
  // String content from user messages (e.g., local command output)
1470
2759
  events.push({
@@ -1478,7 +2767,7 @@ class ClaudeAgentSession {
1478
2767
  });
1479
2768
  }
1480
2769
  else if (Array.isArray(content)) {
1481
- const timelineItems = this.mapBlocksToTimeline(content, { turnContext });
2770
+ const timelineItems = this.mapBlocksToTimeline(content);
1482
2771
  for (const item of timelineItems) {
1483
2772
  if (item.type === "user_message" && messageId && !item.messageId) {
1484
2773
  events.push({
@@ -1495,9 +2784,8 @@ class ClaudeAgentSession {
1495
2784
  }
1496
2785
  case "assistant": {
1497
2786
  const timelineItems = this.mapBlocksToTimeline(message.message.content, {
1498
- turnContext,
1499
- suppressAssistantText: turnContext.streamedAssistantTextThisTurn,
1500
- suppressReasoning: turnContext.streamedReasoningThisTurn,
2787
+ suppressAssistantText: options?.suppressAssistantText ?? false,
2788
+ suppressReasoning: options?.suppressReasoning ?? false,
1501
2789
  });
1502
2790
  for (const item of timelineItems) {
1503
2791
  events.push({ type: "timeline", item, provider: "claude" });
@@ -1505,13 +2793,19 @@ class ClaudeAgentSession {
1505
2793
  break;
1506
2794
  }
1507
2795
  case "stream_event": {
1508
- const timelineItems = this.mapPartialEvent(message.event, turnContext);
2796
+ const timelineItems = this.mapPartialEvent(message.event, {
2797
+ suppressAssistantText: options?.suppressAssistantText ?? false,
2798
+ suppressReasoning: options?.suppressReasoning ?? false,
2799
+ });
1509
2800
  for (const item of timelineItems) {
1510
2801
  events.push({ type: "timeline", item, provider: "claude" });
1511
2802
  }
1512
2803
  break;
1513
2804
  }
1514
2805
  case "result": {
2806
+ if (options?.suppressTerminalEvents) {
2807
+ break;
2808
+ }
1515
2809
  const usage = this.convertUsage(message);
1516
2810
  if (message.subtype === "success") {
1517
2811
  events.push({ type: "turn_completed", provider: "claude", usage });
@@ -1520,7 +2814,7 @@ class ClaudeAgentSession {
1520
2814
  const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
1521
2815
  ? message.errors.join("\n")
1522
2816
  : "Claude run failed";
1523
- events.push({ type: "turn_failed", provider: "claude", error: errorMessage });
2817
+ events.push(this.buildTurnFailedEvent(errorMessage));
1524
2818
  }
1525
2819
  break;
1526
2820
  }
@@ -1529,6 +2823,31 @@ class ClaudeAgentSession {
1529
2823
  }
1530
2824
  return events;
1531
2825
  }
2826
+ captureSessionIdFromMessage(message) {
2827
+ const msg = message;
2828
+ const sessionIdRaw = typeof msg.session_id === "string"
2829
+ ? msg.session_id
2830
+ : typeof msg.sessionId === "string"
2831
+ ? msg.sessionId
2832
+ : typeof msg.session?.id === "string"
2833
+ ? msg.session.id
2834
+ : "";
2835
+ const sessionId = sessionIdRaw.trim();
2836
+ if (!sessionId) {
2837
+ return null;
2838
+ }
2839
+ if (this.claudeSessionId === null) {
2840
+ this.claudeSessionId = sessionId;
2841
+ this.persistence = null;
2842
+ return sessionId;
2843
+ }
2844
+ if (this.claudeSessionId === sessionId) {
2845
+ return null;
2846
+ }
2847
+ throw new Error(`CRITICAL: Claude session ID overwrite detected! ` +
2848
+ `Existing: ${this.claudeSessionId}, New: ${sessionId}. ` +
2849
+ `This indicates a session identity corruption bug.`);
2850
+ }
1532
2851
  handleSystemMessage(message) {
1533
2852
  if (message.subtype !== "init") {
1534
2853
  return null;
@@ -1572,6 +2891,7 @@ class ClaudeAgentSession {
1572
2891
  const normalizedModel = normalizeClaudeRuntimeModelId({
1573
2892
  runtimeModelId: message.model,
1574
2893
  supportedModelIds: this.selectableModelIds,
2894
+ supportedModelFamilyAliases: this.selectableModelFamilyAliases,
1575
2895
  configuredModelId: this.config.model ?? null,
1576
2896
  currentModelId: this.lastOptionsModel,
1577
2897
  });
@@ -1608,6 +2928,7 @@ class ClaudeAgentSession {
1608
2928
  }
1609
2929
  }
1610
2930
  this.toolUseCache.clear();
2931
+ this.activeSidechains.clear();
1611
2932
  }
1612
2933
  pushToolCall(item, target) {
1613
2934
  if (!item) {
@@ -1620,9 +2941,19 @@ class ClaudeAgentSession {
1620
2941
  this.enqueueTimeline(item);
1621
2942
  }
1622
2943
  pushEvent(event) {
1623
- if (this.eventQueue) {
1624
- this.eventQueue.push(event);
2944
+ const foregroundTurn = this.activeForegroundTurn;
2945
+ if (foregroundTurn) {
2946
+ const run = this.runTracker.getRun(foregroundTurn.runId);
2947
+ if (run &&
2948
+ run.owner === "foreground" &&
2949
+ run.queue === foregroundTurn.queue &&
2950
+ this.runTracker.isRunActive(run)) {
2951
+ foregroundTurn.queue.push(event);
2952
+ return;
2953
+ }
2954
+ this.activeForegroundTurn = null;
1625
2955
  }
2956
+ this.liveEventQueue.push(event);
1626
2957
  }
1627
2958
  normalizePermissionUpdates(updates) {
1628
2959
  if (!updates || updates.length === 0) {
@@ -1638,6 +2969,9 @@ class ClaudeAgentSession {
1638
2969
  this.pendingPermissions.delete(id);
1639
2970
  }
1640
2971
  }
2972
+ waitForLiveHistoryPoll() {
2973
+ return new Promise((resolve) => setTimeout(resolve, 250));
2974
+ }
1641
2975
  loadPersistedHistory(sessionId) {
1642
2976
  try {
1643
2977
  const historyPath = this.resolveHistoryPath(sessionId);
@@ -1687,20 +3021,15 @@ class ClaudeAgentSession {
1687
3021
  return path.join(dir, `${sessionId}.jsonl`);
1688
3022
  }
1689
3023
  convertHistoryEntry(entry) {
1690
- return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content, { context: "history" }));
3024
+ return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
1691
3025
  }
1692
3026
  mapBlocksToTimeline(content, options) {
1693
- const context = options?.context ?? "live";
1694
- const turnContext = options?.turnContext;
1695
3027
  const suppressAssistant = options?.suppressAssistantText ?? false;
1696
3028
  const suppressReasoning = options?.suppressReasoning ?? false;
1697
3029
  if (typeof content === "string") {
1698
- if (!content || content === "[Request interrupted by user for tool use]") {
3030
+ if (!content || content === INTERRUPT_TOOL_USE_PLACEHOLDER) {
1699
3031
  return [];
1700
3032
  }
1701
- if (context === "live" && turnContext) {
1702
- turnContext.streamedAssistantTextThisTurn = true;
1703
- }
1704
3033
  if (suppressAssistant) {
1705
3034
  return [];
1706
3035
  }
@@ -1711,10 +3040,7 @@ class ClaudeAgentSession {
1711
3040
  switch (block.type) {
1712
3041
  case "text":
1713
3042
  case "text_delta":
1714
- if (block.text && block.text !== "[Request interrupted by user for tool use]") {
1715
- if (context === "live" && turnContext) {
1716
- turnContext.streamedAssistantTextThisTurn = true;
1717
- }
3043
+ if (block.text && block.text !== INTERRUPT_TOOL_USE_PLACEHOLDER) {
1718
3044
  if (!suppressAssistant) {
1719
3045
  items.push({ type: "assistant_message", text: block.text });
1720
3046
  }
@@ -1723,9 +3049,6 @@ class ClaudeAgentSession {
1723
3049
  case "thinking":
1724
3050
  case "thinking_delta":
1725
3051
  if (block.thinking) {
1726
- if (context === "live" && turnContext) {
1727
- turnContext.streamedReasoningThisTurn = true;
1728
- }
1729
3052
  if (!suppressReasoning) {
1730
3053
  items.push({ type: "reasoning", text: block.thinking });
1731
3054
  }
@@ -1797,6 +3120,7 @@ class ClaudeAgentSession {
1797
3120
  }
1798
3121
  if (typeof block.tool_use_id === "string") {
1799
3122
  this.toolUseCache.delete(block.tool_use_id);
3123
+ this.activeSidechains.delete(block.tool_use_id);
1800
3124
  }
1801
3125
  }
1802
3126
  buildToolOutput(block, entry) {
@@ -1805,7 +3129,7 @@ class ClaudeAgentSession {
1805
3129
  }
1806
3130
  const server = entry?.server ?? block.server ?? "tool";
1807
3131
  const tool = entry?.name ?? block.tool_name ?? "tool";
1808
- const content = typeof block.content === "string" ? block.content : "";
3132
+ const content = coerceToolResultContentToString(block.content);
1809
3133
  const input = entry?.input;
1810
3134
  // Build structured result based on tool type
1811
3135
  const structured = this.buildStructuredToolResult(server, tool, content, input);
@@ -1894,7 +3218,7 @@ class ClaudeAgentSession {
1894
3218
  }
1895
3219
  return undefined;
1896
3220
  }
1897
- mapPartialEvent(event, turnContext) {
3221
+ mapPartialEvent(event, options) {
1898
3222
  if (event.type === "content_block_start") {
1899
3223
  const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
1900
3224
  if (block?.type === "tool_use" && typeof event.index === "number" && typeof block.id === "string") {
@@ -1920,10 +3244,18 @@ class ClaudeAgentSession {
1920
3244
  switch (event.type) {
1921
3245
  case "content_block_start":
1922
3246
  return isClaudeContentChunk(event.content_block)
1923
- ? this.mapBlocksToTimeline([event.content_block], { turnContext })
3247
+ ? this.mapBlocksToTimeline([event.content_block], {
3248
+ suppressAssistantText: options?.suppressAssistantText,
3249
+ suppressReasoning: options?.suppressReasoning,
3250
+ })
1924
3251
  : [];
1925
3252
  case "content_block_delta":
1926
- return isClaudeContentChunk(event.delta) ? this.mapBlocksToTimeline([event.delta], { turnContext }) : [];
3253
+ return isClaudeContentChunk(event.delta)
3254
+ ? this.mapBlocksToTimeline([event.delta], {
3255
+ suppressAssistantText: options?.suppressAssistantText,
3256
+ suppressReasoning: options?.suppressReasoning,
3257
+ })
3258
+ : [];
1927
3259
  default:
1928
3260
  return [];
1929
3261
  }
@@ -2122,6 +3454,24 @@ function hasToolLikeBlock(block) {
2122
3454
  const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
2123
3455
  return type.includes("tool");
2124
3456
  }
3457
+ function readCompactionMetadata(source) {
3458
+ const candidates = [
3459
+ source.compact_metadata,
3460
+ source.compactMetadata,
3461
+ source.compactionMetadata,
3462
+ ];
3463
+ for (const candidate of candidates) {
3464
+ if (!candidate || typeof candidate !== "object") {
3465
+ continue;
3466
+ }
3467
+ const metadata = candidate;
3468
+ const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
3469
+ const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
3470
+ const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
3471
+ return { trigger, preTokens };
3472
+ }
3473
+ return null;
3474
+ }
2125
3475
  function normalizeHistoryBlocks(content) {
2126
3476
  if (Array.isArray(content)) {
2127
3477
  const blocks = content.filter((entry) => isClaudeContentChunk(entry));
@@ -2134,16 +3484,26 @@ function normalizeHistoryBlocks(content) {
2134
3484
  }
2135
3485
  export function convertClaudeHistoryEntry(entry, mapBlocks) {
2136
3486
  if (entry.type === "system" && entry.subtype === "compact_boundary") {
3487
+ const compactMetadata = readCompactionMetadata(entry);
2137
3488
  return [{
2138
3489
  type: "compaction",
2139
3490
  status: "completed",
2140
- trigger: entry.compactMetadata?.trigger === "manual" ? "manual" : "auto",
2141
- preTokens: entry.compactMetadata?.preTokens,
3491
+ trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
3492
+ preTokens: compactMetadata?.preTokens,
2142
3493
  }];
2143
3494
  }
3495
+ if (entry.type === "system") {
3496
+ const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
3497
+ if (taskNotificationItem) {
3498
+ return [taskNotificationItem];
3499
+ }
3500
+ }
2144
3501
  if (entry.isCompactSummary) {
2145
3502
  return [];
2146
3503
  }
3504
+ if (entry.type === "user" && isSyntheticUserEntry(entry)) {
3505
+ return [];
3506
+ }
2147
3507
  const message = entry?.message;
2148
3508
  if (!message || !("content" in message)) {
2149
3509
  return [];
@@ -2154,17 +3514,26 @@ export function convertClaudeHistoryEntry(entry, mapBlocks) {
2154
3514
  ? content
2155
3515
  : normalizedBlocks;
2156
3516
  const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
3517
+ const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
3518
+ ? entry.uuid
3519
+ : null;
3520
+ if (entry.type === "user") {
3521
+ const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
3522
+ content,
3523
+ messageId: userMessageId,
3524
+ });
3525
+ if (taskNotificationItem) {
3526
+ return [taskNotificationItem];
3527
+ }
3528
+ }
2157
3529
  const timeline = [];
2158
3530
  if (entry.type === "user") {
2159
3531
  const text = extractUserMessageText(content);
2160
3532
  if (text) {
2161
- const messageId = typeof entry.uuid === "string" && entry.uuid.length > 0
2162
- ? entry.uuid
2163
- : undefined;
2164
3533
  timeline.push({
2165
3534
  type: "user_message",
2166
3535
  text,
2167
- ...(messageId ? { messageId } : {}),
3536
+ ...(userMessageId ? { messageId: userMessageId } : {}),
2168
3537
  });
2169
3538
  }
2170
3539
  }
@@ -2306,6 +3675,9 @@ async function parseClaudeSessionDescriptor(filePath, mtime) {
2306
3675
  if (entry?.isSidechain) {
2307
3676
  continue;
2308
3677
  }
3678
+ if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
3679
+ continue;
3680
+ }
2309
3681
  if (!sessionId && typeof entry.sessionId === "string") {
2310
3682
  sessionId = entry.sessionId;
2311
3683
  }