mixdog 0.8.0 → 0.9.0
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 +33 -27
- 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 +344 -0
- 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 +1223 -95
- 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 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- 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 +118 -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 +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -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 +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- 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 +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- 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 +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- 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 +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- 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/snapshot-store.mjs +1 -1
- 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 +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- 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/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- 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 +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -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 +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -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 +145 -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 -98
- 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 +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- 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 +367 -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 +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- 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 +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -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 +746 -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 +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- 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/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/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/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
|
@@ -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 = {}) {
|
|
@@ -3,7 +3,7 @@ import { SMART_READ_MAX_BYTES } from './read-formatting.mjs';
|
|
|
3
3
|
// Read tool caps.
|
|
4
4
|
//
|
|
5
5
|
// READ_MAX_SIZE_BYTES (10 MB) — fast-path file-size threshold mirroring
|
|
6
|
-
//
|
|
6
|
+
// Reference FAST_PATH_MAX_SIZE (readFileInRange.ts:44). Files at or below
|
|
7
7
|
// this size use readFile + in-memory split by default, which CC measured at
|
|
8
8
|
// ~2x faster than createReadStream + readline for typical source. Explicit
|
|
9
9
|
// offset/limit windows on files above READ_STREAM_RANGE_MIN_BYTES take the
|
|
@@ -14,7 +14,7 @@ import { SMART_READ_MAX_BYTES } from './read-formatting.mjs';
|
|
|
14
14
|
//
|
|
15
15
|
// READ_WHOLE_FILE_MAX_BYTES (256 KiB) — soft threshold: default whole-file
|
|
16
16
|
// reads larger than this prefer stream smart-elide + READ_MAX_OUTPUT_BYTES
|
|
17
|
-
// truncation (
|
|
17
|
+
// truncation (proceed with cap) rather than refusing. Hard in-memory cap
|
|
18
18
|
// for loading a full file remains READ_MAX_SIZE_BYTES (10 MiB).
|
|
19
19
|
//
|
|
20
20
|
// READ_MAX_OUTPUT_BYTES (30 KB) — output-truncation cap. Lead-facing default tightened from 50k. Mirrors CC's
|
|
@@ -12,7 +12,7 @@ export const SMART_READ_TAIL_LINES = 100;
|
|
|
12
12
|
export const READ_CONTEXT_ADVISORY_BYTES = 40 * 1024;
|
|
13
13
|
export const READ_MAX_RENDERED_LINE_CHARS = 2_000;
|
|
14
14
|
// Claude-Code parity: the read line-prefix separator is `→` (the `→`
|
|
15
|
-
// arrow), matching
|
|
15
|
+
// arrow), matching default cat -n format `<n>→<content>`. It
|
|
16
16
|
// MUST be a NON-WHITESPACE glyph: a tab/space separator collides with the
|
|
17
17
|
// content's own leading indentation, so a model hand-reconstructing an edit
|
|
18
18
|
// old_string cannot tell the separator from the indent and produces
|
|
@@ -42,22 +42,15 @@ function rangeFromRenderedReadRows(rows, fallbackStartLine = 1) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
// TIGHTER head+tail elision than the 600-line / 200+100 default (e.g. read
|
|
47
|
-
// budget:'compact' or max_lines:N) to bound lead-context cost. Omitted -> the
|
|
48
|
-
// standard caps, so existing 4-arg callers are byte-for-byte unchanged.
|
|
49
|
-
export function smartReadTruncate(renderedWithLineNos, totalLines, fileBytes, filePath = '', budget = null) {
|
|
50
|
-
const maxLines = budget?.maxLines ?? SMART_READ_MAX_LINES;
|
|
51
|
-
const headLines = budget?.headLines ?? SMART_READ_HEAD_LINES;
|
|
52
|
-
const tailLines = budget?.tailLines ?? SMART_READ_TAIL_LINES;
|
|
45
|
+
export function smartReadTruncate(renderedWithLineNos, totalLines, fileBytes, filePath = '') {
|
|
53
46
|
const overByBytes = fileBytes > SMART_READ_MAX_BYTES;
|
|
54
|
-
const overByLines = totalLines >
|
|
47
|
+
const overByLines = totalLines > SMART_READ_MAX_LINES;
|
|
55
48
|
if (!overByBytes && !overByLines) {
|
|
56
49
|
return { text: renderedWithLineNos, truncated: false, totalLines, ranges: null };
|
|
57
50
|
}
|
|
58
51
|
const rows = renderedWithLineNos.split('\n');
|
|
59
|
-
const headCount = Math.min(
|
|
60
|
-
const tailStart = Math.max(headCount, rows.length -
|
|
52
|
+
const headCount = Math.min(SMART_READ_HEAD_LINES, rows.length);
|
|
53
|
+
const tailStart = Math.max(headCount, rows.length - SMART_READ_TAIL_LINES);
|
|
61
54
|
const elidedRows = tailStart - headCount;
|
|
62
55
|
if (elidedRows <= 0) {
|
|
63
56
|
return { text: renderedWithLineNos, truncated: false, totalLines, ranges: null };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Optional-sharp image resize / downsample helper. Mirrors
|
|
1
|
+
// Optional-sharp image resize / downsample helper. Mirrors reference
|
|
2
2
|
// imageResizer.ts (maybeResizeAndDownsampleImageBuffer +
|
|
3
3
|
// readImageWithTokenBudget) so a `read` on an image returns a viewable,
|
|
4
4
|
// budget-bounded image block instead of refusing oversized originals.
|
|
@@ -12,23 +12,6 @@ function snapshotBodyWasReturnedByRead(snapshot) {
|
|
|
12
12
|
return String(snapshot?.source || '').startsWith('read');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// Optional context-budget for a whole-file read: `max_lines:N` requests a
|
|
16
|
-
// TIGHTER head+tail elision than the default 600-line / 200+100 cap, to bound
|
|
17
|
-
// lead-context cost when only a glance is needed. Returns null (= default) when
|
|
18
|
-
// unset. No heuristic guessing — the budget is explicit and reuses the existing
|
|
19
|
-
// smartReadTruncate head/tail invariant. (`budget:'compact'` is a SEPARATE,
|
|
20
|
-
// pre-existing knob handled upstream in read-tool.mjs applyCompactReadBudget —
|
|
21
|
-
// it remaps a whole-file read to mode:'count' stats, not head+tail content.)
|
|
22
|
-
function resolveReadBudget(args) {
|
|
23
|
-
const ml = Number(args?.max_lines);
|
|
24
|
-
if (Number.isFinite(ml) && ml > 0) {
|
|
25
|
-
const maxLines = Math.trunc(ml);
|
|
26
|
-
const headLines = Math.max(1, Math.ceil(maxLines * 0.7));
|
|
27
|
-
return { maxLines, headLines, tailLines: Math.max(0, maxLines - headLines) };
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
15
|
function withSymbolReadNote(text, args) {
|
|
33
16
|
const note = typeof args?._symbolReadNote === 'string' ? args._symbolReadNote.trim() : '';
|
|
34
17
|
if (!note || typeof text !== 'string') return text;
|
|
@@ -159,9 +142,6 @@ export async function executeSingleReadTool(args, workDir, readStateScope, optio
|
|
|
159
142
|
// emits a compact truncation marker when rendered bytes overflow
|
|
160
143
|
// READ_MAX_OUTPUT_BYTES.
|
|
161
144
|
const limit = parseLineLimitArg(args.limit, wantFull ? Infinity : 2000);
|
|
162
|
-
// Context-budget (compact / max_lines) — only meaningful on a whole-file
|
|
163
|
-
// read (no range, not full). Ignored otherwise.
|
|
164
|
-
const _readBudget = (!hasRangeArgs && !wantFull) ? resolveReadBudget(args) : null;
|
|
165
145
|
let st;
|
|
166
146
|
let _statErr;
|
|
167
147
|
try {
|
|
@@ -265,7 +245,7 @@ export async function executeSingleReadTool(args, workDir, readStateScope, optio
|
|
|
265
245
|
}
|
|
266
246
|
return _ipynbOut;
|
|
267
247
|
}
|
|
268
|
-
const cacheKey = `read|${fullPath}|${st.mtimeMs}|${st.size}|${hasOffsetArg ? offset : 'd'}|${hasLimitArg ? limit : 'd'}|${wantFull ? 'f' : 's'}
|
|
248
|
+
const cacheKey = `read|${fullPath}|${st.mtimeMs}|${st.size}|${hasOffsetArg ? offset : 'd'}|${hasLimitArg ? limit : 'd'}|${wantFull ? 'f' : 's'}`;
|
|
269
249
|
// Race-guard helper: same-mtime same-size rapid rewrite (NTFS / exFAT 1 s
|
|
270
250
|
// resolution) can pass mtimeMs+size yet differ in content. When the cache
|
|
271
251
|
// entry stores a contentPrefixHash, recompute the current prefix and bail
|
|
@@ -358,7 +338,7 @@ export async function executeSingleReadTool(args, workDir, readStateScope, optio
|
|
|
358
338
|
_recordReadSnapshot(fullPath, st, readStateScope, cachedEntry.readSnapshotMeta || { source: 'read_cached' });
|
|
359
339
|
// G6: file_unchanged stub. The full body is already in the
|
|
360
340
|
// prior tool_result; resending it wastes cache_creation
|
|
361
|
-
// tokens (
|
|
341
|
+
// tokens (reference upstream measured ~18% on Read calls).
|
|
362
342
|
// The stub keeps the snapshot tracking intact (Edit
|
|
363
343
|
// validation still works) while collapsing the response
|
|
364
344
|
// payload. Falls back to the full body when the cached
|
|
@@ -496,19 +476,7 @@ export async function executeSingleReadTool(args, workDir, readStateScope, optio
|
|
|
496
476
|
? await streamSmartReadSummary(fullPath, st, 'read_smart_stream')
|
|
497
477
|
: null;
|
|
498
478
|
if (_streamSmart?.text) {
|
|
499
|
-
|
|
500
|
-
// Honor a compact/max_lines budget on a large file: the stream
|
|
501
|
-
// already elided to head 200/tail 100; re-apply the tighter
|
|
502
|
-
// head+tail so the lead sees only the requested glance.
|
|
503
|
-
if (_readBudget) {
|
|
504
|
-
// Use the file's REAL line count from the stream pass
|
|
505
|
-
// (snapshotMeta.fileLineCount — the result has no top-level
|
|
506
|
-
// totalLines), not the already-elided output's row count: the
|
|
507
|
-
// re-budget marker otherwise reports "[TRUNCATED - 301 lines]"
|
|
508
|
-
// for a 3800-line file.
|
|
509
|
-
const _rebud = smartReadTruncate(out, _streamSmart.snapshotMeta?.fileLineCount || out.split('\n').length, st.size, filePath, _readBudget);
|
|
510
|
-
if (_rebud?.truncated) out = _rebud.text;
|
|
511
|
-
}
|
|
479
|
+
const out = _streamSmart.text;
|
|
512
480
|
const snapshotMeta = _streamSmart.snapshotMeta || {
|
|
513
481
|
source: 'read_smart_stream',
|
|
514
482
|
ranges: [],
|
|
@@ -625,7 +593,7 @@ export async function executeSingleReadTool(args, workDir, readStateScope, optio
|
|
|
625
593
|
// green-light an overwrite against bytes the read never returned.
|
|
626
594
|
let _byteCapTruncated = false;
|
|
627
595
|
const smart = (!hasRangeArgs && !wantFull && typeof smartReadTruncate === 'function')
|
|
628
|
-
? smartReadTruncate(rendered, lineCount, st.size, filePath
|
|
596
|
+
? smartReadTruncate(rendered, lineCount, st.size, filePath)
|
|
629
597
|
: null;
|
|
630
598
|
let _smartTruncated = false;
|
|
631
599
|
let _smartVisibleRanges = null;
|
|
@@ -79,34 +79,6 @@ async function _readReachPreflight(rawPath, workDir, helpers) {
|
|
|
79
79
|
catch (e) { return `Error: ${e?.message || e}`; }
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function capPositiveNumber(value, max, fallback = max) {
|
|
83
|
-
const n = Number(value);
|
|
84
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
85
|
-
return Math.min(max, Math.max(1, Math.trunc(n)));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function applyCompactReadBudget(entry) {
|
|
89
|
-
if (!entry || String(entry.budget || '').toLowerCase() !== 'compact') return entry;
|
|
90
|
-
const next = { ...entry };
|
|
91
|
-
const isFullMode = !next.mode || next.mode === 'full';
|
|
92
|
-
const hasLine = next.line !== undefined && next.line !== null;
|
|
93
|
-
const hasRange = next.offset !== undefined || next.limit !== undefined;
|
|
94
|
-
const hasPathLine = hasLineCoordinate(next.path);
|
|
95
|
-
if (hasLine && (next.context === undefined || next.context === null || Number(next.context) > 20)) {
|
|
96
|
-
next.context = 20;
|
|
97
|
-
}
|
|
98
|
-
if (hasRange && (next.limit === undefined || next.limit === null || Number(next.limit) > 120)) {
|
|
99
|
-
next.limit = 120;
|
|
100
|
-
}
|
|
101
|
-
if ((next.mode === 'head' || next.mode === 'tail') && (next.n === undefined || next.n === null || Number(next.n) > 80)) {
|
|
102
|
-
next.n = capPositiveNumber(next.n, 80, 80);
|
|
103
|
-
}
|
|
104
|
-
if (isFullMode && !hasLine && !hasRange && !hasPathLine && next.full !== true) {
|
|
105
|
-
next.mode = 'count';
|
|
106
|
-
}
|
|
107
|
-
return next;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
82
|
export async function executeReadTool(args, workDir, readStateScope, executeChildBuiltinTool, options = {}, helpers = {}) {
|
|
111
83
|
const {
|
|
112
84
|
classifyResultKind,
|
|
@@ -217,10 +189,7 @@ export async function executeReadTool(args, workDir, readStateScope, executeChil
|
|
|
217
189
|
if (r?.line !== undefined) entry.line = r.line;
|
|
218
190
|
if (r?.context !== undefined) entry.context = r.context;
|
|
219
191
|
if (r?.full !== undefined) entry.full = r.full;
|
|
220
|
-
if (r?.budget !== undefined) entry.budget = r.budget;
|
|
221
|
-
entry = applyCompactReadBudget(entry);
|
|
222
192
|
entry = normaliseReadLineWindowArgs(entry, workDir);
|
|
223
|
-
entry = applyCompactReadBudget(entry);
|
|
224
193
|
return entry;
|
|
225
194
|
});
|
|
226
195
|
const _invertedRawEntry = rawEntries.find((e) => e && e._invertedRangeError);
|
|
@@ -269,10 +238,7 @@ export async function executeReadTool(args, workDir, readStateScope, executeChil
|
|
|
269
238
|
if (args.line !== undefined) entry.line = args.line;
|
|
270
239
|
if (args.context !== undefined) entry.context = args.context;
|
|
271
240
|
if (args.full !== undefined) entry.full = args.full;
|
|
272
|
-
if (args.budget !== undefined) entry.budget = args.budget;
|
|
273
|
-
entry = applyCompactReadBudget(entry);
|
|
274
241
|
entry = normaliseReadLineWindowArgs(entry, workDir);
|
|
275
|
-
entry = applyCompactReadBudget(entry);
|
|
276
242
|
return entry;
|
|
277
243
|
});
|
|
278
244
|
const _invertedStrEntry = entries.find((e) => e && e._invertedRangeError);
|
|
@@ -450,9 +416,9 @@ export async function executeReadTool(args, workDir, readStateScope, executeChil
|
|
|
450
416
|
args.path = normalizeInputPath(args.path);
|
|
451
417
|
// Symbol reads are span-driven. Models (notably gpt-5.5) co-send the whole
|
|
452
418
|
// schema filled with placeholder/zero values (offset:0, limit:0, line:0,
|
|
453
|
-
// context:0, pages:'',
|
|
419
|
+
// context:0, pages:'', mode) alongside symbol, which
|
|
454
420
|
// otherwise makes the symbol branch think a window was requested and falls
|
|
455
|
-
// back to a 0-window read. Treat those zero/empty/mode
|
|
421
|
+
// back to a 0-window read. Treat those zero/empty/mode params as
|
|
456
422
|
// absent so the whole symbol body returns in ONE call; a MEANINGFUL window
|
|
457
423
|
// (offset>0 / limit>0 / non-empty pages / real line) is kept and overrides.
|
|
458
424
|
if (typeof args.symbol === 'string' && args.symbol.trim()) {
|
|
@@ -460,11 +426,8 @@ export async function executeReadTool(args, workDir, readStateScope, executeChil
|
|
|
460
426
|
if (args.limit === 0) delete args.limit;
|
|
461
427
|
if (args.line === 0) delete args.line;
|
|
462
428
|
if (args.pages === '') delete args.pages;
|
|
463
|
-
if (args.max_lines === 0) delete args.max_lines;
|
|
464
|
-
if (args.budget !== undefined) delete args.budget;
|
|
465
429
|
if (args.mode !== undefined) delete args.mode;
|
|
466
430
|
}
|
|
467
|
-
args = applyCompactReadBudget(args);
|
|
468
431
|
const sym = typeof args.symbol === 'string' ? args.symbol.trim() : '';
|
|
469
432
|
if (sym) {
|
|
470
433
|
const hasOffset = args.offset !== undefined && args.offset !== null;
|
|
@@ -515,7 +478,6 @@ export async function executeReadTool(args, workDir, readStateScope, executeChil
|
|
|
515
478
|
}
|
|
516
479
|
args = normaliseReadLineWindowArgs(args, workDir);
|
|
517
480
|
if (args._invertedRangeError) return args._invertedRangeError;
|
|
518
|
-
args = applyCompactReadBudget(args);
|
|
519
481
|
}
|
|
520
482
|
// Mode routing. A window already dropped any conflicting head/tail/summary
|
|
521
483
|
// glance above (so the window is served by executeSingleReadTool); what
|