@qduc/term2 0.38.0 → 0.40.0

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 (338) hide show
  1. package/README.md +12 -2
  2. package/dist/agent.d.ts +4 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +18 -4
  5. package/dist/agent.js.map +1 -1
  6. package/dist/app.d.ts +4 -0
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +50 -4
  9. package/dist/app.js.map +1 -1
  10. package/dist/build-info.json +3 -3
  11. package/dist/cli.js +189 -49
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/bridge-command.d.ts +11 -0
  14. package/dist/commands/bridge-command.d.ts.map +1 -0
  15. package/dist/commands/bridge-command.js +67 -0
  16. package/dist/commands/bridge-command.js.map +1 -0
  17. package/dist/components/input/FirstRunSetupPrompt.js +3 -3
  18. package/dist/components/input/FirstRunSetupPrompt.js.map +1 -1
  19. package/dist/components/input/ModelMenuSession.d.ts.map +1 -1
  20. package/dist/components/input/ModelMenuSession.js +2 -0
  21. package/dist/components/input/ModelMenuSession.js.map +1 -1
  22. package/dist/components/input/SubagentPoolMenuSession.js +1 -1
  23. package/dist/components/input/SubagentPoolMenuSession.js.map +1 -1
  24. package/dist/components/input/menu-types.d.ts +2 -0
  25. package/dist/components/input/menu-types.d.ts.map +1 -1
  26. package/dist/components/layout/BackgroundTaskManager.d.ts.map +1 -1
  27. package/dist/components/layout/BackgroundTaskManager.js +28 -17
  28. package/dist/components/layout/BackgroundTaskManager.js.map +1 -1
  29. package/dist/components/layout/BottomArea.d.ts.map +1 -1
  30. package/dist/components/layout/BottomArea.js +5 -3
  31. package/dist/components/layout/BottomArea.js.map +1 -1
  32. package/dist/components/message/ChatMessage.d.ts +1 -0
  33. package/dist/components/message/ChatMessage.d.ts.map +1 -1
  34. package/dist/components/message/ChatMessage.js +3 -3
  35. package/dist/components/message/ChatMessage.js.map +1 -1
  36. package/dist/components/message/CommandMessage.js +21 -3
  37. package/dist/components/message/CommandMessage.js.map +1 -1
  38. package/dist/components/message/MessageList.d.ts +4 -1
  39. package/dist/components/message/MessageList.d.ts.map +1 -1
  40. package/dist/components/message/MessageList.js +20 -4
  41. package/dist/components/message/MessageList.js.map +1 -1
  42. package/dist/components/prompt/QueuePausedPrompt.d.ts +1 -0
  43. package/dist/components/prompt/QueuePausedPrompt.d.ts.map +1 -1
  44. package/dist/components/prompt/QueuePausedPrompt.js +6 -1
  45. package/dist/components/prompt/QueuePausedPrompt.js.map +1 -1
  46. package/dist/contracts/application-stream.d.ts +3 -0
  47. package/dist/contracts/application-stream.d.ts.map +1 -1
  48. package/dist/contracts/conversation-items.d.ts +6 -0
  49. package/dist/contracts/conversation-items.d.ts.map +1 -1
  50. package/dist/contracts/streamed-model-turn.d.ts +5 -0
  51. package/dist/contracts/streamed-model-turn.d.ts.map +1 -1
  52. package/dist/gateway/gateway.d.ts.map +1 -1
  53. package/dist/gateway/gateway.js +204 -34
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/persistence/projection.d.ts +17 -0
  56. package/dist/gateway/persistence/projection.d.ts.map +1 -1
  57. package/dist/gateway/persistence/projection.js +108 -10
  58. package/dist/gateway/persistence/projection.js.map +1 -1
  59. package/dist/gateway/runtime-factory.d.ts.map +1 -1
  60. package/dist/gateway/runtime-factory.js +1 -0
  61. package/dist/gateway/runtime-factory.js.map +1 -1
  62. package/dist/gateway/workspace-boundary-probe.d.ts +5 -0
  63. package/dist/gateway/workspace-boundary-probe.d.ts.map +1 -1
  64. package/dist/gateway/workspace-boundary-probe.js +6 -1
  65. package/dist/gateway/workspace-boundary-probe.js.map +1 -1
  66. package/dist/hooks/conversation-ui-reducer.d.ts +1 -0
  67. package/dist/hooks/conversation-ui-reducer.d.ts.map +1 -1
  68. package/dist/hooks/conversation-ui-reducer.js +6 -1
  69. package/dist/hooks/conversation-ui-reducer.js.map +1 -1
  70. package/dist/hooks/use-app-commands.d.ts +8 -1
  71. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  72. package/dist/hooks/use-app-commands.js +30 -1
  73. package/dist/hooks/use-app-commands.js.map +1 -1
  74. package/dist/hooks/use-conversation-messages.d.ts +1 -1
  75. package/dist/hooks/use-conversation-messages.d.ts.map +1 -1
  76. package/dist/hooks/use-conversation-messages.js +2 -1
  77. package/dist/hooks/use-conversation-messages.js.map +1 -1
  78. package/dist/hooks/use-conversation.d.ts +1 -1
  79. package/dist/hooks/use-conversation.d.ts.map +1 -1
  80. package/dist/hooks/use-conversation.js +19 -6
  81. package/dist/hooks/use-conversation.js.map +1 -1
  82. package/dist/hooks/use-first-run-setup.d.ts.map +1 -1
  83. package/dist/hooks/use-first-run-setup.js +14 -9
  84. package/dist/hooks/use-first-run-setup.js.map +1 -1
  85. package/dist/hooks/use-model-catalog.d.ts +18 -0
  86. package/dist/hooks/use-model-catalog.d.ts.map +1 -0
  87. package/dist/hooks/use-model-catalog.js +51 -0
  88. package/dist/hooks/use-model-catalog.js.map +1 -0
  89. package/dist/hooks/use-model-selection.d.ts +3 -4
  90. package/dist/hooks/use-model-selection.d.ts.map +1 -1
  91. package/dist/hooks/use-model-selection.js +8 -43
  92. package/dist/hooks/use-model-selection.js.map +1 -1
  93. package/dist/hooks/use-subagent-pool-selection.d.ts +1 -1
  94. package/dist/hooks/use-subagent-pool-selection.d.ts.map +1 -1
  95. package/dist/hooks/use-subagent-pool-selection.js +11 -46
  96. package/dist/hooks/use-subagent-pool-selection.js.map +1 -1
  97. package/dist/lib/agent-client.d.ts +2 -0
  98. package/dist/lib/agent-client.d.ts.map +1 -1
  99. package/dist/lib/agent-client.js +3 -1
  100. package/dist/lib/agent-client.js.map +1 -1
  101. package/dist/lib/agent-configuration.d.ts +3 -0
  102. package/dist/lib/agent-configuration.d.ts.map +1 -1
  103. package/dist/lib/agent-configuration.js +6 -0
  104. package/dist/lib/agent-configuration.js.map +1 -1
  105. package/dist/lib/agent-factory.d.ts +3 -0
  106. package/dist/lib/agent-factory.d.ts.map +1 -1
  107. package/dist/lib/agent-factory.js +4 -2
  108. package/dist/lib/agent-factory.js.map +1 -1
  109. package/dist/lib/input-owner.d.ts +2 -0
  110. package/dist/lib/input-owner.d.ts.map +1 -1
  111. package/dist/lib/input-owner.js +1 -1
  112. package/dist/lib/input-owner.js.map +1 -1
  113. package/dist/non-interactive.d.ts.map +1 -1
  114. package/dist/non-interactive.js +4 -2
  115. package/dist/non-interactive.js.map +1 -1
  116. package/dist/prompts/bare.md +3 -8
  117. package/dist/prompts/discord-communication.md +9 -0
  118. package/dist/prompts/durable-goal-context.d.ts.map +1 -1
  119. package/dist/prompts/durable-goal-context.js +4 -3
  120. package/dist/prompts/durable-goal-context.js.map +1 -1
  121. package/dist/prompts/durable-goal-context.ts +4 -2
  122. package/dist/providers/ai-sdk-streamed-model.d.ts.map +1 -1
  123. package/dist/providers/ai-sdk-streamed-model.js +4 -0
  124. package/dist/providers/ai-sdk-streamed-model.js.map +1 -1
  125. package/dist/providers/codex-responses-model.d.ts.map +1 -1
  126. package/dist/providers/codex-responses-model.js +30 -7
  127. package/dist/providers/codex-responses-model.js.map +1 -1
  128. package/dist/providers/openai-responses-model.d.ts.map +1 -1
  129. package/dist/providers/openai-responses-model.js +7 -0
  130. package/dist/providers/openai-responses-model.js.map +1 -1
  131. package/dist/providers/retrying-model.d.ts.map +1 -1
  132. package/dist/providers/retrying-model.js +3 -1
  133. package/dist/providers/retrying-model.js.map +1 -1
  134. package/dist/providers/websocket-receive-watchdog.d.ts.map +1 -1
  135. package/dist/providers/websocket-receive-watchdog.js +3 -2
  136. package/dist/providers/websocket-receive-watchdog.js.map +1 -1
  137. package/dist/services/agent-runtime/application-run-loop.d.ts.map +1 -1
  138. package/dist/services/agent-runtime/application-run-loop.js +7 -0
  139. package/dist/services/agent-runtime/application-run-loop.js.map +1 -1
  140. package/dist/services/agent-stream.d.ts.map +1 -1
  141. package/dist/services/agent-stream.js +2 -0
  142. package/dist/services/agent-stream.js.map +1 -1
  143. package/dist/services/control-socket/control-session-port.d.ts.map +1 -1
  144. package/dist/services/control-socket/control-session-port.js +37 -1
  145. package/dist/services/control-socket/control-session-port.js.map +1 -1
  146. package/dist/services/control-socket/control-socket.d.ts +36 -1
  147. package/dist/services/control-socket/control-socket.d.ts.map +1 -1
  148. package/dist/services/control-socket/control-socket.js +162 -9
  149. package/dist/services/control-socket/control-socket.js.map +1 -1
  150. package/dist/services/conversation/conversation-adapter-factory.d.ts.map +1 -1
  151. package/dist/services/conversation/conversation-adapter-factory.js +3 -1
  152. package/dist/services/conversation/conversation-adapter-factory.js.map +1 -1
  153. package/dist/services/conversation/conversation-adapter.d.ts +15 -2
  154. package/dist/services/conversation/conversation-adapter.d.ts.map +1 -1
  155. package/dist/services/conversation/conversation-adapter.js +106 -9
  156. package/dist/services/conversation/conversation-adapter.js.map +1 -1
  157. package/dist/services/conversation/conversation-decoder.d.ts.map +1 -1
  158. package/dist/services/conversation/conversation-decoder.js +6 -2
  159. package/dist/services/conversation/conversation-decoder.js.map +1 -1
  160. package/dist/services/conversation/conversation-events.d.ts +8 -1
  161. package/dist/services/conversation/conversation-events.d.ts.map +1 -1
  162. package/dist/services/conversation/conversation-events.js +1 -0
  163. package/dist/services/conversation/conversation-events.js.map +1 -1
  164. package/dist/services/conversation/conversation-history-repair.d.ts.map +1 -1
  165. package/dist/services/conversation/conversation-history-repair.js +11 -10
  166. package/dist/services/conversation/conversation-history-repair.js.map +1 -1
  167. package/dist/services/conversation/conversation-orchestrator.d.ts +5 -0
  168. package/dist/services/conversation/conversation-orchestrator.d.ts.map +1 -1
  169. package/dist/services/conversation/conversation-orchestrator.js +61 -14
  170. package/dist/services/conversation/conversation-orchestrator.js.map +1 -1
  171. package/dist/services/conversation/conversation-orchestrator.types.d.ts +3 -1
  172. package/dist/services/conversation/conversation-orchestrator.types.d.ts.map +1 -1
  173. package/dist/services/conversation/conversation-record-stream.d.ts +2 -0
  174. package/dist/services/conversation/conversation-record-stream.d.ts.map +1 -1
  175. package/dist/services/conversation/conversation-record-stream.js +12 -8
  176. package/dist/services/conversation/conversation-record-stream.js.map +1 -1
  177. package/dist/services/conversation/conversation-record.d.ts +1 -0
  178. package/dist/services/conversation/conversation-record.d.ts.map +1 -1
  179. package/dist/services/conversation/conversation-record.js +32 -14
  180. package/dist/services/conversation/conversation-record.js.map +1 -1
  181. package/dist/services/conversation/conversation-replay.d.ts.map +1 -1
  182. package/dist/services/conversation/conversation-replay.js +74 -149
  183. package/dist/services/conversation/conversation-replay.js.map +1 -1
  184. package/dist/services/conversation/conversation-service.d.ts +9 -0
  185. package/dist/services/conversation/conversation-service.d.ts.map +1 -1
  186. package/dist/services/conversation/conversation-service.js +68 -13
  187. package/dist/services/conversation/conversation-service.js.map +1 -1
  188. package/dist/services/conversation/conversation-state-schema.d.ts +1 -0
  189. package/dist/services/conversation/conversation-state-schema.d.ts.map +1 -1
  190. package/dist/services/conversation/conversation-state-schema.js +1 -0
  191. package/dist/services/conversation/conversation-state-schema.js.map +1 -1
  192. package/dist/services/conversation/conversation-turn-items.d.ts.map +1 -1
  193. package/dist/services/conversation/conversation-turn-items.js +3 -0
  194. package/dist/services/conversation/conversation-turn-items.js.map +1 -1
  195. package/dist/services/conversation/durable-goal-stop-check.js +1 -1
  196. package/dist/services/conversation/durable-goal-stop-check.js.map +1 -1
  197. package/dist/services/conversation/journal-to-ledger.d.ts +2 -11
  198. package/dist/services/conversation/journal-to-ledger.d.ts.map +1 -1
  199. package/dist/services/conversation/journal-to-ledger.js +37 -180
  200. package/dist/services/conversation/journal-to-ledger.js.map +1 -1
  201. package/dist/services/conversation/queue-persistence.d.ts +2 -1
  202. package/dist/services/conversation/queue-persistence.d.ts.map +1 -1
  203. package/dist/services/conversation/queue-persistence.js +14 -1
  204. package/dist/services/conversation/queue-persistence.js.map +1 -1
  205. package/dist/services/conversation/run-item-normalizer.d.ts.map +1 -1
  206. package/dist/services/conversation/run-item-normalizer.js +5 -1
  207. package/dist/services/conversation/run-item-normalizer.js.map +1 -1
  208. package/dist/services/conversation/tool-occurrence.d.ts +18 -0
  209. package/dist/services/conversation/tool-occurrence.d.ts.map +1 -0
  210. package/dist/services/conversation/tool-occurrence.js +32 -0
  211. package/dist/services/conversation/tool-occurrence.js.map +1 -0
  212. package/dist/services/discord/discord-bridge.d.ts +41 -0
  213. package/dist/services/discord/discord-bridge.d.ts.map +1 -0
  214. package/dist/services/discord/discord-bridge.js +859 -0
  215. package/dist/services/discord/discord-bridge.js.map +1 -0
  216. package/dist/services/discord/discord-command.d.ts +3 -0
  217. package/dist/services/discord/discord-command.d.ts.map +1 -0
  218. package/dist/services/discord/discord-command.js +172 -0
  219. package/dist/services/discord/discord-command.js.map +1 -0
  220. package/dist/services/discord/discord-credentials.d.ts +12 -0
  221. package/dist/services/discord/discord-credentials.d.ts.map +1 -0
  222. package/dist/services/discord/discord-credentials.js +155 -0
  223. package/dist/services/discord/discord-credentials.js.map +1 -0
  224. package/dist/services/discord/discord-gateway.d.ts +20 -0
  225. package/dist/services/discord/discord-gateway.d.ts.map +1 -0
  226. package/dist/services/discord/discord-gateway.js +257 -0
  227. package/dist/services/discord/discord-gateway.js.map +1 -0
  228. package/dist/services/discord/discord-outbound.d.ts +51 -0
  229. package/dist/services/discord/discord-outbound.d.ts.map +1 -0
  230. package/dist/services/discord/discord-outbound.js +275 -0
  231. package/dist/services/discord/discord-outbound.js.map +1 -0
  232. package/dist/services/discord/discord-send-capability.d.ts +5 -0
  233. package/dist/services/discord/discord-send-capability.d.ts.map +1 -0
  234. package/dist/services/discord/discord-send-capability.js +11 -0
  235. package/dist/services/discord/discord-send-capability.js.map +1 -0
  236. package/dist/services/input-surge-guard.d.ts.map +1 -1
  237. package/dist/services/input-surge-guard.js +4 -3
  238. package/dist/services/input-surge-guard.js.map +1 -1
  239. package/dist/services/large-uncached-input-guard.d.ts +1 -1
  240. package/dist/services/large-uncached-input-guard.d.ts.map +1 -1
  241. package/dist/services/large-uncached-input-guard.js +0 -6
  242. package/dist/services/large-uncached-input-guard.js.map +1 -1
  243. package/dist/services/logging/assistant-turn-journal.d.ts +1 -1
  244. package/dist/services/logging/assistant-turn-journal.d.ts.map +1 -1
  245. package/dist/services/logging/assistant-turn-journal.js +4 -3
  246. package/dist/services/logging/assistant-turn-journal.js.map +1 -1
  247. package/dist/services/logging/conversation-log-events.d.ts +1 -0
  248. package/dist/services/logging/conversation-log-events.d.ts.map +1 -1
  249. package/dist/services/logging/conversation-log-events.js.map +1 -1
  250. package/dist/services/profiles/registry.d.ts.map +1 -1
  251. package/dist/services/profiles/registry.js +23 -6
  252. package/dist/services/profiles/registry.js.map +1 -1
  253. package/dist/services/queue/queue-controller.d.ts +15 -3
  254. package/dist/services/queue/queue-controller.d.ts.map +1 -1
  255. package/dist/services/queue/queue-controller.js +52 -4
  256. package/dist/services/queue/queue-controller.js.map +1 -1
  257. package/dist/services/retry/retry-classifier.d.ts.map +1 -1
  258. package/dist/services/retry/retry-classifier.js +5 -3
  259. package/dist/services/retry/retry-classifier.js.map +1 -1
  260. package/dist/services/retry/retry-errors.d.ts +15 -0
  261. package/dist/services/retry/retry-errors.d.ts.map +1 -1
  262. package/dist/services/retry/retry-errors.js +29 -0
  263. package/dist/services/retry/retry-errors.js.map +1 -1
  264. package/dist/services/retry/retry-recovery-budget.d.ts +13 -0
  265. package/dist/services/retry/retry-recovery-budget.d.ts.map +1 -1
  266. package/dist/services/retry/retry-recovery-budget.js +35 -4
  267. package/dist/services/retry/retry-recovery-budget.js.map +1 -1
  268. package/dist/services/session/background-task-control.d.ts.map +1 -1
  269. package/dist/services/session/background-task-control.js +4 -6
  270. package/dist/services/session/background-task-control.js.map +1 -1
  271. package/dist/services/session/continuation-recovery-handler.d.ts.map +1 -1
  272. package/dist/services/session/continuation-recovery-handler.js +15 -7
  273. package/dist/services/session/continuation-recovery-handler.js.map +1 -1
  274. package/dist/services/session/initial-turn-recovery-handler.d.ts.map +1 -1
  275. package/dist/services/session/initial-turn-recovery-handler.js +15 -7
  276. package/dist/services/session/initial-turn-recovery-handler.js.map +1 -1
  277. package/dist/services/session/session-composition.d.ts +4 -0
  278. package/dist/services/session/session-composition.d.ts.map +1 -1
  279. package/dist/services/session/session-composition.js +10 -1
  280. package/dist/services/session/session-composition.js.map +1 -1
  281. package/dist/services/session/session-identity.d.ts +8 -3
  282. package/dist/services/session/session-identity.d.ts.map +1 -1
  283. package/dist/services/session/session-identity.js +16 -3
  284. package/dist/services/session/session-identity.js.map +1 -1
  285. package/dist/services/session-rollover/session-rollover-brief.d.ts +1 -2
  286. package/dist/services/session-rollover/session-rollover-brief.d.ts.map +1 -1
  287. package/dist/services/session-rollover/session-rollover-brief.js +9 -11
  288. package/dist/services/session-rollover/session-rollover-brief.js.map +1 -1
  289. package/dist/services/stream-event-processor.d.ts.map +1 -1
  290. package/dist/services/stream-event-processor.js +4 -0
  291. package/dist/services/stream-event-processor.js.map +1 -1
  292. package/dist/services/tool-execution-ledger.d.ts +11 -2
  293. package/dist/services/tool-execution-ledger.d.ts.map +1 -1
  294. package/dist/services/tool-execution-ledger.js +123 -30
  295. package/dist/services/tool-execution-ledger.js.map +1 -1
  296. package/dist/skins/testing/scenes.js +1 -1
  297. package/dist/skins/testing/scenes.js.map +1 -1
  298. package/dist/test-helpers/cli-compile-cache-setup.d.ts +7 -0
  299. package/dist/test-helpers/cli-compile-cache-setup.d.ts.map +1 -0
  300. package/dist/test-helpers/cli-compile-cache-setup.js +7 -0
  301. package/dist/test-helpers/cli-compile-cache-setup.js.map +1 -0
  302. package/dist/test-helpers/cli-compile-cache.d.ts +4 -0
  303. package/dist/test-helpers/cli-compile-cache.d.ts.map +1 -0
  304. package/dist/test-helpers/cli-compile-cache.js +33 -0
  305. package/dist/test-helpers/cli-compile-cache.js.map +1 -0
  306. package/dist/test-helpers/integration-cli-global-setup.d.ts +3 -0
  307. package/dist/test-helpers/integration-cli-global-setup.d.ts.map +1 -0
  308. package/dist/test-helpers/integration-cli-global-setup.js +40 -0
  309. package/dist/test-helpers/integration-cli-global-setup.js.map +1 -0
  310. package/dist/test-helpers/integration-cli.d.ts +7 -0
  311. package/dist/test-helpers/integration-cli.d.ts.map +1 -0
  312. package/dist/test-helpers/integration-cli.js +8 -0
  313. package/dist/test-helpers/integration-cli.js.map +1 -0
  314. package/dist/test-helpers/terminal-e2e.d.ts.map +1 -1
  315. package/dist/test-helpers/terminal-e2e.js +40 -2
  316. package/dist/test-helpers/terminal-e2e.js.map +1 -1
  317. package/dist/tools/agent/goal-check.d.ts.map +1 -1
  318. package/dist/tools/agent/goal-check.js +6 -2
  319. package/dist/tools/agent/goal-check.js.map +1 -1
  320. package/dist/tools/discord-send.d.ts +14 -0
  321. package/dist/tools/discord-send.d.ts.map +1 -0
  322. package/dist/tools/discord-send.js +66 -0
  323. package/dist/tools/discord-send.js.map +1 -0
  324. package/dist/tools/system/shell.d.ts.map +1 -1
  325. package/dist/tools/system/shell.js +10 -0
  326. package/dist/tools/system/shell.js.map +1 -1
  327. package/dist/tools/tool-names.d.ts +1 -0
  328. package/dist/tools/tool-names.d.ts.map +1 -1
  329. package/dist/tools/tool-names.js +1 -0
  330. package/dist/tools/tool-names.js.map +1 -1
  331. package/dist/types/message.d.ts +3 -1
  332. package/dist/types/message.d.ts.map +1 -1
  333. package/dist/types/message.js.map +1 -1
  334. package/dist/utils/process-restart.d.ts +33 -0
  335. package/dist/utils/process-restart.d.ts.map +1 -0
  336. package/dist/utils/process-restart.js +52 -0
  337. package/dist/utils/process-restart.js.map +1 -0
  338. package/package.json +4 -3
@@ -11,7 +11,7 @@ import { projectConversationMessage } from '../services/conversation/conversatio
11
11
  import { normalizeRunItem } from '../services/conversation/run-item-normalizer.js';
12
12
  import { FROZEN_AGENT_EVENT_TYPES } from '@qduc/agent-wire';
13
13
  import { GatewayPersistenceError, TERMINAL_AGENT_EVENT_TYPES, } from './persistence/contracts.js';
14
- import { createSessionProjectionSource, hydrateConversationService, } from './persistence/projection.js';
14
+ import { createSessionProjectionSource, wireCallId, hydrateConversationService, } from './persistence/projection.js';
15
15
  import { decideInteraction, InteractionProtocolError, projectPendingInteraction, validatePendingInteractionDto, } from './interaction-protocol.js';
16
16
  import { GatewayAdmissionPersistence, normalizedBodyHash } from './persistence/admission-persistence.js';
17
17
  import { GATEWAY_EVENT_HEARTBEAT_INTERVAL_MS, GATEWAY_EVENT_STREAM_CONTENT_TYPE, GATEWAY_EVENT_STREAM_HEARTBEAT, } from './server.js';
@@ -32,6 +32,9 @@ import { isOAuthAccountProvider, listOAuthAccounts, removeOAuthAccount, setActiv
32
32
  import { loginToCodex } from '../providers/codex-auth.js';
33
33
  import { loginToGrok } from '../providers/grok-auth.js';
34
34
  const MAX_BUFFERED_SSE_EVENTS = 256;
35
+ // How long the replay phase waits for a client that has stopped reading before
36
+ // it is treated as a slow subscriber and closed.
37
+ const SSE_REPLAY_DRAIN_TIMEOUT_MS = 10_000;
35
38
  const NONCRITICAL_RUNTIME_EVENT_TYPES = new Set(['text_delta', 'reasoning_delta']);
36
39
  let malformedCommandMessageDrops = 0;
37
40
  const isCriticalRuntimeEvent = (event) => !NONCRITICAL_RUNTIME_EVENT_TYPES.has(event.type);
@@ -847,6 +850,10 @@ export class Term2Gateway {
847
850
  ownerUserId,
848
851
  sessionId,
849
852
  journal: persisted.persistence.journal,
853
+ activeTurnId: (() => {
854
+ const live = this.#sessions.get(sessionId);
855
+ return live instanceof ServerSession ? live.activeTurnId : null;
856
+ })(),
850
857
  liveInteraction: () => {
851
858
  const session = this.#sessions.get(sessionId);
852
859
  if (!(session instanceof ServerSession))
@@ -1555,12 +1562,19 @@ export class Term2Gateway {
1555
1562
  });
1556
1563
  this.#interactionBindings.delete(claims.sessionId);
1557
1564
  this.#countInteraction('interaction_resolved');
1565
+ // A multi-question ask_user collects one answer per request; the resolved
1566
+ // result carries all of them (in question order), while this request only
1567
+ // carries the last. A single question keeps the answer as sent so its
1568
+ // tool result format does not change.
1569
+ const toolApprovalAnswer = (binding.dto.askUser?.questions.length ?? 0) > 1 && result.approvalAnswer !== undefined
1570
+ ? result.approvalAnswer
1571
+ : decision.approvalAnswer;
1558
1572
  try {
1559
1573
  if (session.abortGeneration !== continuationAbortGeneration || session.activeTurnId !== binding.turnId) {
1560
1574
  throw new ServerSessionError('interrupted');
1561
1575
  }
1562
1576
  await session.service.handleApprovalDecision(decision.answer, decision.rejectionReason, {
1563
- approvalAnswer: decision.approvalAnswer,
1577
+ approvalAnswer: toolApprovalAnswer,
1564
1578
  // Cancelling an ask_user question ends the turn, exactly as Escape
1565
1579
  // does in the TUI: the question and its unanswered result stay in
1566
1580
  // history, and the user speaks next. Without this the web client's
@@ -1598,7 +1612,11 @@ export class Term2Gateway {
1598
1612
  if (after.error)
1599
1613
  return publicError(400, after.error, 'event cursor is invalid');
1600
1614
  const source = await this.#projectionFor(claims.sub, claims.sessionId);
1615
+ // `activeResponse` is the socket to destroy; `liveWrites` flips on only once
1616
+ // the replay and every event queued behind it have been written, so live
1617
+ // events never overtake the replay.
1601
1618
  let activeResponse = null;
1619
+ let liveWrites = false;
1602
1620
  let closed = false;
1603
1621
  let buffered = [];
1604
1622
  let unsubscribe = () => undefined;
@@ -1620,7 +1638,7 @@ export class Term2Gateway {
1620
1638
  const live = source.subscribeFrom(after.value, (event) => {
1621
1639
  if (closed)
1622
1640
  return;
1623
- if (!activeResponse) {
1641
+ if (!liveWrites || !activeResponse) {
1624
1642
  if (buffered.length >= MAX_BUFFERED_SSE_EVENTS)
1625
1643
  return closeSlowSubscriber();
1626
1644
  buffered.push(event);
@@ -1654,18 +1672,59 @@ export class Term2Gateway {
1654
1672
  stream: {
1655
1673
  start: async (response, request) => {
1656
1674
  activeResponse = response;
1675
+ // The replay can be much larger than the socket's write buffer (a
1676
+ // reload mid-way through a long turn replays the whole turn), so wait
1677
+ // for drain between writes. A client that stops reading for
1678
+ // SSE_REPLAY_DRAIN_TIMEOUT_MS is still closed as a slow subscriber.
1679
+ // Live events that arrive meanwhile queue in `buffered` (bounded by
1680
+ // MAX_BUFFERED_SSE_EVENTS) and are written after the replay, in order.
1681
+ const writeAwaitingDrain = async (event) => {
1682
+ if (!isPublicEventEnvelope(event))
1683
+ return closeSlowSubscriber();
1684
+ if (response.write(`id: ${event.id}\ndata: ${JSON.stringify(event)}\n\n`))
1685
+ return;
1686
+ await new Promise((resolve) => {
1687
+ const finish = () => {
1688
+ clearTimeout(timer);
1689
+ response.off('drain', finish);
1690
+ response.off('close', finish);
1691
+ resolve();
1692
+ };
1693
+ const timer = setTimeout(() => {
1694
+ closeSlowSubscriber();
1695
+ finish();
1696
+ }, SSE_REPLAY_DRAIN_TIMEOUT_MS);
1697
+ response.once('drain', finish);
1698
+ response.once('close', finish);
1699
+ });
1700
+ };
1701
+ request.once('close', () => {
1702
+ closed = true;
1703
+ });
1704
+ response.once('close', () => {
1705
+ closed = true;
1706
+ });
1657
1707
  for (const event of live.replay) {
1658
1708
  if (closed)
1659
1709
  break;
1660
- writeEvent(response, event);
1710
+ await writeAwaitingDrain(event);
1661
1711
  }
1662
- const queued = buffered;
1663
- buffered = [];
1664
- for (const event of queued) {
1665
- if (closed)
1666
- break;
1667
- writeEvent(response, event);
1712
+ while (!closed && buffered.length > 0) {
1713
+ const queued = buffered;
1714
+ buffered = [];
1715
+ for (const event of queued) {
1716
+ if (closed)
1717
+ break;
1718
+ await writeAwaitingDrain(event);
1719
+ }
1668
1720
  }
1721
+ if (closed) {
1722
+ unsubscribe();
1723
+ return;
1724
+ }
1725
+ // No await between the last empty check and this flag, so nothing
1726
+ // can be queued after it and left unwritten.
1727
+ liveWrites = true;
1669
1728
  const heartbeat = setInterval(() => {
1670
1729
  if (!response.writableEnded) {
1671
1730
  const accepted = response.write(`${GATEWAY_EVENT_STREAM_HEARTBEAT} ${new Date().toISOString()}\n\n`);
@@ -2777,8 +2836,8 @@ function commandMessagePayload(input) {
2777
2836
  return null;
2778
2837
  }
2779
2838
  return {
2780
- callId: boundedText(callId, 256),
2781
- toolName: boundedText(toolName, 256),
2839
+ callId: wireCallId(callId),
2840
+ toolName: projectionText(toolName, 256),
2782
2841
  status,
2783
2842
  ...(typeof message.output === 'string' ? { output: boundedText(message.output, 16_384) } : {}),
2784
2843
  ...(typeof message.failureReason === 'string' ? { error: boundedText(message.failureReason, 10_000) } : {}),
@@ -2836,7 +2895,74 @@ function sanitizePublicInteraction(value) {
2836
2895
  return validatePendingInteractionDto(value);
2837
2896
  }
2838
2897
  const PROJECTION_COMMAND_STATUSES = new Set(['completed', 'failed', 'aborted', 'unknown']);
2839
- function projectTranscriptMessages(transcript, journalCommands) {
2898
+ const PROJECTION_COMMAND_OUTPUT_MAX = 16_384;
2899
+ const PROJECTION_COMMAND_ERROR_MAX = 10_000;
2900
+ // Same credential shapes the event journal refuses to persist (event-journal.ts
2901
+ // assertSafePayload). A ledger-only body skips that check, so apply it here.
2902
+ const PROJECTION_CREDENTIAL_PATTERN = /^Bearer\s+|^sk-[A-Za-z0-9]/u;
2903
+ const PROJECTION_WITHHELD_MARKER = '[output withheld: looks like a credential]';
2904
+ // Total characters of command output + error one projection may carry. Bodies
2905
+ // beyond it are cut, newest turns first, so the response stays far below the
2906
+ // BFF's 32 MiB cap (worst case a few times this once JSON-escaped).
2907
+ const PROJECTION_COMMAND_BODY_BUDGET = 2 * 1024 * 1024;
2908
+ const PROJECTION_TRUNCATED_MARKER = '[truncated: session transcript size limit]';
2909
+ // The web client's transcript validator rejects NUL and DEL anywhere in a string,
2910
+ // so projected text carrying them would make the whole session unreadable.
2911
+ function projectionText(value, max) {
2912
+ return boundedText(value, max).replace(/[\u0000\u007f]/gu, '\uFFFD');
2913
+ }
2914
+ function projectionCommandText(value, max) {
2915
+ const bounded = projectionText(value, max);
2916
+ if (bounded.length === 0)
2917
+ return undefined;
2918
+ return PROJECTION_CREDENTIAL_PATTERN.test(bounded) ? PROJECTION_WITHHELD_MARKER : bounded;
2919
+ }
2920
+ function projectionCommandDetails(output, error) {
2921
+ const projectedOutput = projectionCommandText(output, PROJECTION_COMMAND_OUTPUT_MAX);
2922
+ const projectedError = projectionCommandText(error, PROJECTION_COMMAND_ERROR_MAX);
2923
+ return {
2924
+ // A rejected approval records the same sentence as output and as failure
2925
+ // reason; show it once.
2926
+ ...(projectedOutput && projectedOutput !== projectedError ? { output: projectedOutput } : {}),
2927
+ ...(projectedError ? { error: projectedError } : {}),
2928
+ };
2929
+ }
2930
+ function fitToBudget(text, max, budget) {
2931
+ if (text.length <= budget.remaining) {
2932
+ budget.remaining -= text.length;
2933
+ return text;
2934
+ }
2935
+ const keep = Math.max(0, Math.min(budget.remaining, max - PROJECTION_TRUNCATED_MARKER.length - 1));
2936
+ budget.remaining = 0;
2937
+ return keep > 0 ? `${text.slice(0, keep)}\n${PROJECTION_TRUNCATED_MARKER}` : PROJECTION_TRUNCATED_MARKER;
2938
+ }
2939
+ // Walk the kept messages newest first and spend the budget on their command bodies.
2940
+ // A body that no longer fits is cut and ends with a visible marker.
2941
+ function applyCommandBodyBudget(messages) {
2942
+ const budget = { remaining: PROJECTION_COMMAND_BODY_BUDGET };
2943
+ for (let i = messages.length - 1; i >= 0; i--) {
2944
+ const commands = messages[i].commands;
2945
+ if (!commands)
2946
+ continue;
2947
+ const fitted = [...commands];
2948
+ for (let j = fitted.length - 1; j >= 0; j--) {
2949
+ const { output, error } = fitted[j];
2950
+ if (output === undefined && error === undefined)
2951
+ continue;
2952
+ // Charge the short error first so a large output cannot use up its budget.
2953
+ const fittedError = error !== undefined ? fitToBudget(error, PROJECTION_COMMAND_ERROR_MAX, budget) : undefined;
2954
+ const fittedOutput = output !== undefined ? fitToBudget(output, PROJECTION_COMMAND_OUTPUT_MAX, budget) : undefined;
2955
+ fitted[j] = {
2956
+ ...fitted[j],
2957
+ ...(fittedOutput !== undefined ? { output: fittedOutput } : {}),
2958
+ ...(fittedError !== undefined ? { error: fittedError } : {}),
2959
+ };
2960
+ }
2961
+ messages[i] = { ...messages[i], commands: fitted };
2962
+ }
2963
+ return messages;
2964
+ }
2965
+ function projectTranscriptMessages(transcript, journalCommands, abortedTurns) {
2840
2966
  if (!transcript)
2841
2967
  return [];
2842
2968
  const commandsByTurn = new Map();
@@ -2850,14 +2976,22 @@ function projectTranscriptMessages(transcript, journalCommands) {
2850
2976
  if (!PROJECTION_COMMAND_STATUSES.has(command.status))
2851
2977
  continue;
2852
2978
  addCommand(command.turnId, {
2853
- callId: boundedText(command.callId, 256),
2979
+ callId: wireCallId(command.callId),
2854
2980
  toolName: boundedText(command.toolName, 256),
2855
2981
  status: boundedText(command.status, 32),
2982
+ ...projectionCommandDetails(command.output, command.failureReason),
2856
2983
  });
2857
2984
  }
2858
2985
  for (const [turnId, commands] of journalCommands) {
2859
- for (const command of commands) {
2860
- const existing = commandsByTurn.get(turnId)?.find((candidate) => candidate.callId === command.callId);
2986
+ for (const journalCommand of commands) {
2987
+ const existing = commandsByTurn.get(turnId)?.find((candidate) => candidate.callId === journalCommand.callId);
2988
+ // The journal owns status; the result body comes from the journal when it
2989
+ // recorded one, otherwise from the transcript ledger.
2990
+ const { output, error, ...journalBase } = journalCommand;
2991
+ const command = {
2992
+ ...journalBase,
2993
+ ...projectionCommandDetails(output ?? existing?.output, error ?? existing?.error),
2994
+ };
2861
2995
  if (existing) {
2862
2996
  const current = commandsByTurn.get(turnId);
2863
2997
  commandsByTurn.set(turnId, current.map((candidate) => (candidate.callId === command.callId ? command : candidate)));
@@ -2869,12 +3003,41 @@ function projectTranscriptMessages(transcript, journalCommands) {
2869
3003
  }
2870
3004
  const projected = [];
2871
3005
  let currentTurnId = null;
3006
+ let currentTurnHasReply = false;
2872
3007
  const attachedTurns = new Set();
3008
+ const botMessage = (id, text) => {
3009
+ const projectedMessage = {
3010
+ id: boundedText(id, 256),
3011
+ role: 'bot',
3012
+ text: projectionText(text, 16_384),
3013
+ };
3014
+ const commands = currentTurnId ? commandsByTurn.get(currentTurnId) : undefined;
3015
+ if (commands && commands.length > 0 && !attachedTurns.has(currentTurnId)) {
3016
+ // Ledger, journal and transcript commands all end up here, so this is the one place their names are cleaned.
3017
+ projectedMessage.commands = commands.map((command) => ({
3018
+ ...command,
3019
+ toolName: projectionText(command.toolName, 256),
3020
+ }));
3021
+ attachedTurns.add(currentTurnId);
3022
+ }
3023
+ return projectedMessage;
3024
+ };
3025
+ // A stopped turn settles only the journal, so the transcript holds its user
3026
+ // message and at most a synthetic "interrupted" note. Project the journal's
3027
+ // partial answer in that note's place, marked as stopped.
3028
+ const flushAbortedTurn = () => {
3029
+ const partial = currentTurnId && !currentTurnHasReply ? abortedTurns.get(currentTurnId) : undefined;
3030
+ if (partial === undefined)
3031
+ return;
3032
+ projected.push({ ...botMessage(`aborted-${currentTurnId}`, partial), status: 'aborted' });
3033
+ };
2873
3034
  for (const message of transcript.messages) {
2874
3035
  const record = message;
2875
3036
  const sender = typeof record.sender === 'string' ? record.sender : record.role;
2876
3037
  if (sender === 'user') {
3038
+ flushAbortedTurn();
2877
3039
  currentTurnId = typeof record.id === 'string' ? record.id : null;
3040
+ currentTurnHasReply = false;
2878
3041
  }
2879
3042
  if (sender === 'command' || sender === 'reasoning') {
2880
3043
  if (sender === 'command' &&
@@ -2882,38 +3045,45 @@ function projectTranscriptMessages(transcript, journalCommands) {
2882
3045
  typeof record.callId === 'string' &&
2883
3046
  typeof record.toolName === 'string' &&
2884
3047
  typeof record.status === 'string' &&
2885
- !commandsByTurn.get(currentTurnId)?.some((command) => command.callId === record.callId)) {
3048
+ !commandsByTurn.get(currentTurnId)?.some((command) => command.callId === wireCallId(String(record.callId)))) {
2886
3049
  addCommand(currentTurnId, {
2887
- callId: boundedText(record.callId, 256),
3050
+ callId: wireCallId(record.callId),
2888
3051
  toolName: boundedText(record.toolName, 256),
2889
3052
  status: boundedText(record.status, 32),
3053
+ ...projectionCommandDetails(record.output, record.failureReason),
2890
3054
  });
2891
3055
  }
2892
3056
  continue;
2893
3057
  }
2894
- const role = sender === 'user' ? 'user' : 'bot';
2895
- const projectedMessage = {
2896
- id: boundedText(record.id, 256),
2897
- role,
2898
- text: boundedText(record.text, 16_384),
2899
- };
2900
- const commands = role === 'bot' && currentTurnId ? commandsByTurn.get(currentTurnId) : undefined;
2901
- if (commands && commands.length > 0 && !attachedTurns.has(currentTurnId)) {
2902
- projectedMessage.commands = commands;
2903
- attachedTurns.add(currentTurnId);
3058
+ if (sender === 'bot')
3059
+ currentTurnHasReply = true;
3060
+ if (sender === 'system' &&
3061
+ currentTurnId &&
3062
+ !currentTurnHasReply &&
3063
+ abortedTurns.has(currentTurnId) &&
3064
+ typeof record.id === 'string' &&
3065
+ record.id.startsWith('system-interrupted-'))
3066
+ continue;
3067
+ if (sender === 'user') {
3068
+ projected.push({ id: boundedText(record.id, 256), role: 'user', text: projectionText(record.text, 16_384) });
3069
+ }
3070
+ else {
3071
+ projected.push(botMessage(record.id, record.text));
2904
3072
  }
2905
- projected.push(projectedMessage);
2906
3073
  }
2907
- return projected.slice(-200);
3074
+ flushAbortedTurn();
3075
+ return applyCommandBodyBudget(projected.slice(-200));
2908
3076
  }
2909
3077
  /**
2910
3078
  * Session transcript wire shape: messages have `id`, `role` (`user` or `bot`),
2911
3079
  * and bounded `text`. A completed turn's first bot message may carry a bounded
2912
- * `commands` array of `{callId, toolName, status}` entries for its tool cards.
3080
+ * `commands` array of `{callId, toolName, status, output?, error?}` entries for
3081
+ * its tool cards. `output` and `error` are bounded per command and by one total
3082
+ * budget per projection (newest first); a cut body ends with a visible marker.
2913
3083
  */
2914
3084
  function toSessionProjection(source) {
2915
3085
  const transcript = source.transcript;
2916
- const messages = projectTranscriptMessages(transcript, source.journalCommands);
3086
+ const messages = projectTranscriptMessages(transcript, source.journalCommands, source.abortedTurns);
2917
3087
  const interaction = source.interaction
2918
3088
  ? {
2919
3089
  ...source.interaction,
@@ -2980,7 +3150,7 @@ export function mapConversationEvent(event, turnId, sessionId, interactionBindin
2980
3150
  return {
2981
3151
  ...base,
2982
3152
  type: 'tool_started',
2983
- payload: { turnId, callId: event.toolCallId, toolName: boundedText(event.toolName, 256) },
3153
+ payload: { turnId, callId: wireCallId(event.toolCallId), toolName: projectionText(event.toolName, 256) },
2984
3154
  };
2985
3155
  case 'command_message':
2986
3156
  return commandMessageCandidate(base, event.message, 'command_message');
@@ -3073,7 +3243,7 @@ export function mapConversationEvent(event, turnId, sessionId, interactionBindin
3073
3243
  turnId,
3074
3244
  agentId: boundedText(event.agentId, 256),
3075
3245
  role: boundedText(event.role, 256),
3076
- toolCallId: boundedText(event.toolCallId, 256),
3246
+ toolCallId: wireCallId(event.toolCallId),
3077
3247
  toolName: boundedText(event.toolName, 256),
3078
3248
  ...(event.commandMessages
3079
3249
  ? {