mixdog 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -0,0 +1,720 @@
1
+ // Deferred-tool catalog: measured-usage ordering, kind/bucket classification,
2
+ // tool_search ranking + auto-selection, and the session tool-surface
3
+ // application/selection logic. Pure module (session objects passed in).
4
+ import { clean } from './session-text.mjs';
5
+ import { estimateToolSchemaTokens } from '../runtime/agent/orchestrator/session/context-utils.mjs';
6
+ import {
7
+ isResponsesFreeformTool,
8
+ toResponsesCustomTool,
9
+ } from '../runtime/agent/orchestrator/providers/custom-tool-wire.mjs';
10
+
11
+ export const MEASURED_TOOL_USAGE = Object.freeze({
12
+ read: 710,
13
+ code_graph: 520,
14
+ grep: 500,
15
+ find: 480,
16
+ glob: 460,
17
+ list: 430,
18
+ apply_patch: 400,
19
+ explore: 360,
20
+ agent: 330,
21
+ shell: 81,
22
+ cwd: 2,
23
+ diagnostics: 2,
24
+ recall: 2,
25
+ search: 2,
26
+ web_fetch: 2,
27
+ provider_status: 2,
28
+ channel_status: 2,
29
+ });
30
+ const MEASURED_TOOL_ORDER = Object.freeze(Object.keys(MEASURED_TOOL_USAGE));
31
+ export const DEFERRED_DEFAULT_FULL_TOOLS = Object.freeze([
32
+ 'read',
33
+ 'code_graph',
34
+ 'grep',
35
+ 'find',
36
+ 'glob',
37
+ 'list',
38
+ 'explore',
39
+ 'apply_patch',
40
+ 'Skill',
41
+ 'tool_search',
42
+ ]);
43
+ export const DEFERRED_DEFAULT_READONLY_TOOLS = Object.freeze([
44
+ 'read',
45
+ 'code_graph',
46
+ 'grep',
47
+ 'find',
48
+ 'glob',
49
+ 'list',
50
+ 'explore',
51
+ 'Skill',
52
+ 'tool_search',
53
+ ]);
54
+ export const DEFERRED_DEFAULT_LEAD_TOOLS = Object.freeze([
55
+ 'read',
56
+ 'code_graph',
57
+ 'grep',
58
+ 'find',
59
+ 'glob',
60
+ 'list',
61
+ 'shell',
62
+ 'task',
63
+ 'explore',
64
+ 'apply_patch',
65
+ 'agent',
66
+ 'recall',
67
+ 'search',
68
+ 'web_fetch',
69
+ 'cwd',
70
+ 'Skill',
71
+ 'tool_search',
72
+ ]);
73
+ const READONLY_TOOL_NAMES = new Set([
74
+ 'read',
75
+ 'list',
76
+ 'grep',
77
+ 'find',
78
+ 'glob',
79
+ 'code_graph',
80
+ 'search',
81
+ 'web_fetch',
82
+ 'recall',
83
+ 'memory',
84
+ 'provider_status',
85
+ 'channel_status',
86
+ 'schedule_status',
87
+ 'fetch',
88
+ 'Skill',
89
+ ]);
90
+ const DEFERRED_SELECT_ALIASES = {
91
+ filesystem: ['read', 'list', 'grep', 'find', 'glob'],
92
+ search: ['search', 'web_fetch'],
93
+ web: ['web_fetch', 'search'],
94
+ memory: ['memory', 'recall'],
95
+ channels: ['reply', 'fetch', 'react', 'edit_message', 'download_attachment', 'schedule_status', 'trigger_schedule', 'schedule_control', 'reload_config'],
96
+ discord: ['reply', 'fetch', 'react', 'edit_message', 'download_attachment'],
97
+ providers: ['provider_status'],
98
+ provider: ['provider_status'],
99
+ status: ['provider_status', 'channel_status', 'schedule_status'],
100
+ schedule: ['schedule_status', 'trigger_schedule', 'schedule_control'],
101
+ channel: ['channel_status'],
102
+ explore: ['explore'],
103
+ discovery: ['explore'],
104
+ agent: ['agent'],
105
+ graph: ['code_graph'],
106
+ code: ['code_graph'],
107
+ shell: ['shell', 'task'],
108
+ };
109
+ const TOOL_SEARCH_SAFE_AUTO_ALIASES = new Set([
110
+ 'shell',
111
+ 'web',
112
+ 'search',
113
+ 'agent',
114
+ 'provider',
115
+ 'providers',
116
+ 'channel',
117
+ 'schedule',
118
+ 'memory',
119
+ ]);
120
+ const TOOL_SEARCH_AMBIGUOUS_AUTO_QUERIES = new Set([
121
+ 'status',
122
+ 'state',
123
+ 'info',
124
+ 'list',
125
+ 'show',
126
+ 'config',
127
+ ]);
128
+ const TOOL_SEARCH_STOP_WORDS = new Set([
129
+ 'a',
130
+ 'an',
131
+ 'and',
132
+ 'are',
133
+ 'as',
134
+ 'for',
135
+ 'from',
136
+ 'how',
137
+ 'i',
138
+ 'in',
139
+ 'is',
140
+ 'me',
141
+ 'need',
142
+ 'of',
143
+ 'on',
144
+ 'please',
145
+ 'the',
146
+ 'to',
147
+ 'tool',
148
+ 'tools',
149
+ 'use',
150
+ 'using',
151
+ 'with',
152
+ ]);
153
+ const TOOL_SEARCH_ROW_ALIASES = Object.freeze({
154
+ agent: ['delegate', 'subagent', 'worker', 'parallel agent', 'background agent', 'reviewer', 'explorer'],
155
+ channel_status: ['channel status', 'discord status', 'channel config'],
156
+ cwd: ['cwd', 'working directory', 'current directory', 'project root', 'folder'],
157
+ memory: ['save memory', 'store memory', 'delete memory', 'forget memory', 'memory status'],
158
+ provider_status: ['provider status', 'auth status', 'model status', 'oauth status', 'provider config'],
159
+ recall: ['recall', 'previous work', 'past work', 'prior context', 'history', 'resume context'],
160
+ schedule_status: ['schedule status', 'cron status'],
161
+ search: ['web search', 'internet search', 'current info', 'latest info', 'online search', 'docs search'],
162
+ shell: ['run command', 'execute command', 'terminal', 'powershell', 'bash', 'run tests', 'test command', 'build command', 'npm', 'node', 'git'],
163
+ task: ['background task', 'async task', 'wait task', 'cancel task', 'task status'],
164
+ web_fetch: ['fetch url', 'fetch page', 'open url', 'web page', 'read url', 'docs page'],
165
+ });
166
+
167
+ export function toolKind(tool) {
168
+ const name = clean(tool?.name);
169
+ if (name.startsWith('mcp__')) return 'mcp';
170
+ if (name.startsWith('skill:') || tool?.annotations?.mixdogKind === 'skill') return 'skill';
171
+ if (name === 'Skill' || name.startsWith('skill_') || name === 'skills_list' || name === 'skill_view') return 'skill';
172
+ if (tool?.annotations?.agentHidden) return 'control';
173
+ if (['apply_patch', 'shell'].includes(name)) return 'mutation';
174
+ return 'tool';
175
+ }
176
+
177
+ export function toolSchemaBucket(tool) {
178
+ const name = clean(tool?.name);
179
+ const kind = toolKind(tool);
180
+ if (kind === 'mcp') return 'mcp';
181
+ if (kind === 'skill') return 'skills';
182
+ if (name === 'memory' || name === 'recall' || name.includes('memory')) return 'memory';
183
+ if (name === 'search' || name === 'web_fetch') return 'web';
184
+ if (['read', 'grep', 'find', 'glob', 'list', 'code_graph', 'explore'].includes(name)) return 'code';
185
+ if (['shell', 'apply_patch'].includes(name)) return 'mutation';
186
+ if (name === 'agent' || name === 'delegate') return 'agents';
187
+ if (name.includes('channel') || name.includes('discord') || name.includes('webhook')) return 'channels';
188
+ if (name.includes('provider') || name === 'tool_search' || name === 'cwd') return 'setup';
189
+ return 'other';
190
+ }
191
+
192
+ export function estimateToolSchemaBreakdown(tools) {
193
+ const out = {};
194
+ for (const tool of Array.isArray(tools) ? tools : []) {
195
+ const bucket = toolSchemaBucket(tool);
196
+ const row = out[bucket] || { count: 0, tokens: 0 };
197
+ row.count += 1;
198
+ row.tokens += estimateToolSchemaTokens([tool]);
199
+ out[bucket] = row;
200
+ }
201
+ return out;
202
+ }
203
+
204
+ export function measuredToolUsage(name) {
205
+ return MEASURED_TOOL_USAGE[clean(name)] || 0;
206
+ }
207
+
208
+ export function parseToolSelection(value) {
209
+ if (Array.isArray(value)) return value.map(clean).filter(Boolean);
210
+ if (value && typeof value !== 'string' && typeof value[Symbol.iterator] === 'function') {
211
+ return [...value].map(clean).filter(Boolean);
212
+ }
213
+ return String(value || '').replace(/^select\s*:/i, '')
214
+ .split(/[,\s]+/)
215
+ .map(clean)
216
+ .filter(Boolean);
217
+ }
218
+
219
+ export function parseToolSearchQuerySelection(query) {
220
+ const match = clean(query).match(/^select\s*:\s*(.+)$/i);
221
+ return match ? parseToolSelection(match[1]) : [];
222
+ }
223
+
224
+ function measuredToolRank(name) {
225
+ const index = MEASURED_TOOL_ORDER.indexOf(clean(name));
226
+ return index === -1 ? Number.MAX_SAFE_INTEGER : index;
227
+ }
228
+
229
+ export function sortedCatalogByMeasuredUsage(catalog) {
230
+ return (catalog || [])
231
+ .map((tool, index) => ({ tool, index }))
232
+ .sort((a, b) => {
233
+ const au = measuredToolUsage(a.tool?.name);
234
+ const bu = measuredToolUsage(b.tool?.name);
235
+ if (bu !== au) return bu - au;
236
+ const ar = measuredToolRank(a.tool?.name);
237
+ const br = measuredToolRank(b.tool?.name);
238
+ if (ar !== br) return ar - br;
239
+ return a.index - b.index;
240
+ })
241
+ .map((entry) => entry.tool);
242
+ }
243
+
244
+ function activeToolForSurface(tool) {
245
+ if (!tool || typeof tool !== 'object') return tool;
246
+ return JSON.parse(JSON.stringify(tool));
247
+ }
248
+
249
+ function deferredProviderMode(provider) {
250
+ const p = clean(provider).toLowerCase();
251
+ if (p === 'gemini') return 'full';
252
+ if (p === 'anthropic' || p === 'anthropic-oauth'
253
+ || p === 'openai' || p === 'openai-oauth'
254
+ || p === 'xai' || p === 'grok-oauth') {
255
+ return 'native';
256
+ }
257
+ return 'legacy';
258
+ }
259
+
260
+ export function filterDisallowedTools(tools, disallowed = []) {
261
+ if (!Array.isArray(disallowed) || disallowed.length === 0) return tools;
262
+ const deny = new Set(disallowed.map((name) => clean(name)).filter(Boolean));
263
+ if (deny.size === 0) return tools;
264
+ return (tools || []).filter((tool) => !deny.has(clean(tool?.name)));
265
+ }
266
+
267
+ export function sortedNamesByMeasuredUsage(names) {
268
+ return [...(names || [])].sort((a, b) => {
269
+ const au = measuredToolUsage(a);
270
+ const bu = measuredToolUsage(b);
271
+ if (bu !== au) return bu - au;
272
+ const ar = measuredToolRank(a);
273
+ const br = measuredToolRank(b);
274
+ if (ar !== br) return ar - br;
275
+ return String(a).localeCompare(String(b));
276
+ });
277
+ }
278
+
279
+ export function defaultDeferredToolNames(catalog, mode) {
280
+ const available = new Set((catalog || []).map((tool) => clean(tool?.name)).filter(Boolean));
281
+ if (mode === 'lead') {
282
+ return new Set(DEFERRED_DEFAULT_LEAD_TOOLS.filter((name) => available.has(name)));
283
+ }
284
+ if (mode === 'readonly') {
285
+ return new Set(DEFERRED_DEFAULT_READONLY_TOOLS.filter((name) => available.has(name)));
286
+ }
287
+ return new Set(DEFERRED_DEFAULT_FULL_TOOLS.filter((name) => available.has(name)));
288
+ }
289
+
290
+ export function compactToolSearchDescription(value, max = 220) {
291
+ const text = clean(value).replace(/\s+/g, ' ');
292
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
293
+ }
294
+
295
+ export function toolRow(tool, activeNames = new Set()) {
296
+ const name = clean(tool?.name);
297
+ return {
298
+ name,
299
+ kind: toolKind(tool),
300
+ usage: measuredToolUsage(name),
301
+ active: activeNames.has(name),
302
+ description: compactToolSearchDescription(tool?.description),
303
+ };
304
+ }
305
+
306
+ function providerSupportsResponsesCustomTools(provider) {
307
+ const p = clean(provider).toLowerCase();
308
+ if (!p) return true;
309
+ return p === 'openai' || p === 'openai-oauth';
310
+ }
311
+
312
+ function openAILoadableToolSpec(tool, provider = '') {
313
+ if (providerSupportsResponsesCustomTools(provider) && isResponsesFreeformTool(tool)) return toResponsesCustomTool(tool);
314
+ return {
315
+ type: 'function',
316
+ name: clean(tool?.name),
317
+ description: clean(tool?.description),
318
+ defer_loading: true,
319
+ parameters: tool?.inputSchema && typeof tool.inputSchema === 'object'
320
+ ? tool.inputSchema
321
+ : { type: 'object', properties: {} },
322
+ };
323
+ }
324
+
325
+ function toolSearchNativePayload(catalog, names, provider = '') {
326
+ const selected = new Set((names || []).map(clean).filter(Boolean));
327
+ if (!selected.size) return null;
328
+ const tools = [];
329
+ const refs = [];
330
+ for (const tool of catalog || []) {
331
+ const name = clean(tool?.name);
332
+ if (!name || !selected.has(name)) continue;
333
+ refs.push(name);
334
+ tools.push(openAILoadableToolSpec(tool, provider));
335
+ }
336
+ if (!refs.length) return null;
337
+ return {
338
+ toolReferences: refs,
339
+ openaiTools: tools,
340
+ summary: `Loaded deferred tools: ${refs.join(', ')}`,
341
+ };
342
+ }
343
+
344
+ function toolSearchTokens(value) {
345
+ return (clean(value).toLowerCase().match(/[a-z0-9_.-]+/g) || [])
346
+ .map((token) => token.replace(/[-.]+/g, '_'))
347
+ .filter(Boolean);
348
+ }
349
+
350
+ function toolSearchMeaningfulTokens(value) {
351
+ return toolSearchTokens(value).filter((token) => !TOOL_SEARCH_STOP_WORDS.has(token));
352
+ }
353
+
354
+ function toolSearchText(row) {
355
+ const text = `${row.name} ${String(row.name || '').replace(/_/g, ' ')} ${row.kind} ${row.description} ${row.active ? 'active' : 'deferred'}`;
356
+ return `${text} ${text.replace(/[-.]+/g, '_')}`.toLowerCase();
357
+ }
358
+
359
+ function toolSearchRowAliases(name) {
360
+ return TOOL_SEARCH_ROW_ALIASES[clean(name)] || [];
361
+ }
362
+
363
+ function toolSearchRank(row, query) {
364
+ const raw = clean(query).toLowerCase();
365
+ if (!raw) return { score: 0, reasons: [] };
366
+ const name = clean(row?.name).toLowerCase();
367
+ const prettyName = name.replace(/_/g, ' ');
368
+ const haystack = toolSearchText(row);
369
+ const aliases = toolSearchRowAliases(name);
370
+ const aliasText = aliases.join(' ').toLowerCase();
371
+ const queryTokens = toolSearchMeaningfulTokens(raw);
372
+ const nameTokens = new Set(toolSearchTokens(`${name} ${prettyName}`));
373
+ const aliasTokens = new Set(toolSearchTokens(aliasText));
374
+ let score = 0;
375
+ const reasons = [];
376
+ if (raw === name || raw === prettyName) {
377
+ score += 120;
378
+ reasons.push('exact-name');
379
+ }
380
+ if (aliases.some((alias) => raw === alias || raw === alias.replace(/[_-]+/g, ' '))) {
381
+ score += 100;
382
+ reasons.push('exact-alias');
383
+ }
384
+ // Stop-word-only queries (e.g. "tool", "to") have zero meaningful tokens.
385
+ // Without an exact name/alias hit, they must not fall through to the raw
386
+ // substring/haystack checks below — every row description likely contains
387
+ // "tool" somewhere, producing a noisy pseudo-match list instead of "no
388
+ // results". Exact name/alias matches stay intact even when the name is
389
+ // itself a stop word.
390
+ if (!queryTokens.length && !reasons.length) {
391
+ return { score: 0, reasons: [] };
392
+ }
393
+ if (haystack.includes(raw)) {
394
+ score += 34;
395
+ reasons.push('phrase');
396
+ }
397
+ for (const alias of aliases) {
398
+ const normalizedAlias = alias.toLowerCase();
399
+ if (normalizedAlias && raw.includes(normalizedAlias)) {
400
+ score += 58;
401
+ reasons.push('alias-phrase');
402
+ break;
403
+ }
404
+ }
405
+ let matchedTokens = 0;
406
+ for (const token of queryTokens) {
407
+ if (nameTokens.has(token) || name.includes(token)) {
408
+ score += 24;
409
+ matchedTokens += 1;
410
+ continue;
411
+ }
412
+ if (aliasTokens.has(token) || aliasText.includes(token)) {
413
+ score += 18;
414
+ matchedTokens += 1;
415
+ continue;
416
+ }
417
+ if (haystack.includes(token)) {
418
+ score += 7;
419
+ matchedTokens += 1;
420
+ }
421
+ }
422
+ if (queryTokens.length && matchedTokens === queryTokens.length) {
423
+ score += Math.min(18, queryTokens.length * 6);
424
+ reasons.push('all-tokens');
425
+ }
426
+ if (clean(row?.kind).toLowerCase() === raw) score += 10;
427
+ if (score > 0) score += Math.min(6, Math.floor(measuredToolUsage(name) / 150));
428
+ return { score, reasons };
429
+ }
430
+
431
+ export function toolSearchMatches(row, query) {
432
+ const raw = clean(query).toLowerCase();
433
+ if (!raw) return true;
434
+ return toolSearchRank(row, raw).score > 0;
435
+ }
436
+
437
+ function rankedToolSearchRows(rows, query) {
438
+ const raw = clean(query).toLowerCase();
439
+ if (!raw) return rows;
440
+ return rows
441
+ .map((row) => {
442
+ const ranked = toolSearchRank(row, raw);
443
+ return { ...row, score: ranked.score, reasons: ranked.reasons };
444
+ })
445
+ .filter((row) => row.score > 0)
446
+ .sort((a, b) => {
447
+ if (b.score !== a.score) return b.score - a.score;
448
+ if (a.active !== b.active) return a.active ? 1 : -1;
449
+ if (b.usage !== a.usage) return b.usage - a.usage;
450
+ return String(a.name).localeCompare(String(b.name));
451
+ });
452
+ }
453
+
454
+ function isAsciiWordChar(ch) {
455
+ return !!ch && /[A-Za-z0-9]/.test(ch);
456
+ }
457
+
458
+ // Matches `phrase` inside `text` at a word boundary. ASCII letters/digits on
459
+ // either side of the match block it (so "webhook" does not match "web" and
460
+ // "prune" does not match "run"); non-ASCII characters (e.g. Korean, where
461
+ // words are not space-separated) never count as word chars, so Korean
462
+ // substring phrases keep matching as before.
463
+ function phraseMatchesAsWords(text, phrase) {
464
+ if (!phrase) return false;
465
+ let idx = text.indexOf(phrase);
466
+ while (idx !== -1) {
467
+ const before = idx > 0 ? text[idx - 1] : '';
468
+ const after = idx + phrase.length < text.length ? text[idx + phrase.length] : '';
469
+ if (!isAsciiWordChar(before) && !isAsciiWordChar(after)) return true;
470
+ idx = text.indexOf(phrase, idx + 1);
471
+ }
472
+ return false;
473
+ }
474
+
475
+ function queryHasAnyPhrase(query, phrases) {
476
+ const text = clean(query).toLowerCase().replace(/[_-]+/g, ' ');
477
+ return phrases.some((phrase) => phraseMatchesAsWords(text, phrase));
478
+ }
479
+
480
+ const TOOL_SEARCH_AUTO_CATEGORY_BRANCHES = [
481
+ {
482
+ names: ['memory'],
483
+ phrases: ['save memory', 'store memory', 'delete memory', 'forget memory', 'memory status', '기억 저장', '메모리 저장'],
484
+ },
485
+ {
486
+ names: ['shell', 'task'],
487
+ phrases: ['run', 'execute', 'test', 'tests', 'build', 'terminal', 'command', 'powershell', 'bash', 'shell', 'npm', 'node', 'git', '실행', '테스트', '빌드', '터미널', '쉘'],
488
+ },
489
+ {
490
+ names: ['search', 'web_fetch'],
491
+ phrases: ['web', 'internet', 'online', 'current info', 'latest', 'news', 'browse', 'docs', 'documentation', '웹', '인터넷', '최신', '뉴스', '문서'],
492
+ },
493
+ {
494
+ names: ['recall'],
495
+ phrases: ['recall', 'remember', 'memory previous', 'previous', 'history', 'past', 'prior', 'earlier', 'resume', '이전', '기억', '히스토리'],
496
+ },
497
+ {
498
+ names: ['agent'],
499
+ phrases: ['delegate', 'subagent', 'worker', 'parallel agent', 'background agent', 'reviewer', 'explorer', '에이전트', '워커', '병렬'],
500
+ },
501
+ {
502
+ names: ['cwd'],
503
+ phrases: ['working directory', 'project root', 'current directory', 'cwd'],
504
+ },
505
+ ];
506
+
507
+ function autoToolSelectionNames(query, rows) {
508
+ const raw = clean(query).toLowerCase();
509
+ if (!raw || TOOL_SEARCH_AMBIGUOUS_AUTO_QUERIES.has(raw)) return [];
510
+ if (TOOL_SEARCH_SAFE_AUTO_ALIASES.has(raw) && DEFERRED_SELECT_ALIASES[raw]) {
511
+ return DEFERRED_SELECT_ALIASES[raw];
512
+ }
513
+ const matchedBranches = TOOL_SEARCH_AUTO_CATEGORY_BRANCHES.filter((branch) =>
514
+ queryHasAnyPhrase(raw, branch.phrases)
515
+ );
516
+ if (matchedBranches.length === 1) return matchedBranches[0].names;
517
+ // Ambiguous (0 or 2+ category branches matched): never auto-load a
518
+ // category's tools off a possibly-wrong guess. Fall through to the ranked
519
+ // exact-name/alias path below; that path returns [] on its own when there
520
+ // is no exact match, so an ambiguous query still safely resolves to [].
521
+ const ranked = rankedToolSearchRows(rows, raw);
522
+ const top = ranked[0];
523
+ if (!top) return [];
524
+ const reasons = new Set(top.reasons || []);
525
+ if (reasons.has('exact-name') || reasons.has('exact-alias')) return [top.name];
526
+ return [];
527
+ }
528
+
529
+ function expandSelectionNames(names) {
530
+ const out = [];
531
+ for (const raw of names || []) {
532
+ const key = clean(raw);
533
+ if (!key) continue;
534
+ const alias = DEFERRED_SELECT_ALIASES[key.toLowerCase()];
535
+ if (alias) out.push(...alias);
536
+ else out.push(key);
537
+ }
538
+ return [...new Set(out)];
539
+ }
540
+
541
+ function storedDeferredToolNames(session) {
542
+ for (const source of [session?.deferredDiscoveredTools, session?.deferredSelectedTools]) {
543
+ const names = parseToolSelection(source);
544
+ if (names.length) return names;
545
+ }
546
+ return [];
547
+ }
548
+
549
+ function canonicalDeferredToolNames(catalog, names) {
550
+ const byName = new Map();
551
+ for (const tool of catalog || []) {
552
+ const name = clean(tool?.name);
553
+ if (!name) continue;
554
+ byName.set(name, name);
555
+ byName.set(name.toLowerCase(), name);
556
+ }
557
+ const out = [];
558
+ for (const raw of expandSelectionNames(names)) {
559
+ const name = clean(raw);
560
+ const canonical = byName.get(name) || byName.get(name.toLowerCase());
561
+ if (canonical) out.push(canonical);
562
+ }
563
+ return sortedNamesByMeasuredUsage(new Set(out));
564
+ }
565
+
566
+ function setDeferredToolState(session, names) {
567
+ if (!session) return [];
568
+ const selected = sortedNamesByMeasuredUsage(new Set(parseToolSelection(names)));
569
+ session.deferredDiscoveredTools = selected;
570
+ session.deferredSelectedTools = selected;
571
+ return selected;
572
+ }
573
+
574
+ function isReadonlySelectable(tool) {
575
+ const name = clean(tool?.name);
576
+ if (READONLY_TOOL_NAMES.has(name)) return true;
577
+ const annotations = tool?.annotations || {};
578
+ if (annotations.destructiveHint === true) return false;
579
+ if (annotations.readOnlyHint === true) return true;
580
+ return false;
581
+ }
582
+
583
+ export function applyDeferredToolSurface(session, mode, extraTools = [], options = {}) {
584
+ if (!session || !Array.isArray(session.tools)) return session;
585
+ const providerMode = deferredProviderMode(options.provider || session.provider);
586
+ const byName = new Map();
587
+ for (const tool of [...session.tools, ...(extraTools || [])]) {
588
+ const name = clean(tool?.name);
589
+ if (!name || byName.has(name)) continue;
590
+ byName.set(name, activeToolForSurface(tool));
591
+ }
592
+ const catalog = sortedCatalogByMeasuredUsage([...byName.values()]);
593
+ const defaultNames = defaultDeferredToolNames(catalog, mode);
594
+ const storedNames = providerMode === 'native' ? [] : storedDeferredToolNames(session);
595
+ let selectedNames = providerMode === 'full'
596
+ ? sortedNamesByMeasuredUsage(catalog.map((tool) => clean(tool?.name)).filter(Boolean))
597
+ : [];
598
+ if (providerMode !== 'full') {
599
+ selectedNames = storedNames.length ? canonicalDeferredToolNames(catalog, storedNames) : [];
600
+ if (!selectedNames.length || providerMode === 'native') selectedNames = sortedNamesByMeasuredUsage(defaultNames);
601
+ }
602
+ const selected = new Set(selectedNames);
603
+ session.deferredToolCatalog = catalog;
604
+ session.deferredToolUsage = MEASURED_TOOL_USAGE;
605
+ session.deferredDefaultTools = sortedNamesByMeasuredUsage(defaultNames);
606
+ session.deferredProviderMode = providerMode;
607
+ session.deferredNativeTools = providerMode === 'native';
608
+ session.tools.length = 0;
609
+ const active = [];
610
+ for (const tool of catalog) {
611
+ if (!selected.has(clean(tool?.name))) continue;
612
+ if (mode === 'readonly' && !isReadonlySelectable(tool)) continue;
613
+ session.tools.push(tool);
614
+ active.push(clean(tool?.name));
615
+ }
616
+ if (providerMode === 'native') {
617
+ const discovered = canonicalDeferredToolNames(catalog, session.deferredDiscoveredTools || []);
618
+ session.deferredSelectedTools = active;
619
+ session.deferredDiscoveredTools = discovered.filter((name) => !selected.has(name));
620
+ } else {
621
+ setDeferredToolState(session, active);
622
+ }
623
+ return session;
624
+ }
625
+
626
+ export function selectDeferredTools(session, names, mode) {
627
+ const catalog = Array.isArray(session?.deferredToolCatalog)
628
+ ? session.deferredToolCatalog
629
+ : (Array.isArray(session?.tools) ? session.tools : []);
630
+ const active = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
631
+ const native = session?.deferredProviderMode === 'native' || session?.deferredNativeTools === true;
632
+ const discovered = new Set(Array.isArray(session?.deferredDiscoveredTools) ? session.deferredDiscoveredTools : []);
633
+ const byName = new Map();
634
+ for (const tool of catalog) {
635
+ const name = clean(tool?.name);
636
+ if (!name) continue;
637
+ byName.set(name, tool);
638
+ byName.set(name.toLowerCase(), tool);
639
+ }
640
+ const added = [];
641
+ const already = [];
642
+ const blocked = [];
643
+ const missing = [];
644
+ for (const rawName of expandSelectionNames(names)) {
645
+ const requestedName = clean(rawName);
646
+ const tool = byName.get(requestedName) || byName.get(requestedName.toLowerCase());
647
+ const name = clean(tool?.name);
648
+ if (!tool) {
649
+ missing.push(requestedName);
650
+ continue;
651
+ }
652
+ if (mode === 'readonly' && !isReadonlySelectable(tool)) {
653
+ blocked.push({ name, reason: 'readonly mode' });
654
+ continue;
655
+ }
656
+ if (active.has(name) || discovered.has(name)) {
657
+ already.push(name);
658
+ continue;
659
+ }
660
+ if (native) {
661
+ discovered.add(name);
662
+ } else {
663
+ session.tools.push(tool);
664
+ active.add(name);
665
+ }
666
+ added.push(name);
667
+ }
668
+ if (native) {
669
+ session.deferredDiscoveredTools = sortedNamesByMeasuredUsage(discovered);
670
+ session.deferredSelectedTools = sortedNamesByMeasuredUsage(active);
671
+ } else {
672
+ setDeferredToolState(session, active);
673
+ }
674
+ return { added, already, blocked, missing, native };
675
+ }
676
+
677
+ export function renderToolSearch(args = {}, session, mode = 'full') {
678
+ const catalog = Array.isArray(session?.deferredToolCatalog)
679
+ ? session.deferredToolCatalog
680
+ : (Array.isArray(session?.tools) ? session.tools : []);
681
+ const rawQuery = clean(args.query);
682
+ const explicitSelectedNames = parseToolSelection(args.select);
683
+ const querySelectedNames = explicitSelectedNames.length ? [] : parseToolSearchQuerySelection(rawQuery);
684
+ const forcedSelectedNames = explicitSelectedNames.length ? explicitSelectedNames : querySelectedNames;
685
+ const query = querySelectedNames.length ? '' : rawQuery.toLowerCase();
686
+ const limit = Math.max(1, Math.min(50, Number(args.limit) || 20));
687
+ const initialActiveNames = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
688
+ const initialRows = catalog.map((tool) => toolRow(tool, initialActiveNames)).filter((row) => row.name);
689
+ const autoSelectedNames = (!forcedSelectedNames.length && query)
690
+ ? autoToolSelectionNames(query, initialRows)
691
+ : [];
692
+ const selectedNames = forcedSelectedNames.length ? forcedSelectedNames : autoSelectedNames;
693
+ const toolSelection = selectedNames.length ? selectDeferredTools(session, selectedNames, mode) : null;
694
+ const selectionMode = forcedSelectedNames.length ? 'select' : (autoSelectedNames.length ? 'auto' : null);
695
+ const nextActiveNames = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
696
+ const rows = [
697
+ ...catalog.map((tool) => toolRow(tool, nextActiveNames)),
698
+ ].filter((row) => row.name);
699
+ const matches = query
700
+ ? rankedToolSearchRows(rows, query)
701
+ : rows;
702
+ const selected = toolSelection
703
+ ? {
704
+ mode: selectionMode,
705
+ tools: toolSelection,
706
+ }
707
+ : null;
708
+ const nativeToolSearch = toolSelection?.native
709
+ ? toolSearchNativePayload(catalog, toolSelection.added, session?.provider)
710
+ : null;
711
+ return JSON.stringify({
712
+ selected,
713
+ ...(nativeToolSearch ? { nativeToolSearch } : {}),
714
+ totalMatches: matches.length,
715
+ matches: matches.slice(0, limit),
716
+ activeTools: sortedNamesByMeasuredUsage(nextActiveNames),
717
+ discoveredTools: sortedNamesByMeasuredUsage(session?.deferredDiscoveredTools || []),
718
+ note: 'query ranks matches and auto-loads high-confidence deferred tools; select exact tool names, or query select:a,b, to force load.',
719
+ }, null, 2);
720
+ }