mixdog 0.9.0 → 0.9.2

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 (240) hide show
  1. package/package.json +10 -3
  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/background-task-meta-smoke.mjs +1 -1
  6. package/scripts/bench-run.mjs +262 -0
  7. package/scripts/compact-smoke.mjs +12 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  9. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  10. package/scripts/internal-comms-bench.mjs +727 -0
  11. package/scripts/internal-comms-smoke.mjs +75 -0
  12. package/scripts/lead-workflow-smoke.mjs +4 -4
  13. package/scripts/live-worker-smoke.mjs +9 -9
  14. package/scripts/output-style-bench.mjs +285 -0
  15. package/scripts/output-style-smoke.mjs +13 -10
  16. package/scripts/patch-replay.mjs +90 -0
  17. package/scripts/provider-stream-stall-test.mjs +276 -0
  18. package/scripts/provider-toolcall-test.mjs +599 -1
  19. package/scripts/routing-corpus.mjs +281 -0
  20. package/scripts/session-bench.mjs +1526 -0
  21. package/scripts/session-diag.mjs +595 -0
  22. package/scripts/session-ingest-smoke.mjs +2 -2
  23. package/scripts/task-bench.mjs +207 -0
  24. package/scripts/tool-failures.mjs +6 -6
  25. package/scripts/tool-smoke.mjs +306 -66
  26. package/scripts/toolcall-args-test.mjs +81 -0
  27. package/src/agents/debugger/AGENT.md +4 -4
  28. package/src/agents/heavy-worker/AGENT.md +4 -2
  29. package/src/agents/reviewer/AGENT.md +4 -4
  30. package/src/agents/worker/AGENT.md +4 -2
  31. package/src/app.mjs +10 -6
  32. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  33. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  34. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  35. package/src/headless-role.mjs +14 -14
  36. package/src/help.mjs +1 -0
  37. package/src/lib/mixdog-debug.cjs +0 -22
  38. package/src/lib/plugin-paths.cjs +1 -7
  39. package/src/lib/rules-builder.cjs +34 -56
  40. package/src/mixdog-session-runtime.mjs +710 -319
  41. package/src/output-styles/default.md +12 -7
  42. package/src/output-styles/minimal.md +25 -0
  43. package/src/output-styles/oneline.md +21 -0
  44. package/src/output-styles/simple.md +10 -9
  45. package/src/repl.mjs +12 -4
  46. package/src/rules/agent/00-common.md +7 -5
  47. package/src/rules/agent/30-explorer.md +7 -8
  48. package/src/rules/lead/01-general.md +3 -1
  49. package/src/rules/lead/lead-tool.md +7 -0
  50. package/src/rules/shared/01-tool.md +17 -12
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  52. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  54. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  55. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  56. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  57. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  59. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  60. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  61. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  62. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  65. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
  66. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
  67. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  68. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
  75. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  76. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  78. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
  79. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  80. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  81. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  82. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  83. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  84. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  85. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  86. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  87. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  92. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  99. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  100. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  101. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  104. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  105. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  106. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  107. package/src/runtime/channels/backends/discord.mjs +99 -9
  108. package/src/runtime/channels/backends/telegram.mjs +501 -0
  109. package/src/runtime/channels/index.mjs +441 -1254
  110. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  111. package/src/runtime/channels/lib/config.mjs +54 -3
  112. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  113. package/src/runtime/channels/lib/executor.mjs +0 -3
  114. package/src/runtime/channels/lib/format.mjs +4 -2
  115. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  116. package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
  117. package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
  118. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  119. package/src/runtime/channels/lib/session-discovery.mjs +0 -4
  120. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  121. package/src/runtime/channels/lib/tool-format.mjs +1 -2
  122. package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
  123. package/src/runtime/channels/lib/webhook.mjs +59 -31
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  126. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  127. package/src/runtime/memory/index.mjs +187 -43
  128. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  129. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  130. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  131. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  132. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  133. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  134. package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
  135. package/src/runtime/memory/lib/memory.mjs +101 -4
  136. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  137. package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
  138. package/src/runtime/memory/lib/session-ingest.mjs +116 -7
  139. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  140. package/src/runtime/memory/tool-defs.mjs +6 -3
  141. package/src/runtime/search/index.mjs +2 -7
  142. package/src/runtime/search/lib/config.mjs +0 -4
  143. package/src/runtime/search/lib/state.mjs +1 -15
  144. package/src/runtime/search/lib/web-tools.mjs +0 -1
  145. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  146. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  147. package/src/runtime/shared/child-spawn-gate.mjs +0 -6
  148. package/src/runtime/shared/config.mjs +9 -0
  149. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  150. package/src/runtime/shared/schedules-store.mjs +21 -19
  151. package/src/runtime/shared/tool-surface.mjs +98 -13
  152. package/src/runtime/shared/transcript-writer.mjs +129 -0
  153. package/src/runtime/shared/update-checker.mjs +214 -0
  154. package/src/standalone/agent-tool.mjs +255 -109
  155. package/src/standalone/channel-admin.mjs +133 -40
  156. package/src/standalone/channel-worker.mjs +8 -291
  157. package/src/standalone/explore-tool.mjs +2 -2
  158. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  159. package/src/standalone/provider-admin.mjs +11 -0
  160. package/src/standalone/seeds.mjs +1 -11
  161. package/src/standalone/usage-dashboard.mjs +1 -1
  162. package/src/tui/App.jsx +2137 -750
  163. package/src/tui/components/ConfirmBar.jsx +47 -0
  164. package/src/tui/components/ContextPanel.jsx +5 -3
  165. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  166. package/src/tui/components/Markdown.jsx +22 -98
  167. package/src/tui/components/Message.jsx +14 -35
  168. package/src/tui/components/Picker.jsx +87 -12
  169. package/src/tui/components/PromptInput.jsx +146 -9
  170. package/src/tui/components/QueuedCommands.jsx +1 -1
  171. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  172. package/src/tui/components/Spinner.jsx +7 -7
  173. package/src/tui/components/StatusLine.jsx +40 -21
  174. package/src/tui/components/TextEntryPanel.jsx +51 -7
  175. package/src/tui/components/ToolExecution.jsx +177 -100
  176. package/src/tui/components/TurnDone.jsx +4 -4
  177. package/src/tui/components/UsagePanel.jsx +1 -1
  178. package/src/tui/components/tool-output-format.mjs +312 -40
  179. package/src/tui/components/tool-output-format.test.mjs +180 -1
  180. package/src/tui/display-width.mjs +69 -0
  181. package/src/tui/display-width.test.mjs +35 -0
  182. package/src/tui/dist/index.mjs +7324 -2393
  183. package/src/tui/engine.mjs +287 -126
  184. package/src/tui/index.jsx +117 -7
  185. package/src/tui/keyboard-protocol.mjs +42 -0
  186. package/src/tui/lib/voice-recorder.mjs +453 -0
  187. package/src/tui/markdown/format-token.mjs +354 -142
  188. package/src/tui/markdown/format-token.test.mjs +155 -17
  189. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  190. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  191. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  192. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  193. package/src/tui/markdown/table-layout.mjs +9 -9
  194. package/src/tui/paste-attachments.mjs +0 -11
  195. package/src/tui/prompt-history-store.mjs +129 -0
  196. package/src/tui/prompt-history-store.test.mjs +52 -0
  197. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  198. package/src/tui/theme.mjs +41 -647
  199. package/src/tui/themes/base.mjs +86 -0
  200. package/src/tui/themes/basic.mjs +85 -0
  201. package/src/tui/themes/catppuccin.mjs +72 -0
  202. package/src/tui/themes/dracula.mjs +70 -0
  203. package/src/tui/themes/everforest.mjs +71 -0
  204. package/src/tui/themes/gruvbox.mjs +71 -0
  205. package/src/tui/themes/index.mjs +71 -0
  206. package/src/tui/themes/indigo.mjs +78 -0
  207. package/src/tui/themes/kanagawa.mjs +80 -0
  208. package/src/tui/themes/light.mjs +81 -0
  209. package/src/tui/themes/nord.mjs +72 -0
  210. package/src/tui/themes/onedark.mjs +16 -0
  211. package/src/tui/themes/rosepine.mjs +70 -0
  212. package/src/tui/themes/teal.mjs +81 -0
  213. package/src/tui/themes/tokyonight.mjs +79 -0
  214. package/src/tui/themes/utils.mjs +106 -0
  215. package/src/tui/themes/warm.mjs +79 -0
  216. package/src/tui/transcript-tool-failures.mjs +13 -2
  217. package/src/ui/markdown.mjs +1 -1
  218. package/src/ui/model-display.mjs +2 -2
  219. package/src/ui/statusline.mjs +26 -27
  220. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
  221. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  222. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  223. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  224. package/src/workflows/default/WORKFLOW.md +39 -12
  225. package/src/workflows/sequential/WORKFLOW.md +46 -0
  226. package/src/workflows/solo/WORKFLOW.md +7 -0
  227. package/vendor/ink/build/display-width.js +62 -0
  228. package/vendor/ink/build/ink.js +154 -20
  229. package/vendor/ink/build/measure-text.js +4 -1
  230. package/vendor/ink/build/output.js +115 -9
  231. package/vendor/ink/build/render-node-to-output.js +4 -1
  232. package/vendor/ink/build/render.js +4 -0
  233. package/src/hooks/lib/permission-rules.cjs +0 -170
  234. package/src/hooks/lib/settings-loader.cjs +0 -112
  235. package/src/lib/hook-pipe-path.cjs +0 -10
  236. package/src/output-styles/extreme-simple.md +0 -20
  237. package/src/rules/lead/04-workflow.md +0 -51
  238. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  239. package/src/workflows/default/workflow.json +0 -13
  240. package/src/workflows/solo/workflow.json +0 -7
@@ -36,6 +36,11 @@ import {
36
36
  import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
37
37
  import { applyGrepContextLeadPolicy, GREP_CONTEXT_MAX } from './arg-guard.mjs';
38
38
 
39
+ // Default surrounding-lines window applied by output_mode:'content_with_context'
40
+ // when the caller does not pass an explicit -A/-B/-C/context. Sized to cover a
41
+ // typical function/block so a match arrives readable without a follow-up read.
42
+ const GREP_AUTO_CONTEXT_LINES = 25;
43
+
39
44
  const MIXDOG_GREP_CASE_HINT_PROBE = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_GREP_CASE_HINT_PROBE || ''));
40
45
 
41
46
  function expandLegacyEscapedAlternationPattern(rawPattern) {
@@ -353,18 +358,23 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
353
358
  .map(canonicalizeGlobSlashes)
354
359
  .filter((g) => !isRedundantAllFilesGlob(g)));
355
360
 
356
- const ALLOWED_OUTPUT_MODES = new Set(['files_with_matches', 'content', 'count']);
361
+ const ALLOWED_OUTPUT_MODES = new Set(['files_with_matches', 'content', 'content_with_context', 'count']);
357
362
  const rawOutputMode = typeof args.output_mode === 'string' ? args.output_mode.trim() : '';
358
363
  if (rawOutputMode && !ALLOWED_OUTPUT_MODES.has(rawOutputMode)) {
359
364
  return `Error: invalid output_mode ${JSON.stringify(args.output_mode)}; expected one of ${[...ALLOWED_OUTPUT_MODES].join(', ')}`;
360
365
  }
366
+ // `content_with_context` is a convenience alias for `content` that auto-applies
367
+ // a generous surrounding-lines window, so the model can read a match in place
368
+ // without following up with `read`. It maps to content mode; the default
369
+ // context is applied below unless the caller passed an explicit -A/-B/-C/context.
370
+ const wantAutoContext = rawOutputMode === 'content_with_context';
361
371
  // Default to `content` when output_mode is omitted. A pattern is always
362
372
  // present here (the no-pattern case returned above), so this is a content
363
373
  // search — it should return the matching lines WITH line numbers, not just
364
374
  // filenames. Filename-only was forcing callers to re-grep for the actual
365
375
  // coordinates (the explorer over-iteration root cause). `files_with_matches`
366
376
  // is now opt-in; pure filename discovery belongs to `glob`.
367
- const outputMode = rawOutputMode || 'content';
377
+ const outputMode = (rawOutputMode === 'content_with_context') ? 'content' : (rawOutputMode || 'content');
368
378
  const headLimitRaw = args.head_limit;
369
379
  const headLimitCoerced = coerceNonNegInt(headLimitRaw);
370
380
  if (Number.isNaN(headLimitCoerced)) {
@@ -391,6 +401,12 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
391
401
  let contextN = args['-C'] !== undefined && args['-C'] !== null && args['-C'] !== ''
392
402
  ? coerceContext(args['-C'])
393
403
  : coerceContext(args.context);
404
+ // content_with_context: if no explicit context flag was supplied, apply a
405
+ // generous default so the match arrives with enough surrounding code to
406
+ // understand it (function-sized) without a separate read.
407
+ if (wantAutoContext && afterN === null && beforeN === null && contextN === null) {
408
+ contextN = GREP_AUTO_CONTEXT_LINES;
409
+ }
394
410
  if (contextN !== null && contextN > 0) {
395
411
  if (afterN === 0) afterN = null;
396
412
  if (beforeN === 0) beforeN = null;
@@ -1,9 +1,10 @@
1
1
  import { countSplitLines } from './path-utils.mjs';
2
+ import { TOOL_OUTPUT_MAX_BYTES } from './tool-output-limit.mjs';
2
3
 
3
- export const SHELL_OUTPUT_MAX_CHARS = 30_000;
4
+ export const SHELL_OUTPUT_MAX_CHARS = TOOL_OUTPUT_MAX_BYTES;
4
5
 
5
6
  export const SMART_BASH_MAX_LINES = 400;
6
- export const SMART_BASH_MAX_BYTES = 30_000;
7
+ export const SMART_BASH_MAX_BYTES = TOOL_OUTPUT_MAX_BYTES;
7
8
  export const SMART_BASH_HEAD_LINES = 80;
8
9
  export const SMART_BASH_TAIL_LINES = 80;
9
10
 
@@ -0,0 +1,10 @@
1
+ // Single source of truth for the model-facing tool-output byte budget.
2
+ // read / shell / list-tree all truncate their output to this cap so the
3
+ // context cost of any one tool call is bounded and consistent. Override with
4
+ // MIXDOG_TOOL_OUTPUT_MAX_BYTES. Line-based sub-caps (e.g. bash max lines) stay
5
+ // per-tool; this governs only the byte budget.
6
+ function _envInt(name, fallback) {
7
+ const v = parseInt(process.env[name], 10);
8
+ return Number.isFinite(v) && v > 0 ? v : fallback;
9
+ }
10
+ export const TOOL_OUTPUT_MAX_BYTES = _envInt('MIXDOG_TOOL_OUTPUT_MAX_BYTES', 50 * 1024);
@@ -277,6 +277,8 @@ const EXTERNAL_TOOL_REDIRECTS = new Map([
277
277
  ['strreplace', 'use `apply_patch` with a `*** Update File:` V4A section'],
278
278
  ['str_replace', 'use `apply_patch` with a `*** Update File:` V4A section'],
279
279
  ['str_replace_editor', 'use `apply_patch` with a `*** Update File:` V4A section'],
280
+ ['search_replace', 'use `apply_patch` with a `*** Update File:` V4A section'],
281
+ ['applypatch', 'call the `apply_patch` tool (snake_case) with a V4A patch'],
280
282
  ['createfile', 'use `apply_patch` with an `*** Add File:` V4A section'],
281
283
  ['create_file', 'use `apply_patch` with an `*** Add File:` V4A section'],
282
284
  ['deletefile', 'use `apply_patch` with a `*** Delete File:` V4A section'],
@@ -287,9 +289,15 @@ const EXTERNAL_TOOL_REDIRECTS = new Map([
287
289
  ['terminal', 'use the `shell` tool'],
288
290
  ['view', 'use the `read` tool'],
289
291
  ['cat', 'use the `read` tool'],
292
+ ['read_file', 'use the `read` tool'],
290
293
  ['ls', 'use the `list` tool'],
294
+ ['list_dir', 'use the `list` tool'],
291
295
  ['searchfiles', 'use the `grep` tool'],
296
+ ['file_search', 'use the `find` or `glob` tool'],
297
+ ['grep_search', 'use the `grep` tool'],
292
298
  ['codebase_search', 'use the `grep` or `code_graph` tool'],
299
+ ['semanticsearch', 'use the `grep` or `code_graph` tool'],
300
+ ['semantic_search', 'use the `grep` or `code_graph` tool'],
293
301
  ]);
294
302
 
295
303
  export function canonicalizeBuiltinToolName(name) {
@@ -401,6 +409,55 @@ function capToolOutput(result, options = {}) {
401
409
  return `${head}\n... [tool-output truncated: ${Math.round(bytes / 1024)} KB -> ${Math.round(cap / 1024)} KB cap (tool_output_token_limit)] ...\n${tail}`;
402
410
  }
403
411
 
412
+ const _GREP_SWEEP_NUDGE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_ENABLE_GREP_SWEEP_NUDGE || ''));
413
+ const _GREP_SWEEP_MIN_CALLS = 20;
414
+ const _GREP_SWEEP_MIN_UNIQUE = 16;
415
+ const _grepSweepState = new Map();
416
+
417
+ function _grepSweepTerms(pattern) {
418
+ const raw = Array.isArray(pattern) ? pattern.join('|') : String(pattern || '');
419
+ return [...new Set(raw
420
+ .split(/[|,\s()"'`[\]{}.*+?^$\\:-]+/g)
421
+ .map((s) => s.trim().toLowerCase())
422
+ .filter((s) => s.length >= 4))];
423
+ }
424
+
425
+ function _normalizeSweepPath(value) {
426
+ return String(value || '.')
427
+ .replace(/\\/g, '/')
428
+ .replace(/^([A-Za-z]):/, (_, d) => d.toLowerCase() + ':')
429
+ .replace(/\/+/g, '/')
430
+ .replace(/\/$/, '') || '.';
431
+ }
432
+
433
+ function _grepSweepKey(args = {}) {
434
+ const path = _normalizeSweepPath(args.path || '.');
435
+ const glob = Array.isArray(args.glob) ? args.glob.join(',') : String(args.glob || '');
436
+ const terms = _grepSweepTerms(args.pattern).slice(0, 3).join('|');
437
+ return `${path}::${glob}::${terms}`;
438
+ }
439
+
440
+ function _maybeAppendGrepSweepNudge(toolName, args, result, scope) {
441
+ if (!_GREP_SWEEP_NUDGE_ENABLED || toolName !== 'grep' || typeof result !== 'string') return result;
442
+ const key = scope || 'global';
443
+ const now = Date.now();
444
+ let state = _grepSweepState.get(key);
445
+ if (!state || now - state.lastTs > 10 * 60_000) {
446
+ state = { count: 0, unique: new Set(), lastTs: 0, nudged: false };
447
+ _grepSweepState.set(key, state);
448
+ }
449
+ state.count += 1;
450
+ state.unique.add(_grepSweepKey(args));
451
+ state.lastTs = now;
452
+ if (_grepSweepState.size > 256) {
453
+ const oldest = [..._grepSweepState.entries()].sort((a, b) => (a[1].lastTs || 0) - (b[1].lastTs || 0)).slice(0, 32);
454
+ for (const [oldKey] of oldest) _grepSweepState.delete(oldKey);
455
+ }
456
+ if (state.nudged || state.count < _GREP_SWEEP_MIN_CALLS || state.unique.size < _GREP_SWEEP_MIN_UNIQUE) return result;
457
+ state.nudged = true;
458
+ return `${result}\n\n[grep_sweep_hint] Many distinct grep searches were used in this session. Stop rewording search terms; synthesize from existing anchors or switch tools once (code_graph/find/read known regions).`;
459
+ }
460
+
404
461
  export async function executeBuiltinTool(name, args, cwd, options = {}) {
405
462
  if (options.abortSignal && !options.signal) {
406
463
  options = { ...options, signal: options.abortSignal };
@@ -463,7 +520,8 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
463
520
  return formatUnknownBuiltinToolMessage(name, args);
464
521
  }
465
522
  })();
466
- return capToolOutput(_toolResult, options);
523
+ const _withNudge = _maybeAppendGrepSweepNudge(toolName, args, _toolResult, readStateScope);
524
+ return capToolOutput(_withNudge, options);
467
525
  }
468
526
  /**
469
527
  * Check if a tool name is a builtin tool.
@@ -3,14 +3,14 @@ export const CODE_GRAPH_TOOL_DEFS = [
3
3
  name: 'code_graph',
4
4
  title: 'Code Graph',
5
5
  annotations: { title: 'Code Graph', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false, compressibleLossless: true },
6
- description: 'Code structure: symbols, refs, calls, imports, impact.',
6
+ description: 'Repo-local code structure/flow lookup, not web: symbols/references/calls/deps before text grep. Batch symbols[].',
7
7
  inputSchema: {
8
8
  type: 'object',
9
9
  properties: {
10
- mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'search', 'references', 'callers', 'callees', 'prewarm'], description: 'Operation.' },
11
- file: { type: 'string', description: 'Source file.' },
12
- symbol: { type: 'string', description: 'Identifier or symbol keyword.' },
13
- symbols: { type: 'array', items: { type: 'string' }, description: 'Batch identifiers.' },
10
+ mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees', 'prewarm'], description: 'Repo-local operation; code flow before text search.' },
11
+ file: { type: 'string', description: 'Known source file.' },
12
+ symbol: { type: 'string', description: 'Identifier/keyword; one anchor is enough.' },
13
+ symbols: { type: 'array', items: { type: 'string' }, description: 'Batch identifiers in one call.' },
14
14
  body: { type: 'boolean', description: 'Include body.' },
15
15
  language: { type: 'string', description: 'Language hint.' },
16
16
  limit: { type: 'number', minimum: 1, description: 'Max results.' },