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
@@ -8,6 +8,35 @@
8
8
  */
9
9
  import { applySessionStatePatch } from '../session-state-patch.mjs';
10
10
 
11
+ // Desktop-only live fields: `streamingPartial` is the unfinished streamed
12
+ // line the desktop paints ahead of the line-gated tail. This terminal view
13
+ // keeps the line-gated text, so it never holds the field nor re-renders for it.
14
+ const DESKTOP_ONLY_FIELDS = ['streamingPartial'];
15
+
16
+ function terminalState(full) {
17
+ if (!full || typeof full !== 'object' || !DESKTOP_ONLY_FIELDS.some((key) => Object.hasOwn(full, key))) return full;
18
+ const next = { ...full };
19
+ for (const key of DESKTOP_ONLY_FIELDS) delete next[key];
20
+ return next;
21
+ }
22
+
23
+ /** The patch without desktop-only fields, or null when nothing else moved. */
24
+ function terminalPatch(patch) {
25
+ const set = patch.set && typeof patch.set === 'object' ? patch.set : {};
26
+ const remove = Array.isArray(patch.remove) ? patch.remove : [];
27
+ if (!patch.partialAppend && !DESKTOP_ONLY_FIELDS.some((key) => Object.hasOwn(set, key) || remove.includes(key))) {
28
+ return patch;
29
+ }
30
+ const nextSet = { ...set };
31
+ for (const key of DESKTOP_ONLY_FIELDS) delete nextSet[key];
32
+ const nextRemove = remove.filter((key) => !DESKTOP_ONLY_FIELDS.includes(key));
33
+ if (Object.keys(nextSet).length === 0 && nextRemove.length === 0 && !patch.itemsAppend && !patch.itemsPrepend) {
34
+ return null;
35
+ }
36
+ const { partialAppend: _desktopOnly, ...rest } = patch;
37
+ return { ...rest, set: nextSet, remove: nextRemove };
38
+ }
39
+
11
40
  export function createProjection({ binding, log }) {
12
41
  let state = {};
13
42
  let revision = 0;
@@ -40,7 +69,7 @@ export function createProjection({ binding, log }) {
40
69
  const incomingRevision = Number(body.revision);
41
70
  if (revisionAttachment !== sourceAttachment || revisionSessionId !== binding.sessionId) {
42
71
  if (body.full === undefined || body.full === null) return false;
43
- state = body.full;
72
+ state = terminalState(body.full);
44
73
  revision = Number.isFinite(incomingRevision) ? incomingRevision : 0;
45
74
  revisionAttachment = sourceAttachment;
46
75
  revisionSessionId = binding.sessionId;
@@ -49,7 +78,7 @@ export function createProjection({ binding, log }) {
49
78
  }
50
79
  if (Number.isFinite(incomingRevision) && incomingRevision < revision) return true;
51
80
  if (body.full !== undefined && body.full !== null) {
52
- state = body.full;
81
+ state = terminalState(body.full);
53
82
  revision = Number.isFinite(incomingRevision) ? incomingRevision : revision;
54
83
  emit();
55
84
  return true;
@@ -57,8 +86,16 @@ export function createProjection({ binding, log }) {
57
86
  if (body.patch) {
58
87
  if (Number(body.revision) === revision && Number(body.baseRevision) === revision - 1) return true;
59
88
  if (Number(body.baseRevision) !== revision) return false;
60
- state = applySessionStatePatch(state, body.patch);
89
+ const patch = terminalPatch(body.patch);
90
+ // Only a desktop-only field moved: the terminal view advances silently.
91
+ if (!patch) {
92
+ revision = Number(body.revision) || revision;
93
+ return true;
94
+ }
95
+ const patched = applySessionStatePatch(state, patch);
96
+ if (!patched) return false;
61
97
  revision = Number(body.revision) || revision;
98
+ state = patched;
62
99
  emit();
63
100
  return true;
64
101
  }
@@ -76,7 +113,7 @@ export function createProjection({ binding, log }) {
76
113
  return {
77
114
  // The create result seeds the projection for the current binding.
78
115
  seed(created) {
79
- state = created?.full ?? {};
116
+ state = terminalState(created?.full ?? {});
80
117
  revision = Number(created?.revision) || 0;
81
118
  revisionAttachment = binding.attachment;
82
119
  revisionSessionId = binding.sessionId;
@@ -50,6 +50,20 @@ export function createInlineBootLoaders({
50
50
  return localModulePromise;
51
51
  }
52
52
 
53
+ // Background warm-up of the session-local module graph. Independent of the
54
+ // keychain gate (the ESM cache makes the later localModule() import free);
55
+ // never throws — a failed warm-up leaves create on its lazy path.
56
+ async function warmLocalRuntime() {
57
+ if (isClosed()) return false;
58
+ try {
59
+ const module = await loadLocalModule();
60
+ if (isClosed()) return false;
61
+ return (await module.warmLocalSessionRuntime?.()) !== false;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
53
67
  function agentGraph() {
54
68
  agentGraphPromise ??= measured('agent-dispatch-graph-import', () => {
55
69
  assertOpen();
@@ -92,5 +106,5 @@ export function createInlineBootLoaders({
92
106
  await tracked;
93
107
  }
94
108
 
95
- return { prewarmKeychain, localModule, agentGraph, prepareAgentProviders };
109
+ return { prewarmKeychain, warmLocalRuntime, localModule, agentGraph, prepareAgentProviders };
96
110
  }
@@ -74,6 +74,7 @@ export function createInlineSessionRuntimeHost({
74
74
  return {
75
75
  create,
76
76
  prewarmKeychain: loaders.prewarmKeychain,
77
+ warmLocalRuntime: loaders.warmLocalRuntime,
77
78
  agentDispatch: dispatch.agentDispatch,
78
79
  async agentControl(args = {}, context = {}) {
79
80
  if (closed) throw new Error('session runtime host is closed');
@@ -2,10 +2,17 @@
2
2
  // The generic runtime-action lane (read / configure allowlists) and the
3
3
  // session catalog listing.
4
4
  import { SESSION_CONFIGURE_ACTION_SET, SESSION_READ_ACTION_SET } from '../../session-protocol.mjs';
5
+ import { annotateSpan, markPhase } from '../../../runtime/shared/slow-span.mjs';
5
6
  import { sanitizeForWire } from '../../session-wire-values.mjs';
6
7
 
7
8
  /** Reads a session without a live runtime answers from disk (readStoredReview). */
8
- const STORED_REVIEW_ACTIONS = new Set(['getTurnReviewDiff', 'getSessionReviewDiff', 'getTurnChanges']);
9
+ const STORED_REVIEW_ACTIONS = new Set([
10
+ 'getTurnReviewDiff',
11
+ 'getSessionReviewDiff',
12
+ 'getTurnChanges',
13
+ 'getSessionReviewFilePatch',
14
+ 'getTurnReviewFilePatch',
15
+ ]);
9
16
 
10
17
  /** One compact description of an action result for the daemon log, never the payload itself. */
11
18
  function resultSummary(value) {
@@ -44,7 +51,8 @@ export function createSessionActionCalls(ctx) {
44
51
  assertAvailable();
45
52
  if (value === null) throw new Error(`session ${id} is not available`);
46
53
  log(`session action ${name} session=${id} result=${resultSummary(value)} (stored)`);
47
- return {
54
+ markPhase('load');
55
+ const result = {
48
56
  value: sanitizeForWire(value) ?? null,
49
57
  sessionId: id,
50
58
  reservedOnly: false,
@@ -52,6 +60,8 @@ export function createSessionActionCalls(ctx) {
52
60
  unchanged: true,
53
61
  ...(Number.isInteger(baseRevision) ? { revision: baseRevision } : {}),
54
62
  };
63
+ markPhase('project');
64
+ return result;
55
65
  }
56
66
 
57
67
  async function runSessionAction(
@@ -62,6 +72,7 @@ export function createSessionActionCalls(ctx) {
62
72
  const id = String(sessionId || '');
63
73
  if (!id) throw new TypeError('sessionId is required');
64
74
  const name = requireSessionAction(action, allowedActions);
75
+ annotateSpan(name);
65
76
  // Opening an earlier session asks for its review right away; loading the
66
77
  // whole runtime for that held the review bar (and the pane's reveal) for
67
78
  // seconds.
@@ -72,7 +83,9 @@ export function createSessionActionCalls(ctx) {
72
83
  assertAvailable(entry);
73
84
  const target = entry.runtime[name];
74
85
  if (typeof target !== 'function') throw new TypeError(`session action ${name} is unavailable`);
86
+ markPhase('load');
75
87
  const value = await target.apply(entry.runtime, Array.isArray(args) ? args : []);
88
+ markPhase('action');
76
89
  if (name === 'setCwd' && value) {
77
90
  try {
78
91
  const projects = await loadProjectStore();
@@ -89,11 +102,13 @@ export function createSessionActionCalls(ctx) {
89
102
  // Still unwatched: keep it on the retention clock exactly like a runtime
90
103
  // released by its view, so an untouched load cannot leak past the idle window.
91
104
  retainUnwatched(entry);
92
- return {
105
+ const result = {
93
106
  value: sanitizeForWire(value) ?? null,
94
107
  sessionId: responseSessionId(entry, id),
95
108
  ...bodyForClient(step, Number.isInteger(baseRevision) ? baseRevision : null),
96
109
  };
110
+ markPhase('project');
111
+ return result;
97
112
  }
98
113
 
99
114
  async function listSessionCatalog(options = {}) {
@@ -1,3 +1,5 @@
1
+ import { shortId, slowSpans } from '../runtime/shared/slow-span.mjs';
2
+
1
3
  const sessionServiceRoutes = ({ desktop, project, session }) => ({
2
4
  'desktop.init': (params, ctx) => desktop.init(params, ctx),
3
5
  'desktop.invoke': (params, ctx) => desktop.invoke(params, ctx),
@@ -32,13 +34,16 @@ export function createSessionServiceApi({
32
34
  getTurnBusyCount,
33
35
  getStatus,
34
36
  getExternalClientCount,
37
+ spans = slowSpans,
35
38
  }) {
36
39
  const routes = sessionServiceRoutes({ desktop, project, session });
37
40
  return {
38
41
  async handleCall(name, args = {}, ctx = null) {
39
42
  const route = routes[String(name || '')];
40
43
  if (!route) throw new Error(`unknown session service call ${name}`);
41
- return route(args || {}, ctx);
44
+ const params = args || {};
45
+ const sid = shortId(params.sessionId);
46
+ return spans.trace(sid ? `${name}#${sid}` : String(name), () => route(params, ctx));
42
47
  },
43
48
  ...methods,
44
49
  get size() {
@@ -3,5 +3,6 @@ export function diffSessionState(
3
3
  next: unknown,
4
4
  options?: { prepend?: boolean }
5
5
  ): Record<string, unknown> | null;
6
- export function applySessionStatePatch(previous: unknown, patch: unknown): Record<string, unknown>;
6
+ export function streamingPartialSuffix(before: unknown, after: unknown): string | null;
7
+ export function applySessionStatePatch(previous: unknown, patch: unknown): Record<string, unknown> | null;
7
8
  export function transcriptItemsDigest(items: readonly unknown[]): string;
@@ -7,16 +7,51 @@
7
7
  // `transcriptPrepend`) carries rows revealed ABOVE the previous list, an
8
8
  // older-history page: the previous rows then start `values.length` rows in,
9
9
  // and `itemsAppend.from` indexes the PREVIOUS list.
10
+ //
11
+ // `partialAppend` carries growth of the desktop-only `streamingPartial` (the
12
+ // unfinished streamed line) as just its new text: re-sending the whole line on
13
+ // every tick made a long unfinished line cost O(length²) on the wire. A
14
+ // decoder that predates it keeps its last partial, which the desktop join
15
+ // then ignores or shows behind — never wrong text.
10
16
  import { createHash } from 'node:crypto';
11
17
 
18
+ /** The text `after` appends to `before` when both are the same unfinished
19
+ * line (same row, same offset, same row identity), else null: a retraction,
20
+ * another row or a new line travels as a full replacement. */
21
+ export function streamingPartialSuffix(before, after) {
22
+ if (!before || !after || typeof before !== 'object' || typeof after !== 'object') return null;
23
+ const { text: beforeText, ...beforeRest } = before;
24
+ const { text: afterText, ...afterRest } = after;
25
+ if (typeof beforeText !== 'string' || typeof afterText !== 'string') return null;
26
+ if (afterText.length <= beforeText.length || !afterText.startsWith(beforeText)) return null;
27
+ // id, from and the row identity: everything but the text stays put.
28
+ if (JSON.stringify(beforeRest) !== JSON.stringify(afterRest)) return null;
29
+ return afterText.slice(beforeText.length);
30
+ }
31
+
32
+ function appendStreamingPartial(state, suffix) {
33
+ const held = state.streamingPartial;
34
+ if (!held || typeof held !== 'object' || typeof held.text !== 'string' || typeof suffix !== 'string') return false;
35
+ state.streamingPartial = { ...held, text: held.text + suffix };
36
+ return true;
37
+ }
38
+
12
39
  export function diffSessionState(previous, next, { prepend = false } = {}) {
13
40
  if (!previous || !next || typeof previous !== 'object' || typeof next !== 'object') return null;
14
41
  const set = {};
15
42
  const remove = [];
16
43
  let itemsAppend = null;
17
44
  let itemsPrepend = null;
45
+ let partialAppend = null;
18
46
  for (const [key, value] of Object.entries(next)) {
19
47
  if (previous[key] === value) continue;
48
+ if (key === 'streamingPartial') {
49
+ const suffix = streamingPartialSuffix(previous[key], value);
50
+ if (suffix !== null) {
51
+ partialAppend = { text: suffix };
52
+ continue;
53
+ }
54
+ }
20
55
  if (key === 'items' && Array.isArray(value) && Array.isArray(previous.items)) {
21
56
  let head = 0;
22
57
  if (prepend && previous.items.length > 0 && value[0] !== previous.items[0]) {
@@ -36,13 +71,18 @@ export function diffSessionState(previous, next, { prepend = false } = {}) {
36
71
  for (const key of Object.keys(previous)) {
37
72
  if (!(key in next)) remove.push(key);
38
73
  }
39
- return itemsPrepend ? { set, remove, itemsAppend, itemsPrepend } : { set, remove, itemsAppend };
74
+ const patch = itemsPrepend ? { set, remove, itemsAppend, itemsPrepend } : { set, remove, itemsAppend };
75
+ if (partialAppend) patch.partialAppend = partialAppend;
76
+ return patch;
40
77
  }
41
78
 
79
+ /** The patched state, or null when a `partialAppend` has no held line to grow
80
+ * (a broken chain the caller answers with a full resync, never lost text). */
42
81
  export function applySessionStatePatch(previous, patch) {
43
82
  const base = previous && typeof previous === 'object' ? previous : {};
44
83
  if (!patch || typeof patch !== 'object') return base;
45
84
  const next = { ...base, ...(patch.set || {}) };
85
+ if (Object.hasOwn(patch, 'partialAppend') && !appendStreamingPartial(next, patch.partialAppend?.text)) return null;
46
86
  if (patch.itemsAppend || patch.itemsPrepend) {
47
87
  const items = Array.isArray(base.items) ? base.items : [];
48
88
  const head = Array.isArray(patch.itemsPrepend?.values) ? patch.itemsPrepend.values : [];
package/src/tui/App.jsx CHANGED
@@ -21,6 +21,7 @@ import { useApp, useStdin, useStdout } from 'ink';
21
21
  import { useSession } from './hooks/useSession.mjs';
22
22
  import { pickFolder } from '../standalone/folder-dialog.mjs';
23
23
  import { deriveSlashPalette } from './app/slash-palette.mjs';
24
+ import { createBtwController } from './app/btw-controller.mjs';
24
25
  import { skillMentionQuery, useSkillMentionSkills } from './app/skill-mention-palette.mjs';
25
26
  import { shouldSupersedePanelEpoch, supersedePanelEpoch } from './app/panel-epoch.mjs';
26
27
  import { createPanelSurface } from './app/panel-surface.mjs';
@@ -152,6 +153,24 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
152
153
  livePickerRef.current = picker;
153
154
  const [contextPanel, setContextPanel] = useState(null);
154
155
  const [usagePanel, setUsagePanel] = useState(null);
156
+ const [btwPanel, setBtwPanel] = useState(null);
157
+ const btwRef = useRef(null);
158
+ const sessionIdRef = useRef('');
159
+ sessionIdRef.current = state.sessionId || '';
160
+ if (!btwRef.current) {
161
+ btwRef.current = createBtwController({
162
+ getStore: () => store,
163
+ setPanel: setBtwPanel,
164
+ getSessionId: () => sessionIdRef.current,
165
+ });
166
+ }
167
+ const btw = btwRef.current;
168
+ // A side answer belongs to the conversation it was asked in: drop it on a
169
+ // session change (new/resume/clear/switch) and cancel it on unmount.
170
+ useEffect(() => {
171
+ btw.sync();
172
+ }, [btw, state.sessionId]);
173
+ useEffect(() => () => btw.dispose(), [btw]);
155
174
  // OWNING LAYER (app/panel-surface.mjs): the three sinks above are handed over
156
175
  // exactly once and never leave that module. Every panel factory below gets
157
176
  // `surface` instead, so painting or delegating without a claim that proves
@@ -310,6 +329,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
310
329
  setSettingsPrompt,
311
330
  setOnboardingActive,
312
331
  closeUsagePanel,
332
+ btw,
313
333
  oauthSubmitRef,
314
334
  clearModelCaches,
315
335
  onboardingRef,
@@ -518,6 +538,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
518
538
  usagePanel,
519
539
  closeUsagePanel,
520
540
  contextPanel,
541
+ btwPanel,
542
+ closeBtwPanel: btw.close,
521
543
  installPastedImages,
522
544
  clearPastedImagesSnapshot,
523
545
  registerPastedImage,
@@ -650,6 +672,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
650
672
  toolApproval: !!toolApproval,
651
673
  contextPanel: !!contextPanel,
652
674
  usagePanel: !!usagePanel,
675
+ btwPanel: !!btwPanel,
653
676
  providerPrompt: !!providerPrompt,
654
677
  settingsPrompt: !!settingsPrompt,
655
678
  };
@@ -711,6 +734,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
711
734
  picker,
712
735
  contextPanel,
713
736
  usagePanel,
737
+ btwPanel,
714
738
  slashPaletteOpen,
715
739
  tuiReady,
716
740
  state,
@@ -822,6 +846,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
822
846
  activeSlashQuery,
823
847
  activeTools,
824
848
  agentRevision,
849
+ btwPanel,
825
850
  cancelProviderPrompt,
826
851
  cancelSettingsPrompt,
827
852
  cancelSlashPalette,
@@ -10,9 +10,12 @@
10
10
  import { theme } from '../../theme.mjs';
11
11
  import { toolApprovalDescription } from '../app-format.mjs';
12
12
  import { Picker } from '../../components/Picker.jsx';
13
+ import { QuestionPanel } from '../../components/QuestionPanel.jsx';
14
+ import { questionsOf } from '../../components/question-panel-model.mjs';
13
15
  import { SlashCommandPalette } from '../../components/SlashCommandPalette.jsx';
14
16
  import { ContextPanel } from '../../components/ContextPanel.jsx';
15
17
  import { UsagePanel } from '../../components/UsagePanel.jsx';
18
+ import { BtwPanel } from '../../components/BtwPanel.jsx';
16
19
  import { TextEntryPanel } from '../../components/TextEntryPanel.jsx';
17
20
  import { textEntryClearsByEmpty } from '../text-entry-policy.mjs';
18
21
  import { withPickerEnterLatch } from '../picker-enter-latch.mjs';
@@ -24,7 +27,23 @@ const SKILL_PALETTE_TEXT = {
24
27
  help: '↑/↓ select · Enter insert · Esc cancel',
25
28
  };
26
29
 
27
- function renderToolApproval({ toolApproval, store, frameColumns, expandedOptionPanel }) {
30
+ function renderToolApproval({ toolApproval, store, frameColumns, expandedOptionPanel, floatingPanelRows }) {
31
+ if (toolApproval.kind === 'question') {
32
+ return (
33
+ <QuestionPanel
34
+ key={toolApproval.id}
35
+ questions={questionsOf(toolApproval)}
36
+ columns={frameColumns}
37
+ rows={floatingPanelRows}
38
+ onSubmit={(answers) =>
39
+ store.resolveToolApproval?.(toolApproval.id, { approved: true, reason: 'answered by user', answers })
40
+ }
41
+ onDismiss={() =>
42
+ store.resolveToolApproval?.(toolApproval.id, { approved: false, reason: 'dismissed by user' })
43
+ }
44
+ />
45
+ );
46
+ }
28
47
  const resolve = (approved) => {
29
48
  store.resolveToolApproval?.(toolApproval.id, {
30
49
  approved,
@@ -144,6 +163,7 @@ export function renderFloatingPanel(ctx) {
144
163
  slashPaletteOpen,
145
164
  toolApproval,
146
165
  usagePanel,
166
+ btwPanel,
147
167
  } = ctx;
148
168
  if (toolApproval) return renderToolApproval(ctx);
149
169
  if (picker) return renderPicker(ctx);
@@ -227,5 +247,10 @@ export function renderFloatingPanel(ctx) {
227
247
  />
228
248
  );
229
249
  }
250
+ if (btwPanel) {
251
+ return (
252
+ <BtwPanel panel={btwPanel} columns={frameColumns} panelRows={floatingPanelRows} themeEpoch={ctx.state?.themeEpoch || 0} />
253
+ );
254
+ }
230
255
  return null;
231
256
  }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * app/btw-controller.mjs — the transient `/btw` side-question panel.
3
+ *
4
+ * The answer lives only in this panel (never the transcript). `ask` replaces
5
+ * any existing panel, `close` (Esc) cancels a pending request, and a result
6
+ * that arrives after a replace/close is ignored via the request sequence.
7
+ */
8
+ import { measureMarkdownRenderedRows } from '../markdown/measure-rendered-rows.mjs';
9
+
10
+ export const BTW_USAGE_HINT = 'usage: /btw <question>';
11
+ // Border (2) + header (1) + footer (1).
12
+ export const BTW_CHROME_ROWS = 4;
13
+ const BTW_MAX_BODY_ROWS = 10;
14
+
15
+ /** Rendered row count of the answer body (the scrollable content height). */
16
+ export function btwContentRows(panel, columns) {
17
+ const body =
18
+ panel.status === 'done'
19
+ ? measureMarkdownRenderedRows(panel.text, Math.max(1, columns - 4))
20
+ : String(panel.text || '').split('\n').length;
21
+ return Math.max(1, body);
22
+ }
23
+
24
+ export function btwPanelRows(panel, columns) {
25
+ if (!panel) return 0;
26
+ return BTW_CHROME_ROWS + Math.min(BTW_MAX_BODY_ROWS, btwContentRows(panel, columns));
27
+ }
28
+
29
+ /** Largest scroll offset for `total` content rows shown through `visible` rows. */
30
+ export function btwScrollMax(total, visible) {
31
+ return Math.max(0, total - Math.max(1, visible));
32
+ }
33
+
34
+ /** Next scroll offset for a PageUp/PageDown key (other keys leave it unchanged). */
35
+ export function btwScrollOffset(key, offset, visible, total) {
36
+ const max = btwScrollMax(total, visible);
37
+ const page = Math.max(1, visible - 1);
38
+ if (key.pageUp) return Math.max(0, Math.min(max, offset) - page);
39
+ if (key.pageDown) return Math.min(max, offset + page);
40
+ return Math.min(max, offset);
41
+ }
42
+
43
+ /**
44
+ * Each request is bound to the session it was asked in. A settlement whose
45
+ * session no longer matches is ignored; `sync` (session change) and `dispose`
46
+ * (unmount) cancel a pending request for its originating session.
47
+ */
48
+ export function createBtwController({ getStore, setPanel, getSessionId = () => '' }) {
49
+ let seq = 0;
50
+ let pending = null; // { sessionId, dispatched } while a request is queued or in flight
51
+ let origin = null; // session the visible panel was asked in
52
+
53
+ const cancelPending = () => {
54
+ if (!pending) return;
55
+ const { sessionId, dispatched } = pending;
56
+ pending = null;
57
+ // Not dispatched yet: dropping `pending` stops the queued request, so
58
+ // there is nothing to cancel (a cancel sent first would not hit it).
59
+ if (!dispatched) return;
60
+ try {
61
+ const result = getStore().cancelSide?.(sessionId);
62
+ if (result?.catch) void result.catch(() => {});
63
+ } catch {
64
+ /* best-effort cancel */
65
+ }
66
+ };
67
+
68
+ const ask = (question) => {
69
+ cancelPending();
70
+ const id = ++seq;
71
+ const sessionId = getSessionId();
72
+ const request = { sessionId, dispatched: false };
73
+ pending = request;
74
+ origin = sessionId;
75
+ setPanel({ question, status: 'pending', text: '' });
76
+ const settle = (next) => {
77
+ if (id !== seq || getSessionId() !== sessionId) return;
78
+ pending = null;
79
+ setPanel({ question, ...next });
80
+ };
81
+ Promise.resolve()
82
+ .then(() => {
83
+ // askSide runs against the store's current session, so only dispatch
84
+ // while this ask is still live and still in its origin session.
85
+ if (pending !== request || id !== seq || getSessionId() !== sessionId) {
86
+ if (pending === request) pending = null;
87
+ return undefined; // dropped, never sent; `settle` ignores it (stale id/session)
88
+ }
89
+ request.dispatched = true;
90
+ return getStore().askSide(question);
91
+ })
92
+ .then(
93
+ (result) => settle({ status: 'done', text: String(result?.text ?? '') }),
94
+ (error) => settle({ status: 'error', text: String(error?.message || error || 'side question failed') })
95
+ );
96
+ };
97
+
98
+ const close = () => {
99
+ cancelPending();
100
+ seq += 1;
101
+ origin = null;
102
+ setPanel(null);
103
+ };
104
+
105
+ /** Session changed: a panel from another conversation is cancelled and cleared. */
106
+ const sync = () => {
107
+ if (origin !== null && getSessionId() !== origin) close();
108
+ };
109
+
110
+ /** Unmount: cancel without touching React state. */
111
+ const dispose = () => {
112
+ cancelPending();
113
+ seq += 1;
114
+ origin = null;
115
+ };
116
+
117
+ return { ask, close, sync, dispose };
118
+ }
@@ -37,6 +37,7 @@ export function createAppPickers({
37
37
  setSettingsPrompt,
38
38
  setOnboardingActive,
39
39
  closeUsagePanel,
40
+ btw,
40
41
  oauthSubmitRef,
41
42
  clearModelCaches,
42
43
  onboardingRef,
@@ -207,6 +208,7 @@ export function createAppPickers({
207
208
  normalizeSlashCommandName,
208
209
  surface,
209
210
  closeUsagePanel,
211
+ btw,
210
212
  openModelPicker: (...a) => openModelPicker(...a),
211
213
  modelSwitchNotice,
212
214
  openWebSearchPicker: (...a) => openWebSearchPicker(...a),
@@ -5,7 +5,9 @@
5
5
  */
6
6
  import { isLiveSpinnerMetaVisible } from '../live-spinner-visibility.mjs';
7
7
  import { promptContentRows, wrappedDetailRows, queuedBandRows } from '../text-layout.mjs';
8
+ import { questionPanelRows, questionsOf } from '../../components/question-panel-model.mjs';
8
9
  import { CORE_MULTILINE_TEXT_ENTRY_KINDS } from '../panel-signature.mjs';
10
+ import { btwPanelRows } from '../btw-controller.mjs';
9
11
 
10
12
  const SCROLL_HINT_ROWS = 0;
11
13
  const LIVE_STATUS_ROWS = 0;
@@ -114,20 +116,25 @@ function floatingPanelBudget({
114
116
  picker,
115
117
  contextPanel,
116
118
  usagePanel,
119
+ btwPanel,
117
120
  slashPaletteOpen,
118
121
  hasTextEntryPrompt,
119
122
  TEXT_ENTRY_ROWS,
120
123
  OPTION_PANEL_EXTRA_ROWS,
121
124
  maxFloatingPanelRows,
125
+ frameColumns,
122
126
  }) {
123
127
  let desiredFloatingPanelRows = 0;
124
- if (toolApproval) desiredFloatingPanelRows = PANEL_CHROME_ROWS + 2 + OPTION_PANEL_EXTRA_ROWS;
128
+ if (toolApproval?.kind === 'question' && questionsOf(toolApproval).length > 0) {
129
+ desiredFloatingPanelRows = questionPanelRows(questionsOf(toolApproval), frameColumns);
130
+ } else if (toolApproval) desiredFloatingPanelRows = PANEL_CHROME_ROWS + 2 + OPTION_PANEL_EXTRA_ROWS;
125
131
  else if (picker?.fillAvailable) desiredFloatingPanelRows = maxFloatingPanelRows;
126
132
  else if (picker) desiredFloatingPanelRows = PANEL_BASE_ROWS + OPTION_PANEL_EXTRA_ROWS;
127
133
  else if (contextPanel) desiredFloatingPanelRows = PANEL_BASE_ROWS + OPTION_PANEL_EXTRA_ROWS + 3;
128
134
  else if (usagePanel) desiredFloatingPanelRows = PANEL_BASE_ROWS + OPTION_PANEL_EXTRA_ROWS;
129
135
  else if (slashPaletteOpen) desiredFloatingPanelRows = SLASH_PALETTE_ROWS;
130
136
  else if (hasTextEntryPrompt) desiredFloatingPanelRows = TEXT_ENTRY_ROWS;
137
+ else if (btwPanel) desiredFloatingPanelRows = btwPanelRows(btwPanel, frameColumns);
131
138
  const floatingPanelRows = desiredFloatingPanelRows > 0 ? Math.min(desiredFloatingPanelRows, maxFloatingPanelRows) : 0;
132
139
  // Give the list every content row the panel exposes. The panel already grew
133
140
  // by OPTION_PANEL_EXTRA_ROWS; previously that growth was subtracted back out
@@ -174,6 +181,7 @@ export function computeRowBudgets({
174
181
  picker,
175
182
  contextPanel,
176
183
  usagePanel,
184
+ btwPanel,
177
185
  slashPaletteOpen,
178
186
  }) {
179
187
  const { textEntryPrompt, hasTextEntryPrompt, hasFloatingPanel, expandedOptionPanel, inputBoxHidden } = surfaces;
@@ -211,11 +219,13 @@ export function computeRowBudgets({
211
219
  picker,
212
220
  contextPanel,
213
221
  usagePanel,
222
+ btwPanel,
214
223
  slashPaletteOpen,
215
224
  hasTextEntryPrompt,
216
225
  TEXT_ENTRY_ROWS: textEntry.TEXT_ENTRY_ROWS,
217
226
  OPTION_PANEL_EXTRA_ROWS,
218
227
  maxFloatingPanelRows,
228
+ frameColumns,
219
229
  });
220
230
  const rows = {
221
231
  floatingPanelRows: panel.floatingPanelRows,
@@ -10,6 +10,7 @@ export function deriveSurfaces({
10
10
  picker,
11
11
  contextPanel,
12
12
  usagePanel,
13
+ btwPanel,
13
14
  slashPaletteOpen,
14
15
  tuiReady,
15
16
  state,
@@ -24,7 +25,8 @@ export function deriveSurfaces({
24
25
  contextPanel ||
25
26
  usagePanel ||
26
27
  slashPaletteOpen ||
27
- hasTextEntryPrompt
28
+ hasTextEntryPrompt ||
29
+ btwPanel
28
30
  );
29
31
  const expandedOptionPanel = !!(toolApproval || picker || contextPanel || usagePanel || hasTextEntryPrompt);
30
32
  if (panelTransitionRef.current.signature.includes('picker:project') && !picker) {
@@ -15,6 +15,12 @@ export const SLASH_COMMANDS = [
15
15
  description: 'Switch working directory (project)',
16
16
  },
17
17
  { name: 'compact', usage: '/compact', description: 'Compact older conversation context' },
18
+ {
19
+ name: 'btw',
20
+ usage: '/btw',
21
+ params: '<question>',
22
+ description: 'Ask a quick side question without interrupting the current work',
23
+ },
18
24
  {
19
25
  name: 'goal',
20
26
  usage: '/goal',
@@ -30,9 +36,12 @@ export const SLASH_COMMANDS = [
30
36
  { name: 'resume', usage: '/resume', params: '[id]', description: 'Resume a saved chat' },
31
37
  { name: 'context', usage: '/context', description: 'Show current context surface' },
32
38
  {
33
- name: 'inherit',
34
- usage: '/inherit',
35
- description: 'Carry this conversation into a new session on the current model',
39
+ name: 'fork',
40
+ usage: '/fork',
41
+ aliases: ['inherit'],
42
+ aliasUsage: ['inherit'],
43
+ showAliasUsage: false,
44
+ description: 'Fork this conversation into a new session on the current model',
36
45
  },
37
46
  { name: 'usage', usage: '/usage', params: '[refresh]', description: 'Show total provider quota / balance' },
38
47
  { name: 'model', usage: '/model', params: '[name|refresh]', description: 'Switch model for subsequent turns' },
@@ -109,6 +118,7 @@ export function overlayBlocksGlobalTranscriptScroll(owner = {}) {
109
118
  owner.toolApproval ||
110
119
  owner.contextPanel ||
111
120
  owner.usagePanel ||
121
+ owner.btwPanel ||
112
122
  owner.providerPrompt ||
113
123
  owner.settingsPrompt
114
124
  );