mixdog 1.2.4 → 1.2.6

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 (292) hide show
  1. package/package.json +1 -1
  2. package/scripts/lib/gate-scheduler.mjs +2 -2
  3. package/scripts/lib/run-node-tests.mjs +5 -14
  4. package/scripts/mobile-release.mjs +5 -4
  5. package/scripts/provider-pingpong-live.mjs +9 -9
  6. package/src/agents/advisor/AGENT.md +4 -4
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +2 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +11 -0
  11. package/src/defaults/skills/browser-use/SKILL.md +2 -1
  12. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  13. package/src/defaults/skills/visualize/SKILL.md +3 -2
  14. package/src/lib/rules-builder.cjs +1 -1
  15. package/src/output-styles/common.md +22 -5
  16. package/src/output-styles/detailed.md +7 -5
  17. package/src/output-styles/minimal.md +7 -3
  18. package/src/output-styles/simple.md +2 -3
  19. package/src/rules/agent/AGENT.md +3 -1
  20. package/src/rules/lead/LEAD.md +10 -7
  21. package/src/rules/shared/10-tool-workflow.md +2 -1
  22. package/src/rules/shared/60-verification.md +4 -5
  23. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  24. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  25. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
  26. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  27. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
  28. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  29. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  30. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
  32. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  38. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
  39. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  40. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  42. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  43. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  44. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  45. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  46. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  51. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  57. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  60. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  64. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  66. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  67. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  68. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  69. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  70. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  71. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
  72. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
  73. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  74. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  75. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  76. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  77. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  79. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  80. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
  81. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
  82. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
  83. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  84. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  85. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  86. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  87. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  88. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  89. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  90. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  91. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  92. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  93. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  94. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  95. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  96. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  97. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  98. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +255 -32
  99. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
  100. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
  101. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  102. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  103. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  104. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  105. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  106. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  107. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  108. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  109. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  110. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  112. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  113. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  114. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  116. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  118. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  119. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  122. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  123. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  124. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  125. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  126. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  127. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  129. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  130. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  131. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  133. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  134. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  135. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  136. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  137. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  138. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
  139. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  140. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  141. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  142. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  144. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  145. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  146. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  147. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  148. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  152. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
  153. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
  154. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  155. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
  156. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  157. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  158. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  159. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  160. package/src/runtime/attachments/office-extract.mjs +11 -9
  161. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  162. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  163. package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
  164. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  165. package/src/runtime/office/core/tabular.mjs +1 -1
  166. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  167. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  168. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  169. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  170. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  171. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  172. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  173. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  174. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  175. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  176. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  177. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  178. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  179. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  180. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  181. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  182. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  183. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  184. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  185. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  186. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  187. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  188. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  189. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  190. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  191. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  192. package/src/runtime/shared/atomic-file.mjs +22 -13
  193. package/src/runtime/shared/background-tasks.mjs +60 -20
  194. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  195. package/src/runtime/shared/config.mjs +0 -10
  196. package/src/runtime/shared/file-lock.mjs +7 -1
  197. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  198. package/src/runtime/shared/file-permissions.d.mts +19 -4
  199. package/src/runtime/shared/file-permissions.mjs +310 -4
  200. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  201. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  202. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  203. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  204. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  205. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  206. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  207. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  208. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  209. package/src/runtime/shared/notify-trace.mjs +8 -1
  210. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  211. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  212. package/src/runtime/shared/process-runner.mjs +38 -0
  213. package/src/runtime/shared/provider-accounts.mjs +2 -1
  214. package/src/runtime/shared/provider-api-key.mjs +0 -1
  215. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  216. package/src/runtime/shared/resource-admission.mjs +60 -18
  217. package/src/runtime/shared/run-child.mjs +84 -0
  218. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  219. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  220. package/src/runtime/shared/singleton-owner.mjs +126 -42
  221. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  222. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  223. package/src/runtime/shared/user-data-guard.mjs +28 -9
  224. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  225. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  226. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  227. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  228. package/src/runtime/tidy/runners/biome.mjs +3 -1
  229. package/src/runtime/tidy/shadow.mjs +2 -1
  230. package/src/runtime/web-search/index.mjs +5 -7
  231. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  232. package/src/runtime/web-search/tool-defs.mjs +6 -1
  233. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  234. package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
  235. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  236. package/src/session-runtime/lifecycle/shared.mjs +19 -2
  237. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  238. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  239. package/src/session-runtime/lifecycle-api.mjs +3 -1
  240. package/src/session-runtime/provider-auth/login.mjs +10 -6
  241. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  242. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  243. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  244. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  245. package/src/session-runtime/provider-models.mjs +76 -10
  246. package/src/session-runtime/provider-readiness.mjs +4 -0
  247. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  248. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  249. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  250. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  251. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  252. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  253. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  254. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  255. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  256. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  257. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  258. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  259. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  260. package/src/session-runtime/services/agent-tool.mjs +20 -0
  261. package/src/session-runtime/services/provider-admin.mjs +92 -20
  262. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  263. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  264. package/src/session-runtime/turn/session-ops.mjs +2 -2
  265. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  266. package/src/session-runtime/usage-stats-api.mjs +6 -1
  267. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  268. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  269. package/src/standalone/daemon-owner.mjs +117 -25
  270. package/src/standalone/daemon.mjs +43 -13
  271. package/src/standalone/desktop-service-registry.mjs +32 -5
  272. package/src/standalone/session-call-cache.mjs +6 -0
  273. package/src/standalone/session-protocol.mjs +1 -1
  274. package/src/standalone/session-service/retention/busy.mjs +7 -2
  275. package/src/standalone/session-transport/routes.mjs +7 -6
  276. package/src/tui/app/model-options.mjs +39 -2
  277. package/src/tui/app/model-picker.mjs +16 -2
  278. package/src/tui/app/provider-setup-picker.mjs +1 -2
  279. package/src/tui/dist/index.mjs +436 -123
  280. package/src/tui/session/notice-text.mjs +0 -1
  281. package/src/tui/session/session-api/intake/submission.mjs +7 -13
  282. package/src/tui/session/session-api/integrations.mjs +1 -5
  283. package/src/tui/session/session-flow/drain.mjs +8 -0
  284. package/src/tui/session/session-flow/steering.mjs +46 -0
  285. package/src/tui/session/session-flow.mjs +2 -0
  286. package/src/tui/session/turn-stream.mjs +33 -0
  287. package/src/tui/session/turn.mjs +30 -3
  288. package/src/ui/statusline.mjs +1 -6
  289. package/src/ui/streaming-markdown-heal.mjs +94 -25
  290. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  291. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  292. package/src/workflows/default/WORKFLOW.md +9 -13
@@ -35,7 +35,6 @@ const FAILED_NOTICE_ACTIONS = new Map([
35
35
  ['oauth login', 'start OAuth login'],
36
36
  ['output style switch', 'switch output style'],
37
37
  ['openai usage auth save', 'save OpenAI usage auth'],
38
- ['opencode go usage auth save', 'save OpenCode Go usage auth'],
39
38
  ['plugin add', 'add plugin'],
40
39
  ['plugin mcp enable', 'enable plugin MCP'],
41
40
  ['plugin uninstall', 'uninstall plugin'],
@@ -4,7 +4,7 @@
4
4
  import { promptDisplayText } from '../../queue-helpers.mjs';
5
5
 
6
6
  export function createSubmissionIntake(bag) {
7
- const { runtime, nextId, flags, getState, enqueue, autoClearBeforeSubmit } = bag;
7
+ const { nextId, flags, getState, enqueue, autoClearBeforeSubmit } = bag;
8
8
 
9
9
  const submission = (text, options = {}) => {
10
10
  const displayText = promptDisplayText(text, options);
@@ -25,19 +25,13 @@ export function createSubmissionIntake(bag) {
25
25
  };
26
26
 
27
27
  const enqueueSubmission = (intake) => {
28
+ // The queue itself wakes a passive `task wait` or shell hold for typed
29
+ // input (not for slash commands, which run after the turn); see enqueue.
28
30
  const accepted = enqueue(intake.text, intake.queueOptions);
29
- // User input wakes a passive `task wait` without cancelling either the
30
- // turn or the background task. The returned running snapshot creates the
31
- // normal post-tool boundary, where this queued prompt is injected.
32
- if (accepted !== false) {
33
- if ((intake.queueOptions.mode || 'prompt') === 'prompt') {
34
- bag.cancelQuotaResumeOnUserInput?.();
35
- bag.cancelQueuedGoalContinuations?.();
36
- bag.archiveCompletedGoalOnUserInput?.();
37
- }
38
- try {
39
- runtime.interruptTaskWait?.('user-message');
40
- } catch {}
31
+ if (accepted !== false && (intake.queueOptions.mode || 'prompt') === 'prompt') {
32
+ bag.cancelQuotaResumeOnUserInput?.();
33
+ bag.cancelQueuedGoalContinuations?.();
34
+ bag.archiveCompletedGoalOnUserInput?.();
41
35
  }
42
36
  return accepted;
43
37
  };
@@ -139,6 +139,7 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
139
139
  (result) => `custom provider saved: ${result.id}`
140
140
  ),
141
141
  updateCustomProviderModel: (id, modelId, patch) => runtime.updateCustomProviderModel(id, modelId, patch),
142
+ setModelPreferences: (provider, changes) => runtime.setModelPreferences(provider, changes),
142
143
  removeCustomProvider: async (id) => {
143
144
  const result = await runtime.removeCustomProvider(id);
144
145
  refreshRouteStats();
@@ -148,11 +149,6 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
148
149
  testCustomProvider: (input) => runtime.testCustomProvider(input),
149
150
  checkCustomProviderKeyEnv: (name) => runtime.checkCustomProviderKeyEnv(name),
150
151
  discoverCustomProviderModels: (input) => runtime.discoverCustomProviderModels(input),
151
- saveOpenCodeGoUsageAuth: async (opts) => {
152
- await runtime.saveOpenCodeGoUsageAuth(opts);
153
- pushNotice('OpenCode Go usage auth saved', 'info');
154
- return true;
155
- },
156
152
  saveOpenAIUsageSessionKey: async (secret) => {
157
153
  await runtime.saveOpenAIUsageSessionKey(secret);
158
154
  pushNotice('OpenAI usage auth saved', 'info');
@@ -332,6 +332,14 @@ export function createDrainLoop(
332
332
  }
333
333
  submissions.remember(submissionId);
334
334
  pending.push(entry);
335
+ // Typed input or a deliverable notification ends a passive task wait or
336
+ // shell hold so the next boundary hands it to the model now.
337
+ const wake = steering.taskWaitWakeForEntry(entry);
338
+ if (wake) {
339
+ try {
340
+ bag.runtime?.interruptTaskWait?.(wake, wake === 'notification' ? entry.execution?.id || null : null);
341
+ } catch {}
342
+ }
335
343
  const needsDurableSteering =
336
344
  (getState().busy || getState().commandBusy || flags.autoClearRunning || flags.quotaResumePending) &&
337
345
  steering.shouldMirrorSteeringEntry(entry);
@@ -131,6 +131,47 @@ export function createSteeringOps(bag, { queue, submissions }) {
131
131
  );
132
132
  }
133
133
 
134
+ // Queued input the user typed (editable prompts), not runtime notices.
135
+ function hasPendingUserInput() {
136
+ if (!admissionOpen()) return false;
137
+ return pending.some((entry) => isQueuedEntryEditable(entry) && !isSlashQueuedEntry(entry));
138
+ }
139
+
140
+ // A notification the next boundary will deliver: a task notification (one
141
+ // the agent already received through task wait/read is not deliverable) or
142
+ // the Goal closeout. `later` entries (scheduled and channel injects) wait
143
+ // for the turn to end, so they are not deliverable mid-turn.
144
+ function isDeliverableNotification(entry) {
145
+ const mode = entry?.mode;
146
+ return (
147
+ admissionOpen() &&
148
+ (mode === 'task-notification' || mode === 'goal-closeout') &&
149
+ queuePriorityValue(entry.priority) <= queuePriorityValue('next') &&
150
+ canDeliverSteeringEntry(entry) &&
151
+ steeringMessageFromEntry(entry) !== null
152
+ );
153
+ }
154
+
155
+ function hasPendingNotification() {
156
+ return pending.some(isDeliverableNotification);
157
+ }
158
+
159
+ // What one queued entry should wake a passive wait or shell hold for: typed
160
+ // input, a deliverable notification, or nothing (slash commands and
161
+ // informational meta prompts run or ride along without ending a wait).
162
+ function taskWaitWakeForEntry(entry) {
163
+ if (!admissionOpen()) return false;
164
+ if (isQueuedEntryEditable(entry) && !isSlashQueuedEntry(entry)) return 'user-message';
165
+ return isDeliverableNotification(entry) ? 'notification' : false;
166
+ }
167
+
168
+ // What a passive wait or shell hold should wake for right now: user input
169
+ // first, else a deliverable notification.
170
+ function pendingTaskWaitWake() {
171
+ if (hasPendingUserInput()) return 'user-message';
172
+ return hasPendingNotification() ? 'notification' : false;
173
+ }
174
+
134
175
  // `later` notifications (scheduled tasks) are skipped unless the runtime
135
176
  // explicitly asks for a later flush.
136
177
  function drainPendingSteering(_sessionIdOrOptions = null, maybeOptions = null) {
@@ -219,6 +260,11 @@ export function createSteeringOps(bag, { queue, submissions }) {
219
260
  commitSteeringQueueEntries,
220
261
  settleSteeredSubmissions,
221
262
  hasPendingSteering,
263
+ hasPendingUserInput,
264
+ isDeliverableNotification,
265
+ hasPendingNotification,
266
+ taskWaitWakeForEntry,
267
+ pendingTaskWaitWake,
222
268
  drainPendingSteering,
223
269
  restoreLeadSteeringFromDisk,
224
270
  };
@@ -39,6 +39,8 @@ export function createSessionFlow(bag) {
39
39
  enqueue: loop.enqueue,
40
40
  settleClosedQueue: loop.settleClosedQueue,
41
41
  hasPendingSteering: steering.hasPendingSteering,
42
+ hasPendingUserInput: steering.hasPendingUserInput,
43
+ pendingTaskWaitWake: steering.pendingTaskWaitWake,
42
44
  drainPendingSteering: steering.drainPendingSteering,
43
45
  restoreLeadSteeringFromDisk: steering.restoreLeadSteeringFromDisk,
44
46
  autoClearBeforeSubmit: autoClear.autoClearBeforeSubmit,
@@ -33,6 +33,12 @@ export function createTurnStream(deps) {
33
33
  // segment out of the provider's final content individually: the combined
34
34
  // prefix no longer matches when the provider omits an earlier segment.
35
35
  committedSegments: [],
36
+ // The open segment was settled when the provider streamed a tool call
37
+ // (turn.mjs); cleared when that response's tool calls open.
38
+ settledBeforeToolCall: false,
39
+ // The phase the provider named for the message this segment streams
40
+ // (`commentary` / `final_answer`), or null; the desktop row carries it.
41
+ phase: null,
36
42
  thinkingText: '',
37
43
  thinkingStartedAt: 0,
38
44
  thinkingSegmentStartedAt: 0,
@@ -113,6 +119,7 @@ function ensureAssistant(stream, initialText = '') {
113
119
  text: String(initialText || ''),
114
120
  at,
115
121
  messageId,
122
+ ...phaseField(stream),
116
123
  ...stream.routeMeta,
117
124
  });
118
125
  }
@@ -149,11 +156,17 @@ function partialNow(stream) {
149
156
  streaming: true,
150
157
  at: identity.at,
151
158
  messageId: identity.messageId,
159
+ ...phaseField(stream),
152
160
  ...stream.routeMeta,
153
161
  },
154
162
  };
155
163
  }
156
164
 
165
+ // The row's `phase` field; absent when the provider named none.
166
+ function phaseField(stream) {
167
+ return stream.phase ? { phase: stream.phase } : {};
168
+ }
169
+
157
170
  function samePartial(left, right) {
158
171
  if (left === right) return true;
159
172
  if (!left || !right) return false;
@@ -208,6 +221,7 @@ function resetLineScan(stream) {
208
221
  function closeAssistantSegment(stream) {
209
222
  stream.currentAssistantId = null;
210
223
  stream.currentAssistantText = '';
224
+ stream.phase = null;
211
225
  stream.claimPersistedAssistant = false;
212
226
  stream.assistantIdentity = null;
213
227
  resetLineScan(stream);
@@ -296,6 +310,7 @@ function flushTextBatch(stream) {
296
310
  text: visibleText,
297
311
  streaming: true,
298
312
  at: current?.at || Date.now(),
313
+ phase: stream.phase || undefined,
299
314
  ...stream.routeMeta,
300
315
  };
301
316
  }
@@ -449,6 +464,24 @@ export function appendAssistantText(stream, full, { committed = false } = {}) {
449
464
  flushStreamBatch(stream);
450
465
  }
451
466
 
467
+ // The provider named the phase of the message it is streaming. A message of
468
+ // another phase than the open segment's text is its own row: that segment
469
+ // settles first. A segment whose phase was not known yet (the provider named
470
+ // it only on the finished message) takes it.
471
+ export function noteAssistantPhase(stream, phase) {
472
+ if (phase !== 'commentary' && phase !== 'final_answer') return;
473
+ if (stream.phase === phase) return;
474
+ if (stream.phase && stream.currentAssistantText.trim()) {
475
+ flushStreamBatch(stream);
476
+ commitAssistantSegment(stream);
477
+ }
478
+ stream.phase = phase;
479
+ const tail = stream.getState().streamingTail;
480
+ if (stream.currentAssistantId != null && tail?.id === stream.currentAssistantId) {
481
+ stream.updateStreamingTail(tail.id, { phase });
482
+ }
483
+ }
484
+
452
485
  // Reasoning is not a commit boundary: a turn interrupted while it is still
453
486
  // thinking produced no model-visible message, so Esc must hand the prompt
454
487
  // back to the input box. Text, tool calls and turn end still commit it.
@@ -34,6 +34,7 @@ import {
34
34
  appendTextDelta,
35
35
  appendAssistantText,
36
36
  appendReasoningDelta,
37
+ noteAssistantPhase,
37
38
  resetStreamedText,
38
39
  applyStageChange,
39
40
  settleFinalText,
@@ -241,9 +242,21 @@ function injectSteerMessage({ stream, cards, pushUserOrSyntheticItem }, text, st
241
242
  pushUserOrSyntheticItem(value, steeringIds[0], 'injected', steeringItemExtras(steeringMeta));
242
243
  }
243
244
 
245
+ function settleSegmentBeforeToolCall(ctx) {
246
+ const { stream } = ctx;
247
+ if (!stream.currentAssistantText.trim()) return;
248
+ ctx.markPromptCommitted();
249
+ flushStreamBatch(stream);
250
+ commitAssistantSegment(stream, { sealToolBlock: true });
251
+ // The response's own content (onAssistantText, before its tool calls open)
252
+ // is this streamed text again: it must not reopen as a second row.
253
+ stream.settledBeforeToolCall = true;
254
+ }
255
+
244
256
  async function openToolCalls(ctx, calls) {
245
257
  const { stream, cards, runtime } = ctx;
246
258
  ctx.markPromptCommitted();
259
+ stream.settledBeforeToolCall = false;
247
260
  // Flush buffered mid-turn assistant text before the tool card appears;
248
261
  // otherwise, with neither a thinking panel nor a spinner active, the
249
262
  // message above the tool card vanished.
@@ -372,9 +385,14 @@ function turnHandlers(ctx) {
372
385
  };
373
386
  }
374
387
 
375
- function toolHandlers(ctx) {
388
+ export function toolHandlers(ctx) {
376
389
  const { isCurrentTurn, cards, getState, set, options } = ctx;
377
390
  return {
391
+ // The provider streamed a tool call: the text before it is complete (a
392
+ // preamble), so it settles now instead of after the whole response.
393
+ onAssistantToolCallObserved: () => {
394
+ if (isCurrentTurn()) settleSegmentBeforeToolCall(ctx);
395
+ },
378
396
  onToolCall: async (_iter, calls) => {
379
397
  if (isCurrentTurn()) await openToolCalls(ctx, calls);
380
398
  },
@@ -431,13 +449,22 @@ export function streamHandlers(ctx) {
431
449
  // The streaming path owns this segment when it already produced text.
432
450
  // Do not check turn-global assistantText: earlier closed preambles stay
433
451
  // there across tool calls.
434
- if (stream.currentAssistantText.trim()) return;
452
+ if (stream.currentAssistantText.trim() || stream.settledBeforeToolCall) return;
435
453
  ctx.markPromptCommitted();
436
454
  appendAssistantText(stream, full, { committed: detail?.committed === true });
437
455
  },
438
456
  onReasoningDelta: (chunk) => {
439
457
  if (isCurrentTurn()) appendReasoningDelta(stream, String(chunk ?? ''));
440
458
  },
459
+ // A provider output item started. A tool call ends the text before it
460
+ // (a preamble settles at the call's start, not after its arguments or
461
+ // the whole response); a message naming its phase marks its row as
462
+ // commentary or final answer.
463
+ onStreamItemStart: (detail) => {
464
+ if (!isCurrentTurn()) return;
465
+ if (detail?.kind === 'tool') settleSegmentBeforeToolCall(ctx);
466
+ else if (detail?.kind === 'text') noteAssistantPhase(stream, detail.phase);
467
+ },
441
468
  };
442
469
  }
443
470
 
@@ -693,7 +720,7 @@ export function createRunTurn(bag) {
693
720
  markPromptCommitted: () => commitPromptRestore(flags, turn),
694
721
  };
695
722
  const stopTaskWaitSteeringCheck = registerTaskWaitSteeringCheck(
696
- (sessionId) => isCurrentTurn() && runtime.id === sessionId && bag.hasPendingSteering()
723
+ (sessionId) => isCurrentTurn() && runtime.id === sessionId && bag.pendingTaskWaitWake()
697
724
  );
698
725
  try {
699
726
  const { result, session } = await runtime.ask(userText, {
@@ -10,7 +10,6 @@
10
10
  * CLI route passed here.
11
11
  */
12
12
  import { readCachedOAuthUsageSnapshot } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
13
- import { readCachedOpenCodeGoUsageSnapshot } from '../runtime/agent/orchestrator/providers/opencode-go-usage.mjs';
14
13
  import { buildGatewayLimits } from '../runtime/agent/orchestrator/providers/statusline-route-meta.mjs';
15
14
  import { formatGatewayLimitSegments, loadGatewayStatus } from '../vendor/statusline/bin/statusline-route.mjs';
16
15
  import { createSessionStats } from './session-stats.mjs';
@@ -614,14 +613,11 @@ function refreshFallbackQuotaStatus(cacheKey, normalizedProvider, model) {
614
613
  // the route reports nothing worth a segment.
615
614
  function fallbackQuotaValue(routeInfo, normalizedProvider) {
616
615
  let usageSnapshot = null;
617
- if (normalizedProvider === 'opencode-go') {
618
- usageSnapshot = readCachedOpenCodeGoUsageSnapshot();
619
- } else if (normalizedProvider.includes('oauth')) {
616
+ if (normalizedProvider.includes('oauth')) {
620
617
  try {
621
618
  usageSnapshot = readCachedOAuthUsageSnapshot(routeInfo, { allowStale: true });
622
619
  } catch {}
623
620
  }
624
- if (normalizedProvider === 'opencode-go' && !usageSnapshot) return null;
625
621
  // Boot guard: do not render previous-launch usage before the current
626
622
  // runtime has captured at least one snapshot. Once captured in this
627
623
  // process, keep it visible while idle even if refreshes are delayed.
@@ -647,7 +643,6 @@ function fallbackQuotaValue(routeInfo, normalizedProvider) {
647
643
 
648
644
  function providerKindForQuota(provider) {
649
645
  const p = String(provider || '').toLowerCase();
650
- if (p === 'opencode-go') return 'quota-api';
651
646
  if (p.includes('oauth')) return 'oauth';
652
647
  if (p === 'mixdog-local') return 'local';
653
648
  return 'api';
@@ -8,19 +8,47 @@ const HEALABLE_MARKDOWN_SYNTAX = /[`*_~[]/;
8
8
  function openFence(text) {
9
9
  let marker = '';
10
10
  let length = 0;
11
+ let start = 0;
12
+ let lineStart = 0;
11
13
  for (const rawLine of String(text ?? '').split('\n')) {
12
14
  const fence = FENCE_LINE.exec(rawLine.replace(/\r$/, ''));
13
- if (!fence) continue;
14
- const marks = fence[1];
15
- if (!marker) {
16
- marker = marks[0];
17
- length = marks.length;
18
- } else if (marks[0] === marker && marks.length >= length && !fence[2].trim()) {
19
- marker = '';
20
- length = 0;
15
+ if (fence) {
16
+ const marks = fence[1];
17
+ if (!marker) {
18
+ marker = marks[0];
19
+ length = marks.length;
20
+ start = lineStart;
21
+ } else if (marks[0] === marker && marks.length >= length && !fence[2].trim()) {
22
+ marker = '';
23
+ length = 0;
24
+ }
21
25
  }
26
+ lineStart += rawLine.length + 1;
22
27
  }
23
- return marker ? { marker, length } : null;
28
+ return marker ? { marker, length, start } : null;
29
+ }
30
+
31
+ // The opening fence line arrives a token at a time: ``` → ```py → ```python.
32
+ // Rendered at once, the card opened unlabelled and then relabelled (and
33
+ // re-highlighted) per token. The card appears with its first code character:
34
+ // an opening line alone would paint an empty box before any code.
35
+ function trimPartialOpeningFence(text, fence) {
36
+ const lineEnd = text.indexOf('\n', fence.start);
37
+ return lineEnd >= 0 && /\S/.test(text.slice(lineEnd + 1)) ? text : text.slice(0, fence.start);
38
+ }
39
+
40
+ // A line holding only a block marker so far has no content to show, and the
41
+ // marker alone parses as something else: `-` or `=` under a paragraph is a
42
+ // setext underline (the paragraph flashed into a heading until the item text
43
+ // arrived), `2` is a lazy paragraph continuation until its `.` arrives, `#`
44
+ // grows into `##`, and a lone backtick run is literal text until it becomes a
45
+ // fence. Hold the line back until its first content character.
46
+ const PENDING_BLOCK_MARKER_LINE = /(?:^|\n)[ \t]*(?:[-+*=_#>~`]+|\d{1,9}[.)]?)[ \t]*$/;
47
+
48
+ function trimPendingBlockMarker(text) {
49
+ const pending = PENDING_BLOCK_MARKER_LINE.exec(text);
50
+ if (!pending) return text;
51
+ return text.slice(0, pending.index + (pending[0].startsWith('\n') ? 1 : 0));
24
52
  }
25
53
 
26
54
  // A closing fence arrives a token at a time. Until its run is long enough to
@@ -57,7 +85,9 @@ function closeInlineCode(text, isPendingLocalPath) {
57
85
  } else if (run === open) open = 0;
58
86
  index = end;
59
87
  }
60
- if (open && isPendingLocalPath?.(text.slice(openIndex + open))) {
88
+ // An opener with nothing after it yet closes into an empty span, which
89
+ // parses as a literal backtick run that then vanishes into code.
90
+ if (open && (!text.slice(openIndex + open) || isPendingLocalPath?.(text.slice(openIndex + open)))) {
61
91
  return text.slice(0, openIndex);
62
92
  }
63
93
  return open > 0 ? `${text}${'`'.repeat(open)}` : text;
@@ -93,6 +123,7 @@ function scanEmphasisRuns(masked) {
93
123
  let end = index + 1;
94
124
  while (masked[end] === marker) end += 1;
95
125
  const length = end - index;
126
+ const at = index;
96
127
  const before = index > 0 ? masked[index - 1] : '';
97
128
  const after = end < masked.length ? masked[end] : '';
98
129
  const beforeSpace = isMarkdownSpace(before);
@@ -102,16 +133,26 @@ function scanEmphasisRuns(masked) {
102
133
  const left = !afterSpace && (!afterPunctuation || beforeSpace || beforePunctuation);
103
134
  const right = !beforeSpace && (!beforePunctuation || afterSpace || afterPunctuation);
104
135
  if (marker === '~') {
105
- if (length >= 2) runs.push({ marker, length: 2, canOpen: left, canClose: right });
136
+ // A single tilde is inert, but at the end it may be half of `~~`.
137
+ runs.push({
138
+ marker,
139
+ length: Math.min(length, 2),
140
+ canOpen: left && length >= 2,
141
+ canClose: right && length >= 2,
142
+ at,
143
+ end,
144
+ });
106
145
  } else if (marker === '_') {
107
146
  runs.push({
108
147
  marker,
109
148
  length,
110
149
  canOpen: left && (!right || beforePunctuation),
111
150
  canClose: right && (!left || afterPunctuation),
151
+ at,
152
+ end,
112
153
  });
113
154
  } else {
114
- runs.push({ marker, length, canOpen: left, canClose: right });
155
+ runs.push({ marker, length, canOpen: left, canClose: right, at, end });
115
156
  }
116
157
  index = end;
117
158
  }
@@ -120,7 +161,9 @@ function scanEmphasisRuns(masked) {
120
161
 
121
162
  function closeEmphasis(text) {
122
163
  const open = [];
123
- for (const run of scanEmphasisRuns(maskCode(text))) {
164
+ const runs = scanEmphasisRuns(maskCode(text));
165
+ const last = runs[runs.length - 1];
166
+ for (const run of runs) {
124
167
  let length = run.length;
125
168
  while (run.canClose && length > 0) {
126
169
  let match = -1;
@@ -137,13 +180,21 @@ function closeEmphasis(text) {
137
180
  length -= used;
138
181
  open.length = opener.length > 0 ? match + 1 : match;
139
182
  }
183
+ // A run ending the text that closed nothing has no content after it yet:
184
+ // it painted as literal `**` until the next token turned it into style.
185
+ if (run === last && run.end === text.length && length === run.length) {
186
+ return closeEmphasis(text.slice(0, run.at));
187
+ }
140
188
  if (length > 0 && run.canOpen) open.push({ marker: run.marker, length });
141
189
  }
142
- let healed = text;
190
+ // Closers go before trailing whitespace: `**word ` closed after the space
191
+ // could not close (a closing run must follow non-space) and stayed literal.
192
+ const body = text.trimEnd();
193
+ let closers = '';
143
194
  for (let index = open.length - 1; index >= 0; index -= 1) {
144
- healed += open[index].marker.repeat(Math.min(open[index].length, 3));
195
+ closers += open[index].marker.repeat(Math.min(open[index].length, 3));
145
196
  }
146
- return healed;
197
+ return closers ? `${body}${closers}${text.slice(body.length)}` : text;
147
198
  }
148
199
 
149
200
  function linkDestinationEnd(text, start) {
@@ -228,26 +279,44 @@ function tableCells(line) {
228
279
  // time. Until the delimiter is complete (and has the header's cell count) the
229
280
  // rows are a paragraph of raw pipes, which then re-laid out as a table of a
230
281
  // different height — the reply grew and shrank under a followed transcript.
231
- // Hold both rows back until the table can form in one step.
282
+ // Hold both rows back until the table can form in one step. The head may end
283
+ // a block under paragraph lines (a table directly below its caption line),
284
+ // and a header row needs no leading pipe: such a row is held while it is the
285
+ // block's last line, and with a partial delimiter row below it.
232
286
  function trimPendingTableHead(text) {
233
287
  let offset = 0;
234
288
  for (const blank of text.matchAll(/\n[ \t]*\r?\n/g)) offset = blank.index + blank[0].length;
235
289
  const block = text.slice(offset);
236
290
  const lines = block.split('\n').map((line) => line.replace(/\r$/, ''));
237
- if (lines.length === 0 || lines.length > 2 || !PIPE_ROW.test(lines[0])) return text;
238
- if (lines.length === 2) {
239
- const delimiter = lines[1];
240
- if (!PARTIAL_DELIMITER_ROW.test(delimiter)) return text;
241
- if (DELIMITER_ROW.test(delimiter) && tableCells(delimiter) === tableCells(lines[0])) return text;
291
+ // A row that ended with its newline is still the block's last row: the
292
+ // empty line after it is not a (partial) delimiter row.
293
+ let last = lines.length - 1;
294
+ if (last >= 1 && !lines[last].trim()) last -= 1;
295
+ let head = last;
296
+ if (last >= 1 && PARTIAL_DELIMITER_ROW.test(lines[last]) && hasCellPipe(lines[last - 1])) {
297
+ const delimiter = lines[last];
298
+ if (DELIMITER_ROW.test(delimiter) && tableCells(delimiter) === tableCells(lines[last - 1])) return text;
299
+ head = last - 1;
242
300
  }
243
- return text.slice(0, offset);
301
+ if (!(PIPE_ROW.test(lines[head]) || hasCellPipe(lines[head]))) return text;
302
+ // Rows below a delimiter belong to a table that already formed.
303
+ if (lines.slice(0, head).some((line) => DELIMITER_ROW.test(line))) return text;
304
+ let start = offset;
305
+ for (let index = 0; index < head; index += 1) start += lines[index].length + 1;
306
+ return text.slice(0, start);
307
+ }
308
+
309
+ // A cell separator outside inline code: the line can head a table.
310
+ function hasCellPipe(line) {
311
+ return /(?<!\\)\|/.test(line.replace(INLINE_CODE_SPAN, ''));
244
312
  }
245
313
 
246
314
  export function healStreamingMarkdownTail(text, isPendingLocalPath) {
247
315
  const source = String(text ?? '');
248
316
  const fence = source.includes('`') || source.includes('~') ? openFence(source) : null;
249
- if (fence) return trimPartialClosingFence(source, fence);
250
- const value = source.includes('|') ? trimPendingTableHead(source) : source;
317
+ if (fence) return trimPartialOpeningFence(trimPartialClosingFence(source, fence), fence);
318
+ const unmarked = trimPendingBlockMarker(source);
319
+ const value = unmarked.includes('|') ? trimPendingTableHead(unmarked) : unmarked;
251
320
  const hasSyntax = HEALABLE_MARKDOWN_SYNTAX.test(value);
252
321
  if (!value || (!hasSyntax && !isPendingLocalPath)) return value;
253
322
  const healed = hasSyntax
@@ -113,7 +113,6 @@ function cleanString(value) {
113
113
  function providerKind(provider) {
114
114
  const p = String(provider || '').toLowerCase();
115
115
  if (!p) return 'unknown';
116
- if (p === 'opencode-go') return 'quota-api';
117
116
  if (p.includes('oauth')) return 'oauth';
118
117
  if (p === 'mixdog-local') return 'local';
119
118
  return 'api';
@@ -101,7 +101,6 @@ function cleanBool(value) {
101
101
  function providerKind(provider) {
102
102
  const p = String(provider || '').toLowerCase();
103
103
  if (!p) return 'unknown';
104
- if (p === 'opencode-go') return 'quota-api';
105
104
  if (p.includes('oauth')) return 'oauth';
106
105
  if (p === 'mixdog-local') return 'local';
107
106
  return 'api';
@@ -4,17 +4,13 @@ name: Default
4
4
  description: "Handle tasks as requested."
5
5
  ---
6
6
 
7
- Consult the user and build the work plan together. Explicit read-only requests
8
- proceed immediately. When file edits, state mutation, or delegation are
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.
7
+ An explicit request is the approval within its scope: carry it out, including
8
+ routine supporting steps and delegation, without asking again. Resolve
9
+ discoverable facts by inspection; decide minor details yourself and say which
10
+ you chose. Read-only requests do not authorize changes.
12
11
 
13
- When user input is required, park only the affected work and continue
14
- unaffected approved work. Ask pending questions together once no such work
15
- remains.
16
-
17
- While an approved Goal is active, work inside its objective is already
18
- approved: carry it out without stepwise approval. Reserve questions for scope
19
- outside that objective, including a commit, push, release, deployment, or
20
- payment the user has not requested.
12
+ Ask only for a decision that only the user can make and that materially
13
+ changes the outcome, or for authorization outside the request's scope —
14
+ including a commit, push, release, deployment, or payment the user has not
15
+ requested. When user input is required, park only the affected work, continue
16
+ the rest, and ask pending questions together once no such work remains.