mixdog 0.7.1

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 (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,480 @@
1
+ import { getAbortSignalForSession } from '../../session/abort-lookup.mjs';
2
+ import { execShellCommand, stripAnsi, extractPowerShellCommandInner } from '../shell-command.mjs';
3
+ import { wrapCommandWithSnapshot } from '../shell-snapshot.mjs';
4
+ import { stripQuotedAndHeredoc, extractShellCInner, getDestructiveCommandWarning } from '../destructive-warning.mjs';
5
+ import { maybeRewriteWmicProcessCommand } from '../shell-policy.mjs';
6
+ import { buildBashPolicyScanTargets, checkExecPolicyMessage, injectionBlockTargets } from '../bash-policy-scan.mjs';
7
+ import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph.mjs';
8
+ import {
9
+ buildJobNotFoundMessage,
10
+ startBackgroundShellJob,
11
+ waitForShellJob,
12
+ peekShellJob,
13
+ killShellJob,
14
+ watchBackgroundShellJob,
15
+ cancelBackgroundShellJobWatch,
16
+ beginShellJobWait,
17
+ endShellJobWait,
18
+ clearShellJobNotifyCtx,
19
+ } from './shell-jobs.mjs';
20
+ import {
21
+ analyzeShellCommandEffects,
22
+ foregroundLongCommandHint,
23
+ preflightShellLargeFileProbe,
24
+ } from './shell-analysis.mjs';
25
+ import { resolveShellFor } from './shell-runtime.mjs';
26
+ import { smartMiddleTruncate } from './shell-output.mjs';
27
+ import { normalizeOutputPath } from './path-utils.mjs';
28
+ import { normalizeErrorMessage } from './path-diagnostics.mjs';
29
+ import { invalidateBuiltinResultCache } from './cache-layers.mjs';
30
+ import { resolveOptionalCwd } from './cwd-utils.mjs';
31
+ import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
32
+
33
+ // Post-exec drift detection. After a foreground shell command, compare the
34
+ // live mtime+size of files mixdog has already read this session against their
35
+ // pre-command state (captured just before exec). Files this command changed
36
+ // surface as ONE compact reminder so the model re-reads before editing —
37
+ // closing the "external write -> stale old_string -> code 8" gap when shell is
38
+ // routed through this tool. Bounded to the tracked-read set (capped) so cost
39
+ // stays off the whole-cwd path; emits nothing when no read file changed.
40
+ const _DRIFT_SCAN_CAP = 256;
41
+ export function _captureTrackedMtimes(scope) {
42
+ return new Map();
43
+ }
44
+ export function _trackedDriftNoteAfter(scope, pre) {
45
+ return '';
46
+ }
47
+
48
+ // Combine an existing session abort signal with an externally-supplied
49
+ // AbortSignal (e.g. the MCP/request signal threaded through options.abortSignal).
50
+ // Returns null when neither is present so existing session-only behavior is
51
+ // preserved unchanged. Uses AbortSignal.any when available; falls back to a
52
+ // manual controller + listener bridge otherwise. The returned signal aborts as
53
+ // soon as either input signal aborts, which propagates to execShellCommand /
54
+ // executeBashSessionTool and triggers the same child-kill path the session
55
+ // signal already drives.
56
+ function _combineAbortSignals(sessionSignal, externalSignal) {
57
+ const a = sessionSignal || null;
58
+ const b = externalSignal || null;
59
+ if (!a && !b) return null;
60
+ if (!a) return b;
61
+ if (!b) return a;
62
+ if (a === b) return a;
63
+ if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.any === 'function') {
64
+ try { return AbortSignal.any([a, b]); } catch { /* fall through */ }
65
+ }
66
+ const ctl = new AbortController();
67
+ const onAbort = (sig) => {
68
+ if (ctl.signal.aborted) return;
69
+ try { ctl.abort(sig?.reason); } catch { try { ctl.abort(); } catch {} }
70
+ };
71
+ if (a.aborted) { onAbort(a); return ctl.signal; }
72
+ if (b.aborted) { onAbort(b); return ctl.signal; }
73
+ try { a.addEventListener('abort', () => onAbort(a), { once: true }); } catch {}
74
+ try { b.addEventListener('abort', () => onAbort(b), { once: true }); } catch {}
75
+ return ctl.signal;
76
+ }
77
+
78
+ // Decode ANSI-C $'…' and locale $"…" escapes so the blocklist scan sees the
79
+ // literal command (e.g. $'\x72m' → "rm"). Defensive against quoting bypass.
80
+ function _decodeAnsiCQuotes(s) {
81
+ if (typeof s !== 'string') return '';
82
+ if (s.indexOf('$') === -1) return s;
83
+ return s.replace(/\$(['"])((?:\\.|[^\\])*?)\1/g, (_full, _q, body) =>
84
+ body
85
+ .replace(/\\x([0-9a-fA-F]{1,2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
86
+ .replace(/\\u([0-9a-fA-F]{1,4})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
87
+ .replace(/\\0([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
88
+ .replace(/\\([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
89
+ .replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r')
90
+ .replace(/\\\\/g, '\\').replace(/\\(['"])/g, '$1'),
91
+ );
92
+ }
93
+
94
+ // Extract $(…) and `…` command-substitution bodies so each is re-scanned by
95
+ // isBlockedCommand (e.g. eval $(printf 'rm -rf ~')).
96
+ function _extractSubstitutionBodies(s) {
97
+ if (typeof s !== 'string') return [];
98
+ const out = [];
99
+ const re = /\$\(([^()]*(?:\([^()]*\)[^()]*)*)\)|`([^`]*)`/g;
100
+ let m;
101
+ while ((m = re.exec(s)) !== null) {
102
+ const body = m[1] != null ? m[1] : m[2];
103
+ if (body && body.trim()) out.push(body);
104
+ }
105
+ return out;
106
+ }
107
+
108
+ // Combined injection-aware block targets: decoded form + substitution bodies
109
+ // (and their decoded forms). Used on BOTH the persistent and stateless paths.
110
+ export function _injectionBlockTargets(cmd) {
111
+ return injectionBlockTargets(cmd);
112
+ }
113
+
114
+ function _prefixPowerShellUtf8(command) {
115
+ const prefix = '[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=[System.Text.Encoding]::UTF8;';
116
+ const text = String(command || '');
117
+ return text.trimStart().startsWith(prefix) ? text : `${prefix}\n${text}`;
118
+ }
119
+
120
+ const _unquoteSpansForPolicy = (s) => s.replace(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/g, (m) => m.slice(1, -1));
121
+
122
+ // Same normalized + decoded target set as hard-block (strip/unquote/shell -c/PS).
123
+ export { buildBashPolicyScanTargets } from '../bash-policy-scan.mjs';
124
+
125
+ export function getDedupedDestructiveWarnings(command) {
126
+ const seenMsg = new Set();
127
+ const warnings = [];
128
+ for (const t of buildBashPolicyScanTargets(command)) {
129
+ const w = getDestructiveCommandWarning(t);
130
+ if (w && !seenMsg.has(w)) {
131
+ seenMsg.add(w);
132
+ warnings.push(w);
133
+ }
134
+ }
135
+ return warnings;
136
+ }
137
+
138
+ function _prependDestructiveWarning(command, text) {
139
+ const warnings = getDedupedDestructiveWarnings(command);
140
+ if (!warnings.length) return text;
141
+ return `${warnings.map((w) => `⚠️ ${w}`).join('\n')}\n${text}`;
142
+ }
143
+
144
+ export async function executeBashTool(args, workDir, options = {}) {
145
+ const cwdResult = resolveOptionalCwd(args.cwd, workDir);
146
+ if (cwdResult.error) return cwdResult.error;
147
+ const bashWorkDir = cwdResult.cwd;
148
+ const readStateScope = options?.readStateScope ?? options?.sessionId ?? null;
149
+
150
+ // Run hard-block policy BEFORE branching into the persistent-shell tool.
151
+ // The persistent path used to bypass the one-shot block scan because the
152
+ // normalization (stripQuotedAndHeredoc / extractShellCInner / unquoted
153
+ // span sweep) lived only on the one-shot side. Centralised policy in
154
+ // shell-policy.mjs already covers the literal scan + EncodedCommand
155
+ // decode + rm token guard; calling it here applies the same allowlist
156
+ // to both persistent and stateless paths.
157
+ const _rawCmd = String(args && args.command != null ? args.command : '');
158
+ if (_rawCmd) {
159
+ // R5-③: persistent:true used to route into bash_session BEFORE the
160
+ // stripQuotedAndHeredoc / extractShellCInner / unquote sweep ran
161
+ // (that sweep lived only on the stateless one-shot path below at
162
+ // ~:218). Result: `bash -c 'shutdown -h now'` / `sh -c 'mkfs ...'` /
163
+ // dd payloads were rejected stateless but accepted with
164
+ // persistent:true. Run the full sweep here so both paths share the
165
+ // same blocklist before dispatch.
166
+ const _policyBlock = checkExecPolicyMessage(_rawCmd);
167
+ if (_policyBlock) return _policyBlock;
168
+ }
169
+
170
+ // An empty-string session_id is NOT a persistent-session request: `typeof
171
+ // '' === 'string'` would otherwise route a stateless call into the
172
+ // persistent path and (on Windows) hard-fail with the disabled-sessions
173
+ // error, which models then retry in a loop. Require a non-blank id.
174
+ if (args.persistent === true || (typeof args.session_id === 'string' && args.session_id.trim().length > 0)) {
175
+ if (process.platform === 'win32') {
176
+ return 'Error: persistent shell sessions are disabled on Windows native-shell mode; run one-shot PowerShell commands without persistent/session_id.';
177
+ }
178
+ const { executeBashSessionTool } = await import('../bash-session.mjs');
179
+ let persistAbort = null;
180
+ try { persistAbort = (await getAbortSignalForSession(options?.sessionId)) || null; }
181
+ catch { persistAbort = null; }
182
+ const combinedPersistAbort = _combineAbortSignals(persistAbort, options?.abortSignal || null);
183
+ let effectiveArgs = (args.persistent === true && !args.session_id && options?.sessionId)
184
+ ? { ...args, session_id: `__default__${options.sessionId}` }
185
+ : (typeof args.session_id === 'string' && options?.sessionId)
186
+ ? { ...args, session_id: `${options.sessionId}__${args.session_id}` }
187
+ : args;
188
+ const userProvidedSession = typeof args.session_id === 'string' && args.session_id.trim().length > 0;
189
+ const shouldCreate = args.create === true || !userProvidedSession;
190
+ effectiveArgs = { ...effectiveArgs, create: shouldCreate };
191
+ return executeBashSessionTool('bash_session', effectiveArgs, bashWorkDir, { abortSignal: combinedPersistAbort, sessionId: options?.sessionId });
192
+ }
193
+
194
+ let command = args.command;
195
+ if (!command) return 'Error: command is required';
196
+
197
+ // Resolve the shell up front so shell-type-specific handling (PS-only wmic
198
+ // rewrite, PS UTF-8 prefix) can gate on it. kind 'default' is byte-identical
199
+ // to today's resolveShell(); kind 'bash' on Windows resolves Git Bash, and a
200
+ // null spec means it is genuinely not installed — surface a clear error with
201
+ // NO silent fallback to the other shell.
202
+ const shellKind = args.shell === 'bash' || args.shell === 'powershell' ? args.shell : 'default';
203
+ const resolvedSpec = resolveShellFor(shellKind);
204
+ if (!resolvedSpec) {
205
+ if (shellKind === 'bash') {
206
+ return "Error: Git Bash not found — install Git for Windows or omit shell:'bash'.";
207
+ }
208
+ return "Error: pwsh (PowerShell) not found — install PowerShell or omit shell:'powershell'.";
209
+ }
210
+
211
+ // wmic→PowerShell rewrite is PowerShell-only; never mangle a command bound
212
+ // for bash (gate on the resolved shell type).
213
+ // Note: gating this to powershell did NOT change POSIX behavior — wmic is a
214
+ // Windows-only tool, so the rewrite was already dead code on POSIX hosts;
215
+ // the gate just makes that explicit.
216
+ const wmicRewrite = resolvedSpec.shellType === 'powershell'
217
+ ? maybeRewriteWmicProcessCommand(command)
218
+ : null;
219
+ if (wmicRewrite?.error) return `Error: ${wmicRewrite.error}`;
220
+ if (wmicRewrite?.command) command = wmicRewrite.command;
221
+
222
+ const _execPolicyBlock = checkExecPolicyMessage(command);
223
+ if (_execPolicyBlock) {
224
+ return _execPolicyBlock;
225
+ }
226
+
227
+ const largeProbe = await preflightShellLargeFileProbe(command, bashWorkDir);
228
+ if (largeProbe) return `Error: ${largeProbe.message}`;
229
+ let shellEffects;
230
+ try {
231
+ shellEffects = await analyzeShellCommandEffects(command, bashWorkDir);
232
+ } catch (err) {
233
+ return `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
234
+ }
235
+ // Keep foreground commands on a long tool-owned timeout. The MCP dispatch
236
+ // layer must not add a shorter fallback ceiling when timeout is omitted.
237
+ const DEFAULT_BASH_TIMEOUT_MS = 600_000;
238
+ const DEFAULT_BACKGROUND_BASH_TIMEOUT_MS = 600_000;
239
+ const MAX_BASH_TIMEOUT_MS = 1_800_000;
240
+ const defaultTimeoutMs = args.run_in_background === true
241
+ ? DEFAULT_BACKGROUND_BASH_TIMEOUT_MS
242
+ : DEFAULT_BASH_TIMEOUT_MS;
243
+ const rawTimeout = (typeof args.timeout === 'number' && args.timeout > 0)
244
+ ? args.timeout : defaultTimeoutMs;
245
+ const timeoutMs = rawTimeout <= 600 ? rawTimeout * 1000 : rawTimeout;
246
+ const timeout = Math.min(timeoutMs, wmicRewrite?.timeoutMs || MAX_BASH_TIMEOUT_MS);
247
+ const mergeStderr = args.merge_stderr === true;
248
+ const longForegroundHint = foregroundLongCommandHint(command, timeout, args);
249
+ if (longForegroundHint) return longForegroundHint;
250
+ // Auto-background threshold (CC ASSISTANT_BLOCKING_BUDGET_MS analogue):
251
+ // a foreground one-shot that is still running after this many ms is
252
+ // detached into a tracked shell-job instead of blocking the tool call
253
+ // indefinitely. Only the foreground one-shot path uses it — never
254
+ // run_in_background (already detached) or persistent sessions (handled
255
+ // far above). Capped below the hard timeout so the 600 s upper bound
256
+ // stays a separate, later ceiling.
257
+ const DEFAULT_AUTO_BACKGROUND_MS = 30_000;
258
+ const autoBackgroundMs = args.run_in_background === true
259
+ ? 0
260
+ : Math.min(DEFAULT_AUTO_BACKGROUND_MS, timeout);
261
+
262
+ try {
263
+ const { shell, shellArg, shellArgs, shellType } = resolvedSpec;
264
+ const spawnEnv = { ...process.env, LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' };
265
+ // R5/R11: same scrub as background/persistent spawn sites (env-scrub.mjs).
266
+ scrubProviderSecrets(spawnEnv);
267
+ scrubLoaderVars(spawnEnv);
268
+ let wrappedCommand;
269
+ // PowerShell UTF-8 prefix is PS-only: the Windows Git Bash path
270
+ // (shellType==='posix') must NOT receive it. Snapshot wrapper stays
271
+ // POSIX-host-only for now — no snapshot for Windows Git Bash initially.
272
+ if (process.platform === 'win32' && shellType === 'powershell') {
273
+ wrappedCommand = _prefixPowerShellUtf8(command);
274
+ } else if (process.platform !== 'win32' && (shell.includes('bash') || shell.includes('zsh'))) {
275
+ try {
276
+ wrappedCommand = await wrapCommandWithSnapshot(shell, command);
277
+ } catch (wrapErr) {
278
+ return `Error: shell snapshot wrapper failed — ${normalizeErrorMessage(wrapErr instanceof Error ? wrapErr.message : String(wrapErr))}`;
279
+ }
280
+ } else {
281
+ wrappedCommand = command;
282
+ }
283
+ if (args.run_in_background === true) {
284
+ const job = startBackgroundShellJob({
285
+ command: wrappedCommand,
286
+ timeoutMs: timeout,
287
+ workDir: bashWorkDir,
288
+ mergeStderr,
289
+ spawnEnv,
290
+ shell,
291
+ shellArg,
292
+ shellArgs,
293
+ shellType,
294
+ // Per-terminal session stamp: the dispatching terminal's
295
+ // claude.exe pid (server-main threads callerSession.clientHostPid).
296
+ clientHostPid: options?.clientHostPid,
297
+ });
298
+ if (job && job.error) return `Error: ${job.error}`;
299
+ // Wire a one-shot completion push so the dispatching session learns
300
+ // the background job finished (no polling tool is auto-driven). The
301
+ // notify ctx is threaded down from the MCP dispatch frame
302
+ // (server-main agentContext / _dispatchByModule) the same way the
303
+ // explore tool receives notifyFn/routingSessionId/clientHostPid.
304
+ // Missing notifyFn (e.g. a non-MCP caller) degrades to a stderr
305
+ // diagnostic inside watchBackgroundShellJob — never fails the spawn.
306
+ try {
307
+ watchBackgroundShellJob(job.jobId, {
308
+ notifyFn: typeof options?.notifyFn === 'function' ? options.notifyFn : null,
309
+ routingSessionId: options?.routingSessionId,
310
+ clientHostPid: options?.clientHostPid,
311
+ });
312
+ } catch { /* watcher arm is best-effort; never blocks the spawn */ }
313
+ return _prependDestructiveWarning(command, [
314
+ `[job: ${job.jobId}]`,
315
+ `[pid: ${job.pid}]`,
316
+ `[stdout: ${normalizeOutputPath(job.stdoutPath)}]`,
317
+ mergeStderr ? null : `[stderr: ${normalizeOutputPath(job.stderrPath)}]`,
318
+ '',
319
+ `Background job started for command: ${command}`,
320
+ ].filter(Boolean).join('\n'));
321
+ }
322
+
323
+ let bashAbortSignal = null;
324
+ try { bashAbortSignal = (await getAbortSignalForSession(options?.sessionId)) || null; }
325
+ catch { bashAbortSignal = null; }
326
+ const combinedBashAbort = _combineAbortSignals(bashAbortSignal, options?.abortSignal || null);
327
+ const result = await execShellCommand({
328
+ shell, shellArg, shellArgs, command: wrappedCommand,
329
+ env: spawnEnv,
330
+ cwd: bashWorkDir,
331
+ timeoutMs: timeout,
332
+ abortSignal: combinedBashAbort,
333
+ autoBackgroundMs,
334
+ // Threaded so an auto-backgrounded foreground job is stamped with
335
+ // the dispatching terminal's claude.exe pid (per-terminal scope).
336
+ clientHostPid: options?.clientHostPid,
337
+ // MCP live-progress reporter (null unless the client subscribed via
338
+ // callTool onprogress). execShellCommand emits throttled "running
339
+ // Ns" frames while the foreground command runs.
340
+ onProgress: typeof options?.onProgress === 'function' ? options.onProgress : null,
341
+ });
342
+ // Auto-backgrounded: the command outlived autoBackgroundMs and is
343
+ // still running, now adopted as a tracked shell-job. Surface the
344
+ // jobId + partial output so the model can poll via job_wait instead
345
+ // of the tool call hanging until the hard timeout.
346
+ if (result.backgrounded) {
347
+ const partialStdout = smartMiddleTruncate(stripAnsi(result.stdout || ''));
348
+ const partialStderr = stripAnsi(result.stderr || '');
349
+ const lines = [
350
+ result.jobId ? `[job: ${result.jobId}]` : null,
351
+ result.stdoutPath ? `[stdout: ${normalizeOutputPath(result.stdoutPath)}]` : null,
352
+ (!mergeStderr && result.stderrPath) ? `[stderr: ${normalizeOutputPath(result.stderrPath)}]` : null,
353
+ '',
354
+ result.backgroundMessage || 'auto-backgrounded; still running',
355
+ partialStdout ? `\n[partial stdout]\n${partialStdout}` : '',
356
+ (!mergeStderr && partialStderr) ? `\n[partial stderr]\n${partialStderr}` : '',
357
+ ].filter((l) => l !== null && l !== '');
358
+ return _prependDestructiveWarning(command, lines.join('\n'));
359
+ }
360
+ const stdout = stripAnsi(result.stdout || '');
361
+ const stderr = stripAnsi(result.stderr || '');
362
+ const signal = result.timedOut
363
+ ? 'SIGTERM'
364
+ : (result.killed ? 'SIGKILL' : (result.signal || null));
365
+ const exitCode = signal ? null : result.exitCode;
366
+ const isReallyErrored = !!signal || (exitCode !== 0 && exitCode !== null);
367
+ const _driftNote = '';
368
+ // Distinct timeout marker so callers see "killed by timeout after Nms"
369
+ // vs an external signal (e.g. user Ctrl-C, OOM kill). result.timedOut
370
+ // is the runtime's own timeout escalation (SIGTERM → SIGKILL via
371
+ // treeKill on Windows taskkill), so report the timeout ceiling that
372
+ // fired alongside the actual signal used to kill the tree.
373
+ // Timeout marker carries an inline recovery hint so the caller can
374
+ // act in one round (increase ceiling or detach) instead of repeating
375
+ // the same command and hitting the same wall.
376
+ const statusMarker = result.timedOut
377
+ ? `[timeout: ${timeout}ms signal: ${signal || 'SIGTERM'}]`
378
+ : (signal
379
+ ? `[signal: ${signal}]`
380
+ : (isReallyErrored ? `[exit code: ${exitCode}]` : ''));
381
+ if (mergeStderr) {
382
+ // Post-exit concatenation. True chunk-level interleaving would
383
+ // require shell-level `2>&1` redirection (bash) or `*>&1`
384
+ // (PowerShell) inside wrappedCommand, or an in-process ordered
385
+ // merged stream in shell-command.mjs. Current implementation
386
+ // preserves stdout/stderr ordering within each stream but loses
387
+ // cross-stream interleaving. Acceptable for most diagnostic
388
+ // outputs; flag in shell-command if exact interleaving is required.
389
+ const merged = stdout + stderr;
390
+ if (statusMarker) return _prependDestructiveWarning(command, smartMiddleTruncate(`${statusMarker}\n\n${merged || '(no output)'}`) + _driftNote);
391
+ return _prependDestructiveWarning(command, smartMiddleTruncate(merged || '(no output)') + _driftNote);
392
+ }
393
+ const truncatedStdout = smartMiddleTruncate(stdout);
394
+ const truncatedStderr = stderr ? smartMiddleTruncate(stderr) : '';
395
+ const body = truncatedStdout || (truncatedStderr ? '' : '(no output)');
396
+ const stderrBlock = truncatedStderr ? `\n\n[stderr]\n${truncatedStderr}` : '';
397
+ let spillBlock = '';
398
+ if (result.stdoutPath) {
399
+ const sizeKb = Math.round((result.stdoutFileSize || 0) / 1024);
400
+ spillBlock += `\n\n[stdout: ${normalizeOutputPath(result.stdoutPath)} (${sizeKb} KB)]`;
401
+ }
402
+ if (result.stderrPath && (result.stderrFileSize || 0) > 0) {
403
+ const sizeKb = Math.round((result.stderrFileSize || 0) / 1024);
404
+ spillBlock += `\n[stderr: ${normalizeOutputPath(result.stderrPath)} (${sizeKb} KB)]`;
405
+ }
406
+ const warningBlock = [
407
+ wmicRewrite?.note || '',
408
+ ].filter(Boolean).join('\n');
409
+ const payload = `${body}${stderrBlock}${spillBlock}${_driftNote}`;
410
+ if (statusMarker) return _prependDestructiveWarning(command, `${warningBlock ? `${warningBlock}\n` : ''}${statusMarker}\n\n${payload}`);
411
+ return _prependDestructiveWarning(command, warningBlock ? `${warningBlock}\n${payload}` : payload);
412
+ }
413
+ finally {
414
+ if (shellEffects.mutationMode === 'paths') {
415
+ invalidateBuiltinResultCache(shellEffects.paths);
416
+ markCodeGraphDirtyPaths(shellEffects.paths);
417
+ } else if (shellEffects.mutationMode === 'global') {
418
+ invalidateBuiltinResultCache();
419
+ drainCodeGraphCache();
420
+ }
421
+ }
422
+ }
423
+
424
+ export async function executeJobWaitTool(args) {
425
+ const jobId = typeof args.job_id === 'string' ? args.job_id : '';
426
+ if (!jobId) return 'Error: job_id is required';
427
+ // bridge_* / sess_* are bridge-worker / orchestrator session ids, not
428
+ // background shell jobs. job_wait only resolves `bash run_in_background`
429
+ // jobs, so surface a self-correcting hint instead of the bare
430
+ // "job not found" that otherwise invites a wrong-tool retry loop.
431
+ if (/^(?:bridge_|sess_)/.test(jobId)) {
432
+ return `Error: "${jobId}" is a bridge/session id, not a background shell job. job_wait only waits on ids returned by \`bash\` with run_in_background:true. Bridge workers are detached and reply asynchronously — check their status with bridge type=list.`;
433
+ }
434
+ const action = typeof args.action === 'string' ? args.action.toLowerCase() : 'wait';
435
+ if (action === 'peek') {
436
+ const job = peekShellJob(jobId);
437
+ return job ? JSON.stringify(job, null, 2) : buildJobNotFoundMessage(jobId);
438
+ }
439
+ if (action === 'kill') {
440
+ const job = killShellJob(jobId);
441
+ // kill forces completion the caller observes here, so cancel the armed
442
+ // watcher (before/after kill is equivalent) to avoid a double-notify.
443
+ cancelBackgroundShellJobWatch(jobId);
444
+ // Killed entry will never fire, so drop its persistent notify ctx too —
445
+ // cancel keeps the ctx (for re-arm) but kill has no re-arm path.
446
+ clearShellJobNotifyCtx(jobId);
447
+ return job ? JSON.stringify(job, null, 2) : buildJobNotFoundMessage(jobId);
448
+ }
449
+ // Register as a synchronous waiter and cancel the armed watcher BEFORE
450
+ // awaiting: the caller consumes the outcome via this job_wait, so no async
451
+ // push is wanted, and cancelling up front closes the race where the armed
452
+ // watcher (watch callback or 2s poll) fires during the await window. The
453
+ // persistent notify ctx survives the cancel for a possible re-arm.
454
+ beginShellJobWait(jobId);
455
+ cancelBackgroundShellJobWatch(jobId);
456
+ try {
457
+ const job = await waitForShellJob(jobId, {
458
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : 30_000,
459
+ pollMs: typeof args.poll_ms === 'number' ? args.poll_ms : 250,
460
+ });
461
+ if (!job) return buildJobNotFoundMessage(jobId);
462
+ return JSON.stringify(job, null, 2);
463
+ } finally {
464
+ // Only the LAST concurrent waiter (post-decrement count 0) may re-arm,
465
+ // and only for a still-running job (timed-out wait). Re-arm with no ctx
466
+ // arg — watchBackgroundShellJob falls back to the persistent ctx. This
467
+ // prevents the concurrent-waiter double-deliver: while any other waiter
468
+ // is still synchronously consuming the outcome, the watcher stays off.
469
+ const remaining = endShellJobWait(jobId);
470
+ if (remaining === 0) {
471
+ const latest = peekShellJob(jobId);
472
+ if (latest && latest.status === 'running') watchBackgroundShellJob(jobId);
473
+ // LAST waiter out and the job already finished — the outcome was
474
+ // consumed synchronously, so no re-arm. Drop the persisted ctx here
475
+ // or it leaks (cleanup only runs on a real watcher settle, which
476
+ // never happens for a never-re-armed entry).
477
+ else clearShellJobNotifyCtx(jobId);
478
+ }
479
+ }
480
+ }
@@ -0,0 +1,76 @@
1
+ import { closeSync, openSync, readSync } from 'fs';
2
+
3
+ // Binary detection: reading a PNG / ELF / zip / compressed blob as utf-8
4
+ // pollutes the context with U+FFFD characters and wastes tokens. Sample the
5
+ // head and tail of the file and look for a null byte — the canonical signal
6
+ // that the file is not plain text. Head window scales with file size:
7
+ // min(fileSize, 64KB) head + 4KB tail, so a 250KB file with a null byte at
8
+ // 9KB or 249KB is caught equally. The sampling is synchronous and cheap
9
+ // relative to the 256KB read budget it guards.
10
+ // Callers inside the ≤READ_MAX_SIZE_BYTES branch should pass st.size so the
11
+ // tail probe fires; callers above the cap pass the real size from err.size.
12
+ export function isBinaryFile(fullPath, fileSize = 0) {
13
+ const HEAD_CAP = 64 * 1024; // 64 KB max head window
14
+ const TAIL_SIZE = 4 * 1024; // 4 KB tail probe
15
+ const headBytes = fileSize > 0 ? Math.min(fileSize, HEAD_CAP) : HEAD_CAP;
16
+ let fd = null;
17
+ try {
18
+ fd = openSync(fullPath, 'r');
19
+ // Head probe
20
+ const headBuf = Buffer.allocUnsafe(headBytes);
21
+ const nHead = readSync(fd, headBuf, 0, headBytes, 0);
22
+ if (nHead === 0) return false;
23
+ // UTF-16 text has a null byte in every other position; a leading
24
+ // UTF-16 BOM marks it as text the read path can decode
25
+ // (detectReadEncodingFromBuffer/decodeReadBuffer support utf16le and
26
+ // utf16be), so exempt it rather than reject the file as binary.
27
+ // FF FE = UTF-16LE, FE FF = UTF-16BE.
28
+ if (nHead >= 2
29
+ && ((headBuf[0] === 0xff && headBuf[1] === 0xfe)
30
+ || (headBuf[0] === 0xfe && headBuf[1] === 0xff))) return false;
31
+ for (let i = 0; i < nHead; i++) {
32
+ if (headBuf[i] === 0) return true;
33
+ }
34
+ // Tail probe (only when file is larger than head window)
35
+ if (fileSize > headBytes && fileSize > TAIL_SIZE) {
36
+ const tailOffset = fileSize - TAIL_SIZE;
37
+ const tailBuf = Buffer.allocUnsafe(TAIL_SIZE);
38
+ const nTail = readSync(fd, tailBuf, 0, TAIL_SIZE, tailOffset);
39
+ for (let i = 0; i < nTail; i++) {
40
+ if (tailBuf[i] === 0) return true;
41
+ }
42
+ }
43
+ return false;
44
+ } catch {
45
+ return false;
46
+ } finally {
47
+ if (fd !== null) { try { closeSync(fd); } catch {} }
48
+ }
49
+ }
50
+
51
+ const BINARY_PREVIEW_BYTES = 256;
52
+
53
+ /** Short hex preview for read when null bytes mark the file as binary. */
54
+ export function formatBinaryReadPreview(fullPath, displayPath, fileSize, { previewBytes = BINARY_PREVIEW_BYTES } = {}) {
55
+ const n = Math.max(0, Math.min(previewBytes, fileSize > 0 ? fileSize : previewBytes));
56
+ let fd = null;
57
+ try {
58
+ fd = openSync(fullPath, 'r');
59
+ const buf = Buffer.alloc(n);
60
+ const bytesRead = readSync(fd, buf, 0, n, 0);
61
+ const slice = buf.subarray(0, bytesRead);
62
+ const hex = Array.from(slice).map((b) => b.toString(16).padStart(2, '0')).join(' ');
63
+ const disp = displayPath || fullPath;
64
+ const note = `binary, ${fileSize} byte${fileSize === 1 ? '' : 's'}`;
65
+ const text = `${disp}\n${note}\n${hex || '(empty)'}`;
66
+ return { text, snapshotMeta: { source: 'read_hex', ranges: [] } };
67
+ } catch {
68
+ const disp = displayPath || fullPath;
69
+ return {
70
+ text: `${disp}\nbinary, ${fileSize} bytes\n(preview unavailable)`,
71
+ snapshotMeta: { source: 'read_hex', ranges: [] },
72
+ };
73
+ } finally {
74
+ if (fd !== null) { try { closeSync(fd); } catch {} }
75
+ }
76
+ }