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
|
@@ -41,14 +41,16 @@ import * as nodeUtil from 'node:util';
|
|
|
41
41
|
import { existsSync } from 'node:fs';
|
|
42
42
|
import { randomUUID } from 'node:crypto';
|
|
43
43
|
import { invalidateBuiltinResultCache, analyzeShellCommandEffects, preflightShellLargeFileProbe } from './builtin.mjs';
|
|
44
|
-
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from './code-graph.mjs';
|
|
44
|
+
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from './code-graph-state.mjs';
|
|
45
45
|
import { maybeRewriteWmicProcessCommand } from './shell-policy.mjs';
|
|
46
46
|
import { _maybeEncodePowerShellCommand } from './shell-command.mjs';
|
|
47
|
-
import { _captureTrackedMtimes, _trackedDriftNoteAfter,
|
|
47
|
+
import { _captureTrackedMtimes, _trackedDriftNoteAfter, getDedupedDestructiveWarnings } from './builtin/bash-tool.mjs';
|
|
48
48
|
import { scrubLoaderVars, scrubProviderSecrets } from './env-scrub.mjs';
|
|
49
49
|
import { checkExecPolicyMessage } from './bash-policy-scan.mjs';
|
|
50
50
|
import { startChildGuardian } from '../../../shared/child-guardian.mjs';
|
|
51
51
|
|
|
52
|
+
globalThis.__mixdogBashSessionRuntimeLoaded = true;
|
|
53
|
+
|
|
52
54
|
// Default 600 s (10 min), max 1800 s. Aligned with the one-shot bash tool's
|
|
53
55
|
// 600 s default (builtin/bash-tool.mjs); the persistent shell carries
|
|
54
56
|
// longer-running pipelines (build/test/install) than one-shot calls, so a
|
|
@@ -171,15 +173,18 @@ function _startReaper() {
|
|
|
171
173
|
_killSession(id, 'idle-timeout');
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
|
-
|
|
175
|
-
clearInterval(_reaperTimer);
|
|
176
|
-
_reaperTimer = null;
|
|
177
|
-
}
|
|
176
|
+
_clearReaperIfIdle();
|
|
178
177
|
}, 30_000);
|
|
179
178
|
// Don't keep the event loop alive just for the reaper.
|
|
180
179
|
if (typeof _reaperTimer.unref === 'function') _reaperTimer.unref();
|
|
181
180
|
}
|
|
182
181
|
|
|
182
|
+
function _clearReaperIfIdle() {
|
|
183
|
+
if (_sessions.size !== 0 || !_reaperTimer) return;
|
|
184
|
+
clearInterval(_reaperTimer);
|
|
185
|
+
_reaperTimer = null;
|
|
186
|
+
}
|
|
187
|
+
|
|
183
188
|
// Kill a spawned shell along with any child processes it forked. Posix path
|
|
184
189
|
// signals the process group (pgid == pid because we spawn with detached:true),
|
|
185
190
|
// so `sleep 1000 &` or a node server started inside the session is reaped
|
|
@@ -239,6 +244,17 @@ async function _killSession(id, _reason) {
|
|
|
239
244
|
try { s.proc.stdin?.end(); } catch { /* ignore */ }
|
|
240
245
|
_killProcessTree(s.proc);
|
|
241
246
|
await Promise.race([exited, new Promise((r) => setTimeout(r, 3000))]);
|
|
247
|
+
_clearReaperIfIdle();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _killSessionNow(id, _reason) {
|
|
251
|
+
const s = _sessions.get(id);
|
|
252
|
+
if (!s) return false;
|
|
253
|
+
_sessions.delete(id);
|
|
254
|
+
try { s.proc.stdin?.end(); } catch { /* ignore */ }
|
|
255
|
+
_killProcessTree(s.proc);
|
|
256
|
+
_clearReaperIfIdle();
|
|
257
|
+
return true;
|
|
242
258
|
}
|
|
243
259
|
|
|
244
260
|
function shellQuoteSingle(s) {
|
|
@@ -713,10 +729,21 @@ export function closeBashSession(sessionId, reason = 'external-close') {
|
|
|
713
729
|
return true;
|
|
714
730
|
}
|
|
715
731
|
|
|
732
|
+
export async function shutdownBashSessions(reason = 'runtime-close') {
|
|
733
|
+
const ids = [..._sessions.keys()];
|
|
734
|
+
await Promise.allSettled(ids.map((id) => _killSession(id, reason)));
|
|
735
|
+
_clearReaperIfIdle();
|
|
736
|
+
return { closed: ids.length };
|
|
737
|
+
}
|
|
738
|
+
|
|
716
739
|
// Best-effort cleanup on process exit so orphan bash children don't linger
|
|
717
740
|
// when the plugin host shuts down. Self-registered exit drain; bare 'exit' hook stays as idempotent backup.
|
|
718
|
-
export function drainBashSessions() {
|
|
719
|
-
|
|
741
|
+
export function drainBashSessions(reason = 'process-exit') {
|
|
742
|
+
let closed = 0;
|
|
743
|
+
for (const id of [..._sessions.keys()]) {
|
|
744
|
+
if (_killSessionNow(id, reason)) closed += 1;
|
|
745
|
+
}
|
|
746
|
+
return { closed };
|
|
720
747
|
}
|
|
721
748
|
if (typeof process?.on === 'function') {
|
|
722
749
|
process.on('exit', drainBashSessions);
|
|
@@ -76,11 +76,6 @@ function isStringOrStringArray(v) {
|
|
|
76
76
|
return true;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function hasBackslashPipe(value) {
|
|
80
|
-
if (typeof value === 'string') return false;
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
79
|
function hasUnsupportedRipgrepRegex(value) {
|
|
85
80
|
const values = Array.isArray(value) ? value : [value];
|
|
86
81
|
return values.some((item) => {
|
|
@@ -144,9 +139,6 @@ function guardGrep(a) {
|
|
|
144
139
|
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
145
140
|
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
146
141
|
}
|
|
147
|
-
if (hasOwn(a, k) && hasBackslashPipe(a[k])) {
|
|
148
|
-
return `Error: grep arg "${k}" contains \\|. Use pattern:["a","b"] for OR terms, or unescaped | for regex alternation.`;
|
|
149
|
-
}
|
|
150
142
|
if (hasOwn(a, k) && hasUnsupportedRipgrepRegex(a[k])) {
|
|
151
143
|
return `Error: grep arg "${k}" uses regex syntax ripgrep does not support here (lookaround/backrefs). Use plain pattern arrays or simpler regex.`;
|
|
152
144
|
}
|
|
@@ -311,6 +303,24 @@ function guardList(a) {
|
|
|
311
303
|
return null;
|
|
312
304
|
}
|
|
313
305
|
|
|
306
|
+
function guardFind(a) {
|
|
307
|
+
if (!hasOwn(a, 'query') || typeof a.query !== 'string' || a.query.trim().length === 0) {
|
|
308
|
+
return `Error: find requires non-empty string "query" (got ${describeType(a.query)})`;
|
|
309
|
+
}
|
|
310
|
+
if (hasOwn(a, 'path') && !isString(a.path)) {
|
|
311
|
+
return `Error: find arg "path" must be a string (got ${describeType(a.path)})`;
|
|
312
|
+
}
|
|
313
|
+
if (hasOwn(a, 'head_limit') && a.head_limit !== undefined && a.head_limit !== null) {
|
|
314
|
+
if (!isFiniteInt(a.head_limit)) {
|
|
315
|
+
return `Error: find arg "head_limit" must be a finite integer (got ${describeType(a.head_limit)})`;
|
|
316
|
+
}
|
|
317
|
+
if (a.head_limit < 0) {
|
|
318
|
+
return `Error: find arg "head_limit" must be >= 0 (0 means no cap); got ${a.head_limit}`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
314
324
|
function guardGlob(a) {
|
|
315
325
|
// path alias root; pattern aliases glob/name/file_pattern
|
|
316
326
|
for (const k of ['path', 'root']) {
|
|
@@ -365,6 +375,7 @@ const GUARDS = {
|
|
|
365
375
|
shell: guardShell,
|
|
366
376
|
task: guardTask,
|
|
367
377
|
list: guardList,
|
|
378
|
+
find: guardFind,
|
|
368
379
|
glob: guardGlob,
|
|
369
380
|
code_graph: guardCodeGraph,
|
|
370
381
|
};
|
|
@@ -3,8 +3,8 @@ import { execShellCommand, stripAnsi } from '../shell-command.mjs';
|
|
|
3
3
|
import { wrapCommandWithSnapshot } from '../shell-snapshot.mjs';
|
|
4
4
|
import { getDestructiveCommandWarning } from '../destructive-warning.mjs';
|
|
5
5
|
import { maybeRewriteWmicProcessCommand } from '../shell-policy.mjs';
|
|
6
|
-
import { buildBashPolicyScanTargets, checkExecPolicyMessage
|
|
7
|
-
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph.mjs';
|
|
6
|
+
import { buildBashPolicyScanTargets, checkExecPolicyMessage } from '../bash-policy-scan.mjs';
|
|
7
|
+
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph-state.mjs';
|
|
8
8
|
import {
|
|
9
9
|
buildJobNotFoundMessage,
|
|
10
10
|
startBackgroundShellJob,
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
cancelBackgroundTask,
|
|
28
28
|
completeBackgroundTask,
|
|
29
29
|
getBackgroundTask,
|
|
30
|
-
listBackgroundTasks,
|
|
31
30
|
registerBackgroundTask,
|
|
32
31
|
renderBackgroundTask,
|
|
33
32
|
renderBackgroundTaskList,
|
|
@@ -48,7 +47,6 @@ import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
|
|
|
48
47
|
// closing the "external write -> stale old_string -> code 8" gap when shell is
|
|
49
48
|
// routed through this tool. Bounded to the tracked-read set (capped) so cost
|
|
50
49
|
// stays off the whole-cwd path; emits nothing when no read file changed.
|
|
51
|
-
const _DRIFT_SCAN_CAP = 256;
|
|
52
50
|
export function _captureTrackedMtimes(_scope) {
|
|
53
51
|
return new Map();
|
|
54
52
|
}
|
|
@@ -60,7 +58,7 @@ export function _trackedDriftNoteAfter(_scope, _pre) {
|
|
|
60
58
|
// AbortSignal (e.g. the MCP/request signal threaded through options.abortSignal).
|
|
61
59
|
// Returns null when neither is present so existing session-only behavior is
|
|
62
60
|
// preserved unchanged. Uses AbortSignal.any when available; falls back to a
|
|
63
|
-
// manual controller + listener
|
|
61
|
+
// manual controller + listener path otherwise. The returned signal aborts as
|
|
64
62
|
// soon as either input signal aborts, which propagates to execShellCommand /
|
|
65
63
|
// executeBashSessionTool and triggers the same child-kill path the session
|
|
66
64
|
// signal already drives.
|
|
@@ -86,53 +84,12 @@ function _combineAbortSignals(sessionSignal, externalSignal) {
|
|
|
86
84
|
return ctl.signal;
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
// Decode ANSI-C $'…' and locale $"…" escapes so the blocklist scan sees the
|
|
90
|
-
// literal command (e.g. $'\x72m' → "rm"). Defensive against quoting bypass.
|
|
91
|
-
function _decodeAnsiCQuotes(s) {
|
|
92
|
-
if (typeof s !== 'string') return '';
|
|
93
|
-
if (s.indexOf('$') === -1) return s;
|
|
94
|
-
return s.replace(/\$(['"])((?:\\.|[^\\])*?)\1/g, (_full, _q, body) =>
|
|
95
|
-
body
|
|
96
|
-
.replace(/\\x([0-9a-fA-F]{1,2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
|
|
97
|
-
.replace(/\\u([0-9a-fA-F]{1,4})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
|
|
98
|
-
.replace(/\\0([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
|
|
99
|
-
.replace(/\\([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
|
|
100
|
-
.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r')
|
|
101
|
-
.replace(/\\\\/g, '\\').replace(/\\(['"])/g, '$1'),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Extract $(…) and `…` command-substitution bodies so each is re-scanned by
|
|
106
|
-
// isBlockedCommand (e.g. eval $(printf 'rm -rf ~')).
|
|
107
|
-
function _extractSubstitutionBodies(s) {
|
|
108
|
-
if (typeof s !== 'string') return [];
|
|
109
|
-
const out = [];
|
|
110
|
-
const re = /\$\(([^()]*(?:\([^()]*\)[^()]*)*)\)|`([^`]*)`/g;
|
|
111
|
-
let m;
|
|
112
|
-
while ((m = re.exec(s)) !== null) {
|
|
113
|
-
const body = m[1] != null ? m[1] : m[2];
|
|
114
|
-
if (body && body.trim()) out.push(body);
|
|
115
|
-
}
|
|
116
|
-
return out;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Combined injection-aware block targets: decoded form + substitution bodies
|
|
120
|
-
// (and their decoded forms). Used on BOTH the persistent and stateless paths.
|
|
121
|
-
export function _injectionBlockTargets(cmd) {
|
|
122
|
-
return injectionBlockTargets(cmd);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
87
|
function _prefixPowerShellUtf8(command) {
|
|
126
88
|
const prefix = '[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=[System.Text.Encoding]::UTF8;';
|
|
127
89
|
const text = String(command || '');
|
|
128
90
|
return text.trimStart().startsWith(prefix) ? text : `${prefix}\n${text}`;
|
|
129
91
|
}
|
|
130
92
|
|
|
131
|
-
const _unquoteSpansForPolicy = (s) => s.replace(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/g, (m) => m.slice(1, -1));
|
|
132
|
-
|
|
133
|
-
// Same normalized + decoded target set as hard-block (strip/unquote/shell -c/PS).
|
|
134
|
-
export { buildBashPolicyScanTargets } from '../bash-policy-scan.mjs';
|
|
135
|
-
|
|
136
93
|
export function getDedupedDestructiveWarnings(command) {
|
|
137
94
|
const seenMsg = new Set();
|
|
138
95
|
const warnings = [];
|
|
@@ -343,7 +300,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
343
300
|
// the background task finished (no polling tool is auto-driven). The
|
|
344
301
|
// notify ctx is threaded down from the MCP dispatch frame
|
|
345
302
|
// (server-main agentContext / _dispatchByModule) the same way the
|
|
346
|
-
//
|
|
303
|
+
// agent/explore-style tools receive notifyFn/routingSessionId/clientHostPid.
|
|
347
304
|
// Missing notifyFn (e.g. a non-MCP caller) degrades to a stderr
|
|
348
305
|
// diagnostic inside watchBackgroundShellJob — never fails the spawn.
|
|
349
306
|
try {
|
|
@@ -548,12 +505,12 @@ export async function executeTaskTool(args, options = {}) {
|
|
|
548
505
|
|
|
549
506
|
const taskId = typeof args.task_id === 'string' ? args.task_id.trim() : '';
|
|
550
507
|
if (!taskId) return 'Error: task_id is required';
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
if (/^
|
|
556
|
-
return `Error: "${taskId}" is
|
|
508
|
+
// sess_* values are agent/orchestrator session ids, not background shell
|
|
509
|
+
// tasks. task only resolves `shell mode=async` tasks, so surface a
|
|
510
|
+
// self-correcting hint instead of the bare "task not found" that otherwise
|
|
511
|
+
// invites a wrong-tool retry loop.
|
|
512
|
+
if (/^sess_/.test(taskId)) {
|
|
513
|
+
return `Error: "${taskId}" is an agent/session id, not a background task_id. Agent tasks deliver completion notifications; use agent list/read only for manual recovery.`;
|
|
557
514
|
}
|
|
558
515
|
|
|
559
516
|
const task = getBackgroundTask(taskId, { context: options });
|
|
@@ -17,14 +17,23 @@ export const BUILTIN_TOOLS = [
|
|
|
17
17
|
name: 'read',
|
|
18
18
|
title: 'Mixdog Read',
|
|
19
19
|
annotations: { title: 'Mixdog Read', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false },
|
|
20
|
-
description: 'Read
|
|
20
|
+
description: 'Read known file path(s). Use line+context for small windows. Batch paths.',
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: 'object',
|
|
23
23
|
properties: {
|
|
24
|
-
path: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
path: {
|
|
25
|
+
anyOf: [
|
|
26
|
+
{ type: 'string' },
|
|
27
|
+
{
|
|
28
|
+
type: 'array',
|
|
29
|
+
items: { type: 'string' },
|
|
30
|
+
minItems: 1,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
description: 'File path or array. Dirs use list.',
|
|
34
|
+
},
|
|
35
|
+
line: { type: 'number', minimum: 1, description: 'Anchor line.' },
|
|
36
|
+
context: { type: 'number', minimum: 0, maximum: 200, description: 'Lines around line; max 200.' },
|
|
28
37
|
},
|
|
29
38
|
},
|
|
30
39
|
},
|
|
@@ -32,11 +41,11 @@ export const BUILTIN_TOOLS = [
|
|
|
32
41
|
name: 'diagnostics',
|
|
33
42
|
title: 'Mixdog Diagnostics',
|
|
34
43
|
annotations: { title: 'Mixdog Diagnostics', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
35
|
-
description: 'Run
|
|
44
|
+
description: 'Run matching type/lint checker. Default cwd; no LSP.',
|
|
36
45
|
inputSchema: {
|
|
37
46
|
type: 'object',
|
|
38
47
|
properties: {
|
|
39
|
-
path: { type: 'string', description: 'File
|
|
48
|
+
path: { type: 'string', description: 'File/dir. Default cwd.' },
|
|
40
49
|
},
|
|
41
50
|
required: [],
|
|
42
51
|
},
|
|
@@ -45,29 +54,29 @@ export const BUILTIN_TOOLS = [
|
|
|
45
54
|
name: 'open_config',
|
|
46
55
|
title: 'Open Config UI',
|
|
47
56
|
annotations: { title: 'Open Config UI', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
48
|
-
description: 'Open
|
|
57
|
+
description: 'Open settings UI; returns URL. No params.',
|
|
49
58
|
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
50
59
|
},
|
|
51
60
|
{
|
|
52
61
|
name: 'shell',
|
|
53
62
|
title: 'Mixdog Shell',
|
|
54
63
|
annotations: { title: 'Mixdog Shell', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
55
|
-
description: `Run
|
|
64
|
+
description: `Run shell for git/build/test/run. Not for source browsing; use code_graph/grep/read/list/glob. Set shell: powershell or bash. ${TOOL_ASYNC_EXECUTION_CONTRACT}`,
|
|
56
65
|
inputSchema: {
|
|
57
66
|
type: 'object',
|
|
58
67
|
properties: {
|
|
59
|
-
command: { type: 'string', description: '
|
|
60
|
-
cwd: { type: 'string', description: 'Working directory
|
|
68
|
+
command: { type: 'string', description: 'Command.' },
|
|
69
|
+
cwd: { type: 'string', description: 'Working directory.' },
|
|
61
70
|
workdir: { type: 'string', description: 'Alias for cwd; omit if empty.' },
|
|
62
|
-
timeout: { type: 'number', description: 'Timeout
|
|
63
|
-
merge_stderr: { type: 'boolean', description: 'Merge stderr
|
|
71
|
+
timeout: { type: 'number', description: 'Timeout ms.' },
|
|
72
|
+
merge_stderr: { type: 'boolean', description: 'Merge stderr.' },
|
|
64
73
|
mode: { type: 'string', enum: ['sync', 'async'], description: executionModeSchemaDescription('sync') },
|
|
65
74
|
run_in_background: { type: 'boolean', description: `Legacy alias for mode=async. ${TOOL_ASYNC_EXECUTION_CONTRACT}` },
|
|
66
|
-
persistent: { type: 'boolean', description: 'Persistent shell mode
|
|
67
|
-
session_id: { type: 'string', description: 'Persistent shell id
|
|
68
|
-
create: { type: 'boolean', description: '
|
|
69
|
-
close: { type: 'boolean', description: 'Close
|
|
70
|
-
shell: { type: 'string', enum: ['bash', 'powershell'], description:
|
|
75
|
+
persistent: { type: 'boolean', description: 'Persistent shell mode.' },
|
|
76
|
+
session_id: { type: 'string', description: 'Persistent shell id.' },
|
|
77
|
+
create: { type: 'boolean', description: 'Create session_id if missing.' },
|
|
78
|
+
close: { type: 'boolean', description: 'Close persistent session.' },
|
|
79
|
+
shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell. Windows default is PowerShell; bash means Git Bash/POSIX.' },
|
|
71
80
|
},
|
|
72
81
|
required: ['command'],
|
|
73
82
|
},
|
|
@@ -76,14 +85,14 @@ export const BUILTIN_TOOLS = [
|
|
|
76
85
|
name: 'task',
|
|
77
86
|
title: 'Background Task Control',
|
|
78
87
|
annotations: { title: 'Background Task Control', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
79
|
-
description: `Manual control for
|
|
88
|
+
description: `Manual control for shell async task_id. actions: list/status/read/wait/cancel. ${TOOL_MANUAL_CONTROL_CONTRACT} Not sess_* or agent ids.`,
|
|
80
89
|
inputSchema: {
|
|
81
90
|
type: 'object',
|
|
82
91
|
properties: {
|
|
83
|
-
task_id: { type: 'string', description: '
|
|
84
|
-
action: { type: 'string', enum: ['list', 'status', 'read', 'wait', 'cancel'], description: 'Manual
|
|
85
|
-
timeout_ms: { type: 'number', description: '
|
|
86
|
-
poll_ms: { type: 'number', description: '
|
|
92
|
+
task_id: { type: 'string', description: 'shell async task_id.' },
|
|
93
|
+
action: { type: 'string', enum: ['list', 'status', 'read', 'wait', 'cancel'], description: 'Manual action. Avoid polling loops.' },
|
|
94
|
+
timeout_ms: { type: 'number', description: 'Wait timeout ms.' },
|
|
95
|
+
poll_ms: { type: 'number', description: 'Wait poll ms.' },
|
|
87
96
|
},
|
|
88
97
|
required: [],
|
|
89
98
|
},
|
|
@@ -92,7 +101,7 @@ export const BUILTIN_TOOLS = [
|
|
|
92
101
|
name: 'grep',
|
|
93
102
|
title: 'Mixdog Grep',
|
|
94
103
|
annotations: { title: 'Mixdog Grep', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
95
|
-
description: 'Search text
|
|
104
|
+
description: 'Search text/logs/regex.',
|
|
96
105
|
inputSchema: {
|
|
97
106
|
type: 'object',
|
|
98
107
|
properties: {
|
|
@@ -101,19 +110,19 @@ export const BUILTIN_TOOLS = [
|
|
|
101
110
|
{ type: 'string' },
|
|
102
111
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
103
112
|
],
|
|
104
|
-
description: '
|
|
113
|
+
description: 'Text/regex. Array = OR.',
|
|
105
114
|
},
|
|
106
|
-
path: { type: 'string', description: '
|
|
115
|
+
path: { type: 'string', description: 'File or directory.' },
|
|
107
116
|
glob: {
|
|
108
117
|
anyOf: [
|
|
109
118
|
{ type: 'string' },
|
|
110
119
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
111
120
|
],
|
|
112
|
-
description: '
|
|
121
|
+
description: 'Optional narrowing glob(s).',
|
|
113
122
|
},
|
|
114
|
-
output_mode: { type: 'string', enum: ['files_with_matches', 'content', 'count'], description: '
|
|
115
|
-
head_limit: { type: 'number', description: 'Max
|
|
116
|
-
offset: { type: 'number', description: 'Skip
|
|
123
|
+
output_mode: { type: 'string', enum: ['files_with_matches', 'content', 'count'], description: 'Output mode.' },
|
|
124
|
+
head_limit: { type: 'number', description: 'Max lines.' },
|
|
125
|
+
offset: { type: 'number', description: 'Skip lines.' },
|
|
117
126
|
},
|
|
118
127
|
required: [],
|
|
119
128
|
},
|
|
@@ -122,7 +131,7 @@ export const BUILTIN_TOOLS = [
|
|
|
122
131
|
name: 'glob',
|
|
123
132
|
title: 'Mixdog Glob',
|
|
124
133
|
annotations: { title: 'Mixdog Glob', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
125
|
-
description: 'Find files by glob
|
|
134
|
+
description: 'Find files by glob.',
|
|
126
135
|
inputSchema: {
|
|
127
136
|
type: 'object',
|
|
128
137
|
properties: {
|
|
@@ -131,33 +140,47 @@ export const BUILTIN_TOOLS = [
|
|
|
131
140
|
{ type: 'string' },
|
|
132
141
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
133
142
|
],
|
|
134
|
-
description: 'Glob pattern
|
|
143
|
+
description: 'Glob pattern(s).',
|
|
135
144
|
},
|
|
136
145
|
path: {
|
|
137
146
|
anyOf: [
|
|
138
147
|
{ type: 'string' },
|
|
139
148
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
140
149
|
],
|
|
141
|
-
description: 'Base directory
|
|
150
|
+
description: 'Base directory/directories.',
|
|
142
151
|
},
|
|
143
152
|
head_limit: { type: 'number', description: 'Max entries.' },
|
|
144
|
-
offset: { type: 'number', description: 'Skip
|
|
153
|
+
offset: { type: 'number', description: 'Skip entries.' },
|
|
145
154
|
},
|
|
146
155
|
required: ['pattern'],
|
|
147
156
|
},
|
|
148
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: 'find',
|
|
160
|
+
title: 'Mixdog Find Files',
|
|
161
|
+
annotations: { title: 'Mixdog Find Files', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
162
|
+
description: 'Fuzzy-find files by partial path/name. Returns paths.',
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
query: { type: 'string', description: 'Partial path/name words.' },
|
|
167
|
+
path: { type: 'string', description: 'Base directory.' },
|
|
168
|
+
head_limit: { type: 'number', description: 'Max paths.' },
|
|
169
|
+
},
|
|
170
|
+
required: ['query'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
149
173
|
{
|
|
150
174
|
name: 'list',
|
|
151
175
|
title: 'Mixdog List Directory',
|
|
152
176
|
annotations: { title: 'Mixdog List Directory', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
153
|
-
description: 'List directory entries
|
|
177
|
+
description: 'List known directory entries. Use glob for broad discovery.',
|
|
154
178
|
inputSchema: {
|
|
155
179
|
type: 'object',
|
|
156
180
|
properties: {
|
|
157
|
-
path: { type: 'string', description: 'Directory
|
|
181
|
+
path: { type: 'string', description: 'Directory.' },
|
|
158
182
|
head_limit: { type: 'number', description: 'Max entries.' },
|
|
159
183
|
offset: { type: 'number', description: 'Skip N entries for paging.' },
|
|
160
|
-
fuzzy: { type: 'string', description: 'Rank files by partial name.' },
|
|
161
184
|
},
|
|
162
185
|
required: [],
|
|
163
186
|
},
|
|
@@ -5,6 +5,7 @@ import { deleteReadRangeIndexForPath } from './read-range-index.mjs';
|
|
|
5
5
|
import { resolveAgainstCwd } from './path-utils.mjs';
|
|
6
6
|
|
|
7
7
|
const RESULT_CACHE = new Map(); // key → { ts, value, paths, scopes, readSnapshotMeta, contentPrefixHash, bytes }
|
|
8
|
+
const RESULT_CACHE_INFLIGHT = new Map(); // key → Promise<value>
|
|
8
9
|
const RESULT_CACHE_TTL_MS = 30_000;
|
|
9
10
|
const RESULT_CACHE_MAX_ENTRIES = 200;
|
|
10
11
|
const RESULT_CACHE_MAX_BYTES = (() => {
|
|
@@ -140,6 +141,22 @@ export function cacheSet(key, value, meta = {}) {
|
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
export async function runResultCacheInFlight(key, compute) {
|
|
145
|
+
const cached = cacheGet(key);
|
|
146
|
+
if (cached !== null) return cached;
|
|
147
|
+
const existing = RESULT_CACHE_INFLIGHT.get(key);
|
|
148
|
+
if (existing) return await existing;
|
|
149
|
+
const promise = Promise.resolve()
|
|
150
|
+
.then(() => compute())
|
|
151
|
+
.finally(() => {
|
|
152
|
+
if (RESULT_CACHE_INFLIGHT.get(key) === promise) {
|
|
153
|
+
RESULT_CACHE_INFLIGHT.delete(key);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
RESULT_CACHE_INFLIGHT.set(key, promise);
|
|
157
|
+
return await promise;
|
|
158
|
+
}
|
|
159
|
+
|
|
143
160
|
function rawContentCacheDelete(key) {
|
|
144
161
|
const entry = RAW_CONTENT_CACHE.get(key);
|
|
145
162
|
if (entry?.rawBuf) RAW_CONTENT_CACHE_BYTES = Math.max(0, RAW_CONTENT_CACHE_BYTES - entry.rawBuf.length);
|
|
@@ -230,7 +247,7 @@ export async function statPathsForMtime(paths, workDir, concurrency = 64, opts =
|
|
|
230
247
|
// Hard per-stat deadline (0 = disabled, legacy behaviour). A hung stat
|
|
231
248
|
// (dead mount / unresponsive network path) must not pin a worker forever;
|
|
232
249
|
// on expiry the entry resolves to null (stat-failed) so glob's post-rg stat
|
|
233
|
-
// phase is bounded instead of running to the 600s
|
|
250
|
+
// phase is bounded instead of running to the 600s agent watchdog.
|
|
234
251
|
const deadlineMs = Number(opts.deadlineMs) > 0 ? Number(opts.deadlineMs) : 0;
|
|
235
252
|
// Injectable stat impl for testing hung-FS behaviour deterministically.
|
|
236
253
|
const statImpl = typeof opts._statImpl === 'function' ? opts._statImpl : fsPromises.stat;
|
|
@@ -293,7 +310,7 @@ export async function lstatPathsForMtime(paths, workDir, concurrency = 64, opts
|
|
|
293
310
|
// Hard per-lstat deadline (0 = disabled, legacy behaviour). A hung lstat
|
|
294
311
|
// (dead mount / unresponsive network path) must not pin a worker forever;
|
|
295
312
|
// on expiry the entry resolves to null (stat-failed) so list's stat phase
|
|
296
|
-
// is bounded instead of running to the 600s
|
|
313
|
+
// is bounded instead of running to the 600s agent watchdog.
|
|
297
314
|
const deadlineMs = Number(opts.deadlineMs) > 0 ? Number(opts.deadlineMs) : 0;
|
|
298
315
|
// Injectable lstat impl for testing hung-FS behaviour deterministically.
|
|
299
316
|
const lstatImpl = typeof opts._lstatImpl === 'function' ? opts._lstatImpl : fsPromises.lstat;
|
|
@@ -342,6 +359,7 @@ export async function lstatPathsForMtime(paths, workDir, concurrency = 64, opts
|
|
|
342
359
|
|
|
343
360
|
function cacheInvalidateAll() {
|
|
344
361
|
RESULT_CACHE.clear();
|
|
362
|
+
RESULT_CACHE_INFLIGHT.clear();
|
|
345
363
|
RESULT_CACHE_BYTES = 0;
|
|
346
364
|
STAT_CACHE.clear();
|
|
347
365
|
RAW_CONTENT_CACHE.clear();
|
|
@@ -356,6 +374,7 @@ function cacheInvalidatePaths(paths) {
|
|
|
356
374
|
cacheInvalidateAll();
|
|
357
375
|
return;
|
|
358
376
|
}
|
|
377
|
+
RESULT_CACHE_INFLIGHT.clear();
|
|
359
378
|
for (const [key, entry] of RESULT_CACHE) {
|
|
360
379
|
if (cacheEntryOverlapsPaths(entry, affectedPaths)) {
|
|
361
380
|
resultCacheDelete(key);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// G6: device path block list (
|
|
1
|
+
// G6: device path block list (reference parity). Reading these paths
|
|
2
2
|
// would either hang (waiting for stdin / tty) or produce infinite output
|
|
3
3
|
// (/dev/zero, /dev/random). The device block list catches pseudo-files on
|
|
4
4
|
// POSIX hosts that a user-allowed path can still hit.
|
|
@@ -282,11 +282,4 @@ async function _executeDiagnosticsImpl(args, workDir) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
function _isDir(p, workDir) {
|
|
286
|
-
try {
|
|
287
|
-
const abs = isAbsolute(p) ? p : pathResolve(workDir, p);
|
|
288
|
-
return statSync(abs).isDirectory();
|
|
289
|
-
} catch { return false; }
|
|
290
|
-
}
|
|
291
|
-
|
|
292
285
|
export default executeDiagnosticsTool;
|
|
@@ -118,12 +118,10 @@ export function compileSimpleGlob(pattern) {
|
|
|
118
118
|
// simpler and matches the noise patterns developers actually create.
|
|
119
119
|
export const NOISE_DIR_NAMES = new Set([
|
|
120
120
|
'node_modules', '.git',
|
|
121
|
-
'dist', 'build', 'out', 'target',
|
|
122
121
|
'.next', '.nuxt', '.svelte-kit',
|
|
123
122
|
'.cache', '.parcel-cache', '.turbo',
|
|
124
123
|
'venv', '.venv', '__pycache__', '.pytest_cache',
|
|
125
|
-
'.gradle',
|
|
126
|
-
'refs',
|
|
124
|
+
'.gradle',
|
|
127
125
|
]);
|
|
128
126
|
|
|
129
127
|
export const MAX_WALK_ENTRIES = 200_000;
|
|
@@ -60,7 +60,7 @@ function listGuardPath(p) {
|
|
|
60
60
|
|
|
61
61
|
export async function executeListTool(args, workDir, options = {}) {
|
|
62
62
|
if (typeof args.fuzzy === 'string' && args.fuzzy.length > 0) {
|
|
63
|
-
return
|
|
63
|
+
return executeFuzzyFindTool({ ...args, query: args.fuzzy }, workDir, options);
|
|
64
64
|
}
|
|
65
65
|
if (args.mode === 'tree') return executeTreeTool(args, workDir, options);
|
|
66
66
|
if (args.mode === 'find') return executeFindFilesTool(args, workDir, options);
|
|
@@ -296,12 +296,13 @@ export async function executeTreeTool(args, workDir, options = {}) {
|
|
|
296
296
|
return out;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
// Fuzzy filename search (
|
|
300
|
-
// list via `rg --files`, then rank by subsequence score.
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
async function
|
|
304
|
-
const query = String(args.fuzzy);
|
|
299
|
+
// Fuzzy filename search (nucleo-style): collect the file
|
|
300
|
+
// list via `rg --files`, then rank by subsequence score. `list.fuzzy` still
|
|
301
|
+
// routes here for hidden backward compatibility, but the model-facing tool is
|
|
302
|
+
// `find`.
|
|
303
|
+
export async function executeFuzzyFindTool(args, workDir, options = {}) {
|
|
304
|
+
const query = String(args.query ?? args.fuzzy ?? '').trim();
|
|
305
|
+
if (!query) return 'Error: find requires query.';
|
|
305
306
|
const inputPath = normalizeInputPath(args.path) || '.';
|
|
306
307
|
const guard = listGuardPath(inputPath);
|
|
307
308
|
if (guard) return guard;
|
|
@@ -310,10 +311,25 @@ async function executeFuzzyFind(args, workDir) {
|
|
|
310
311
|
if (guardFull) return guardFull;
|
|
311
312
|
const hidden = Boolean(args.hidden);
|
|
312
313
|
const includeNoise = Boolean(args.include_noise);
|
|
313
|
-
// head_limit:0 means "no cap" per list semantics
|
|
314
|
-
|
|
314
|
+
// head_limit:0 means "no cap" per list semantics; default is intentionally
|
|
315
|
+
// compact so ambiguous discovery does not dump a huge candidate list.
|
|
316
|
+
const headLimit = normalizeListHeadLimit(args.head_limit, 25);
|
|
315
317
|
const depth = args.depth != null ? Math.max(parseInt(args.depth, 10) || 1, 1) : null;
|
|
316
|
-
const
|
|
318
|
+
const cacheKey = buildListCacheKey({
|
|
319
|
+
mode: 'fuzzy_find',
|
|
320
|
+
inputPath: normalizeOutputPath(fullPath),
|
|
321
|
+
depth: depth ?? '',
|
|
322
|
+
hidden,
|
|
323
|
+
sort: 'score',
|
|
324
|
+
typeFilter: 'file',
|
|
325
|
+
headLimit,
|
|
326
|
+
offset: '',
|
|
327
|
+
namePattern: query,
|
|
328
|
+
includeNoise,
|
|
329
|
+
});
|
|
330
|
+
const cached = cacheGet(cacheKey);
|
|
331
|
+
if (cached !== null) return cached;
|
|
332
|
+
const rgArgs = ['--files'];
|
|
317
333
|
if (hidden) rgArgs.push('--hidden');
|
|
318
334
|
if (depth != null) rgArgs.push('--max-depth', String(depth));
|
|
319
335
|
if (!includeNoise) {
|
|
@@ -333,12 +349,20 @@ async function executeFuzzyFind(args, workDir) {
|
|
|
333
349
|
.map((p) => (p.endsWith('\r') ? p.slice(0, -1) : p))
|
|
334
350
|
.filter((p) => p.length > 0)
|
|
335
351
|
.map((p) => ({ path: normalizeOutputPath(p.replace(/^\.[/\\]/, '')) }));
|
|
336
|
-
const
|
|
352
|
+
const rankLimit = headLimit > 0 ? headLimit + 1 : headLimit;
|
|
353
|
+
const rankedRaw = fuzzyRank(query, items, rankLimit);
|
|
354
|
+
const hasMore = headLimit > 0 && rankedRaw.length > headLimit;
|
|
355
|
+
const ranked = hasMore ? rankedRaw.slice(0, headLimit) : rankedRaw;
|
|
337
356
|
if (ranked.length === 0) return `(no fuzzy match for "${query}")`;
|
|
338
357
|
const out = ranked.map((r) => r.item.path).join('\n');
|
|
339
|
-
|
|
358
|
+
const result = hasMore
|
|
340
359
|
? `${out}\n... (top ${headLimit}; raise head_limit for more)`
|
|
341
360
|
: out;
|
|
361
|
+
cacheSet(cacheKey, result, { scopes: [fullPath] });
|
|
362
|
+
if (typeof options?.onProgress === 'function') {
|
|
363
|
+
try { options.onProgress(`${ranked.length} candidates`); } catch { /* best-effort */ }
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
342
366
|
}
|
|
343
367
|
|
|
344
368
|
export async function executeFindFilesTool(args, workDir, options = {}) {
|