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,10 +9,13 @@ import {
9
9
  shellJobPublicTaskResult,
10
10
  shellJobTaskStatus,
11
11
  waitForShellJob,
12
+ waitShellJobTerminated,
12
13
  } from './shell-jobs.mjs';
14
+ import { beginSessionTaskWait } from '../../session/manager/runtime-liveness.mjs';
13
15
  import { TASK_WAIT_TIMEOUT_DEFAULT_MS, TASK_WAIT_TIMEOUT_MAX_MS, TASK_WAIT_TIMEOUT_MIN_MS } from './builtin-tools.mjs';
16
+ import { AGENT_TASK_WAIT_DEFAULT_MS } from './wait-budgets.mjs';
14
17
  import { getAbortSignalForSession } from '../../session/abort-lookup.mjs';
15
- import { beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
18
+ import { TASK_WAIT_WAKE_NOTIFICATION, beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
16
19
  import {
17
20
  acknowledgeBackgroundTaskCompletion,
18
21
  cancelBackgroundTask,
@@ -31,6 +34,18 @@ import { readShellTaskOutput } from './lib/shell-task-output.mjs';
31
34
  // wait returns the moment the native task settles, so this only bounds a
32
35
  // process that ignores the kill.
33
36
  const TASK_CANCEL_CONFIRM_MS = 5_000;
37
+ // Ceiling for confirming that every process the task started is gone once its
38
+ // root exited: a sweep is a table read, the kill and a second read (each read
39
+ // ~1-2 s on Windows).
40
+ const TASK_CANCEL_TREE_CONFIRM_MS = 15_000;
41
+
42
+ function cancelPendingText(taskId) {
43
+ return [
44
+ 'status: cancel-pending',
45
+ `task_id: ${taskId}`,
46
+ 'the kill was sent and the command itself exited, but processes it started are still alive. They keep being terminated and stay tracked (the folder stays held) until they are gone; re-run task cancel or task read to check again.',
47
+ ].join('\n');
48
+ }
34
49
 
35
50
  function recoveredTaskMatches(record, options = {}) {
36
51
  const context =
@@ -84,12 +99,22 @@ function renderTaskCancelSuccess(taskId, task) {
84
99
 
85
100
  // Clamp lives next to the caller; the bounds themselves are published by the
86
101
  // tool schema, so an out-of-range request is corrected rather than rejected.
87
- function resolveTaskWaitTimeoutMs(value) {
102
+ function resolveTaskWaitTimeoutMs(value, defaultMs = TASK_WAIT_TIMEOUT_DEFAULT_MS) {
88
103
  const raw = Number(value);
89
- if (!Number.isFinite(raw) || raw <= 0) return TASK_WAIT_TIMEOUT_DEFAULT_MS;
104
+ if (!Number.isFinite(raw) || raw <= 0) return defaultMs;
90
105
  return Math.min(TASK_WAIT_TIMEOUT_MAX_MS, Math.max(TASK_WAIT_TIMEOUT_MIN_MS, Math.floor(raw)));
91
106
  }
92
107
 
108
+ // Agents run on the 5 m prompt cache, so their default wakes before it lapses;
109
+ // the Lead keeps the 1 h cache and the longer default.
110
+ async function defaultTaskWaitMs(sessionId) {
111
+ if (!sessionId) return TASK_WAIT_TIMEOUT_DEFAULT_MS;
112
+ const { getSession } = await import('../../session/manager.mjs');
113
+ const session = getSession(String(sessionId));
114
+ const agentOwned = session?.owner === 'agent' || session?.visibility === 'agent-only';
115
+ return agentOwned ? AGENT_TASK_WAIT_DEFAULT_MS : TASK_WAIT_TIMEOUT_DEFAULT_MS;
116
+ }
117
+
93
118
  // Read-only counts of this runtime's shell work owned by OTHER sessions.
94
119
  // Admission is runtime-wide while task list/cancel are session-scoped, so
95
120
  // without this those holders are invisible. Counts only: another session's
@@ -128,8 +153,15 @@ async function listTasks(options) {
128
153
  return sections.join('\n');
129
154
  }
130
155
 
131
- // Blocks until the shell task settles, the timeout elapses, or the turn/user
132
- // interrupts; returns whether new user input cut the wait short.
156
+ // Ceiling hits per still-running task, so a wait that keeps timing out on the
157
+ // same job can suggest a different move. Keyed by the task object, so the count
158
+ // goes with the task however it settles or is removed.
159
+ const waitCeilingHits = new WeakMap();
160
+
161
+ // Blocks until the shell task settles, the timeout elapses, the turn is
162
+ // cancelled, or anything deliverable is queued for this session (user input,
163
+ // steering, or any task notification: an agent or another shell job
164
+ // finishing). Returns what cut the wait short.
133
165
  async function waitForShellTask(task, taskId, args, options) {
134
166
  // The turn's abort signal must cut the wait short; a cancelled turn
135
167
  // cannot sit out the remaining ceiling.
@@ -140,9 +172,12 @@ async function waitForShellTask(task, taskId, args, options) {
140
172
  turnSignal = null;
141
173
  }
142
174
  const waitControl = beginInterruptibleTaskWait(options?.sessionId, turnSignal);
175
+ const releaseWaitState = beginSessionTaskWait(options?.sessionId);
143
176
  const waitSignal = waitControl.signal;
144
- const waitTimeoutMs = resolveTaskWaitTimeoutMs(args.timeout_ms);
177
+ const waitTimeoutMs = resolveTaskWaitTimeoutMs(args.timeout_ms, await defaultTaskWaitMs(options?.sessionId));
145
178
  let interruptedByUser = false;
179
+ let notified = false;
180
+ let notifiedTaskId = null;
146
181
  try {
147
182
  // A shell task whose descendants outlived the shell process has no
148
183
  // native job to subscribe to — the process it belonged to is gone.
@@ -175,9 +210,12 @@ async function waitForShellTask(task, taskId, args, options) {
175
210
  }
176
211
  } finally {
177
212
  interruptedByUser = waitControl.interruptedByUser;
213
+ notified = waitControl.wakeReason === TASK_WAIT_WAKE_NOTIFICATION;
214
+ notifiedTaskId = waitControl.wakeDetail;
178
215
  waitControl.dispose();
216
+ releaseWaitState();
179
217
  }
180
- return interruptedByUser;
218
+ return { interruptedByUser, notified, notifiedTaskId };
181
219
  }
182
220
 
183
221
  // Outcome of a shell cancel: `outcome` is 'cancelled' only when the process is
@@ -208,6 +246,11 @@ async function cancelShellTaskOutcome(task, taskId, options) {
208
246
  ].join('\n'),
209
247
  };
210
248
  }
249
+ // The root exited; what it started outside its process group is swept
250
+ // by the job's termination watch. Cancelled means all of it is gone.
251
+ if (confirmed && !(await waitShellJobTerminated(taskId, TASK_CANCEL_TREE_CONFIRM_MS))) {
252
+ return { outcome: 'unconfirmed', text: cancelPendingText(taskId) };
253
+ }
211
254
  if (!confirmed) {
212
255
  // No terminal event and no observable task: absence of
213
256
  // state is not proof of termination. Stop tracking it so it
@@ -228,6 +271,10 @@ async function cancelShellTaskOutcome(task, taskId, options) {
228
271
  cancelBackgroundShellJobWatch(taskId);
229
272
  clearShellJobNotifyCtx(taskId);
230
273
  cancelBackgroundTask(taskId, 'cancelled by task control');
274
+ // A task whose own cancel hook kills (what a finished command left running)
275
+ // reports what that kill confirmed.
276
+ const hookKill = job ? null : await task.cancelOutcome;
277
+ if (hookKill && hookKill.terminated !== true) return { outcome: 'unconfirmed', text: cancelPendingText(taskId) };
231
278
  return job || wasRunning
232
279
  ? {
233
280
  outcome: 'cancelled',
@@ -323,27 +370,59 @@ export async function executeTaskTool(args, options = {}) {
323
370
 
324
371
  if (action === 'read' || action === 'wait') {
325
372
  let waitInterruptedByUser = false;
373
+ let waitNotified = false;
374
+ let notifiedTaskId = null;
326
375
  if (action === 'wait') {
327
376
  if (!isShellTask) return `Error: task wait is only available for shell tasks: ${taskId}`;
328
- waitInterruptedByUser = await waitForShellTask(task, taskId, args, options);
377
+ ({
378
+ interruptedByUser: waitInterruptedByUser,
379
+ notified: waitNotified,
380
+ notifiedTaskId,
381
+ } = await waitForShellTask(task, taskId, args, options));
329
382
  }
330
383
  if (isShellTask) refreshShellTask(taskId, { includeRunning: true });
331
384
  const latest = getBackgroundTask(taskId, { context: options }) || task;
385
+ // A finished task whose completion notice is still unconsumed returns that
386
+ // notice's output (the unseen remainder) once; everything else reads the
387
+ // cursor. A wait that leaves the job running carries the progress digest.
388
+ const pendingCompletionOutput =
389
+ isShellTask &&
390
+ latest.status !== 'running' &&
391
+ latest.completionAcknowledged !== true &&
392
+ args.output !== 'tail' &&
393
+ typeof latest.completionOutput === 'string'
394
+ ? latest.completionOutput
395
+ : null;
332
396
  const visible =
333
397
  isShellTask && latest.result && typeof latest.result === 'object'
334
- ? { ...latest, resultText: readShellTaskOutput(latest, latest.result, { output: args.output }) }
398
+ ? {
399
+ ...latest,
400
+ resultText:
401
+ pendingCompletionOutput ??
402
+ readShellTaskOutput(latest, latest.result, {
403
+ output: args.output,
404
+ digest: action === 'wait' && latest.status === 'running',
405
+ }),
406
+ }
335
407
  : latest;
336
408
  let rendered = renderBackgroundTask(visible, { includeResult: true });
337
409
  if (action === 'wait' && latest.status === 'running') {
338
410
  if (waitInterruptedByUser) {
339
411
  rendered +=
340
- '\n\nWait interrupted by new user input; task still running. Handle the new message now and reply to it before waiting on this task again.';
412
+ '\n\nWait ended by new user input; task still running in the background. Handle the new message first.';
413
+ } else if (waitNotified) {
414
+ const from = notifiedTaskId && notifiedTaskId !== taskId ? ` (task_id: ${notifiedTaskId})` : '';
415
+ rendered += `\n\nWait ended because a notification arrived${from}; it is delivered to you now. This task (${taskId}) is still running in the background.`;
341
416
  } else {
417
+ const hits = (waitCeilingHits.get(latest) || 0) + 1;
418
+ waitCeilingHits.set(latest, hits);
342
419
  // In-band report cue: the static "periodic reports" rule does not
343
420
  // survive a long tool loop, so a ceiling-hit wait carries the
344
421
  // protocol reminder itself.
345
422
  rendered +=
346
- '\n\nWait ceiling reached; task still running. If periodic reports were requested, write the user-facing report now, then call task wait for the next interval; otherwise continue independent work or end the turn.';
423
+ hits > 1
424
+ ? `\n\nWait ceiling reached again (${hits} times); task still running. Check whether it is progressing (task read output:tail, or the log file) and cancel it if it is stuck; otherwise continue other work — its result is delivered automatically.`
425
+ : '\n\nWait ceiling reached; task still running and its result is delivered automatically. If periodic reports were requested, write the user-facing report now; otherwise continue independent work or end the turn.';
347
426
  }
348
427
  }
349
428
  if (acknowledgeBackgroundTaskCompletion(taskId, { context: options })) {
@@ -0,0 +1,23 @@
1
+ // The one place the shell hold and the task wait budgets are set. Every
2
+ // schema, clamp, description and result text reads them from here.
3
+ //
4
+ // Shell hold: how long a foreground command may hold the tool call before it
5
+ // moves to the background (it keeps running; completion is delivered
6
+ // automatically). New user input ends the hold early. `wait_ms` picks a
7
+ // different hold for one call, clamped to MIN..MAX.
8
+ export const SHELL_HOLD_DEFAULT_MS = 60_000;
9
+ export const SHELL_HOLD_MIN_MS = 1_000;
10
+ export const SHELL_HOLD_MAX_MS = 600_000;
11
+
12
+ // `task wait` ceiling. The wait returns the moment the task settles or new
13
+ // user input arrives, so the ceiling only bounds a still-running task. The
14
+ // floor stops a caller from turning wait into a busy poll. An agent's default
15
+ // stays under the 5 m prompt-cache TTL with room for the turn's own generation
16
+ // time, so a wait that runs to the ceiling wakes to a warm cache.
17
+ export const TASK_WAIT_DEFAULT_MS = 300_000;
18
+ export const AGENT_TASK_WAIT_DEFAULT_MS = 240_000;
19
+ export const TASK_WAIT_MIN_MS = 10_000;
20
+ export const TASK_WAIT_MAX_MS = 3_600_000;
21
+
22
+ /** Whole seconds for descriptions and result text. */
23
+ export const budgetSeconds = (ms) => Math.round(ms / 1000);
@@ -58,6 +58,7 @@ import {
58
58
  GREP_OUTPUT_MAX_BYTES,
59
59
  LOCATOR_OUTPUT_MAX_BYTES,
60
60
  capLineOrientedToolOutput,
61
+ capMarker,
61
62
  } from './builtin/tool-output-limit.mjs';
62
63
  import {
63
64
  isBlockedDevicePath,
@@ -340,6 +341,46 @@ function _locatorTargets(toolName, args) {
340
341
  return [toolName === 'find' ? '' : '.'];
341
342
  }
342
343
 
344
+ const _GREP_MATCH_ROW_RE = /^(?:\d+|.+?:\d+):/;
345
+
346
+ // Match rows the byte cap left visible, in the units grep's `offset` counts.
347
+ function _grepShownEntries(keptLines, mode) {
348
+ // A context block counts only when its header and every source line survived.
349
+ let blocks = 0;
350
+ let sawHeader = false;
351
+ keptLines.forEach((line, index) => {
352
+ const header = /^# .+:\d+ \[lines (\d+)-(\d+)\]$/.exec(line);
353
+ if (!header) return;
354
+ sawHeader = true;
355
+ if (index + Number(header[2]) - Number(header[1]) + 1 < keptLines.length) blocks += 1;
356
+ });
357
+ if (sawHeader) return blocks;
358
+ const rows = keptLines.filter((line) => line && !/^(?:#|--$|\[|\.\.\.|…|\()/.test(line));
359
+ return mode === 'content' ? rows.filter((line) => _GREP_MATCH_ROW_RE.test(line)).length : rows.length;
360
+ }
361
+
362
+ // Byte-cap marker for grep: the continuation is the offset of the first match
363
+ // row not shown. A pattern/path fan-out has per-target offsets, so it gets
364
+ // narrowing advice instead of a misleading global offset.
365
+ function _grepBudgetFooter(args, keptLines, capBytes) {
366
+ const fanout = keptLines.some((line) => line.startsWith('# grep '));
367
+ const baseOffset =
368
+ Number.isFinite(Number(args?.offset)) && Number(args.offset) > 0 ? Math.floor(Number(args.offset)) : 0;
369
+ const shown = _grepShownEntries(keptLines, args?.mode);
370
+ // Every mode orders the whole result set before `offset` slices it (files
371
+ // and count pages are sorted first, then cut), so the rows kept here are a
372
+ // prefix of the offset order and offset resumes right after them.
373
+ let narrow = '';
374
+ if (fanout) narrow = 'run one pattern/path per call, or add path/glob/type to cut the match set';
375
+ else if (shown === 0) narrow = 'one match row exceeds the cap; read that file window or use a tighter pattern';
376
+ return capMarker({
377
+ limit: capBytes,
378
+ shown: `${shown} match row(s) shown`,
379
+ nextOffset: fanout || shown === 0 ? null : baseOffset + shown,
380
+ narrow,
381
+ });
382
+ }
383
+
343
384
  function _locatorBudgetFooter(toolName, args, keptLines, capBytes) {
344
385
  const targets = _locatorTargets(toolName, args);
345
386
  const header = new RegExp(`^# ${toolName} (.+)$`);
@@ -358,17 +399,24 @@ function _locatorBudgetFooter(toolName, args, keptLines, capBytes) {
358
399
  }
359
400
  const target = targets[targetIndex] || targets[0] || '';
360
401
  const remaining = targets.length - targetIndex - 1;
361
- const capKb = Math.round((Number(capBytes) > 0 ? Number(capBytes) : LOCATOR_OUTPUT_MAX_BYTES) / 1024);
362
- // Factual continuation only — no retry imperatives: the marker states
363
- // what was cut, never instructs another call.
402
+ const limit = Number(capBytes) > 0 ? Number(capBytes) : LOCATOR_OUTPUT_MAX_BYTES;
364
403
  const nextTarget =
365
404
  remaining > 0 ? `; ${remaining} target(s) not shown, next=${JSON.stringify(targets[targetIndex + 1])}` : '';
366
405
  if (toolName === 'find') {
367
- return `... [find output capped at ${capKb} KB; remaining matches for query=${JSON.stringify(target)} omitted${nextTarget}]`;
406
+ // find has no offset: the rest of the ranked matches needs a tighter query.
407
+ return capMarker({
408
+ limit,
409
+ shown: `matches for query=${JSON.stringify(target)} cut${nextTarget}`,
410
+ narrow: 'use a more specific query or add path',
411
+ });
368
412
  }
369
413
  const baseOffset =
370
414
  Number.isFinite(Number(args?.offset)) && Number(args.offset) > 0 ? Math.floor(Number(args.offset)) : 0;
371
- return `... [${toolName} output capped at ${capKb} KB; rows above are complete; continue path=${JSON.stringify(target)} offset:${baseOffset + visibleEntries}${nextTarget}]`;
415
+ return capMarker({
416
+ limit,
417
+ shown: `${visibleEntries} complete row(s) shown for path=${JSON.stringify(target)}${nextTarget}`,
418
+ nextOffset: baseOffset + visibleEntries,
419
+ });
372
420
  }
373
421
 
374
422
  export async function executeBuiltinTool(name, args, cwd, options = {}) {
@@ -489,11 +537,7 @@ function _budgetBuiltinOutput(toolName, args, result, options) {
489
537
  const explicitCap = Number(options?.toolOutputMaxBytes) > 0 ? Math.trunc(Number(options.toolOutputMaxBytes)) : null;
490
538
  if (toolName === 'grep') {
491
539
  const grepCap = explicitCap ? Math.min(explicitCap, GREP_OUTPUT_MAX_BYTES) : GREP_OUTPUT_MAX_BYTES;
492
- return capLineOrientedToolOutput(
493
- result,
494
- grepCap,
495
- (kept) => `[grep output capped at ${grepCap} bytes; ${kept.length} line(s) shown, remainder omitted]`
496
- );
540
+ return capLineOrientedToolOutput(result, grepCap, (kept) => _grepBudgetFooter(args, kept, grepCap));
497
541
  }
498
542
  if (_LOCATOR_BUDGET_TOOLS.has(toolName)) {
499
543
  const locatorCap = explicitCap ? Math.min(explicitCap, LOCATOR_OUTPUT_MAX_BYTES) : LOCATOR_OUTPUT_MAX_BYTES;
@@ -172,9 +172,10 @@ async function _validateDiskCodeGraphHit({
172
172
  return { graph, manifest, signature };
173
173
  }
174
174
 
175
- // Owns a pre-acquired slot until a validated miss hands it to spawnWorker.
176
- // Keeping this seam dependency-injectable makes slot/abort behavior testable
177
- // without spawning native processes or Worker threads.
175
+ // Validates the disk graph under a read-only query slot; a miss releases it
176
+ // and spawnWorker queues for a build slot, so queries never wait behind
177
+ // builds. Keeping this seam dependency-injectable makes slot/abort behavior
178
+ // testable without spawning native processes or Worker threads.
178
179
  export async function _runDiskCodeGraphFastPath({
179
180
  graphCwd,
180
181
  diskProbe,
@@ -182,36 +183,31 @@ export async function _runDiskCodeGraphFastPath({
182
183
  signal = null,
183
184
  loadDiskEntry,
184
185
  consumeDirty = () => {},
185
- // Long-lived code-graph lane so a cold graph build never blocks short rg
186
- // processes in the independent search lane.
187
- acquireSlot = (signal) => acquireChildSpawnSlot(signal, 'code-graph'),
186
+ acquireSlot = (signal) => acquireChildSpawnSlot(signal, 'code-graph-query'),
188
187
  validateDiskHit = _validateDiskCodeGraphHit,
189
188
  spawnWorker,
190
189
  maxFiles = CODE_GRAPH_MAX_FILES,
191
190
  }) {
192
191
  if (!diskProbe?.isFastPathEligible || diskProbe.maxFiles !== maxFiles) {
193
- return spawnWorker(null, null, null);
192
+ return spawnWorker(null, null);
194
193
  }
195
194
  const diskEntry = loadDiskEntry();
196
195
  if (!_isCompatibleDiskCodeGraphEntry(diskEntry, maxFiles)) {
197
- return spawnWorker(null, null, null);
196
+ return spawnWorker(null, null);
198
197
  }
199
198
  consumeDirty();
200
199
  const release = await acquireSlot(signal || null);
201
- let handedToWorker = false;
200
+ let hit;
202
201
  try {
203
202
  _throwIfAborted(signal);
204
- const hit = await validateDiskHit({ graphCwd, diskEntry, genAtStart, signal });
205
- _throwIfAborted(signal);
206
- if (hit?.invalidated) throw codeGraphBuildInvalidatedError(hit.graph);
207
- if (hit?.graph) return hit.graph;
208
- _throwIfAborted(signal);
209
- const workerPromise = spawnWorker(release, hit?.manifest || null, hit?.signature || null);
210
- handedToWorker = true;
211
- return workerPromise;
203
+ hit = await validateDiskHit({ graphCwd, diskEntry, genAtStart, signal });
212
204
  } finally {
213
- if (!handedToWorker) release();
205
+ release();
214
206
  }
207
+ _throwIfAborted(signal);
208
+ if (hit?.invalidated) throw codeGraphBuildInvalidatedError(hit.graph);
209
+ if (hit?.graph) return hit.graph;
210
+ return spawnWorker(hit?.manifest || null, hit?.signature || null);
215
211
  }
216
212
 
217
213
  function _prepareDiskCodeGraphFastPath({
@@ -306,8 +302,10 @@ export async function buildCodeGraphAsync(
306
302
  const existing = _inflightAsyncBuilds.get(inflightKey);
307
303
  if (existing) return raceAbort(existing, signal);
308
304
  // Non-competing prewarm: the signature-validation manifest also needs a
309
- // child-spawn slot, so warmers skip before either it or a Worker can queue.
310
- if (bestEffort && !childSpawnHasSpareCapacity('code-graph')) return null;
305
+ // query slot and a miss a build slot, so warmers skip before either queues.
306
+ if (bestEffort && !(childSpawnHasSpareCapacity('code-graph-query') && childSpawnHasSpareCapacity('code-graph'))) {
307
+ return null;
308
+ }
311
309
  const promise = _retryCodeGraphBuildAfterInvalidation(
312
310
  async () => {
313
311
  const genAtStart = _getCodeGraphGen(graphCwd);
@@ -315,7 +313,7 @@ export async function buildCodeGraphAsync(
315
313
  // bounded I/O. Do not run a manifest at all when no disk candidate exists:
316
314
  // cold/dirty misses remain entirely on the existing Worker path.
317
315
  if (scoped) {
318
- return _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, null, null, null, {
316
+ return _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, null, null, {
319
317
  buildOptions: scopeOptions,
320
318
  cacheResult: false,
321
319
  });
@@ -330,8 +328,8 @@ export async function buildCodeGraphAsync(
330
328
  signal,
331
329
  loadDiskEntry: () => getDiskCodeGraphEntry(graphCwd),
332
330
  consumeDirty: () => _consumeCodeGraphDirtyPaths(graphCwd),
333
- spawnWorker: (preAcquiredRelease, manifest, signature) =>
334
- _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, preAcquiredRelease, manifest, signature),
331
+ spawnWorker: (manifest, signature) =>
332
+ _spawnCodeGraphWorker(cwd, graphCwd, genAtStart, signal, manifest, signature),
335
333
  }),
336
334
  });
337
335
  },
@@ -350,7 +348,6 @@ export function _spawnCodeGraphWorker(
350
348
  graphCwd,
351
349
  genAtStart,
352
350
  signal,
353
- preAcquiredRelease = null,
354
351
  manifest = null,
355
352
  signature = null,
356
353
  {
@@ -416,10 +413,7 @@ export function _spawnCodeGraphWorker(
416
413
  /* best-effort */
417
414
  }
418
415
  };
419
- (preAcquiredRelease
420
- ? Promise.resolve(preAcquiredRelease)
421
- : acquireChildSpawnSlot(signal || null, 'code-graph')
422
- ).then((release) => {
416
+ acquireChildSpawnSlot(signal || null, 'code-graph').then((release) => {
423
417
  _releaseSlot = release;
424
418
  if (settled) {
425
419
  release();
@@ -14,7 +14,7 @@ import { resolve as pathResolve, isAbsolute, relative as pathRelative, dirname a
14
14
  import { normalizeInputPath } from '../builtin/path-utils.mjs';
15
15
  import { markScopedCacheIncomplete, recordScopedCacheDependency } from '../../session/cache/scoped-cache-outcome.mjs';
16
16
  import { CODE_GRAPH_TOOL_DEFS } from '../code-graph-tool-defs.mjs';
17
- import { CODE_GRAPH_OUTPUT_MAX_BYTES, capLineOrientedToolOutput } from '../builtin/tool-output-limit.mjs';
17
+ import { CODE_GRAPH_OUTPUT_MAX_BYTES, capLineOrientedToolOutput, capMarker } from '../builtin/tool-output-limit.mjs';
18
18
  import { _graphRel, _appendSameBasenameHint, _isExistingFile } from './source-access.mjs';
19
19
  import {
20
20
  _capGraphList,
@@ -375,7 +375,6 @@ async function executeCodeGraphToolRaw(name, rawArgs, cwd, signal = null, option
375
375
 
376
376
  function _codeGraphBudgetFooter(args, keptLines) {
377
377
  const rawMode = String(args?.mode || '').trim();
378
- const capKb = Math.round(CODE_GRAPH_OUTPUT_MAX_BYTES / 1024);
379
378
  const targets = collectGraphSymbolList(args);
380
379
  const files = _collectGraphFileList(args);
381
380
  const batchTargets = targets.length ? targets : files;
@@ -389,13 +388,18 @@ function _codeGraphBudgetFooter(args, keptLines) {
389
388
  const index = batchTargets.indexOf(match[1]);
390
389
  if (index >= 0) currentIndex = index;
391
390
  }
392
- return `... [code_graph output capped at ${capKb} KB; not fully shown: ${label}=${JSON.stringify(batchTargets.slice(currentIndex))}]`;
391
+ return capMarker({
392
+ limit: CODE_GRAPH_OUTPUT_MAX_BYTES,
393
+ shown: `${label} from index ${currentIndex} not fully shown`,
394
+ narrow: `re-query ${label}=${JSON.stringify(batchTargets.slice(currentIndex))} separately`,
395
+ });
393
396
  }
394
397
  const target = batchTargets[0];
395
- const targetTail = target
396
- ? `; remainder for ${label.slice(0, -1)}=${JSON.stringify(target)} omitted`
397
- : '; remainder omitted';
398
- return `... [code_graph output capped at ${capKb} KB${targetTail}]`;
398
+ return capMarker({
399
+ limit: CODE_GRAPH_OUTPUT_MAX_BYTES,
400
+ shown: target ? `remainder for ${label.slice(0, -1)}=${JSON.stringify(target)} omitted` : 'remainder omitted',
401
+ narrow: 'add file/path scope, a narrower mode, or a smaller depth/limit',
402
+ });
399
403
  }
400
404
 
401
405
  export async function executeCodeGraphTool(name, args, cwd, signal = null, options = {}) {
@@ -9,12 +9,111 @@
9
9
  import { SHELL_NAMES as _SHELL_NAMES, WRAPPER_NAMES as _WRAPPER_NAMES } from './shell-policy.mjs';
10
10
  import { extractPowerShellCommandInner } from './shell-powershell.mjs';
11
11
 
12
+ // A `#` (or PowerShell `<#`) starts a comment only at the start of a word:
13
+ // line start, after whitespace, or after a command operator. Inside a word or
14
+ // URL (`a#b`, `http://x/#y`, `$#`, `${#v}`) it is an ordinary character.
15
+ function _isCommentStart(text, i) {
16
+ return i === 0 || /[\s;&|(]/.test(text[i - 1]);
17
+ }
18
+
19
+ const _PS_HERE_SQ = /@'[ \t]*\r?\n[\s\S]*?\r?\n'@/y;
20
+ const _PS_HERE_DQ = /@"[ \t]*\r?\n[\s\S]*?\r?\n"@/y;
21
+ const _HEREDOC_START = /<<-?\s*['"]?(\w+)['"]?/y;
22
+
23
+ function _closingQuote(text, from, quote, escapes) {
24
+ let j = from;
25
+ while (j < text.length && text[j] !== quote) j += escapes && text[j] === '\\' ? 2 : 1;
26
+ return j < text.length ? j : -1;
27
+ }
28
+
29
+ // Single left-to-right scan so each construct is recognised by what the shell
30
+ // would see at that position (a `'` inside a comment, a `#` inside a word or a
31
+ // quote inside a here-string never mis-pair later delimiters). Any construct
32
+ // that cannot be closed (unterminated quote, `<#` without `#>`, heredoc without
33
+ // terminator) is left in the output verbatim, so uncertain text stays visible
34
+ // to the policy checks instead of being dropped.
12
35
  export function stripQuotedAndHeredoc(s) {
13
- return String(s || '')
14
- .replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\b/g, '<<HEREDOC>>')
15
- .replace(/'[^']*'/g, "''")
16
- .replace(/"(?:[^"\\]|\\.)*"/g, '""')
17
- .replace(/#[^\n]*/g, '');
36
+ const text = String(s || '');
37
+ const n = text.length;
38
+ const pendingHeredocs = [];
39
+ let out = '';
40
+ let i = 0;
41
+ while (i < n) {
42
+ const ch = text[i];
43
+ if (ch === '\\') {
44
+ out += text.slice(i, i + 2);
45
+ i += 2;
46
+ continue;
47
+ }
48
+ if (ch === '@' && (text[i + 1] === "'" || text[i + 1] === '"')) {
49
+ const re = text[i + 1] === "'" ? _PS_HERE_SQ : _PS_HERE_DQ;
50
+ re.lastIndex = i;
51
+ const m = re.exec(text);
52
+ if (m) {
53
+ out += text[i + 1] === "'" ? "''" : '""';
54
+ i += m[0].length;
55
+ continue;
56
+ }
57
+ }
58
+ if (ch === '<' && text[i + 1] === '<' && text[i - 1] !== '<') {
59
+ _HEREDOC_START.lastIndex = i;
60
+ const m = _HEREDOC_START.exec(text);
61
+ if (m && new RegExp(`\\n${m[1]}\\b`).test(text.slice(i + m[0].length))) {
62
+ // The rest of the opening line stays visible; the body is skipped at
63
+ // the next newline.
64
+ pendingHeredocs.push(m[1]);
65
+ out += '<<HEREDOC>>';
66
+ i += m[0].length;
67
+ continue;
68
+ }
69
+ }
70
+ if (ch === '<' && text[i + 1] === '#' && _isCommentStart(text, i)) {
71
+ const end = text.indexOf('#>', i + 2);
72
+ if (end >= 0) {
73
+ out += ' ';
74
+ i = end + 2;
75
+ continue;
76
+ }
77
+ }
78
+ if (ch === '#' && _isCommentStart(text, i)) {
79
+ const nl = text.indexOf('\n', i);
80
+ i = nl < 0 ? n : nl;
81
+ continue;
82
+ }
83
+ if (ch === "'" || ch === '"' || (ch === '$' && text[i + 1] === "'")) {
84
+ const ansiC = ch === '$';
85
+ const quote = ansiC ? "'" : ch;
86
+ const open = ansiC ? i + 2 : i + 1;
87
+ const close = _closingQuote(text, open, quote, ch !== "'");
88
+ if (close >= 0) {
89
+ out += (ansiC ? '$' : '') + quote + quote;
90
+ i = close + 1;
91
+ continue;
92
+ }
93
+ }
94
+ if (ch === '\n' && pendingHeredocs.length) {
95
+ let at = i;
96
+ let found = true;
97
+ for (const delimiter of pendingHeredocs) {
98
+ const re = new RegExp(`\\n${delimiter}\\b`, 'g');
99
+ re.lastIndex = at;
100
+ const m = re.exec(text);
101
+ if (!m) {
102
+ found = false;
103
+ break;
104
+ }
105
+ at = m.index + m[0].length;
106
+ }
107
+ pendingHeredocs.length = 0;
108
+ if (found) {
109
+ i = at;
110
+ continue;
111
+ }
112
+ }
113
+ out += ch;
114
+ i++;
115
+ }
116
+ return out;
18
117
  }
19
118
 
20
119
  // Heredoc bodies (shell-exec contexts route the body through scanning).
@@ -37,8 +136,11 @@ function _tokenize(s) {
37
136
  if (c === "'") {
38
137
  const e = src.indexOf("'", i + 1);
39
138
  if (e === -1) {
40
- cur += src.slice(i);
41
- break;
139
+ // Unterminated: an apostrophe inside a PowerShell word is literal, so
140
+ // keep tokenizing instead of swallowing the rest of the command.
141
+ cur += c;
142
+ i++;
143
+ continue;
42
144
  }
43
145
  cur += src.slice(i, e + 1);
44
146
  i = e + 1;
@@ -55,8 +157,9 @@ function _tokenize(s) {
55
157
  j++;
56
158
  }
57
159
  if (j >= src.length) {
58
- cur += src.slice(i);
59
- break;
160
+ cur += c;
161
+ i++;
162
+ continue;
60
163
  }
61
164
  cur += src.slice(i, j + 1);
62
165
  i = j + 1;
@@ -67,6 +170,20 @@ function _tokenize(s) {
67
170
  i += 2;
68
171
  continue;
69
172
  }
173
+ // `cur` is empty exactly at a word start (after whitespace or an operator),
174
+ // the only place `#` / PowerShell `<#` begins a comment.
175
+ if (!cur && c === '#') {
176
+ const nl = src.indexOf('\n', i);
177
+ i = nl < 0 ? src.length : nl;
178
+ continue;
179
+ }
180
+ if (!cur && c === '<' && src[i + 1] === '#') {
181
+ const end = src.indexOf('#>', i + 2);
182
+ if (end >= 0) {
183
+ i = end + 2;
184
+ continue;
185
+ }
186
+ }
70
187
  if (c === ' ' || c === '\t') {
71
188
  if (cur) {
72
189
  t.push(cur);