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,168 @@
1
+ import { readFileSync } from 'fs';
2
+ import {
3
+ diagnoseFoldTierAmbiguity,
4
+ findActualString,
5
+ formatStageInline,
6
+ } from '../edit-normalize.mjs';
7
+ import { assertEditTargetUtf8 } from './edit-utf8-guard.mjs';
8
+ import { hashText } from './hash-utils.mjs';
9
+ import { normalizeOutputPath } from './path-utils.mjs';
10
+ import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
11
+ import {
12
+ countLiteralOccurrences,
13
+ findCrlfNormalisedMatches,
14
+ findLiteralOccurrenceState,
15
+ formatMatchLines,
16
+ occurrenceLinesCrlf,
17
+ occurrenceLinesPlain,
18
+ } from './edit-match-utils.mjs';
19
+ import { buildStaleEditRecovery } from './edit-failure-context.mjs';
20
+
21
+ function normalizeOldStringEntries(oldStrings) {
22
+ const out = [];
23
+ const src = Array.isArray(oldStrings) ? oldStrings : [];
24
+ for (let i = 0; i < src.length; i++) {
25
+ const entry = src[i];
26
+ if (typeof entry === 'string') {
27
+ out.push({ old_string: entry, replace_all: false, label: `edit ${i}` });
28
+ continue;
29
+ }
30
+ if (!entry || typeof entry.old_string !== 'string') continue;
31
+ out.push({
32
+ old_string: entry.old_string,
33
+ replace_all: entry.replace_all === true,
34
+ label: entry.label || `edit ${i}`,
35
+ });
36
+ }
37
+ return out;
38
+ }
39
+
40
+ function foldTierAmbiguityError(content, oldString, filePath, editPrefix = '') {
41
+ const amb = diagnoseFoldTierAmbiguity(content, oldString);
42
+ if (!amb || amb.count <= 1) return null;
43
+ const stageNote = formatStageInline(amb.stage);
44
+ return `Error [code 9]: ${editPrefix}old_string found ${amb.count} times in ${filePath}${stageNote};${formatMatchLines(amb.lines, amb.count)} set replace_all:true or provide more unique context`;
45
+ }
46
+
47
+ function oldStringMatchableOnCurrent(content, oldStr, replaceAll, { filePath, editPrefix }) {
48
+ const literal = findLiteralOccurrenceState(content, oldStr);
49
+ if (literal.count === 1) return { ok: true };
50
+ if (literal.count > 1) {
51
+ if (replaceAll) return { ok: true };
52
+ const count = countLiteralOccurrences(content, oldStr);
53
+ return {
54
+ ok: false,
55
+ error: `Error [code 9]: ${editPrefix}old_string found ${count} times in ${filePath} (exact);${formatMatchLines(occurrenceLinesPlain(content, oldStr), count)} set replace_all:true or provide more unique context`,
56
+ };
57
+ }
58
+
59
+ const matchInfo = {};
60
+ const matched = findActualString(content, oldStr, matchInfo) || null;
61
+ if (matched) {
62
+ const occ = findLiteralOccurrenceState(content, matched);
63
+ if (occ.count === 1 || replaceAll) return { ok: true };
64
+ const count = countLiteralOccurrences(content, matched);
65
+ return {
66
+ ok: false,
67
+ error: `Error [code 9]: ${editPrefix}old_string found ${count} times in ${filePath}${formatStageInline(matchInfo.stage)};${formatMatchLines(occurrenceLinesPlain(content, matched), count)} set replace_all:true or provide more unique context`,
68
+ };
69
+ }
70
+
71
+ const crlfMatch = findCrlfNormalisedMatches(content, oldStr);
72
+ const crlfCount = crlfMatch ? crlfMatch.ranges.length : 0;
73
+ if (crlfCount === 1 || (crlfCount > 1 && replaceAll)) return { ok: true };
74
+ if (crlfCount > 1) {
75
+ return {
76
+ ok: false,
77
+ error: `Error [code 9]: ${editPrefix}old_string found ${crlfCount} times in ${filePath} (via crlf-fold);${formatMatchLines(occurrenceLinesCrlf(content, crlfMatch.ranges), crlfCount)} set replace_all:true or provide more unique context`,
78
+ };
79
+ }
80
+
81
+ const foldAmb = foldTierAmbiguityError(content, oldStr, filePath, editPrefix);
82
+ if (foldAmb) return { ok: false, error: foldAmb };
83
+
84
+ return { ok: false, notFound: true };
85
+ }
86
+
87
+ /**
88
+ * When a read snapshot is stale and bytes no longer match the snapshot hash,
89
+ * re-load current disk and allow the edit only if every old_string is
90
+ * matchable on current content (unique unless replace_all). On success,
91
+ * refresh the read snapshot to current bytes.
92
+ */
93
+ export function attemptStaleEditAutoRefresh({
94
+ fullPath,
95
+ filePath,
96
+ scope = null,
97
+ stat = null,
98
+ readRanges = [],
99
+ oldStrings = [],
100
+ readCache = null,
101
+ recordPreviewSnapshot = false,
102
+ } = {}) {
103
+ if (!fullPath) return null;
104
+
105
+ let rawBuf;
106
+ try {
107
+ rawBuf = readFileSync(fullPath);
108
+ } catch {
109
+ return null;
110
+ }
111
+ if (!Buffer.isBuffer(rawBuf)) return null;
112
+
113
+ const displayPath = filePath || normalizeOutputPath(fullPath);
114
+ const utf8Err = assertEditTargetUtf8(rawBuf, displayPath);
115
+ if (utf8Err) return { ok: false, error: utf8Err };
116
+
117
+ const content = rawBuf.toString('utf-8');
118
+ const entries = normalizeOldStringEntries(oldStrings);
119
+ if (entries.length === 0) return null;
120
+
121
+ for (const entry of entries) {
122
+ const editPrefix = entries.length > 1 ? `${entry.label} — ` : '';
123
+ const verdict = oldStringMatchableOnCurrent(content, entry.old_string, entry.replace_all, {
124
+ filePath: displayPath,
125
+ editPrefix,
126
+ });
127
+ if (verdict.error) return { ok: false, error: verdict.error };
128
+ if (verdict.notFound) {
129
+ // Friction fix (Codex-style): the snapshot is stale AND the
130
+ // old_string is absent from CURRENT disk bytes. Don't force a
131
+ // wasteful re-read turn — report a plain not-found against current
132
+ // content (code 8) with recovery context, so the model can correct
133
+ // the string in place without a separate Read call. Commit-time
134
+ // race protection (validatePreparedEditBase / atomicWrite) is
135
+ // unchanged.
136
+ const recovery = buildStaleEditRecovery({
137
+ fullPath,
138
+ scope,
139
+ oldStrings: entries,
140
+ recordPreviewSnapshot,
141
+ });
142
+ return {
143
+ ok: false,
144
+ error: `Error [code 8]: old_string not found in ${displayPath} (file changed since read; matched against current content)${recovery}`,
145
+ };
146
+ }
147
+ }
148
+
149
+ try {
150
+ recordReadSnapshot(fullPath, stat || undefined, scope, {
151
+ source: 'stale_auto_refresh',
152
+ contentHash: hashText(content),
153
+ ranges: Array.isArray(readRanges) ? readRanges : [],
154
+ replaceExisting: true,
155
+ });
156
+ } catch {
157
+ return null;
158
+ }
159
+
160
+ if (readCache && typeof readCache.seedBuffer === 'function') {
161
+ readCache.seedBuffer(fullPath, rawBuf);
162
+ } else if (readCache) {
163
+ readCache.rawBuf = rawBuf;
164
+ readCache.content = content;
165
+ }
166
+
167
+ return { ok: true, content, rawBuf };
168
+ }
@@ -0,0 +1,173 @@
1
+ import {
2
+ appendMutationPlanFailure,
3
+ executeMutationPlan,
4
+ isMutationPlanRoutable,
5
+ planEditMutationRoute,
6
+ } from '../mutation-planner.mjs';
7
+ import { resolveAgainstCwd, normalizeInputPath } from './path-utils.mjs';
8
+ import { normalizeErrorMessage } from './path-diagnostics.mjs';
9
+ import { assertPathsReachable } from './fs-reachability.mjs';
10
+ import { isUncPath, isWindowsDevicePath, hasUnsafeWin32Component, isBlockedDevicePath } from './device-paths.mjs';
11
+
12
+ function editItemsFromBatchArgs(args) {
13
+ return args.edits.map((edit) => ({
14
+ path: edit?.path || args.path,
15
+ old_string: edit?.old_string,
16
+ new_string: edit?.new_string,
17
+ replace_all: edit?.replace_all,
18
+ }));
19
+ }
20
+
21
+ function editItemFromSingleArgs(args) {
22
+ return {
23
+ path: args.path,
24
+ old_string: args.old_string,
25
+ new_string: args.new_string,
26
+ replace_all: args.replace_all,
27
+ };
28
+ }
29
+
30
+ async function maybeExecutePlannedMutation(items, workDir, readStateScope, options, routeOptions) {
31
+ const plan = planEditMutationRoute(items, routeOptions);
32
+ if (!isMutationPlanRoutable(plan)) return null;
33
+ return executeMutationPlan(plan, { workDir, readStateScope, options });
34
+ }
35
+
36
+ // Strip a trailing line coordinate (`:N`, `:N-M`, `#LN`) using a PURE regex —
37
+ // no filesystem access. The real edit path does precise line-vs-colon
38
+ // disambiguation later (which needs FS); the reachability preflight only needs
39
+ // a statable base path, and using existsSync here would itself block on a dead
40
+ // mount BEFORE the async reachability check could run. A Windows drive colon
41
+ // (`C:\...`) is not a trailing `:digits`, so it is unaffected.
42
+ function stripLineCoordForReach(p) {
43
+ return String(p)
44
+ .replace(/#L\d+(?:-L?\d+)?(?:\b.*)?$/i, '')
45
+ .replace(/:\d+(?:-\d+)?(?::.*)?$/, '');
46
+ }
47
+
48
+ // Reject UNC/device/ADS/blocked paths BEFORE the reachability stat — statting
49
+ // a UNC/device path would itself trigger the network/raw-device access (NTLM
50
+ // leak) the edit engine's own guards prevent. edit-engine guards run only
51
+ // after this preflight, so we mirror them here to avoid the stat entirely.
52
+ function _guardedEditError(p) {
53
+ if (isUncPath(p)) return `Error: cannot edit UNC / SMB path (network credential leak risk): ${p}`;
54
+ if (isWindowsDevicePath(p)) return `Error: cannot edit Windows device path (reserved name or raw-device namespace): ${p}`;
55
+ if (hasUnsafeWin32Component(p)) return `Error: cannot edit Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${p}`;
56
+ if (isBlockedDevicePath(p)) return `Error: cannot edit device file: ${p}`;
57
+ return null;
58
+ }
59
+
60
+ async function assertEditTargetsReachable(args, workDir) {
61
+ const fullPaths = [];
62
+ const seen = new Set();
63
+ let guardErr = null;
64
+ const addPath = (raw) => {
65
+ if (typeof raw !== 'string' || !raw) return;
66
+ // normalizeInputPath FIRST (FS-pure: trim/~/win32-mount/NFC) so we stat
67
+ // the same path the edit engine later opens; then strip line coord.
68
+ const norm = stripLineCoordForReach(normalizeInputPath(raw));
69
+ if (!norm) return;
70
+ const fullPath = resolveAgainstCwd(norm, workDir);
71
+ if (seen.has(fullPath)) return;
72
+ seen.add(fullPath);
73
+ // Guarded paths are rejected (not stat'd) — see _guardedEditError.
74
+ if (!guardErr) guardErr = _guardedEditError(norm) || _guardedEditError(fullPath);
75
+ fullPaths.push(fullPath);
76
+ };
77
+ if (Array.isArray(args.edits) && args.edits.length > 0) {
78
+ for (const item of editItemsFromBatchArgs(args)) addPath(item.path);
79
+ } else {
80
+ addPath(args.path);
81
+ }
82
+ if (guardErr) return guardErr;
83
+ try {
84
+ await assertPathsReachable(fullPaths);
85
+ } catch (err) {
86
+ return `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
87
+ }
88
+ return null;
89
+ }
90
+
91
+ export async function executeEditTool(args, workDir, readStateScope, executeChildBuiltinTool, options = {}, handlers = {}) {
92
+ const result = await _executeEditToolImpl(args, workDir, readStateScope, executeChildBuiltinTool, options, handlers);
93
+ // ② completion progress (claude "Found N" parity). Best-effort, no-op when
94
+ // onProgress is absent (no progressToken). Never throws — only emits on a
95
+ // successful edit (an "Error:" body is left to the tool result alone).
96
+ if (typeof options?.onProgress === 'function') {
97
+ try {
98
+ const _body = String(result);
99
+ if (!/^Error[\s[]/.test(_body)) {
100
+ const _rep = /\((\d+) replacements applied\)/.exec(_body);
101
+ if (_rep) options.onProgress(`edited ${_rep[1]} replacements`);
102
+ else {
103
+ const _one = /^Edited:\s+(.+?)(?:\s+\((?:native|\d+)\))?$/m.exec(_body);
104
+ if (_one) options.onProgress(`edited ${_one[1].trim()}`);
105
+ else {
106
+ const _ok = (_body.match(/^OK\s/gm) || []).length;
107
+ if (_ok) options.onProgress(`edited ${_ok} files`);
108
+ }
109
+ }
110
+ }
111
+ } catch { /* best-effort */ }
112
+ }
113
+ return result;
114
+ }
115
+
116
+ async function _executeEditToolImpl(args, workDir, readStateScope, executeChildBuiltinTool, options = {}, handlers = {}) {
117
+ if (typeof args.file_path === 'string' && !args.path) args.path = args.file_path;
118
+ if (Array.isArray(args.edits)) {
119
+ for (const edit of args.edits) {
120
+ if (edit && typeof edit.file_path === 'string' && !edit.path) edit.path = edit.file_path;
121
+ }
122
+ }
123
+
124
+ if (Array.isArray(args.edits) && args.edits.length > 0) {
125
+ const items = editItemsFromBatchArgs(args);
126
+ const paths = new Set(items.map((item) => item.path).filter(Boolean));
127
+ if (paths.size === 0) return 'Error: each edit requires a path (either on the item or at top level)';
128
+
129
+ const reachErr = await assertEditTargetsReachable(args, workDir);
130
+ if (reachErr) return reachErr;
131
+
132
+ const planned = await maybeExecutePlannedMutation(items, workDir, readStateScope, options);
133
+ if (planned?.ok) return planned.text;
134
+
135
+ let result;
136
+ if (paths.size === 1) {
137
+ const onePath = [...paths][0];
138
+ result = await handlers.runMultiEdit({
139
+ path: onePath,
140
+ edits: items.map(({ path: _path, ...rest }) => rest),
141
+ }, workDir, readStateScope, null, options);
142
+ } else {
143
+ result = await handlers.runBatchEdit({
144
+ edits: items.map((item) => ({
145
+ path: item.path,
146
+ old_string: item.old_string,
147
+ new_string: item.new_string,
148
+ replace_all: item.replace_all,
149
+ })),
150
+ }, workDir, readStateScope, null, executeChildBuiltinTool, options);
151
+ }
152
+
153
+ const fallback = await maybeExecutePlannedMutation(items, workDir, readStateScope, options, {
154
+ priorResult: result,
155
+ });
156
+ if (fallback?.ok) return fallback.text;
157
+ return appendMutationPlanFailure(appendMutationPlanFailure(result, planned), fallback);
158
+ }
159
+
160
+ const item = editItemFromSingleArgs(args);
161
+ const reachErr = await assertEditTargetsReachable(args, workDir);
162
+ if (reachErr) return reachErr;
163
+
164
+ const planned = await maybeExecutePlannedMutation([item], workDir, readStateScope, options);
165
+ if (planned?.ok) return planned.text;
166
+
167
+ const result = await handlers.runSingleEdit(args, workDir, readStateScope, options);
168
+ const fallback = await maybeExecutePlannedMutation([item], workDir, readStateScope, options, {
169
+ priorResult: result,
170
+ });
171
+ if (fallback?.ok) return fallback.text;
172
+ return appendMutationPlanFailure(appendMutationPlanFailure(result, planned), fallback);
173
+ }
@@ -0,0 +1,48 @@
1
+ import { isValidUtf8Buffer } from '../mutation-content-cache.mjs';
2
+
3
+ // Shared guard. Centralises the UTF-8 check so every byte-exact write
4
+ // path (native-exact dispatch, single-edit byte-exact buffer,
5
+ // multi-edit byte-exact buffer, AND the stale-auto-refresh path)
6
+ // refuses non-UTF-8 targets with identical wording. Without a single
7
+ // shared hook the stale-refresh path read with 'utf-8' encoding,
8
+ // silently replacing invalid sequences with U+FFFD and bypassing the
9
+ // downstream guards that only saw the re-encoded decoded text.
10
+ export function assertEditTargetUtf8(rawBuf, filePath) {
11
+ if (isValidUtf8Buffer(rawBuf)) return null;
12
+ // UTF-16 BOM detection BEFORE the generic message: a UTF-16 file is a
13
+ // recognizable, recoverable case (read decodes it; write preserves the
14
+ // BOM round-trip) — calling it "Shift-JIS/Latin-1/binary mix" sends the
15
+ // caller down the wrong recovery path.
16
+ if (rawBuf.length >= 2 && rawBuf[0] === 0xFF && rawBuf[1] === 0xFE) {
17
+ return `Error: file is UTF-16LE (BOM FF FE) — edit only supports UTF-8; use write (preserves UTF-16) or convert the file. Path: ${filePath}`;
18
+ }
19
+ if (rawBuf.length >= 2 && rawBuf[0] === 0xFE && rawBuf[1] === 0xFF) {
20
+ return `Error: file is UTF-16BE (BOM FE FF) — edit only supports UTF-8; convert the file first. Path: ${filePath}`;
21
+ }
22
+ // Strict manual UTF-8 walk for Node <18 / environments where
23
+ // Buffer.isUtf8 is unavailable. Rejects overlong sequences,
24
+ // surrogates, out-of-range code points, and 5/6-byte sequences
25
+ // (Unicode §3.9 Table 3-7).
26
+ let idx2 = 0;
27
+ while (idx2 < rawBuf.length) {
28
+ const b0 = rawBuf[idx2];
29
+ if (b0 < 0x80) { idx2++; continue; }
30
+ let seqLen = 0;
31
+ if ((b0 & 0xE0) === 0xC0) seqLen = 2;
32
+ else if ((b0 & 0xF0) === 0xE0) seqLen = 3;
33
+ else if ((b0 & 0xF8) === 0xF0) seqLen = 4;
34
+ else return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
35
+ if (idx2 + seqLen > rawBuf.length) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
36
+ if (seqLen === 2 && b0 <= 0xC1) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
37
+ const b1 = rawBuf[idx2 + 1];
38
+ if (seqLen === 3 && b0 === 0xE0 && b1 < 0xA0) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
39
+ if (seqLen === 3 && b0 === 0xED && b1 >= 0xA0) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
40
+ if (seqLen === 4 && b0 === 0xF0 && b1 < 0x90) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
41
+ if (seqLen === 4 && (b0 > 0xF4 || (b0 === 0xF4 && b1 >= 0x90))) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
42
+ for (let k = 1; k < seqLen; k++) {
43
+ if ((rawBuf[idx2 + k] & 0xC0) !== 0x80) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
44
+ }
45
+ idx2 += seqLen;
46
+ }
47
+ return null;
48
+ }
@@ -0,0 +1,48 @@
1
+ // fs-reachability.mjs — async reachability preflight for tools that then do
2
+ // synchronous filesystem work (read / write / edit / apply_patch).
3
+ //
4
+ // WHY: a synchronous `statSync` / `readFileSync` / `realpathSync` on a dead
5
+ // mount or hung network path BLOCKS the Node main thread. Because the event
6
+ // loop is frozen, even the 630s dispatch ceiling (a main-loop setTimeout)
7
+ // cannot fire — the tool call hangs indefinitely. An async `fsPromises.stat`
8
+ // runs on the libuv threadpool, so a per-path deadline CAN fire on the main
9
+ // loop and surface a clean error BEFORE the blocking sync call is reached.
10
+ //
11
+ // This is a preflight gate, not a full sync->async rewrite: the existing sync
12
+ // logic is unchanged; we only refuse to enter it when the path is unreachable.
13
+ import { stat } from 'node:fs/promises';
14
+
15
+ export const FS_REACHABILITY_DEADLINE_MS = 5000;
16
+
17
+ // Resolve true when the path is reachable (exists OR cleanly absent — ENOENT,
18
+ // EACCES, etc. are "the FS answered", let the real sync logic produce its own
19
+ // error). Reject with EFSUNREACHABLE only when the stat itself exceeds the
20
+ // deadline, which is the dead-mount / hung-FS signature.
21
+ export async function assertPathReachable(path, deadlineMs = FS_REACHABILITY_DEADLINE_MS) {
22
+ if (typeof path !== 'string' || path.length === 0) return;
23
+ const ms = Number(deadlineMs) > 0 ? Number(deadlineMs) : FS_REACHABILITY_DEADLINE_MS;
24
+ let timer = null;
25
+ const probe = stat(path).then(() => true, () => true); // any answer = reachable
26
+ const deadline = new Promise((resolve) => {
27
+ timer = setTimeout(() => resolve('TIMEOUT'), ms);
28
+ });
29
+ const result = await Promise.race([
30
+ probe.finally(() => { if (timer) clearTimeout(timer); }),
31
+ deadline,
32
+ ]);
33
+ if (result === 'TIMEOUT') {
34
+ const err = new Error(
35
+ `path unreachable: stat exceeded ${ms}ms (possible dead mount / hung filesystem): ${path}`,
36
+ );
37
+ err.code = 'EFSUNREACHABLE';
38
+ throw err;
39
+ }
40
+ }
41
+
42
+ // Batch variant: reject if ANY path is unreachable. Runs probes concurrently so
43
+ // the wall-clock cost is one deadline, not N.
44
+ export async function assertPathsReachable(paths, deadlineMs = FS_REACHABILITY_DEADLINE_MS) {
45
+ const list = Array.isArray(paths) ? paths.filter((p) => typeof p === 'string' && p.length) : [];
46
+ if (list.length === 0) return;
47
+ await Promise.all(list.map((p) => assertPathReachable(p, deadlineMs)));
48
+ }
@@ -0,0 +1,99 @@
1
+ // fuzzy-match.mjs — lightweight subsequence fuzzy scorer for filename / path
2
+ // search (codex file-search / nucleo style, in JS). Returns a score where a
3
+ // higher value is a better match, or null when the query is not a subsequence
4
+ // of the candidate.
5
+ //
6
+ // Scoring favors, in rough order of weight:
7
+ // - matches at the START of the basename (last path segment)
8
+ // - matches at a word boundary (/, \, _, -, ., space, or camelCase hump)
9
+ // - contiguous runs of matched characters
10
+ // - exact-case hits (small tie-break)
11
+ // - shorter / earlier candidates (mild brevity + earliness pull)
12
+ //
13
+ // Matching is case-insensitive. The query is matched as an ordered subsequence
14
+ // (the chars must appear in order but need not be contiguous), which is what
15
+ // lets "edeng" find "edit-engine.mjs".
16
+
17
+ function isBoundaryChar(ch) {
18
+ return ch === '/' || ch === '\\' || ch === '_' || ch === '-' || ch === '.' || ch === ' ';
19
+ }
20
+
21
+ // A camelCase hump (lower/digit followed by upper) is also a word boundary.
22
+ function isHump(prevCh, ch) {
23
+ return prevCh !== undefined
24
+ && /[a-z0-9]/.test(prevCh)
25
+ && /[A-Z]/.test(ch);
26
+ }
27
+
28
+ /**
29
+ * @param {string} query user query (partial name)
30
+ * @param {string} str candidate path (relative)
31
+ * @returns {number|null} score, or null if `query` is not a subsequence
32
+ */
33
+ export function fuzzyScore(query, str) {
34
+ if (!query) return 0;
35
+ const q = query.toLowerCase();
36
+ const s = str.toLowerCase();
37
+ const qlen = q.length;
38
+ const slen = s.length;
39
+ if (qlen === 0) return 0;
40
+ if (qlen > slen) return null;
41
+
42
+ const lastSep = Math.max(str.lastIndexOf('/'), str.lastIndexOf('\\'));
43
+
44
+ let score = 0;
45
+ let si = 0;
46
+ let prevMatch = -2;
47
+ let firstMatchIdx = -1;
48
+
49
+ for (let qi = 0; qi < qlen; qi++) {
50
+ const qc = q[qi];
51
+ let found = -1;
52
+ for (let k = si; k < slen; k++) {
53
+ if (s[k] === qc) { found = k; break; }
54
+ }
55
+ if (found === -1) return null;
56
+ if (firstMatchIdx === -1) firstMatchIdx = found;
57
+
58
+ score += 1; // base point per matched char
59
+
60
+ if (found === prevMatch + 1) score += 5; // contiguous run
61
+
62
+ const prevCh = found > 0 ? str[found - 1] : undefined;
63
+ if (prevCh === undefined || isBoundaryChar(prevCh) || isHump(prevCh, str[found])) {
64
+ score += 8; // word-boundary start
65
+ }
66
+
67
+ if (str[found] === query[qi]) score += 1; // exact-case tie-break
68
+
69
+ prevMatch = found;
70
+ si = found + 1;
71
+ }
72
+
73
+ // Matches that begin inside the basename (after the last separator) are far
74
+ // more relevant than ones buried in directory components.
75
+ if (firstMatchIdx > lastSep) score += 10;
76
+
77
+ // Mild pulls: shorter candidates and earlier first matches rank higher.
78
+ score -= Math.floor(slen / 16);
79
+ score -= Math.floor(firstMatchIdx / 8);
80
+
81
+ return score;
82
+ }
83
+
84
+ /**
85
+ * Rank candidates by fuzzy score against `query`, dropping non-matches.
86
+ * @param {string} query
87
+ * @param {Array<{path:string}>} items each must expose a `path` string
88
+ * @param {number} [limit]
89
+ * @returns {Array<{item:object, score:number}>} sorted desc, then path asc
90
+ */
91
+ export function fuzzyRank(query, items, limit = 0) {
92
+ const scored = [];
93
+ for (const item of items) {
94
+ const sc = fuzzyScore(query, item.path);
95
+ if (sc !== null) scored.push({ item, score: sc });
96
+ }
97
+ scored.sort((a, b) => (b.score - a.score) || (a.item.path < b.item.path ? -1 : a.item.path > b.item.path ? 1 : 0));
98
+ return limit > 0 ? scored.slice(0, limit) : scored;
99
+ }