mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,8 +1,6 @@
1
- import { spawn, spawnSync } from 'child_process';
2
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, watch as fsWatch, writeFileSync } from 'fs';
3
- import * as fsPromises from 'fs/promises';
4
- import { basename, join } from 'path';
5
- import { getPluginData } from '../../config.mjs';
1
+ import { spawn } from 'child_process';
2
+ import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, watch as fsWatch, writeFileSync } from 'fs';
3
+ import { basename } from 'path';
6
4
  import { stripAnsi } from '../shell-command.mjs';
7
5
  import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
8
6
  import {
@@ -21,6 +19,31 @@ import {
21
19
  getBackgroundTask,
22
20
  } from '../../../../shared/background-tasks.mjs';
23
21
  import { startChildGuardian } from '../../../../shared/child-guardian.mjs';
22
+ import {
23
+ getShellJobsDir,
24
+ shellJobStdoutPath,
25
+ shellJobStderrPath,
26
+ shellJobExitPath,
27
+ shellJobDonePath,
28
+ shellJobEnforcedPath,
29
+ resolveJobOwnerHostPid,
30
+ writeShellJobDetail,
31
+ readShellJobDetail,
32
+ } from './shell-job-paths.mjs';
33
+ import {
34
+ isPidAlive,
35
+ killProcessTree,
36
+ _unregisterLiveJobPid,
37
+ _installShellJobsExitHook,
38
+ _registerLiveJobPid,
39
+ _liveJobPids,
40
+ _liveJobIdsByPid,
41
+ _killLiveJobPid,
42
+ } from './shell-job-process.mjs';
43
+
44
+ // Facade re-exports: path/detail helpers and the job-not-found message moved
45
+ // to sibling modules; keep existing importers of shell-jobs.mjs resolving.
46
+ export { buildJobNotFoundMessage } from './shell-job-paths.mjs';
24
47
 
25
48
  globalThis.__mixdogShellJobsRuntimeLoaded = true;
26
49
 
@@ -28,269 +51,9 @@ function sleep(ms) {
28
51
  return new Promise((resolve) => setTimeout(resolve, ms));
29
52
  }
30
53
 
31
- // One-shot sweep of stale shell-job artefacts. Each backgrounded `bash`
32
- // emits five files (.json/.done/.exit/.stdout.log/.stderr.log); the .done
33
- // flag is written when the job exits, so a .done file older than
34
- // SHELL_JOB_STALE_MS is the invariant proof its sibling files are also
35
- // safe to remove. Active and recently-completed jobs are kept so
36
- // `task` status/read/wait readers still find them. Runs once per mcp child
37
- // lifetime on first getShellJobsDir() call. Async dirent walk + parallel
38
- // stat/unlink keeps the main event loop free; fire-and-forget so the
39
- // synchronous caller receives `dir` immediately.
40
- const SHELL_JOB_STALE_MS = 24 * 60 * 60 * 1000;
41
- let shellJobsSwept = false;
42
- async function sweepStaleShellJobs(dir) {
43
- if (shellJobsSwept) return;
44
- shellJobsSwept = true;
45
- const cutoff = Date.now() - SHELL_JOB_STALE_MS;
46
- let names;
47
- try { names = await fsPromises.readdir(dir); } catch { return; }
48
- const expired = [];
49
- await Promise.all(names.map(async (name) => {
50
- if (!name.endsWith('.done')) return;
51
- const p = join(dir, name);
52
- try {
53
- const st = await fsPromises.stat(p);
54
- if (st.mtimeMs < cutoff) expired.push(name.slice(0, -5));
55
- } catch {}
56
- }));
57
- // Orphan reclaim: a crashed wrapper leaves <id>.json with no .done —
58
- // forever. Invariant proofs of death (either suffices, both gated on the
59
- // stale cutoff so a young orphan can't race its own spawn):
60
- // a) deadline: the wrapper enforces timeoutMs, so an entry older than
61
- // timeoutMs + grace cannot still be running — pid-reuse-proof. Only
62
- // trusted on runtime proof: detail.timeoutEnforced:true (PS wrapper,
63
- // unconditional) or the <id>.enforced marker the posix wrapper
64
- // touches when its `timeout` branch actually runs.
65
- // b) ESRCH: the recorded pid no longer exists. Alone this misses pids
66
- // recycled by unrelated live processes, hence (a).
67
- const ORPHAN_DEADLINE_GRACE_MS = 30 * 60_000;
68
- const doneSet = new Set(names.filter(n => n.endsWith('.done')).map(n => n.slice(0, -5)));
69
- await Promise.all(names.map(async (name) => {
70
- if (!name.endsWith('.json')) return;
71
- const jobId = name.slice(0, -5);
72
- if (doneSet.has(jobId)) return;
73
- const p = join(dir, name);
74
- try {
75
- const st = await fsPromises.stat(p);
76
- if (st.mtimeMs >= cutoff) return;
77
- const detail = JSON.parse(await fsPromises.readFile(p, 'utf-8'));
78
- const tmo = Number(detail?.timeoutMs);
79
- const enforced = detail?.timeoutEnforced === true
80
- || existsSync(join(dir, `${jobId}.enforced`));
81
- const deadlinePassed = enforced
82
- && Number.isFinite(tmo) && tmo > 0
83
- && (Date.now() - st.mtimeMs) > tmo + ORPHAN_DEADLINE_GRACE_MS;
84
- if (!deadlinePassed) {
85
- const pid = Number(detail?.pid);
86
- if (Number.isFinite(pid) && pid > 0) {
87
- try { process.kill(pid, 0); return; } // alive (or EPERM → treated dead only via ESRCH below)
88
- catch (e) { if (e?.code !== 'ESRCH') return; }
89
- }
90
- }
91
- expired.push(jobId);
92
- } catch {}
93
- }));
94
- // Owner sidecar markers carry a dynamic `.owner-<pid>` suffix, so they
95
- // can't sit in the fixed-extension list — map each expired jobId to the
96
- // marker name(s) actually present in this listing and unlink those too.
97
- const expiredSet = new Set(expired);
98
- const ownerMarkers = names.filter((n) => {
99
- const i = n.lastIndexOf('.owner-');
100
- return i > 0 && expiredSet.has(n.slice(0, i));
101
- });
102
- await Promise.all([
103
- ...expired.flatMap((jobId) =>
104
- ['.json', '.done', '.exit', '.enforced', '.exit.cmd.sh', '.exit.cmd.ps1', '.stdout.log', '.stderr.log'].map((ext) =>
105
- fsPromises.unlink(join(dir, jobId + ext)).catch(() => {}),
106
- ),
107
- ),
108
- ...ownerMarkers.map((n) => fsPromises.unlink(join(dir, n)).catch(() => {})),
109
- ]);
110
- }
111
-
112
- function getShellJobsDir() {
113
- const dir = join(getPluginData(), 'shell-jobs');
114
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
115
- sweepStaleShellJobs(dir);
116
- return dir;
117
- }
118
-
119
- function shellJobDetailPath(jobId) { return join(getShellJobsDir(), `${jobId}.json`); }
120
- function shellJobStdoutPath(jobId) { return join(getShellJobsDir(), `${jobId}.stdout.log`); }
121
- function shellJobStderrPath(jobId) { return join(getShellJobsDir(), `${jobId}.stderr.log`); }
122
- function shellJobExitPath(jobId) { return join(getShellJobsDir(), `${jobId}.exit`); }
123
- function shellJobDonePath(jobId) { return join(getShellJobsDir(), `${jobId}.done`); }
124
- // Runtime proof that the posix wrapper's `timeout` branch actually ran: the
125
- // wrapper touches this marker immediately before exec'ing `timeout`, so its
126
- // existence is never optimistic (no spawn-time probe can guarantee the
127
- // wrapper's own env/cwd resolution). PS jobs don't need it — their wrapper
128
- // enforces unconditionally and records detail.timeoutEnforced:true.
129
- function shellJobEnforcedPath(jobId) { return join(getShellJobsDir(), `${jobId}.enforced`); }
130
- // Owner sidecar marker: a zero-byte file whose NAME encodes the owning CC host
131
- // (claude.exe) pid — `<jobId>.owner-<pid>`. It lets the statusline owner-filter
132
- // jobs from a SINGLE directory listing (no per-job JSON read) so the filter can
133
- // precede its per-tick scan cap — otherwise other sessions' newer jobs evict
134
- // this session's live jobs before filtering. Swept alongside the other
135
- // artefacts.
136
- function shellJobOwnerPath(jobId, pid) { return join(getShellJobsDir(), `${jobId}.owner-${pid}`); }
137
-
138
54
  const JOB_STATUS_PREVIEW_MAX_BYTES = 4096;
139
55
  const JOB_STATUS_PREVIEW_MAX_LINES = 20;
140
56
  const JOB_STATUS_PREVIEW_MAX_CHARS = 1200;
141
-
142
- // Resolve the CLI host pid that owns a freshly-spawned job. Standalone CLI
143
- // execution is process-owned: background tasks are scoped to this CLI lifetime,
144
- // and the pid stamp is only for statusline filtering / legacy sidecars.
145
- function resolveJobOwnerHostPid(clientHostPid) {
146
- const explicit = Number(clientHostPid);
147
- if (Number.isInteger(explicit) && explicit > 0) return explicit;
148
- const envPid = Number(process.env.MIXDOG_OWNER_HOST_PID);
149
- if (Number.isInteger(envPid) && envPid > 0) return envPid;
150
- return null;
151
- }
152
-
153
- function writeShellJobDetail(detail) {
154
- // Session scope stamp: every job record is tagged with the CC host pid
155
- // that owns it (the claude.exe pid). This is the SAME physical pid the
156
- // statusline shim passes as --client-host-pid, so the statusline can count
157
- // only its own session's jobs by exact pid equality (no heuristic). The
158
- // spawn sites set detail.ownerHostPid from the per-request threaded
159
- // clientHostPid (resolveJobOwnerHostPid); this fallback only stamps the
160
- // single-client env value when no spawn-site pid was set, and NEVER
161
- // overwrites an existing stamp — so the field round-trips through disk on
162
- // refresh/kill and a correct per-terminal stamp is preserved across rewrites.
163
- if (detail && detail.ownerHostPid == null) {
164
- const hostPid = Number(process.env.MIXDOG_OWNER_HOST_PID);
165
- if (Number.isInteger(hostPid) && hostPid > 0) detail.ownerHostPid = hostPid;
166
- }
167
- writeFileSync(shellJobDetailPath(detail.jobId), JSON.stringify(detail, null, 2), 'utf-8');
168
- // Owner sidecar: encode the resolved owner pid in the marker filename so the
169
- // statusline can owner-filter from the directory listing alone (before its
170
- // scan cap). Idempotent zero-byte write — safe to repeat on refresh/kill
171
- // rewrites. Skipped when no owner is known (legacy/unattributed jobs).
172
- if (detail && Number.isInteger(detail.ownerHostPid) && detail.ownerHostPid > 0) {
173
- try { writeFileSync(shellJobOwnerPath(detail.jobId, detail.ownerHostPid), '', 'utf-8'); }
174
- catch { /* best-effort marker */ }
175
- }
176
- }
177
-
178
- function readShellJobDetail(jobId) {
179
- try {
180
- const p = shellJobDetailPath(jobId);
181
- if (!existsSync(p)) return null;
182
- return JSON.parse(readFileSync(p, 'utf-8'));
183
- } catch {
184
- return null;
185
- }
186
- }
187
-
188
- export function buildJobNotFoundMessage(jobId) {
189
- return `Error: task not found: ${jobId}`;
190
- }
191
-
192
- function isPidAlive(pid) {
193
- if (!Number.isFinite(pid) || pid <= 0) return false;
194
- try {
195
- process.kill(pid, 0);
196
- return true;
197
- } catch {
198
- return false;
199
- }
200
- }
201
-
202
- function killProcessTree(pid, signal = 'SIGTERM') {
203
- if (!Number.isFinite(pid) || pid <= 0) return false;
204
- try {
205
- if (process.platform === 'win32') {
206
- spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' });
207
- } else {
208
- try { process.kill(-pid, signal); }
209
- catch { process.kill(pid, signal); }
210
- // SIGKILL escalation: a background child that ignores SIGTERM must
211
- // not survive (foreground treeKill / persistent _killProcessTree
212
- // already do this). After a 3s grace, force-kill the group/pid.
213
- // unref so this backstop never holds the host process open.
214
- if (signal === 'SIGTERM') {
215
- const t = setTimeout(() => {
216
- try { process.kill(-pid, 'SIGKILL'); }
217
- catch { try { process.kill(pid, 'SIGKILL'); } catch { /* already gone */ } }
218
- }, 3000);
219
- if (t.unref) t.unref();
220
- }
221
- }
222
- return true;
223
- } catch {
224
- return false;
225
- }
226
- }
227
-
228
- // Module-level tracking of live background-job pids so CLI shutdown can reap
229
- // owned children. Async jobs are intentionally CLI-owned; no restart replay or
230
- // daemon handoff is attempted.
231
- const _liveJobPids = new Set();
232
- const _liveJobIdsByPid = new Map();
233
- let _shellJobsExitHookInstalled = false;
234
- function _registerLiveJobPid(pid, jobId = null) {
235
- if (Number.isFinite(pid) && pid > 0) {
236
- _liveJobPids.add(pid);
237
- if (jobId) _liveJobIdsByPid.set(pid, jobId);
238
- }
239
- }
240
- function _unregisterLiveJobPid(pid) {
241
- if (Number.isFinite(pid) && pid > 0) {
242
- _liveJobPids.delete(pid);
243
- _liveJobIdsByPid.delete(pid);
244
- }
245
- }
246
- function _killLiveJobPid(pid, { sync = false } = {}) {
247
- try {
248
- if (process.platform === 'win32') {
249
- if (sync) {
250
- spawnSync('taskkill.exe', ['/pid', String(pid), '/t', '/f'], {
251
- windowsHide: true,
252
- stdio: 'ignore',
253
- timeout: 1500,
254
- });
255
- } else {
256
- spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' });
257
- }
258
- } else {
259
- try { process.kill(-pid, 'SIGKILL'); }
260
- catch { try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ } }
261
- }
262
- return true;
263
- } catch {
264
- return false;
265
- }
266
- }
267
- function _sweepLiveJobsSync() {
268
- for (const pid of _liveJobPids) {
269
- _killLiveJobPid(pid, { sync: true });
270
- }
271
- _liveJobPids.clear();
272
- _liveJobIdsByPid.clear();
273
- }
274
- function _ensureProcessListenerHeadroom(events, extra = 1) {
275
- try {
276
- if (typeof process.getMaxListeners !== 'function' || typeof process.setMaxListeners !== 'function') return;
277
- const current = process.getMaxListeners();
278
- if (current === 0) return;
279
- let needed = current;
280
- for (const event of events) needed = Math.max(needed, process.listenerCount(event) + extra);
281
- if (needed > current) process.setMaxListeners(needed);
282
- } catch { /* ignore */ }
283
- }
284
- function _installShellJobsExitHook() {
285
- if (_shellJobsExitHookInstalled) return;
286
- _shellJobsExitHookInstalled = true;
287
- _ensureProcessListenerHeadroom(['exit', 'SIGTERM', 'SIGINT', 'SIGHUP'], 1);
288
- try { process.on('exit', _sweepLiveJobsSync); } catch { /* ignore */ }
289
- try { process.on('SIGTERM', _sweepLiveJobsSync); } catch { /* ignore */ }
290
- try { process.on('SIGINT', _sweepLiveJobsSync); } catch { /* ignore */ }
291
- try { process.on('SIGHUP', _sweepLiveJobsSync); } catch { /* ignore */ }
292
- }
293
-
294
57
  function shellQuoteSingle(s) {
295
58
  return `'${String(s).replace(/'/g, `'\"'\"'`)}'`;
296
59
  }
@@ -35,8 +35,6 @@ import {
35
35
  executeGlobTool,
36
36
  executeGrepTool,
37
37
  } from './builtin/search-tool.mjs';
38
- import { executeDiagnosticsTool } from './builtin/diagnostics-tool.mjs';
39
- import { executeOpenConfigTool } from './builtin/open-config-tool.mjs';
40
38
  import {
41
39
  configureReadRangeIndexTelemetry,
42
40
  flushReadRangeIndexesSync,
@@ -491,10 +489,6 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
491
489
  return executeTaskTool(args, options);
492
490
  case 'read':
493
491
  return executeReadTool(args, workDir, readStateScope, executeChildBuiltinTool, options, _readToolHelpers);
494
- case 'diagnostics':
495
- return executeDiagnosticsTool(args, workDir, options);
496
- case 'open_config':
497
- return executeOpenConfigTool();
498
492
  case 'grep':
499
493
  return executeGrepTool(args, workDir, executeChildBuiltinTool, readStateScope, options);
500
494
  case 'glob':
@@ -38,6 +38,7 @@ import {
38
38
  } from './search.mjs';
39
39
 
40
40
  const CODE_GRAPH_BATCHABLE_MODES = new Set(['symbol', 'find_symbol', 'symbol_search', 'callers', 'callees', 'references']);
41
+ const CODE_GRAPH_FILE_BATCHABLE_MODES = new Set(['imports', 'dependents', 'related', 'impact', 'symbols', 'overview']);
41
42
 
42
43
  function _collectGraphSymbolList(args) {
43
44
  const split = (s) => String(s || '').split(/[,\s]+/).map((t) => t.trim()).filter(Boolean);
@@ -48,6 +49,23 @@ function _collectGraphSymbolList(args) {
48
49
  ])];
49
50
  }
50
51
 
52
+ const CODE_GRAPH_FILE_BATCH_CAP = 20;
53
+
54
+ function _collectGraphFileList(args) {
55
+ const split = (s) => String(s || '').split(/,+/).map((t) => t.trim()).filter(Boolean);
56
+ const list = [...new Set([
57
+ ...(Array.isArray(args?.files) ? args.files.map((f) => String(f || '').trim()).filter(Boolean) : []),
58
+ ...(typeof args?.files === 'string' ? split(args.files) : []),
59
+ ...(typeof args?.file === 'string' && args.file.trim() ? [args.file.trim()] : []),
60
+ ])];
61
+ if (list.length > CODE_GRAPH_FILE_BATCH_CAP) {
62
+ const capped = list.slice(0, CODE_GRAPH_FILE_BATCH_CAP);
63
+ capped._capped = true;
64
+ return capped;
65
+ }
66
+ return list;
67
+ }
68
+
51
69
  async function codeGraph(args, cwd, signal = null, options = {}) {
52
70
  let mode = String(args?.mode || '').trim();
53
71
  if (!mode) throw new Error('code_graph: "mode" is required');
@@ -208,11 +226,27 @@ async function codeGraph(args, cwd, signal = null, options = {}) {
208
226
  }
209
227
 
210
228
  if (mode === 'symbol_search') {
211
- const keyword = String(args?.symbol || '').trim();
212
- if (!keyword) throw new Error('code_graph symbol_search: "symbol" is required.');
213
229
  const language = String(args?.language || '').trim() || null;
214
230
  const limit = Math.max(1, Math.min(100, Number(args?.limit || 30)));
215
- return _searchSymbolsByKeyword(graph, keyword, cwd, { language, limit });
231
+ const symbolsList = Array.isArray(args?.symbols)
232
+ ? args.symbols.map((s) => String(s || '').trim()).filter(Boolean)
233
+ : [];
234
+ const keyword = String(args?.symbol || '').trim();
235
+ const keywords = symbolsList.length ? symbolsList : (keyword ? [keyword] : []);
236
+ if (!keywords.length) throw new Error('code_graph symbol_search: "symbol" (or "symbols[]") is required.');
237
+ if (keywords.length === 1) {
238
+ return _searchSymbolsByKeyword(graph, keywords[0], cwd, { language, limit });
239
+ }
240
+ // Batch: merge results across symbols, dedupe identical result blocks.
241
+ const seen = new Set();
242
+ const sections = [];
243
+ for (const kw of keywords) {
244
+ const result = _searchSymbolsByKeyword(graph, kw, cwd, { language, limit });
245
+ if (seen.has(result)) continue;
246
+ seen.add(result);
247
+ sections.push(`# symbol_search: ${kw}\n${result}`);
248
+ }
249
+ return sections.join('\n\n');
216
250
  }
217
251
 
218
252
  if (mode === 'references') {
@@ -468,6 +502,26 @@ export async function executeCodeGraphTool(name, args, cwd, signal = null, optio
468
502
  return dispatchOne({ ...args, symbol: symbolList[0], symbols: undefined });
469
503
  }
470
504
  }
505
+ if (CODE_GRAPH_FILE_BATCHABLE_MODES.has(batchMode)) {
506
+ const fileList = _collectGraphFileList(args);
507
+ if (fileList.length > 1) {
508
+ const capped = fileList._capped;
509
+ return (async () => {
510
+ const sections = [];
511
+ for (const f of fileList) {
512
+ let body;
513
+ try { body = await dispatchOne({ ...args, file: f, files: undefined }); }
514
+ catch (e) { body = `Error: ${e?.message || String(e)}`; }
515
+ sections.push(`# ${batchMode} ${f}\n${body}`);
516
+ }
517
+ if (capped) sections.push(`Note: file list capped at ${CODE_GRAPH_FILE_BATCH_CAP} entries.`);
518
+ return sections.join('\n\n');
519
+ })();
520
+ }
521
+ if (fileList.length === 1 && args?.file !== fileList[0]) {
522
+ return dispatchOne({ ...args, file: fileList[0], files: undefined });
523
+ }
524
+ }
471
525
  return dispatchOne(args);
472
526
  }
473
527
  default: throw new Error(`Unknown code-graph tool: ${name}`);
@@ -0,0 +1,82 @@
1
+ // Keyword-to-symbol fuzzy matching: tokenization, token-aligned contiguous
2
+ // match, ordered token match, and ranking key. Pure string helpers with no
3
+ // graph/cache state. Extracted from search.mjs.
4
+
5
+ export function _keywordSymbolSortKey(symbolName, keyword) {
6
+ const lowerName = String(symbolName || '').toLowerCase();
7
+ const lowerKey = String(keyword || '').toLowerCase();
8
+ const idx = lowerName.indexOf(lowerKey);
9
+ if (idx < 0) return null;
10
+ const atStart = idx === 0 ? 0 : 1;
11
+ return [lowerName.length, atStart, idx, symbolName];
12
+ }
13
+
14
+ export function _tokenizeKeyword(s) {
15
+ return String(s || '')
16
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
17
+ .split(/[^a-zA-Z0-9]+/)
18
+ .filter(Boolean)
19
+ .map((t) => t.toLowerCase());
20
+ }
21
+
22
+ function _tokenStartOffsets(sym) {
23
+ const starts = new Set();
24
+ let prevAlnum = false;
25
+ let prevUpper = false;
26
+ for (let i = 0; i < sym.length; i += 1) {
27
+ const c = sym[i];
28
+ const isAlnum = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
29
+ if (!isAlnum) { prevAlnum = false; prevUpper = false; continue; }
30
+ const isUpper = c >= 'A' && c <= 'Z';
31
+ if (!prevAlnum) starts.add(i);
32
+ else if (isUpper && !prevUpper) starts.add(i);
33
+ prevAlnum = true;
34
+ prevUpper = isUpper;
35
+ }
36
+ return starts;
37
+ }
38
+
39
+ function _contiguousMatchTokenAligned(sym, lowerKey) {
40
+ const len = lowerKey.length;
41
+ if (!len) return false;
42
+ const symLower = sym.toLowerCase();
43
+ const starts = _tokenStartOffsets(sym);
44
+ let lead = 0;
45
+ while (lead < len) {
46
+ const c = lowerKey[lead];
47
+ const isAlnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
48
+ if (isAlnum) break;
49
+ lead += 1;
50
+ }
51
+ let from = 0;
52
+ for (;;) {
53
+ const idx = symLower.indexOf(lowerKey, from);
54
+ if (idx < 0) return false;
55
+ const end = idx + len;
56
+ const effectiveIdx = idx + lead;
57
+ if (starts.has(effectiveIdx)) return true;
58
+ let interiorBoundary = false;
59
+ for (const s of starts) {
60
+ if (s > effectiveIdx && s < end) { interiorBoundary = true; break; }
61
+ }
62
+ if (!interiorBoundary) return true;
63
+ from = idx + 1;
64
+ }
65
+ }
66
+
67
+ function _orderedTokenMatch(symLower, tokens) {
68
+ let from = 0;
69
+ for (const t of tokens) {
70
+ const i = symLower.indexOf(t, from);
71
+ if (i < 0) return false;
72
+ from = i + t.length;
73
+ }
74
+ return true;
75
+ }
76
+
77
+ export function _keywordMatchesSymbolName(name, lowerKey, keyTokens) {
78
+ const sym = String(name || '').trim();
79
+ if (!sym || !lowerKey) return false;
80
+ if (_contiguousMatchTokenAligned(sym, lowerKey)) return true;
81
+ return keyTokens.length >= 2 && _orderedTokenMatch(sym.toLowerCase(), keyTokens);
82
+ }
@@ -19,6 +19,16 @@ import {
19
19
  } from './symbol-index.mjs';
20
20
  import { CODE_GRAPH_MAX_FILES } from './constants.mjs';
21
21
  import { _inferSpanEndByIndent } from './span.mjs';
22
+ import {
23
+ _toByteColumn,
24
+ _byteColToCharCol,
25
+ _nearestEnclosingSymbol,
26
+ } from './text-columns.mjs';
27
+ import {
28
+ _keywordSymbolSortKey,
29
+ _tokenizeKeyword,
30
+ _keywordMatchesSymbolName,
31
+ } from './keyword-match.mjs';
22
32
 
23
33
  export function _extractCallees(graph, declHit, _cwd, { cap = 200, callerSymbol = null, language = null } = {}) {
24
34
  if (!declHit || !_CALLEES_BRACE_LANGS.has(declHit.lang)) return [];
@@ -535,85 +545,6 @@ function _resolveCalleeDeclaration(graph, name, { language = null, preferRel = n
535
545
  return _pickCalleeDeclHit(_findSymbolHits(graph, name, { language }), preferRel);
536
546
  }
537
547
 
538
- function _keywordSymbolSortKey(symbolName, keyword) {
539
- const lowerName = String(symbolName || '').toLowerCase();
540
- const lowerKey = String(keyword || '').toLowerCase();
541
- const idx = lowerName.indexOf(lowerKey);
542
- if (idx < 0) return null;
543
- const atStart = idx === 0 ? 0 : 1;
544
- return [lowerName.length, atStart, idx, symbolName];
545
- }
546
-
547
- function _tokenizeKeyword(s) {
548
- return String(s || '')
549
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
550
- .split(/[^a-zA-Z0-9]+/)
551
- .filter(Boolean)
552
- .map((t) => t.toLowerCase());
553
- }
554
-
555
- function _tokenStartOffsets(sym) {
556
- const starts = new Set();
557
- let prevAlnum = false;
558
- let prevUpper = false;
559
- for (let i = 0; i < sym.length; i += 1) {
560
- const c = sym[i];
561
- const isAlnum = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
562
- if (!isAlnum) { prevAlnum = false; prevUpper = false; continue; }
563
- const isUpper = c >= 'A' && c <= 'Z';
564
- if (!prevAlnum) starts.add(i);
565
- else if (isUpper && !prevUpper) starts.add(i);
566
- prevAlnum = true;
567
- prevUpper = isUpper;
568
- }
569
- return starts;
570
- }
571
-
572
- function _contiguousMatchTokenAligned(sym, lowerKey) {
573
- const len = lowerKey.length;
574
- if (!len) return false;
575
- const symLower = sym.toLowerCase();
576
- const starts = _tokenStartOffsets(sym);
577
- let lead = 0;
578
- while (lead < len) {
579
- const c = lowerKey[lead];
580
- const isAlnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
581
- if (isAlnum) break;
582
- lead += 1;
583
- }
584
- let from = 0;
585
- for (;;) {
586
- const idx = symLower.indexOf(lowerKey, from);
587
- if (idx < 0) return false;
588
- const end = idx + len;
589
- const effectiveIdx = idx + lead;
590
- if (starts.has(effectiveIdx)) return true;
591
- let interiorBoundary = false;
592
- for (const s of starts) {
593
- if (s > effectiveIdx && s < end) { interiorBoundary = true; break; }
594
- }
595
- if (!interiorBoundary) return true;
596
- from = idx + 1;
597
- }
598
- }
599
-
600
- function _orderedTokenMatch(symLower, tokens) {
601
- let from = 0;
602
- for (const t of tokens) {
603
- const i = symLower.indexOf(t, from);
604
- if (i < 0) return false;
605
- from = i + t.length;
606
- }
607
- return true;
608
- }
609
-
610
- function _keywordMatchesSymbolName(name, lowerKey, keyTokens) {
611
- const sym = String(name || '').trim();
612
- if (!sym || !lowerKey) return false;
613
- if (_contiguousMatchTokenAligned(sym, lowerKey)) return true;
614
- return keyTokens.length >= 2 && _orderedTokenMatch(sym.toLowerCase(), keyTokens);
615
- }
616
-
617
548
  function _nativeSymbolHit(node, sym) {
618
549
  const line = Number(sym?.line ?? sym?.startLine);
619
550
  if (!Number.isFinite(line) || line < 1) return null;
@@ -1009,49 +940,6 @@ function _referenceKind(line, symbol, lang = null) {
1009
940
  return 'reference';
1010
941
  }
1011
942
 
1012
- function _toByteColumn(lineText, charCol) {
1013
- if (!Number.isFinite(charCol) || charCol < 1) return charCol;
1014
- const prefix = String(lineText || '').slice(0, charCol - 1);
1015
- return Buffer.byteLength(prefix, 'utf8') + 1;
1016
- }
1017
-
1018
- function _byteColToCharCol(lineText, byteCol) {
1019
- if (!Number.isFinite(byteCol) || byteCol < 1) return 1;
1020
- const s = String(lineText || '');
1021
- let bytes = 0;
1022
- let k = 0;
1023
- while (k < s.length && bytes < byteCol - 1) {
1024
- const cp = s.codePointAt(k);
1025
- bytes += Buffer.byteLength(String.fromCodePoint(cp), 'utf8');
1026
- k += cp > 0xFFFF ? 2 : 1;
1027
- }
1028
- return k + 1;
1029
- }
1030
-
1031
- function _nearestEnclosingSymbol(node, sourceText, lineNumber, col = null) {
1032
- const FUNCTION_LIKE = new Set([
1033
- 'function', 'method', 'arrow', 'class', 'generator', 'fn', 'async-function',
1034
- 'constructor', 'record', 'local-function',
1035
- ]);
1036
- const symbols = Array.isArray(node?.symbols) ? node.symbols : [];
1037
- const inRange = (item) => {
1038
- if (item.line > lineNumber || Number(item.endLine) < lineNumber) return false;
1039
- if (col != null) {
1040
- const sl = Number(item.startLine);
1041
- const sc = Number(item.startCol);
1042
- const ec = Number(item.endCol);
1043
- if (Number.isFinite(sl) && sl === lineNumber && Number.isFinite(sc) && col < sc) return false;
1044
- if (Number(item.endLine) === lineNumber && Number.isFinite(ec) && col > ec) return false;
1045
- }
1046
- return true;
1047
- };
1048
- const candidates = symbols
1049
- .filter(inRange)
1050
- .sort((a, b) => (b.line - a.line) || ((Number(b.startCol) || 0) - (Number(a.startCol) || 0)));
1051
- const fn = candidates.find((item) => FUNCTION_LIKE.has(String(item.kind || '').toLowerCase()));
1052
- return fn || candidates[0] || null;
1053
- }
1054
-
1055
943
  function _collectCallerEntries(graph, symbol, referenceText) {
1056
944
  const entries = _parseReferenceEntries(referenceText);
1057
945
  const detailed = [];