mixdog 0.8.1 → 0.9.1
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.
- package/README.md +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -30,11 +30,14 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
cacheGet,
|
|
32
32
|
cacheSet,
|
|
33
|
+
runResultCacheInFlight,
|
|
33
34
|
statPathsForMtime,
|
|
34
35
|
} from './cache-layers.mjs';
|
|
35
36
|
import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
|
|
36
37
|
import { applyGrepContextLeadPolicy, GREP_CONTEXT_MAX } from './arg-guard.mjs';
|
|
37
38
|
|
|
39
|
+
const MIXDOG_GREP_CASE_HINT_PROBE = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_GREP_CASE_HINT_PROBE || ''));
|
|
40
|
+
|
|
38
41
|
function expandLegacyEscapedAlternationPattern(rawPattern) {
|
|
39
42
|
if (typeof rawPattern !== 'string' || !rawPattern.includes('\\|')) return null;
|
|
40
43
|
const parts = rawPattern.split('\\|').map((part) => part.trim()).filter(Boolean);
|
|
@@ -180,6 +183,14 @@ function splitGlobString(value) {
|
|
|
180
183
|
return out;
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
function isRedundantAllFilesGlob(value) {
|
|
187
|
+
const g = canonicalizeGlobSlashes(String(value || '').trim())
|
|
188
|
+
.replace(/^\.\//, '')
|
|
189
|
+
.replace(/^\/+/, '')
|
|
190
|
+
.replace(/\/+$/, '');
|
|
191
|
+
return g === '**/*' || g === '**';
|
|
192
|
+
}
|
|
193
|
+
|
|
183
194
|
function resolveSearchScope(root, workDir) {
|
|
184
195
|
return isAbsolute(root) ? resolve(root) : resolveAgainstCwd(root, workDir);
|
|
185
196
|
}
|
|
@@ -226,46 +237,6 @@ function parseGrepCountLine(line) {
|
|
|
226
237
|
return { path, count };
|
|
227
238
|
}
|
|
228
239
|
|
|
229
|
-
// Per-pattern file-count probe for array grby: runs ONE rg --files-with-matches
|
|
230
|
-
// per pattern (reusing the same scope/glob/type/flags as the merged search) so
|
|
231
|
-
// the summary line can surface patterns that matched zero files — otherwise the
|
|
232
|
-
// merged result hides which member of the array contributed nothing.
|
|
233
|
-
async function _perPatternFileCounts({ patterns, searchPath, globPatterns, caseInsensitive, multilineMode, fileType, workDir }) {
|
|
234
|
-
const results = await Promise.all(patterns.map(async (pattern) => {
|
|
235
|
-
try {
|
|
236
|
-
const rgArgs = buildGrepRgArgs({
|
|
237
|
-
patterns: [pattern],
|
|
238
|
-
searchPath,
|
|
239
|
-
globPatterns,
|
|
240
|
-
outputMode: 'files_with_matches',
|
|
241
|
-
caseInsensitive,
|
|
242
|
-
showLineNumbers: false,
|
|
243
|
-
beforeN: null,
|
|
244
|
-
afterN: null,
|
|
245
|
-
contextN: null,
|
|
246
|
-
multilineMode,
|
|
247
|
-
fileType,
|
|
248
|
-
onlyMatching: false,
|
|
249
|
-
});
|
|
250
|
-
const stdout = await runRg(rgArgs, { cwd: workDir });
|
|
251
|
-
// A boxed partial/truncated stdout means the count is not the true
|
|
252
|
-
// total — render '?' rather than a misleadingly-low or zero number.
|
|
253
|
-
if (stdout && typeof stdout === 'object' && (stdout.partial || stdout.truncated)) return null;
|
|
254
|
-
return String(stdout).split('\n').filter(Boolean).length;
|
|
255
|
-
} catch {
|
|
256
|
-
// Probe threw → count unknown, NOT zero.
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
}));
|
|
260
|
-
// Quote first (so newline/control chars can't inject footer lines), then
|
|
261
|
-
// truncate the quoted string to 40 chars — matches the no-match path.
|
|
262
|
-
const trunc = (p) => {
|
|
263
|
-
const q = JSON.stringify(p);
|
|
264
|
-
return q.length > 40 ? `${q.slice(0, 40)}...` : q;
|
|
265
|
-
};
|
|
266
|
-
return `\n# per-pattern: ${patterns.map((p, i) => `${trunc(p)}=${results[i] === null ? '?' : `${results[i]} files`}`).join(', ')}`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
240
|
function formatGrepOutput({ windowed, totalWindowed, totalKnown, headLimit, offset, outputMode, patterns: _patterns, beforeN, afterN, contextN, searchPath, grepResolvedPath: _grepResolvedPath, workDir, globPatterns: _globPatterns, fileType: _fileType, filenameOmitted = false, prefix = '', broadAdvisory: _broadAdvisory = true }) {
|
|
270
241
|
const lines = headLimit === Infinity ? windowed : windowed.slice(0, headLimit);
|
|
271
242
|
const normalized = lines.map((line) => relativeGrepLine(line, workDir, outputMode === 'files_with_matches', outputMode, filenameOmitted));
|
|
@@ -378,7 +349,9 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
378
349
|
// ripgrep `--glob` uses forward slashes on all platforms; canonicalize
|
|
379
350
|
// `\`→`/` (win32 only) so a `**\*.ts` filter matches instead of being
|
|
380
351
|
// parsed as an escape sequence.
|
|
381
|
-
const normalizedGlobPatterns = uniqueStrings(globPatterns
|
|
352
|
+
const normalizedGlobPatterns = uniqueStrings(globPatterns
|
|
353
|
+
.map(canonicalizeGlobSlashes)
|
|
354
|
+
.filter((g) => !isRedundantAllFilesGlob(g)));
|
|
382
355
|
|
|
383
356
|
const ALLOWED_OUTPUT_MODES = new Set(['files_with_matches', 'content', 'count']);
|
|
384
357
|
const rawOutputMode = typeof args.output_mode === 'string' ? args.output_mode.trim() : '';
|
|
@@ -484,6 +457,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
484
457
|
// drift detection. So only the fresh-compute path (below) records a read.
|
|
485
458
|
if (cached !== null) return cached;
|
|
486
459
|
|
|
460
|
+
return await runResultCacheInFlight(cacheKey, async () => {
|
|
487
461
|
let grepStat;
|
|
488
462
|
try { grepStat = statSync(grepResolvedPath); }
|
|
489
463
|
catch (err) {
|
|
@@ -597,7 +571,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
597
571
|
// pre-offset matches) just means the window skipped past real
|
|
598
572
|
// case-sensitive hits, so the hint would be misleading.
|
|
599
573
|
const trueZeroMatch = offset === 0 && totalWindowed === 0;
|
|
600
|
-
if (trueZeroMatch && !caseInsensitive && patterns.length === 1 && /[A-Za-z]/.test(patterns[0])) {
|
|
574
|
+
if (MIXDOG_GREP_CASE_HINT_PROBE && trueZeroMatch && !caseInsensitive && patterns.length === 1 && /[A-Za-z]/.test(patterns[0])) {
|
|
601
575
|
try {
|
|
602
576
|
const probeArgs = buildGrepRgArgs({
|
|
603
577
|
patterns,
|
|
@@ -620,21 +594,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
620
594
|
} catch { /* best-effort hint */ }
|
|
621
595
|
}
|
|
622
596
|
}
|
|
623
|
-
|
|
624
|
-
// zero-hit members of the merged result are not silently hidden.
|
|
625
|
-
let perPatternSummary = '';
|
|
626
|
-
if (patterns.length > 1) {
|
|
627
|
-
perPatternSummary = await _perPatternFileCounts({
|
|
628
|
-
patterns,
|
|
629
|
-
searchPath,
|
|
630
|
-
globPatterns: normalizedGlobPatterns,
|
|
631
|
-
caseInsensitive,
|
|
632
|
-
multilineMode,
|
|
633
|
-
fileType,
|
|
634
|
-
workDir: rgSpawnCwd,
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
const out = body + rgPartialSuffix + perPatternSummary;
|
|
597
|
+
const out = body + rgPartialSuffix;
|
|
638
598
|
const shownLines = headLimit === Infinity ? windowed : windowed.slice(0, headLimit);
|
|
639
599
|
const remaining = Math.max(0, totalWindowed - shownLines.length);
|
|
640
600
|
// Mirrors formatGrepOutput truncation / totalKnown semantics.
|
|
@@ -749,6 +709,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
749
709
|
const msg = stderr || err?.message || String(err);
|
|
750
710
|
return `Error: ${msg.slice(0, 500)}`;
|
|
751
711
|
}
|
|
712
|
+
});
|
|
752
713
|
}
|
|
753
714
|
|
|
754
715
|
export async function executeGlobTool(args, workDir, options = {}) {
|
|
@@ -835,6 +796,8 @@ export async function executeGlobTool(args, workDir, options = {}) {
|
|
|
835
796
|
return `Error: invalid offset ${JSON.stringify(args.offset)}; expected a non-negative integer`;
|
|
836
797
|
}
|
|
837
798
|
const offset = offsetCoerced === null || offsetCoerced === 0 ? 0 : offsetCoerced;
|
|
799
|
+
const rawSort = typeof args.sort === 'string' ? args.sort.trim() : '';
|
|
800
|
+
const sortMode = rawSort === 'mtime' ? 'mtime' : 'natural';
|
|
838
801
|
// Internal-only ignore extension (see normalizeGlobArgs). Caller (e.g.
|
|
839
802
|
// ai-wrapped-dispatch broad-cwd preflight) appends basename ignore globs
|
|
840
803
|
// so head_limit bounds SOURCE entries rather than artifact noise.
|
|
@@ -860,10 +823,11 @@ export async function executeGlobTool(args, workDir, options = {}) {
|
|
|
860
823
|
.map((root) => normalizeOutputPath(resolvedForSearchRoot(root)))
|
|
861
824
|
.sort()
|
|
862
825
|
.join('\x01');
|
|
863
|
-
const cacheKey = buildGlobCacheKey({ patterns, basePath: cacheBasePath, headLimit, offset, extraIgnore: extraIgnoreGlobs });
|
|
826
|
+
const cacheKey = buildGlobCacheKey({ patterns, basePath: cacheBasePath, headLimit, offset, extraIgnore: extraIgnoreGlobs, sort: sortMode });
|
|
864
827
|
const cached = cacheGet(cacheKey);
|
|
865
828
|
if (cached !== null) return cached;
|
|
866
829
|
|
|
830
|
+
return await runResultCacheInFlight(cacheKey, async () => {
|
|
867
831
|
const globGroups = [...groups.entries()];
|
|
868
832
|
|
|
869
833
|
const allFiles = [];
|
|
@@ -929,23 +893,26 @@ export async function executeGlobTool(args, workDir, options = {}) {
|
|
|
929
893
|
}
|
|
930
894
|
|
|
931
895
|
const unique = Array.from(new Set(allFiles));
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
896
|
+
let orderedPaths;
|
|
897
|
+
if (sortMode === 'mtime') {
|
|
898
|
+
// Opt-in mtime sorting is intentionally slower: it stats every match.
|
|
899
|
+
// Bound the post-rg stat phase so a hung mount cannot pin glob until
|
|
900
|
+
// the agent stall watchdog fires.
|
|
901
|
+
const withStatAll = await statPathsForMtime(unique, workDir, 64, { deadlineMs: 5000 });
|
|
902
|
+
const withStat = withStatAll.filter((entry) => entry?.stat != null);
|
|
903
|
+
withStat.sort((a, b) => {
|
|
904
|
+
const dm = b.mtime - a.mtime;
|
|
905
|
+
if (dm !== 0) return dm;
|
|
906
|
+
return globMtimeTiePath(a).localeCompare(globMtimeTiePath(b));
|
|
907
|
+
});
|
|
908
|
+
orderedPaths = withStat.map((entry) => entry.full || resolveAgainstCwd(entry.path, workDir));
|
|
909
|
+
} else {
|
|
910
|
+
orderedPaths = unique.map((entry) => isAbsolute(entry) ? resolve(entry) : resolveAgainstCwd(entry, workDir));
|
|
911
|
+
}
|
|
912
|
+
const totalBeforeOffset = orderedPaths.length;
|
|
913
|
+
const windowed = offset > 0 ? orderedPaths.slice(offset) : orderedPaths;
|
|
914
|
+
const capped = (headLimit === Infinity ? windowed : windowed.slice(0, headLimit))
|
|
915
|
+
.map((abs) => relativeSearchResultPath(abs, workDir));
|
|
949
916
|
const remaining = windowed.length - capped.length;
|
|
950
917
|
const truncSuffix = accumTruncated
|
|
951
918
|
? '\n... [truncated at accumulation cap (50000)]'
|
|
@@ -979,4 +946,5 @@ export async function executeGlobTool(args, workDir, options = {}) {
|
|
|
979
946
|
try { options.onProgress(`found ${totalBeforeOffset} files`); } catch { /* best-effort */ }
|
|
980
947
|
}
|
|
981
948
|
return out;
|
|
949
|
+
});
|
|
982
950
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
1
|
+
import { spawn, spawnSync } from 'child_process';
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, watch as fsWatch, writeFileSync } from 'fs';
|
|
3
3
|
import * as fsPromises from 'fs/promises';
|
|
4
4
|
import { basename, join } from 'path';
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
} from '../../../../shared/background-tasks.mjs';
|
|
16
16
|
import { startChildGuardian } from '../../../../shared/child-guardian.mjs';
|
|
17
17
|
|
|
18
|
+
globalThis.__mixdogShellJobsRuntimeLoaded = true;
|
|
19
|
+
|
|
18
20
|
function sleep(ms) {
|
|
19
21
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
22
|
}
|
|
@@ -220,29 +222,62 @@ function killProcessTree(pid, signal = 'SIGTERM') {
|
|
|
220
222
|
// owned children. Async jobs are intentionally CLI-owned; no restart replay or
|
|
221
223
|
// daemon handoff is attempted.
|
|
222
224
|
const _liveJobPids = new Set();
|
|
225
|
+
const _liveJobIdsByPid = new Map();
|
|
223
226
|
let _shellJobsExitHookInstalled = false;
|
|
224
|
-
function _registerLiveJobPid(pid) {
|
|
225
|
-
if (Number.isFinite(pid) && pid > 0)
|
|
227
|
+
function _registerLiveJobPid(pid, jobId = null) {
|
|
228
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
229
|
+
_liveJobPids.add(pid);
|
|
230
|
+
if (jobId) _liveJobIdsByPid.set(pid, jobId);
|
|
231
|
+
}
|
|
226
232
|
}
|
|
227
233
|
function _unregisterLiveJobPid(pid) {
|
|
228
|
-
if (Number.isFinite(pid) && pid > 0)
|
|
234
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
235
|
+
_liveJobPids.delete(pid);
|
|
236
|
+
_liveJobIdsByPid.delete(pid);
|
|
237
|
+
}
|
|
229
238
|
}
|
|
230
|
-
function
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
|
|
239
|
+
function _killLiveJobPid(pid, { sync = false } = {}) {
|
|
240
|
+
try {
|
|
241
|
+
if (process.platform === 'win32') {
|
|
242
|
+
if (sync) {
|
|
243
|
+
spawnSync('taskkill.exe', ['/pid', String(pid), '/t', '/f'], {
|
|
244
|
+
windowsHide: true,
|
|
245
|
+
stdio: 'ignore',
|
|
246
|
+
timeout: 1500,
|
|
247
|
+
});
|
|
235
248
|
} else {
|
|
236
|
-
|
|
237
|
-
catch { try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ } }
|
|
249
|
+
spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' });
|
|
238
250
|
}
|
|
239
|
-
}
|
|
251
|
+
} else {
|
|
252
|
+
try { process.kill(-pid, 'SIGKILL'); }
|
|
253
|
+
catch { try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ } }
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function _sweepLiveJobsSync() {
|
|
261
|
+
for (const pid of _liveJobPids) {
|
|
262
|
+
_killLiveJobPid(pid, { sync: true });
|
|
240
263
|
}
|
|
241
264
|
_liveJobPids.clear();
|
|
265
|
+
_liveJobIdsByPid.clear();
|
|
266
|
+
}
|
|
267
|
+
function _ensureProcessListenerHeadroom(events, extra = 1) {
|
|
268
|
+
try {
|
|
269
|
+
if (typeof process.getMaxListeners !== 'function' || typeof process.setMaxListeners !== 'function') return;
|
|
270
|
+
const current = process.getMaxListeners();
|
|
271
|
+
if (current === 0) return;
|
|
272
|
+
let needed = current;
|
|
273
|
+
for (const event of events) needed = Math.max(needed, process.listenerCount(event) + extra);
|
|
274
|
+
if (needed > current) process.setMaxListeners(needed);
|
|
275
|
+
} catch { /* ignore */ }
|
|
242
276
|
}
|
|
243
277
|
function _installShellJobsExitHook() {
|
|
244
278
|
if (_shellJobsExitHookInstalled) return;
|
|
245
279
|
_shellJobsExitHookInstalled = true;
|
|
280
|
+
_ensureProcessListenerHeadroom(['exit', 'SIGTERM', 'SIGINT', 'SIGHUP'], 1);
|
|
246
281
|
try { process.on('exit', _sweepLiveJobsSync); } catch { /* ignore */ }
|
|
247
282
|
try { process.on('SIGTERM', _sweepLiveJobsSync); } catch { /* ignore */ }
|
|
248
283
|
try { process.on('SIGINT', _sweepLiveJobsSync); } catch { /* ignore */ }
|
|
@@ -538,8 +573,8 @@ function refreshShellJob(jobId) {
|
|
|
538
573
|
return detail;
|
|
539
574
|
}
|
|
540
575
|
|
|
541
|
-
export function startBackgroundShellJob({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellType, clientHostPid }) {
|
|
542
|
-
return _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellType, clientHostPid });
|
|
576
|
+
export function startBackgroundShellJob({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellArgs, shellType, clientHostPid }) {
|
|
577
|
+
return _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellArgs, shellType, clientHostPid });
|
|
543
578
|
}
|
|
544
579
|
|
|
545
580
|
// In-process completion watcher. After a background shell task is spawned the
|
|
@@ -572,6 +607,20 @@ const jobNotifyCtxByJobId = new Map();
|
|
|
572
607
|
// consuming the outcome, so the watcher must stay cancelled; the last waiter to
|
|
573
608
|
// leave (count===0) owns the decision to re-arm a still-running job.
|
|
574
609
|
const jobWaitWaiterCountByJobId = new Map();
|
|
610
|
+
function markShellJobCancelledByShutdown(jobId, reason = 'shutdown') {
|
|
611
|
+
const detail = readShellJobDetail(jobId);
|
|
612
|
+
if (!detail || detail.status !== 'running') return false;
|
|
613
|
+
detail.status = 'cancelled';
|
|
614
|
+
detail.exitCode = 137;
|
|
615
|
+
detail.killed = true;
|
|
616
|
+
detail.error = `cancelled by runtime shutdown (${reason})`;
|
|
617
|
+
detail.finishedAt = new Date().toISOString();
|
|
618
|
+
try { writeFileSync(detail.exitPath || shellJobExitPath(jobId), '137'); } catch { /* ignore */ }
|
|
619
|
+
try { writeFileSync(detail.donePath || shellJobDonePath(jobId), ''); } catch { /* ignore */ }
|
|
620
|
+
writeShellJobDetail(detail);
|
|
621
|
+
_unregisterLiveJobPid(detail.pid);
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
575
624
|
// Register a synchronous task waiter. Paired with endShellJobWait in a
|
|
576
625
|
// finally so the count can't leak on throw.
|
|
577
626
|
export function beginShellJobWait(jobId) {
|
|
@@ -590,6 +639,26 @@ export function endShellJobWait(jobId) {
|
|
|
590
639
|
export function clearShellJobNotifyCtx(jobId) {
|
|
591
640
|
jobNotifyCtxByJobId.delete(jobId);
|
|
592
641
|
}
|
|
642
|
+
export function shutdownShellJobs(reason = 'runtime-close', { sync = false } = {}) {
|
|
643
|
+
const livePids = [..._liveJobPids];
|
|
644
|
+
const jobIds = new Set([..._liveJobIdsByPid.values()].filter(Boolean));
|
|
645
|
+
const watcherJobIds = [...backgroundShellJobWatchers.keys()];
|
|
646
|
+
for (const jobId of watcherJobIds) {
|
|
647
|
+
jobIds.add(jobId);
|
|
648
|
+
try { cancelBackgroundShellJobWatch(jobId); } catch { /* ignore */ }
|
|
649
|
+
}
|
|
650
|
+
for (const pid of livePids) _killLiveJobPid(pid, { sync });
|
|
651
|
+
_liveJobPids.clear();
|
|
652
|
+
_liveJobIdsByPid.clear();
|
|
653
|
+
let marked = 0;
|
|
654
|
+
for (const jobId of jobIds) {
|
|
655
|
+
try { if (markShellJobCancelledByShutdown(jobId, reason)) marked += 1; } catch { /* ignore */ }
|
|
656
|
+
}
|
|
657
|
+
backgroundShellJobWatchers.clear();
|
|
658
|
+
jobNotifyCtxByJobId.clear();
|
|
659
|
+
jobWaitWaiterCountByJobId.clear();
|
|
660
|
+
return { killed: livePids.length, cancelledJobs: marked, cancelledWatchers: watcherJobIds.length };
|
|
661
|
+
}
|
|
593
662
|
// Cancel (and unregister) an armed watcher without notifying. Idempotent: a
|
|
594
663
|
// no-op when no watcher is armed, and the per-watcher cancel respects the
|
|
595
664
|
// `settled` guard so it cannot race a real completion notify. The persistent
|
|
@@ -824,12 +893,12 @@ export function adoptForegroundShellJob({ command, cwd, pid, timeoutMs, mergeStd
|
|
|
824
893
|
writeShellJobDetail(detail);
|
|
825
894
|
if (Number.isFinite(pid) && pid > 0) {
|
|
826
895
|
_installShellJobsExitHook();
|
|
827
|
-
_registerLiveJobPid(pid);
|
|
896
|
+
_registerLiveJobPid(pid, jobId);
|
|
828
897
|
}
|
|
829
898
|
return { ...detail, exitPath, donePath };
|
|
830
899
|
}
|
|
831
900
|
|
|
832
|
-
function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellType, clientHostPid }) {
|
|
901
|
+
function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr, spawnEnv, shell, shellArg, shellArgs, shellType, clientHostPid }) {
|
|
833
902
|
// Route ANY PowerShell shell to the PS wrapper, regardless of platform.
|
|
834
903
|
// Gating on win32 sent shell:'powershell' on macOS/Linux down the POSIX
|
|
835
904
|
// path below, which spawns `pwsh <wrapper>.sh` — pwsh then tries to run a
|
|
@@ -871,7 +940,8 @@ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr
|
|
|
871
940
|
// it trivial to distinguish a timeout (124) from a user-side
|
|
872
941
|
// SIGTERM exit (143).
|
|
873
942
|
const innerShellQ = shellQuoteSingle(shell);
|
|
874
|
-
const
|
|
943
|
+
const innerArgs = Array.isArray(shellArgs) && shellArgs.length > 0 ? shellArgs : [shellArg];
|
|
944
|
+
const innerArgsQ = innerArgs.filter((arg) => arg != null && String(arg).length > 0).map(shellQuoteSingle).join(' ');
|
|
875
945
|
// Runtime enforcement proof: the wrapper touches <jobId>.enforced right
|
|
876
946
|
// before exec'ing `timeout`, so the marker exists iff the timeout branch
|
|
877
947
|
// actually ran under the wrapper's own env/cwd. A spawn-time probe can't
|
|
@@ -885,7 +955,7 @@ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr
|
|
|
885
955
|
// status for processes that actually exited 0. `rm -- "$0"` removes
|
|
886
956
|
// the staged wrapper .cmd.sh after donePath is published so a host
|
|
887
957
|
// crash before this point still leaves the file for the sweep to GC.
|
|
888
|
-
const wrapped = `{ if command -v timeout >/dev/null 2>&1; then _to=timeout; elif command -v gtimeout >/dev/null 2>&1; then _to=gtimeout; else _to=; fi; if [ -n "$_to" ]; then touch ${shellQuoteSingle(enforcedPath)}; "$_to" ${timeoutSeconds} ${innerShellQ} ${
|
|
958
|
+
const wrapped = `{ if command -v timeout >/dev/null 2>&1; then _to=timeout; elif command -v gtimeout >/dev/null 2>&1; then _to=gtimeout; else _to=; fi; if [ -n "$_to" ]; then touch ${shellQuoteSingle(enforcedPath)}; "$_to" ${timeoutSeconds} ${innerShellQ} ${innerArgsQ} ${userCmdQuoted}; else ${innerShellQ} ${innerArgsQ} ${userCmdQuoted}; fi; rc=$?; printf '%s' "$rc" > ${shellQuoteSingle(exitPath)}; touch ${shellQuoteSingle(donePath)}; rm -- "$0" 2>/dev/null; exit $rc; }`;
|
|
889
959
|
// Stage the wrapped command to a .sh and let the script open its own
|
|
890
960
|
// output files via `exec > … 2> …`. The parent does NOT pass file
|
|
891
961
|
// descriptors via stdio inheritance (`stdio: 'ignore'` for all three).
|
|
@@ -919,7 +989,7 @@ function _startBackgroundShellJobImpl({ command, timeoutMs, workDir, mergeStderr
|
|
|
919
989
|
label: 'shell-job',
|
|
920
990
|
});
|
|
921
991
|
_installShellJobsExitHook();
|
|
922
|
-
_registerLiveJobPid(child.pid);
|
|
992
|
+
_registerLiveJobPid(child.pid, jobId);
|
|
923
993
|
const detail = {
|
|
924
994
|
jobId,
|
|
925
995
|
kind: 'bash',
|
|
@@ -1056,7 +1126,7 @@ function startBackgroundPowerShellJob({ command, timeoutMs, workDir, mergeStderr
|
|
|
1056
1126
|
return { jobId, kind: 'bash', status: 'failed', error: 'PowerShell background task spawn returned no pid' };
|
|
1057
1127
|
}
|
|
1058
1128
|
_installShellJobsExitHook();
|
|
1059
|
-
_registerLiveJobPid(childPid);
|
|
1129
|
+
_registerLiveJobPid(childPid, jobId);
|
|
1060
1130
|
const detail = {
|
|
1061
1131
|
jobId,
|
|
1062
1132
|
kind: 'bash',
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from './builtin/bash-tool.mjs';
|
|
20
20
|
import {
|
|
21
21
|
executeFindFilesTool,
|
|
22
|
+
executeFuzzyFindTool,
|
|
22
23
|
executeListTool,
|
|
23
24
|
executeTreeTool,
|
|
24
25
|
} from './builtin/list-tool.mjs';
|
|
@@ -129,7 +130,7 @@ export {
|
|
|
129
130
|
} from './builtin/cache-layers.mjs';
|
|
130
131
|
export { atomicWrite } from './builtin/atomic-write.mjs';
|
|
131
132
|
// ---------------------------------------------------------------------------
|
|
132
|
-
// User-cwd persistence
|
|
133
|
+
// User-cwd persistence hook: writes user-cwd.txt on SessionStart so
|
|
133
134
|
// the MCP server (spawned from cache dir) resolves the correct sandbox root.
|
|
134
135
|
// Helper extracted to src/shared/user-cwd.mjs so server-main.mjs can import
|
|
135
136
|
// the same primitive without circular-import risk.
|
|
@@ -252,9 +253,54 @@ const BUILTIN_TOOL_ALIASES = new Map([
|
|
|
252
253
|
['golb', 'glob'],
|
|
253
254
|
]);
|
|
254
255
|
|
|
256
|
+
// Case-insensitive lookup of canonical builtin names, built once from the
|
|
257
|
+
// declarative BUILTIN_TOOLS registry. Models trained on other coding-agent
|
|
258
|
+
// CLIs frequently emit PascalCase tool names ("Read", "Shell", "Grep",
|
|
259
|
+
// "List") for tools mixdog exposes in lowercase. Those calls used to fall
|
|
260
|
+
// through to formatUnknownBuiltinToolMessage and, because the error did not
|
|
261
|
+
// resolve the case mismatch, the model re-issued the SAME capitalised call
|
|
262
|
+
// every iteration until the loop cap. A lowercase→canonical map makes the
|
|
263
|
+
// dispatcher accept the case variant directly.
|
|
264
|
+
const _BUILTIN_LOWER_TO_CANONICAL = new Map(
|
|
265
|
+
BUILTIN_TOOLS.map((t) => [String(t.name || '').toLowerCase(), t.name]).filter(([k]) => k),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Well-known tool names from OTHER agent CLIs that have no mixdog builtin.
|
|
269
|
+
// They cannot be silently remapped (their argument shapes differ — e.g.
|
|
270
|
+
// Write{path,contents} vs apply_patch{patch}) so the unknown-tool error
|
|
271
|
+
// surfaces a concrete redirect instead of a bare "unknown tool", which is
|
|
272
|
+
// what kept models looping on a non-existent tool.
|
|
273
|
+
const EXTERNAL_TOOL_REDIRECTS = new Map([
|
|
274
|
+
['write', 'use `apply_patch` with an `*** Add File:` V4A section to create a file'],
|
|
275
|
+
['edit', 'use `apply_patch` with a `*** Update File:` V4A section'],
|
|
276
|
+
['multiedit', 'use a single `apply_patch` call with multiple `*** Update File:` sections'],
|
|
277
|
+
['strreplace', 'use `apply_patch` with a `*** Update File:` V4A section'],
|
|
278
|
+
['str_replace', 'use `apply_patch` with a `*** Update File:` V4A section'],
|
|
279
|
+
['str_replace_editor', 'use `apply_patch` with a `*** Update File:` V4A section'],
|
|
280
|
+
['createfile', 'use `apply_patch` with an `*** Add File:` V4A section'],
|
|
281
|
+
['create_file', 'use `apply_patch` with an `*** Add File:` V4A section'],
|
|
282
|
+
['deletefile', 'use `apply_patch` with a `*** Delete File:` V4A section'],
|
|
283
|
+
['delete_file', 'use `apply_patch` with a `*** Delete File:` V4A section'],
|
|
284
|
+
['bash', 'use the `shell` tool'],
|
|
285
|
+
['run', 'use the `shell` tool'],
|
|
286
|
+
['runcommand', 'use the `shell` tool'],
|
|
287
|
+
['terminal', 'use the `shell` tool'],
|
|
288
|
+
['view', 'use the `read` tool'],
|
|
289
|
+
['cat', 'use the `read` tool'],
|
|
290
|
+
['ls', 'use the `list` tool'],
|
|
291
|
+
['searchfiles', 'use the `grep` tool'],
|
|
292
|
+
['codebase_search', 'use the `grep` or `code_graph` tool'],
|
|
293
|
+
]);
|
|
294
|
+
|
|
255
295
|
export function canonicalizeBuiltinToolName(name) {
|
|
256
296
|
if (typeof name !== 'string') return name;
|
|
257
|
-
|
|
297
|
+
const aliased = BUILTIN_TOOL_ALIASES.get(name);
|
|
298
|
+
if (aliased) return aliased;
|
|
299
|
+
// Exact match wins (cheapest, most common path).
|
|
300
|
+
if (_BUILTIN_LOWER_TO_CANONICAL.get(name) === name) return name;
|
|
301
|
+
// Case-insensitive fall-through: "Read"/"SHELL"/"Grep" → canonical.
|
|
302
|
+
const lowerCanonical = _BUILTIN_LOWER_TO_CANONICAL.get(name.toLowerCase());
|
|
303
|
+
return lowerCanonical || name;
|
|
258
304
|
}
|
|
259
305
|
|
|
260
306
|
function editDistance(a, b) {
|
|
@@ -294,6 +340,12 @@ export function suggestBuiltinToolName(name) {
|
|
|
294
340
|
}
|
|
295
341
|
|
|
296
342
|
export function formatUnknownBuiltinToolMessage(name, _args = {}, noun = 'builtin tool') {
|
|
343
|
+
// Concrete redirect for a well-known tool from another agent CLI whose
|
|
344
|
+
// argument shape differs from any mixdog builtin (so it cannot be
|
|
345
|
+
// case-normalised into one). Without this, the model re-issues the same
|
|
346
|
+
// non-existent tool every iteration until the loop cap.
|
|
347
|
+
const redirect = typeof name === 'string' ? EXTERNAL_TOOL_REDIRECTS.get(name.toLowerCase()) : null;
|
|
348
|
+
if (redirect) return `Error: unknown ${noun} "${name}" — ${redirect}.`;
|
|
297
349
|
const suggestion = suggestBuiltinToolName(name);
|
|
298
350
|
if (!suggestion) return `Error: unknown ${noun} "${name}"`;
|
|
299
351
|
return `Error: unknown ${noun} "${name}". Did you mean "${suggestion}"?`;
|
|
@@ -308,7 +360,7 @@ process.on('exit', flushReadRangeIndexesSync);
|
|
|
308
360
|
|
|
309
361
|
configureReadRangeIndexTelemetry({ trace: _ioTrace, hashText: _hashText });
|
|
310
362
|
|
|
311
|
-
// Uniform tool-output cap (
|
|
363
|
+
// Uniform tool-output cap (`tool_output_token_limit` analogue): a SINGLE
|
|
312
364
|
// optional knob that bounds EVERY builtin string result before it enters the
|
|
313
365
|
// lead context, on top of each tool's own caps. Default 0 = OFF (per-tool caps
|
|
314
366
|
// only, no behaviour change). Set MIXDOG_TOOL_OUTPUT_MAX_BYTES (or pass
|
|
@@ -342,7 +394,7 @@ function capToolOutput(result, options = {}) {
|
|
|
342
394
|
const bytes = Buffer.byteLength(result, 'utf8');
|
|
343
395
|
if (bytes <= cap) return result;
|
|
344
396
|
// Middle-truncate: keep head + tail (byte-accurate), drop the middle. Mirrors
|
|
345
|
-
//
|
|
397
|
+
// Middle truncation so the model still sees both ends of a runaway.
|
|
346
398
|
const half = Math.max(1, Math.floor(cap / 2) - 64);
|
|
347
399
|
const head = _sliceToBytesFromStart(result, half);
|
|
348
400
|
const tail = _sliceToBytesFromEnd(result, half);
|
|
@@ -371,7 +423,7 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
|
|
|
371
423
|
const readStateScope = options?.readStateScope ?? options?.sessionId ?? null;
|
|
372
424
|
const executeChildBuiltinTool = (childName, childArgs, childCwd = workDir, childOptions = null) =>
|
|
373
425
|
executeBuiltinTool(childName, childArgs, childCwd, childOptions ? { ...options, ...childOptions } : options);
|
|
374
|
-
// Path policy:
|
|
426
|
+
// Path policy: host settings.json permissions (mcp__* allow) are the
|
|
375
427
|
// sole arbiter for workspace-boundary decisions.
|
|
376
428
|
const _toolResult = await (async () => {
|
|
377
429
|
switch (toolName) {
|
|
@@ -389,6 +441,8 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
|
|
|
389
441
|
return executeGrepTool(args, workDir, executeChildBuiltinTool, readStateScope, options);
|
|
390
442
|
case 'glob':
|
|
391
443
|
return executeGlobTool(args, workDir, options);
|
|
444
|
+
case 'find':
|
|
445
|
+
return executeFuzzyFindTool(args, workDir, options);
|
|
392
446
|
case 'list':
|
|
393
447
|
return executeListTool(args, workDir, options);
|
|
394
448
|
case 'tree':
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve as pathResolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const codeGraphCache = new Map();
|
|
5
|
+
export const codeGraphDirtyPaths = new Map();
|
|
6
|
+
|
|
7
|
+
const codeGraphDirtyGen = new Map();
|
|
8
|
+
let drainCodeGraphCacheFn = null;
|
|
9
|
+
|
|
10
|
+
export function canonicalGraphCwd(cwd) {
|
|
11
|
+
if (!cwd) throw new Error('code_graph requires cwd - caller did not provide a working directory');
|
|
12
|
+
const full = pathResolve(cwd);
|
|
13
|
+
return process.platform === 'win32' ? full.toLowerCase() : full;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function canonicalGraphPath(p) {
|
|
17
|
+
const full = pathResolve(String(p || ''));
|
|
18
|
+
return process.platform === 'win32' ? full.toLowerCase() : full;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getCodeGraphGen(graphCwd) {
|
|
22
|
+
return codeGraphDirtyGen.get(graphCwd) || 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function bumpCodeGraphGen(graphCwd) {
|
|
26
|
+
codeGraphDirtyGen.set(graphCwd, (codeGraphDirtyGen.get(graphCwd) || 0) + 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function consumeCodeGraphDirtyPaths(cwd) {
|
|
30
|
+
const key = canonicalGraphCwd(cwd);
|
|
31
|
+
const set = codeGraphDirtyPaths.get(key);
|
|
32
|
+
if (!set || set.size === 0) return [];
|
|
33
|
+
codeGraphDirtyPaths.delete(key);
|
|
34
|
+
return [...set];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Accept absolute written paths only; resolve affected indexed roots centrally.
|
|
38
|
+
export function markCodeGraphDirtyPaths(paths) {
|
|
39
|
+
const values = Array.isArray(paths) ? paths : [paths];
|
|
40
|
+
const cleaned = values
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.map((p) => canonicalGraphPath(p));
|
|
43
|
+
if (cleaned.length === 0) return;
|
|
44
|
+
|
|
45
|
+
const knownRoots = new Set([...codeGraphDirtyPaths.keys(), ...codeGraphCache.keys()]);
|
|
46
|
+
const affectedRoots = new Set();
|
|
47
|
+
for (const absPath of cleaned) {
|
|
48
|
+
let matchedThisPath = false;
|
|
49
|
+
for (const root of knownRoots) {
|
|
50
|
+
const canonRoot = canonicalGraphPath(root);
|
|
51
|
+
if (absPath.startsWith(canonRoot + '/') || absPath.startsWith(canonRoot + '\\') || absPath === canonRoot) {
|
|
52
|
+
affectedRoots.add(root);
|
|
53
|
+
matchedThisPath = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!matchedThisPath) {
|
|
57
|
+
let dir = dirname(absPath);
|
|
58
|
+
while (dir && dir !== dirname(dir)) {
|
|
59
|
+
if (existsSync(join(dir, 'package.json')) || existsSync(join(dir, '.git'))) {
|
|
60
|
+
affectedRoots.add(canonicalGraphCwd(dir));
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
dir = dirname(dir);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const root of affectedRoots) {
|
|
69
|
+
if (!codeGraphDirtyPaths.has(root)) codeGraphDirtyPaths.set(root, new Set());
|
|
70
|
+
const set = codeGraphDirtyPaths.get(root);
|
|
71
|
+
for (const p of cleaned) set.add(p);
|
|
72
|
+
|
|
73
|
+
const canonRoot = canonicalGraphCwd(root);
|
|
74
|
+
codeGraphCache.delete(canonRoot);
|
|
75
|
+
bumpCodeGraphGen(canonRoot);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function registerCodeGraphDrain(fn) {
|
|
80
|
+
drainCodeGraphCacheFn = typeof fn === 'function' ? fn : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function drainCodeGraphCache() {
|
|
84
|
+
if (!drainCodeGraphCacheFn) return;
|
|
85
|
+
drainCodeGraphCacheFn();
|
|
86
|
+
}
|
|
@@ -3,20 +3,20 @@ export const CODE_GRAPH_TOOL_DEFS = [
|
|
|
3
3
|
name: 'code_graph',
|
|
4
4
|
title: 'Code Graph',
|
|
5
5
|
annotations: { title: 'Code Graph', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false, compressibleLossless: true },
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Code structure: symbols, refs, calls, imports, impact.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
|
-
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'search', 'references', 'callers', 'callees', 'prewarm'], description: 'Operation
|
|
11
|
-
file: { type: 'string', description: '
|
|
12
|
-
symbol: { type: 'string', description: 'Identifier or
|
|
13
|
-
symbols: { type: 'array', items: { type: 'string' }, description: 'Batch identifiers
|
|
14
|
-
body: { type: 'boolean', description: '
|
|
15
|
-
language: { type: 'string', description: '
|
|
16
|
-
limit: { type: 'number', minimum: 1, description: 'Max results
|
|
17
|
-
depth: { type: 'number', minimum: 1, maximum: 5, description: '
|
|
18
|
-
page: { type: 'number', minimum: 1, description: '
|
|
19
|
-
cwd: { type: 'string', description: 'Project root
|
|
10
|
+
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'search', 'references', 'callers', 'callees', 'prewarm'], description: 'Operation.' },
|
|
11
|
+
file: { type: 'string', description: 'Source file.' },
|
|
12
|
+
symbol: { type: 'string', description: 'Identifier or symbol keyword.' },
|
|
13
|
+
symbols: { type: 'array', items: { type: 'string' }, description: 'Batch identifiers.' },
|
|
14
|
+
body: { type: 'boolean', description: 'Include body.' },
|
|
15
|
+
language: { type: 'string', description: 'Language hint.' },
|
|
16
|
+
limit: { type: 'number', minimum: 1, description: 'Max results.' },
|
|
17
|
+
depth: { type: 'number', minimum: 1, maximum: 5, description: 'Caller depth.' },
|
|
18
|
+
page: { type: 'number', minimum: 1, description: 'Caller page.' },
|
|
19
|
+
cwd: { type: 'string', description: 'Project root.' },
|
|
20
20
|
},
|
|
21
21
|
required: ['mode'],
|
|
22
22
|
},
|