mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -29,6 +29,7 @@ export function buildGrepCacheKey(parts) {
29
29
  multilineMode,
30
30
  fileType,
31
31
  onlyMatching,
32
+ pcre2,
32
33
  } = parts;
33
34
  return [
34
35
  'grep',
@@ -46,6 +47,7 @@ export function buildGrepCacheKey(parts) {
46
47
  multilineMode ? 'm1' : 'm0',
47
48
  onlyMatching ? 'o1' : 'o0',
48
49
  Array.isArray(fileType) ? fileType.join('\x01') : (fileType || ''),
50
+ pcre2 ? 'p1' : 'p0',
49
51
  ].join('|');
50
52
  }
51
53
 
@@ -64,6 +66,7 @@ export function buildGrepRgArgs(parts) {
64
66
  fileType,
65
67
  onlyMatching,
66
68
  fixedStrings = false,
69
+ pcre2 = false,
67
70
  } = parts;
68
71
  // `--hidden`: search dotfiles/dot-dirs (.github, .mixdog) that
69
72
  // rg skips by default. The DEFAULT_IGNORE_GLOBS below still exclude .git and
@@ -84,6 +87,11 @@ export function buildGrepRgArgs(parts) {
84
87
  }
85
88
  if (caseInsensitive) rgArgs.push('-i');
86
89
  if (fixedStrings) rgArgs.push('-F');
90
+ // PCRE2 engine: opt-in only when the caller already confirmed (via
91
+ // rgSupportsPcre2() in search-tool.mjs) that the installed rg binary was
92
+ // built with PCRE2 support. Enables lookaround/backreference patterns
93
+ // that the default Rust regex engine rejects outright.
94
+ if (pcre2) rgArgs.push('-P');
87
95
  if (multilineMode) rgArgs.push('-U', '--multiline-dotall');
88
96
  if (Array.isArray(fileType)) {
89
97
  for (const t of fileType) if (t) rgArgs.push('--type', t);
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Search path diagnostics: ENOENT recovery + path-shape helpers shared by
3
+ * grep/glob in search-tool.mjs. Extracted verbatim so search-tool.mjs stays a
4
+ * thin executor; search-tool.mjs re-exports these for unchanged importers.
5
+ */
6
+ import { statSync } from 'fs';
7
+ import { isAbsolute, resolve } from 'path';
8
+ import { findBySuffixStrip, findFileByBasename } from './path-diagnostics.mjs';
9
+ import { normalizeOutputPath, resolveAgainstCwd } from './path-utils.mjs';
10
+
11
+ // Deterministic ENOENT recovery: when a grep path does not exist, surface
12
+ // indexed files that share the missing path's basename, turning a guessed or
13
+ // misplaced path (e.g. session/result-compression.mjs vs the real
14
+ // tools/result-compression.mjs) into the actual file in one step. Exact
15
+ // basename only — no stem/token fuzzing — so the hint is high-signal and
16
+ // noise-free. Invariant: every ENOENT runs the same basename lookup; there is
17
+ // no "guessed a lot" branch. Returns '' (appends nothing) when no same-named
18
+ // indexed file exists or the glob child is unavailable.
19
+ export async function _suggestIndexedPaths(missingPath, executeChildBuiltinTool, workDir) {
20
+ if (typeof executeChildBuiltinTool !== 'function') return '';
21
+ const base = String(missingPath).replace(/\\/g, '/').split('/').pop();
22
+ // Skip when there is no usable basename or it carries glob magic (the
23
+ // pattern, not a literal filename, would not map to a real file).
24
+ if (!base || /[*?[\]{}]/.test(base)) return '';
25
+ try {
26
+ const out = await executeChildBuiltinTool('glob', { pattern: `**/${base}`, head_limit: 6 }, workDir);
27
+ if (typeof out !== 'string') return '';
28
+ // Drop ONLY the exact diagnostic forms glob emits: the empty-result line
29
+ // ("(no files found ...", "(no entries after offset ...") and the
30
+ // suffix/warning lines ("... [N more entries]", "... [warning] ...").
31
+ // A broad startsWith('(' / '...' / 'Error') would wrongly drop real
32
+ // paths like ErrorBoundary.mjs or a "(draft)/x.mjs" leading segment.
33
+ const hits = out.split('\n')
34
+ .filter((s) => s && !/^\(no (?:files found|entries\b)/.test(s) && !/^\.\.\. \[/.test(s))
35
+ .map((s) => s.trim())
36
+ .filter(Boolean)
37
+ .slice(0, 5);
38
+ return hits.length ? `\n[path not found here; same-named indexed file(s): ${hits.join(', ')}]` : '';
39
+ } catch {
40
+ return '';
41
+ }
42
+ }
43
+
44
+ // Models occasionally glue a quoted segment onto an otherwise-unquoted path
45
+ // (e.g. `C:/Project/mixdog/"src/x.mjs"` — prefix bare, trailing segment
46
+ // wrapped in literal quote chars from a mis-escaped interpolation). But
47
+ // quote chars CAN be legit path content (POSIX `src/Bob's/file.mjs`), so
48
+ // stripping is existence-guarded: keep the original if it stats, only fall
49
+ // back to the stripped variant otherwise. Cost is one statSync and only on
50
+ // the rare quote-containing path.
51
+ export function stripEmbeddedPathQuotes(p) {
52
+ if (typeof p !== 'string' || !p) return p;
53
+ if (!p.includes('"') && !p.includes("'")) return p;
54
+ try { statSync(p); return p; } catch { /* fall through to stripped */ }
55
+ return p.replace(/['"]/g, '');
56
+ }
57
+
58
+ // Reuse read's own ENOENT recovery (path-diagnostics findBySuffixStrip /
59
+ // findFileByBasename — the exact fs scans read-single-tool.mjs already runs
60
+ // for its "same filename exists at" hint) so a grep/glob path miss redirects
61
+ // to the real location instead of dead-ending. No new fs scan is added here;
62
+ // this only calls the two helpers read already calls, with a caller-supplied
63
+ // action verb ("Search"/"List") in place of read's "Read". Gated to genuine
64
+ // not-found codes: EACCES/EPERM etc. keep their real failure semantics and
65
+ // must not trigger same-basename guidance or the BFS scan.
66
+ const NOT_FOUND_CODES = new Set(['ENOENT', 'ENOTDIR']);
67
+ export function buildNotFoundHint(workDir, missingPath, actionVerb, errCode = 'ENOENT') {
68
+ if (!NOT_FOUND_CODES.has(String(errCode || 'ENOENT'))) return '';
69
+ const suffixHit = findBySuffixStrip(workDir, missingPath);
70
+ if (suffixHit) {
71
+ return ` Not found at this path; the same filename exists at: "${normalizeOutputPath(suffixHit)}". ${actionVerb} that path directly.`;
72
+ }
73
+ const elsewhere = findFileByBasename(workDir, missingPath);
74
+ if (elsewhere.length) {
75
+ return ` Not found at this path; the same filename exists at: ${elsewhere.map((p) => `"${normalizeOutputPath(p)}"`).join(', ')}. ${actionVerb} that path directly.`;
76
+ }
77
+ return '';
78
+ }
79
+
80
+ export function relativePathPrefix(pathPrefix, workDir) {
81
+ if (!workDir) return pathPrefix;
82
+ const cwdFwd = workDir.replace(/\\/g, '/').replace(/\/+$/, '');
83
+ const absFwd = String(pathPrefix || '').replace(/\\/g, '/');
84
+ const haystack = process.platform === 'win32' ? absFwd.toLocaleLowerCase() : absFwd;
85
+ const needle = process.platform === 'win32' ? cwdFwd.toLocaleLowerCase() : cwdFwd;
86
+ if (haystack.startsWith(needle + '/') || haystack === needle) {
87
+ return absFwd.slice(cwdFwd.length + 1) || '.';
88
+ }
89
+ return pathPrefix;
90
+ }
91
+
92
+ export function relativeSearchResultPath(path, workDir) {
93
+ const normalizedWorkDir = normalizeOutputPath(workDir);
94
+ const normalizedAbs = normalizeOutputPath(path);
95
+ if (normalizedAbs.startsWith(normalizedWorkDir + '/') || normalizedAbs.startsWith(normalizedWorkDir + '\\')) {
96
+ return normalizedAbs.slice(normalizedWorkDir.length + 1);
97
+ }
98
+ return normalizedAbs;
99
+ }
100
+
101
+ export function resolveSearchScope(root, workDir) {
102
+ return isAbsolute(root) ? resolve(root) : resolveAgainstCwd(root, workDir);
103
+ }
104
+
105
+ export function isUncOrSmbPath(path) {
106
+ if (typeof path !== 'string' || !path) return false;
107
+ return path.startsWith('\\\\') || path.startsWith('//');
108
+ }
109
+
110
+ export function uncRefusalMessage(toolName, original, resolved) {
111
+ const shown = normalizeOutputPath(resolved || original || '');
112
+ return `Error: ${toolName} refuses UNC/SMB path ${JSON.stringify(shown)}; remote share access is blocked to prevent NTLM credential leaks`;
113
+ }
114
+
115
+ export function basePathDiagnostic(basePaths, workDir) {
116
+ return basePaths.map((basePath) => {
117
+ const resolved = resolveSearchScope(basePath, workDir);
118
+ try {
119
+ const st = statSync(resolved);
120
+ return `${normalizeOutputPath(basePath)}: ${st.isDirectory() ? 'path exists (dir)' : 'path exists (file)'}`;
121
+ } catch (err) {
122
+ return `${normalizeOutputPath(basePath)}: path does not exist (${err?.code || 'ENOENT'})`
123
+ + buildNotFoundHint(workDir, resolved, 'Search', err?.code);
124
+ }
125
+ }).join('; ');
126
+ }
@@ -13,13 +13,37 @@ import {
13
13
  normalizeSearchPattern,
14
14
  resolveAgainstCwd,
15
15
  } from './path-utils.mjs';
16
+ import {
17
+ _suggestIndexedPaths,
18
+ basePathDiagnostic,
19
+ buildNotFoundHint,
20
+ isUncOrSmbPath,
21
+ relativePathPrefix,
22
+ relativeSearchResultPath,
23
+ resolveSearchScope,
24
+ stripEmbeddedPathQuotes,
25
+ uncRefusalMessage,
26
+ } from './search-path-diagnostics.mjs';
27
+ // Facade re-export: path-diagnostic helpers moved to search-path-diagnostics.mjs;
28
+ // keep prior importers of search-tool.mjs unchanged.
29
+ export {
30
+ _suggestIndexedPaths,
31
+ basePathDiagnostic,
32
+ buildNotFoundHint,
33
+ isUncOrSmbPath,
34
+ relativePathPrefix,
35
+ relativeSearchResultPath,
36
+ resolveSearchScope,
37
+ stripEmbeddedPathQuotes,
38
+ uncRefusalMessage,
39
+ } from './search-path-diagnostics.mjs';
16
40
  import {
17
41
  buildGlobCacheKey,
18
42
  buildGrepCacheKey,
19
43
  buildGrepRgArgs,
20
44
  DEFAULT_IGNORE_GLOBS,
21
45
  } from './search-builders.mjs';
22
- import { runRg, runRgWindowedLines } from './rg-runner.mjs';
46
+ import { runRg, runRgWindowedLines, rgSupportsPcre2 } from './rg-runner.mjs';
23
47
  import { markScopedCacheIncomplete } from '../../session/cache/scoped-cache-outcome.mjs';
24
48
  import {
25
49
  groupGrepContentByFile,
@@ -34,7 +58,7 @@ import {
34
58
  statPathsForMtime,
35
59
  } from './cache-layers.mjs';
36
60
  import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
37
- import { applyGrepContextLeadPolicy, GREP_CONTEXT_MAX } from './arg-guard.mjs';
61
+ import { applyGrepContextLeadPolicy, GREP_CONTEXT_MAX, hasUnsupportedRipgrepRegex } from './arg-guard.mjs';
38
62
 
39
63
  // Default surrounding-lines window applied by output_mode:'content_with_context'
40
64
  // when the caller does not pass an explicit -A/-B/-C/context. Sized to cover a
@@ -49,51 +73,6 @@ function expandLegacyEscapedAlternationPattern(rawPattern) {
49
73
  return parts.length > 1 ? parts : null;
50
74
  }
51
75
 
52
- // Deterministic ENOENT recovery: when a grep path does not exist, surface
53
- // indexed files that share the missing path's basename, turning a guessed or
54
- // misplaced path (e.g. session/result-compression.mjs vs the real
55
- // tools/result-compression.mjs) into the actual file in one step. Exact
56
- // basename only — no stem/token fuzzing — so the hint is high-signal and
57
- // noise-free. Invariant: every ENOENT runs the same basename lookup; there is
58
- // no "guessed a lot" branch. Returns '' (appends nothing) when no same-named
59
- // indexed file exists or the glob child is unavailable.
60
- async function _suggestIndexedPaths(missingPath, executeChildBuiltinTool, workDir) {
61
- if (typeof executeChildBuiltinTool !== 'function') return '';
62
- const base = String(missingPath).replace(/\\/g, '/').split('/').pop();
63
- // Skip when there is no usable basename or it carries glob magic (the
64
- // pattern, not a literal filename, would not map to a real file).
65
- if (!base || /[*?[\]{}]/.test(base)) return '';
66
- try {
67
- const out = await executeChildBuiltinTool('glob', { pattern: `**/${base}`, head_limit: 6 }, workDir);
68
- if (typeof out !== 'string') return '';
69
- // Drop ONLY the exact diagnostic forms glob emits: the empty-result line
70
- // ("(no files found ...", "(no entries after offset ...") and the
71
- // suffix/warning lines ("... [N more entries]", "... [warning] ...").
72
- // A broad startsWith('(' / '...' / 'Error') would wrongly drop real
73
- // paths like ErrorBoundary.mjs or a "(draft)/x.mjs" leading segment.
74
- const hits = out.split('\n')
75
- .filter((s) => s && !/^\(no (?:files found|entries\b)/.test(s) && !/^\.\.\. \[/.test(s))
76
- .map((s) => s.trim())
77
- .filter(Boolean)
78
- .slice(0, 5);
79
- return hits.length ? `\n[path not found here; same-named indexed file(s): ${hits.join(', ')}]` : '';
80
- } catch {
81
- return '';
82
- }
83
- }
84
-
85
- function relativePathPrefix(pathPrefix, workDir) {
86
- if (!workDir) return pathPrefix;
87
- const cwdFwd = workDir.replace(/\\/g, '/').replace(/\/+$/, '');
88
- const absFwd = String(pathPrefix || '').replace(/\\/g, '/');
89
- const haystack = process.platform === 'win32' ? absFwd.toLocaleLowerCase() : absFwd;
90
- const needle = process.platform === 'win32' ? cwdFwd.toLocaleLowerCase() : cwdFwd;
91
- if (haystack.startsWith(needle + '/') || haystack === needle) {
92
- return absFwd.slice(cwdFwd.length + 1) || '.';
93
- }
94
- return pathPrefix;
95
- }
96
-
97
76
  function relativeGrepLine(line, workDir, pathOnly = false, outputMode = 'content', filenameOmitted = false) {
98
77
  const normalized = normalizeGrepLine(line, pathOnly, outputMode, filenameOmitted);
99
78
  if (!workDir) return normalized;
@@ -112,15 +91,6 @@ function relativeGrepLine(line, workDir, pathOnly = false, outputMode = 'content
112
91
  return normalized;
113
92
  }
114
93
 
115
- function relativeSearchResultPath(path, workDir) {
116
- const normalizedWorkDir = normalizeOutputPath(workDir);
117
- const normalizedAbs = normalizeOutputPath(path);
118
- if (normalizedAbs.startsWith(normalizedWorkDir + '/') || normalizedAbs.startsWith(normalizedWorkDir + '\\')) {
119
- return normalizedAbs.slice(normalizedWorkDir.length + 1);
120
- }
121
- return normalizedAbs;
122
- }
123
-
124
94
  function uniqueStrings(values) {
125
95
  return Array.from(new Set(values.filter((value) => typeof value === 'string' && value)));
126
96
  }
@@ -196,32 +166,6 @@ function isRedundantAllFilesGlob(value) {
196
166
  return g === '**/*' || g === '**';
197
167
  }
198
168
 
199
- function resolveSearchScope(root, workDir) {
200
- return isAbsolute(root) ? resolve(root) : resolveAgainstCwd(root, workDir);
201
- }
202
-
203
- function isUncOrSmbPath(path) {
204
- if (typeof path !== 'string' || !path) return false;
205
- return path.startsWith('\\\\') || path.startsWith('//');
206
- }
207
-
208
- function uncRefusalMessage(toolName, original, resolved) {
209
- const shown = normalizeOutputPath(resolved || original || '');
210
- return `Error: ${toolName} refuses UNC/SMB path ${JSON.stringify(shown)}; remote share access is blocked to prevent NTLM credential leaks`;
211
- }
212
-
213
- function basePathDiagnostic(basePaths, workDir) {
214
- return basePaths.map((basePath) => {
215
- const resolved = resolveSearchScope(basePath, workDir);
216
- try {
217
- const st = statSync(resolved);
218
- return `${normalizeOutputPath(basePath)}: ${st.isDirectory() ? 'path exists (dir)' : 'path exists (file)'}`;
219
- } catch (err) {
220
- return `${normalizeOutputPath(basePath)}: path does not exist (${err?.code || 'ENOENT'})`;
221
- }
222
- }).join('; ');
223
- }
224
-
225
169
  function grepMissingPatternMessage() {
226
170
  return 'Error: grep requires pattern.';
227
171
  }
@@ -275,11 +219,37 @@ function formatGrepOutput({ windowed, totalWindowed, totalKnown, headLimit, offs
275
219
 
276
220
  export async function executeGrepTool(args, workDir, executeChildBuiltinTool, readStateScope = null, options = {}) {
277
221
  args = normalizeGrepArgs(args);
222
+ // Fan-out guard: batch multiple string paths sequentially, mirroring
223
+ // code_graph files[] batching. Recursive calls pass a single string
224
+ // path, so recursion bottoms out after one level.
225
+ if (Array.isArray(args.path)) {
226
+ const GREP_PATH_CAP = 10;
227
+ const seen = new Set();
228
+ const list = args.path
229
+ .map(p => typeof p === 'string' ? p.trim() : '')
230
+ .filter(p => p && !seen.has(p) && seen.add(p));
231
+ if (list.length > 1) {
232
+ const capped = list.slice(0, GREP_PATH_CAP);
233
+ const parts = [];
234
+ for (const p of capped) {
235
+ let body;
236
+ try {
237
+ body = await executeGrepTool({ ...args, path: p }, workDir, executeChildBuiltinTool, readStateScope, options);
238
+ } catch (err) {
239
+ body = `Error: ${err && err.message ? err.message : err}`;
240
+ }
241
+ parts.push(`# grep ${p}\n${body}`);
242
+ }
243
+ if (list.length > GREP_PATH_CAP) parts.push(`[capped at ${GREP_PATH_CAP} of ${list.length} paths]`);
244
+ return parts.join('\n\n');
245
+ }
246
+ args.path = list[0];
247
+ }
278
248
  // Shape context immediately before deriving rg flags. This keeps the
279
249
  // Lead-direct MCP path and direct executeGrepTool callers on the same
280
250
  // policy even if they bypass or race the outer builtin arg guard.
281
251
  applyGrepContextLeadPolicy(args);
282
- args.path = normalizeInputPath(args.path);
252
+ args.path = stripEmbeddedPathQuotes(normalizeInputPath(args.path));
283
253
  args.pattern = coerceShapeFlex(args.pattern);
284
254
  args.glob = coerceShapeFlex(args.glob);
285
255
  const rawPattern = args.pattern;
@@ -302,13 +272,32 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
302
272
 
303
273
  const GREP_MULTILINE_PATTERN_CAP = 5;
304
274
  const GREP_ARRAY_PATTERN_CAP = 20;
305
- const multilineMode = args.multiline === true;
275
+ // Rescue: a pattern containing a literal two-char "\n" (backslash + n)
276
+ // outside multiline mode makes rg hard-error with "the literal '\"\\n\"'
277
+ // is not allowed in a regex" — rg requires -U/--multiline before a
278
+ // pattern is allowed to match across a newline. Rather than reject-and-
279
+ // retry, auto-enable multiline whenever any pattern carries a literal
280
+ // \n; an explicit multiline:true from the caller still wins outright.
281
+ const patternsWantMultiline = patterns.some((p) => /\\n/.test(p));
282
+ const multilineMode = args.multiline === true || patternsWantMultiline;
306
283
  if (multilineMode && patterns.length > GREP_MULTILINE_PATTERN_CAP) {
307
284
  return `Error: multiline:true with more than ${GREP_MULTILINE_PATTERN_CAP} patterns is not allowed (got ${patterns.length}); split into separate grep calls`;
308
285
  }
309
286
  if (patterns.length > GREP_ARRAY_PATTERN_CAP) {
310
287
  return `Error: pattern array exceeds the ${GREP_ARRAY_PATTERN_CAP}-pattern cap (got ${patterns.length}); split into separate grep calls`;
311
288
  }
289
+ // Rescue: lookaround/backreference patterns are rejected by rg's default
290
+ // Rust regex engine ("look-around ... is not supported" / "backreferences
291
+ // ... not supported"). rg builds compiled with the optional `pcre2`
292
+ // feature accept the same syntax via -P/--pcre2. Probe capability once
293
+ // (cached) and route there instead of hard-erroring; when the installed
294
+ // rg has no PCRE2 support, fall through unchanged and let the existing
295
+ // arg-guard rejection (or rg's own runtime error) stand.
296
+ const patternsWantPcre2 = hasUnsupportedRipgrepRegex(patterns);
297
+ const pcre2Mode = patternsWantPcre2 && await rgSupportsPcre2();
298
+ if (patternsWantPcre2 && !pcre2Mode) {
299
+ return `Error: grep pattern uses regex syntax ripgrep does not support here (lookaround/backrefs), and the installed rg build has no PCRE2 support (-P unavailable). Use plain pattern arrays or simpler regex.`;
300
+ }
312
301
 
313
302
  let searchPath = args.path || '.';
314
303
  const rawGlob = args.glob;
@@ -453,6 +442,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
453
442
  multilineMode,
454
443
  onlyMatching: args['-o'] === true,
455
444
  fileType,
445
+ pcre2: pcre2Mode,
456
446
  });
457
447
  // Single-file grep registers a whole-file read snapshot (parity with
458
448
  // apply_patch), satisfying the read-before-edit guard while keeping drift
@@ -478,6 +468,8 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
478
468
  try { grepStat = statSync(grepResolvedPath); }
479
469
  catch (err) {
480
470
  const msg = `Error: path does not exist: ${normalizeOutputPath(grepResolvedPath)} (${err?.code || 'ENOENT'})`;
471
+ const hint = buildNotFoundHint(workDir, grepResolvedPath, 'Search', err?.code);
472
+ if (hint) return msg + hint;
481
473
  return msg + await _suggestIndexedPaths(grepResolvedPath, executeChildBuiltinTool, workDir);
482
474
  }
483
475
  const filenameOmitted = grepStat.isFile();
@@ -514,6 +506,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
514
506
  multilineMode,
515
507
  fileType,
516
508
  onlyMatching: args['-o'] === true,
509
+ pcre2: pcre2Mode,
517
510
  });
518
511
  let windowed;
519
512
  let totalWindowed = 0;
@@ -731,8 +724,8 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
731
724
  export async function executeGlobTool(args, workDir, options = {}) {
732
725
  args = normalizeGlobArgs(args);
733
726
  args.path = Array.isArray(args.path)
734
- ? args.path.map((p) => normalizeInputPath(p)).filter((p) => typeof p === 'string' && p)
735
- : normalizeInputPath(args.path);
727
+ ? args.path.map((p) => stripEmbeddedPathQuotes(normalizeInputPath(p))).filter((p) => typeof p === 'string' && p)
728
+ : stripEmbeddedPathQuotes(normalizeInputPath(args.path));
736
729
  if (Array.isArray(args.path) && args.path.length === 0) {
737
730
  return 'Error: path array must contain at least one base directory';
738
731
  }
@@ -861,8 +854,9 @@ export async function executeGlobTool(args, workDir, options = {}) {
861
854
  rgArgs.push('.');
862
855
  try { statSync(rgCwd); }
863
856
  catch (err) {
857
+ const hint = buildNotFoundHint(workDir, rgCwd, 'Search', err?.code);
864
858
  return {
865
- error: `path does not exist: ${normalizeOutputPath(rgCwd)} (${err?.code || 'ENOENT'})`,
859
+ error: `path does not exist: ${normalizeOutputPath(rgCwd)} (${err?.code || 'ENOENT'})${hint}`,
866
860
  paths: [],
867
861
  stdoutTruncated: false,
868
862
  };
@@ -0,0 +1,161 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import * as fsPromises from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { getPluginData } from '../../config.mjs';
5
+
6
+ // One-shot sweep of stale shell-job artefacts. Each backgrounded `bash`
7
+ // emits five files (.json/.done/.exit/.stdout.log/.stderr.log); the .done
8
+ // flag is written when the job exits, so a .done file older than
9
+ // SHELL_JOB_STALE_MS is the invariant proof its sibling files are also
10
+ // safe to remove. Active and recently-completed jobs are kept so
11
+ // `task` status/read/wait readers still find them. Runs once per mcp child
12
+ // lifetime on first getShellJobsDir() call. Async dirent walk + parallel
13
+ // stat/unlink keeps the main event loop free; fire-and-forget so the
14
+ // synchronous caller receives `dir` immediately.
15
+ const SHELL_JOB_STALE_MS = 24 * 60 * 60 * 1000;
16
+ let shellJobsSwept = false;
17
+ async function sweepStaleShellJobs(dir) {
18
+ if (shellJobsSwept) return;
19
+ shellJobsSwept = true;
20
+ const cutoff = Date.now() - SHELL_JOB_STALE_MS;
21
+ let names;
22
+ try { names = await fsPromises.readdir(dir); } catch { return; }
23
+ const expired = [];
24
+ await Promise.all(names.map(async (name) => {
25
+ if (!name.endsWith('.done')) return;
26
+ const p = join(dir, name);
27
+ try {
28
+ const st = await fsPromises.stat(p);
29
+ if (st.mtimeMs < cutoff) expired.push(name.slice(0, -5));
30
+ } catch {}
31
+ }));
32
+ // Orphan reclaim: a crashed wrapper leaves <id>.json with no .done —
33
+ // forever. Invariant proofs of death (either suffices, both gated on the
34
+ // stale cutoff so a young orphan can't race its own spawn):
35
+ // a) deadline: the wrapper enforces timeoutMs, so an entry older than
36
+ // timeoutMs + grace cannot still be running — pid-reuse-proof. Only
37
+ // trusted on runtime proof: detail.timeoutEnforced:true (PS wrapper,
38
+ // unconditional) or the <id>.enforced marker the posix wrapper
39
+ // touches when its `timeout` branch actually runs.
40
+ // b) ESRCH: the recorded pid no longer exists. Alone this misses pids
41
+ // recycled by unrelated live processes, hence (a).
42
+ const ORPHAN_DEADLINE_GRACE_MS = 30 * 60_000;
43
+ const doneSet = new Set(names.filter(n => n.endsWith('.done')).map(n => n.slice(0, -5)));
44
+ await Promise.all(names.map(async (name) => {
45
+ if (!name.endsWith('.json')) return;
46
+ const jobId = name.slice(0, -5);
47
+ if (doneSet.has(jobId)) return;
48
+ const p = join(dir, name);
49
+ try {
50
+ const st = await fsPromises.stat(p);
51
+ if (st.mtimeMs >= cutoff) return;
52
+ const detail = JSON.parse(await fsPromises.readFile(p, 'utf-8'));
53
+ const tmo = Number(detail?.timeoutMs);
54
+ const enforced = detail?.timeoutEnforced === true
55
+ || existsSync(join(dir, `${jobId}.enforced`));
56
+ const deadlinePassed = enforced
57
+ && Number.isFinite(tmo) && tmo > 0
58
+ && (Date.now() - st.mtimeMs) > tmo + ORPHAN_DEADLINE_GRACE_MS;
59
+ if (!deadlinePassed) {
60
+ const pid = Number(detail?.pid);
61
+ if (Number.isFinite(pid) && pid > 0) {
62
+ try { process.kill(pid, 0); return; } // alive (or EPERM → treated dead only via ESRCH below)
63
+ catch (e) { if (e?.code !== 'ESRCH') return; }
64
+ }
65
+ }
66
+ expired.push(jobId);
67
+ } catch {}
68
+ }));
69
+ // Owner sidecar markers carry a dynamic `.owner-<pid>` suffix, so they
70
+ // can't sit in the fixed-extension list — map each expired jobId to the
71
+ // marker name(s) actually present in this listing and unlink those too.
72
+ const expiredSet = new Set(expired);
73
+ const ownerMarkers = names.filter((n) => {
74
+ const i = n.lastIndexOf('.owner-');
75
+ return i > 0 && expiredSet.has(n.slice(0, i));
76
+ });
77
+ await Promise.all([
78
+ ...expired.flatMap((jobId) =>
79
+ ['.json', '.done', '.exit', '.enforced', '.exit.cmd.sh', '.exit.cmd.ps1', '.stdout.log', '.stderr.log'].map((ext) =>
80
+ fsPromises.unlink(join(dir, jobId + ext)).catch(() => {}),
81
+ ),
82
+ ),
83
+ ...ownerMarkers.map((n) => fsPromises.unlink(join(dir, n)).catch(() => {})),
84
+ ]);
85
+ }
86
+
87
+ export function getShellJobsDir() {
88
+ const dir = join(getPluginData(), 'shell-jobs');
89
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
90
+ sweepStaleShellJobs(dir);
91
+ return dir;
92
+ }
93
+
94
+ export function shellJobDetailPath(jobId) { return join(getShellJobsDir(), `${jobId}.json`); }
95
+ export function shellJobStdoutPath(jobId) { return join(getShellJobsDir(), `${jobId}.stdout.log`); }
96
+ export function shellJobStderrPath(jobId) { return join(getShellJobsDir(), `${jobId}.stderr.log`); }
97
+ export function shellJobExitPath(jobId) { return join(getShellJobsDir(), `${jobId}.exit`); }
98
+ export function shellJobDonePath(jobId) { return join(getShellJobsDir(), `${jobId}.done`); }
99
+ // Runtime proof that the posix wrapper's `timeout` branch actually ran: the
100
+ // wrapper touches this marker immediately before exec'ing `timeout`, so its
101
+ // existence is never optimistic (no spawn-time probe can guarantee the
102
+ // wrapper's own env/cwd resolution). PS jobs don't need it — their wrapper
103
+ // enforces unconditionally and records detail.timeoutEnforced:true.
104
+ export function shellJobEnforcedPath(jobId) { return join(getShellJobsDir(), `${jobId}.enforced`); }
105
+ // Owner sidecar marker: a zero-byte file whose NAME encodes the owning CC host
106
+ // (claude.exe) pid — `<jobId>.owner-<pid>`. It lets the statusline owner-filter
107
+ // jobs from a SINGLE directory listing (no per-job JSON read) so the filter can
108
+ // precede its per-tick scan cap — otherwise other sessions' newer jobs evict
109
+ // this session's live jobs before filtering. Swept alongside the other
110
+ // artefacts.
111
+ export function shellJobOwnerPath(jobId, pid) { return join(getShellJobsDir(), `${jobId}.owner-${pid}`); }
112
+
113
+ // Resolve the CLI host pid that owns a freshly-spawned job. Standalone CLI
114
+ // execution is process-owned: background tasks are scoped to this CLI lifetime,
115
+ // and the pid stamp is only for statusline filtering / legacy sidecars.
116
+ export function resolveJobOwnerHostPid(clientHostPid) {
117
+ const explicit = Number(clientHostPid);
118
+ if (Number.isInteger(explicit) && explicit > 0) return explicit;
119
+ const envPid = Number(process.env.MIXDOG_OWNER_HOST_PID);
120
+ if (Number.isInteger(envPid) && envPid > 0) return envPid;
121
+ return null;
122
+ }
123
+
124
+ export function writeShellJobDetail(detail) {
125
+ // Session scope stamp: every job record is tagged with the CC host pid
126
+ // that owns it (the claude.exe pid). This is the SAME physical pid the
127
+ // statusline shim passes as --client-host-pid, so the statusline can count
128
+ // only its own session's jobs by exact pid equality (no heuristic). The
129
+ // spawn sites set detail.ownerHostPid from the per-request threaded
130
+ // clientHostPid (resolveJobOwnerHostPid); this fallback only stamps the
131
+ // single-client env value when no spawn-site pid was set, and NEVER
132
+ // overwrites an existing stamp — so the field round-trips through disk on
133
+ // refresh/kill and a correct per-terminal stamp is preserved across rewrites.
134
+ if (detail && detail.ownerHostPid == null) {
135
+ const hostPid = Number(process.env.MIXDOG_OWNER_HOST_PID);
136
+ if (Number.isInteger(hostPid) && hostPid > 0) detail.ownerHostPid = hostPid;
137
+ }
138
+ writeFileSync(shellJobDetailPath(detail.jobId), JSON.stringify(detail, null, 2), 'utf-8');
139
+ // Owner sidecar: encode the resolved owner pid in the marker filename so the
140
+ // statusline can owner-filter from the directory listing alone (before its
141
+ // scan cap). Idempotent zero-byte write — safe to repeat on refresh/kill
142
+ // rewrites. Skipped when no owner is known (legacy/unattributed jobs).
143
+ if (detail && Number.isInteger(detail.ownerHostPid) && detail.ownerHostPid > 0) {
144
+ try { writeFileSync(shellJobOwnerPath(detail.jobId, detail.ownerHostPid), '', 'utf-8'); }
145
+ catch { /* best-effort marker */ }
146
+ }
147
+ }
148
+
149
+ export function readShellJobDetail(jobId) {
150
+ try {
151
+ const p = shellJobDetailPath(jobId);
152
+ if (!existsSync(p)) return null;
153
+ return JSON.parse(readFileSync(p, 'utf-8'));
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ export function buildJobNotFoundMessage(jobId) {
160
+ return `Error: task not found: ${jobId}`;
161
+ }