mixdog 0.9.2 → 0.9.4
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/package.json +8 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/telegram-format.mjs +19 -22
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +6 -14
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +259 -80
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +56 -588
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -39,6 +39,7 @@ import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
|
39
39
|
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
40
40
|
import { resolveOptionalCwd } from './cwd-utils.mjs';
|
|
41
41
|
import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
|
|
42
|
+
import { resolveSessionCwd, stateFilePath, wrapPowerShellWithCwdProbe, wrapBashWithCwdProbe } from '../shell-state.mjs';
|
|
42
43
|
|
|
43
44
|
// Post-exec drift detection. After a foreground shell command, compare the
|
|
44
45
|
// live mtime+size of files mixdog has already read this session against their
|
|
@@ -113,7 +114,12 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
113
114
|
const requestedCwd = args.cwd ?? args.workdir;
|
|
114
115
|
const cwdResult = resolveOptionalCwd(requestedCwd, workDir);
|
|
115
116
|
if (cwdResult.error) return cwdResult.error;
|
|
116
|
-
|
|
117
|
+
// Session cwd carry-over (Claude Code style, no live shell): when the model
|
|
118
|
+
// passes an explicit cwd it wins and updates the store on the next probe;
|
|
119
|
+
// otherwise reuse the last stored cwd for this session if it still exists.
|
|
120
|
+
const _hasExplicitCwd = typeof requestedCwd === 'string' && requestedCwd.trim() !== '';
|
|
121
|
+
const _sessionCwdKey = options?.sessionId ?? options?.readStateScope ?? options?.callerSessionId ?? null;
|
|
122
|
+
const bashWorkDir = resolveSessionCwd(_sessionCwdKey, _hasExplicitCwd ? cwdResult.cwd : null, cwdResult.cwd);
|
|
117
123
|
const _readStateScope = options?.readStateScope ?? options?.sessionId ?? null;
|
|
118
124
|
const executionMode = resolveExecutionMode(args || {}, args?.run_in_background === true ? 'async' : 'sync');
|
|
119
125
|
const runInBackground = executionMode === 'async';
|
|
@@ -318,8 +324,22 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
318
324
|
try { bashAbortSignal = (await getAbortSignalForSession(options?.sessionId)) || null; }
|
|
319
325
|
catch { bashAbortSignal = null; }
|
|
320
326
|
const combinedBashAbort = _combineAbortSignals(bashAbortSignal, options?.abortSignal || null);
|
|
327
|
+
// Sync path only: chain a trailing cwd probe so the session's final
|
|
328
|
+
// working directory persists to the next shell call. Async jobs run
|
|
329
|
+
// detached and are intentionally excluded (they never reach here). The
|
|
330
|
+
// probe captures the command's exit status first and re-exits with it,
|
|
331
|
+
// so the exit code the model sees is unchanged.
|
|
332
|
+
let syncCommand = wrappedCommand;
|
|
333
|
+
try {
|
|
334
|
+
const _stateFile = stateFilePath(_sessionCwdKey);
|
|
335
|
+
if (_stateFile) {
|
|
336
|
+
syncCommand = (process.platform === 'win32' && shellType === 'powershell')
|
|
337
|
+
? wrapPowerShellWithCwdProbe(wrappedCommand, _stateFile)
|
|
338
|
+
: wrapBashWithCwdProbe(wrappedCommand, _stateFile);
|
|
339
|
+
}
|
|
340
|
+
} catch { syncCommand = wrappedCommand; }
|
|
321
341
|
const result = await execShellCommand({
|
|
322
|
-
shell, shellArg, shellArgs, command:
|
|
342
|
+
shell, shellArg, shellArgs, command: syncCommand,
|
|
323
343
|
env: spawnEnv,
|
|
324
344
|
cwd: bashWorkDir,
|
|
325
345
|
timeoutMs: timeout,
|
|
@@ -17,7 +17,7 @@ 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 known file path(s). Prefer grep content_with_context or code_graph anchors first. Window with numeric offset+limit only. Batch paths/regions as real arrays. Dirs use list.',
|
|
20
|
+
description: 'Read known file path(s). Prefer grep content_with_context or code_graph anchors first. Window with numeric offset+limit only. Batch paths/regions as real arrays; adjacent spans in one file = one window, not repeated calls. Dirs use list.',
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: 'object',
|
|
23
23
|
properties: {
|
|
@@ -42,36 +42,13 @@ export const BUILTIN_TOOLS = [
|
|
|
42
42
|
minItems: 1,
|
|
43
43
|
},
|
|
44
44
|
],
|
|
45
|
-
description: 'File path
|
|
45
|
+
description: 'File path. To batch spans: path[] or {path,offset,limit}[] region objects. Pass arrays directly; JSON strings are legacy recovery only.',
|
|
46
46
|
},
|
|
47
47
|
offset: { type: 'number', minimum: 0, description: 'Numeric lines to skip before reading; 0 starts at line 1. Continue with offset:N.' },
|
|
48
48
|
limit: { type: 'number', minimum: 1, description: 'Numeric max lines to return after offset.' },
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
|
-
{
|
|
53
|
-
name: 'diagnostics',
|
|
54
|
-
title: 'Mixdog Diagnostics',
|
|
55
|
-
annotations: { title: 'Mixdog Diagnostics', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
56
|
-
description: 'Run matching type/lint checker. Default cwd; no LSP.',
|
|
57
|
-
inputSchema: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
properties: {
|
|
60
|
-
path: { type: 'string', description: 'File/dir. Default cwd.' },
|
|
61
|
-
},
|
|
62
|
-
required: [],
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'open_config',
|
|
67
|
-
title: 'Open Config UI',
|
|
68
|
-
// agentHidden: a worker/reviewer session has no business popping the
|
|
69
|
-
// settings UI on the user's machine; it also wastes schema bytes on
|
|
70
|
-
// every agent request. Lead keeps it.
|
|
71
|
-
annotations: { title: 'Open Config UI', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false, agentHidden: true },
|
|
72
|
-
description: 'Open settings UI; returns URL. No params.',
|
|
73
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
74
|
-
},
|
|
75
52
|
{
|
|
76
53
|
name: 'shell',
|
|
77
54
|
title: 'Mixdog Shell',
|
|
@@ -81,16 +58,10 @@ export const BUILTIN_TOOLS = [
|
|
|
81
58
|
type: 'object',
|
|
82
59
|
properties: {
|
|
83
60
|
command: { type: 'string', description: 'Command.' },
|
|
84
|
-
cwd: { type: 'string', description: 'Working directory.' },
|
|
85
|
-
workdir: { type: 'string', description: 'Alias for cwd; omit if empty.' },
|
|
61
|
+
cwd: { type: 'string', description: 'Working directory. Persists across shell calls within a session — omit to reuse the previous command\'s directory (e.g. after cd); pass an absolute path to change it.' },
|
|
86
62
|
timeout: { type: 'number', description: 'Timeout ms.' },
|
|
87
63
|
merge_stderr: { type: 'boolean', description: 'Merge stderr.' },
|
|
88
64
|
mode: { type: 'string', enum: ['sync', 'async'], description: executionModeSchemaDescription('sync') },
|
|
89
|
-
run_in_background: { type: 'boolean', description: `Legacy alias for mode=async. ${TOOL_ASYNC_EXECUTION_CONTRACT}` },
|
|
90
|
-
persistent: { type: 'boolean', description: 'Persistent shell mode.' },
|
|
91
|
-
session_id: { type: 'string', description: 'Persistent shell id.' },
|
|
92
|
-
create: { type: 'boolean', description: 'Create session_id if missing.' },
|
|
93
|
-
close: { type: 'boolean', description: 'Close persistent session.' },
|
|
94
65
|
shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell. Windows default is PowerShell; bash means Git Bash/POSIX.' },
|
|
95
66
|
},
|
|
96
67
|
required: ['command'],
|
|
@@ -116,7 +87,7 @@ export const BUILTIN_TOOLS = [
|
|
|
116
87
|
name: 'grep',
|
|
117
88
|
title: 'Mixdog Grep',
|
|
118
89
|
annotations: { title: 'Mixdog Grep', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
119
|
-
description: 'Search file contents by text/regex in a known scope. Use files_with_matches/count for broad anchors, content_with_context for narrow code answers. One concept → one grep.',
|
|
90
|
+
description: 'Search file contents by text/regex in a known scope. Use files_with_matches/count for broad anchors, content_with_context for narrow code answers. One concept → one grep; multiple scopes = ONE call with path[].',
|
|
120
91
|
inputSchema: {
|
|
121
92
|
type: 'object',
|
|
122
93
|
properties: {
|
|
@@ -127,7 +98,13 @@ export const BUILTIN_TOOLS = [
|
|
|
127
98
|
],
|
|
128
99
|
description: 'Text/regex. Put synonyms in pattern[] as OR in ONE grep; no serial rewording or equivalent repeats.',
|
|
129
100
|
},
|
|
130
|
-
path: {
|
|
101
|
+
path: {
|
|
102
|
+
anyOf: [
|
|
103
|
+
{ type: 'string' },
|
|
104
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
105
|
+
],
|
|
106
|
+
description: 'Known narrowest file/dir, or path[] to search several scopes in one call; broad scopes return paths first, then refine from returned paths.',
|
|
107
|
+
},
|
|
131
108
|
glob: {
|
|
132
109
|
anyOf: [
|
|
133
110
|
{ type: 'string' },
|
|
@@ -136,9 +113,11 @@ export const BUILTIN_TOOLS = [
|
|
|
136
113
|
description: 'Narrow in same grep; no follow-up grep for equivalent scope changes.',
|
|
137
114
|
},
|
|
138
115
|
output_mode: { type: 'string', enum: ['content_with_context', 'content', 'files_with_matches', 'count'], description: 'Broad scope: files_with_matches/count. Narrow scope: content_with_context; answer from it, skip read unless span is not shown.' },
|
|
139
|
-
context: { type: 'number', minimum: 0, description: 'Lines before/after each match; keep bounded.' },
|
|
140
116
|
head_limit: { type: 'number', minimum: 0, description: 'Max output lines; keep small.' },
|
|
141
117
|
offset: { type: 'number', minimum: 0, description: 'Skip output lines for paging.' },
|
|
118
|
+
'-A': { type: 'number', minimum: 0, description: 'Lines after each match.' },
|
|
119
|
+
'-B': { type: 'number', minimum: 0, description: 'Lines before each match.' },
|
|
120
|
+
'-C': { type: 'number', minimum: 0, description: 'Lines before/after each match.' },
|
|
142
121
|
},
|
|
143
122
|
required: [],
|
|
144
123
|
},
|
|
@@ -147,7 +126,7 @@ export const BUILTIN_TOOLS = [
|
|
|
147
126
|
name: 'glob',
|
|
148
127
|
title: 'Mixdog Glob',
|
|
149
128
|
annotations: { title: 'Mixdog Glob', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
150
|
-
description: 'Find files by exact glob. Unknown path/name uses find.',
|
|
129
|
+
description: 'Find files by exact glob. Unknown path/name uses find. Multiple patterns/dirs = ONE call with pattern[]/path[], never parallel single-pattern calls.',
|
|
151
130
|
inputSchema: {
|
|
152
131
|
type: 'object',
|
|
153
132
|
properties: {
|
|
@@ -156,14 +135,14 @@ export const BUILTIN_TOOLS = [
|
|
|
156
135
|
{ type: 'string' },
|
|
157
136
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
158
137
|
],
|
|
159
|
-
description: 'Exact glob pattern(s).',
|
|
138
|
+
description: 'Exact glob pattern(s). Batch related patterns as pattern[] in one call.',
|
|
160
139
|
},
|
|
161
140
|
path: {
|
|
162
141
|
anyOf: [
|
|
163
142
|
{ type: 'string' },
|
|
164
143
|
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
165
144
|
],
|
|
166
|
-
description: 'Base directory/directories.',
|
|
145
|
+
description: 'Base directory/directories. Batch multiple roots as path[] in one call.',
|
|
167
146
|
},
|
|
168
147
|
head_limit: { type: 'number', description: 'Max entries.' },
|
|
169
148
|
offset: { type: 'number', description: 'Skip entries.' },
|
|
@@ -175,11 +154,17 @@ export const BUILTIN_TOOLS = [
|
|
|
175
154
|
name: 'find',
|
|
176
155
|
title: 'Mixdog Find Files',
|
|
177
156
|
annotations: { title: 'Mixdog Find Files', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
178
|
-
description: 'Find files by partial path/name. Exact structure uses glob. Returns verified paths.',
|
|
157
|
+
description: 'Find files by partial path/name. Exact structure uses glob. Returns verified paths. Multiple names = ONE call with query[].',
|
|
179
158
|
inputSchema: {
|
|
180
159
|
type: 'object',
|
|
181
160
|
properties: {
|
|
182
|
-
query: {
|
|
161
|
+
query: {
|
|
162
|
+
anyOf: [
|
|
163
|
+
{ type: 'string' },
|
|
164
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
165
|
+
],
|
|
166
|
+
description: 'Partial path/name words (not file contents), or query[] to batch several lookups in one call.',
|
|
167
|
+
},
|
|
183
168
|
path: { type: 'string', description: 'Base directory.' },
|
|
184
169
|
head_limit: { type: 'number', description: 'Max paths.' },
|
|
185
170
|
},
|
|
@@ -190,11 +175,17 @@ export const BUILTIN_TOOLS = [
|
|
|
190
175
|
name: 'list',
|
|
191
176
|
title: 'Mixdog List Directory',
|
|
192
177
|
annotations: { title: 'Mixdog List Directory', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
193
|
-
description: 'List known directory entries. Use glob for broad discovery.',
|
|
178
|
+
description: 'List known directory entries. Use glob for broad discovery. Multiple dirs = ONE call with path[].',
|
|
194
179
|
inputSchema: {
|
|
195
180
|
type: 'object',
|
|
196
181
|
properties: {
|
|
197
|
-
path: {
|
|
182
|
+
path: {
|
|
183
|
+
anyOf: [
|
|
184
|
+
{ type: 'string' },
|
|
185
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
186
|
+
],
|
|
187
|
+
description: 'Directory, or path[] to list several directories in one call.',
|
|
188
|
+
},
|
|
198
189
|
head_limit: { type: 'number', description: 'Max entries.' },
|
|
199
190
|
offset: { type: 'number', description: 'Skip N entries for paging.' },
|
|
200
191
|
},
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Native adapters for well-known tool names from OTHER agent CLIs whose
|
|
2
|
+
// argument shapes differ from any mixdog builtin (StrReplace/Write/bash
|
|
3
|
+
// variants). Executed directly against the filesystem via atomicWrite / the
|
|
4
|
+
// existing shell runner — NOT by synthesizing an apply_patch V4A string
|
|
5
|
+
// (that approach was tried and abandoned: building a correct V4A envelope
|
|
6
|
+
// from arbitrary old_string/new_string/contents blew up in edge-case
|
|
7
|
+
// complexity for no benefit over a direct fs edit).
|
|
8
|
+
//
|
|
9
|
+
// Contract: tryExecuteExternalToolAdapter(name, args, workDir, options)
|
|
10
|
+
// returns a result STRING when the call was handled (success or a concrete
|
|
11
|
+
// tool-level error), or `null` when the args shape didn't match what the
|
|
12
|
+
// adapter expects — the caller (builtin.mjs default: case) falls back to
|
|
13
|
+
// the existing EXTERNAL_TOOL_REDIRECTS guidance message in that case.
|
|
14
|
+
import { readFileSync, mkdirSync, existsSync, lstatSync, realpathSync, statSync } from 'node:fs';
|
|
15
|
+
import { dirname, sep } from 'node:path';
|
|
16
|
+
import { atomicWrite } from './atomic-write.mjs';
|
|
17
|
+
import { assertPathsReachable } from './fs-reachability.mjs';
|
|
18
|
+
import { normalizeInputPath, resolveAgainstCwd, normalizeOutputPath } from './path-utils.mjs';
|
|
19
|
+
import { isUncPath, isWindowsDevicePath, hasUnsafeWin32Component, isBlockedDevicePath, isSpecialFileStat } from './device-paths.mjs';
|
|
20
|
+
import { executeBashTool } from './bash-tool.mjs';
|
|
21
|
+
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
22
|
+
import { markCodeGraphDirtyPaths } from '../code-graph-state.mjs';
|
|
23
|
+
|
|
24
|
+
const STR_REPLACE_NAMES = new Set(['strreplace', 'str_replace', 'str_replace_editor', 'search_replace']);
|
|
25
|
+
const WRITE_NAMES = new Set(['write', 'create_file', 'createfile']);
|
|
26
|
+
// 'bash'/'Bash' explicitly request the posix/git-bash shell kind; the
|
|
27
|
+
// run/runcommand/terminal/run_terminal_cmd family leaves `shell` unset so
|
|
28
|
+
// executeBashTool's own default-shell resolution applies (mirrors how the
|
|
29
|
+
// native `shell` tool behaves when the caller omits `shell`).
|
|
30
|
+
const BASH_SHELL_KIND_NAMES = new Set(['bash']);
|
|
31
|
+
const BASH_DEFAULT_NAMES = new Set(['run', 'runcommand', 'terminal', 'run_terminal_cmd']);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* True when `name` is a foreign-CLI tool this module can adapt natively.
|
|
35
|
+
* Used by the session loop's dispatch so these names route INTO
|
|
36
|
+
* executeBuiltinTool (whose default: case invokes the adapter) instead of
|
|
37
|
+
* short-circuiting to the unknown-tool redirect message.
|
|
38
|
+
*/
|
|
39
|
+
export function isExternalAdapterTool(name) {
|
|
40
|
+
if (typeof name !== 'string' || !name) return false;
|
|
41
|
+
const key = name.toLowerCase();
|
|
42
|
+
return STR_REPLACE_NAMES.has(key) || WRITE_NAMES.has(key)
|
|
43
|
+
|| BASH_SHELL_KIND_NAMES.has(key) || BASH_DEFAULT_NAMES.has(key);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Same write-target guards the read/list surfaces enforce (UNC → NTLM hash
|
|
47
|
+
// leak, device namespace → raw device access / hang, trailing-dot / ADS →
|
|
48
|
+
// device-guard bypass). Writes are strictly more dangerous than reads, so
|
|
49
|
+
// every adapter write path must run these on both the normalized input and
|
|
50
|
+
// the fully resolved path. Returns an Error string or null.
|
|
51
|
+
function guardWritePath(p) {
|
|
52
|
+
if (isUncPath(p))
|
|
53
|
+
return `Error: cannot write UNC / SMB path (network credential leak risk): ${normalizeOutputPath(p)}`;
|
|
54
|
+
if (isWindowsDevicePath(p))
|
|
55
|
+
return `Error: cannot write Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(p)}`;
|
|
56
|
+
if (hasUnsafeWin32Component(p))
|
|
57
|
+
return `Error: cannot write Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(p)}`;
|
|
58
|
+
if (isBlockedDevicePath(p))
|
|
59
|
+
return `Error: cannot write device file (would block or corrupt a device): ${normalizeOutputPath(p)}`;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Realpath to the nearest existing ancestor (create-mode leaves don't exist
|
|
64
|
+
// yet). Returns { probe, real } or null when nothing on the path exists /
|
|
65
|
+
// realpath itself fails.
|
|
66
|
+
function realpathNearestExisting(fullPath) {
|
|
67
|
+
let probe = fullPath;
|
|
68
|
+
while (probe && !existsSync(probe)) {
|
|
69
|
+
const parent = dirname(probe);
|
|
70
|
+
if (!parent || parent === probe) return null;
|
|
71
|
+
probe = parent;
|
|
72
|
+
}
|
|
73
|
+
if (!probe || !existsSync(probe)) return null;
|
|
74
|
+
try { return { probe, real: realpathSync(probe) }; } catch { return null; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Realpath-based guard: a symlink/junction in the target (or its nearest
|
|
78
|
+
// existing ancestor, for create-mode paths) can point at a UNC share or a
|
|
79
|
+
// device namespace that the lexical checks above never see. Mirrors the
|
|
80
|
+
// realpath verification apply_patch runs on every header. Returns an Error
|
|
81
|
+
// string or null; never throws.
|
|
82
|
+
function guardRealTarget(fullPath) {
|
|
83
|
+
const nearest = realpathNearestExisting(fullPath);
|
|
84
|
+
if (nearest && nearest.real !== nearest.probe) {
|
|
85
|
+
const guardErr = guardWritePath(nearest.real);
|
|
86
|
+
if (guardErr) return `${guardErr} (symlink target of ${normalizeOutputPath(nearest.probe)})`;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const lst = lstatSync(fullPath);
|
|
90
|
+
if (lst.isSymbolicLink()) {
|
|
91
|
+
const realTarget = realpathSync(fullPath);
|
|
92
|
+
const linkGuardErr = guardWritePath(realTarget);
|
|
93
|
+
if (linkGuardErr) return `${linkGuardErr} (symlink target of ${normalizeOutputPath(fullPath)})`;
|
|
94
|
+
}
|
|
95
|
+
// statSync FOLLOWS a leaf symlink, so a link pointing at a custom
|
|
96
|
+
// FIFO/socket/char/block inode trips here too (lstat alone only saw
|
|
97
|
+
// the link inode itself).
|
|
98
|
+
const st = statSync(fullPath);
|
|
99
|
+
if (isSpecialFileStat(st))
|
|
100
|
+
return `Error: cannot write special file (FIFO / character / block device / socket): ${normalizeOutputPath(fullPath)}`;
|
|
101
|
+
} catch { /* ENOENT (create mode) — nothing to check */ }
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Write containment — mirrors apply_patch's realBase check: the REAL resolved
|
|
106
|
+
// target (symlinks flattened, create-mode suffix re-attached lexically) must
|
|
107
|
+
// stay inside the REAL workDir. A lexically-inside path whose ancestor
|
|
108
|
+
// symlink/junction lands outside the project must not be writable through
|
|
109
|
+
// this adapter surface (apply_patch refuses the same shape).
|
|
110
|
+
function guardBaseContainment(fullPath, workDir) {
|
|
111
|
+
let realBase;
|
|
112
|
+
try { realBase = realpathSync(workDir); } catch { return null; }
|
|
113
|
+
const nearest = realpathNearestExisting(fullPath);
|
|
114
|
+
if (!nearest) return null;
|
|
115
|
+
const realResolved = nearest.real + fullPath.slice(nearest.probe.length);
|
|
116
|
+
const fold = process.platform === 'win32' ? (s) => s.toLowerCase() : (s) => s;
|
|
117
|
+
const baseWithSep = realBase.endsWith(sep) ? realBase : realBase + sep;
|
|
118
|
+
if (fold(realResolved) !== fold(realBase) && !fold(realResolved).startsWith(fold(baseWithSep))) {
|
|
119
|
+
return `Error: cannot write outside the working directory: ${normalizeOutputPath(realResolved)} escapes ${normalizeOutputPath(realBase)}`;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function resolveTargetPath(args, workDir) {
|
|
125
|
+
const raw = args?.path ?? args?.file_path;
|
|
126
|
+
if (typeof raw !== 'string' || raw.length === 0) return null;
|
|
127
|
+
const norm = normalizeInputPath(raw);
|
|
128
|
+
const guardErr = guardWritePath(norm);
|
|
129
|
+
if (guardErr) return { error: guardErr };
|
|
130
|
+
const full = resolveAgainstCwd(norm, workDir);
|
|
131
|
+
const fullGuardErr = guardWritePath(full);
|
|
132
|
+
if (fullGuardErr) return { error: fullGuardErr };
|
|
133
|
+
// Reachability preflight BEFORE any sync fs (existsSync/realpathSync/
|
|
134
|
+
// lstatSync in the guards below): a dead mount would wedge the event loop
|
|
135
|
+
// on the first sync stat, defeating every downstream timeout. Same
|
|
136
|
+
// deadline-raced probe the read path runs (_readReachPreflight).
|
|
137
|
+
try { await assertPathsReachable([full]); }
|
|
138
|
+
catch (e) { return { error: `Error: ${e?.message || e}` }; }
|
|
139
|
+
const realGuardErr = guardRealTarget(full);
|
|
140
|
+
if (realGuardErr) return { error: realGuardErr };
|
|
141
|
+
const containErr = guardBaseContainment(full, workDir);
|
|
142
|
+
if (containErr) return { error: containErr };
|
|
143
|
+
return { full };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function invalidateAfterWrite(fullPath) {
|
|
147
|
+
try { invalidateBuiltinResultCache([fullPath]); } catch { /* best-effort */ }
|
|
148
|
+
try { markCodeGraphDirtyPaths([fullPath]); } catch { /* best-effort */ }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function countOccurrences(haystack, needle) {
|
|
152
|
+
let count = 0;
|
|
153
|
+
let idx = -1;
|
|
154
|
+
while ((idx = haystack.indexOf(needle, idx + 1)) !== -1) count += 1;
|
|
155
|
+
return count;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function adaptStrReplace(args, workDir, options) {
|
|
159
|
+
const oldStr = args?.old_string;
|
|
160
|
+
const newStr = args?.new_string;
|
|
161
|
+
if (typeof oldStr !== 'string' || typeof newStr !== 'string') return null;
|
|
162
|
+
// Empty old_string would loop forever in countOccurrences (indexOf('', i)
|
|
163
|
+
// never returns -1) and has no meaningful replace semantics anyway.
|
|
164
|
+
if (oldStr.length === 0) return 'Error: old_string must be a non-empty string';
|
|
165
|
+
const target = await resolveTargetPath(args, workDir);
|
|
166
|
+
if (!target) return null;
|
|
167
|
+
if (target.error) return target.error;
|
|
168
|
+
const fullPath = target.full;
|
|
169
|
+
let content;
|
|
170
|
+
let statBefore = null;
|
|
171
|
+
try {
|
|
172
|
+
try { statBefore = statSync(fullPath); } catch { /* readFileSync below surfaces the real error */ }
|
|
173
|
+
content = readFileSync(fullPath, 'utf8');
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return `Error: cannot read ${fullPath} (${err?.message || err})`;
|
|
176
|
+
}
|
|
177
|
+
const matchCount = countOccurrences(content, oldStr);
|
|
178
|
+
if (matchCount === 0) return `Error: old_string not found in ${fullPath}`;
|
|
179
|
+
if (matchCount > 1) return `Error: old_string is ambiguous: ${matchCount} matches in ${fullPath}`;
|
|
180
|
+
const first = content.indexOf(oldStr);
|
|
181
|
+
const updated = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
182
|
+
// Lost-update guard: hand the pre-read stat to atomicWrite as an expected
|
|
183
|
+
// target snapshot — it re-stats immediately before EACH rename attempt and
|
|
184
|
+
// aborts with ESTALE_TARGET when another writer (parallel tool call,
|
|
185
|
+
// interleaved apply_patch) changed the file after our read. This closes
|
|
186
|
+
// the check-then-write race a pre-write mtime comparison here would leave.
|
|
187
|
+
try {
|
|
188
|
+
await atomicWrite(fullPath, updated, {
|
|
189
|
+
sessionId: options?.readStateScope || options?.sessionId,
|
|
190
|
+
expectedTargetSnapshot: statBefore ? {
|
|
191
|
+
exists: true,
|
|
192
|
+
size: statBefore.size,
|
|
193
|
+
mtimeMs: statBefore.mtimeMs,
|
|
194
|
+
ctimeMs: statBefore.ctimeMs,
|
|
195
|
+
ino: statBefore.ino,
|
|
196
|
+
} : undefined,
|
|
197
|
+
});
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (err?.code === 'ESTALE_TARGET') {
|
|
200
|
+
return `Error: ${fullPath} changed on disk during the replace; re-read and retry`;
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
invalidateAfterWrite(fullPath);
|
|
205
|
+
return `Updated ${fullPath} (1 replacement)`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function adaptWrite(args, workDir, options) {
|
|
209
|
+
const contents = args?.contents ?? args?.content ?? args?.file_text;
|
|
210
|
+
if (typeof contents !== 'string') return null;
|
|
211
|
+
const target = await resolveTargetPath(args, workDir);
|
|
212
|
+
if (!target) return null;
|
|
213
|
+
if (target.error) return target.error;
|
|
214
|
+
const fullPath = target.full;
|
|
215
|
+
const existed = existsSync(fullPath);
|
|
216
|
+
try { mkdirSync(dirname(fullPath), { recursive: true }); } catch { /* best-effort: parent may already exist */ }
|
|
217
|
+
await atomicWrite(fullPath, contents, { sessionId: options?.readStateScope || options?.sessionId });
|
|
218
|
+
invalidateAfterWrite(fullPath);
|
|
219
|
+
return `${existed ? 'Updated' : 'Created'} ${fullPath} (${Buffer.byteLength(contents, 'utf8')} bytes)`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function adaptBash(key, args, workDir, options) {
|
|
223
|
+
if (typeof args?.command !== 'string' || args.command.length === 0) return null;
|
|
224
|
+
const shellArgs = { ...args };
|
|
225
|
+
if (BASH_SHELL_KIND_NAMES.has(key) && shellArgs.shell === undefined) shellArgs.shell = 'bash';
|
|
226
|
+
return executeBashTool(shellArgs, workDir, options);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function tryExecuteExternalToolAdapter(name, args, workDir, options) {
|
|
230
|
+
if (typeof name !== 'string' || !name) return null;
|
|
231
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) return null;
|
|
232
|
+
const key = name.toLowerCase();
|
|
233
|
+
try {
|
|
234
|
+
if (STR_REPLACE_NAMES.has(key)) return await adaptStrReplace(args, workDir, options);
|
|
235
|
+
if (WRITE_NAMES.has(key)) return await adaptWrite(args, workDir, options);
|
|
236
|
+
if (BASH_SHELL_KIND_NAMES.has(key) || BASH_DEFAULT_NAMES.has(key)) return await adaptBash(key, args, workDir, options);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return `Error: ${name} failed (${err?.message || String(err)})`;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// fuzzy-match.mjs — lightweight subsequence fuzzy scorer for filename / path
|
|
2
|
-
// search
|
|
2
|
+
// search. Returns a score where a
|
|
3
3
|
// higher value is a better match, or null when the query is not a subsequence
|
|
4
4
|
// of the candidate.
|
|
5
5
|
//
|
|
@@ -60,6 +60,26 @@ function listGuardPath(p) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export async function executeListTool(args, workDir, options = {}) {
|
|
63
|
+
if (Array.isArray(args.path)) {
|
|
64
|
+
const list = [...new Set(args.path.map((p) => (typeof p === 'string' ? p.trim() : '')).filter((p) => p.length > 0))];
|
|
65
|
+
const capped = list.length > 10;
|
|
66
|
+
const targets = capped ? list.slice(0, 10) : list;
|
|
67
|
+
if (targets.length > 1) {
|
|
68
|
+
const sections = [];
|
|
69
|
+
for (const p of targets) {
|
|
70
|
+
let body;
|
|
71
|
+
try {
|
|
72
|
+
body = await executeListTool({ ...args, path: p }, workDir, options);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
body = `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
|
|
75
|
+
}
|
|
76
|
+
sections.push(`# list ${p}\n${body}`);
|
|
77
|
+
}
|
|
78
|
+
if (capped) sections.push(`... [capped at 10 of ${list.length} paths]`);
|
|
79
|
+
return sections.join('\n\n');
|
|
80
|
+
}
|
|
81
|
+
args.path = targets[0];
|
|
82
|
+
}
|
|
63
83
|
if (typeof args.fuzzy === 'string' && args.fuzzy.length > 0) {
|
|
64
84
|
return executeFuzzyFindTool({ ...args, query: args.fuzzy }, workDir, options);
|
|
65
85
|
}
|
|
@@ -302,6 +322,26 @@ export async function executeTreeTool(args, workDir, options = {}) {
|
|
|
302
322
|
// routes here for hidden backward compatibility, but the model-facing tool is
|
|
303
323
|
// `find`.
|
|
304
324
|
export async function executeFuzzyFindTool(args, workDir, options = {}) {
|
|
325
|
+
if (Array.isArray(args.query)) {
|
|
326
|
+
const list = [...new Set(args.query.map((q) => (typeof q === 'string' ? q.trim() : '')).filter((q) => q.length > 0))];
|
|
327
|
+
const capped = list.length > 10;
|
|
328
|
+
const targets = capped ? list.slice(0, 10) : list;
|
|
329
|
+
if (targets.length > 1) {
|
|
330
|
+
const sections = [];
|
|
331
|
+
for (const q of targets) {
|
|
332
|
+
let body;
|
|
333
|
+
try {
|
|
334
|
+
body = await executeFuzzyFindTool({ ...args, query: q }, workDir, options);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
body = `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
|
|
337
|
+
}
|
|
338
|
+
sections.push(`# find ${q}\n${body}`);
|
|
339
|
+
}
|
|
340
|
+
if (capped) sections.push(`... [capped at 10 of ${list.length} queries]`);
|
|
341
|
+
return sections.join('\n\n');
|
|
342
|
+
}
|
|
343
|
+
args.query = targets[0];
|
|
344
|
+
}
|
|
305
345
|
const query = String(args.query ?? args.fuzzy ?? '').trim();
|
|
306
346
|
if (!query) return 'Error: find requires query.';
|
|
307
347
|
const inputPath = normalizeInputPath(args.path) || '.';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readdirSync } from 'fs';
|
|
2
|
-
import { basename, dirname, extname, isAbsolute, join, relative } from 'path';
|
|
1
|
+
import { readdirSync, statSync } from 'fs';
|
|
2
|
+
import { basename, dirname, extname, isAbsolute, join, relative, sep } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { resolvePluginData, mixdogRoot } from '../../../../shared/plugin-paths.mjs';
|
|
5
5
|
|
|
@@ -80,6 +80,46 @@ export function findFileByBasename(searchRoot, fullPath, { limit = 3, maxDirs =
|
|
|
80
80
|
} catch { return []; }
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// Recover a hallucinated absolute-path PREFIX: models frequently request
|
|
84
|
+
// paths like /Users/foo/Local/Project/ink/src/tui/input-editing.mjs where the
|
|
85
|
+
// TAIL (src/tui/input-editing.mjs) is the real repo-relative path but the
|
|
86
|
+
// leading segments are an invented (or wrong-machine) prefix. findFileByBasename
|
|
87
|
+
// only matches the final basename and walks the whole tree (skipping vendor/
|
|
88
|
+
// noise dirs for performance); this instead peels leading segments off the
|
|
89
|
+
// requested path one at a time and stats the remaining tail directly against
|
|
90
|
+
// searchRoot — no directory walk, no skip-dir filtering, so it finds files
|
|
91
|
+
// under vendor/ or any other normally-skipped directory. Min tail length is 2
|
|
92
|
+
// segments so a bare basename (which would match almost anything) never
|
|
93
|
+
// counts as a hit. Capped iterations; pure best-effort, never throws.
|
|
94
|
+
export function findBySuffixStrip(searchRoot, fullPath, { maxIterations = 12 } = {}) {
|
|
95
|
+
try {
|
|
96
|
+
if (typeof searchRoot !== 'string' || !searchRoot) return null;
|
|
97
|
+
if (typeof fullPath !== 'string' || !fullPath) return null;
|
|
98
|
+
// Containment guard: drop `.`/`..` and drive/UNC-ish segments outright.
|
|
99
|
+
// Tails are joined under searchRoot; a `..` segment could stat (and
|
|
100
|
+
// hint) outside the repo, so no relative-traversal token may survive.
|
|
101
|
+
const segments = fullPath.replace(/\\/g, '/').split('/')
|
|
102
|
+
.filter((s) => s && s !== '.' && s !== '..' && !/^[A-Za-z]:$/.test(s));
|
|
103
|
+
// Peel 0..N leading segments; each peel costs one stat. maxIterations
|
|
104
|
+
// bounds the number of stats (strict <), and tails shorter than 2
|
|
105
|
+
// segments never count as a hit.
|
|
106
|
+
const iterations = Math.min(Math.max(segments.length - 1, 0), Math.max(maxIterations, 0));
|
|
107
|
+
for (let i = 0; i < iterations; i++) {
|
|
108
|
+
const tail = segments.slice(i);
|
|
109
|
+
if (tail.length < 2) break;
|
|
110
|
+
const candidate = join(searchRoot, ...tail);
|
|
111
|
+
const rel = relative(searchRoot, candidate);
|
|
112
|
+
if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) continue;
|
|
113
|
+
try {
|
|
114
|
+
if (statSync(candidate).isFile()) {
|
|
115
|
+
return rel.replace(/\\/g, '/');
|
|
116
|
+
}
|
|
117
|
+
} catch { /* miss this tail length, keep peeling */ }
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
} catch { return null; }
|
|
121
|
+
}
|
|
122
|
+
|
|
83
123
|
// Node's native fs errors embed the failing path wrapped in single quotes
|
|
84
124
|
// using OS-native separators ('C:\\Users\\foo\\bar.mjs' on Windows). Without
|
|
85
125
|
// this pass, read error bodies surface backslash paths that
|
|
@@ -20,7 +20,7 @@ export const SMART_READ_TAIL_LINES = _readEnvInt('MIXDOG_READ_TAIL_LINES', 400);
|
|
|
20
20
|
// file actually hurts.
|
|
21
21
|
export const READ_CONTEXT_ADVISORY_BYTES = 40 * 1024;
|
|
22
22
|
export const READ_MAX_RENDERED_LINE_CHARS = 2_000;
|
|
23
|
-
//
|
|
23
|
+
// The read line-prefix separator is `→` (the `→`
|
|
24
24
|
// arrow), matching default cat -n format `<n>→<content>`. It
|
|
25
25
|
// MUST be a NON-WHITESPACE glyph: a tab/space separator collides with the
|
|
26
26
|
// content's own leading indentation, so a model hand-reconstructing an edit
|
|
@@ -3,7 +3,7 @@ import * as fsPromises from 'fs/promises';
|
|
|
3
3
|
import { readFile } from 'fs/promises';
|
|
4
4
|
import { extname } from 'path';
|
|
5
5
|
import { normalizeInputPath } from './path-utils.mjs';
|
|
6
|
-
import { findFileByBasename } from './path-diagnostics.mjs';
|
|
6
|
+
import { findFileByBasename, findBySuffixStrip } from './path-diagnostics.mjs';
|
|
7
7
|
import { getReadSnapshot } from './read-snapshot-runtime.mjs';
|
|
8
8
|
import { snapshotCoversFullFile, statMatchesSnapshot } from './snapshot-helpers.mjs';
|
|
9
9
|
import { formatBinaryReadPreview } from './binary-file.mjs';
|
|
@@ -209,9 +209,16 @@ export async function executeSingleReadTool(args, workDir, readStateScope, optio
|
|
|
209
209
|
// path(s) directly — the route a model would otherwise reconstruct with
|
|
210
210
|
// a grep/glob storm.
|
|
211
211
|
if (!similar) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
// Hallucinated absolute-prefix recovery first: no directory walk,
|
|
213
|
+
// stats directly (finds vendor/ paths the basename BFS skips).
|
|
214
|
+
const suffixHit = findBySuffixStrip(workDir, fullPath);
|
|
215
|
+
if (suffixHit) {
|
|
216
|
+
hint = ` Not found at this path; the same filename exists at: "${normalizeOutputPath(suffixHit)}". Read that path directly.`;
|
|
217
|
+
} else {
|
|
218
|
+
const elsewhere = findFileByBasename(workDir, fullPath);
|
|
219
|
+
if (elsewhere.length) {
|
|
220
|
+
hint = ` Not found at this path; the same filename exists at: ${elsewhere.map((p) => `"${normalizeOutputPath(p)}"`).join(', ')}. Read that path directly.`;
|
|
221
|
+
}
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
const _rawMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -181,6 +181,35 @@ async function assertRgAvailable() {
|
|
|
181
181
|
throw e;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// ── PCRE2 capability detection ───────────────────────────────────────────
|
|
185
|
+
// ripgrep's default regex engine (Rust `regex` crate) intentionally rejects
|
|
186
|
+
// lookaround/backreferences ("look-around ... is not supported" /
|
|
187
|
+
// "backreferences ... not supported"). Some rg builds are compiled with the
|
|
188
|
+
// optional `pcre2` feature, which adds `-P`/`--pcre2` support for exactly
|
|
189
|
+
// those constructs. Detect capability once via `rg --pcre2-version`: it
|
|
190
|
+
// exits 0 and prints a version when PCRE2 is linked in, and exits non-zero
|
|
191
|
+
// ("PCRE2 is not available...") when it is not. Cached permanently on
|
|
192
|
+
// success; failure is cached briefly so a mid-session rg upgrade/reinstall
|
|
193
|
+
// is picked up without a daemon restart (mirrors rgFallbackUsable's TTL).
|
|
194
|
+
const RG_PCRE2_FAIL_TTL_MS = 30000;
|
|
195
|
+
let _rgPcre2Supported = null;
|
|
196
|
+
let _rgPcre2FailAt = 0;
|
|
197
|
+
export async function rgSupportsPcre2() {
|
|
198
|
+
if (_rgPcre2Supported === true) return true;
|
|
199
|
+
if (_rgPcre2Supported === false && (Date.now() - _rgPcre2FailAt) < RG_PCRE2_FAIL_TTL_MS) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await ensureRgResolved();
|
|
204
|
+
await execFileAsync(rgExecutable(), ['--pcre2-version'], { windowsHide: true, timeout: 3000 });
|
|
205
|
+
_rgPcre2Supported = true;
|
|
206
|
+
} catch {
|
|
207
|
+
_rgPcre2Supported = false;
|
|
208
|
+
_rgPcre2FailAt = Date.now();
|
|
209
|
+
}
|
|
210
|
+
return _rgPcre2Supported;
|
|
211
|
+
}
|
|
212
|
+
|
|
184
213
|
// Cap rg stdout accumulation so a runaway producer (huge repo, accidental
|
|
185
214
|
// match-all) cannot balloon the JS string heap. Mirrors CC's ripgrep.ts cap.
|
|
186
215
|
const MAX_RG_STDOUT_BYTES = 20 * 1024 * 1024; // 20MB
|