mixdog 0.8.1 → 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 +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 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2212 -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 +110 -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 +76 -28
- 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/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 +34 -7
- 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.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/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 +520 -98
- 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 +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 -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 +1 -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 -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 +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4788 -2151
- 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 +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 +592 -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 +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8826 -2390
- 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 +2 -13
- 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
|
@@ -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
|
|
@@ -1,15 +1,105 @@
|
|
|
1
|
-
import { spawn,
|
|
1
|
+
import { spawn, execFile } from 'child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import { acquire as acquireChildSpawnSlot } from '../../../../shared/child-spawn-gate.mjs';
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
// ── rg orphan-reap policy (accepted risk) ────────────────────────────────
|
|
11
|
+
// The previous design started a 1:1 node child-guardian per rg spawn so a
|
|
12
|
+
// hard daemon kill (SIGKILL, no chance to run our own cleanup) could not leave
|
|
13
|
+
// an rg orphaned. That per-process guardian is exactly what over-saturated the
|
|
14
|
+
// box (each grep doubled the process count), so it has been REMOVED here and
|
|
15
|
+
// deliberately NOT replaced with another per-rg guardian.
|
|
16
|
+
//
|
|
17
|
+
// Accepted residual risk: on a hard daemon SIGKILL an in-flight rg can briefly
|
|
18
|
+
// outlive the daemon. This is judged acceptable because (a) on every NORMAL
|
|
19
|
+
// path rg is reaped by this module's own timeout → SIGTERM → grace → force-kill
|
|
20
|
+
// plus the force-settle backstop, so it is always collected when the daemon is
|
|
21
|
+
// alive to settle the promise; (b) rg is a short-lived, self-terminating
|
|
22
|
+
// (bounded by the 20s timeout) read-only process, not a long-running server; a
|
|
23
|
+
// stray instance exits on its own shortly after. A shared/OS-job-based reaper
|
|
24
|
+
// (one guardian for the whole daemon, or a Windows Job Object / POSIX process
|
|
25
|
+
// group kill on daemon teardown) is the right long-term fix but is out of scope
|
|
26
|
+
// for this change and intentionally not added per-spawn.
|
|
27
|
+
|
|
28
|
+
// ── rg --threads cap ─────────────────────────────────────────────────────
|
|
29
|
+
// Each rg process otherwise fans out across EVERY core; with several agents
|
|
30
|
+
// running grep at once the box over-saturates and the whole batch trips the
|
|
31
|
+
// 20s deadline together. Cap rg's worker threads to a fraction of the host so
|
|
32
|
+
// concurrent greps share the machine. Internal dynamic default; env override
|
|
33
|
+
// only — deliberately NOT exposed on any tool schema / parameter surface.
|
|
34
|
+
function _rgThreadCap() {
|
|
35
|
+
const override = Number(process.env.MIXDOG_RG_THREADS);
|
|
36
|
+
if (Number.isFinite(override) && override >= 1) return Math.floor(override);
|
|
37
|
+
let cpus = 0;
|
|
38
|
+
try { cpus = os.cpus()?.length || 0; } catch { cpus = 0; }
|
|
39
|
+
return Math.max(2, Math.ceil((cpus || 4) / 4));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Single source of truth for "did the caller already pin rg's thread count?".
|
|
43
|
+
// Detects every form rg accepts: separated (`-j`, `--threads`) where the count
|
|
44
|
+
// is the NEXT arg, short-attached (`-j8`), and long-attached (`--threads=8`).
|
|
45
|
+
// Used by both the _withRgThreads injection guard and the EAGAIN retry guard
|
|
46
|
+
// so the two never disagree.
|
|
47
|
+
function _hasRgThreadArg(argsList) {
|
|
48
|
+
for (const a of argsList) {
|
|
49
|
+
if (typeof a !== 'string') continue;
|
|
50
|
+
if (a === '-j' || a === '--threads') return true;
|
|
51
|
+
if (/^-j\d+$/.test(a)) return true;
|
|
52
|
+
if (a.startsWith('--threads=')) return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Inject `--threads N` unless the caller already pinned thread count
|
|
58
|
+
// (`-j`/`--threads`/`-jN`/`--threads=N`, e.g. the EAGAIN `-j 1` retry). Does
|
|
59
|
+
// not mutate; may return the ORIGINAL array unchanged when already
|
|
60
|
+
// thread-pinned (callers must not assume a fresh copy).
|
|
61
|
+
function _withRgThreads(argsList) {
|
|
62
|
+
if (_hasRgThreadArg(argsList)) return argsList;
|
|
63
|
+
return ['--threads', String(_rgThreadCap()), ...argsList];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Build the EAGAIN single-thread retry args. If the caller already pinned a
|
|
67
|
+
// thread count we strip the existing flag(s) first, then prepend `-j 1`, so the
|
|
68
|
+
// retry is unambiguously single-threaded (never `-j 8 ... -j 1`). Drops both the
|
|
69
|
+
// separated form (`-j N` / `--threads N` → flag + following count) and the
|
|
70
|
+
// attached forms (`-jN` / `--threads=N`).
|
|
71
|
+
function _rgEagainRetryArgs(argsList) {
|
|
72
|
+
const out = [];
|
|
73
|
+
for (let i = 0; i < argsList.length; i++) {
|
|
74
|
+
const a = argsList[i];
|
|
75
|
+
if (a === '-j' || a === '--threads') { i++; continue; } // skip flag + its count
|
|
76
|
+
if (typeof a === 'string' && (/^-j\d+$/.test(a) || a.startsWith('--threads='))) continue;
|
|
77
|
+
out.push(a);
|
|
78
|
+
}
|
|
79
|
+
return ['-j', '1', ...out];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// True only when the args are ALREADY exactly single-threaded — i.e. an EAGAIN
|
|
83
|
+
// retry would change nothing. Matches the separated `-j 1` / `--threads 1` form
|
|
84
|
+
// and the attached `-j1` / `--threads=1` form. Any other thread pin (e.g. -j8)
|
|
85
|
+
// is NOT "single-thread pinned": the retry should still run and downshift it.
|
|
86
|
+
function _isRgSingleThreadPinned(argsList) {
|
|
87
|
+
for (let i = 0; i < argsList.length; i++) {
|
|
88
|
+
const a = argsList[i];
|
|
89
|
+
if (typeof a !== 'string') continue;
|
|
90
|
+
if ((a === '-j' || a === '--threads') && String(argsList[i + 1]).trim() === '1') return true;
|
|
91
|
+
if (a === '-j1' || a === '--threads=1') return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
5
95
|
|
|
6
96
|
let _rgExecutableResolved = null;
|
|
7
97
|
|
|
8
|
-
function _resolveRgExecutable() {
|
|
98
|
+
async function _resolveRgExecutable() {
|
|
9
99
|
const isWin = process.platform === 'win32';
|
|
10
100
|
try {
|
|
11
101
|
const cmd = isWin ? 'where' : 'which';
|
|
12
|
-
const out =
|
|
102
|
+
const { stdout: out } = await execFileAsync(cmd, ['rg'], { encoding: 'utf8', windowsHide: true });
|
|
13
103
|
const first = out.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
14
104
|
if (first && existsSync(first)) return first;
|
|
15
105
|
} catch { /* fall through to PATH scan */ }
|
|
@@ -33,11 +123,27 @@ function _resolveRgExecutable() {
|
|
|
33
123
|
return 'rg';
|
|
34
124
|
}
|
|
35
125
|
|
|
126
|
+
// Synchronous accessor for the spawn call sites — only valid AFTER
|
|
127
|
+
// ensureRgResolved() has run (runRg/runRgWindowedLines await it first). Falls
|
|
128
|
+
// back to the bare 'rg' if somehow called before resolution.
|
|
36
129
|
function rgExecutable() {
|
|
37
|
-
|
|
38
|
-
|
|
130
|
+
return _rgExecutableResolved ?? 'rg';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Async resolver: the `where`/`which` lookup used to run via execFileSync,
|
|
134
|
+
// blocking the event loop ~90ms on the first grep/glob/find of a session
|
|
135
|
+
// (freezing the TUI). Resolve asynchronously and cache the result; subsequent
|
|
136
|
+
// calls return immediately.
|
|
137
|
+
let _rgResolvePromise = null;
|
|
138
|
+
async function ensureRgResolved() {
|
|
139
|
+
if (_rgExecutableResolved !== null) return _rgExecutableResolved;
|
|
140
|
+
if (!_rgResolvePromise) {
|
|
141
|
+
_rgResolvePromise = _resolveRgExecutable().then((resolved) => {
|
|
142
|
+
_rgExecutableResolved = resolved;
|
|
143
|
+
return resolved;
|
|
144
|
+
});
|
|
39
145
|
}
|
|
40
|
-
return
|
|
146
|
+
return _rgResolvePromise;
|
|
41
147
|
}
|
|
42
148
|
|
|
43
149
|
// When _resolveRgExecutable() exhausts `where`/`which` and the PATH scan and
|
|
@@ -48,13 +154,13 @@ function rgExecutable() {
|
|
|
48
154
|
const RG_FALLBACK_FAIL_TTL_MS = 30000;
|
|
49
155
|
let _rgFallbackUsable = null;
|
|
50
156
|
let _rgFallbackFailAt = 0;
|
|
51
|
-
function rgFallbackUsable() {
|
|
157
|
+
async function rgFallbackUsable() {
|
|
52
158
|
if (_rgFallbackUsable === true) return true;
|
|
53
159
|
if (_rgFallbackUsable === false && (Date.now() - _rgFallbackFailAt) < RG_FALLBACK_FAIL_TTL_MS) {
|
|
54
160
|
return false;
|
|
55
161
|
}
|
|
56
162
|
try {
|
|
57
|
-
|
|
163
|
+
await execFileAsync('rg', ['--version'], { windowsHide: true, timeout: 3000 });
|
|
58
164
|
_rgFallbackUsable = true;
|
|
59
165
|
} catch {
|
|
60
166
|
_rgFallbackUsable = false;
|
|
@@ -65,10 +171,11 @@ function rgFallbackUsable() {
|
|
|
65
171
|
|
|
66
172
|
// Throws a clear error only when the bare 'rg' fallback was reached AND it is
|
|
67
173
|
// not actually runnable. A real resolved path short-circuits with no probe, so
|
|
68
|
-
// behavior is unchanged whenever rg exists.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
174
|
+
// behavior is unchanged whenever rg exists. Async so the underlying lookups
|
|
175
|
+
// (`where`/`which`, `rg --version`) never block the event loop.
|
|
176
|
+
async function assertRgAvailable() {
|
|
177
|
+
if (await ensureRgResolved() !== 'rg') return;
|
|
178
|
+
if (await rgFallbackUsable()) return;
|
|
72
179
|
const e = new Error('ripgrep (rg) not found on PATH — install ripgrep or add it to PATH');
|
|
73
180
|
e.code = 'ERG_NOT_FOUND';
|
|
74
181
|
throw e;
|
|
@@ -168,14 +275,19 @@ function _armRgForceSettle({ timeoutMs, isSettled, timers, proc, onForceSettle }
|
|
|
168
275
|
// surfaced as empty stdout so callers can render "(no matches)" uniformly.
|
|
169
276
|
function spawnRg(argsList, execOptions) {
|
|
170
277
|
const timeoutMs = Number(execOptions?.timeout ?? 20000);
|
|
171
|
-
|
|
172
|
-
|
|
278
|
+
const gateSignal = execOptions?.abortSignal || execOptions?.signal || null;
|
|
279
|
+
// Gate the spawn (not the whole call) so over-saturation queues here while
|
|
280
|
+
// the cap's worth of rg keep running. The rg timeout timer is armed AFTER
|
|
281
|
+
// spawn below, so queue-wait time is excluded from the 20s deadline. No
|
|
282
|
+
// node guardian is started: rg owns its full SIGTERM→grace→force-kill +
|
|
283
|
+
// force-settle teardown, so the 1:1 guardian process was pure overhead.
|
|
284
|
+
return acquireChildSpawnSlot(gateSignal).then((releaseSlot) => new Promise((resolve, reject) => {
|
|
285
|
+
const proc = spawn(rgExecutable(), _withRgThreads(argsList), {
|
|
173
286
|
cwd: execOptions?.cwd,
|
|
174
287
|
env: execOptions?.env || process.env,
|
|
175
288
|
windowsHide: true,
|
|
176
289
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
177
290
|
});
|
|
178
|
-
startChildGuardian({ childPid: proc.pid, label: 'rg-runner' });
|
|
179
291
|
let stdout = '';
|
|
180
292
|
let stderr = '';
|
|
181
293
|
let timedOut = false;
|
|
@@ -282,17 +394,23 @@ function spawnRg(argsList, execOptions) {
|
|
|
282
394
|
e.stderr = stderr;
|
|
283
395
|
reject(e);
|
|
284
396
|
});
|
|
285
|
-
|
|
397
|
+
// Release the gate slot exactly once on ANY settle path (resolve/reject:
|
|
398
|
+
// close, error, or force-settle). releaseSlot is idempotent.
|
|
399
|
+
}).finally(() => releaseSlot()));
|
|
286
400
|
}
|
|
287
401
|
|
|
288
402
|
export async function runRg(argsList, execOptions = {}) {
|
|
289
|
-
assertRgAvailable();
|
|
403
|
+
await assertRgAvailable();
|
|
290
404
|
try {
|
|
291
405
|
return await spawnRg(argsList, execOptions);
|
|
292
406
|
} catch (err) {
|
|
293
407
|
const msg = String(err?.message || err?.stderr || '');
|
|
294
|
-
|
|
295
|
-
|
|
408
|
+
// Retry single-threaded on EAGAIN. Skip only when the caller already
|
|
409
|
+
// pinned threads AND that pin is already -j 1 (nothing to gain); any
|
|
410
|
+
// other pin (-j8/--threads=N) is rewritten to a clean -j 1 by
|
|
411
|
+
// _rgEagainRetryArgs so we never emit conflicting thread flags.
|
|
412
|
+
if (/EAGAIN/i.test(msg) && !_isRgSingleThreadPinned(argsList)) {
|
|
413
|
+
return spawnRg(_rgEagainRetryArgs(argsList), execOptions);
|
|
296
414
|
}
|
|
297
415
|
throw err;
|
|
298
416
|
}
|
|
@@ -304,14 +422,17 @@ function spawnRgWindowedLines(argsList, execOptions, opts = {}) {
|
|
|
304
422
|
const lineLimit = Number.isFinite(opts.limit) ? Math.max(0, Math.floor(Number(opts.limit) || 0)) : Infinity;
|
|
305
423
|
const summaryLimit = Math.max(0, Math.floor(Number(opts.summaryLimit) || 0));
|
|
306
424
|
const collectLimit = lineLimit === Infinity ? Infinity : Math.max(lineLimit, summaryLimit);
|
|
307
|
-
|
|
308
|
-
|
|
425
|
+
const gateSignal = execOptions?.abortSignal || execOptions?.signal || null;
|
|
426
|
+
// Same gate + thread-cap + no-guardian treatment as spawnRg; queue-wait is
|
|
427
|
+
// excluded from the rg timeout (armed after spawn). releaseSlot is fired
|
|
428
|
+
// once via .finally on every settle path.
|
|
429
|
+
return acquireChildSpawnSlot(gateSignal).then((releaseSlot) => new Promise((resolve, reject) => {
|
|
430
|
+
const proc = spawn(rgExecutable(), _withRgThreads(argsList), {
|
|
309
431
|
cwd: execOptions?.cwd,
|
|
310
432
|
env: execOptions?.env || process.env,
|
|
311
433
|
windowsHide: true,
|
|
312
434
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
313
435
|
});
|
|
314
|
-
startChildGuardian({ childPid: proc.pid, label: 'rg-runner-windowed' });
|
|
315
436
|
let buffer = '';
|
|
316
437
|
let stderr = '';
|
|
317
438
|
let skipped = 0;
|
|
@@ -448,17 +569,17 @@ function spawnRgWindowedLines(argsList, execOptions, opts = {}) {
|
|
|
448
569
|
e.stderr = stderr;
|
|
449
570
|
reject(e);
|
|
450
571
|
});
|
|
451
|
-
});
|
|
572
|
+
}).finally(() => releaseSlot()));
|
|
452
573
|
}
|
|
453
574
|
|
|
454
575
|
export async function runRgWindowedLines(argsList, execOptions = {}, opts = {}) {
|
|
455
|
-
assertRgAvailable();
|
|
576
|
+
await assertRgAvailable();
|
|
456
577
|
try {
|
|
457
578
|
return await spawnRgWindowedLines(argsList, execOptions, opts);
|
|
458
579
|
} catch (err) {
|
|
459
580
|
const msg = String(err?.message || err?.stderr || '');
|
|
460
|
-
if (/EAGAIN/i.test(msg) && !argsList
|
|
461
|
-
return spawnRgWindowedLines(
|
|
581
|
+
if (/EAGAIN/i.test(msg) && !_isRgSingleThreadPinned(argsList)) {
|
|
582
|
+
return spawnRgWindowedLines(_rgEagainRetryArgs(argsList), execOptions, opts);
|
|
462
583
|
}
|
|
463
584
|
throw err;
|
|
464
585
|
}
|
|
@@ -114,13 +114,13 @@ export function buildGrepRgArgs(parts) {
|
|
|
114
114
|
return rgArgs;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
export function buildGlobCacheKey({ patterns, basePath, headLimit, offset, extraIgnore }) {
|
|
117
|
+
export function buildGlobCacheKey({ patterns, basePath, headLimit, offset, extraIgnore, sort }) {
|
|
118
118
|
// extraIgnore (rg ignore globs from _extraIgnoreDirs) alters which files
|
|
119
119
|
// match, so it MUST partake in the key — otherwise calls that differ only
|
|
120
120
|
// by extra ignores collide and return stale over-/under-filtered results.
|
|
121
121
|
// Sorted so the same ignore set in any order maps to one key.
|
|
122
122
|
const extra = Array.isArray(extraIgnore) && extraIgnore.length ? [...extraIgnore].sort().join('\x01') : '';
|
|
123
|
-
return ['glob', patterns.join('\x01'), basePath, headLimit ?? '', offset ?? '', extra].join('|');
|
|
123
|
+
return ['glob', patterns.join('\x01'), basePath, headLimit ?? '', offset ?? '', sort || 'natural', extra].join('|');
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
export function buildListCacheKey(parts) {
|