mixdog 1.2.2 → 1.2.3

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 (415) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/provider-pingpong-live.mjs +509 -0
  4. package/src/agents/advisor/AGENT.md +1 -1
  5. package/src/agents/advisor/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +5 -0
  7. package/src/defaults/skills/setup/SKILL.md +6 -1
  8. package/src/defaults/skills/setup/references/actions.md +44 -22
  9. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  10. package/src/headless-exec.mjs +15 -8
  11. package/src/lib/keychain-cjs.cjs +7 -4
  12. package/src/output-styles/common.md +11 -10
  13. package/src/rules/agent/AGENT.md +5 -5
  14. package/src/rules/lead/LEAD.md +5 -5
  15. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  16. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  17. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  19. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  21. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  22. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  23. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
  39. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  40. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
  41. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  42. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
  43. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
  45. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  50. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
  52. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  53. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  54. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  56. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  57. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  59. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
  60. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
  61. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  62. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
  63. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
  64. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  65. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
  66. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
  68. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  71. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  72. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  73. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  74. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  75. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  76. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  77. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  80. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  81. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  82. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  83. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  108. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  109. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  110. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  111. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  114. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  119. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  121. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
  122. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  123. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
  131. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  132. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  133. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  134. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
  135. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  136. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
  137. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  138. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  139. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  140. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  141. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  142. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  147. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  150. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  151. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  156. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  157. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  158. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  159. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  160. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  161. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  162. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  163. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  164. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  165. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  167. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  168. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  169. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  170. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  171. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  172. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
  173. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
  174. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  175. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  179. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
  180. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  181. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  184. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  187. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  188. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
  189. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  190. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  192. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  193. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  195. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  196. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
  197. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  198. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  199. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  200. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  201. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  203. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  204. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  205. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  206. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  207. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  208. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  209. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  210. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  211. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  212. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  213. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  216. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  217. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  218. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  223. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  224. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  225. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  226. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  227. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  228. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  229. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  230. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  231. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  232. package/src/runtime/github/tool.mjs +92 -2
  233. package/src/runtime/memory/index.mjs +1 -0
  234. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  235. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  236. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  237. package/src/runtime/memory/lib/pg/process.mjs +31 -26
  238. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
  240. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  241. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  242. package/src/runtime/office/README.md +1 -1
  243. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  244. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  245. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  246. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  247. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  248. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  249. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  250. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  251. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  252. package/src/runtime/office/core/office-sessions.mjs +5 -0
  253. package/src/runtime/office/core/office-transactions.mjs +3 -0
  254. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  255. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  256. package/src/runtime/office/index.mjs +30 -0
  257. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  258. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  259. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  260. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  261. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  262. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  263. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  264. package/src/runtime/shared/buffered-appender.mjs +18 -0
  265. package/src/runtime/shared/config.mjs +78 -18
  266. package/src/runtime/shared/err-text.mjs +6 -0
  267. package/src/runtime/shared/file-permissions.d.mts +6 -1
  268. package/src/runtime/shared/file-permissions.mjs +54 -6
  269. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  270. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  271. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  272. package/src/runtime/shared/loopback-listener.mjs +9 -0
  273. package/src/runtime/shared/provider-api-key.mjs +2 -0
  274. package/src/runtime/shared/review-diff.mjs +52 -0
  275. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  276. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  277. package/src/runtime/shared/slow-span.mjs +114 -0
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  279. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  280. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  281. package/src/runtime/shared/tool-card-model.mjs +5 -2
  282. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  283. package/src/runtime/shared/tool-primitives.mjs +13 -0
  284. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  285. package/src/runtime/shared/tool-surface.mjs +18 -3
  286. package/src/runtime/shared/tool-work-units.mjs +40 -4
  287. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  288. package/src/runtime/shared/transcript-writer.mjs +10 -2
  289. package/src/runtime/shared/turn-changes.mjs +268 -100
  290. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  291. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  292. package/src/runtime/shared/turn-snapshot.mjs +857 -683
  293. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  294. package/src/runtime/shared/user-data-guard.mjs +262 -49
  295. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  296. package/src/runtime/shared/worktrees/contract.mjs +34 -0
  297. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  298. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  299. package/src/runtime/shared/worktrees/git.mjs +8 -3
  300. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  301. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  302. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  303. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  304. package/src/runtime/terminal-bridge/client.mjs +44 -11
  305. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  306. package/src/runtime/tidy/apply.mjs +47 -2
  307. package/src/runtime/tidy/run-engines.mjs +11 -5
  308. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  309. package/src/session-runtime/boot/apis.mjs +13 -5
  310. package/src/session-runtime/boot/facade.mjs +1 -0
  311. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  312. package/src/session-runtime/boot/session.mjs +0 -1
  313. package/src/session-runtime/boot/tools.mjs +4 -1
  314. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  315. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  316. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  317. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  318. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  319. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  320. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  321. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  322. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
  323. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  324. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  325. package/src/session-runtime/lifecycle/teardown.mjs +72 -7
  326. package/src/session-runtime/lifecycle-api.mjs +1 -0
  327. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  328. package/src/session-runtime/model-route/set-route.mjs +2 -2
  329. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  330. package/src/session-runtime/native-web-search.mjs +10 -1
  331. package/src/session-runtime/new-session-config.mjs +4 -2
  332. package/src/session-runtime/provider-auth/login.mjs +4 -0
  333. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  334. package/src/session-runtime/runtime-review-api.mjs +8 -2
  335. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  336. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  337. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  338. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  339. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  340. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  341. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  342. package/src/session-runtime/services/agent-tool.mjs +3 -0
  343. package/src/session-runtime/services/channel-admin.mjs +19 -3
  344. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  345. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  346. package/src/session-runtime/services/provider-admin.mjs +81 -15
  347. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  348. package/src/session-runtime/session-turn-api.mjs +14 -5
  349. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  350. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  351. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  352. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  353. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  354. package/src/session-runtime/skills-api.mjs +3 -5
  355. package/src/session-runtime/tool-surface.mjs +5 -1
  356. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  357. package/src/session-runtime/turn/session-ops.mjs +7 -5
  358. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  359. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  360. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  361. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  362. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  363. package/src/session-runtime/turn/turn-run.mjs +25 -9
  364. package/src/session-runtime/usage-stats-api.mjs +43 -11
  365. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  366. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  367. package/src/session-runtime/workspace/transitions.mjs +190 -15
  368. package/src/standalone/daemon-agent-control.mjs +9 -1
  369. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  370. package/src/standalone/daemon-owner.mjs +17 -4
  371. package/src/standalone/daemon-pg-stop.mjs +51 -0
  372. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  373. package/src/standalone/daemon-stop-budget.mjs +18 -0
  374. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  375. package/src/standalone/daemon-telemetry.mjs +19 -0
  376. package/src/standalone/daemon.mjs +61 -1
  377. package/src/standalone/desktop-service-registry.mjs +5 -0
  378. package/src/standalone/local-session-runtime.mjs +70 -19
  379. package/src/standalone/session-client.mjs +42 -0
  380. package/src/standalone/session-protocol.mjs +7 -0
  381. package/src/standalone/session-proxy/projection.mjs +41 -4
  382. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  383. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  384. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  385. package/src/standalone/session-service-api.mjs +6 -1
  386. package/src/standalone/session-state-patch.d.mts +2 -1
  387. package/src/standalone/session-state-patch.mjs +41 -1
  388. package/src/tui/App.jsx +25 -0
  389. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  390. package/src/tui/app/btw-controller.mjs +118 -0
  391. package/src/tui/app/create-app-pickers.mjs +2 -0
  392. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  393. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  394. package/src/tui/app/slash-commands.mjs +13 -3
  395. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  396. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  397. package/src/tui/app/use-global-key-input.mjs +5 -12
  398. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  399. package/src/tui/components/BtwPanel.jsx +53 -0
  400. package/src/tui/components/QuestionPanel.jsx +275 -0
  401. package/src/tui/components/question-panel-model.mjs +156 -0
  402. package/src/tui/dist/index.mjs +2413 -1465
  403. package/src/tui/session/context-state.mjs +4 -2
  404. package/src/tui/session/session-api/commands.mjs +10 -0
  405. package/src/tui/session/session-api/controls.mjs +2 -2
  406. package/src/tui/session/session-api/integrations.mjs +9 -2
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  408. package/src/tui/session/session-api/lifecycle.mjs +2 -1
  409. package/src/tui/session/session-api/settings.mjs +6 -0
  410. package/src/tui/session/session-api.mjs +1 -0
  411. package/src/tui/session/tool-approval.mjs +10 -7
  412. package/src/tui/session/transcript-spill.mjs +14 -7
  413. package/src/tui/session/turn-stream.mjs +174 -31
  414. package/src/tui/session/turn.mjs +12 -5
  415. package/src/workflows/default/WORKFLOW.md +3 -1
@@ -71,8 +71,10 @@ export function createContextState({
71
71
  // Lead workspace record (worktrees/contract.mjs SessionWorkspace): the
72
72
  // registered Project, the execution root, the worktree binding and its
73
73
  // revision. Null while the session has never left its project folder.
74
- // Identity-stable until a transition commits.
75
- workspace: runtime.getWorkspace?.() ?? null,
74
+ // Identity-stable until a transition commits. A project-folder session in a
75
+ // git repository publishes the folder's branch / detached commit too
76
+ // (getLiveWorkspace reads HEAD without spawning git).
77
+ workspace: runtime.getLiveWorkspace?.() ?? runtime.getWorkspace?.() ?? null,
76
78
  systemShell: runtime.systemShell || { source: 'auto', command: '', effective: '' },
77
79
  webSearchRoute: runtime.getWebSearchRoute?.() || runtime.webSearchRoute || null,
78
80
  autoClear: autoClearState(),
@@ -2,6 +2,7 @@
2
2
  * commands.mjs — the session object's long-running slash commands: memory
3
3
  * control, recall, doctor, and manual compaction (scheduled for turn end when
4
4
  * a turn is running). Each shows a command status while it holds the lock.
5
+ * /btw side questions (askSide/cancelSide) run beside them, lock-free.
5
6
  */
6
7
  import { compactEventDetail } from '../labels.mjs';
7
8
  import { archiveItemsForCompaction, compactionArchivedRows } from '../compaction-view.mjs';
@@ -175,5 +176,14 @@ export function createSessionCommandsApi(bag) {
175
176
  });
176
177
  },
177
178
  compact: compactCommand,
179
+ // /btw: answered from the conversation so far while a turn keeps running.
180
+ // No command lock and no transcript row: the answer is only returned.
181
+ askSide: (question) => runtime.askSide(question),
182
+ // `sessionId` names the session the side question was asked in (it may
183
+ // no longer be the current one after a switch); omitted, the current one.
184
+ cancelSide: (sessionId) => {
185
+ if (typeof sessionId === 'string' && sessionId) runtime.cancelSide(sessionId);
186
+ else runtime.cancelSide();
187
+ },
178
188
  };
179
189
  }
@@ -91,8 +91,8 @@ export function createSessionControlsApi(bag) {
91
91
  );
92
92
  return result;
93
93
  },
94
- setCwd: (path, options = {}) => {
95
- const next = runtime.setCwd(path);
94
+ setCwd: async (path, options = {}) => {
95
+ const next = await runtime.setCwd(path);
96
96
  // Republish up-arrow history for the NEW project: current session prompts
97
97
  // merged with the cwd-scoped persisted store for the new cwd.
98
98
  const sessionList = recomputePromptHistory(getState().items);
@@ -38,8 +38,8 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
38
38
  getQuotaHistory: async (options = {}) => {
39
39
  return await runtime.getQuotaHistory?.(options);
40
40
  },
41
- getSessionUsage: async () => {
42
- return await runtime.getSessionUsage?.();
41
+ getSessionUsage: async (options = {}) => {
42
+ return await runtime.getSessionUsage?.(options);
43
43
  },
44
44
  consumeCodexRateLimitResetCredit: async (options = {}) => {
45
45
  // Desktop capability parity: without this delegation the session runtime
@@ -52,6 +52,12 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
52
52
  getSessionReviewDiff: async () => {
53
53
  return (await runtime.getSessionReviewDiff?.()) ?? { supported: false, files: [], patch: '' };
54
54
  },
55
+ getSessionReviewFilePatch: async (file, oldPath) => {
56
+ return (await runtime.getSessionReviewFilePatch?.(file, oldPath)) ?? { supported: false, patch: '' };
57
+ },
58
+ getTurnReviewFilePatch: async (file) => {
59
+ return (await runtime.getTurnReviewFilePatch?.(file)) ?? { supported: false, patch: '' };
60
+ },
55
61
  revertTurnReview: async (checkpointId) => {
56
62
  return await requireRuntimeMethod('revertTurnReview', 'Turn review revert is unavailable')(checkpointId);
57
63
  },
@@ -140,6 +146,7 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
140
146
  return result;
141
147
  },
142
148
  testCustomProvider: (input) => runtime.testCustomProvider(input),
149
+ checkCustomProviderKeyEnv: (name) => runtime.checkCustomProviderKeyEnv(name),
143
150
  discoverCustomProviderModels: (input) => runtime.discoverCustomProviderModels(input),
144
151
  saveOpenCodeGoUsageAuth: async (opts) => {
145
152
  await runtime.saveOpenCodeGoUsageAuth(opts);
@@ -40,6 +40,7 @@ export function createDisposeAction(bag, { oauthFlows }) {
40
40
  'unsubscribeAgentStatus',
41
41
  'unsubscribeRemoteState',
42
42
  'unsubscribeCompletionWakeGate',
43
+ 'unsubscribeGitHeadWatch',
43
44
  ]) {
44
45
  try {
45
46
  lifecycle[key]?.();
@@ -56,7 +56,8 @@ export function createSessionLifecycleApi(bag, { restoreTranscriptItems, oauthFl
56
56
  id,
57
57
  approved,
58
58
  decision?.reason || (approved ? 'approved by user' : 'denied by user'),
59
- typeof decision?.device === 'string' ? decision.device : ''
59
+ typeof decision?.device === 'string' ? decision.device : '',
60
+ decision?.answers
60
61
  );
61
62
  },
62
63
  pushNotice,
@@ -106,6 +106,12 @@ export function createSessionSettingsApi(bag) {
106
106
  deferStatsRefresh();
107
107
  return next;
108
108
  }),
109
+ getSessionTitleSettings: () => {
110
+ return runtime.getSessionTitleSettings?.() || { aiSessionTitles: true };
111
+ },
112
+ setAiSessionTitles: withCommandLock(async (enabled) => {
113
+ return await runtime.setAiSessionTitles?.(enabled);
114
+ }),
109
115
  getToolModuleSettings: () => {
110
116
  return (
111
117
  runtime.getToolModuleSettings?.() || {
@@ -33,6 +33,7 @@ export function createSessionApi(bag) {
33
33
  autoClear: await read(api.getAutoClear),
34
34
  compaction: await read(api.getCompactionSettings),
35
35
  recap: await read(api.getRecapSettings),
36
+ sessionTitles: await read(api.getSessionTitleSettings),
36
37
  toolModules: await read(api.getToolModuleSettings),
37
38
  channels: await read(api.getChannelSettings, { includeStatus: false }),
38
39
  systemShell: await read(api.getSystemShell),
@@ -12,17 +12,20 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
12
12
 
13
13
  function normalizeToolApprovalRequest(input = {}, id = nextId()) {
14
14
  const now = Date.now();
15
+ const requestTimeoutMs =
16
+ Number.isFinite(Number(input?.timeoutMs)) && Number(input.timeoutMs) > 0 ? Number(input.timeoutMs) : timeoutMs;
15
17
  return {
16
18
  id,
17
19
  name: String(input?.name || input?.tool_name || 'tool'),
20
+ kind: typeof input?.kind === 'string' ? input.kind : '',
18
21
  args: input?.args ?? input?.tool_input ?? null,
19
22
  cwd: input?.cwd || null,
20
23
  sessionId: input?.sessionId || input?.session_id || null,
21
24
  toolCallId: input?.toolCallId || input?.tool_use_id || null,
22
25
  reason: String(input?.reason || input?.message || 'approval requested by hook').trim(),
23
26
  requestedAt: now,
24
- timeoutMs,
25
- expiresAt: now + timeoutMs,
27
+ timeoutMs: requestTimeoutMs,
28
+ expiresAt: now + requestTimeoutMs,
26
29
  };
27
30
  }
28
31
  // Every request expires from ITS OWN requestedAt, queued or active. Arming
@@ -39,10 +42,10 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
39
42
  // Settle one entry: drop its timeout, then hand the waiter its answer. The
40
43
  // resolve stays guarded — a waiter whose caller already went away must never
41
44
  // take the rest of the queue down with it.
42
- function settleToolApproval(entry, approved, reason) {
45
+ function settleToolApproval(entry, approved, reason, answers) {
43
46
  if (entry?.timer) clearTimeout(entry.timer);
44
47
  try {
45
- entry.resolve({ approved, reason });
48
+ entry.resolve(answers === undefined ? { approved, reason } : { approved, reason, answers });
46
49
  } catch {}
47
50
  }
48
51
  function presentNextToolApproval() {
@@ -56,7 +59,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
56
59
  armToolApprovalTimeout(entry);
57
60
  set({ toolApproval: entry.request });
58
61
  }
59
- function finishToolApproval(id, approved, reason = '', device = '') {
62
+ function finishToolApproval(id, approved, reason = '', device = '', answers = undefined) {
60
63
  const targetId = String(id || '');
61
64
  if (activeToolApproval && activeToolApproval.id === targetId) {
62
65
  const entry = activeToolApproval;
@@ -67,14 +70,14 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
67
70
  ? { id: targetId, approved: approved === true, device: String(device), at: Date.now() }
68
71
  : null;
69
72
  set({ toolApproval: null, toolApprovalResult });
70
- settleToolApproval(entry, approved === true, String(reason || ''));
73
+ settleToolApproval(entry, approved === true, String(reason || ''), answers);
71
74
  presentNextToolApproval();
72
75
  return true;
73
76
  }
74
77
  const index = toolApprovalQueue.findIndex((entry) => entry.id === targetId);
75
78
  if (index >= 0) {
76
79
  const [entry] = toolApprovalQueue.splice(index, 1);
77
- settleToolApproval(entry, approved === true, String(reason || ''));
80
+ settleToolApproval(entry, approved === true, String(reason || ''), answers);
78
81
  return true;
79
82
  }
80
83
  return false;
@@ -30,6 +30,13 @@ export { cleanupStaleTranscriptSpillDirs };
30
30
 
31
31
  let staleSpillSweepDone = false;
32
32
 
33
+ /** One sweep per process: it lists the whole temp directory synchronously. */
34
+ export function sweepStaleTranscriptSpillDirsOnce() {
35
+ if (staleSpillSweepDone) return;
36
+ staleSpillSweepDone = true;
37
+ cleanupStaleTranscriptSpillDirs();
38
+ }
39
+
33
40
  // Serialized pages deliberately release the old item object graph while
34
41
  // keeping every byte restorable. Only `items` is render-live and walkable.
35
42
  // This buffer owns the page list, the reading cursor and snapshots; the spill
@@ -47,12 +54,9 @@ export function createTranscriptSpillBuffer({
47
54
  try {
48
55
  writeOwnerRegistry();
49
56
  } catch {}
50
- // One sweep per process: it lists the whole temp directory synchronously,
51
- // and a buffer is created per session.
52
- if (!staleSpillSweepDone) {
53
- staleSpillSweepDone = true;
54
- cleanupStaleTranscriptSpillDirs();
55
- }
57
+ // A buffer is created per session; the sweep runs once per process (the
58
+ // daemon runs it in its post-ready warm-up).
59
+ sweepStaleTranscriptSpillDirsOnce();
56
60
  const pages = [];
57
61
  let cursor = null;
58
62
  let spillDir = null;
@@ -228,13 +232,16 @@ export function reindexItems(itemIndexById, items) {
228
232
  export function replaceSessionItemsState({ state, items, itemIndexById, preserveStreamingTail = false, extra = {} }) {
229
233
  const nextItems = Array.isArray(items) ? items : [];
230
234
  reindexItems(itemIndexById, nextItems);
231
- return {
235
+ const next = {
232
236
  ...state,
233
237
  ...extra,
234
238
  items: nextItems,
235
239
  structureRevision: (Number(state.structureRevision) || 0) + 1,
236
240
  streamingTail: preserveStreamingTail ? state.streamingTail : null,
237
241
  };
242
+ // The desktop's partial line belongs to the live row it continues.
243
+ if (!preserveStreamingTail && next.streamingPartial) next.streamingPartial = null;
244
+ return next;
238
245
  }
239
246
 
240
247
  // Shared by the live session runtime and focused transcript tests so revision/tail
@@ -1,6 +1,7 @@
1
1
  // Per-turn streaming state for the lead TUI turn: the assistant text
2
2
  // segments, the thinking (reasoning) timer, and the frame-batched flush that
3
- // publishes completed visible lines and thinking transitions to the store.
3
+ // publishes completed visible lines and thinking transitions to the store,
4
+ // plus the desktop-only unfinished line (`streamingPartial`, see partialNow).
4
5
  // `createTurnStream` returns one explicit state object (deps + mutable
5
6
  // scalars); every function here takes it as its first argument.
6
7
  import { TUI_FRAME_MS, yieldToRenderer } from './render-timing.mjs';
@@ -11,6 +12,10 @@ import { newTranscriptMessageId } from '../../runtime/shared/transcript-metadata
11
12
  // 16ms timer left every other terminal frame idle, so completed script lines
12
13
  // accumulated and then climbed into view in coarse two-step chunks.
13
14
  const STREAM_BATCH_INTERVAL_MS = TUI_FRAME_MS;
15
+ // The desktop's unfinished-line channel (see partialNow): at most one
16
+ // publication per interval, the first chunk after an idle gap on the next
17
+ // frame. Forced flushes (tool calls, notices, finalization) publish at once.
18
+ const PARTIAL_INTERVAL_MS = 40;
14
19
 
15
20
  // deps: getState, set, nextId, updateStreamingTail, settleStreamingTail,
16
21
  // clearStreamingTail, isCurrentTurn, transcriptMeta, routeMeta, sealToolBlock.
@@ -53,42 +58,59 @@ export function createTurnStream(deps) {
53
58
  publishedThinkingActive: false,
54
59
  // Live countdown of a reconnect wait (see startReconnectCountdown).
55
60
  reconnectTimer: null,
61
+ // The row identity (id, time, message id) claimed before the row exists,
62
+ // while only the desktop's partial line shows it; the row takes it over.
63
+ assistantIdentity: null,
64
+ // Desktop partial-line publication timer and last publish time.
65
+ partialTimer: null,
66
+ partialPublishedAt: 0,
67
+ // This segment's partial is on screen / was cleared by a transcript
68
+ // replacement (and must not come back).
69
+ partialShown: false,
70
+ partialDropped: false,
56
71
  };
57
72
  }
58
73
 
74
+ function claimAssistantIdentity(stream) {
75
+ if (stream.assistantIdentity) return stream.assistantIdentity;
76
+ const id = stream.nextId();
77
+ const meta = stream.transcriptMeta;
78
+ // A non-streaming continuation was persisted before its text reached this
79
+ // row: the runtime left that message's id and time (ask-session
80
+ // takeAssistant), and the row carries them instead of minting new ones.
81
+ const persisted = stream.claimPersistedAssistant ? meta.persistedAssistant : null;
82
+ if (persisted) meta.persistedAssistant = null;
83
+ let assistantAt = persisted?.at;
84
+ let messageId = persisted?.messageId;
85
+ if (!persisted) {
86
+ assistantAt =
87
+ Number.isFinite(Number(meta.assistantAt)) && Number(meta.assistantAt) > 0 ? Number(meta.assistantAt) : Date.now();
88
+ meta.assistantAt = assistantAt;
89
+ // The reply this segment becomes is persisted with this stable id (the
90
+ // runtime takes it with assistantAt), so a compaction that drops the
91
+ // reply greys this row.
92
+ if (!(typeof meta.assistantMessageId === 'string' && meta.assistantMessageId)) {
93
+ meta.assistantMessageId = newTranscriptMessageId();
94
+ }
95
+ messageId = meta.assistantMessageId;
96
+ }
97
+ stream.assistantIdentity = { id, at: assistantAt, messageId };
98
+ return stream.assistantIdentity;
99
+ }
100
+
59
101
  function ensureAssistant(stream, initialText = '') {
60
102
  if (!stream.currentAssistantId) {
61
- stream.currentAssistantId = stream.nextId();
62
- const meta = stream.transcriptMeta;
63
- // A non-streaming continuation was persisted before its text reached this
64
- // row: the runtime left that message's id and time (ask-session
65
- // takeAssistant), and the row carries them instead of minting new ones.
66
- const persisted = stream.claimPersistedAssistant ? meta.persistedAssistant : null;
67
- if (persisted) meta.persistedAssistant = null;
68
- let assistantAt = persisted?.at;
69
- let messageId = persisted?.messageId;
70
- if (!persisted) {
71
- assistantAt =
72
- Number.isFinite(Number(meta.assistantAt)) && Number(meta.assistantAt) > 0
73
- ? Number(meta.assistantAt)
74
- : Date.now();
75
- meta.assistantAt = assistantAt;
76
- // The reply this segment becomes is persisted with this stable id (the
77
- // runtime takes it with assistantAt), so a compaction that drops the
78
- // reply greys this row.
79
- if (!(typeof meta.assistantMessageId === 'string' && meta.assistantMessageId)) {
80
- meta.assistantMessageId = newTranscriptMessageId();
81
- }
82
- messageId = meta.assistantMessageId;
83
- }
103
+ const { id, at, messageId } = claimAssistantIdentity(stream);
104
+ stream.assistantIdentity = null;
105
+ stream.currentAssistantId = id;
84
106
  // Do NOT reset currentAssistantText here: the first onTextDelta has
85
107
  // already accumulated the opening chunk before this batched flush runs.
86
108
  // Segment resets are owned by closeAssistantSegment(). Seed the new row
87
109
  // with the already-visible text so the ● gutter and the first body line
88
110
  // appear in the SAME set()/emit() — no empty "●-only" row.
89
- stream.updateStreamingTail(stream.currentAssistantId, {
111
+ stream.updateStreamingTail(id, {
90
112
  text: String(initialText || ''),
91
- at: assistantAt,
113
+ at,
92
114
  messageId,
93
115
  ...stream.routeMeta,
94
116
  });
@@ -96,6 +118,85 @@ function ensureAssistant(stream, initialText = '') {
96
118
  return stream.currentAssistantId;
97
119
  }
98
120
 
121
+ // The desktop paints streamed text before its line completes; the terminal
122
+ // keeps the line-gated tail below. `streamingPartial` is the unfinished line
123
+ // beyond the published tail text (`from` is that text's length), cut from the
124
+ // same leak-guarded text the tail is, never raw provider output. Before the
125
+ // row's first completed line there is no tail: the partial then carries all
126
+ // visible text so far plus the row identity (`tail`) the row will take over.
127
+ // Terminal projections drop the field (session-proxy/projection.mjs).
128
+ function partialNow(stream) {
129
+ const text = stream.currentAssistantText;
130
+ const id = stream.currentAssistantId;
131
+ if (id != null) {
132
+ const tail = stream.getState().streamingTail;
133
+ if (!tail || tail.id !== id) return null;
134
+ const shown = typeof tail.text === 'string' ? tail.text : '';
135
+ if (text.length <= shown.length || !text.startsWith(shown)) return null;
136
+ return { id, from: shown.length, text: text.slice(shown.length) };
137
+ }
138
+ // Same rule as the row itself: nothing appears before visible content.
139
+ if (!text.trim()) return null;
140
+ const identity = claimAssistantIdentity(stream);
141
+ return {
142
+ id: identity.id,
143
+ from: 0,
144
+ text,
145
+ tail: {
146
+ kind: 'assistant',
147
+ id: identity.id,
148
+ streaming: true,
149
+ at: identity.at,
150
+ messageId: identity.messageId,
151
+ ...stream.routeMeta,
152
+ },
153
+ };
154
+ }
155
+
156
+ function samePartial(left, right) {
157
+ if (left === right) return true;
158
+ if (!left || !right) return false;
159
+ return left.id === right.id && left.from === right.from && left.text === right.text && !left.tail === !right.tail;
160
+ }
161
+
162
+ function clearPartialTimer(stream) {
163
+ if (stream.partialTimer === null) return;
164
+ clearTimeout(stream.partialTimer);
165
+ stream.partialTimer = null;
166
+ }
167
+
168
+ function publishPartial(stream) {
169
+ clearPartialTimer(stream);
170
+ if (!stream.isCurrentTurn()) return;
171
+ const current = stream.getState().streamingPartial ?? null;
172
+ // A transcript replacement cleared the partial this segment showed: the
173
+ // segment stays gone, like a tail the replacement dropped.
174
+ if (stream.partialShown && current === null) stream.partialDropped = true;
175
+ if (stream.partialDropped) return;
176
+ const next = partialNow(stream);
177
+ stream.partialShown = next !== null;
178
+ if (samePartial(current, next)) return;
179
+ stream.partialPublishedAt = Date.now();
180
+ stream.set({ streamingPartial: next });
181
+ }
182
+
183
+ function schedulePartial(stream) {
184
+ if (stream.partialTimer !== null) return;
185
+ const wait = Math.max(STREAM_BATCH_INTERVAL_MS, PARTIAL_INTERVAL_MS - (Date.now() - stream.partialPublishedAt));
186
+ stream.partialTimer = setTimeout(() => {
187
+ stream.partialTimer = null;
188
+ flushStreamBatch(stream);
189
+ }, wait);
190
+ stream.partialTimer?.unref?.();
191
+ }
192
+
193
+ // The segment's text settled (or was dropped): no partial line outlives it.
194
+ function clearPartial(stream) {
195
+ clearPartialTimer(stream);
196
+ stream.partialShown = false;
197
+ if (stream.getState().streamingPartial != null && stream.isCurrentTurn()) stream.set({ streamingPartial: null });
198
+ }
199
+
99
200
  function resetLineScan(stream) {
100
201
  stream.scanLen = 0;
101
202
  stream.lastNewlineIdx = -1;
@@ -107,7 +208,10 @@ function closeAssistantSegment(stream) {
107
208
  stream.currentAssistantId = null;
108
209
  stream.currentAssistantText = '';
109
210
  stream.claimPersistedAssistant = false;
211
+ stream.assistantIdentity = null;
110
212
  resetLineScan(stream);
213
+ clearPartial(stream);
214
+ stream.partialDropped = false;
111
215
  }
112
216
 
113
217
  export function commitAssistantSegment(stream, { sealToolBlock = false } = {}) {
@@ -173,12 +277,14 @@ function flushTextBatch(stream) {
173
277
  const { getState, set } = stream;
174
278
  const visibleText = streamingVisibleTextNow(stream);
175
279
  const patch = {};
280
+ let rowCreated = false;
176
281
  // Do NOT create the assistant row (and scroll the transcript) before there
177
282
  // is a completed line with VISIBLE content: the row appears together with
178
283
  // its first visible line. `.trim()` also guards a response that opens with
179
284
  // leading newlines, whose blank first line would reserve rows and scroll
180
285
  // onto an empty band until a non-blank line lands.
181
286
  if (stream.currentAssistantId || visibleText.trim()) {
287
+ rowCreated = !stream.currentAssistantId;
182
288
  const id = ensureAssistant(stream, visibleText);
183
289
  const current = getState().streamingTail;
184
290
  if (!current || current.id !== id || !Object.is(current.text, visibleText)) {
@@ -214,6 +320,9 @@ function flushTextBatch(stream) {
214
320
  set(patch);
215
321
  }
216
322
  stream.pendingThinkingLastEndedAt = 0;
323
+ // A completed line created or moved the tail: the partial line restarts
324
+ // after it in the same frame, so the two never disagree on screen.
325
+ if (rowCreated || patch.streamingTail) publishPartial(stream);
217
326
  }
218
327
 
219
328
  function thinkingSpinnerPatch(stream, thinkingElapsedMs) {
@@ -256,7 +365,7 @@ function flushThinkBatch(stream) {
256
365
  stream.publishedThinkingActive = nextThinkingActive;
257
366
  }
258
367
 
259
- export function flushStreamBatch(stream) {
368
+ function flushPendingBatches(stream) {
260
369
  if (stream.batchTimer !== null) {
261
370
  clearTimeout(stream.batchTimer);
262
371
  stream.batchTimer = null;
@@ -265,6 +374,7 @@ export function flushStreamBatch(stream) {
265
374
  stream.pendingTextFlush = false;
266
375
  stream.pendingThinkFlush = false;
267
376
  stream.pendingThinkingLastEndedAt = 0;
377
+ clearPartialTimer(stream);
268
378
  return;
269
379
  }
270
380
  if (stream.pendingTextFlush) {
@@ -277,9 +387,17 @@ export function flushStreamBatch(stream) {
277
387
  }
278
388
  }
279
389
 
390
+ // A forced flush is a boundary (tool call, notice, finalization, error, or
391
+ // the partial-line tick): the line-gated batch lands, and so does the
392
+ // desktop's unfinished line.
393
+ export function flushStreamBatch(stream) {
394
+ flushPendingBatches(stream);
395
+ publishPartial(stream);
396
+ }
397
+
280
398
  function scheduleStreamFlush(stream) {
281
399
  if (stream.batchTimer !== null) return; // already scheduled; do not re-arm
282
- stream.batchTimer = setTimeout(() => flushStreamBatch(stream), STREAM_BATCH_INTERVAL_MS);
400
+ stream.batchTimer = setTimeout(() => flushPendingBatches(stream), STREAM_BATCH_INTERVAL_MS);
283
401
  stream.batchTimer?.unref?.(); // don't prevent process exit
284
402
  }
285
403
 
@@ -306,6 +424,7 @@ export function appendTextDelta(stream, textChunk) {
306
424
  stream.pendingTextFlush = true;
307
425
  if (thinkingLastEndedAt) stream.pendingThinkingLastEndedAt = thinkingLastEndedAt;
308
426
  scheduleStreamFlush(stream);
427
+ schedulePartial(stream);
309
428
  }
310
429
 
311
430
  // Mid-turn assistant text that precedes a tool call from a provider that only
@@ -320,6 +439,9 @@ export function appendAssistantText(stream, full, { committed = false } = {}) {
320
439
  // id. Any other text (a tool preamble published before its message commits)
321
440
  // leaves an earlier recorded id unclaimed, so drop it.
322
441
  stream.claimPersistedAssistant = committed;
442
+ // A committed message's row takes its persisted identity, never one claimed
443
+ // earlier for partial text that has since been retracted.
444
+ if (committed && !stream.currentAssistantId) stream.assistantIdentity = null;
323
445
  if (!committed && stream.transcriptMeta && typeof stream.transcriptMeta === 'object') {
324
446
  stream.transcriptMeta.persistedAssistant = null;
325
447
  }
@@ -364,6 +486,8 @@ export function resetStreamedText(stream, { chars, reasoning } = {}) {
364
486
  stream.currentAssistantId = null;
365
487
  }
366
488
  }
489
+ // The retraction applies to the desktop's partial line in the same frame.
490
+ publishPartial(stream);
367
491
  return true;
368
492
  }
369
493
 
@@ -508,18 +632,37 @@ export function settleFinalText(stream, finalText) {
508
632
  text: stream.currentAssistantText || stream.assistantText,
509
633
  });
510
634
  }
635
+ clearPartial(stream);
511
636
  }
512
637
 
513
638
  // On abort, preserve only the still-open segment: tool boundaries already
514
639
  // sealed prior progress segments into their own rows, and replaying the
515
640
  // turn-global text would create one giant duplicate after the cancelled card.
516
641
  export function settleOpenSegmentOnCancel(stream) {
517
- if (!stream.currentAssistantText.trim()) return;
518
- const id = stream.currentAssistantId || ensureAssistant(stream, stream.currentAssistantText);
519
- stream.settleStreamingTail(id, { text: stream.currentAssistantText });
642
+ if (stream.currentAssistantText.trim()) {
643
+ const id = stream.currentAssistantId || ensureAssistant(stream, stream.currentAssistantText);
644
+ stream.settleStreamingTail(id, { text: stream.currentAssistantText });
645
+ }
646
+ clearPartial(stream);
647
+ }
648
+
649
+ // A segment that never completed a line has no tail, only the partial row the
650
+ // desktop shows. A turn that ends without finalizing it (provider error,
651
+ // aborted tool loop) keeps that text as its row, like cancellation does. A
652
+ // transcript replacement cleared the partial, and then the text stays gone.
653
+ function settlePartialOnlySegment(stream) {
654
+ const identity = stream.assistantIdentity;
655
+ const open = stream.getState().streamingPartial;
656
+ if (stream.currentAssistantId || stream.partialDropped || !identity || open?.id !== identity.id) return;
657
+ if (!stream.isCurrentTurn()) return;
658
+ const text = stream.currentAssistantText;
659
+ if (!text.trim()) return;
660
+ stream.settleStreamingTail(ensureAssistant(stream, text), { text });
520
661
  }
521
662
 
522
663
  export function settleStreamingTailAtClose(stream) {
664
+ settlePartialOnlySegment(stream);
665
+ clearPartial(stream);
523
666
  const id = stream.currentAssistantId;
524
667
  if (!id || stream.getState().streamingTail?.id !== id) return;
525
668
  if (stream.currentAssistantText.trim()) stream.settleStreamingTail(id, { text: stream.currentAssistantText });
@@ -15,10 +15,7 @@ import { preserveGoalStateAfterTurn } from './goal-turn-state.mjs';
15
15
  export { preserveGoalStateAfterTurn } from './goal-turn-state.mjs';
16
16
  export { transcriptToolCallDisplayMode } from './turn-tool-cards.mjs';
17
17
  import { isRejected429, safeErrorDetails } from '../../runtime/shared/error-presentation.mjs';
18
- import {
19
- QUOTA_KINDS,
20
- providerErrorKind,
21
- } from '../../runtime/agent/orchestrator/providers/provider-error-kind.mjs';
18
+ import { QUOTA_KINDS, providerErrorKind } from '../../runtime/agent/orchestrator/providers/provider-error-kind.mjs';
22
19
  import { registerTaskWaitSteeringCheck } from '../../runtime/agent/orchestrator/session/task-wait-control.mjs';
23
20
  import { promptDisplayText, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
24
21
  import { yieldToRenderer } from './render-timing.mjs';
@@ -394,7 +391,15 @@ export function streamHandlers(ctx) {
394
391
  const { isCurrentTurn, stream } = ctx;
395
392
  return {
396
393
  onStageChange: async (stage, detail = null) => {
397
- if (isCurrentTurn()) await applyStageChange(stream, stage, detail);
394
+ if (!isCurrentTurn()) return;
395
+ // A runtime notice (e.g. a tool the provider left out of the request)
396
+ // becomes a transcript notice the TUI and desktop render.
397
+ if (stage === 'notice') {
398
+ flushStreamBatch(stream);
399
+ ctx.pushNotice(detail?.message, detail?.tone === 'warn' ? 'warn' : 'info', { transcript: true });
400
+ return;
401
+ }
402
+ await applyStageChange(stream, stage, detail);
398
403
  },
399
404
  onTextDelta: (chunk) => {
400
405
  const textChunk = String(chunk ?? '');
@@ -649,6 +654,8 @@ export function createRunTurn(bag) {
649
654
  set({
650
655
  busy: true,
651
656
  lastTurn: null,
657
+ // A turn that unwound stale never cleared its desktop partial line.
658
+ ...(getState().streamingPartial ? { streamingPartial: null } : {}),
652
659
  spinner: {
653
660
  active: true,
654
661
  verb: pickVerb(turn.index),
@@ -6,7 +6,9 @@ description: "Handle tasks as requested."
6
6
 
7
7
  Consult the user and build the work plan together. Explicit read-only requests
8
8
  proceed immediately. When file edits, state mutation, or delegation are
9
- required, investigate and plan only until the user approves the latest plan.
9
+ required, investigate and plan only until the user approves the latest plan. If the
10
+ plan leaves the user a choice among clear options, ask it with `ask_user`
11
+ before asking for approval.
10
12
 
11
13
  When user input is required, park only the affected work and continue
12
14
  unaffected approved work. Ask pending questions together once no such work