mixdog 0.9.3 → 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 +7 -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/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/freevar-smoke.mjs +95 -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/provider-toolcall-test.mjs +7 -3
- 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 +23 -63
- 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 +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- 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/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- 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 +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -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 +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- 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/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 +182 -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 +251 -397
- 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/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 +32 -41
- 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 +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -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/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/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 +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/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- 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/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 +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -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/launcher-control.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 +84 -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 +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/settings-api.mjs +319 -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/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 +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- 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 +945 -8094
- 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 +23 -101
- 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 +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 +19652 -18630
- 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 +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- 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 +67 -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 +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -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/external-tool-adapters.test.mjs +0 -162
- 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/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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// Path resolution / entry classification helpers for apply_patch. Moved
|
|
2
|
+
// verbatim from patch.mjs; path/diff semantics unchanged.
|
|
3
|
+
|
|
4
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
5
|
+
import { resolve as pathResolve, relative as pathRelative, isAbsolute, dirname as pathDirname } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
normalizeInputPath,
|
|
8
|
+
normalizeOutputPath,
|
|
9
|
+
resolveAgainstCwd,
|
|
10
|
+
} from '../builtin.mjs';
|
|
11
|
+
import { assertPathReachable, assertPathsReachable } from '../builtin/fs-reachability.mjs';
|
|
12
|
+
import { nativePatchEnabled, nativePatchBinPath } from './native-server.mjs';
|
|
13
|
+
import { DEV_NULL } from './constants.mjs';
|
|
14
|
+
|
|
15
|
+
// Strip the leading `a/` or `b/` prefix that `diff -u` / git emit by
|
|
16
|
+
// default, plus timestamp suffixes (`\t2024-...`) that some tools append
|
|
17
|
+
// to header lines. parsePatch already splits the name from the header
|
|
18
|
+
// so timestamps land in `oldHeader` / `newHeader`, but be defensive.
|
|
19
|
+
export function stripDiffPrefix(name) {
|
|
20
|
+
if (!name) return name;
|
|
21
|
+
if (isAbsolute(name) || /^[A-Za-z]:[\\/]/.test(name)) return name;
|
|
22
|
+
const m = /^[ab]\/(.+)$/.exec(name);
|
|
23
|
+
return m ? m[1] : name;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveEntryPath(basePath, rawName) {
|
|
27
|
+
const stripped = stripDiffPrefix(rawName);
|
|
28
|
+
const norm = normalizeInputPath(stripped);
|
|
29
|
+
return isAbsolute(norm) ? pathResolve(norm) : resolveAgainstCwd(norm, basePath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// V4A section paths are real repository paths and never carry the unified
|
|
33
|
+
// diff `a/`·`b/` prefix, so resolution must NOT apply stripDiffPrefix — a
|
|
34
|
+
// legitimate top-level `a/` or `b/` directory would otherwise be silently
|
|
35
|
+
// rewritten to its child, reading/writing the wrong file.
|
|
36
|
+
export function resolveV4AEntryPath(basePath, rawName) {
|
|
37
|
+
const norm = normalizeInputPath(rawName);
|
|
38
|
+
return isAbsolute(norm) ? pathResolve(norm) : resolveAgainstCwd(norm, basePath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveBasePath(cwd, basePath) {
|
|
42
|
+
if (!basePath) return cwd;
|
|
43
|
+
const norm = normalizeInputPath(basePath);
|
|
44
|
+
return isAbsolute(norm) ? pathResolve(norm) : resolveAgainstCwd(norm, cwd);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Categorise the per-file entry. A unified diff can describe:
|
|
48
|
+
// - modify : both files named, oldFileName exists on disk
|
|
49
|
+
// - create : oldFileName === /dev/null (or file doesn't exist + hunks start at 0)
|
|
50
|
+
// - delete : newFileName === /dev/null
|
|
51
|
+
export function classifyEntry(entry) {
|
|
52
|
+
const oldIsNull = DEV_NULL.test(entry.oldFileName || '');
|
|
53
|
+
const newIsNull = DEV_NULL.test(entry.newFileName || '');
|
|
54
|
+
if (oldIsNull && !newIsNull) return 'create';
|
|
55
|
+
if (!oldIsNull && newIsNull) return 'delete';
|
|
56
|
+
return 'modify';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parsedEntryTargetKey(entry, basePath) {
|
|
60
|
+
if (classifyEntry(entry) !== 'modify') return '';
|
|
61
|
+
const headerName = entry.oldFileName || entry.newFileName;
|
|
62
|
+
if (!headerName || DEV_NULL.test(headerName)) return '';
|
|
63
|
+
const fullPath = resolveEntryPath(basePath, headerName);
|
|
64
|
+
return process.platform === 'win32' ? fullPath.toLowerCase() : fullPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function mergeDuplicateParsedModifyEntries(parsed, basePath) {
|
|
68
|
+
const out = [];
|
|
69
|
+
const byTarget = new Map();
|
|
70
|
+
let changed = false;
|
|
71
|
+
for (const entry of parsed || []) {
|
|
72
|
+
const key = parsedEntryTargetKey(entry, basePath);
|
|
73
|
+
if (!key) {
|
|
74
|
+
out.push(entry);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const existing = byTarget.get(key);
|
|
78
|
+
if (!existing) {
|
|
79
|
+
byTarget.set(key, entry);
|
|
80
|
+
out.push(entry);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
existing.hunks.push(...(entry.hunks || []));
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
return { parsed: out, changed };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function unifiedRange(start, lines) {
|
|
90
|
+
const s = Math.max(0, Number(start) || 0);
|
|
91
|
+
const n = Math.max(0, Number(lines) || 0);
|
|
92
|
+
return `${s},${n}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function renderParsedUnifiedPatch(parsed) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const entry of parsed || []) {
|
|
98
|
+
out.push(`--- ${entry.oldFileName || '/dev/null'}`);
|
|
99
|
+
out.push(`+++ ${entry.newFileName || '/dev/null'}`);
|
|
100
|
+
for (const hunk of entry.hunks || []) {
|
|
101
|
+
const section = hunk.section ? ` ${hunk.section}` : '';
|
|
102
|
+
out.push(`@@ -${unifiedRange(hunk.oldStart, hunk.oldLines)} +${unifiedRange(hunk.newStart, hunk.newLines)} @@${section}`);
|
|
103
|
+
for (const line of hunk.lines || []) out.push(line);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return `${out.join('\n')}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Count how many source lines a hunk consumes vs produces so we can
|
|
110
|
+
// surface a concise `lines_changed` figure without re-diffing.
|
|
111
|
+
export function countHunkChanges(hunks) {
|
|
112
|
+
let added = 0;
|
|
113
|
+
let removed = 0;
|
|
114
|
+
for (const h of hunks || []) {
|
|
115
|
+
for (const line of h.lines || []) {
|
|
116
|
+
if (line.startsWith('+')) added++;
|
|
117
|
+
else if (line.startsWith('-')) removed++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { added, removed };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Header-shape pre-validator (lexical only): a missing header, a
|
|
124
|
+
// `..` segment, or an absolute path that does not resolve inside the
|
|
125
|
+
// basePath returns false. Caller wraps the negative result into a clean
|
|
126
|
+
// throw so unsupported headers surface as a clean error instead of
|
|
127
|
+
// silently degrading to a JS fallback.
|
|
128
|
+
function nativeHeaderSupported(entry, basePath) {
|
|
129
|
+
const kind = classifyEntry(entry);
|
|
130
|
+
const headerName = kind === 'create' ? entry.newFileName : entry.oldFileName;
|
|
131
|
+
if (!headerName || DEV_NULL.test(headerName)) return false;
|
|
132
|
+
// Reject any `..` segment on EITHER oldFileName or newFileName (every
|
|
133
|
+
// non-/dev/null header) — a modify whose newFileName traverses out of
|
|
134
|
+
// base must still be refused even when the resolved path lands inside.
|
|
135
|
+
for (const which of ['oldFileName', 'newFileName']) {
|
|
136
|
+
const raw = entry[which];
|
|
137
|
+
if (!raw || DEV_NULL.test(raw)) continue;
|
|
138
|
+
const segs = normalizeInputPath(stripDiffPrefix(raw)).split(/[\\/]+/);
|
|
139
|
+
if (segs.some((part) => part === '..')) return false;
|
|
140
|
+
}
|
|
141
|
+
const stripped = stripDiffPrefix(headerName);
|
|
142
|
+
const norm = normalizeInputPath(stripped);
|
|
143
|
+
if (isAbsolute(norm) || /^[A-Za-z]:[\\/]/.test(norm)) {
|
|
144
|
+
if (!basePath) return false;
|
|
145
|
+
const absHeader = pathResolve(norm);
|
|
146
|
+
const absBase = pathResolve(basePath);
|
|
147
|
+
const rel = pathRelative(absBase, absHeader);
|
|
148
|
+
if (!rel || rel.startsWith('..') || isAbsolute(rel)) return false;
|
|
149
|
+
if (rel.split(/[\\/]+/).some((part) => part === '..')) return false;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Resolve `absPath` via fs.realpathSync, walking up to the nearest existing
|
|
156
|
+
// ancestor when the leaf does not yet exist (e.g. a create-mode target).
|
|
157
|
+
// Returns the resolved real path, or the lexically-resolved path if no
|
|
158
|
+
// ancestor can be realpath'd.
|
|
159
|
+
export function realpathNearestExistingAncestor(absPath) {
|
|
160
|
+
let cur = pathResolve(absPath);
|
|
161
|
+
while (true) {
|
|
162
|
+
try {
|
|
163
|
+
return realpathSync(cur);
|
|
164
|
+
} catch {
|
|
165
|
+
const parent = pathDirname(cur);
|
|
166
|
+
if (!parent || parent === cur) return cur;
|
|
167
|
+
cur = parent;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Pre-validator (throws): walks every parsed entry and enforces
|
|
173
|
+
// - native engine is enabled
|
|
174
|
+
// - native binary exists on disk
|
|
175
|
+
// - each header is shape-supported (no `..`, no out-of-base absolute)
|
|
176
|
+
// - realpath of each non-/dev/null header stays inside the real basePath
|
|
177
|
+
// (catches symlink/junction escapes that lexical checks miss)
|
|
178
|
+
// - no duplicate target paths in the patch (case-insensitive on win32)
|
|
179
|
+
// Returns the list of normalized entry rows the dispatcher uses to format
|
|
180
|
+
// output, plus the header-rewrite map for absolute-path normalization.
|
|
181
|
+
export async function preValidateNativeBatch(parsed, basePath) {
|
|
182
|
+
if (!nativePatchEnabled()) {
|
|
183
|
+
throw new Error('apply_patch: native engine disabled via MIXDOG_PATCH_NATIVE; set it to "auto" or "1" to apply patches.');
|
|
184
|
+
}
|
|
185
|
+
const binPath = nativePatchBinPath();
|
|
186
|
+
if (!existsSync(binPath)) {
|
|
187
|
+
throw new Error(`apply_patch: native patch binary not found at ${binPath}; build native/mixdog-patch or fetch the prebuilt before invoking apply_patch.`);
|
|
188
|
+
}
|
|
189
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
190
|
+
throw new Error('apply_patch: patch contained no file sections');
|
|
191
|
+
}
|
|
192
|
+
await assertPathReachable(basePath);
|
|
193
|
+
const reachabilityPaths = [];
|
|
194
|
+
for (const entry of parsed) {
|
|
195
|
+
for (const which of ['oldFileName', 'newFileName']) {
|
|
196
|
+
const checkName = entry[which];
|
|
197
|
+
if (!checkName || DEV_NULL.test(checkName)) continue;
|
|
198
|
+
reachabilityPaths.push(resolveEntryPath(basePath, checkName));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
await assertPathsReachable(reachabilityPaths);
|
|
202
|
+
let realBase;
|
|
203
|
+
try {
|
|
204
|
+
realBase = realpathSync(pathResolve(basePath));
|
|
205
|
+
} catch (err) {
|
|
206
|
+
throw new Error(`apply_patch: base_path unreadable (${err?.code || err?.message || String(err)}): ${basePath}`);
|
|
207
|
+
}
|
|
208
|
+
const entries = [];
|
|
209
|
+
const seenPaths = new Set();
|
|
210
|
+
const headerRewrites = [];
|
|
211
|
+
for (const entry of parsed) {
|
|
212
|
+
const kind = classifyEntry(entry);
|
|
213
|
+
const headerName = kind === 'create' ? entry.newFileName : entry.oldFileName;
|
|
214
|
+
if (!nativeHeaderSupported(entry, basePath)) {
|
|
215
|
+
// Two distinct failure modes share nativeHeaderSupported()===false:
|
|
216
|
+
// (a) no usable target path could be parsed from the section header
|
|
217
|
+
// (oldFileName/newFileName empty or /dev/null) — a SHAPE problem,
|
|
218
|
+
// (b) a parsed path that escapes base_path (`..` segment / out-of-base
|
|
219
|
+
// absolute) — a SECURITY refusal.
|
|
220
|
+
// Collapsing both into the "path escapes base_path or contains `..`"
|
|
221
|
+
// message mislabels (a) as (b): the model sees a nonsensical reason for a
|
|
222
|
+
// patch that uses no `..`, cannot self-correct, and re-submits the same
|
|
223
|
+
// patch until the iteration cap. Emit a shape-specific, actionable hint
|
|
224
|
+
// for (a) so the loop breaks on the first failure.
|
|
225
|
+
const hasUsableHeader = headerName && !DEV_NULL.test(headerName);
|
|
226
|
+
if (!hasUsableHeader) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
'apply_patch: a file section header could not be parsed (no target path). '
|
|
229
|
+
+ 'Each section must start with a valid header: `*** Update File: <path>` / '
|
|
230
|
+
+ '`*** Add File: <path>` / `*** Delete File: <path>` (V4A), or a '
|
|
231
|
+
+ '`--- a/<path>` + `+++ b/<path>` pair (unified). Wrap multi-hunk V4A '
|
|
232
|
+
+ 'edits in a `*** Begin Patch` / `*** End Patch` envelope and pass format:"v4a".',
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const display = normalizeOutputPath(stripDiffPrefix(headerName));
|
|
236
|
+
throw new Error(`apply_patch: header ${display} is unsupported (path escapes base_path or contains \`..\`).`);
|
|
237
|
+
}
|
|
238
|
+
if (kind !== 'delete' && !(entry.hunks?.length > 0)) {
|
|
239
|
+
const display = headerName ? normalizeOutputPath(stripDiffPrefix(headerName)) : '(unknown)';
|
|
240
|
+
throw new Error(`apply_patch: entry ${display} has no hunks — patch header malformed (use \`@@ -A,B +C,D @@\` per hunk).`);
|
|
241
|
+
}
|
|
242
|
+
// Realpath each non-/dev/null header; nearest-existing-ancestor handles
|
|
243
|
+
// create-mode leaves that do not yet exist. A symlink/junction whose
|
|
244
|
+
// target escapes basePath fails here even when the lexical check above
|
|
245
|
+
// looked safe.
|
|
246
|
+
for (const which of ['oldFileName', 'newFileName']) {
|
|
247
|
+
const checkName = entry[which];
|
|
248
|
+
if (!checkName || DEV_NULL.test(checkName)) continue;
|
|
249
|
+
const checkFull = resolveEntryPath(basePath, checkName);
|
|
250
|
+
const checkReal = realpathNearestExistingAncestor(checkFull);
|
|
251
|
+
const checkRel = pathRelative(realBase, checkReal);
|
|
252
|
+
if (checkRel.split(/[\\/]+/).some((part) => part === '..') || isAbsolute(checkRel)) {
|
|
253
|
+
const display = normalizeOutputPath(stripDiffPrefix(checkName));
|
|
254
|
+
throw new Error(`apply_patch: ${display} resolves outside base_path via symlink/junction; refusing to apply.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const fullPath = resolveEntryPath(basePath, headerName);
|
|
258
|
+
const pathKey = process.platform === 'win32' ? fullPath.toLowerCase() : fullPath;
|
|
259
|
+
if (seenPaths.has(pathKey)) {
|
|
260
|
+
const display = normalizeOutputPath(stripDiffPrefix(headerName));
|
|
261
|
+
throw new Error(`apply_patch: duplicate target ${display} — patch lists the same path twice.`);
|
|
262
|
+
}
|
|
263
|
+
seenPaths.add(pathKey);
|
|
264
|
+
const displayPath = normalizeOutputPath(stripDiffPrefix(headerName));
|
|
265
|
+
const { added, removed } = countHunkChanges(entry.hunks);
|
|
266
|
+
entries.push({
|
|
267
|
+
kind,
|
|
268
|
+
fullPath,
|
|
269
|
+
displayPath,
|
|
270
|
+
added,
|
|
271
|
+
removed,
|
|
272
|
+
hunks: entry.hunks?.length || 0,
|
|
273
|
+
linesChanged: added + removed,
|
|
274
|
+
});
|
|
275
|
+
// Absolute-form headers must be rewritten to repo-relative before the
|
|
276
|
+
// native server, which joins headers to basePath, sees them.
|
|
277
|
+
for (const which of ['oldFileName', 'newFileName']) {
|
|
278
|
+
const raw = entry[which];
|
|
279
|
+
if (!raw || DEV_NULL.test(raw)) continue;
|
|
280
|
+
const stripped = stripDiffPrefix(raw);
|
|
281
|
+
const norm = normalizeInputPath(stripped);
|
|
282
|
+
if (!isAbsolute(norm) && !/^[A-Za-z]:[\\/]/.test(norm)) continue;
|
|
283
|
+
const rel = pathRelative(pathResolve(basePath), pathResolve(norm)).replace(/\\/g, '/');
|
|
284
|
+
if (!rel || rel.startsWith('..') || isAbsolute(rel)) {
|
|
285
|
+
const display = normalizeOutputPath(stripDiffPrefix(raw));
|
|
286
|
+
throw new Error(`apply_patch: absolute header ${display} does not resolve inside base_path.`);
|
|
287
|
+
}
|
|
288
|
+
headerRewrites.push({ from: raw, to: rel });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { entries, headerRewrites };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Rewrite ONLY the file-section header lines (`--- old`/`+++ new`) that
|
|
295
|
+
// precede each hunk so the native server, which joins headers to
|
|
296
|
+
// basePath, never sees an absolute header. A hunk DELETION line is `-`
|
|
297
|
+
// + content, so a deleted line whose body text is `-- C:/...` renders as
|
|
298
|
+
// `--- C:/...`; track hunk-body state by consuming each `@@ -a,b +c,d @@`
|
|
299
|
+
// header's declared line counts so only lines outside any hunk body are
|
|
300
|
+
// eligible for rewrite.
|
|
301
|
+
export function rewriteHeaderPaths(patchStr, headerRewrites) {
|
|
302
|
+
if (!headerRewrites || headerRewrites.length === 0) return patchStr;
|
|
303
|
+
const lines = patchStr.split('\n');
|
|
304
|
+
let i = 0;
|
|
305
|
+
while (i < lines.length) {
|
|
306
|
+
const line = lines[i];
|
|
307
|
+
if (line.startsWith('@@ ')) {
|
|
308
|
+
const m = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/.exec(line);
|
|
309
|
+
let oldRem = m && m[1] !== undefined ? Number(m[1]) : 1;
|
|
310
|
+
let newRem = m && m[2] !== undefined ? Number(m[2]) : 1;
|
|
311
|
+
i++;
|
|
312
|
+
while (i < lines.length && (oldRem > 0 || newRem > 0)) {
|
|
313
|
+
const body = lines[i];
|
|
314
|
+
const c = body.charAt(0);
|
|
315
|
+
if (c === ' ') { oldRem--; newRem--; }
|
|
316
|
+
else if (c === '-') { oldRem--; }
|
|
317
|
+
else if (c === '+') { newRem--; }
|
|
318
|
+
else if (c === '\\') { /* "" marker */ }
|
|
319
|
+
else break;
|
|
320
|
+
i++;
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (line.startsWith('--- ') || line.startsWith('+++ ')) {
|
|
325
|
+
const prefix = line.slice(0, 4);
|
|
326
|
+
const rest = line.slice(4);
|
|
327
|
+
const tabIdx = rest.indexOf('\t');
|
|
328
|
+
const pathPart = tabIdx === -1 ? rest : rest.slice(0, tabIdx);
|
|
329
|
+
const suffix = tabIdx === -1 ? '' : rest.slice(tabIdx);
|
|
330
|
+
for (const { from, to } of headerRewrites) {
|
|
331
|
+
if (pathPart === from) {
|
|
332
|
+
lines[i] = `${prefix}${to}${suffix}`;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
i++;
|
|
338
|
+
}
|
|
339
|
+
return lines.join('\n');
|
|
340
|
+
}
|