mixdog 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -1,4 +1,4 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, spawnSync } from 'child_process';
2
2
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, watch as fsWatch, writeFileSync } from 'fs';
3
3
  import * as fsPromises from 'fs/promises';
4
4
  import { basename, join } from 'path';
@@ -15,6 +15,8 @@ import {
15
15
  } from '../../../../shared/background-tasks.mjs';
16
16
  import { startChildGuardian } from '../../../../shared/child-guardian.mjs';
17
17
 
18
+ globalThis.__mixdogShellJobsRuntimeLoaded = true;
19
+
18
20
  function sleep(ms) {
19
21
  return new Promise((resolve) => setTimeout(resolve, ms));
20
22
  }
@@ -220,29 +222,62 @@ function killProcessTree(pid, signal = 'SIGTERM') {
220
222
  // owned children. Async jobs are intentionally CLI-owned; no restart replay or
221
223
  // daemon handoff is attempted.
222
224
  const _liveJobPids = new Set();
225
+ const _liveJobIdsByPid = new Map();
223
226
  let _shellJobsExitHookInstalled = false;
224
- function _registerLiveJobPid(pid) {
225
- if (Number.isFinite(pid) && pid > 0) _liveJobPids.add(pid);
227
+ function _registerLiveJobPid(pid, jobId = null) {
228
+ if (Number.isFinite(pid) && pid > 0) {
229
+ _liveJobPids.add(pid);
230
+ if (jobId) _liveJobIdsByPid.set(pid, jobId);
231
+ }
226
232
  }
227
233
  function _unregisterLiveJobPid(pid) {
228
- if (Number.isFinite(pid) && pid > 0) _liveJobPids.delete(pid);
234
+ if (Number.isFinite(pid) && pid > 0) {
235
+ _liveJobPids.delete(pid);
236
+ _liveJobIdsByPid.delete(pid);
237
+ }
229
238
  }
230
- function _sweepLiveJobsSync() {
231
- for (const pid of _liveJobPids) {
232
- try {
233
- if (process.platform === 'win32') {
234
- spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' });
239
+ function _killLiveJobPid(pid, { sync = false } = {}) {
240
+ try {
241
+ if (process.platform === 'win32') {
242
+ if (sync) {
243
+ spawnSync('taskkill.exe', ['/pid', String(pid), '/t', '/f'], {
244
+ windowsHide: true,
245
+ stdio: 'ignore',
246
+ timeout: 1500,
247
+ });
235
248
  } else {
236
- try { process.kill(-pid, 'SIGKILL'); }
237
- catch { try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ } }
249
+ spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' });
238
250
  }
239
- } catch { /* ignore */ }
251
+ } else {
252
+ try { process.kill(-pid, 'SIGKILL'); }
253
+ catch { try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ } }
254
+ }
255
+ return true;
256
+ } catch {
257
+ return false;
258
+ }
259
+ }
260
+ function _sweepLiveJobsSync() {
261
+ for (const pid of _liveJobPids) {
262
+ _killLiveJobPid(pid, { sync: true });
240
263
  }
241
264
  _liveJobPids.clear();
265
+ _liveJobIdsByPid.clear();
266
+ }
267
+ function _ensureProcessListenerHeadroom(events, extra = 1) {
268
+ try {
269
+ if (typeof process.getMaxListeners !== 'function' || typeof process.setMaxListeners !== 'function') return;
270
+ const current = process.getMaxListeners();
271
+ if (current === 0) return;
272
+ let needed = current;
273
+ for (const event of events) needed = Math.max(needed, process.listenerCount(event) + extra);
274
+ if (needed > current) process.setMaxListeners(needed);
275
+ } catch { /* ignore */ }
242
276
  }
243
277
  function _installShellJobsExitHook() {
244
278
  if (_shellJobsExitHookInstalled) return;
245
279
  _shellJobsExitHookInstalled = true;
280
+ _ensureProcessListenerHeadroom(['exit', 'SIGTERM', 'SIGINT', 'SIGHUP'], 1);
246
281
  try { process.on('exit', _sweepLiveJobsSync); } catch { /* ignore */ }
247
282
  try { process.on('SIGTERM', _sweepLiveJobsSync); } catch { /* ignore */ }
248
283
  try { process.on('SIGINT', _sweepLiveJobsSync); } catch { /* ignore */ }
@@ -538,8 +573,8 @@ function refreshShellJob(jobId) {
538
573
  return detail;
539
574
  }
540
575
 
541
- export function startBackgroundShellJob({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellType, clientHostPid }) {
542
- return _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellType, clientHostPid });
576
+ export function startBackgroundShellJob({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellArgs, shellType, clientHostPid }) {
577
+ return _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellArgs, shellType, clientHostPid });
543
578
  }
544
579
 
545
580
  // In-process completion watcher. After a background shell task is spawned the
@@ -572,6 +607,20 @@ const jobNotifyCtxByJobId = new Map();
572
607
  // consuming the outcome, so the watcher must stay cancelled; the last waiter to
573
608
  // leave (count===0) owns the decision to re-arm a still-running job.
574
609
  const jobWaitWaiterCountByJobId = new Map();
610
+ function markShellJobCancelledByShutdown(jobId, reason = 'shutdown') {
611
+ const detail = readShellJobDetail(jobId);
612
+ if (!detail || detail.status !== 'running') return false;
613
+ detail.status = 'cancelled';
614
+ detail.exitCode = 137;
615
+ detail.killed = true;
616
+ detail.error = `cancelled by runtime shutdown (${reason})`;
617
+ detail.finishedAt = new Date().toISOString();
618
+ try { writeFileSync(detail.exitPath || shellJobExitPath(jobId), '137'); } catch { /* ignore */ }
619
+ try { writeFileSync(detail.donePath || shellJobDonePath(jobId), ''); } catch { /* ignore */ }
620
+ writeShellJobDetail(detail);
621
+ _unregisterLiveJobPid(detail.pid);
622
+ return true;
623
+ }
575
624
  // Register a synchronous task waiter. Paired with endShellJobWait in a
576
625
  // finally so the count can't leak on throw.
577
626
  export function beginShellJobWait(jobId) {
@@ -590,6 +639,26 @@ export function endShellJobWait(jobId) {
590
639
  export function clearShellJobNotifyCtx(jobId) {
591
640
  jobNotifyCtxByJobId.delete(jobId);
592
641
  }
642
+ export function shutdownShellJobs(reason = 'runtime-close', { sync = false } = {}) {
643
+ const livePids = [..._liveJobPids];
644
+ const jobIds = new Set([..._liveJobIdsByPid.values()].filter(Boolean));
645
+ const watcherJobIds = [...backgroundShellJobWatchers.keys()];
646
+ for (const jobId of watcherJobIds) {
647
+ jobIds.add(jobId);
648
+ try { cancelBackgroundShellJobWatch(jobId); } catch { /* ignore */ }
649
+ }
650
+ for (const pid of livePids) _killLiveJobPid(pid, { sync });
651
+ _liveJobPids.clear();
652
+ _liveJobIdsByPid.clear();
653
+ let marked = 0;
654
+ for (const jobId of jobIds) {
655
+ try { if (markShellJobCancelledByShutdown(jobId, reason)) marked += 1; } catch { /* ignore */ }
656
+ }
657
+ backgroundShellJobWatchers.clear();
658
+ jobNotifyCtxByJobId.clear();
659
+ jobWaitWaiterCountByJobId.clear();
660
+ return { killed: livePids.length, cancelledJobs: marked, cancelledWatchers: watcherJobIds.length };
661
+ }
593
662
  // Cancel (and unregister) an armed watcher without notifying. Idempotent: a
594
663
  // no-op when no watcher is armed, and the per-watcher cancel respects the
595
664
  // `settled` guard so it cannot race a real completion notify. The persistent
@@ -824,12 +893,12 @@ export function adoptForegroundShellJob({ command, cwd, pid, timeoutMs, mergeStd
824
893
  writeShellJobDetail(detail);
825
894
  if (Number.isFinite(pid) && pid > 0) {
826
895
  _installShellJobsExitHook();
827
- _registerLiveJobPid(pid);
896
+ _registerLiveJobPid(pid, jobId);
828
897
  }
829
898
  return { ...detail, exitPath, donePath };
830
899
  }
831
900
 
832
- function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellType, clientHostPid }) {
901
+ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellArgs, shellType, clientHostPid }) {
833
902
  // Route ANY PowerShell shell to the PS wrapper, regardless of platform.
834
903
  // Gating on win32 sent shell:'powershell' on macOS/Linux down the POSIX
835
904
  // path below, which spawns `pwsh <wrapper>.sh` — pwsh then tries to run a
@@ -871,7 +940,8 @@ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr
871
940
  // it trivial to distinguish a timeout (124) from a user-side
872
941
  // SIGTERM exit (143).
873
942
  const innerShellQ = shellQuoteSingle(shell);
874
- const innerArgQ = shellQuoteSingle(shellArg);
943
+ const innerArgs = Array.isArray(shellArgs) && shellArgs.length > 0 ? shellArgs : [shellArg];
944
+ const innerArgsQ = innerArgs.filter((arg) => arg != null && String(arg).length > 0).map(shellQuoteSingle).join(' ');
875
945
  // Runtime enforcement proof: the wrapper touches <jobId>.enforced right
876
946
  // before exec'ing `timeout`, so the marker exists iff the timeout branch
877
947
  // actually ran under the wrapper's own env/cwd. A spawn-time probe can't
@@ -885,7 +955,7 @@ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr
885
955
  // status for processes that actually exited 0. `rm -- "$0"` removes
886
956
  // the staged wrapper .cmd.sh after donePath is published so a host
887
957
  // crash before this point still leaves the file for the sweep to GC.
888
- const wrapped = `{ if command -v timeout >/dev/null 2>&1; then _to=timeout; elif command -v gtimeout >/dev/null 2>&1; then _to=gtimeout; else _to=; fi; if [ -n "$_to" ]; then touch ${shellQuoteSingle(enforcedPath)}; "$_to" ${timeoutSeconds} ${innerShellQ} ${innerArgQ} ${userCmdQuoted}; else ${innerShellQ} ${innerArgQ} ${userCmdQuoted}; fi; rc=$?; printf '%s' "$rc" > ${shellQuoteSingle(exitPath)}; touch ${shellQuoteSingle(donePath)}; rm -- "$0" 2>/dev/null; exit $rc; }`;
958
+ const wrapped = `{ if command -v timeout >/dev/null 2>&1; then _to=timeout; elif command -v gtimeout >/dev/null 2>&1; then _to=gtimeout; else _to=; fi; if [ -n "$_to" ]; then touch ${shellQuoteSingle(enforcedPath)}; "$_to" ${timeoutSeconds} ${innerShellQ} ${innerArgsQ} ${userCmdQuoted}; else ${innerShellQ} ${innerArgsQ} ${userCmdQuoted}; fi; rc=$?; printf '%s' "$rc" > ${shellQuoteSingle(exitPath)}; touch ${shellQuoteSingle(donePath)}; rm -- "$0" 2>/dev/null; exit $rc; }`;
889
959
  // Stage the wrapped command to a .sh and let the script open its own
890
960
  // output files via `exec > … 2> …`. The parent does NOT pass file
891
961
  // descriptors via stdio inheritance (`stdio: 'ignore'` for all three).
@@ -919,7 +989,7 @@ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr
919
989
  label: 'shell-job',
920
990
  });
921
991
  _installShellJobsExitHook();
922
- _registerLiveJobPid(child.pid);
992
+ _registerLiveJobPid(child.pid, jobId);
923
993
  const detail = {
924
994
  jobId,
925
995
  kind: 'bash',
@@ -1056,7 +1126,7 @@ function startBackgroundPowerShellJob({ command, timeoutMs, workDir, mergeStderr
1056
1126
  return { jobId, kind: 'bash', status: 'failed', error: 'PowerShell background task spawn returned no pid' };
1057
1127
  }
1058
1128
  _installShellJobsExitHook();
1059
- _registerLiveJobPid(childPid);
1129
+ _registerLiveJobPid(childPid, jobId);
1060
1130
  const detail = {
1061
1131
  jobId,
1062
1132
  kind: 'bash',
@@ -3,7 +3,7 @@ import { join } from 'path';
3
3
  import { writeJsonAtomicSync } from '../../../../shared/atomic-file.mjs';
4
4
  import { resolvePluginData } from '../../../../shared/plugin-paths.mjs';
5
5
 
6
- // Mixdog read/write/edit share a scoped snapshot store. Value stores the
6
+ // Mixdog read/patch paths share a scoped snapshot store. Value stores the
7
7
  // mtime + size at read-time. A missing scope is fail-closed: snapshots without
8
8
  // a real scope id are NOT recorded under a shared default bucket (would let
9
9
  // worker A's read satisfy worker B's edit-gate across sessions via a persisted
@@ -19,6 +19,7 @@ import {
19
19
  } from './builtin/bash-tool.mjs';
20
20
  import {
21
21
  executeFindFilesTool,
22
+ executeFuzzyFindTool,
22
23
  executeListTool,
23
24
  executeTreeTool,
24
25
  } from './builtin/list-tool.mjs';
@@ -129,7 +130,7 @@ export {
129
130
  } from './builtin/cache-layers.mjs';
130
131
  export { atomicWrite } from './builtin/atomic-write.mjs';
131
132
  // ---------------------------------------------------------------------------
132
- // User-cwd persistence bridge: hook writes user-cwd.txt on SessionStart so
133
+ // User-cwd persistence hook: writes user-cwd.txt on SessionStart so
133
134
  // the MCP server (spawned from cache dir) resolves the correct sandbox root.
134
135
  // Helper extracted to src/shared/user-cwd.mjs so server-main.mjs can import
135
136
  // the same primitive without circular-import risk.
@@ -252,9 +253,54 @@ const BUILTIN_TOOL_ALIASES = new Map([
252
253
  ['golb', 'glob'],
253
254
  ]);
254
255
 
256
+ // Case-insensitive lookup of canonical builtin names, built once from the
257
+ // declarative BUILTIN_TOOLS registry. Models trained on other coding-agent
258
+ // CLIs frequently emit PascalCase tool names ("Read", "Shell", "Grep",
259
+ // "List") for tools mixdog exposes in lowercase. Those calls used to fall
260
+ // through to formatUnknownBuiltinToolMessage and, because the error did not
261
+ // resolve the case mismatch, the model re-issued the SAME capitalised call
262
+ // every iteration until the loop cap. A lowercase→canonical map makes the
263
+ // dispatcher accept the case variant directly.
264
+ const _BUILTIN_LOWER_TO_CANONICAL = new Map(
265
+ BUILTIN_TOOLS.map((t) => [String(t.name || '').toLowerCase(), t.name]).filter(([k]) => k),
266
+ );
267
+
268
+ // Well-known tool names from OTHER agent CLIs that have no mixdog builtin.
269
+ // They cannot be silently remapped (their argument shapes differ — e.g.
270
+ // Write{path,contents} vs apply_patch{patch}) so the unknown-tool error
271
+ // surfaces a concrete redirect instead of a bare "unknown tool", which is
272
+ // what kept models looping on a non-existent tool.
273
+ const EXTERNAL_TOOL_REDIRECTS = new Map([
274
+ ['write', 'use `apply_patch` with an `*** Add File:` V4A section to create a file'],
275
+ ['edit', 'use `apply_patch` with a `*** Update File:` V4A section'],
276
+ ['multiedit', 'use a single `apply_patch` call with multiple `*** Update File:` sections'],
277
+ ['strreplace', 'use `apply_patch` with a `*** Update File:` V4A section'],
278
+ ['str_replace', 'use `apply_patch` with a `*** Update File:` V4A section'],
279
+ ['str_replace_editor', 'use `apply_patch` with a `*** Update File:` V4A section'],
280
+ ['createfile', 'use `apply_patch` with an `*** Add File:` V4A section'],
281
+ ['create_file', 'use `apply_patch` with an `*** Add File:` V4A section'],
282
+ ['deletefile', 'use `apply_patch` with a `*** Delete File:` V4A section'],
283
+ ['delete_file', 'use `apply_patch` with a `*** Delete File:` V4A section'],
284
+ ['bash', 'use the `shell` tool'],
285
+ ['run', 'use the `shell` tool'],
286
+ ['runcommand', 'use the `shell` tool'],
287
+ ['terminal', 'use the `shell` tool'],
288
+ ['view', 'use the `read` tool'],
289
+ ['cat', 'use the `read` tool'],
290
+ ['ls', 'use the `list` tool'],
291
+ ['searchfiles', 'use the `grep` tool'],
292
+ ['codebase_search', 'use the `grep` or `code_graph` tool'],
293
+ ]);
294
+
255
295
  export function canonicalizeBuiltinToolName(name) {
256
296
  if (typeof name !== 'string') return name;
257
- return BUILTIN_TOOL_ALIASES.get(name) || name;
297
+ const aliased = BUILTIN_TOOL_ALIASES.get(name);
298
+ if (aliased) return aliased;
299
+ // Exact match wins (cheapest, most common path).
300
+ if (_BUILTIN_LOWER_TO_CANONICAL.get(name) === name) return name;
301
+ // Case-insensitive fall-through: "Read"/"SHELL"/"Grep" → canonical.
302
+ const lowerCanonical = _BUILTIN_LOWER_TO_CANONICAL.get(name.toLowerCase());
303
+ return lowerCanonical || name;
258
304
  }
259
305
 
260
306
  function editDistance(a, b) {
@@ -294,6 +340,12 @@ export function suggestBuiltinToolName(name) {
294
340
  }
295
341
 
296
342
  export function formatUnknownBuiltinToolMessage(name, _args = {}, noun = 'builtin tool') {
343
+ // Concrete redirect for a well-known tool from another agent CLI whose
344
+ // argument shape differs from any mixdog builtin (so it cannot be
345
+ // case-normalised into one). Without this, the model re-issues the same
346
+ // non-existent tool every iteration until the loop cap.
347
+ const redirect = typeof name === 'string' ? EXTERNAL_TOOL_REDIRECTS.get(name.toLowerCase()) : null;
348
+ if (redirect) return `Error: unknown ${noun} "${name}" — ${redirect}.`;
297
349
  const suggestion = suggestBuiltinToolName(name);
298
350
  if (!suggestion) return `Error: unknown ${noun} "${name}"`;
299
351
  return `Error: unknown ${noun} "${name}". Did you mean "${suggestion}"?`;
@@ -308,7 +360,7 @@ process.on('exit', flushReadRangeIndexesSync);
308
360
 
309
361
  configureReadRangeIndexTelemetry({ trace: _ioTrace, hashText: _hashText });
310
362
 
311
- // Uniform tool-output cap (Codex `tool_output_token_limit` analogue): a SINGLE
363
+ // Uniform tool-output cap (`tool_output_token_limit` analogue): a SINGLE
312
364
  // optional knob that bounds EVERY builtin string result before it enters the
313
365
  // lead context, on top of each tool's own caps. Default 0 = OFF (per-tool caps
314
366
  // only, no behaviour change). Set MIXDOG_TOOL_OUTPUT_MAX_BYTES (or pass
@@ -342,7 +394,7 @@ function capToolOutput(result, options = {}) {
342
394
  const bytes = Buffer.byteLength(result, 'utf8');
343
395
  if (bytes <= cap) return result;
344
396
  // Middle-truncate: keep head + tail (byte-accurate), drop the middle. Mirrors
345
- // Codex middle truncation so the model still sees both ends of a runaway.
397
+ // Middle truncation so the model still sees both ends of a runaway.
346
398
  const half = Math.max(1, Math.floor(cap / 2) - 64);
347
399
  const head = _sliceToBytesFromStart(result, half);
348
400
  const tail = _sliceToBytesFromEnd(result, half);
@@ -371,7 +423,7 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
371
423
  const readStateScope = options?.readStateScope ?? options?.sessionId ?? null;
372
424
  const executeChildBuiltinTool = (childName, childArgs, childCwd = workDir, childOptions = null) =>
373
425
  executeBuiltinTool(childName, childArgs, childCwd, childOptions ? { ...options, ...childOptions } : options);
374
- // Path policy: Claude Code's settings.json permissions (mcp__* allow) are the
426
+ // Path policy: host settings.json permissions (mcp__* allow) are the
375
427
  // sole arbiter for workspace-boundary decisions.
376
428
  const _toolResult = await (async () => {
377
429
  switch (toolName) {
@@ -389,6 +441,8 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
389
441
  return executeGrepTool(args, workDir, executeChildBuiltinTool, readStateScope, options);
390
442
  case 'glob':
391
443
  return executeGlobTool(args, workDir, options);
444
+ case 'find':
445
+ return executeFuzzyFindTool(args, workDir, options);
392
446
  case 'list':
393
447
  return executeListTool(args, workDir, options);
394
448
  case 'tree':
@@ -0,0 +1,86 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, join, resolve as pathResolve } from 'node:path';
3
+
4
+ export const codeGraphCache = new Map();
5
+ export const codeGraphDirtyPaths = new Map();
6
+
7
+ const codeGraphDirtyGen = new Map();
8
+ let drainCodeGraphCacheFn = null;
9
+
10
+ export function canonicalGraphCwd(cwd) {
11
+ if (!cwd) throw new Error('code_graph requires cwd - caller did not provide a working directory');
12
+ const full = pathResolve(cwd);
13
+ return process.platform === 'win32' ? full.toLowerCase() : full;
14
+ }
15
+
16
+ export function canonicalGraphPath(p) {
17
+ const full = pathResolve(String(p || ''));
18
+ return process.platform === 'win32' ? full.toLowerCase() : full;
19
+ }
20
+
21
+ export function getCodeGraphGen(graphCwd) {
22
+ return codeGraphDirtyGen.get(graphCwd) || 0;
23
+ }
24
+
25
+ export function bumpCodeGraphGen(graphCwd) {
26
+ codeGraphDirtyGen.set(graphCwd, (codeGraphDirtyGen.get(graphCwd) || 0) + 1);
27
+ }
28
+
29
+ export function consumeCodeGraphDirtyPaths(cwd) {
30
+ const key = canonicalGraphCwd(cwd);
31
+ const set = codeGraphDirtyPaths.get(key);
32
+ if (!set || set.size === 0) return [];
33
+ codeGraphDirtyPaths.delete(key);
34
+ return [...set];
35
+ }
36
+
37
+ // Accept absolute written paths only; resolve affected indexed roots centrally.
38
+ export function markCodeGraphDirtyPaths(paths) {
39
+ const values = Array.isArray(paths) ? paths : [paths];
40
+ const cleaned = values
41
+ .filter(Boolean)
42
+ .map((p) => canonicalGraphPath(p));
43
+ if (cleaned.length === 0) return;
44
+
45
+ const knownRoots = new Set([...codeGraphDirtyPaths.keys(), ...codeGraphCache.keys()]);
46
+ const affectedRoots = new Set();
47
+ for (const absPath of cleaned) {
48
+ let matchedThisPath = false;
49
+ for (const root of knownRoots) {
50
+ const canonRoot = canonicalGraphPath(root);
51
+ if (absPath.startsWith(canonRoot + '/') || absPath.startsWith(canonRoot + '\\') || absPath === canonRoot) {
52
+ affectedRoots.add(root);
53
+ matchedThisPath = true;
54
+ }
55
+ }
56
+ if (!matchedThisPath) {
57
+ let dir = dirname(absPath);
58
+ while (dir && dir !== dirname(dir)) {
59
+ if (existsSync(join(dir, 'package.json')) || existsSync(join(dir, '.git'))) {
60
+ affectedRoots.add(canonicalGraphCwd(dir));
61
+ break;
62
+ }
63
+ dir = dirname(dir);
64
+ }
65
+ }
66
+ }
67
+
68
+ for (const root of affectedRoots) {
69
+ if (!codeGraphDirtyPaths.has(root)) codeGraphDirtyPaths.set(root, new Set());
70
+ const set = codeGraphDirtyPaths.get(root);
71
+ for (const p of cleaned) set.add(p);
72
+
73
+ const canonRoot = canonicalGraphCwd(root);
74
+ codeGraphCache.delete(canonRoot);
75
+ bumpCodeGraphGen(canonRoot);
76
+ }
77
+ }
78
+
79
+ export function registerCodeGraphDrain(fn) {
80
+ drainCodeGraphCacheFn = typeof fn === 'function' ? fn : null;
81
+ }
82
+
83
+ export function drainCodeGraphCache() {
84
+ if (!drainCodeGraphCacheFn) return;
85
+ drainCodeGraphCacheFn();
86
+ }
@@ -3,20 +3,20 @@ export const CODE_GRAPH_TOOL_DEFS = [
3
3
  name: 'code_graph',
4
4
  title: 'Code Graph',
5
5
  annotations: { title: 'Code Graph', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false, compressibleLossless: true },
6
- description: 'Inspect code structure. Top-level entry for code-related questions: symbols, definitions, references, callers, callees, imports, and related files. Use before read when structure can narrow the target; use grep for exact text.',
6
+ description: 'Code structure: symbols, refs, calls, imports, impact.',
7
7
  inputSchema: {
8
8
  type: 'object',
9
9
  properties: {
10
- mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'search', 'references', 'callers', 'callees', 'prewarm'], description: 'Operation: repo/file overview, import graph, symbol lookup/search, references, callers/callees, or cache prewarm.' },
11
- file: { type: 'string', description: 'Target source file. Directory scope is only for references/callers; omit for repo-wide overview/search.' },
12
- symbol: { type: 'string', description: 'Identifier or search keyword for symbol/search/reference/call modes.' },
13
- symbols: { type: 'array', items: { type: 'string' }, description: 'Batch identifiers for find_symbol/callers/callees/references/prewarm.' },
14
- body: { type: 'boolean', description: 'Return full symbol body for find_symbol. Default true.' },
15
- language: { type: 'string', description: 'Optional language hint.' },
16
- limit: { type: 'number', minimum: 1, description: 'Max results to return; clamped per mode.' },
17
- depth: { type: 'number', minimum: 1, maximum: 5, description: 'callers only: 1 direct, 2-5 transitive.' },
18
- page: { type: 'number', minimum: 1, description: 'callers depth>=2 page.' },
19
- cwd: { type: 'string', description: 'Project root override; omit when runtime cwd is already the repo root.' },
10
+ mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'search', 'references', 'callers', 'callees', 'prewarm'], description: 'Operation.' },
11
+ file: { type: 'string', description: 'Source file.' },
12
+ symbol: { type: 'string', description: 'Identifier or symbol keyword.' },
13
+ symbols: { type: 'array', items: { type: 'string' }, description: 'Batch identifiers.' },
14
+ body: { type: 'boolean', description: 'Include body.' },
15
+ language: { type: 'string', description: 'Language hint.' },
16
+ limit: { type: 'number', minimum: 1, description: 'Max results.' },
17
+ depth: { type: 'number', minimum: 1, maximum: 5, description: 'Caller depth.' },
18
+ page: { type: 'number', minimum: 1, description: 'Caller page.' },
19
+ cwd: { type: 'string', description: 'Project root.' },
20
20
  },
21
21
  required: ['mode'],
22
22
  },