mixdog 1.2.5 → 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 (290) 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/provider-pingpong-live.mjs +9 -9
  5. package/src/agents/advisor/AGENT.md +4 -4
  6. package/src/agents/advisor/agent.json +1 -1
  7. package/src/agents/reviewer/AGENT.md +2 -3
  8. package/src/agents/reviewer/agent.json +1 -1
  9. package/src/defaults/shipped-history.json +10 -0
  10. package/src/defaults/skills/browser-use/SKILL.md +2 -1
  11. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  12. package/src/defaults/skills/visualize/SKILL.md +3 -2
  13. package/src/lib/rules-builder.cjs +1 -1
  14. package/src/output-styles/common.md +22 -6
  15. package/src/output-styles/detailed.md +7 -5
  16. package/src/output-styles/minimal.md +7 -3
  17. package/src/output-styles/simple.md +2 -3
  18. package/src/rules/agent/AGENT.md +3 -1
  19. package/src/rules/lead/LEAD.md +4 -3
  20. package/src/rules/shared/10-tool-workflow.md +2 -1
  21. package/src/rules/shared/60-verification.md +4 -5
  22. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  23. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  24. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
  25. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  26. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
  27. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  29. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  37. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
  38. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  39. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  40. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  41. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  42. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  43. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  44. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  45. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  46. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  51. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  56. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  65. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  66. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  67. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  68. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  69. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  70. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
  71. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
  72. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  73. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  74. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  75. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  76. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  77. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  78. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  79. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
  80. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
  82. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  83. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  84. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  85. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  86. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  87. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  88. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  89. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  90. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  91. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  92. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  93. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  94. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  95. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  96. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  97. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
  98. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
  99. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
  100. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  101. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  102. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  103. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  104. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  105. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  106. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  109. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  111. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  112. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  113. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  114. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  115. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  117. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  119. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  122. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  123. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  124. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  125. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  126. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  127. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  129. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  130. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  132. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  133. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  134. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  135. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  136. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  137. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
  138. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  139. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  140. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  141. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  142. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  143. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  144. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  145. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  146. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
  153. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
  155. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  156. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  157. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  158. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  159. package/src/runtime/attachments/office-extract.mjs +11 -9
  160. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  161. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  162. package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
  163. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  164. package/src/runtime/office/core/tabular.mjs +1 -1
  165. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  166. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  167. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  168. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  169. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  170. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  171. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  172. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  173. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  174. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  175. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  176. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  177. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  178. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  179. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  180. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  181. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  182. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  183. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  184. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  185. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  186. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  187. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  188. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  189. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  190. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  191. package/src/runtime/shared/atomic-file.mjs +22 -13
  192. package/src/runtime/shared/background-tasks.mjs +60 -20
  193. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  194. package/src/runtime/shared/config.mjs +0 -10
  195. package/src/runtime/shared/file-lock.mjs +7 -1
  196. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  197. package/src/runtime/shared/file-permissions.d.mts +19 -4
  198. package/src/runtime/shared/file-permissions.mjs +310 -4
  199. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  200. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  201. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  202. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  203. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  204. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  205. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  206. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  207. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  208. package/src/runtime/shared/notify-trace.mjs +8 -1
  209. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  210. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  211. package/src/runtime/shared/process-runner.mjs +38 -0
  212. package/src/runtime/shared/provider-accounts.mjs +2 -1
  213. package/src/runtime/shared/provider-api-key.mjs +0 -1
  214. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  215. package/src/runtime/shared/resource-admission.mjs +60 -18
  216. package/src/runtime/shared/run-child.mjs +84 -0
  217. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  218. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  219. package/src/runtime/shared/singleton-owner.mjs +126 -42
  220. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  221. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  222. package/src/runtime/shared/user-data-guard.mjs +28 -9
  223. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  224. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  225. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  226. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  227. package/src/runtime/tidy/runners/biome.mjs +3 -1
  228. package/src/runtime/tidy/shadow.mjs +2 -1
  229. package/src/runtime/web-search/index.mjs +5 -7
  230. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  231. package/src/runtime/web-search/tool-defs.mjs +6 -1
  232. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  233. package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
  234. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  235. package/src/session-runtime/lifecycle/shared.mjs +19 -2
  236. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  237. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  238. package/src/session-runtime/lifecycle-api.mjs +3 -1
  239. package/src/session-runtime/provider-auth/login.mjs +10 -6
  240. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  241. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  242. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  243. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  244. package/src/session-runtime/provider-models.mjs +76 -10
  245. package/src/session-runtime/provider-readiness.mjs +4 -0
  246. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  247. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  248. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  249. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  250. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  251. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  252. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  253. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  254. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  255. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  256. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  257. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  258. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  259. package/src/session-runtime/services/agent-tool.mjs +20 -0
  260. package/src/session-runtime/services/provider-admin.mjs +92 -20
  261. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  262. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  263. package/src/session-runtime/turn/session-ops.mjs +2 -2
  264. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  265. package/src/session-runtime/usage-stats-api.mjs +6 -1
  266. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  267. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  268. package/src/standalone/daemon-owner.mjs +117 -25
  269. package/src/standalone/daemon.mjs +43 -13
  270. package/src/standalone/session-call-cache.mjs +6 -0
  271. package/src/standalone/session-protocol.mjs +1 -1
  272. package/src/standalone/session-service/retention/busy.mjs +7 -2
  273. package/src/standalone/session-transport/routes.mjs +7 -6
  274. package/src/tui/app/model-options.mjs +39 -2
  275. package/src/tui/app/model-picker.mjs +16 -2
  276. package/src/tui/app/provider-setup-picker.mjs +1 -2
  277. package/src/tui/dist/index.mjs +385 -104
  278. package/src/tui/session/notice-text.mjs +0 -1
  279. package/src/tui/session/session-api/intake/submission.mjs +7 -13
  280. package/src/tui/session/session-api/integrations.mjs +1 -5
  281. package/src/tui/session/session-flow/drain.mjs +8 -0
  282. package/src/tui/session/session-flow/steering.mjs +46 -0
  283. package/src/tui/session/session-flow.mjs +2 -0
  284. package/src/tui/session/turn-stream.mjs +33 -0
  285. package/src/tui/session/turn.mjs +30 -3
  286. package/src/ui/statusline.mjs +1 -6
  287. package/src/ui/streaming-markdown-heal.mjs +24 -7
  288. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  289. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  290. package/src/workflows/default/WORKFLOW.md +9 -13
@@ -4,7 +4,9 @@ import { isAbsolute, join, resolve } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { isBuiltin } from 'node:module';
7
+ import { Script } from 'node:vm';
7
8
  import { cwdRelativePath, normalizeInputPath, normalizeOutputPath, resolveAgainstCwd } from './path-utils.mjs';
9
+ import { codeMask } from './bash-tool/bashism-rewrite.mjs';
8
10
 
9
11
  // Hard-block patterns live exclusively in ../shell-policy.mjs (BLOCKED_PATTERNS /
10
12
  // isBlockedCommand). This lighter detector only classifies cache invalidation
@@ -35,6 +37,14 @@ const SHELL_GLOBAL_MUTATORS = new Set([
35
37
  'dd',
36
38
  ]);
37
39
 
40
+ // Whether a backslash escapes the character after it: anything outside quotes,
41
+ // only `"` or `\` inside double quotes, never inside single quotes (`'C:\'` is
42
+ // a closed string, not an escaped quote).
43
+ function startsEscape(quote, next) {
44
+ if (quote === "'") return false;
45
+ return quote === '"' ? next === '"' || next === '\\' : true;
46
+ }
47
+
38
48
  export function shellSplitSegments(command) {
39
49
  const parts = [];
40
50
  let current = '';
@@ -47,7 +57,7 @@ export function shellSplitSegments(command) {
47
57
  escaped = false;
48
58
  continue;
49
59
  }
50
- if (ch === '\\') {
60
+ if (ch === '\\' && startsEscape(quote, command[i + 1])) {
51
61
  current += ch;
52
62
  escaped = true;
53
63
  continue;
@@ -83,20 +93,24 @@ export function shellTokenize(segment) {
83
93
  const tokens = [];
84
94
  let current = '';
85
95
  let quote = null;
86
- let escaped = false;
87
96
  const push = () => {
88
97
  if (current !== '') tokens.push(current);
89
98
  current = '';
90
99
  };
91
100
  for (let i = 0; i < segment.length; i++) {
92
101
  const ch = segment[i];
93
- if (escaped) {
94
- current += ch;
95
- escaped = false;
96
- continue;
97
- }
98
- if (ch === '\\') {
99
- escaped = true;
102
+ if (ch === '\\' && quote !== "'") {
103
+ // A backslash escapes only what a shell would need it for. Before an
104
+ // ordinary character it is a path separator (`C:\Users\x`, `\\server\s`),
105
+ // which dropping would turn into a different, nonexistent path.
106
+ const next = segment[i + 1];
107
+ const escapes = quote === '"' ? next === '"' : next !== undefined && /[\s"'$`;&|<>()*?]/.test(next);
108
+ if (!escapes) {
109
+ current += ch;
110
+ continue;
111
+ }
112
+ current += next;
113
+ i++;
100
114
  continue;
101
115
  }
102
116
  if (quote) {
@@ -228,7 +242,7 @@ export function shellSplitPipelineSegments(segment) {
228
242
  escaped = false;
229
243
  continue;
230
244
  }
231
- if (ch === '\\') {
245
+ if (ch === '\\' && startsEscape(quote, segment[i + 1])) {
232
246
  current += ch;
233
247
  escaped = true;
234
248
  continue;
@@ -518,6 +532,17 @@ const MSYS_ABS_PATH_RE = /(^|[\s"'=,(])\/([A-Za-z])\/([^\s"'`|&;<>()]*)/g;
518
532
  // the drive-rewrite and bash-ism regexes against the mask so quoted literals /
519
533
  // regex patterns are never treated as real path tokens or connectors.
520
534
  function maskQuotedRegions(command) {
535
+ // The full PowerShell tokeniser first: it knows here-strings, comments and
536
+ // doubled quotes, which a bare quote-pair scan reads wrongly (an apostrophe
537
+ // inside `@'…'@` or a `# don't` comment flips every region after it). It
538
+ // declines on text it cannot tokenise with confidence; the pair scan below
539
+ // then keeps the previous behaviour.
540
+ const codeOnly = codeMask(command);
541
+ if (codeOnly) {
542
+ let masked = '';
543
+ for (let i = 0; i < command.length; i++) masked += codeOnly[i] ? command[i] : '\0';
544
+ return masked;
545
+ }
521
546
  const FILL = '\0';
522
547
  let out = '';
523
548
  let quote = null;
@@ -655,6 +680,12 @@ export function hasPowerShellOnlySyntax(command) {
655
680
  const INLINE_SCRIPT_RE =
656
681
  /\b(node(?:\.exe)?|python3?|py)((?:\s+--?[\w-]+(?:=[^\s"']+)?)*)\s+(-e|--eval|-c)\s+"((?:[^"\\]|\\.)*)"/;
657
682
  const INLINE_UNSAFE_BODY = /[$`\\]/;
683
+ // What may follow the closing quote for the run to be the same as a file run:
684
+ // end of command or a shell operator/newline. Anything else is either more
685
+ // argument text (`""` is an escaped quote in PowerShell, and adjacent quoted
686
+ // strings concatenate in bash, so the body did not end at that quote) or script
687
+ // arguments, which `-e` and a file run number differently in process.argv.
688
+ const INLINE_TRAILING_OK = /^[ \t]*(?:$|[\r\n|;&)<>]|\d+>)/;
658
689
  const INLINE_FILE_RELATIVE =
659
690
  /(?:require|import)\s*\(\s*['"]\.{1,2}\/|from\s+['"]\.{1,2}\/|import\.meta|__dirname|__filename|__file__|require\.main|module\.(?:filename|parent)|sys\.argv\s*\[\s*0\s*\]|sys\.path\s*\[\s*0\s*\]|argv\s*\[\s*1\s*\]/;
660
691
  const WINDOWS_INLINE_COMMAND_FILE_THRESHOLD = 24_000;
@@ -695,21 +726,143 @@ function inlineScriptFileShape(exe, rawFlags) {
695
726
  return { extension, keptFlags: flags.replace(/\s+--input-type=\w+/g, '') };
696
727
  }
697
728
 
698
- export function planInlineScriptHoist(command) {
729
+ // `python -c` puts the working directory first on sys.path; a script file puts
730
+ // its own (temp) directory there. Only a body whose every import is a standard
731
+ // library module resolves identically from both, so any other import — a
732
+ // project-local module, a relative import, a dynamic import — keeps `-c`.
733
+ const PYTHON_STDLIB = new Set(
734
+ `__future__ abc argparse array ast asyncio atexit base64 bdb binascii bisect builtins bz2 cProfile calendar cmath cmd code codecs codeop collections colorsys compileall concurrent configparser contextlib contextvars copy copyreg csv ctypes curses dataclasses datetime dbm decimal difflib dis doctest email encodings ensurepip enum errno faulthandler fcntl filecmp fileinput fnmatch fractions ftplib functools gc genericpath getopt getpass gettext glob graphlib grp gzip hashlib heapq hmac html http imaplib inspect io ipaddress itertools json keyword linecache locale logging lzma mailbox marshal math mimetypes mmap modulefinder msvcrt multiprocessing netrc nt ntpath numbers opcode operator optparse os pathlib pdb pickle pickletools pkgutil platform plistlib poplib posix posixpath pprint profile pstats pty pwd py_compile pyclbr pydoc pyexpat queue quopri random re readline reprlib resource rlcompleter runpy sched secrets select selectors shelve shlex shutil signal site smtplib socket socketserver sqlite3 ssl stat statistics string stringprep struct subprocess symtable sys sysconfig syslog tabnanny tarfile tempfile termios textwrap threading time timeit tkinter token tokenize tomllib trace traceback tracemalloc tty turtle types typing unicodedata unittest urllib uuid venv warnings wave weakref webbrowser winreg winsound wsgiref xml xmlrpc zipapp zipfile zipimport zlib zoneinfo`.split(
735
+ ' '
736
+ )
737
+ );
738
+ const PYTHON_DYNAMIC_IMPORT = /\b(?:__import__|importlib|exec|eval|compile|runpy)\b/;
739
+ function pythonImportsOutsideStdlib(body) {
740
+ if (PYTHON_DYNAMIC_IMPORT.test(body)) return true;
741
+ for (const [, module] of body.matchAll(/(?:^|[;:\n])[ \t]*from[ \t]+([.\w]+)[ \t]+import\b/g)) {
742
+ if (module.startsWith('.') || !PYTHON_STDLIB.has(module.split('.')[0])) return true;
743
+ }
744
+ for (const [, list] of body.matchAll(/(?:^|[;:\n])[ \t]*import[ \t]+([^;\n]+)/g)) {
745
+ for (const part of list.split(',')) {
746
+ const name = part
747
+ .trim()
748
+ .split(/\s+as\s+/)[0]
749
+ .trim()
750
+ .split('.')[0];
751
+ if (name && !PYTHON_STDLIB.has(name)) return true;
752
+ }
753
+ }
754
+ return false;
755
+ }
756
+
757
+ // `node -e` accepts ESM syntax and top-level `await` (Node's syntax detection);
758
+ // the `.cjs` file a hoist writes does not, so a body using either stays inline.
759
+ // An `await` is top-level exactly when the body no longer compiles as CommonJS.
760
+ const NODE_ESM_SYNTAX = /(?:^|[;{}\n])[ \t]*(?:import[ \t]*[\w$*{'"]|export[ \t])/;
761
+ function nodeBodyNeedsModuleSyntax(body) {
762
+ if (NODE_ESM_SYNTAX.test(body)) return true;
763
+ if (!/\bawait\b/.test(body)) return false;
764
+ try {
765
+ new Script(`(function (exports, require, module, __filename, __dirname) {${body}\n})`);
766
+ return false;
767
+ } catch {
768
+ return true;
769
+ }
770
+ }
771
+
772
+ // PowerShell ends a double-quoted string at any typographic double quote and a
773
+ // single-quoted one at any typographic single quote.
774
+ const POWERSHELL_SMART_DOUBLE = /[\u201C\u201D\u201E\u201F]/;
775
+ const POWERSHELL_SMART_SINGLE = /[\u2018\u2019\u201A\u201B]/;
776
+
777
+ function defaultInlineShellType() {
778
+ return process.platform === 'win32' ? 'powershell' : 'posix';
779
+ }
780
+
781
+ // True when `index` is in live shell code of a POSIX shell: not inside a
782
+ // quote, `$'…'`, comment, or a heredoc body (any heredoc earlier in the text
783
+ // declines, since its body cannot be bounded here).
784
+ function posixLiveAt(text, index) {
785
+ let i = 0;
786
+ const skipTo = (end) => {
787
+ if (end < 0 || end >= index) return false;
788
+ i = end + 1;
789
+ return true;
790
+ };
791
+ // The quote that closes a region whose backslashes escape (`"…"`, `$'…'`).
792
+ const closer = (from, quote) => {
793
+ let j = from;
794
+ while (j < text.length && text[j] !== quote) j += text[j] === '\\' ? 2 : 1;
795
+ return j < text.length ? j : -1;
796
+ };
797
+ while (i < index) {
798
+ const ch = text[i];
799
+ if (ch === '\\') i += 2;
800
+ else if (ch === "'") {
801
+ if (!skipTo(text.indexOf("'", i + 1))) return false;
802
+ } else if (ch === '$' && text[i + 1] === "'") {
803
+ if (!skipTo(closer(i + 2, "'"))) return false;
804
+ } else if (ch === '"') {
805
+ if (!skipTo(closer(i + 1, '"'))) return false;
806
+ } else if (ch === '#' && (i === 0 || /[\s;&|(]/.test(text[i - 1]))) {
807
+ const nl = text.indexOf('\n', i);
808
+ if (nl < 0 || nl >= index) return false;
809
+ i = nl;
810
+ } else if (ch === '<' && text[i + 1] === '<' && text[i + 2] !== '<') return false;
811
+ else i++;
812
+ }
813
+ return true;
814
+ }
815
+
816
+ // A node/python invocation may be turned into a file run only when the shell
817
+ // itself runs it: live code (not text inside a string, here-string, comment or
818
+ // heredoc) at command position (not an argument of docker/ssh/wsl/ts-node/…,
819
+ // which would run the file somewhere that cannot see it).
820
+ function isShellInvocationAt(text, index, shellType) {
821
+ if (shellType === 'powershell') {
822
+ if (!codeMask(text)?.[index]) return false;
823
+ } else if (!posixLiveAt(text, index)) {
824
+ return false;
825
+ }
826
+ let start = index;
827
+ while (start > 0 && /[^\s;|&(){}<>'"]/.test(text[start - 1])) start--;
828
+ if (start < index && !/[\\/]$/.test(text.slice(start, index))) return false;
829
+ const before = text.slice(0, start);
830
+ const assignments = /(?:^|[\s;|&(){}])[A-Za-z_]\w*=[^\s;|&(){}]*[ \t]+$/;
831
+ let head = before;
832
+ while (assignments.test(head)) head = head.replace(assignments, (m) => m.match(/^[\s;|&(){}]?/)[0]);
833
+ const trimmed = head.replace(/[ \t]+$/, '');
834
+ if (trimmed === '') return true;
835
+ const last = trimmed.at(-1);
836
+ if (!';|&({\n'.includes(last)) return false;
837
+ // A line continuation joins the next line onto this command's arguments.
838
+ return !(last === '\n' && /[\\`]\r?\n$/.test(trimmed));
839
+ }
840
+
841
+ export function planInlineScriptHoist(command, { shellType = defaultInlineShellType() } = {}) {
699
842
  const text = String(command || '');
700
843
  if (!text.trim()) return null;
701
844
  const match = text.match(INLINE_SCRIPT_RE);
702
845
  if (!match) return null;
703
846
  const [whole, exe, rawFlags, , body] = match;
704
847
  if (INLINE_UNSAFE_BODY.test(body)) return null;
848
+ if (!INLINE_TRAILING_OK.test(text.slice(match.index + whole.length))) return null;
705
849
  if (INLINE_FILE_RELATIVE.test(body)) return null;
706
- if (/^node/i.test(exe) && importsPackage(body)) return null;
850
+ if (shellType === 'powershell' && POWERSHELL_SMART_DOUBLE.test(body)) return null;
851
+ const isNode = /^node/i.test(exe);
852
+ if (isNode && importsPackage(body)) return null;
853
+ if (isNode && !/--input-type=module/.test(rawFlags) && nodeBodyNeedsModuleSyntax(body)) return null;
854
+ if (!isNode && pythonImportsOutsideStdlib(body)) return null;
855
+ if (!isShellInvocationAt(text, match.index, shellType)) return null;
707
856
  const { extension, keptFlags } = inlineScriptFileShape(exe, rawFlags);
708
857
  return {
709
858
  exe,
710
859
  extension,
711
860
  body,
712
- replace: (filePath) => text.replace(whole, `${exe}${keptFlags} "${filePath}"`),
861
+ // Spliced at the match offset, not by searching for its text: an earlier
862
+ // look-alike (`xnode -e "…"`) must not be the one rewritten. Plain
863
+ // concatenation also keeps a path holding `$&` / `$1` from being expanded.
864
+ replace: (filePath) =>
865
+ `${text.slice(0, match.index)}${exe}${keptFlags} "${filePath}"${text.slice(match.index + whole.length)}`,
713
866
  };
714
867
  }
715
868
 
@@ -727,11 +880,26 @@ export function planLongInlineScriptFileTransport(
727
880
  const match = text.match(LONG_INLINE_SCRIPT_RE);
728
881
  if (!match) return null;
729
882
  const [whole, exe, rawFlags, , doubleBody, singleBody] = match;
883
+ const powershell = String(shellType || '').toLowerCase() === 'powershell';
884
+ // The same refusals as the short hoist: text after the closing quote means
885
+ // the body did not end there (or the script has arguments), and the match
886
+ // must be a command the shell itself runs.
887
+ if (!INLINE_TRAILING_OK.test(text.slice(match.index + whole.length))) return null;
888
+ if (!isShellInvocationAt(text, match.index, powershell ? 'powershell' : 'posix')) return null;
730
889
  const { extension, keptFlags } = inlineScriptFileShape(exe, rawFlags);
731
890
  let body = doubleBody;
732
891
  if (body === undefined) {
733
892
  body = String(singleBody || '');
734
- body = String(shellType || '').toLowerCase() === 'powershell' ? body.replace(/''/g, "'") : body.replace(/''/g, '');
893
+ if (powershell && POWERSHELL_SMART_SINGLE.test(body)) return null;
894
+ body = powershell ? body.replace(/''/g, "'") : body.replace(/''/g, '');
895
+ } else if (
896
+ // Inside double quotes the shell rewrites `$…`, backtick and backslash
897
+ // escapes (and PowerShell ends the string at a typographic quote) before
898
+ // the child sees them, so the verbatim body would be a different script.
899
+ /[$`]|\\"/.test(body) ||
900
+ (powershell ? POWERSHELL_SMART_DOUBLE.test(body) : body.includes('\\\\'))
901
+ ) {
902
+ return null;
735
903
  }
736
904
  return {
737
905
  command: text,
@@ -739,7 +907,7 @@ export function planLongInlineScriptFileTransport(
739
907
  body,
740
908
  replace(filePath) {
741
909
  const normalized = String(filePath || '').replace(/\\/g, '/');
742
- return text.replace(whole, `${exe}${keptFlags} "${normalized}"`);
910
+ return `${text.slice(0, match.index)}${exe}${keptFlags} "${normalized}"${text.slice(match.index + whole.length)}`;
743
911
  },
744
912
  };
745
913
  }
@@ -769,7 +937,11 @@ export function planLongShellScriptFileTransport(
769
937
  const escaped = String(filePath || '')
770
938
  .replace(/\\/g, '/')
771
939
  .replace(/'/g, "''");
772
- return `& '${escaped}'`;
940
+ // Evaluated in the host's own scope, not called as a script: `& file.ps1`
941
+ // turns `exit N` into exit code 1 and a failing last command into 0,
942
+ // where the command itself would have ended with N and 1. The trailing
943
+ // statement reads the last command's `$?` the way -Command does.
944
+ return `Invoke-Expression ([System.IO.File]::ReadAllText('${escaped}', [System.Text.Encoding]::UTF8) + "\`n" + '$global:__mixdogLastOk = $?'); if ($global:__mixdogLastOk -eq $false) { exit 1 }`;
773
945
  },
774
946
  };
775
947
  }
@@ -937,9 +1109,8 @@ export async function analyzeShellCommandEffects(command, cwd) {
937
1109
  const sub = String(tokens[1] || '').toLowerCase();
938
1110
  if (['status', 'diff', 'show', 'log', 'rev-parse', 'branch', 'remote', 'ls-files'].includes(sub)) continue;
939
1111
  }
940
- if (cmd === 'python' || cmd === 'python3') {
941
- if (!(tokens[1] === '-m' && tokens[2] === 'pip' && /^install$/i.test(tokens[3] || ''))) continue;
942
- }
1112
+ // A python run can write anywhere (alone it already classifies as global
1113
+ // below the read-only check), so it never narrows a chain's effects.
943
1114
  global = true;
944
1115
  continue;
945
1116
  }
@@ -1054,6 +1225,9 @@ export function buildPowerShellFilterTeePlan(command) {
1054
1225
  const trimmed = cmd.trimEnd();
1055
1226
  if (!trimmed.endsWith(last)) return null;
1056
1227
  if (!_groupingBalanced(last) || !_groupingBalanced(trimmed.slice(0, trimmed.length - last.length))) return null;
1228
+ // Behind `&&` / `||` the rewrite's leading assignment is not a pipeline (a
1229
+ // parse error), and it would run the producer even when `||` skips it.
1230
+ if (/(?:&&|\|\|)\s*$/.test(trimmed.slice(0, trimmed.length - last.length))) return null;
1057
1231
  const stages = shellSplitPipelineSegments(last);
1058
1232
  if (stages.length < 2) return null;
1059
1233
  const tokenized = [];
@@ -23,6 +23,7 @@ const POWERSHELL_RESERVED_HEADS = new Set([
23
23
  'dir',
24
24
  'echo',
25
25
  'erase',
26
+ 'fc',
26
27
  'foreach',
27
28
  'help',
28
29
  'history',
@@ -110,12 +111,19 @@ function _disabledByEnv(env) {
110
111
 
111
112
  // Quote-aware argv split for native CreateProcess. Unlike shellTokenize this
112
113
  // keeps Windows backslashes literal so `C:\foo\bar.exe` is one path token.
113
- export function tokenizeDirectArgv(command) {
114
+ // `strict` is for callers that must reproduce PowerShell's own parse (the
115
+ // direct spawn): a form where PowerShell and this splitter could disagree —
116
+ // a `\"` that PowerShell does not treat as an escape, or text glued to the
117
+ // closing quote of a string that opened its argument (PowerShell starts a new
118
+ // argument there) — returns null so the command runs through the shell
119
+ // unchanged.
120
+ export function tokenizeDirectArgv(command, { strict = false } = {}) {
114
121
  const text = String(command || '');
115
122
  const tokens = [];
116
123
  let current = '';
117
124
  let quote = null;
118
125
  let started = false;
126
+ let leading = false;
119
127
  const push = () => {
120
128
  if (started) tokens.push(current);
121
129
  current = '';
@@ -125,12 +133,20 @@ export function tokenizeDirectArgv(command) {
125
133
  const ch = text[i];
126
134
  if (quote === '"') {
127
135
  if (ch === '\\' && text[i + 1] === '"') {
136
+ if (strict) return null;
128
137
  current += '"';
129
138
  i += 1;
130
139
  continue;
131
140
  }
132
141
  if (ch === '"') {
142
+ // PowerShell: a doubled quote inside a double-quoted string is a literal quote.
143
+ if (text[i + 1] === '"') {
144
+ current += '"';
145
+ i += 1;
146
+ continue;
147
+ }
133
148
  quote = null;
149
+ if (strict && leading && i + 1 < text.length && !/\s/.test(text[i + 1])) return null;
134
150
  continue;
135
151
  }
136
152
  current += ch;
@@ -138,13 +154,24 @@ export function tokenizeDirectArgv(command) {
138
154
  }
139
155
  if (quote === "'") {
140
156
  if (ch === "'") {
157
+ // PowerShell: a doubled quote inside a single-quoted string is a literal quote.
158
+ if (text[i + 1] === "'") {
159
+ current += "'";
160
+ i += 1;
161
+ continue;
162
+ }
141
163
  quote = null;
164
+ if (strict && leading && i + 1 < text.length && !/\s/.test(text[i + 1])) return null;
142
165
  continue;
143
166
  }
144
167
  current += ch;
145
168
  continue;
146
169
  }
147
170
  if (ch === "'" || ch === '"') {
171
+ // A string that opens an argument ends it: PowerShell starts a NEW
172
+ // argument at whatever is glued behind the closing quote. A string
173
+ // opened mid-argument (`a"b c"d`) joins its neighbours.
174
+ leading = !started;
148
175
  quote = ch;
149
176
  started = true;
150
177
  continue;
@@ -185,6 +212,9 @@ export function commandNeedsShell(command) {
185
212
  continue;
186
213
  }
187
214
  if ('|&;<>()\n\r{}'.includes(ch) || ch === '`') return true;
215
+ // A bare `~` expands to the home directory and `@name` splats, both before
216
+ // the native program sees the argument.
217
+ if ((ch === '~' || ch === '@') && (i === 0 || /\s/.test(text[i - 1]))) return true;
188
218
  if ('*?[]'.includes(ch)) return true;
189
219
  if (ch === '$') return true;
190
220
  if (ch === '#' && (i === 0 || /\s/.test(text[i - 1]))) return true;
@@ -192,6 +222,14 @@ export function commandNeedsShell(command) {
192
222
  return quote != null;
193
223
  }
194
224
 
225
+ function _isFile(file) {
226
+ try {
227
+ return statSync(file).isFile();
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
195
233
  function _isUsableExe(file) {
196
234
  try {
197
235
  if (!/\.exe$/i.test(file)) return false;
@@ -215,17 +253,29 @@ export function resolveDirectExe(
215
253
  const resolved = isAbsolute(name) || /^[A-Za-z]:[\\/]/.test(name) ? name : pathResolve(baseCwd, name);
216
254
  return _isUsableExe(resolved) ? resolved : null;
217
255
  }
218
- const fileName = /\.exe$/i.test(name) ? name : `${name}.exe`;
256
+ const explicitExe = /\.exe$/i.test(name);
219
257
  const seen = new Set();
220
- const dirs = [baseCwd, ...String(pathValue || '').split(pathDelimiter)];
221
- for (const dir of dirs) {
258
+ // PowerShell never looks in the current directory for a bare command name,
259
+ // so neither may this resolver: a project-local `tool.exe` is not `tool`.
260
+ for (const dir of String(pathValue || '').split(pathDelimiter)) {
222
261
  if (!dir) continue;
223
262
  const key = dir.toLowerCase();
224
263
  if (seen.has(key)) continue;
225
264
  seen.add(key);
226
- if (seen.size > maxDirectories + 1) break;
227
- const candidate = pathJoin(dir, fileName);
265
+ if (seen.size > maxDirectories) break;
266
+ if (explicitExe) {
267
+ const candidate = pathJoin(dir, name);
268
+ if (_isUsableExe(candidate)) return candidate;
269
+ continue;
270
+ }
271
+ // PowerShell takes the first directory holding ANY runnable form of the
272
+ // name, and a `.com`/`.ps1` there outranks the `.exe` while a `.cmd`/`.bat`
273
+ // shim in an earlier directory shadows a later `.exe`: all stay with the shell.
274
+ const has = (ext) => _isFile(pathJoin(dir, `${name}${ext}`));
275
+ if (has('.com') || has('.ps1')) return null;
276
+ const candidate = pathJoin(dir, `${name}.exe`);
228
277
  if (_isUsableExe(candidate)) return candidate;
278
+ if (has('.bat') || has('.cmd')) return null;
229
279
  }
230
280
  return null;
231
281
  }
@@ -244,7 +294,7 @@ export function planDirectExeSpawn(
244
294
  if (_disabledByEnv(env)) return null;
245
295
  const text = String(command || '').trim();
246
296
  if (!text || commandNeedsShell(text) || hasPowerShellOnlySyntax(text)) return null;
247
- const tokens = tokenizeDirectArgv(text);
297
+ const tokens = tokenizeDirectArgv(text, { strict: true });
248
298
  if (!tokens || tokens.length === 0) return null;
249
299
  if (tokens.includes('--%')) return null;
250
300
  const head = tokens[0];