mixdog 0.9.1 → 0.9.3

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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -9,9 +9,16 @@ import {
9
9
  normalizeToolNotifyContext,
10
10
  notifyToolCompletion,
11
11
  } from '../../../../shared/tool-execution-contract.mjs';
12
+ import {
13
+ renderShellCompletionEnvelope,
14
+ shellCompletionInstruction,
15
+ renderShellPromptStallEnvelope,
16
+ shellPromptStallInstruction,
17
+ } from '../../../../shared/task-notification-envelope.mjs';
12
18
  import {
13
19
  completeBackgroundTask,
14
20
  notifyBackgroundTaskProgress,
21
+ getBackgroundTask,
15
22
  } from '../../../../shared/background-tasks.mjs';
16
23
  import { startChildGuardian } from '../../../../shared/child-guardian.mjs';
17
24
 
@@ -729,22 +736,21 @@ export function watchBackgroundShellJob(jobId, notifyCtx) {
729
736
  const elapsedMs = Number.isFinite(startedAtMs) ? Math.max(0, finishedAtMs - startedAtMs) : null;
730
737
  const exitCode = (typeof detail.exitCode === 'number') ? detail.exitCode : null;
731
738
  const status = detail.status || (reason === 'timeout' ? 'running' : 'unknown');
732
- const lines = [
733
- `[task_id: ${jobId}]`,
734
- `[status: ${status}]`,
735
- `[exit: ${exitCode === null ? 'n/a' : exitCode}]`,
736
- elapsedMs !== null ? `[elapsed: ${elapsedMs} ms]` : null,
737
- detail.command ? `[command: ${detail.command}]` : null,
738
- '',
739
- detail.summary ? `Summary: ${detail.summary}` : null,
740
- detail.stdoutPreview ? `\n[stdout preview]\n${detail.stdoutPreview}` : null,
741
- (detail.mergeStderr !== true && detail.stderrPreview) ? `\n[stderr preview]\n${detail.stderrPreview}` : null,
742
- ].filter((l) => l !== null && l !== '');
743
- const body = lines.join('\n');
739
+ const body = renderShellCompletionEnvelope({
740
+ jobId,
741
+ status,
742
+ exitCode,
743
+ elapsedMs,
744
+ command: detail.command,
745
+ summary: detail.summary,
746
+ stdoutPreview: detail.stdoutPreview,
747
+ stderrPreview: detail.stderrPreview,
748
+ mergeStderr: detail.mergeStderr,
749
+ });
744
750
  const taskStatus = status === 'completed'
745
751
  ? 'completed'
746
752
  : (status === 'cancelled' ? 'cancelled' : 'failed');
747
- const instruction = `The background shell task ${jobId} you started earlier has finished (${status}, exit ${exitCode === null ? 'n/a' : exitCode}) - review this result in your next step.`;
753
+ const instruction = shellCompletionInstruction({ jobId, status, exitCode });
748
754
  const completedTask = completeBackgroundTask(jobId, {
749
755
  status: taskStatus,
750
756
  result: shellJobPublicTaskResult(detail),
@@ -780,33 +786,38 @@ export function watchBackgroundShellJob(jobId, notifyCtx) {
780
786
  if (now - lastOutputAtMs < SHELL_JOB_PROMPT_STALL_MS) return;
781
787
  if (!looksLikeInteractivePrompt(tail.text)) return;
782
788
  const elapsedMs = now - (Date.parse(detail.startedAt || '') || now);
783
- const body = [
784
- `[task_id: ${jobId}]`,
785
- '[status: running]',
786
- `[stalled: no output growth for ${now - lastOutputAtMs} ms]`,
787
- elapsedMs >= 0 ? `[elapsed: ${elapsedMs} ms]` : null,
788
- detail.command ? `[command: ${detail.command}]` : null,
789
- '',
790
- 'This background shell task appears to be waiting for interactive input. Background tasks cannot answer prompts automatically; cancel it or rerun with non-interactive flags/input.',
791
- tail.text ? `\n${tail.text}` : null,
792
- ].filter((line) => line !== null && line !== '').join('\n');
793
- const instruction = `The background shell task ${jobId} appears to be waiting for interactive input; inspect the prompt, then cancel or rerun it non-interactively.`;
794
- const sent = notifyBackgroundTaskProgress(jobId, {
795
- text: body,
796
- resultType: 'shell_task_progress',
797
- instruction,
798
- key: 'interactive-prompt-stall',
799
- status: null,
800
- }) || notifyToolCompletion({
801
- surface: 'shell',
802
- id: jobId,
803
- status: null,
804
- text: body,
805
- resultType: 'shell_task_progress',
806
- instruction,
807
- context: ctx,
808
- logPrefix: 'shell-jobs',
789
+ const body = renderShellPromptStallEnvelope({
790
+ jobId,
791
+ stalledMs: now - lastOutputAtMs,
792
+ elapsedMs,
793
+ command: detail.command,
794
+ tailText: tail.text,
809
795
  });
796
+ const instruction = shellPromptStallInstruction({ jobId });
797
+ // Prefer the progress-notify path when a background task row exists.
798
+ // Its once-key ('interactive-prompt-stall') dedupe returns false after
799
+ // a re-arm even though the row is present — do NOT fall back to
800
+ // notifyToolCompletion in that case (it would fire a duplicate). Only
801
+ // use the completion fallback when the task row is absent (progress
802
+ // path unavailable).
803
+ const sent = getBackgroundTask(jobId)
804
+ ? notifyBackgroundTaskProgress(jobId, {
805
+ text: body,
806
+ resultType: 'shell_task_progress',
807
+ instruction,
808
+ key: 'interactive-prompt-stall',
809
+ status: null,
810
+ })
811
+ : notifyToolCompletion({
812
+ surface: 'shell',
813
+ id: jobId,
814
+ status: null,
815
+ text: body,
816
+ resultType: 'shell_task_progress',
817
+ instruction,
818
+ context: ctx,
819
+ logPrefix: 'shell-jobs',
820
+ });
810
821
  if (sent) promptStallNotified = true;
811
822
  };
812
823
  const checkDone = (reason) => {
@@ -1,9 +1,10 @@
1
1
  import { countSplitLines } from './path-utils.mjs';
2
+ import { TOOL_OUTPUT_MAX_BYTES } from './tool-output-limit.mjs';
2
3
 
3
- export const SHELL_OUTPUT_MAX_CHARS = 30_000;
4
+ export const SHELL_OUTPUT_MAX_CHARS = TOOL_OUTPUT_MAX_BYTES;
4
5
 
5
6
  export const SMART_BASH_MAX_LINES = 400;
6
- export const SMART_BASH_MAX_BYTES = 30_000;
7
+ export const SMART_BASH_MAX_BYTES = TOOL_OUTPUT_MAX_BYTES;
7
8
  export const SMART_BASH_HEAD_LINES = 80;
8
9
  export const SMART_BASH_TAIL_LINES = 80;
9
10
 
@@ -0,0 +1,10 @@
1
+ // Single source of truth for the model-facing tool-output byte budget.
2
+ // read / shell / list-tree all truncate their output to this cap so the
3
+ // context cost of any one tool call is bounded and consistent. Override with
4
+ // MIXDOG_TOOL_OUTPUT_MAX_BYTES. Line-based sub-caps (e.g. bash max lines) stay
5
+ // per-tool; this governs only the byte budget.
6
+ function _envInt(name, fallback) {
7
+ const v = parseInt(process.env[name], 10);
8
+ return Number.isFinite(v) && v > 0 ? v : fallback;
9
+ }
10
+ export const TOOL_OUTPUT_MAX_BYTES = _envInt('MIXDOG_TOOL_OUTPUT_MAX_BYTES', 50 * 1024);
@@ -46,6 +46,7 @@ import {
46
46
  extractPdfText,
47
47
  } from './builtin/read-special-files.mjs';
48
48
  import { computeUnifiedDiff } from './builtin/diff-utils.mjs';
49
+ import { tryExecuteExternalToolAdapter, isExternalAdapterTool } from './builtin/external-tool-adapters.mjs';
49
50
  import { formatToolStartProgress } from './progress-message.mjs';
50
51
  import { BUILTIN_TOOLS } from './builtin/builtin-tools.mjs';
51
52
  import { validateBuiltinArgs } from './builtin/arg-guard.mjs';
@@ -277,6 +278,8 @@ const EXTERNAL_TOOL_REDIRECTS = new Map([
277
278
  ['strreplace', 'use `apply_patch` with a `*** Update File:` V4A section'],
278
279
  ['str_replace', 'use `apply_patch` with a `*** Update File:` V4A section'],
279
280
  ['str_replace_editor', 'use `apply_patch` with a `*** Update File:` V4A section'],
281
+ ['search_replace', 'use `apply_patch` with a `*** Update File:` V4A section'],
282
+ ['applypatch', 'call the `apply_patch` tool (snake_case) with a V4A patch'],
280
283
  ['createfile', 'use `apply_patch` with an `*** Add File:` V4A section'],
281
284
  ['create_file', 'use `apply_patch` with an `*** Add File:` V4A section'],
282
285
  ['deletefile', 'use `apply_patch` with a `*** Delete File:` V4A section'],
@@ -287,9 +290,15 @@ const EXTERNAL_TOOL_REDIRECTS = new Map([
287
290
  ['terminal', 'use the `shell` tool'],
288
291
  ['view', 'use the `read` tool'],
289
292
  ['cat', 'use the `read` tool'],
293
+ ['read_file', 'use the `read` tool'],
290
294
  ['ls', 'use the `list` tool'],
295
+ ['list_dir', 'use the `list` tool'],
291
296
  ['searchfiles', 'use the `grep` tool'],
297
+ ['file_search', 'use the `find` or `glob` tool'],
298
+ ['grep_search', 'use the `grep` tool'],
292
299
  ['codebase_search', 'use the `grep` or `code_graph` tool'],
300
+ ['semanticsearch', 'use the `grep` or `code_graph` tool'],
301
+ ['semantic_search', 'use the `grep` or `code_graph` tool'],
293
302
  ]);
294
303
 
295
304
  export function canonicalizeBuiltinToolName(name) {
@@ -401,6 +410,55 @@ function capToolOutput(result, options = {}) {
401
410
  return `${head}\n... [tool-output truncated: ${Math.round(bytes / 1024)} KB -> ${Math.round(cap / 1024)} KB cap (tool_output_token_limit)] ...\n${tail}`;
402
411
  }
403
412
 
413
+ const _GREP_SWEEP_NUDGE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_ENABLE_GREP_SWEEP_NUDGE || ''));
414
+ const _GREP_SWEEP_MIN_CALLS = 20;
415
+ const _GREP_SWEEP_MIN_UNIQUE = 16;
416
+ const _grepSweepState = new Map();
417
+
418
+ function _grepSweepTerms(pattern) {
419
+ const raw = Array.isArray(pattern) ? pattern.join('|') : String(pattern || '');
420
+ return [...new Set(raw
421
+ .split(/[|,\s()"'`[\]{}.*+?^$\\:-]+/g)
422
+ .map((s) => s.trim().toLowerCase())
423
+ .filter((s) => s.length >= 4))];
424
+ }
425
+
426
+ function _normalizeSweepPath(value) {
427
+ return String(value || '.')
428
+ .replace(/\\/g, '/')
429
+ .replace(/^([A-Za-z]):/, (_, d) => d.toLowerCase() + ':')
430
+ .replace(/\/+/g, '/')
431
+ .replace(/\/$/, '') || '.';
432
+ }
433
+
434
+ function _grepSweepKey(args = {}) {
435
+ const path = _normalizeSweepPath(args.path || '.');
436
+ const glob = Array.isArray(args.glob) ? args.glob.join(',') : String(args.glob || '');
437
+ const terms = _grepSweepTerms(args.pattern).slice(0, 3).join('|');
438
+ return `${path}::${glob}::${terms}`;
439
+ }
440
+
441
+ function _maybeAppendGrepSweepNudge(toolName, args, result, scope) {
442
+ if (!_GREP_SWEEP_NUDGE_ENABLED || toolName !== 'grep' || typeof result !== 'string') return result;
443
+ const key = scope || 'global';
444
+ const now = Date.now();
445
+ let state = _grepSweepState.get(key);
446
+ if (!state || now - state.lastTs > 10 * 60_000) {
447
+ state = { count: 0, unique: new Set(), lastTs: 0, nudged: false };
448
+ _grepSweepState.set(key, state);
449
+ }
450
+ state.count += 1;
451
+ state.unique.add(_grepSweepKey(args));
452
+ state.lastTs = now;
453
+ if (_grepSweepState.size > 256) {
454
+ const oldest = [..._grepSweepState.entries()].sort((a, b) => (a[1].lastTs || 0) - (b[1].lastTs || 0)).slice(0, 32);
455
+ for (const [oldKey] of oldest) _grepSweepState.delete(oldKey);
456
+ }
457
+ if (state.nudged || state.count < _GREP_SWEEP_MIN_CALLS || state.unique.size < _GREP_SWEEP_MIN_UNIQUE) return result;
458
+ state.nudged = true;
459
+ return `${result}\n\n[grep_sweep_hint] Many distinct grep searches were used in this session. Stop rewording search terms; synthesize from existing anchors or switch tools once (code_graph/find/read known regions).`;
460
+ }
461
+
404
462
  export async function executeBuiltinTool(name, args, cwd, options = {}) {
405
463
  if (options.abortSignal && !options.signal) {
406
464
  options = { ...options, signal: options.abortSignal };
@@ -460,10 +518,15 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
460
518
  case 'hex':
461
519
  return executeHexTool(args, workDir, readStateScope, _readModeHelpers);
462
520
  default:
521
+ {
522
+ const adapted = await tryExecuteExternalToolAdapter(name, args, workDir, options);
523
+ if (adapted !== null) return adapted;
524
+ }
463
525
  return formatUnknownBuiltinToolMessage(name, args);
464
526
  }
465
527
  })();
466
- return capToolOutput(_toolResult, options);
528
+ const _withNudge = _maybeAppendGrepSweepNudge(toolName, args, _toolResult, readStateScope);
529
+ return capToolOutput(_withNudge, options);
467
530
  }
468
531
  /**
469
532
  * Check if a tool name is a builtin tool.
@@ -473,6 +536,12 @@ export function isBuiltinTool(name) {
473
536
  return BUILTIN_TOOLS.some(t => t.name === toolName);
474
537
  }
475
538
 
539
+ // Re-export so the session loop can route foreign-CLI tool names
540
+ // (StrReplace/Write/bash variants) into executeBuiltinTool — whose default:
541
+ // case runs the native adapter — instead of short-circuiting to the
542
+ // unknown-tool redirect message at the dispatch layer.
543
+ export { isExternalAdapterTool };
544
+
476
545
  // Test-only exports for smart truncation helpers (see
477
546
  // scripts/test-smart-truncation.mjs). Runtime callers inside this module
478
547
  // use the local bindings unchanged; these named exports just make the
@@ -0,0 +1,303 @@
1
+ // Graph build orchestration: worker-thread async build (buildCodeGraphAsync),
2
+ // synchronous _buildCodeGraph (imported by the prewarm worker), and the
3
+ // prewarm entry points. Owns the in-flight single-flight map. Extracted
4
+ // verbatim from code-graph.mjs.
5
+ //
6
+ // SELF-REFERENTIAL DYNAMIC IMPORT: buildCodeGraphAsync spawns the prewarm
7
+ // worker via `new URL('../code-graph-prewarm-worker.mjs', import.meta.url)`.
8
+ // That worker in turn does `import { _buildCodeGraph } from '../code-graph.mjs'`
9
+ // (the facade), which re-exports _buildCodeGraph from THIS module. The worker
10
+ // path is unchanged relative to the facade, so it keeps importing the facade —
11
+ // no module imports its own path. The URL below is resolved against THIS
12
+ // module's dir (tools/code-graph/), so it walks one level up to reach the
13
+ // worker at tools/.
14
+ import { resolve as pathResolve } from 'node:path';
15
+ import { Worker } from 'node:worker_threads';
16
+ import { acquire as acquireChildSpawnSlot } from '../../../../shared/child-spawn-gate.mjs';
17
+ import {
18
+ canonicalGraphCwd as _canonicalGraphCwd,
19
+ codeGraphCache as _codeGraphCache,
20
+ consumeCodeGraphDirtyPaths as _consumeCodeGraphDirtyPaths,
21
+ getCodeGraphGen as _getCodeGraphGen,
22
+ } from '../code-graph-state.mjs';
23
+ import {
24
+ CODE_GRAPH_TTL_MS,
25
+ CODE_GRAPH_MAX_FILES,
26
+ CODE_GRAPH_WORKER_TIMEOUT_MS,
27
+ SYMBOL_SCHEMA_VERSION,
28
+ } from './constants.mjs';
29
+ import {
30
+ _computeGraphSignature,
31
+ _deserializeGraph,
32
+ _attachGraphRuntimeCaches,
33
+ } from './graph-model.mjs';
34
+ import { _touchCodeGraphCache, _setCodeGraphCache } from './memory-cache.mjs';
35
+ import {
36
+ ensureDiskCodeGraphLoaded,
37
+ getDiskCodeGraphEntry,
38
+ _setDiskCodeGraphEntry,
39
+ } from './disk-cache.mjs';
40
+ import {
41
+ _runGraphManifest,
42
+ _runGraphWalk,
43
+ _runGraphFiles,
44
+ _fileInfoFromRustRecord,
45
+ _reuseFileInfo,
46
+ } from './graph-binary.mjs';
47
+ import { _lookupCandidateNodes } from './symbol-index.mjs';
48
+ import { _findDirProjectRoot } from './project-root.mjs';
49
+
50
+ // In-flight async builds keyed by canonical graphCwd. Same-cwd parallel
51
+ // callers (prewarm + cache-miss + multiple find_symbol) share one Worker
52
+ // spawn instead of fanning out. Entry removed on settle so the next caller
53
+ // after a failure can retry.
54
+ const _inflightAsyncBuilds = new Map();
55
+
56
+ export function prewarmCodeGraph(cwd) {
57
+ if (!cwd) return;
58
+ // Reuse the buildCodeGraphAsync single-flight path. Fire-and-forget.
59
+ buildCodeGraphAsync(cwd).catch(() => { /* best-effort */ });
60
+ }
61
+
62
+ export function prewarmCodeGraphSymbols(cwd, symbols, { language = null } = {}) {
63
+ if (!cwd) return;
64
+ const wanted = (Array.isArray(symbols) ? symbols : [symbols])
65
+ .map((s) => String(s || '').trim())
66
+ .filter(Boolean);
67
+ buildCodeGraphAsync(cwd).then((graph) => {
68
+ if (!graph) return;
69
+ for (const symbol of wanted) {
70
+ try { _lookupCandidateNodes(graph, symbol, language); } catch { /* best-effort */ }
71
+ }
72
+ }).catch(() => { /* best-effort */ });
73
+ }
74
+
75
+ export function prewarmCodeGraphIfProject(cwd) {
76
+ if (!cwd) return false;
77
+ const root = _findDirProjectRoot(cwd);
78
+ if (!root) return false;
79
+ prewarmCodeGraph(root);
80
+ return true;
81
+ }
82
+
83
+ export async function buildCodeGraphAsync(cwd, signal = null) {
84
+ if (signal?.aborted) throw new Error('aborted');
85
+ const graphCwd = _canonicalGraphCwd(cwd);
86
+ const cached = _codeGraphCache.get(graphCwd);
87
+ if (cached?.graph && Date.now() - cached.ts < CODE_GRAPH_TTL_MS) {
88
+ _touchCodeGraphCache(graphCwd);
89
+ return cached.graph;
90
+ }
91
+ const existing = _inflightAsyncBuilds.get(graphCwd);
92
+ if (existing) {
93
+ if (!signal) return existing;
94
+ let onAbort = null;
95
+ const abortP = new Promise((_, reject) => {
96
+ onAbort = () => reject(new Error('aborted'));
97
+ signal.addEventListener('abort', onAbort, { once: true });
98
+ });
99
+ const cleanup = () => {
100
+ if (onAbort) {
101
+ try { signal.removeEventListener('abort', onAbort); } catch {}
102
+ onAbort = null;
103
+ }
104
+ };
105
+ return Promise.race([existing, abortP]).then(
106
+ (v) => { cleanup(); return v; },
107
+ (e) => { cleanup(); throw e; },
108
+ );
109
+ }
110
+ const _genAtStart = _getCodeGraphGen(graphCwd);
111
+ let _worker = null;
112
+ const promise = new Promise((resolve, reject) => {
113
+ let settled = false;
114
+ let timeout = null;
115
+ let _onSignalAbort = null;
116
+ let _releaseSlot = null;
117
+ const settle = (val) => {
118
+ if (settled) return;
119
+ settled = true;
120
+ if (timeout) { clearTimeout(timeout); timeout = null; }
121
+ if (_onSignalAbort && signal) {
122
+ try { signal.removeEventListener('abort', _onSignalAbort); } catch {}
123
+ _onSignalAbort = null;
124
+ }
125
+ if (_releaseSlot) { try { _releaseSlot(); } catch {} _releaseSlot = null; }
126
+ _inflightAsyncBuilds.delete(graphCwd);
127
+ if (val instanceof Error) reject(val);
128
+ else resolve(val);
129
+ };
130
+ acquireChildSpawnSlot(signal || null).then((release) => {
131
+ _releaseSlot = release;
132
+ if (settled) { release(); _releaseSlot = null; return; }
133
+ const workerUrl = new URL('../code-graph-prewarm-worker.mjs', import.meta.url);
134
+ _worker = new Worker(workerUrl, {
135
+ workerData: { cwd },
136
+ execArgv: [],
137
+ });
138
+ const w = _worker;
139
+ timeout = setTimeout(() => {
140
+ try { _worker?.terminate(); } catch {}
141
+ settle(new Error(`code-graph worker timed out after ${CODE_GRAPH_WORKER_TIMEOUT_MS}ms for cwd=${graphCwd}`));
142
+ }, CODE_GRAPH_WORKER_TIMEOUT_MS);
143
+ timeout.unref?.();
144
+ if (signal) {
145
+ _onSignalAbort = () => {
146
+ try { _worker?.terminate(); } catch {}
147
+ settle(new Error('aborted'));
148
+ };
149
+ signal.addEventListener('abort', _onSignalAbort, { once: true });
150
+ }
151
+ w.once('message', (msg) => {
152
+ try {
153
+ if (msg && msg.ok && msg.graph && typeof msg.signature === 'string') {
154
+ if (_getCodeGraphGen(graphCwd) === _genAtStart) {
155
+ _setCodeGraphCache(graphCwd, { ts: Date.now(), signature: msg.signature, graph: msg.graph });
156
+ _setDiskCodeGraphEntry(graphCwd, msg.graph);
157
+ }
158
+ settle(msg.graph);
159
+ } else {
160
+ settle(new Error('code-graph prewarm worker failed'));
161
+ }
162
+ } catch (e) { settle(e instanceof Error ? e : new Error(String(e))); }
163
+ });
164
+ w.once('error', (e) => settle(e instanceof Error ? e : new Error(String(e))));
165
+ }, (e) => settle(e instanceof Error ? e : new Error(String(e))));
166
+ });
167
+ _inflightAsyncBuilds.set(graphCwd, promise);
168
+ return promise;
169
+ }
170
+
171
+ /**
172
+ * Internal — exported (via the facade) solely for code-graph-prewarm-worker.mjs
173
+ * to import. NOT part of the public API. External callers should use
174
+ * buildCodeGraphAsync (worker-thread isolated) or the code_graph / find_symbol
175
+ * tools, never this synchronous form on the main event loop.
176
+ */
177
+ export async function _buildCodeGraph(cwd) {
178
+ const now = Date.now();
179
+ let _tp = performance.now();
180
+ const _trace = (label) => { if (process.env.MIXDOG_GRAPH_TRACE) { const n = performance.now(); process.stderr.write(`[cg-trace] ${label}=${(n - _tp).toFixed(0)}ms\n`); _tp = n; } };
181
+ const graphCwd = _canonicalGraphCwd(cwd);
182
+ const absRoot = graphCwd;
183
+ const _genAtStart = _getCodeGraphGen(graphCwd);
184
+ const cached = _codeGraphCache.get(graphCwd);
185
+ let previousGraph = cached?.graph || null;
186
+ _consumeCodeGraphDirtyPaths(graphCwd);
187
+
188
+ // 1. Change-detect via Rust --manifest (fp/rel/size only, no parse).
189
+ const manifest = await _runGraphManifest(absRoot);
190
+ const signature = _computeGraphSignature(manifest);
191
+ _trace('manifest+sig');
192
+ const truncated = manifest.length > CODE_GRAPH_MAX_FILES;
193
+ const indexed = truncated ? manifest.slice(0, CODE_GRAPH_MAX_FILES) : manifest;
194
+
195
+ // 2. Memory cache hit.
196
+ if (cached && cached.signature === signature && now - cached.ts < CODE_GRAPH_TTL_MS) {
197
+ _touchCodeGraphCache(graphCwd);
198
+ return cached.graph;
199
+ }
200
+
201
+ // 3. Disk cache hit.
202
+ ensureDiskCodeGraphLoaded(now);
203
+ const diskEntry = getDiskCodeGraphEntry(graphCwd);
204
+ if (diskEntry?.signature === signature) {
205
+ const graph = _deserializeGraph(graphCwd, diskEntry);
206
+ if (graph) {
207
+ if (_getCodeGraphGen(graphCwd) === _genAtStart) {
208
+ _setCodeGraphCache(graphCwd, { ts: now, signature, graph });
209
+ }
210
+ return graph;
211
+ }
212
+ }
213
+ if (!previousGraph && diskEntry) previousGraph = _deserializeGraph(graphCwd, diskEntry);
214
+ if (previousGraph && previousGraph.schemaVersion !== SYMBOL_SCHEMA_VERSION) {
215
+ previousGraph = null;
216
+ }
217
+
218
+ // 4. Build fileInfos. Reuse unchanged nodes by fp; parse the rest in Rust.
219
+ const reusable = [];
220
+ const freshRels = [];
221
+ for (const meta of indexed) {
222
+ const previousNode = previousGraph?.nodes?.get(meta.rel) || null;
223
+ if (previousNode && previousNode.fingerprint === meta.fp) {
224
+ reusable.push(_reuseFileInfo(previousNode, previousGraph, absRoot));
225
+ } else {
226
+ freshRels.push(meta.rel);
227
+ }
228
+ }
229
+ let fileInfos;
230
+ if (freshRels.length === 0) {
231
+ fileInfos = reusable;
232
+ } else if (reusable.length > 0 && freshRels.length <= 256) {
233
+ const recs = await _runGraphFiles(absRoot, freshRels, reusable);
234
+ const reusedByRel = new Map(reusable.map((info) => [info.rel, info]));
235
+ const freshSet = new Set(freshRels);
236
+ fileInfos = [...reusable];
237
+ for (const rec of recs) {
238
+ if (freshSet.has(rec.rel)) {
239
+ fileInfos.push(_fileInfoFromRustRecord(rec, absRoot));
240
+ } else {
241
+ const reusedInfo = reusedByRel.get(rec.rel);
242
+ if (!reusedInfo) continue;
243
+ const resolved = Array.isArray(rec.resolvedImports)
244
+ ? rec.resolvedImports.filter((v) => typeof v === 'string')
245
+ : [];
246
+ reusedInfo.resolvedImports = resolved;
247
+ if (Array.isArray(rec.importedBy)) {
248
+ reusedInfo.importedBy = rec.importedBy.filter((v) => typeof v === 'string');
249
+ }
250
+ }
251
+ }
252
+ } else {
253
+ let recs = await _runGraphWalk(absRoot);
254
+ if (recs.length > CODE_GRAPH_MAX_FILES) recs = recs.slice(0, CODE_GRAPH_MAX_FILES);
255
+ fileInfos = recs.map((rec) => _fileInfoFromRustRecord(rec, absRoot));
256
+ }
257
+ _trace('walk+parse');
258
+ const nodes = new Map();
259
+ const reverse = new Map();
260
+ for (const info of fileInfos) {
261
+ const resolvedImportsRel = Array.isArray(info.resolvedImports) ? info.resolvedImports : [];
262
+ const importedBy = Array.isArray(info.importedBy) ? info.importedBy : [];
263
+ const node = {
264
+ abs: info.abs,
265
+ rel: info.rel,
266
+ lang: info.lang,
267
+ fingerprint: info.fingerprint,
268
+ rawImports: info.rawImports,
269
+ resolvedImportsRel,
270
+ resolvedImports: resolvedImportsRel.map((rel) => pathResolve(absRoot, rel)),
271
+ importedBy,
272
+ packageName: info.packageName,
273
+ namespaceName: info.namespaceName,
274
+ goPackageName: info.goPackageName,
275
+ topLevelTypes: info.topLevelTypes,
276
+ tokenSymbols: info.tokenSymbols,
277
+ symbols: Array.isArray(info.symbols) ? info.symbols : [],
278
+ };
279
+ nodes.set(info.rel, node);
280
+ for (const rel of resolvedImportsRel) {
281
+ if (!reverse.has(rel)) reverse.set(rel, new Set());
282
+ reverse.get(rel).add(node.rel);
283
+ }
284
+ }
285
+ _trace('assemble');
286
+ const graph = _attachGraphRuntimeCaches({ cwd: graphCwd, nodes, reverse, schemaVersion: SYMBOL_SCHEMA_VERSION, builtAt: now, signature });
287
+ graph.truncated = Boolean(truncated);
288
+ for (const info of fileInfos) {
289
+ if (typeof info.sourceText === 'string') {
290
+ graph._sourceTextCache.set(info.rel, {
291
+ fingerprint: info.fingerprint || '',
292
+ text: info.sourceText,
293
+ });
294
+ }
295
+ }
296
+ graph._symbolTokenIndexDirty = true;
297
+ if (_getCodeGraphGen(graphCwd) === _genAtStart) {
298
+ _setCodeGraphCache(graphCwd, { ts: now, signature, graph });
299
+ _setDiskCodeGraphEntry(graphCwd, graph);
300
+ _trace('cache+disk');
301
+ }
302
+ return graph;
303
+ }
@@ -0,0 +1,43 @@
1
+ // Code-graph tuning constants + cache-file name/regex patterns. Extracted
2
+ // verbatim from code-graph.mjs so the build/cache/search modules can share
3
+ // one source of truth. Behavior-identical: same env vars, same defaults.
4
+
5
+ export const CODE_GRAPH_TTL_MS = 30_000;
6
+ export const CODE_GRAPH_MAX_FILES = 10_000;
7
+ export const CODE_GRAPH_WORKER_TIMEOUT_MS = 120_000;
8
+ // Timeout for the native mixdog-graph binary child process (spawned per graph build).
9
+ export const CODE_GRAPH_BINARY_TIMEOUT_MS = Math.max(1000, Number(process.env.MIXDOG_CODE_GRAPH_BINARY_TIMEOUT_MS) || 20000);
10
+ // Legacy single-file cache. Kept as a constant for the one-shot migration
11
+ // path; new writes go into the per-cwd directory layout below.
12
+ export const CODE_GRAPH_DISK_FILE = 'code-graph-cache.json';
13
+ // Per-cwd cache: <data>/code-graph-cache/manifest.json + <hash>.json per
14
+ // indexed root. Avoids the unbounded single-file blob (observed >50 MB on
15
+ // long-running workspaces) that had to be JSON.parsed in full on every
16
+ // fresh process startup.
17
+ export const CODE_GRAPH_DISK_DIR = 'code-graph-cache';
18
+ export const CODE_GRAPH_DISK_MAX_ENTRIES = 24;
19
+ export const CODE_GRAPH_DISK_MAX_BYTES = Math.max(
20
+ 1 * 1024 * 1024,
21
+ Math.floor((Number(process.env.MIXDOG_CODE_GRAPH_CACHE_MAX_MB) || 80) * 1024 * 1024),
22
+ );
23
+ // Reap writeFileAtomicSync debris only after this age (see _sweepCodeGraphCacheDir).
24
+ // Younger .tmp files may belong to an in-flight persist still holding the sibling .lock;
25
+ // DEFAULT_LOCK_TIMEOUT_MS is 8s — 120s is a safe margin for large graph JSON writes.
26
+ export const ORPHAN_TMP_MIN_AGE_MS = 120_000;
27
+ export const RE_CACHE_TMP = /^\.[0-9a-f]{16}\.json\.[0-9a-f]{24}\.tmp$/i;
28
+ export const RE_MANIFEST_TMP = /^\.manifest\.json\.[0-9a-f]{24}\.tmp$/i;
29
+ export const RE_CACHE_LOCK = /^[0-9a-f]{16}\.json\.lock$/i;
30
+ export const CODE_GRAPH_MEMORY_MAX_ENTRIES = Math.max(
31
+ 1,
32
+ Math.floor(Number(process.env.MIXDOG_CODE_GRAPH_MEMORY_MAX_ENTRIES) || 6),
33
+ );
34
+ export const CODE_GRAPH_MEMORY_MAX_SOURCE_BYTES = Math.max(
35
+ 1 * 1024 * 1024,
36
+ Math.floor((Number(process.env.MIXDOG_CODE_GRAPH_MEMORY_MAX_MB) || 48) * 1024 * 1024),
37
+ );
38
+ // Bump when the per-symbol record SHAPE changes (e.g. adding endLine). The
39
+ // version is folded into the cache signature so graphs built by an older
40
+ // binary/schema (symbols without a finite endLine) no longer match and are
41
+ // rebuilt instead of served — otherwise a stale cache would feed endLine-less
42
+ // symbols and silently defeat body-span containment in _nearestEnclosingSymbol.
43
+ export const SYMBOL_SCHEMA_VERSION = 'sym-range-v3-rustimports';