mixdog 1.2.5 → 1.3.0

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 (493) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +19 -0
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/gate-scheduler.mjs +2 -2
  7. package/scripts/lib/isolated-test-env.mjs +16 -0
  8. package/scripts/lib/run-node-tests.mjs +5 -14
  9. package/scripts/provider-pingpong-live.mjs +9 -9
  10. package/src/agents/advisor/AGENT.md +4 -4
  11. package/src/agents/advisor/agent.json +1 -1
  12. package/src/agents/reviewer/AGENT.md +5 -4
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/defaults/shipped-history.json +17 -0
  15. package/src/defaults/skills/browser-use/SKILL.md +7 -1
  16. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  17. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  18. package/src/defaults/skills/setup/references/actions.md +11 -4
  19. package/src/defaults/skills/setup-guide/SKILL.md +175 -0
  20. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  21. package/src/defaults/skills/visualize/SKILL.md +3 -2
  22. package/src/lib/rules-builder.cjs +56 -7
  23. package/src/output-styles/common.md +22 -6
  24. package/src/output-styles/detailed.md +7 -5
  25. package/src/output-styles/minimal.md +7 -3
  26. package/src/output-styles/simple.md +2 -3
  27. package/src/rules/agent/AGENT.md +7 -1
  28. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  29. package/src/rules/agent-mode/MIXY.md +21 -0
  30. package/src/rules/agent-mode/ROUTINE.md +6 -0
  31. package/src/rules/lead/LEAD.md +9 -4
  32. package/src/rules/shared/10-tool-workflow.md +2 -1
  33. package/src/rules/shared/60-verification.md +4 -5
  34. package/src/rules/shared/70-delivery.md +3 -0
  35. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  37. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  39. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +182 -13
  40. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +5 -3
  41. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  42. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -0
  43. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +58 -64
  44. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  45. package/src/runtime/agent/orchestrator/mcp/client.mjs +122 -15
  46. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  49. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  50. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  51. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  53. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +41 -19
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  62. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +11 -5
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  65. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  66. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  69. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  70. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  72. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  80. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  83. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  84. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  86. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  88. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  92. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  94. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  95. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  96. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  97. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  98. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  99. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  100. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +98 -4
  101. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  102. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  103. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +15 -8
  104. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -4
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
  109. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -20
  110. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  111. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  112. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  113. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  114. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  115. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  116. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  117. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +162 -0
  118. package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -0
  119. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  120. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  121. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  122. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  123. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  124. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  125. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  126. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +165 -0
  127. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  128. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  129. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  130. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  131. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  132. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  133. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  134. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  135. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -1
  136. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +82 -33
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +11 -4
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  140. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +7 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +6 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  146. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  147. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  148. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  149. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  150. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  151. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  152. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  153. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  154. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -5
  155. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -8
  156. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  157. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  158. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  159. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +24 -0
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  163. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -0
  166. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  167. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  168. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  169. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  171. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  173. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  174. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  175. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
  176. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +325 -0
  177. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +84 -21
  178. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  179. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  180. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  181. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  182. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  183. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  184. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  185. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  186. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +44 -1
  189. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  190. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  191. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  197. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  198. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  199. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  200. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  201. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  203. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  207. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  208. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  209. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  210. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  211. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  212. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  213. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  214. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  215. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  216. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  217. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  218. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  219. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  220. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  221. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +123 -32
  222. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  224. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  225. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  226. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  227. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  228. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  229. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  230. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  231. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  232. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +64 -2
  233. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +10 -2
  234. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  235. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +8 -0
  236. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -1
  237. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  238. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +104 -55
  239. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  240. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  241. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  242. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  243. package/src/runtime/attachments/office-extract.mjs +11 -9
  244. package/src/runtime/browser-bridge/client.mjs +24 -0
  245. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  246. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  247. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  248. package/src/runtime/channels/lib/webhook.mjs +0 -1
  249. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  250. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  251. package/src/runtime/computer-bridge/client.mjs +28 -1
  252. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  253. package/src/runtime/connectors/google-mcp.mjs +446 -0
  254. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  255. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  256. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  257. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  258. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  259. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  260. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  261. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  262. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  263. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  264. package/src/runtime/memory/lib/memory.mjs +7 -1
  265. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  266. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  268. package/src/runtime/memory/lib/tool-call-handler.mjs +9 -5
  269. package/src/runtime/memory/tool-defs.mjs +5 -0
  270. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  271. package/src/runtime/office/core/tabular.mjs +1 -1
  272. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  273. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  274. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  275. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  276. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  277. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  278. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  279. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  280. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  281. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  282. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  283. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  284. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  285. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  286. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  287. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  288. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  289. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  290. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  291. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  292. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  293. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  294. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  295. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  296. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  297. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  298. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  299. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  300. package/src/runtime/shared/agent-avatar.mjs +320 -0
  301. package/src/runtime/shared/agent-route-config.d.mts +3 -0
  302. package/src/runtime/shared/agent-route-config.mjs +16 -0
  303. package/src/runtime/shared/atomic-file.mjs +36 -14
  304. package/src/runtime/shared/background-tasks.mjs +60 -20
  305. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  306. package/src/runtime/shared/config.mjs +8 -11
  307. package/src/runtime/shared/connector-accounts.mjs +15 -0
  308. package/src/runtime/shared/connector-catalog.d.mts +52 -0
  309. package/src/runtime/shared/connector-catalog.mjs +230 -0
  310. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  311. package/src/runtime/shared/connector-triggers.mjs +43 -0
  312. package/src/runtime/shared/file-lock.mjs +7 -1
  313. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  314. package/src/runtime/shared/file-permissions.d.mts +19 -4
  315. package/src/runtime/shared/file-permissions.mjs +310 -4
  316. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  317. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  318. package/src/runtime/shared/held-request-host.d.mts +12 -0
  319. package/src/runtime/shared/held-request-host.mjs +71 -0
  320. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  321. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  322. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  323. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  324. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  325. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  326. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  327. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  328. package/src/runtime/shared/notify-trace.mjs +8 -1
  329. package/src/runtime/shared/plain-preview.d.mts +1 -0
  330. package/src/runtime/shared/plain-preview.mjs +28 -0
  331. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  332. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  333. package/src/runtime/shared/process-runner.mjs +38 -0
  334. package/src/runtime/shared/provider-accounts.mjs +2 -1
  335. package/src/runtime/shared/provider-api-key.mjs +0 -1
  336. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  337. package/src/runtime/shared/resource-admission.mjs +60 -18
  338. package/src/runtime/shared/routine-limits.d.mts +11 -0
  339. package/src/runtime/shared/routine-limits.mjs +21 -0
  340. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  341. package/src/runtime/shared/routine-trigger.mjs +44 -0
  342. package/src/runtime/shared/run-child.mjs +84 -0
  343. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  344. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  345. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  346. package/src/runtime/shared/singleton-owner.mjs +126 -42
  347. package/src/runtime/shared/slow-span.mjs +4 -12
  348. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  349. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  350. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  351. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  352. package/src/runtime/shared/turn-changes.mjs +0 -8
  353. package/src/runtime/shared/usage-readout-totals.mjs +56 -0
  354. package/src/runtime/shared/user-data-guard.mjs +131 -34
  355. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  356. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  357. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  358. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  359. package/src/runtime/tidy/runners/biome.mjs +3 -1
  360. package/src/runtime/tidy/shadow.mjs +2 -1
  361. package/src/runtime/web-search/index.mjs +5 -7
  362. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  363. package/src/runtime/web-search/tool-defs.mjs +6 -1
  364. package/src/session-runtime/agent-home-delivery.mjs +459 -0
  365. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  366. package/src/session-runtime/agent-room-delivery.mjs +92 -0
  367. package/src/session-runtime/agent-task-sessions.mjs +200 -0
  368. package/src/session-runtime/boot/apis.mjs +50 -18
  369. package/src/session-runtime/boot/facade.mjs +5 -8
  370. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  371. package/src/session-runtime/boot/tools.mjs +3 -0
  372. package/src/session-runtime/channel-config-api.mjs +53 -5
  373. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  374. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  375. package/src/session-runtime/held-requests.mjs +228 -0
  376. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  377. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  378. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  379. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +31 -2
  380. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  381. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  382. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  383. package/src/session-runtime/lifecycle/session-switch/resume.mjs +11 -4
  384. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  385. package/src/session-runtime/lifecycle/shared.mjs +25 -2
  386. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  387. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  388. package/src/session-runtime/lifecycle-api.mjs +3 -1
  389. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  390. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  391. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  392. package/src/session-runtime/model-route/set-route.mjs +14 -3
  393. package/src/session-runtime/owned-automation-run.mjs +229 -0
  394. package/src/session-runtime/provider-auth/login.mjs +10 -6
  395. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  396. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  397. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  398. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  399. package/src/session-runtime/provider-models.mjs +76 -10
  400. package/src/session-runtime/provider-readiness.mjs +4 -0
  401. package/src/session-runtime/resource-api.mjs +5 -0
  402. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  403. package/src/session-runtime/resource-skills-api.mjs +12 -0
  404. package/src/session-runtime/room-member-gate.mjs +48 -0
  405. package/src/session-runtime/routine-home-turn.mjs +156 -0
  406. package/src/session-runtime/routine-owner.mjs +48 -0
  407. package/src/session-runtime/schedule-session-run.mjs +30 -64
  408. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  409. package/src/session-runtime/services/agent-tool/helpers.mjs +10 -0
  410. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  411. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  412. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  413. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  414. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  415. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  416. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  417. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  418. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  419. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  420. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  422. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  423. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  424. package/src/session-runtime/services/agent-tool.mjs +20 -0
  425. package/src/session-runtime/services/channel-admin.mjs +93 -24
  426. package/src/session-runtime/services/memory-access.mjs +37 -0
  427. package/src/session-runtime/services/provider-admin.mjs +166 -59
  428. package/src/session-runtime/services/schedule-access.mjs +91 -0
  429. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  430. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  431. package/src/session-runtime/session-create/session-options.mjs +34 -7
  432. package/src/session-runtime/session-hooks.mjs +16 -8
  433. package/src/session-runtime/setup-tool/executor.mjs +68 -6
  434. package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
  435. package/src/session-runtime/setup-tool/settings-contract.mjs +8 -6
  436. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  437. package/src/session-runtime/skills-api.mjs +16 -1
  438. package/src/session-runtime/tool-policy-refresh.mjs +238 -29
  439. package/src/session-runtime/turn/session-ops.mjs +10 -3
  440. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +11 -2
  441. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +3 -0
  442. package/src/session-runtime/usage-stats-api.mjs +6 -1
  443. package/src/session-runtime/webhook-session-run.mjs +49 -67
  444. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  445. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +556 -22
  446. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  447. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  448. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  449. package/src/standalone/daemon-agent-control.mjs +1 -8
  450. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  451. package/src/standalone/daemon-owner.mjs +117 -25
  452. package/src/standalone/daemon-session-control.mjs +11 -2
  453. package/src/standalone/daemon.mjs +61 -17
  454. package/src/standalone/session-call-cache.mjs +6 -0
  455. package/src/standalone/session-protocol.mjs +28 -1
  456. package/src/standalone/session-service/retention/busy.mjs +7 -2
  457. package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
  458. package/src/standalone/session-service.mjs +19 -0
  459. package/src/standalone/session-transport/routes.mjs +7 -6
  460. package/src/tui/app/model-options.mjs +39 -2
  461. package/src/tui/app/model-picker.mjs +16 -2
  462. package/src/tui/app/provider-setup-picker.mjs +1 -2
  463. package/src/tui/app/slash-commands.mjs +20 -0
  464. package/src/tui/dist/index.mjs +487 -128
  465. package/src/tui/session/context-state.mjs +6 -1
  466. package/src/tui/session/notice-surface.mjs +35 -4
  467. package/src/tui/session/notice-text.mjs +0 -1
  468. package/src/tui/session/queue-helpers.mjs +2 -1
  469. package/src/tui/session/session-api/extensions.mjs +4 -0
  470. package/src/tui/session/session-api/intake/submission.mjs +12 -13
  471. package/src/tui/session/session-api/integrations.mjs +9 -5
  472. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  473. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  474. package/src/tui/session/session-api/routes.mjs +48 -3
  475. package/src/tui/session/session-flow/drain.mjs +20 -3
  476. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  477. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  478. package/src/tui/session/session-flow/steering.mjs +104 -24
  479. package/src/tui/session/session-flow.mjs +2 -0
  480. package/src/tui/session/tool-approval.mjs +26 -5
  481. package/src/tui/session/transcript-intake.mjs +6 -2
  482. package/src/tui/session/transcript-spill.mjs +5 -1
  483. package/src/tui/session/tui-steering-persist.mjs +65 -42
  484. package/src/tui/session/turn-live-call.mjs +56 -0
  485. package/src/tui/session/turn-stream.mjs +33 -0
  486. package/src/tui/session/turn.mjs +50 -7
  487. package/src/ui/statusline.mjs +1 -6
  488. package/src/ui/streaming-markdown-heal.mjs +24 -7
  489. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  490. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  491. package/src/workflows/default/WORKFLOW.md +9 -13
  492. package/src/session-runtime/automation-agents.mjs +0 -94
  493. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -9,6 +9,7 @@ import { readFile } from 'node:fs/promises';
9
9
  import { sameFileVersion } from './file-version.mjs';
10
10
  import { canCacheReadFile, READ_PREFIX_HASH_BYTES } from './read-cache-policy.mjs';
11
11
  import { decodeUtf16Body, isUtf16Encoding } from './snapshot-helpers.mjs';
12
+ import { capMarker, windowMarker } from './tool-output-limit.mjs';
12
13
 
13
14
  /**
14
15
  * Encoding-aware decode (fresh read AND raw-content cache hit both flow
@@ -51,17 +52,27 @@ function rangeFooter(ctx, render, rendered) {
51
52
  const emittedStart = offset + 1;
52
53
  if (byteCapTruncated) {
53
54
  const emittedEnd = offset + renderedLineCount;
54
- const capKb = Math.round(readMaxOutputBytes / 1024);
55
- const more = emittedEnd < lineCount ? `; pass offset:${emittedEnd + readOffsetBase} to continue` : '';
56
- return { footer: `[lines ${emittedStart}-${emittedEnd} of ${lineCount}; output truncated at ${capKb} KB${more}]` };
55
+ return {
56
+ footer: windowMarker({
57
+ from: emittedStart,
58
+ to: emittedEnd,
59
+ total: lineCount,
60
+ capBytes: readMaxOutputBytes,
61
+ nextOffset: emittedEnd < lineCount ? emittedEnd + readOffsetBase : null,
62
+ }),
63
+ };
57
64
  }
58
65
  if (Buffer.byteLength(rendered, 'utf8') > readMaxOutputBytes) return {};
59
66
  const emittedEnd = offset + sliced.length;
60
67
  // Continuation uses the originating caller's coordinate base. Remaining
61
68
  // content is not automatically required evidence; when it is, it fits one
62
69
  // wider read, not a walk window by window.
63
- const more = emittedEnd < lineCount ? `; pass offset:${emittedEnd + readOffsetBase} to continue` : '';
64
- const footer = `[lines ${emittedStart}-${emittedEnd} of ${lineCount}${more}]`;
70
+ const footer = windowMarker({
71
+ from: emittedStart,
72
+ to: emittedEnd,
73
+ total: lineCount,
74
+ nextOffset: emittedEnd < lineCount ? emittedEnd + readOffsetBase : null,
75
+ });
65
76
  return { footer: widenNote ? `${footer}\n${widenNote}` : footer };
66
77
  }
67
78
 
@@ -110,8 +121,15 @@ function renderReadWindow(content, ctx, helpers) {
110
121
  const slice = byteCapSlice(rendered, readMaxOutputBytes);
111
122
  render.renderedLineCount = Math.max(0, slice.split('\n').length - 1);
112
123
  render.byteCapTruncated = true;
113
- const ofFile = st?.size > 0 ? ` of a ${Math.max(1, Math.round(st.size / 1024))} KB file` : '';
114
- out = `${slice}\n\n... [output truncated at ${Math.round(readMaxOutputBytes / 1024)} KB${ofFile}] ...`;
124
+ const ofFile = st?.size > 0 ? `${Math.max(1, Math.round(st.size / 1024))} KB file` : '';
125
+ // A windowed read's footer already carries the cap and continuation.
126
+ out = hasRangeArgs
127
+ ? slice
128
+ : `${slice}\n\n${capMarker({
129
+ limit: readMaxOutputBytes,
130
+ shown: ofFile ? `${render.renderedLineCount} of ${lineCount} lines shown (${ofFile})` : '',
131
+ nextOffset: offset + render.renderedLineCount + ctx.readOffsetBase,
132
+ })}`;
115
133
  } else {
116
134
  out = rendered;
117
135
  }
@@ -1,6 +1,7 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
2
  import { MAX_PDF_BYTES, MAX_PDF_PAGES, isPdfBuffer } from '../../../../attachments/limits.mjs';
3
3
  import { READ_MAX_SIZE_BYTES } from './read-constants.mjs';
4
+ import { capMarker } from './tool-output-limit.mjs';
4
5
  import { imageBlocksFromBuffer } from './read-image-resize.mjs';
5
6
  import { inspectPdfBuffer } from '../../../../attachments/pdf-extract.mjs';
6
7
 
@@ -190,7 +191,7 @@ function capNotebookText(blocks, maxOutputBytes) {
190
191
  if (truncated) {
191
192
  capped.push({
192
193
  type: 'text',
193
- text: `\n\n... [notebook output truncated at ${Math.round(maxOutputBytes / 1024)} KB]`,
194
+ text: `\n\n${capMarker({ limit: maxOutputBytes, shown: 'notebook cells cut', narrow: 'grep the notebook for the cell text you need' })}`,
194
195
  });
195
196
  }
196
197
  return capped;
@@ -27,7 +27,9 @@ const bodyTextFor = (value) => {
27
27
  .join('\n');
28
28
  };
29
29
 
30
- const TRUNCATED_MARKER = /\[TRUNCATED (?:—|-) (\d+) lines \/ (\d+) KB;/;
30
+ // The smart-read cap marker (read-formatting.mjs buildSmartReadTruncationMarker):
31
+ // `[output capped at X; lines a-b of N omitted (K KB file); offset:a for more]`.
32
+ const TRUNCATED_MARKER = /\[output capped at [^;\]]+; lines \d+-\d+ of (\d+) omitted \((\d+) KB file\);/;
31
33
  // When `read` emitted a smart-cap marker, surface the truncation state in the
32
34
  // header so downstream skimming spots it without parsing the body.
33
35
  const truncatedSuffix = (textBody) => {
@@ -1,4 +1,5 @@
1
1
  import { statSync } from 'node:fs';
2
+ import { capMarker, windowMarker } from './tool-output-limit.mjs';
2
3
  import { isAbsolute, resolve } from 'node:path';
3
4
  import { expandAbsoluteGlobs } from './lib/absolute-glob-expand.mjs';
4
5
  import { buildGlobPatternGroups } from './lib/glob-static-prefix.mjs';
@@ -407,17 +408,22 @@ async function orderGlobPaths(unique, { nativeMtimeTotal, groupCount, sortMode,
407
408
 
408
409
  function globIntegritySuffix({ accumTruncated, rgStdoutTruncated, rgStdoutPartial, rgErrors }) {
409
410
  let truncSuffix = '';
410
- if (accumTruncated) truncSuffix = `\n... [truncated at accumulation cap (${GLOB_ACCUM_CAP})]`;
411
- if (rgStdoutTruncated) truncSuffix += '\n... [truncated at rg stdout cap (20MB); results incomplete]';
411
+ const narrow = 'narrow the pattern or path';
412
+ if (accumTruncated) {
413
+ truncSuffix = `\n${capMarker({ limit: `${GLOB_ACCUM_CAP} matches`, shown: 'results incomplete', narrow })}`;
414
+ }
415
+ if (rgStdoutTruncated) {
416
+ truncSuffix += `\n${capMarker({ limit: '20 MB of rg stdout', shown: 'results incomplete', narrow })}`;
417
+ }
412
418
  if (rgStdoutPartial) truncSuffix += '\n... [warning] rg exit 2 (partial results); listing may be incomplete';
413
419
  const errorSuffix = rgErrors.length > 0 ? `\n... [warning] ${rgErrors.join(' | ')}` : '';
414
420
  return errorSuffix + truncSuffix;
415
421
  }
416
422
 
417
- function globMoreSuffix({ rgWindowIncomplete, remaining, totalBeforeOffset, nextOffset }) {
418
- if (rgWindowIncomplete) return `\n... [more entries available — pass offset:${nextOffset} to continue]`;
423
+ function globMoreSuffix({ rgWindowIncomplete, remaining, totalBeforeOffset, from, nextOffset }) {
424
+ if (rgWindowIncomplete) return `\n${windowMarker({ unit: 'entries', from, to: nextOffset, nextOffset })}`;
419
425
  if (remaining > 0) {
420
- return `\n... [${remaining} more entries of ${totalBeforeOffset} total — pass offset:${nextOffset} to continue]`;
426
+ return `\n${windowMarker({ unit: 'entries', from, to: nextOffset, total: totalBeforeOffset, nextOffset })}`;
421
427
  }
422
428
  return '';
423
429
  }
@@ -489,6 +495,7 @@ async function renderGlobPage(scan, merged, { capped, remaining, totalBeforeOffs
489
495
  rgWindowIncomplete: merged.rgWindowIncomplete,
490
496
  remaining,
491
497
  totalBeforeOffset,
498
+ from: offset + 1,
492
499
  nextOffset: offset + capped.length,
493
500
  });
494
501
  if (capped.length > 0) return `${capped.join('\n')}${moreSuffix}${errSuffix}`;
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { isAbsolute } from 'node:path';
2
+ import { isAbsolute, resolve as resolvePath, sep as pathSep } from 'node:path';
3
3
  import {
4
4
  coerceReadFamilyPathArg,
5
5
  GREP_AUTO_CONTEXT_LINES,
@@ -12,6 +12,7 @@ import {
12
12
  appendNotFoundCwdNote,
13
13
  buildNotFoundHintBody,
14
14
  finalizeReadFamilyEnoentTail,
15
+ realRootOf,
15
16
  } from './search-path-diagnostics.mjs';
16
17
  import { buildGrepCacheKey, buildGrepRgArgs } from './search-builders.mjs';
17
18
  import { runRg, runRgWindowedLines } from './native-search-runner.mjs';
@@ -498,10 +499,7 @@ async function runLegacyContextGrep(scope, rgArgs) {
498
499
  async function runPlainGrep(scope, rgArgs, effectiveHeadLimit) {
499
500
  const { options, outputMode, headLimit, offset, cacheKey, grepResolvedPath } = scope;
500
501
  const window = await collectGrepWindow(scope, rgArgs, effectiveHeadLimit);
501
- let { windowed } = window;
502
- if (outputMode === 'files_with_matches' && windowed.length > 1) {
503
- windowed = await sortNewestFirst(windowed, scope.rgSpawnCwd);
504
- }
502
+ const { windowed } = window;
505
503
  let body = formatGrepOutput({
506
504
  windowed,
507
505
  totalWindowed: window.totalWindowed,
@@ -542,7 +540,12 @@ async function runPlainGrep(scope, rgArgs, effectiveHeadLimit) {
542
540
  // complete.
543
541
  async function collectGrepWindow(scope, rgArgs, effectiveHeadLimit) {
544
542
  const { outputMode, offset, searchExecOptions } = scope;
545
- if (effectiveHeadLimit !== Infinity) {
543
+ // The native scan emits content matches in inventory path order (stable, so
544
+ // the streamed content window pages directly). File lists are presented
545
+ // newest-first, so they are pageable only once the WHOLE set is re-ordered;
546
+ // `offset` slices after the sort.
547
+ const fileList = outputMode === 'files_with_matches' || outputMode === 'count';
548
+ if (effectiveHeadLimit !== Infinity && !fileList) {
546
549
  const streamed = await runRgWindowedLines(rgArgs, searchExecOptions, {
547
550
  offset,
548
551
  limit: effectiveHeadLimit,
@@ -556,7 +559,8 @@ async function collectGrepWindow(scope, rgArgs, effectiveHeadLimit) {
556
559
  };
557
560
  }
558
561
  const stdout = await runRg(rgArgs, searchExecOptions);
559
- const allLines = String(stdout).split('\n').filter(Boolean);
562
+ let allLines = String(stdout).split('\n').filter(Boolean);
563
+ if (fileList) allLines = await orderFileListLines(unifyRootSpelling(allLines, scope), outputMode, scope.rgSpawnCwd);
560
564
  const windowed = offset > 0 ? allLines.slice(offset) : allLines;
561
565
  const boxed = typeof stdout === 'object' ? stdout : {};
562
566
  return {
@@ -568,25 +572,60 @@ async function collectGrepWindow(scope, rgArgs, effectiveHeadLimit) {
568
572
  };
569
573
  }
570
574
 
571
- // files_with_matches: newest-first (mtime desc, path tiebreak) so recently
572
- // edited files surface first. Statting is bounded (64 workers, 5s
573
- // deadline); stat-failed entries keep rg traversal order after the statted
574
- // ones. Sort covers the shown window — with the default head limit the
575
- // common case is the complete result set.
576
- async function sortNewestFirst(windowed, rgSpawnCwd) {
577
- const withStat = await statPathsForMtime(windowed, rgSpawnCwd, 64, { deadlineMs: 5000 });
575
+ // A search root reached through a symlink or junction (macOS /var ->
576
+ // /private/var) can come back from the walker under either spelling: rows that
577
+ // the inventory repaired after a filesystem event carry the realpath while the
578
+ // rest carry the given root. Left alone they sort apart, page twice and print
579
+ // as absolute paths. Rewrite realpath rows to the given spelling, then drop the
580
+ // repeats, so ordering, offset paging and relativization see one spelling.
581
+ function unifyRootSpelling(lines, scope) {
582
+ const given = scope.searchPath;
583
+ const absGiven = resolvePath(scope.rgSpawnCwd, given);
584
+ const real = realRootOf(absGiven);
585
+ if (real === absGiven) return lines;
586
+ const fold = (text) => (process.platform === 'win32' ? text.toLocaleLowerCase() : text);
587
+ const realPrefix = fold(`${real.replace(/\\/g, '/').replace(/\/+$/, '')}/`);
588
+ const sep = given.includes('/') && !given.includes('\\') ? '/' : pathSep;
589
+ const givenPrefix = `${given.replace(/[\\/]+$/, '')}${sep}`;
590
+ const seen = new Set();
591
+ const unified = [];
592
+ for (const line of lines) {
593
+ const fwd = line.replace(/\\/g, '/');
594
+ const row = fold(fwd).startsWith(realPrefix)
595
+ ? givenPrefix + fwd.slice(realPrefix.length).replace(/\//g, sep)
596
+ : line;
597
+ if (seen.has(row)) continue;
598
+ seen.add(row);
599
+ unified.push(row);
600
+ }
601
+ return unified;
602
+ }
603
+
604
+ function compareCodeUnits(a, b) {
605
+ if (a === b) return 0;
606
+ return a < b ? -1 : 1;
607
+ }
608
+
609
+ // Total order over the complete file list, applied BEFORE offset/limit slice:
610
+ // files_with_matches is newest-first (mtime desc, path tiebreak) so recently
611
+ // edited files surface first; stat-failed entries follow in path order
612
+ // (statting is bounded: 64 workers, 5s deadline). count lines sort by path.
613
+ async function orderFileListLines(lines, outputMode, rgSpawnCwd) {
614
+ if (lines.length < 2) return lines;
615
+ if (outputMode === 'count') {
616
+ const pathOf = (line) => parseGrepCountLine(line)?.path ?? line;
617
+ return [...lines].sort((a, b) => compareCodeUnits(pathOf(a), pathOf(b)) || compareCodeUnits(a, b));
618
+ }
619
+ const withStat = await statPathsForMtime(lines, rgSpawnCwd, 64, { deadlineMs: 5000 });
578
620
  const statted = [];
579
621
  const unstatted = [];
580
- for (let i = 0; i < windowed.length; i++) {
622
+ for (let i = 0; i < lines.length; i++) {
581
623
  const entry = withStat[i];
582
- if (entry?.stat) statted.push({ line: windowed[i], mtimeMs: entry.mtimeMs });
583
- else unstatted.push(windowed[i]);
624
+ if (entry?.stat) statted.push({ line: lines[i], mtimeMs: entry.mtimeMs });
625
+ else unstatted.push(lines[i]);
584
626
  }
585
- statted.sort((a, b) => {
586
- const dm = b.mtimeMs - a.mtimeMs;
587
- if (dm !== 0) return dm;
588
- return a.line.localeCompare(b.line);
589
- });
627
+ statted.sort((a, b) => b.mtimeMs - a.mtimeMs || compareCodeUnits(a.line, b.line));
628
+ unstatted.sort(compareCodeUnits);
590
629
  return [...statted.map((e) => e.line), ...unstatted];
591
630
  }
592
631
 
@@ -3,7 +3,7 @@
3
3
  * grep/glob in search-tool.mjs. search-tool.mjs re-exports these for unchanged
4
4
  * importers so it can stay a thin executor.
5
5
  */
6
- import { statSync } from 'node:fs';
6
+ import { realpathSync, statSync } from 'node:fs';
7
7
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
8
8
  import { findDirectoryByBasename, findFileByBasename, listSiblings } from './path-diagnostics.mjs';
9
9
  import { normalizeOutputPath, resolveAgainstCwd } from './path-utils.mjs';
@@ -233,16 +233,40 @@ export async function buildNotFoundHintBody(workDir, missingPath, actionVerb, er
233
233
  return elsewhere.exhaustive === true ? ENOENT_ABSENT : '';
234
234
  }
235
235
 
236
+ // The realpath of a working directory, memoized: a search root reached through
237
+ // a symlink or junction (macOS /var -> /private/var) is reported by the walker
238
+ // under its realpath, so results must be relativized against both spellings.
239
+ const realRootCache = new Map();
240
+ const REAL_ROOT_CACHE_MAX = 64;
241
+ export function realRootOf(root) {
242
+ if (realRootCache.has(root)) return realRootCache.get(root);
243
+ let real;
244
+ try {
245
+ real = realpathSync.native(root);
246
+ } catch {
247
+ return root;
248
+ }
249
+ if (realRootCache.size >= REAL_ROOT_CACHE_MAX) realRootCache.clear();
250
+ realRootCache.set(root, real);
251
+ return real;
252
+ }
253
+
254
+ function relativeUnderRoot(absFwd, root) {
255
+ const rootFwd = root.replace(/\\/g, '/').replace(/\/+$/, '');
256
+ const haystack = process.platform === 'win32' ? absFwd.toLocaleLowerCase() : absFwd;
257
+ const needle = process.platform === 'win32' ? rootFwd.toLocaleLowerCase() : rootFwd;
258
+ if (haystack.startsWith(`${needle}/`) || haystack === needle) return absFwd.slice(rootFwd.length + 1) || '.';
259
+ return null;
260
+ }
261
+
236
262
  export function relativePathPrefix(pathPrefix, workDir) {
237
263
  if (!workDir) return pathPrefix;
238
- const cwdFwd = workDir.replace(/\\/g, '/').replace(/\/+$/, '');
239
264
  const absFwd = String(pathPrefix || '').replace(/\\/g, '/');
240
- const haystack = process.platform === 'win32' ? absFwd.toLocaleLowerCase() : absFwd;
241
- const needle = process.platform === 'win32' ? cwdFwd.toLocaleLowerCase() : cwdFwd;
242
- if (haystack.startsWith(`${needle}/`) || haystack === needle) {
243
- return absFwd.slice(cwdFwd.length + 1) || '.';
244
- }
245
- return pathPrefix;
265
+ const given = relativeUnderRoot(absFwd, workDir);
266
+ if (given !== null) return given;
267
+ const real = realRootOf(workDir);
268
+ if (real === workDir) return pathPrefix;
269
+ return relativeUnderRoot(absFwd, real) ?? pathPrefix;
246
270
  }
247
271
 
248
272
  export function relativeSearchResultPath(path, workDir) {
@@ -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 = [];