mixdog 0.9.3 → 0.9.5
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 +112 -38
- package/package.json +10 -3
- 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/r5-orchestrated-task.json +7 -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-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -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 +251 -32
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +3 -4
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- 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/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +25 -65
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +9 -8
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +10 -7
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +647 -2056
- package/src/output-styles/default.md +30 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +8 -6
- package/src/output-styles/simple.md +21 -7
- package/src/rules/agent/00-common.md +6 -3
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +15 -0
- package/src/rules/lead/lead-tool.md +6 -15
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- 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/providers/anthropic-effort.mjs +62 -20
- 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 +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- 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 +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- 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 +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -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/provider-catalog-cache.mjs +80 -0
- 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-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -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/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- 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 +20 -1
- 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 +35 -44
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- 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 -92
- 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 +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- 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 +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- 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 +27 -318
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +247 -701
- package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
- package/src/runtime/channels/lib/event-queue.mjs +78 -13
- package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -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/scheduler.mjs +27 -113
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- 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 +313 -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 +43 -616
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +210 -1948
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -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/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/ko-morph.mjs +195 -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 +24 -842
- 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-store.mjs +69 -12
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +802 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -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 +5 -13
- 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/atomic-file.mjs +26 -1
- package/src/runtime/shared/config.mjs +14 -4
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- 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 +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +119 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -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 +11 -9
- 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 +205 -0
- package/src/session-runtime/quick-search-models.mjs +47 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +352 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +1 -1
- 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 +138 -669
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +64 -14
- 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 +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1059 -8110
- package/src/tui/app/app-format.mjs +213 -0
- package/src/tui/app/channel-pickers.mjs +508 -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 +356 -0
- package/src/tui/app/model-options.mjs +334 -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 +489 -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 +677 -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 +512 -0
- package/src/tui/app/use-transcript-window.mjs +607 -0
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +64 -15
- package/src/tui/components/PromptInput.jsx +33 -102
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +69 -15
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- 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/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +13553 -12384
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -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.mjs +170 -574
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +65 -1
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- 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 -501
- 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 -18
- package/src/workflows/sequential/WORKFLOW.md +16 -18
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +112 -7
- 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/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/channels/lib/holidays.mjs +0 -138
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- 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/input-editing.selection.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/paste-fix.test.mjs +0 -119
- 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
|
@@ -85,7 +85,7 @@ function isStringOrStringArray(v) {
|
|
|
85
85
|
return true;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function hasUnsupportedRipgrepRegex(value) {
|
|
88
|
+
export function hasUnsupportedRipgrepRegex(value) {
|
|
89
89
|
const values = Array.isArray(value) ? value : [value];
|
|
90
90
|
return values.some((item) => {
|
|
91
91
|
if (typeof item !== 'string') return false;
|
|
@@ -99,6 +99,30 @@ function hasMultipleAbsoluteWindowsPaths(value) {
|
|
|
99
99
|
return Array.isArray(matches) && matches.length > 1;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Rescue for hasMultipleAbsoluteWindowsPaths: a single string arg that packs
|
|
103
|
+
// multiple absolute Windows paths together (space/comma/semicolon separated,
|
|
104
|
+
// e.g. "C:\a\b C:\c\d" or "C:\a\b, C:\c\d") is a common model mistake meaning
|
|
105
|
+
// two separate search roots, not one malformed path. Split on each drive-
|
|
106
|
+
// letter boundary and trim separator debris instead of hard-erroring;
|
|
107
|
+
// executeGrepTool's own array fan-out (search-tool.mjs) already knows how to
|
|
108
|
+
// run one grep per path and join the results. Returns null when the value
|
|
109
|
+
// does not cleanly resolve to 2+ non-empty segments.
|
|
110
|
+
function splitMultipleAbsoluteWindowsPaths(value) {
|
|
111
|
+
if (typeof value !== 'string') return null;
|
|
112
|
+
const starts = [];
|
|
113
|
+
const re = /[A-Za-z]:[\\/]/g;
|
|
114
|
+
let m;
|
|
115
|
+
while ((m = re.exec(value)) !== null) starts.push(m.index);
|
|
116
|
+
if (starts.length < 2) return null;
|
|
117
|
+
const parts = [];
|
|
118
|
+
for (let i = 0; i < starts.length; i++) {
|
|
119
|
+
const end = i + 1 < starts.length ? starts[i + 1] : value.length;
|
|
120
|
+
const part = value.slice(starts[i], end).trim().replace(/[,;]+$/, '').trim();
|
|
121
|
+
if (part) parts.push(part);
|
|
122
|
+
}
|
|
123
|
+
return parts.length > 1 ? parts : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
function hasWindowsDrivePath(value) {
|
|
103
127
|
return typeof value === 'string' && /[A-Za-z]:[\\/]/.test(value);
|
|
104
128
|
}
|
|
@@ -124,9 +148,15 @@ function coerceIntegerString(v) {
|
|
|
124
148
|
return n;
|
|
125
149
|
}
|
|
126
150
|
|
|
151
|
+
// Range-shaped strings ("1,40", "1-40", "1:40") are a common model mistake
|
|
152
|
+
// when trying to pass a line range through a single-integer field. They are
|
|
153
|
+
// not coerced (ambiguous which bound is intended); instead the rejection
|
|
154
|
+
// tells the model exactly what to do instead.
|
|
155
|
+
const RANGE_SHAPED_INT_STRING = /^\d+\s*[,\-:]\s*\d+$/;
|
|
156
|
+
|
|
127
157
|
// Mutates a[field] in place when it is a lossless integer string, then
|
|
128
158
|
// validates the (possibly coerced) value against [min, max].
|
|
129
|
-
function checkIntInRange(a, field, min, max) {
|
|
159
|
+
function checkIntInRange(a, field, min, max, opts = {}) {
|
|
130
160
|
let value = a[field];
|
|
131
161
|
if (value === undefined || value === null) return null;
|
|
132
162
|
if (typeof value === 'string') {
|
|
@@ -136,8 +166,20 @@ function checkIntInRange(a, field, min, max) {
|
|
|
136
166
|
a[field] = coerced;
|
|
137
167
|
}
|
|
138
168
|
}
|
|
169
|
+
if (typeof value === 'string' && RANGE_SHAPED_INT_STRING.test(value.trim())) {
|
|
170
|
+
return `Error: builtin arg "${field}" takes a single integer; for a line range use offset+limit as separate integers or "path#L<start>-L<end>" in the path arg (got "${value}")`;
|
|
171
|
+
}
|
|
139
172
|
if (!isFiniteInt(value)) {
|
|
140
|
-
return `Error: builtin arg "${field}" must be a finite integer (got ${describeType(value)})`;
|
|
173
|
+
return `Error: builtin arg "${field}" must be a finite integer (got ${describeType(value)}); expected a single integer like 40`;
|
|
174
|
+
}
|
|
175
|
+
// Soft-cap fields (grep -A/-B/-C/context): silently clamp out-of-range
|
|
176
|
+
// instead of erroring — a model-guessed "300" for a 200-line cap is not
|
|
177
|
+
// a shape violation worth a retry turn, just clamp to the boundary.
|
|
178
|
+
if (opts.clamp) {
|
|
179
|
+
if (value < min) { a[field] = min; return null; }
|
|
180
|
+
if (value > max) { a[field] = max; return null; }
|
|
181
|
+
a[field] = value;
|
|
182
|
+
return null;
|
|
141
183
|
}
|
|
142
184
|
if (value < min) {
|
|
143
185
|
return `Error: builtin arg "${field}" must be >= ${min} (got ${value})`;
|
|
@@ -216,10 +258,11 @@ function guardGrep(a) {
|
|
|
216
258
|
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
217
259
|
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
218
260
|
}
|
|
219
|
-
if (hasOwn(a, k) && hasUnsupportedRipgrepRegex(a[k])) {
|
|
220
|
-
return `Error: grep arg "${k}" uses regex syntax ripgrep does not support here (lookaround/backrefs). Use plain pattern arrays or simpler regex.`;
|
|
221
|
-
}
|
|
222
261
|
}
|
|
262
|
+
// Lookaround/backreference patterns are no longer hard-rejected here:
|
|
263
|
+
// executeGrepTool (search-tool.mjs) detects them at runtime and routes to
|
|
264
|
+
// rg's PCRE2 engine (-P/--pcre2) when the installed rg build supports it,
|
|
265
|
+
// falling back to this same error text only when PCRE2 is unavailable.
|
|
223
266
|
for (const k of globKeys) {
|
|
224
267
|
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
225
268
|
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
@@ -231,7 +274,11 @@ function guardGrep(a) {
|
|
|
231
274
|
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
232
275
|
}
|
|
233
276
|
if (hasOwn(a, k) && hasMultipleAbsoluteWindowsPaths(a[k])) {
|
|
234
|
-
|
|
277
|
+
const split = splitMultipleAbsoluteWindowsPaths(a[k]);
|
|
278
|
+
if (!split) {
|
|
279
|
+
return `Error: grep arg "${k}" contains multiple absolute paths in one string. Use one common parent path plus glob, or separate grep calls.`;
|
|
280
|
+
}
|
|
281
|
+
a[k] = split;
|
|
235
282
|
}
|
|
236
283
|
}
|
|
237
284
|
for (const k of ['head_limit', 'offset']) {
|
|
@@ -239,7 +286,7 @@ function guardGrep(a) {
|
|
|
239
286
|
if (err) return err;
|
|
240
287
|
}
|
|
241
288
|
for (const k of ['-A', '-B', '-C', 'context']) {
|
|
242
|
-
const err = checkIntInRange(a, k, 0, GREP_CONTEXT_MAX);
|
|
289
|
+
const err = checkIntInRange(a, k, 0, GREP_CONTEXT_MAX, { clamp: true });
|
|
243
290
|
if (err) return err;
|
|
244
291
|
}
|
|
245
292
|
// output_mode / mode enum
|
|
@@ -291,13 +338,19 @@ function applyLineContextWindow(obj, labelPrefix) {
|
|
|
291
338
|
if (!hasLine) {
|
|
292
339
|
return `Error: read arg "${labelPrefix}context" requires "${labelPrefix}line" to compute a window`;
|
|
293
340
|
}
|
|
341
|
+
if (typeof obj.line === 'string' && RANGE_SHAPED_INT_STRING.test(obj.line.trim())) {
|
|
342
|
+
return `Error: read arg "${labelPrefix}line" takes a single integer; for a line range use offset+limit as separate integers or "path#L<start>-L<end>" in the path arg (got "${obj.line}")`;
|
|
343
|
+
}
|
|
294
344
|
if (!isFiniteInt(obj.line) || obj.line < 1) {
|
|
295
|
-
return `Error: read arg "${labelPrefix}line" must be a finite integer >= 1 (got ${describeType(obj.line)})`;
|
|
345
|
+
return `Error: read arg "${labelPrefix}line" must be a finite integer >= 1 (got ${describeType(obj.line)}); expected a single integer like 40`;
|
|
296
346
|
}
|
|
297
347
|
let ctx = 0;
|
|
298
348
|
if (hasContext) {
|
|
349
|
+
if (typeof obj.context === 'string' && RANGE_SHAPED_INT_STRING.test(obj.context.trim())) {
|
|
350
|
+
return `Error: read arg "${labelPrefix}context" takes a single integer; for a line range use offset+limit as separate integers or "path#L<start>-L<end>" in the path arg (got "${obj.context}")`;
|
|
351
|
+
}
|
|
299
352
|
if (!isFiniteInt(obj.context) || obj.context < 0) {
|
|
300
|
-
return `Error: read arg "${labelPrefix}context" must be a non-negative finite integer (got ${describeType(obj.context)})`;
|
|
353
|
+
return `Error: read arg "${labelPrefix}context" must be a non-negative finite integer (got ${describeType(obj.context)}); expected a single integer like 40`;
|
|
301
354
|
}
|
|
302
355
|
ctx = obj.context;
|
|
303
356
|
}
|
|
@@ -381,13 +434,6 @@ function guardRead(a) {
|
|
|
381
434
|
return null;
|
|
382
435
|
}
|
|
383
436
|
|
|
384
|
-
function guardDiagnostics(a) {
|
|
385
|
-
if (hasOwn(a, 'path') && a.path !== undefined && a.path !== null && !isNonEmptyString(a.path)) {
|
|
386
|
-
return `Error: diagnostics arg "path" must be a non-empty string (got ${describeType(a.path)})`;
|
|
387
|
-
}
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
437
|
function guardShell(a) {
|
|
392
438
|
if (!hasOwn(a, 'command')) {
|
|
393
439
|
return 'Error: shell requires "command"';
|
|
@@ -458,8 +504,13 @@ function guardList(a) {
|
|
|
458
504
|
}
|
|
459
505
|
|
|
460
506
|
function guardFind(a) {
|
|
461
|
-
|
|
462
|
-
|
|
507
|
+
// query accepts string or string[] (query[] fans out per-lookup downstream).
|
|
508
|
+
const queryOk = hasOwn(a, 'query') && (
|
|
509
|
+
(typeof a.query === 'string' && a.query.trim().length > 0)
|
|
510
|
+
|| (Array.isArray(a.query) && a.query.length > 0 && a.query.every((q) => typeof q === 'string' && q.trim().length > 0))
|
|
511
|
+
);
|
|
512
|
+
if (!queryOk) {
|
|
513
|
+
return `Error: find requires non-empty string (or string[]) "query" (got ${describeType(a.query)})`;
|
|
463
514
|
}
|
|
464
515
|
if (hasOwn(a, 'path') && !isString(a.path)) {
|
|
465
516
|
return `Error: find arg "path" must be a string (got ${describeType(a.path)})`;
|
|
@@ -541,7 +592,6 @@ function guardCodeGraph(a) {
|
|
|
541
592
|
const GUARDS = {
|
|
542
593
|
grep: guardGrep,
|
|
543
594
|
read: guardRead,
|
|
544
|
-
diagnostics: guardDiagnostics,
|
|
545
595
|
shell: guardShell,
|
|
546
596
|
task: guardTask,
|
|
547
597
|
list: guardList,
|
|
@@ -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
|
|
20
|
+
description: 'Read known file path(s). Prefer grep content_with_context or code_graph anchors first. Window with numeric offset+limit. 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,56 +42,27 @@ export const BUILTIN_TOOLS = [
|
|
|
42
42
|
minItems: 1,
|
|
43
43
|
},
|
|
44
44
|
],
|
|
45
|
-
description: 'File path
|
|
45
|
+
description: 'File path. Batch spans via path[] or {path,offset,limit}[] regions. 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',
|
|
78
55
|
annotations: { title: 'Mixdog Shell', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
79
|
-
description: `Run shell to CHANGE state or RUN programs (git/build/test/run). Never to inspect the filesystem — reading, listing, searching,
|
|
56
|
+
description: `Run shell to CHANGE state or RUN programs (git/build/test/run). Never to inspect the filesystem — reading, listing, searching, existence checks use the dedicated tools, never shell. Set shell: powershell or bash. ${TOOL_ASYNC_EXECUTION_CONTRACT}`,
|
|
80
57
|
inputSchema: {
|
|
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 in 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
|
-
|
|
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
|
-
shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell. Windows default is PowerShell; bash means Git Bash/POSIX.' },
|
|
65
|
+
shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell. Windows defaults to PowerShell; bash = Git Bash/POSIX.' },
|
|
95
66
|
},
|
|
96
67
|
required: ['command'],
|
|
97
68
|
},
|
|
@@ -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.
|
|
90
|
+
description: 'Search file contents by text/regex in a known scope. files_with_matches/count for broad anchors, content_with_context for narrow 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 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
|
},
|
|
@@ -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) || '.';
|
|
@@ -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
|
|
@@ -29,6 +29,7 @@ export function buildGrepCacheKey(parts) {
|
|
|
29
29
|
multilineMode,
|
|
30
30
|
fileType,
|
|
31
31
|
onlyMatching,
|
|
32
|
+
pcre2,
|
|
32
33
|
} = parts;
|
|
33
34
|
return [
|
|
34
35
|
'grep',
|
|
@@ -46,6 +47,7 @@ export function buildGrepCacheKey(parts) {
|
|
|
46
47
|
multilineMode ? 'm1' : 'm0',
|
|
47
48
|
onlyMatching ? 'o1' : 'o0',
|
|
48
49
|
Array.isArray(fileType) ? fileType.join('\x01') : (fileType || ''),
|
|
50
|
+
pcre2 ? 'p1' : 'p0',
|
|
49
51
|
].join('|');
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -64,6 +66,7 @@ export function buildGrepRgArgs(parts) {
|
|
|
64
66
|
fileType,
|
|
65
67
|
onlyMatching,
|
|
66
68
|
fixedStrings = false,
|
|
69
|
+
pcre2 = false,
|
|
67
70
|
} = parts;
|
|
68
71
|
// `--hidden`: search dotfiles/dot-dirs (.github, .mixdog) that
|
|
69
72
|
// rg skips by default. The DEFAULT_IGNORE_GLOBS below still exclude .git and
|
|
@@ -84,6 +87,11 @@ export function buildGrepRgArgs(parts) {
|
|
|
84
87
|
}
|
|
85
88
|
if (caseInsensitive) rgArgs.push('-i');
|
|
86
89
|
if (fixedStrings) rgArgs.push('-F');
|
|
90
|
+
// PCRE2 engine: opt-in only when the caller already confirmed (via
|
|
91
|
+
// rgSupportsPcre2() in search-tool.mjs) that the installed rg binary was
|
|
92
|
+
// built with PCRE2 support. Enables lookaround/backreference patterns
|
|
93
|
+
// that the default Rust regex engine rejects outright.
|
|
94
|
+
if (pcre2) rgArgs.push('-P');
|
|
87
95
|
if (multilineMode) rgArgs.push('-U', '--multiline-dotall');
|
|
88
96
|
if (Array.isArray(fileType)) {
|
|
89
97
|
for (const t of fileType) if (t) rgArgs.push('--type', t);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search path diagnostics: ENOENT recovery + path-shape helpers shared by
|
|
3
|
+
* grep/glob in search-tool.mjs. Extracted verbatim so search-tool.mjs stays a
|
|
4
|
+
* thin executor; search-tool.mjs re-exports these for unchanged importers.
|
|
5
|
+
*/
|
|
6
|
+
import { statSync } from 'fs';
|
|
7
|
+
import { isAbsolute, resolve } from 'path';
|
|
8
|
+
import { findBySuffixStrip, findFileByBasename } from './path-diagnostics.mjs';
|
|
9
|
+
import { normalizeOutputPath, resolveAgainstCwd } from './path-utils.mjs';
|
|
10
|
+
|
|
11
|
+
// Deterministic ENOENT recovery: when a grep path does not exist, surface
|
|
12
|
+
// indexed files that share the missing path's basename, turning a guessed or
|
|
13
|
+
// misplaced path (e.g. session/result-compression.mjs vs the real
|
|
14
|
+
// tools/result-compression.mjs) into the actual file in one step. Exact
|
|
15
|
+
// basename only — no stem/token fuzzing — so the hint is high-signal and
|
|
16
|
+
// noise-free. Invariant: every ENOENT runs the same basename lookup; there is
|
|
17
|
+
// no "guessed a lot" branch. Returns '' (appends nothing) when no same-named
|
|
18
|
+
// indexed file exists or the glob child is unavailable.
|
|
19
|
+
export async function _suggestIndexedPaths(missingPath, executeChildBuiltinTool, workDir) {
|
|
20
|
+
if (typeof executeChildBuiltinTool !== 'function') return '';
|
|
21
|
+
const base = String(missingPath).replace(/\\/g, '/').split('/').pop();
|
|
22
|
+
// Skip when there is no usable basename or it carries glob magic (the
|
|
23
|
+
// pattern, not a literal filename, would not map to a real file).
|
|
24
|
+
if (!base || /[*?[\]{}]/.test(base)) return '';
|
|
25
|
+
try {
|
|
26
|
+
const out = await executeChildBuiltinTool('glob', { pattern: `**/${base}`, head_limit: 6 }, workDir);
|
|
27
|
+
if (typeof out !== 'string') return '';
|
|
28
|
+
// Drop ONLY the exact diagnostic forms glob emits: the empty-result line
|
|
29
|
+
// ("(no files found ...", "(no entries after offset ...") and the
|
|
30
|
+
// suffix/warning lines ("... [N more entries]", "... [warning] ...").
|
|
31
|
+
// A broad startsWith('(' / '...' / 'Error') would wrongly drop real
|
|
32
|
+
// paths like ErrorBoundary.mjs or a "(draft)/x.mjs" leading segment.
|
|
33
|
+
const hits = out.split('\n')
|
|
34
|
+
.filter((s) => s && !/^\(no (?:files found|entries\b)/.test(s) && !/^\.\.\. \[/.test(s))
|
|
35
|
+
.map((s) => s.trim())
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.slice(0, 5);
|
|
38
|
+
return hits.length ? `\n[path not found here; same-named indexed file(s): ${hits.join(', ')}]` : '';
|
|
39
|
+
} catch {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Models occasionally glue a quoted segment onto an otherwise-unquoted path
|
|
45
|
+
// (e.g. `C:/Project/mixdog/"src/x.mjs"` — prefix bare, trailing segment
|
|
46
|
+
// wrapped in literal quote chars from a mis-escaped interpolation). But
|
|
47
|
+
// quote chars CAN be legit path content (POSIX `src/Bob's/file.mjs`), so
|
|
48
|
+
// stripping is existence-guarded: keep the original if it stats, only fall
|
|
49
|
+
// back to the stripped variant otherwise. Cost is one statSync and only on
|
|
50
|
+
// the rare quote-containing path.
|
|
51
|
+
export function stripEmbeddedPathQuotes(p) {
|
|
52
|
+
if (typeof p !== 'string' || !p) return p;
|
|
53
|
+
if (!p.includes('"') && !p.includes("'")) return p;
|
|
54
|
+
try { statSync(p); return p; } catch { /* fall through to stripped */ }
|
|
55
|
+
return p.replace(/['"]/g, '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Reuse read's own ENOENT recovery (path-diagnostics findBySuffixStrip /
|
|
59
|
+
// findFileByBasename — the exact fs scans read-single-tool.mjs already runs
|
|
60
|
+
// for its "same filename exists at" hint) so a grep/glob path miss redirects
|
|
61
|
+
// to the real location instead of dead-ending. No new fs scan is added here;
|
|
62
|
+
// this only calls the two helpers read already calls, with a caller-supplied
|
|
63
|
+
// action verb ("Search"/"List") in place of read's "Read". Gated to genuine
|
|
64
|
+
// not-found codes: EACCES/EPERM etc. keep their real failure semantics and
|
|
65
|
+
// must not trigger same-basename guidance or the BFS scan.
|
|
66
|
+
const NOT_FOUND_CODES = new Set(['ENOENT', 'ENOTDIR']);
|
|
67
|
+
export function buildNotFoundHint(workDir, missingPath, actionVerb, errCode = 'ENOENT') {
|
|
68
|
+
if (!NOT_FOUND_CODES.has(String(errCode || 'ENOENT'))) return '';
|
|
69
|
+
const suffixHit = findBySuffixStrip(workDir, missingPath);
|
|
70
|
+
if (suffixHit) {
|
|
71
|
+
return ` Not found at this path; the same filename exists at: "${normalizeOutputPath(suffixHit)}". ${actionVerb} that path directly.`;
|
|
72
|
+
}
|
|
73
|
+
const elsewhere = findFileByBasename(workDir, missingPath);
|
|
74
|
+
if (elsewhere.length) {
|
|
75
|
+
return ` Not found at this path; the same filename exists at: ${elsewhere.map((p) => `"${normalizeOutputPath(p)}"`).join(', ')}. ${actionVerb} that path directly.`;
|
|
76
|
+
}
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function relativePathPrefix(pathPrefix, workDir) {
|
|
81
|
+
if (!workDir) return pathPrefix;
|
|
82
|
+
const cwdFwd = workDir.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
83
|
+
const absFwd = String(pathPrefix || '').replace(/\\/g, '/');
|
|
84
|
+
const haystack = process.platform === 'win32' ? absFwd.toLocaleLowerCase() : absFwd;
|
|
85
|
+
const needle = process.platform === 'win32' ? cwdFwd.toLocaleLowerCase() : cwdFwd;
|
|
86
|
+
if (haystack.startsWith(needle + '/') || haystack === needle) {
|
|
87
|
+
return absFwd.slice(cwdFwd.length + 1) || '.';
|
|
88
|
+
}
|
|
89
|
+
return pathPrefix;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function relativeSearchResultPath(path, workDir) {
|
|
93
|
+
const normalizedWorkDir = normalizeOutputPath(workDir);
|
|
94
|
+
const normalizedAbs = normalizeOutputPath(path);
|
|
95
|
+
if (normalizedAbs.startsWith(normalizedWorkDir + '/') || normalizedAbs.startsWith(normalizedWorkDir + '\\')) {
|
|
96
|
+
return normalizedAbs.slice(normalizedWorkDir.length + 1);
|
|
97
|
+
}
|
|
98
|
+
return normalizedAbs;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function resolveSearchScope(root, workDir) {
|
|
102
|
+
return isAbsolute(root) ? resolve(root) : resolveAgainstCwd(root, workDir);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isUncOrSmbPath(path) {
|
|
106
|
+
if (typeof path !== 'string' || !path) return false;
|
|
107
|
+
return path.startsWith('\\\\') || path.startsWith('//');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function uncRefusalMessage(toolName, original, resolved) {
|
|
111
|
+
const shown = normalizeOutputPath(resolved || original || '');
|
|
112
|
+
return `Error: ${toolName} refuses UNC/SMB path ${JSON.stringify(shown)}; remote share access is blocked to prevent NTLM credential leaks`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function basePathDiagnostic(basePaths, workDir) {
|
|
116
|
+
return basePaths.map((basePath) => {
|
|
117
|
+
const resolved = resolveSearchScope(basePath, workDir);
|
|
118
|
+
try {
|
|
119
|
+
const st = statSync(resolved);
|
|
120
|
+
return `${normalizeOutputPath(basePath)}: ${st.isDirectory() ? 'path exists (dir)' : 'path exists (file)'}`;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return `${normalizeOutputPath(basePath)}: path does not exist (${err?.code || 'ENOENT'})`
|
|
123
|
+
+ buildNotFoundHint(workDir, resolved, 'Search', err?.code);
|
|
124
|
+
}
|
|
125
|
+
}).join('; ');
|
|
126
|
+
}
|