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
@@ -58,6 +58,7 @@ import {
58
58
  GREP_OUTPUT_MAX_BYTES,
59
59
  LOCATOR_OUTPUT_MAX_BYTES,
60
60
  capLineOrientedToolOutput,
61
+ capMarker,
61
62
  } from './builtin/tool-output-limit.mjs';
62
63
  import {
63
64
  isBlockedDevicePath,
@@ -340,6 +341,46 @@ function _locatorTargets(toolName, args) {
340
341
  return [toolName === 'find' ? '' : '.'];
341
342
  }
342
343
 
344
+ const _GREP_MATCH_ROW_RE = /^(?:\d+|.+?:\d+):/;
345
+
346
+ // Match rows the byte cap left visible, in the units grep's `offset` counts.
347
+ function _grepShownEntries(keptLines, mode) {
348
+ // A context block counts only when its header and every source line survived.
349
+ let blocks = 0;
350
+ let sawHeader = false;
351
+ keptLines.forEach((line, index) => {
352
+ const header = /^# .+:\d+ \[lines (\d+)-(\d+)\]$/.exec(line);
353
+ if (!header) return;
354
+ sawHeader = true;
355
+ if (index + Number(header[2]) - Number(header[1]) + 1 < keptLines.length) blocks += 1;
356
+ });
357
+ if (sawHeader) return blocks;
358
+ const rows = keptLines.filter((line) => line && !/^(?:#|--$|\[|\.\.\.|…|\()/.test(line));
359
+ return mode === 'content' ? rows.filter((line) => _GREP_MATCH_ROW_RE.test(line)).length : rows.length;
360
+ }
361
+
362
+ // Byte-cap marker for grep: the continuation is the offset of the first match
363
+ // row not shown. A pattern/path fan-out has per-target offsets, so it gets
364
+ // narrowing advice instead of a misleading global offset.
365
+ function _grepBudgetFooter(args, keptLines, capBytes) {
366
+ const fanout = keptLines.some((line) => line.startsWith('# grep '));
367
+ const baseOffset =
368
+ Number.isFinite(Number(args?.offset)) && Number(args.offset) > 0 ? Math.floor(Number(args.offset)) : 0;
369
+ const shown = _grepShownEntries(keptLines, args?.mode);
370
+ // Every mode orders the whole result set before `offset` slices it (files
371
+ // and count pages are sorted first, then cut), so the rows kept here are a
372
+ // prefix of the offset order and offset resumes right after them.
373
+ let narrow = '';
374
+ if (fanout) narrow = 'run one pattern/path per call, or add path/glob/type to cut the match set';
375
+ else if (shown === 0) narrow = 'one match row exceeds the cap; read that file window or use a tighter pattern';
376
+ return capMarker({
377
+ limit: capBytes,
378
+ shown: `${shown} match row(s) shown`,
379
+ nextOffset: fanout || shown === 0 ? null : baseOffset + shown,
380
+ narrow,
381
+ });
382
+ }
383
+
343
384
  function _locatorBudgetFooter(toolName, args, keptLines, capBytes) {
344
385
  const targets = _locatorTargets(toolName, args);
345
386
  const header = new RegExp(`^# ${toolName} (.+)$`);
@@ -358,17 +399,24 @@ function _locatorBudgetFooter(toolName, args, keptLines, capBytes) {
358
399
  }
359
400
  const target = targets[targetIndex] || targets[0] || '';
360
401
  const remaining = targets.length - targetIndex - 1;
361
- const capKb = Math.round((Number(capBytes) > 0 ? Number(capBytes) : LOCATOR_OUTPUT_MAX_BYTES) / 1024);
362
- // Factual continuation only — no retry imperatives: the marker states
363
- // what was cut, never instructs another call.
402
+ const limit = Number(capBytes) > 0 ? Number(capBytes) : LOCATOR_OUTPUT_MAX_BYTES;
364
403
  const nextTarget =
365
404
  remaining > 0 ? `; ${remaining} target(s) not shown, next=${JSON.stringify(targets[targetIndex + 1])}` : '';
366
405
  if (toolName === 'find') {
367
- return `... [find output capped at ${capKb} KB; remaining matches for query=${JSON.stringify(target)} omitted${nextTarget}]`;
406
+ // find has no offset: the rest of the ranked matches needs a tighter query.
407
+ return capMarker({
408
+ limit,
409
+ shown: `matches for query=${JSON.stringify(target)} cut${nextTarget}`,
410
+ narrow: 'use a more specific query or add path',
411
+ });
368
412
  }
369
413
  const baseOffset =
370
414
  Number.isFinite(Number(args?.offset)) && Number(args.offset) > 0 ? Math.floor(Number(args.offset)) : 0;
371
- return `... [${toolName} output capped at ${capKb} KB; rows above are complete; continue path=${JSON.stringify(target)} offset:${baseOffset + visibleEntries}${nextTarget}]`;
415
+ return capMarker({
416
+ limit,
417
+ shown: `${visibleEntries} complete row(s) shown for path=${JSON.stringify(target)}${nextTarget}`,
418
+ nextOffset: baseOffset + visibleEntries,
419
+ });
372
420
  }
373
421
 
374
422
  export async function executeBuiltinTool(name, args, cwd, options = {}) {
@@ -489,11 +537,7 @@ function _budgetBuiltinOutput(toolName, args, result, options) {
489
537
  const explicitCap = Number(options?.toolOutputMaxBytes) > 0 ? Math.trunc(Number(options.toolOutputMaxBytes)) : null;
490
538
  if (toolName === 'grep') {
491
539
  const grepCap = explicitCap ? Math.min(explicitCap, GREP_OUTPUT_MAX_BYTES) : GREP_OUTPUT_MAX_BYTES;
492
- return capLineOrientedToolOutput(
493
- result,
494
- grepCap,
495
- (kept) => `[grep output capped at ${grepCap} bytes; ${kept.length} line(s) shown, remainder omitted]`
496
- );
540
+ return capLineOrientedToolOutput(result, grepCap, (kept) => _grepBudgetFooter(args, kept, grepCap));
497
541
  }
498
542
  if (_LOCATOR_BUDGET_TOOLS.has(toolName)) {
499
543
  const locatorCap = explicitCap ? Math.min(explicitCap, LOCATOR_OUTPUT_MAX_BYTES) : LOCATOR_OUTPUT_MAX_BYTES;
@@ -172,9 +172,10 @@ async function _validateDiskCodeGraphHit({
172
172
  return { graph, manifest, signature };
173
173
  }
174
174
 
175
- // Owns a pre-acquired slot until a validated miss hands it to spawnWorker.
176
- // Keeping this seam dependency-injectable makes slot/abort behavior testable
177
- // without spawning native processes or Worker threads.
175
+ // Validates the disk graph under a read-only query slot; a miss releases it
176
+ // and spawnWorker queues for a build slot, so queries never wait behind
177
+ // builds. Keeping this seam dependency-injectable makes slot/abort behavior
178
+ // testable without spawning native processes or Worker threads.
178
179
  export async function _runDiskCodeGraphFastPath({
179
180
  graphCwd,
180
181
  diskProbe,
@@ -182,36 +183,31 @@ export async function _runDiskCodeGraphFastPath({
182
183
  signal = null,
183
184
  loadDiskEntry,
184
185
  consumeDirty = () => {},
185
- // Long-lived code-graph lane so a cold graph build never blocks short rg
186
- // processes in the independent search lane.
187
- acquireSlot = (signal) => acquireChildSpawnSlot(signal, 'code-graph'),
186
+ acquireSlot = (signal) => acquireChildSpawnSlot(signal, 'code-graph-query'),
188
187
  validateDiskHit = _validateDiskCodeGraphHit,
189
188
  spawnWorker,
190
189
  maxFiles = CODE_GRAPH_MAX_FILES,
191
190
  }) {
192
191
  if (!diskProbe?.isFastPathEligible || diskProbe.maxFiles !== maxFiles) {
193
- return spawnWorker(null, null, null);
192
+ return spawnWorker(null, null);
194
193
  }
195
194
  const diskEntry = loadDiskEntry();
196
195
  if (!_isCompatibleDiskCodeGraphEntry(diskEntry, maxFiles)) {
197
- return spawnWorker(null, null, null);
196
+ return spawnWorker(null, null);
198
197
  }
199
198
  consumeDirty();
200
199
  const release = await acquireSlot(signal || null);
201
- let handedToWorker = false;
200
+ let hit;
202
201
  try {
203
202
  _throwIfAborted(signal);
204
- const hit = await validateDiskHit({ graphCwd, diskEntry, genAtStart, signal });
205
- _throwIfAborted(signal);
206
- if (hit?.invalidated) throw codeGraphBuildInvalidatedError(hit.graph);
207
- if (hit?.graph) return hit.graph;
208
- _throwIfAborted(signal);
209
- const workerPromise = spawnWorker(release, hit?.manifest || null, hit?.signature || null);
210
- handedToWorker = true;
211
- return workerPromise;
203
+ hit = await validateDiskHit({ graphCwd, diskEntry, genAtStart, signal });
212
204
  } finally {
213
- if (!handedToWorker) release();
205
+ release();
214
206
  }
207
+ _throwIfAborted(signal);
208
+ if (hit?.invalidated) throw codeGraphBuildInvalidatedError(hit.graph);
209
+ if (hit?.graph) return hit.graph;
210
+ return spawnWorker(hit?.manifest || null, hit?.signature || null);
215
211
  }
216
212
 
217
213
  function _prepareDiskCodeGraphFastPath({
@@ -306,8 +302,10 @@ export async function buildCodeGraphAsync(
306
302
  const existing = _inflightAsyncBuilds.get(inflightKey);
307
303
  if (existing) return raceAbort(existing, signal);
308
304
  // Non-competing prewarm: the signature-validation manifest also needs a
309
- // child-spawn slot, so warmers skip before either it or a Worker can queue.
310
- if (bestEffort && !childSpawnHasSpareCapacity('code-graph')) return null;
305
+ // query slot and a miss a build slot, so warmers skip before either queues.
306
+ if (bestEffort && !(childSpawnHasSpareCapacity('code-graph-query') && childSpawnHasSpareCapacity('code-graph'))) {
307
+ return null;
308
+ }
311
309
  const promise = _retryCodeGraphBuildAfterInvalidation(
312
310
  async () => {
313
311
  const genAtStart = _getCodeGraphGen(graphCwd);
@@ -315,7 +313,7 @@ export async function buildCodeGraphAsync(
315
313
  // bounded I/O. Do not run a manifest at all when no disk candidate exists:
316
314
  // cold/dirty misses remain entirely on the existing Worker path.
317
315
  if (scoped) {
318
- return _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, null, null, null, {
316
+ return _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, null, null, {
319
317
  buildOptions: scopeOptions,
320
318
  cacheResult: false,
321
319
  });
@@ -330,8 +328,8 @@ export async function buildCodeGraphAsync(
330
328
  signal,
331
329
  loadDiskEntry: () => getDiskCodeGraphEntry(graphCwd),
332
330
  consumeDirty: () => _consumeCodeGraphDirtyPaths(graphCwd),
333
- spawnWorker: (preAcquiredRelease, manifest, signature) =>
334
- _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, preAcquiredRelease, manifest, signature),
331
+ spawnWorker: (manifest, signature) =>
332
+ _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, manifest, signature),
335
333
  }),
336
334
  });
337
335
  },
@@ -350,7 +348,6 @@ export function _spawnCodeGraphWorker(
350
348
  graphCwd,
351
349
  genAtStart,
352
350
  signal,
353
- preAcquiredRelease = null,
354
351
  manifest = null,
355
352
  signature = null,
356
353
  {
@@ -416,10 +413,7 @@ export function _spawnCodeGraphWorker(
416
413
  /* best-effort */
417
414
  }
418
415
  };
419
- (preAcquiredRelease
420
- ? Promise.resolve(preAcquiredRelease)
421
- : acquireChildSpawnSlot(signal || null, 'code-graph')
422
- ).then((release) => {
416
+ acquireChildSpawnSlot(signal || null, 'code-graph').then((release) => {
423
417
  _releaseSlot = release;
424
418
  if (settled) {
425
419
  release();
@@ -14,7 +14,7 @@ import { resolve as pathResolve, isAbsolute, relative as pathRelative, dirname a
14
14
  import { normalizeInputPath } from '../builtin/path-utils.mjs';
15
15
  import { markScopedCacheIncomplete, recordScopedCacheDependency } from '../../session/cache/scoped-cache-outcome.mjs';
16
16
  import { CODE_GRAPH_TOOL_DEFS } from '../code-graph-tool-defs.mjs';
17
- import { CODE_GRAPH_OUTPUT_MAX_BYTES, capLineOrientedToolOutput } from '../builtin/tool-output-limit.mjs';
17
+ import { CODE_GRAPH_OUTPUT_MAX_BYTES, capLineOrientedToolOutput, capMarker } from '../builtin/tool-output-limit.mjs';
18
18
  import { _graphRel, _appendSameBasenameHint, _isExistingFile } from './source-access.mjs';
19
19
  import {
20
20
  _capGraphList,
@@ -375,7 +375,6 @@ async function executeCodeGraphToolRaw(name, rawArgs, cwd, signal = null, option
375
375
 
376
376
  function _codeGraphBudgetFooter(args, keptLines) {
377
377
  const rawMode = String(args?.mode || '').trim();
378
- const capKb = Math.round(CODE_GRAPH_OUTPUT_MAX_BYTES / 1024);
379
378
  const targets = collectGraphSymbolList(args);
380
379
  const files = _collectGraphFileList(args);
381
380
  const batchTargets = targets.length ? targets : files;
@@ -389,13 +388,18 @@ function _codeGraphBudgetFooter(args, keptLines) {
389
388
  const index = batchTargets.indexOf(match[1]);
390
389
  if (index >= 0) currentIndex = index;
391
390
  }
392
- return `... [code_graph output capped at ${capKb} KB; not fully shown: ${label}=${JSON.stringify(batchTargets.slice(currentIndex))}]`;
391
+ return capMarker({
392
+ limit: CODE_GRAPH_OUTPUT_MAX_BYTES,
393
+ shown: `${label} from index ${currentIndex} not fully shown`,
394
+ narrow: `re-query ${label}=${JSON.stringify(batchTargets.slice(currentIndex))} separately`,
395
+ });
393
396
  }
394
397
  const target = batchTargets[0];
395
- const targetTail = target
396
- ? `; remainder for ${label.slice(0, -1)}=${JSON.stringify(target)} omitted`
397
- : '; remainder omitted';
398
- return `... [code_graph output capped at ${capKb} KB${targetTail}]`;
398
+ return capMarker({
399
+ limit: CODE_GRAPH_OUTPUT_MAX_BYTES,
400
+ shown: target ? `remainder for ${label.slice(0, -1)}=${JSON.stringify(target)} omitted` : 'remainder omitted',
401
+ narrow: 'add file/path scope, a narrower mode, or a smaller depth/limit',
402
+ });
399
403
  }
400
404
 
401
405
  export async function executeCodeGraphTool(name, args, cwd, signal = null, options = {}) {
@@ -9,12 +9,111 @@
9
9
  import { SHELL_NAMES as _SHELL_NAMES, WRAPPER_NAMES as _WRAPPER_NAMES } from './shell-policy.mjs';
10
10
  import { extractPowerShellCommandInner } from './shell-powershell.mjs';
11
11
 
12
+ // A `#` (or PowerShell `<#`) starts a comment only at the start of a word:
13
+ // line start, after whitespace, or after a command operator. Inside a word or
14
+ // URL (`a#b`, `http://x/#y`, `$#`, `${#v}`) it is an ordinary character.
15
+ function _isCommentStart(text, i) {
16
+ return i === 0 || /[\s;&|(]/.test(text[i - 1]);
17
+ }
18
+
19
+ const _PS_HERE_SQ = /@'[ \t]*\r?\n[\s\S]*?\r?\n'@/y;
20
+ const _PS_HERE_DQ = /@"[ \t]*\r?\n[\s\S]*?\r?\n"@/y;
21
+ const _HEREDOC_START = /<<-?\s*['"]?(\w+)['"]?/y;
22
+
23
+ function _closingQuote(text, from, quote, escapes) {
24
+ let j = from;
25
+ while (j < text.length && text[j] !== quote) j += escapes && text[j] === '\\' ? 2 : 1;
26
+ return j < text.length ? j : -1;
27
+ }
28
+
29
+ // Single left-to-right scan so each construct is recognised by what the shell
30
+ // would see at that position (a `'` inside a comment, a `#` inside a word or a
31
+ // quote inside a here-string never mis-pair later delimiters). Any construct
32
+ // that cannot be closed (unterminated quote, `<#` without `#>`, heredoc without
33
+ // terminator) is left in the output verbatim, so uncertain text stays visible
34
+ // to the policy checks instead of being dropped.
12
35
  export function stripQuotedAndHeredoc(s) {
13
- return String(s || '')
14
- .replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\b/g, '<<HEREDOC>>')
15
- .replace(/'[^']*'/g, "''")
16
- .replace(/"(?:[^"\\]|\\.)*"/g, '""')
17
- .replace(/#[^\n]*/g, '');
36
+ const text = String(s || '');
37
+ const n = text.length;
38
+ const pendingHeredocs = [];
39
+ let out = '';
40
+ let i = 0;
41
+ while (i < n) {
42
+ const ch = text[i];
43
+ if (ch === '\\') {
44
+ out += text.slice(i, i + 2);
45
+ i += 2;
46
+ continue;
47
+ }
48
+ if (ch === '@' && (text[i + 1] === "'" || text[i + 1] === '"')) {
49
+ const re = text[i + 1] === "'" ? _PS_HERE_SQ : _PS_HERE_DQ;
50
+ re.lastIndex = i;
51
+ const m = re.exec(text);
52
+ if (m) {
53
+ out += text[i + 1] === "'" ? "''" : '""';
54
+ i += m[0].length;
55
+ continue;
56
+ }
57
+ }
58
+ if (ch === '<' && text[i + 1] === '<' && text[i - 1] !== '<') {
59
+ _HEREDOC_START.lastIndex = i;
60
+ const m = _HEREDOC_START.exec(text);
61
+ if (m && new RegExp(`\\n${m[1]}\\b`).test(text.slice(i + m[0].length))) {
62
+ // The rest of the opening line stays visible; the body is skipped at
63
+ // the next newline.
64
+ pendingHeredocs.push(m[1]);
65
+ out += '<<HEREDOC>>';
66
+ i += m[0].length;
67
+ continue;
68
+ }
69
+ }
70
+ if (ch === '<' && text[i + 1] === '#' && _isCommentStart(text, i)) {
71
+ const end = text.indexOf('#>', i + 2);
72
+ if (end >= 0) {
73
+ out += ' ';
74
+ i = end + 2;
75
+ continue;
76
+ }
77
+ }
78
+ if (ch === '#' && _isCommentStart(text, i)) {
79
+ const nl = text.indexOf('\n', i);
80
+ i = nl < 0 ? n : nl;
81
+ continue;
82
+ }
83
+ if (ch === "'" || ch === '"' || (ch === '$' && text[i + 1] === "'")) {
84
+ const ansiC = ch === '$';
85
+ const quote = ansiC ? "'" : ch;
86
+ const open = ansiC ? i + 2 : i + 1;
87
+ const close = _closingQuote(text, open, quote, ch !== "'");
88
+ if (close >= 0) {
89
+ out += (ansiC ? '$' : '') + quote + quote;
90
+ i = close + 1;
91
+ continue;
92
+ }
93
+ }
94
+ if (ch === '\n' && pendingHeredocs.length) {
95
+ let at = i;
96
+ let found = true;
97
+ for (const delimiter of pendingHeredocs) {
98
+ const re = new RegExp(`\\n${delimiter}\\b`, 'g');
99
+ re.lastIndex = at;
100
+ const m = re.exec(text);
101
+ if (!m) {
102
+ found = false;
103
+ break;
104
+ }
105
+ at = m.index + m[0].length;
106
+ }
107
+ pendingHeredocs.length = 0;
108
+ if (found) {
109
+ i = at;
110
+ continue;
111
+ }
112
+ }
113
+ out += ch;
114
+ i++;
115
+ }
116
+ return out;
18
117
  }
19
118
 
20
119
  // Heredoc bodies (shell-exec contexts route the body through scanning).
@@ -37,8 +136,11 @@ function _tokenize(s) {
37
136
  if (c === "'") {
38
137
  const e = src.indexOf("'", i + 1);
39
138
  if (e === -1) {
40
- cur += src.slice(i);
41
- break;
139
+ // Unterminated: an apostrophe inside a PowerShell word is literal, so
140
+ // keep tokenizing instead of swallowing the rest of the command.
141
+ cur += c;
142
+ i++;
143
+ continue;
42
144
  }
43
145
  cur += src.slice(i, e + 1);
44
146
  i = e + 1;
@@ -55,8 +157,9 @@ function _tokenize(s) {
55
157
  j++;
56
158
  }
57
159
  if (j >= src.length) {
58
- cur += src.slice(i);
59
- break;
160
+ cur += c;
161
+ i++;
162
+ continue;
60
163
  }
61
164
  cur += src.slice(i, j + 1);
62
165
  i = j + 1;
@@ -67,6 +170,20 @@ function _tokenize(s) {
67
170
  i += 2;
68
171
  continue;
69
172
  }
173
+ // `cur` is empty exactly at a word start (after whitespace or an operator),
174
+ // the only place `#` / PowerShell `<#` begins a comment.
175
+ if (!cur && c === '#') {
176
+ const nl = src.indexOf('\n', i);
177
+ i = nl < 0 ? src.length : nl;
178
+ continue;
179
+ }
180
+ if (!cur && c === '<' && src[i + 1] === '#') {
181
+ const end = src.indexOf('#>', i + 2);
182
+ if (end >= 0) {
183
+ i = end + 2;
184
+ continue;
185
+ }
186
+ }
70
187
  if (c === ' ' || c === '\t') {
71
188
  if (cur) {
72
189
  t.push(cur);
@@ -1,30 +1,30 @@
1
1
  {
2
- "version": "0.1.32",
2
+ "version": "0.1.33",
3
3
  "_comment": "Synced from immutable graph-v release assets.",
4
4
  "assets": {
5
5
  "darwin-arm64": {
6
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.32/mixdog-graph-darwin-arm64",
7
- "sha256": "e4efda42c8fac65f4ba7da894ad0679cbb541069470633ccf5e9752c9fac680e"
6
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.33/mixdog-graph-darwin-arm64",
7
+ "sha256": "8637d61493eddca3eee80620a64e737f12eead44ea899ad19a18966a3843ff3a"
8
8
  },
9
9
  "darwin-x64": {
10
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.32/mixdog-graph-darwin-x64",
11
- "sha256": "9b6cc73185a0b3a59a1279727fc92b8674fc0f7b7519b28a5e75f26926802de8"
10
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.33/mixdog-graph-darwin-x64",
11
+ "sha256": "49ce311687c3df7e7080ecb7694219c9b51bbbb272864aa43d2102a8baa83550"
12
12
  },
13
13
  "linux-arm64": {
14
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.32/mixdog-graph-linux-arm64",
15
- "sha256": "373522940be2e261bf7f4de3ad226d1e68d73ef1efe95307525b2f867322e722"
14
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.33/mixdog-graph-linux-arm64",
15
+ "sha256": "2e2a4ea6dccc5b33f583040b0ee6e8d47f93a1e879eb17343acd0b2f9838696f"
16
16
  },
17
17
  "linux-x64": {
18
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.32/mixdog-graph-linux-x64",
19
- "sha256": "ae8dce99b93c31f5e9f5c5f510dafdc035896fb7faf1e04b5fb603e182a21525"
18
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.33/mixdog-graph-linux-x64",
19
+ "sha256": "2cd1caa7f39e183d6fb6f7764f49e9a0643863d137c2bfb5f40bb7ac616c8d73"
20
20
  },
21
21
  "win32-arm64": {
22
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.32/mixdog-graph-win32-arm64.exe",
23
- "sha256": "077012187e7a48c6cea8b7e5cdcfd567e518239078a247a6062c32562a76658c"
22
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.33/mixdog-graph-win32-arm64.exe",
23
+ "sha256": "592dfa7e95e1e91424c7467e74f35d09758de502be403b7d229444b435600392"
24
24
  },
25
25
  "win32-x64": {
26
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.32/mixdog-graph-win32-x64.exe",
27
- "sha256": "9a449f632ea6f5c6f08feb158a6bbf7f829a3d7731e36821d1dbc042ab69bf48"
26
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.33/mixdog-graph-win32-x64.exe",
27
+ "sha256": "0ed17859a39f0160421865f3a3486ce53957b836ee164d606434519b85b90830"
28
28
  }
29
29
  }
30
30
  }
@@ -27,14 +27,14 @@ export function unregisterOwnedShellTree(jobId, entry) {
27
27
  if (ownedTrees.get(key) === entry) ownedTrees.delete(key);
28
28
  }
29
29
 
30
- /** Cancel every shell tree of one owner session. Synchronous (the native
31
- * cancel's own result) when every snapshot is already complete; otherwise a
32
- * promise that sends the cancel once the snapshots land or the bound passes. */
33
- export function cancelOwnedShellTrees(ownerSessionId) {
34
- const owner = String(ownerSessionId || '').trim();
30
+ /** Prepare the kill of every registered tree — of one owner, or of all when
31
+ * `ownerSessionId` is null (runtime shutdown) — and unregister them. Returns
32
+ * the snapshots still pending (empty when all were complete). */
33
+ export function prepareOwnedShellTreeKills(ownerSessionId = null) {
34
+ const owner = ownerSessionId == null ? null : String(ownerSessionId).trim();
35
35
  const pending = [];
36
36
  for (const [jobId, entry] of [...ownedTrees]) {
37
- if (String(entry.ownerSessionId || '') !== owner) continue;
37
+ if (owner !== null && String(entry.ownerSessionId || '') !== owner) continue;
38
38
  let ready;
39
39
  try {
40
40
  ready = entry.prepareKill();
@@ -44,6 +44,15 @@ export function cancelOwnedShellTrees(ownerSessionId) {
44
44
  if (ready !== true) pending.push(Promise.resolve(ready).catch(() => false));
45
45
  unregisterOwnedShellTree(jobId, entry);
46
46
  }
47
+ return pending;
48
+ }
49
+
50
+ /** Cancel every shell tree of one owner session. Synchronous (the native
51
+ * cancel's own result) when every snapshot is already complete; otherwise a
52
+ * promise that sends the cancel once the snapshots land or the bound passes. */
53
+ export function cancelOwnedShellTrees(ownerSessionId) {
54
+ const owner = String(ownerSessionId || '').trim();
55
+ const pending = prepareOwnedShellTreeKills(owner);
47
56
  if (pending.length === 0) return cancelNativeTasks({ ownerSessionId: owner });
48
57
  let result = null;
49
58
  const send = () => {
@@ -32,6 +32,9 @@ export const TERMINATION_RECHECK_MS = 5_000;
32
32
  // read is not abandoned — a snapshot that lands later is still absorbed.
33
33
  export const PROCESS_TABLE_READ_BOUND_MS = 2_000;
34
34
 
35
+ // Kill-and-observe rounds of a survivor kill before it reports what is left.
36
+ const KILL_ROUNDS = 3;
37
+
35
38
  /** Resolve `promise`'s value, or `onTimeout` once `ms` elapse first. The
36
39
  * bound's timer is cleared whichever side wins. */
37
40
  export function withinBound(promise, ms, onTimeout) {
@@ -169,13 +172,30 @@ export function createProcessTreeTracker({ pid, members = [], exitObserved = fal
169
172
  };
170
173
  }
171
174
 
175
+ /** Terminate every identity the last observation found alive (console hosts
176
+ * excepted: they are witnesses, never workers). These are processes of a tree
177
+ * that was KILLED — they escaped its process group (setsid, job control, a
178
+ * double fork that kept its parent link) or outlived the root on Windows —
179
+ * so the kill is not finished while they run. Windows: taskkill /T /F;
180
+ * POSIX: SIGTERM, then SIGKILL after a short grace. */
181
+ function sweepLiveIdentities(tracker) {
182
+ const targets = tracker.live
183
+ .filter((entry) => entry.token != null && !CONSOLE_HOST.test(entry.name))
184
+ .map((entry) => entry.pid);
185
+ return targets.length > 0 ? killProcessTrees(targets) : Promise.resolve();
186
+ }
187
+
172
188
  /** One observation loop: observe now, then every `intervalMs` until the tree
173
189
  * is confirmed gone, then run `onTerminated` once. 'running', 'unknown', a
174
190
  * failed or unanswered (by the next cadence tick) observation and a failing
175
191
  * `onTerminated` all re-check on the same cadence; the first pass that is not
176
192
  * 'gone' calls `onUnconfirmed` once. The returned stop() clears the pending
177
193
  * timer; nothing else (listener, timer) outlives the loop. Nothing in it ever
178
- * releases on time alone. */
194
+ * releases on time alone.
195
+ *
196
+ * `sweep` (a killed tree): a pass that finds captured identities alive
197
+ * terminates them and observes again in the same pass, so a cancellation
198
+ * ends with the tree gone instead of a held folder over live processes. */
179
199
  export function watchTermination({
180
200
  tracker,
181
201
  rootExited = () => true,
@@ -183,6 +203,7 @@ export function watchTermination({
183
203
  onUnconfirmed = null,
184
204
  releaseRequested = null,
185
205
  intervalMs = TERMINATION_RECHECK_MS,
206
+ sweep = false,
186
207
  }) {
187
208
  let timer = null;
188
209
  let stopped = false;
@@ -206,6 +227,17 @@ export function watchTermination({
206
227
  observation.catch(() => {});
207
228
  // A pass that has not answered by the next cadence tick is unknown.
208
229
  state = await withinBound(observation, intervalMs, 'unknown');
230
+ if (state === 'running' && sweep && !stopped) {
231
+ await withinBound(
232
+ sweepLiveIdentities(tracker).catch(() => {}),
233
+ intervalMs,
234
+ null
235
+ );
236
+ await observation.catch(() => {});
237
+ observation = tracker.observe({ rootExited: rootExited() });
238
+ observation.catch(() => {});
239
+ state = await withinBound(observation, intervalMs, 'unknown');
240
+ }
209
241
  } catch {
210
242
  state = 'unknown';
211
243
  }
@@ -365,7 +397,13 @@ export async function killShellDescendants(handle) {
365
397
  .map((entry) => entry.pid);
366
398
  await killProcessTrees(targets).catch(() => {});
367
399
  const tracker = trackerFor(handle);
368
- const state = await tracker.observe({ rootExited: true });
400
+ let state = await tracker.observe({ rootExited: true });
401
+ // What the first kill missed (a child forked meanwhile, a slow exit) is
402
+ // found by the re-observation itself and killed in the next round.
403
+ for (let round = 1; state === 'running' && round < KILL_ROUNDS; round += 1) {
404
+ await sweepLiveIdentities(tracker).catch(() => {});
405
+ state = await tracker.observe({ rootExited: true });
406
+ }
369
407
  return { terminated: state === 'gone', survivors: tracker.live.map((entry) => entry.pid) };
370
408
  } catch {
371
409
  return { terminated: false, survivors: [...handle.watchPids] };
@@ -75,8 +75,8 @@ export function killAfterFailedPromotion(run, reason, promotionFailure) {
75
75
  }
76
76
 
77
77
  /**
78
- * The promoted job leaves foreground admission: its lease is RELEASED, which
79
- * also restores the parent's high-load share. A long-lived server must never
78
+ * The promoted job leaves foreground admission: its lease is RELEASED. A
79
+ * long-lived server must never
80
80
  * hold a foreground slot for its whole life; background jobs are not counted
81
81
  * against any cap (session/runtime close stops them via shutdownShellJobs).
82
82
  */
@@ -13,7 +13,18 @@ import {
13
13
  } from '../shell-run-state.mjs';
14
14
 
15
15
  const BACKGROUND_GUIDANCE =
16
- 'Completion arrives automatically; call task wait (not task read polling) when the result is due.';
16
+ 'Its result is delivered automatically when it finishes; use task wait only if you need it now.';
17
+
18
+ function backgroundVerb(reason, secs) {
19
+ if (reason === 'timeout') return `moved to background at timeout after ${secs}s`;
20
+ if (reason === 'input') return `moved to background on new user input after ${secs}s`;
21
+ if (reason === 'notification') {
22
+ return `moved to background after ${secs}s because a notification arrived (delivered to you now)`;
23
+ }
24
+ if (reason === 'interrupt') return `moved to background on interrupt after ${secs}s`;
25
+ if (reason === 'background') return 'started in the background';
26
+ return `moved to background after the ${secs}s hold`;
27
+ }
17
28
 
18
29
  export function promotedSpillPaths(taskOutput) {
19
30
  return {
@@ -89,8 +100,7 @@ export function resolveBackgrounded(run, { jobId, reason, remainingBackgroundTim
89
100
  // frame alive and could later kill an unrelated, already-returned job.
90
101
  detachAbortHandler(run);
91
102
  const secs = Math.max(0, Math.round(elapsedSinceStart(run) / 1000));
92
- const verb =
93
- reason === 'timeout' ? `moved to background at timeout after ${secs}s` : `auto-backgrounded after ${secs}s`;
103
+ const verb = backgroundVerb(reason, secs);
94
104
  run.resolveResult(
95
105
  new ExecResult({
96
106
  ...capturedFields(run, capture),
@@ -56,6 +56,8 @@ export function releaseAdmissionOnConfirmedTermination(run, { markNow = true } =
56
56
  run.stopTerminationWatch = watchTermination({
57
57
  tracker: run.terminationTracker,
58
58
  rootExited: () => child.exitCode != null || child.signalCode != null,
59
+ // A killed run is not over while processes it started still run.
60
+ sweep: run.killed === true,
59
61
  onTerminated: release,
60
62
  onUnconfirmed: markNow ? null : () => keepTerminationUnconfirmedRecord(run),
61
63
  releaseRequested: () => takeReleaseRequest(run.foregroundRecordId),
@@ -30,7 +30,9 @@ export function registerForegroundOwnedTree(run, ownerSessionId) {
30
30
  run.killed = true;
31
31
  run.ownerCancelled = true;
32
32
  run.killCause = run.killCause || 'session-close';
33
- return run.terminationTracker?.complete === true || capturePreKill(run);
33
+ // Always a fresh read: an earlier one predates what the run started
34
+ // since. Pending, the kill is sent at the snapshot bound regardless.
35
+ return capturePreKill(run);
34
36
  },
35
37
  };
36
38
  registerOwnedShellTree(run.foregroundRecordId, run.ownedTree);
@@ -829,7 +829,7 @@ function unifiedUpdateHunks(sourceLines, section, displayPath, { fuzzy, rejectPa
829
829
  const loc = resolveV4AHunkPosition(sourceLines, hunk, nextSearchLine, { fuzzy });
830
830
  if (loc.skip) continue;
831
831
  if (loc.error) {
832
- const msg = `${loc.error.replace(/^V4A hunk /, `V4A hunk ${section.path}: `)}`;
832
+ const msg = `${loc.error.replace(/^V4A hunk /, () => `V4A hunk ${section.path}: `)}`;
833
833
  if (rejectPartial) throw new Error(msg);
834
834
  if (rejectedHunks) rejectedHunks.push({ file: section.path, hunk, reason: msg });
835
835
  continue;