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
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
// V4A hunk locator, in-memory apply, rename sections, and V4A -> unified
|
|
2
|
+
// conversion. Moved verbatim from patch.mjs; anchor/context matching, EOF
|
|
3
|
+
// handling, rename atomicity, and conversion output are all unchanged.
|
|
4
|
+
|
|
5
|
+
import { readFileSync, realpathSync, statSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { unlink } from 'node:fs/promises';
|
|
7
|
+
import { resolve as pathResolve, relative as pathRelative, isAbsolute, dirname as pathDirname } from 'node:path';
|
|
8
|
+
import {
|
|
9
|
+
normalizeInputPath,
|
|
10
|
+
normalizeOutputPath,
|
|
11
|
+
invalidateBuiltinResultCache,
|
|
12
|
+
clearReadSnapshotForPath,
|
|
13
|
+
} from '../builtin.mjs';
|
|
14
|
+
import {
|
|
15
|
+
rawContentCacheGet,
|
|
16
|
+
rawContentCacheSet,
|
|
17
|
+
} from '../builtin/cache-layers.mjs';
|
|
18
|
+
import { atomicWrite } from '../builtin/atomic-write.mjs';
|
|
19
|
+
import { assertPathReachable, assertPathsReachable } from '../builtin/fs-reachability.mjs';
|
|
20
|
+
import { markCodeGraphDirtyPaths } from '../code-graph-state.mjs';
|
|
21
|
+
import { resolveV4AEntryPath, realpathNearestExistingAncestor } from './paths.mjs';
|
|
22
|
+
import {
|
|
23
|
+
isV4AEndOfFileMarker,
|
|
24
|
+
parseUnifiedBareV4APatch,
|
|
25
|
+
parseUnifiedCountedAsV4APatch,
|
|
26
|
+
} from './parsing.mjs';
|
|
27
|
+
import {
|
|
28
|
+
findLineSequence,
|
|
29
|
+
findLineSequenceEscapeEquiv,
|
|
30
|
+
splitTextLinesForPatch,
|
|
31
|
+
firstMeaningfulPatchLine,
|
|
32
|
+
nearestPatchLineHint,
|
|
33
|
+
compactPatchPreviewLine,
|
|
34
|
+
decodeValidUtf8OrNull,
|
|
35
|
+
} from './matcher.mjs';
|
|
36
|
+
|
|
37
|
+
function v4AHunkLineStats(hunk) {
|
|
38
|
+
let oldCount = 0;
|
|
39
|
+
let newCount = 0;
|
|
40
|
+
const oldLines = [];
|
|
41
|
+
const newLines = [];
|
|
42
|
+
for (const raw of hunk.lines || []) {
|
|
43
|
+
if (!raw) continue;
|
|
44
|
+
const tag = raw[0];
|
|
45
|
+
const body = raw.slice(1);
|
|
46
|
+
if (tag === ' ') {
|
|
47
|
+
oldCount++;
|
|
48
|
+
newCount++;
|
|
49
|
+
oldLines.push(body);
|
|
50
|
+
newLines.push(body);
|
|
51
|
+
} else if (tag === '-') {
|
|
52
|
+
oldCount++;
|
|
53
|
+
oldLines.push(body);
|
|
54
|
+
} else if (tag === '+') {
|
|
55
|
+
newCount++;
|
|
56
|
+
newLines.push(body);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { oldCount, newCount, oldLines, newLines };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findAnchorLine(lines, anchors, fromLine) {
|
|
63
|
+
let cursor = Math.max(0, fromLine || 0);
|
|
64
|
+
for (const anchorRaw of anchors || []) {
|
|
65
|
+
const anchor = String(anchorRaw || '').trim();
|
|
66
|
+
if (!anchor) continue;
|
|
67
|
+
const found = lines.findIndex((line, idx) => idx >= cursor && line.includes(anchor));
|
|
68
|
+
if (found === -1) return -1;
|
|
69
|
+
cursor = found + 1;
|
|
70
|
+
}
|
|
71
|
+
return cursor;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatV4AHunkLocator(hunk) {
|
|
75
|
+
return (hunk.anchors || []).filter(Boolean).join(' > ') || '(no anchor)';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatV4AAnchorMissHint(sourceLines, hunk) {
|
|
79
|
+
const anchors = (hunk?.anchors || []).filter(Boolean);
|
|
80
|
+
const nearest = anchors.length > 0
|
|
81
|
+
? anchors.map((anchor) => nearestPatchLineHint(sourceLines, anchor, 0)).find(Boolean)
|
|
82
|
+
: null;
|
|
83
|
+
return anchors.length === 0
|
|
84
|
+
? ' use an existing @@ anchor from the current file or add exact context lines.'
|
|
85
|
+
: ` use an existing @@ anchor from the current file or add exact context lines; no stubs.${nearest ? ` nearest anchor candidate: ${nearest}.` : ''}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatV4AContextMissHint(sourceLines, stats, anchorLine) {
|
|
89
|
+
const expected = firstMeaningfulPatchLine(stats.oldLines);
|
|
90
|
+
const parts = [];
|
|
91
|
+
if (expected) {
|
|
92
|
+
const nearest = nearestPatchLineHint(sourceLines, expected, anchorLine);
|
|
93
|
+
parts.push(`expected first old line: ${JSON.stringify(compactPatchPreviewLine(expected))}`);
|
|
94
|
+
if (nearest) parts.push(nearest);
|
|
95
|
+
const divergence = firstV4ADivergenceHint(sourceLines, stats.oldLines, anchorLine);
|
|
96
|
+
if (divergence) parts.push(divergence);
|
|
97
|
+
}
|
|
98
|
+
parts.push('use exact current context or a broader @@ anchor; no stubs.');
|
|
99
|
+
return ` ${parts.join('; ')}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// When the FIRST old line does exist verbatim in the source, the real
|
|
103
|
+
// mismatch is some later line of the block — name it, with both sides
|
|
104
|
+
// JSON-escaped so invisible differences (real char vs literal \uXXXX
|
|
105
|
+
// escape, tabs, trailing spaces) become visible in the error.
|
|
106
|
+
function firstV4ADivergenceHint(sourceLines, oldLines, anchorLine) {
|
|
107
|
+
const lines = oldLines || [];
|
|
108
|
+
const firstIdx = lines.findIndex((l) => String(l ?? '').trim().length > 0);
|
|
109
|
+
if (firstIdx < 0) return '';
|
|
110
|
+
const first = lines[firstIdx];
|
|
111
|
+
const starts = [];
|
|
112
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
113
|
+
if (sourceLines[i] === first) starts.push(i - firstIdx);
|
|
114
|
+
}
|
|
115
|
+
const pref = Number.isFinite(anchorLine) && anchorLine >= 0 ? anchorLine : 0;
|
|
116
|
+
const start = starts.filter((s) => s >= 0)
|
|
117
|
+
.sort((a, b) => Math.abs(a - pref) - Math.abs(b - pref) || a - b)[0];
|
|
118
|
+
if (start === undefined) return '';
|
|
119
|
+
for (let k = 0; k < lines.length; k++) {
|
|
120
|
+
const exp = lines[k];
|
|
121
|
+
const act = sourceLines[start + k];
|
|
122
|
+
if (act !== exp) {
|
|
123
|
+
const actText = act === undefined ? '(past EOF)' : JSON.stringify(compactPatchPreviewLine(act));
|
|
124
|
+
return `first divergent line: old[${k + 1}] expected ${JSON.stringify(compactPatchPreviewLine(exp))} vs file line ${start + k + 1} actual ${actText}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function joinTextLinesForPatch(lines) {
|
|
131
|
+
const body = (lines || []).join('\n');
|
|
132
|
+
return lines?.hasFinalNewline !== false ? `${body}\n` : body;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function cloneTextLinesForPatch(sourceLines) {
|
|
136
|
+
const lines = [...(sourceLines || [])];
|
|
137
|
+
lines.hasFinalNewline = sourceLines?.hasFinalNewline !== false;
|
|
138
|
+
return lines;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function resolveV4AHunkPosition(sourceLines, hunk, nextSearchLine, options = {}) {
|
|
142
|
+
const stats = v4AHunkLineStats(hunk);
|
|
143
|
+
if (stats.oldCount === 0 && stats.newCount === 0) return { skip: true };
|
|
144
|
+
const fuzzy = options.fuzzy !== false;
|
|
145
|
+
const eof = hunk?.isEndOfFile === true;
|
|
146
|
+
const anchorLine = findAnchorLine(sourceLines, hunk.anchors, nextSearchLine);
|
|
147
|
+
if (anchorLine < 0) {
|
|
148
|
+
const msg = `V4A hunk anchor not found: ${formatV4AHunkLocator(hunk)};${formatV4AAnchorMissHint(sourceLines, hunk)}`;
|
|
149
|
+
return { error: msg };
|
|
150
|
+
}
|
|
151
|
+
let oldLinesPattern = stats.oldLines;
|
|
152
|
+
let newLinesPattern = stats.newLines;
|
|
153
|
+
let oldStartIdx;
|
|
154
|
+
let trimmedTrailing = 0;
|
|
155
|
+
let trimmedTrailingNew = 0;
|
|
156
|
+
if (stats.oldCount === 0) {
|
|
157
|
+
oldStartIdx = eof ? sourceLines.length : anchorLine;
|
|
158
|
+
} else {
|
|
159
|
+
const searchFrom = Math.max(0, anchorLine - 1);
|
|
160
|
+
oldStartIdx = findLineSequence(
|
|
161
|
+
sourceLines,
|
|
162
|
+
oldLinesPattern,
|
|
163
|
+
searchFrom,
|
|
164
|
+
searchFrom,
|
|
165
|
+
{ fuzzy, eof },
|
|
166
|
+
);
|
|
167
|
+
if (eof && oldStartIdx < 0 && oldLinesPattern.length > 0 && oldLinesPattern[oldLinesPattern.length - 1] === '') {
|
|
168
|
+
oldLinesPattern = oldLinesPattern.slice(0, -1);
|
|
169
|
+
trimmedTrailing = 1;
|
|
170
|
+
if (newLinesPattern.length > 0 && newLinesPattern[newLinesPattern.length - 1] === '') {
|
|
171
|
+
newLinesPattern = newLinesPattern.slice(0, -1);
|
|
172
|
+
trimmedTrailingNew = 1;
|
|
173
|
+
}
|
|
174
|
+
oldStartIdx = findLineSequence(
|
|
175
|
+
sourceLines,
|
|
176
|
+
oldLinesPattern,
|
|
177
|
+
searchFrom,
|
|
178
|
+
searchFrom,
|
|
179
|
+
{ fuzzy, eof },
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Escape-equivalence fallback (fuzzy, non-EOF only): accept a window where each old
|
|
184
|
+
// line matches the source verbatim OR as the file's literal `\uXXXX` escape
|
|
185
|
+
// of the patch's real character. On match, remap old/context lines to the
|
|
186
|
+
// file's on-disk form so untouched context stays byte-identical and the
|
|
187
|
+
// escape representation survives the edit.
|
|
188
|
+
if (oldStartIdx < 0 && fuzzy && !eof && oldLinesPattern.length > 0) {
|
|
189
|
+
const from = Math.max(0, anchorLine - 1);
|
|
190
|
+
const alt = findLineSequenceEscapeEquiv(sourceLines, oldLinesPattern, from, from);
|
|
191
|
+
if (alt >= 0) {
|
|
192
|
+
const remapped = new Map();
|
|
193
|
+
let ambiguous = false;
|
|
194
|
+
for (let k = 0; k < oldLinesPattern.length; k++) {
|
|
195
|
+
const pat = oldLinesPattern[k];
|
|
196
|
+
const src = sourceLines[alt + k];
|
|
197
|
+
if (remapped.has(pat) && remapped.get(pat) !== src) { ambiguous = true; break; }
|
|
198
|
+
remapped.set(pat, src);
|
|
199
|
+
}
|
|
200
|
+
if (!ambiguous) {
|
|
201
|
+
newLinesPattern = newLinesPattern.map((l) => remapped.get(l) ?? l);
|
|
202
|
+
oldLinesPattern = oldLinesPattern.map((_, k) => sourceLines[alt + k]);
|
|
203
|
+
oldStartIdx = alt;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (oldStartIdx < 0) {
|
|
208
|
+
const msg = `V4A hunk context not found: ${formatV4AHunkLocator(hunk)};${formatV4AContextMissHint(sourceLines, stats, anchorLine)} Copy context lines verbatim from the latest read output — do not retype them from memory.`;
|
|
209
|
+
return { error: msg };
|
|
210
|
+
}
|
|
211
|
+
const matchLen = stats.oldCount === 0 ? 0 : oldLinesPattern.length;
|
|
212
|
+
return {
|
|
213
|
+
oldStartIdx,
|
|
214
|
+
matchLen,
|
|
215
|
+
newLines: newLinesPattern,
|
|
216
|
+
nextSearchLine: oldStartIdx + Math.max(1, matchLen),
|
|
217
|
+
trimmedTrailing,
|
|
218
|
+
trimmedTrailingNew,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function applyV4AHunksToLines(sourceLines, hunks, options = {}) {
|
|
223
|
+
const lines = cloneTextLinesForPatch(sourceLines);
|
|
224
|
+
const orderedHunks = orderV4AHunksByFilePosition(lines, hunks, options.fuzzy !== false);
|
|
225
|
+
let nextSearchLine = 0;
|
|
226
|
+
const replacements = [];
|
|
227
|
+
for (const hunk of orderedHunks) {
|
|
228
|
+
const loc = resolveV4AHunkPosition(lines, hunk, nextSearchLine, options);
|
|
229
|
+
if (loc.skip) continue;
|
|
230
|
+
if (loc.error) throw new Error(loc.error);
|
|
231
|
+
replacements.push({
|
|
232
|
+
oldStartIdx: loc.oldStartIdx,
|
|
233
|
+
oldLen: loc.matchLen,
|
|
234
|
+
newLines: loc.newLines,
|
|
235
|
+
});
|
|
236
|
+
nextSearchLine = loc.nextSearchLine;
|
|
237
|
+
}
|
|
238
|
+
for (const rep of replacements.reverse()) {
|
|
239
|
+
lines.splice(rep.oldStartIdx, rep.oldLen, ...rep.newLines);
|
|
240
|
+
}
|
|
241
|
+
return lines;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Order-independent hunk ordering for the V4A apply / V4A->unified conversion.
|
|
245
|
+
// Two-phase, semantics-preserving; see the original patch.mjs commentary.
|
|
246
|
+
function orderV4AHunksByFilePosition(sourceLines, hunks, fuzzy) {
|
|
247
|
+
const list = hunks || [];
|
|
248
|
+
if (list.length <= 1) return list;
|
|
249
|
+
let nextSearchLine = 0;
|
|
250
|
+
let inputOrderValid = true;
|
|
251
|
+
for (const hunk of list) {
|
|
252
|
+
const stats = v4AHunkLineStats(hunk);
|
|
253
|
+
if (stats.oldCount === 0 && stats.newCount === 0) continue;
|
|
254
|
+
let loc;
|
|
255
|
+
try { loc = resolveV4AHunkPosition(sourceLines, hunk, nextSearchLine, { fuzzy }); }
|
|
256
|
+
catch { loc = { error: true }; }
|
|
257
|
+
if (!loc || loc.error || loc.skip || typeof loc.nextSearchLine !== 'number') {
|
|
258
|
+
inputOrderValid = false;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
nextSearchLine = loc.nextSearchLine;
|
|
262
|
+
}
|
|
263
|
+
if (inputOrderValid) return list;
|
|
264
|
+
const keyed = [];
|
|
265
|
+
for (let idx = 0; idx < list.length; idx++) {
|
|
266
|
+
const hunk = list[idx];
|
|
267
|
+
const stats = v4AHunkLineStats(hunk);
|
|
268
|
+
if (stats.oldCount === 0 && stats.newCount === 0) {
|
|
269
|
+
keyed.push({ hunk, key: Number.MAX_SAFE_INTEGER, idx });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const seq = [];
|
|
273
|
+
for (const ln of hunk.lines || []) {
|
|
274
|
+
if (isV4AEndOfFileMarker(ln)) continue;
|
|
275
|
+
const p = ln[0];
|
|
276
|
+
if (p === ' ' || p === '-') seq.push(ln.slice(1));
|
|
277
|
+
}
|
|
278
|
+
if (seq.length === 0) return list;
|
|
279
|
+
let pos = -1;
|
|
280
|
+
let count = 0;
|
|
281
|
+
for (let i = 0; i + seq.length <= sourceLines.length; i++) {
|
|
282
|
+
let match = true;
|
|
283
|
+
for (let j = 0; j < seq.length; j++) {
|
|
284
|
+
if (sourceLines[i + j] !== seq[j]) { match = false; break; }
|
|
285
|
+
}
|
|
286
|
+
if (match) {
|
|
287
|
+
if (pos < 0) pos = i;
|
|
288
|
+
count++;
|
|
289
|
+
if (count >= 2) break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (count !== 1) return list;
|
|
293
|
+
keyed.push({ hunk, key: pos, idx });
|
|
294
|
+
}
|
|
295
|
+
keyed.sort((a, b) => (a.key - b.key) || (a.idx - b.idx));
|
|
296
|
+
return keyed.map((e) => e.hunk);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function isV4ARenameSection(section) {
|
|
300
|
+
return section?.kind === 'update' && !!section?.movePath;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function v4aRenamePathKey(absPath) {
|
|
304
|
+
return process.platform === 'win32' ? String(absPath || '').toLowerCase() : String(absPath || '');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function v4aRenamePathInsideRealBase(absPath, realBase) {
|
|
308
|
+
const checkReal = realpathNearestExistingAncestor(absPath);
|
|
309
|
+
const checkRel = pathRelative(realBase, checkReal);
|
|
310
|
+
if (!checkRel || checkRel.startsWith('..') || isAbsolute(checkRel)) return false;
|
|
311
|
+
return !checkRel.split(/[\\/]+/).some((part) => part === '..');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function validateV4ARenameSection(section, basePath, seenDestKeys, realBase) {
|
|
315
|
+
const escapeErr = v4aSectionPathEscapeError(section, basePath);
|
|
316
|
+
if (escapeErr) return escapeErr;
|
|
317
|
+
const escapeDest = v4aSectionPathEscapeError({ path: section.movePath }, basePath);
|
|
318
|
+
if (escapeDest) return escapeDest;
|
|
319
|
+
const srcFull = resolveV4AEntryPath(basePath, section.path);
|
|
320
|
+
const destFull = resolveV4AEntryPath(basePath, section.movePath);
|
|
321
|
+
if (realBase) {
|
|
322
|
+
if (!v4aRenamePathInsideRealBase(srcFull, realBase)) {
|
|
323
|
+
return `apply_patch: ${normalizeOutputPath(section.path)} resolves outside base_path via symlink/junction; refusing V4A rename.`;
|
|
324
|
+
}
|
|
325
|
+
if (!v4aRenamePathInsideRealBase(destFull, realBase)) {
|
|
326
|
+
return `apply_patch: ${normalizeOutputPath(section.movePath)} resolves outside base_path via symlink/junction; refusing V4A rename.`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (v4aRenamePathKey(srcFull) === v4aRenamePathKey(destFull)) {
|
|
330
|
+
return `apply_patch: V4A rename source and destination are the same path (${normalizeOutputPath(section.path)})`;
|
|
331
|
+
}
|
|
332
|
+
const destKey = v4aRenamePathKey(destFull);
|
|
333
|
+
if (seenDestKeys.has(destKey)) {
|
|
334
|
+
return `apply_patch: duplicate V4A rename destination ${normalizeOutputPath(section.movePath)}`;
|
|
335
|
+
}
|
|
336
|
+
seenDestKeys.add(destKey);
|
|
337
|
+
try {
|
|
338
|
+
const st = statSync(srcFull);
|
|
339
|
+
if (!st.isFile()) {
|
|
340
|
+
return `apply_patch: V4A rename source is not a regular file: ${normalizeOutputPath(section.path)}`;
|
|
341
|
+
}
|
|
342
|
+
} catch (err) {
|
|
343
|
+
return `apply_patch: V4A rename source missing or unreadable: ${normalizeOutputPath(section.path)} (${err?.code || err?.message || String(err)})`;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const destSt = statSync(destFull);
|
|
347
|
+
if (destSt.isDirectory()) {
|
|
348
|
+
return `apply_patch: V4A rename destination is a directory: ${normalizeOutputPath(section.movePath)}`;
|
|
349
|
+
}
|
|
350
|
+
if (!destSt.isFile()) {
|
|
351
|
+
return `apply_patch: V4A rename destination is not a regular file: ${normalizeOutputPath(section.movePath)}`;
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
if (err?.code !== 'ENOENT') {
|
|
355
|
+
return `apply_patch: V4A rename destination unreadable: ${normalizeOutputPath(section.movePath)} (${err?.code || err?.message || String(err)})`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (!section.hunks?.length) {
|
|
359
|
+
return `apply_patch: V4A rename for ${normalizeOutputPath(section.path)} has no update hunks`;
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function applyV4ARenameSection(section, basePath, options = {}) {
|
|
365
|
+
const srcFull = resolveV4AEntryPath(basePath, section.path);
|
|
366
|
+
const destFull = resolveV4AEntryPath(basePath, section.movePath);
|
|
367
|
+
const displaySrc = normalizeOutputPath(section.path);
|
|
368
|
+
const displayDest = normalizeOutputPath(section.movePath);
|
|
369
|
+
let sourceLines;
|
|
370
|
+
try {
|
|
371
|
+
sourceLines = v4aConversionSourceLines(srcFull, options.linesCache || new Map());
|
|
372
|
+
} catch (err) {
|
|
373
|
+
throw new Error(`apply_patch: V4A rename source unreadable: ${displaySrc} (${err?.code || err?.message || String(err)})`);
|
|
374
|
+
}
|
|
375
|
+
let updatedLines;
|
|
376
|
+
try {
|
|
377
|
+
updatedLines = applyV4AHunksToLines(sourceLines, section.hunks, options);
|
|
378
|
+
} catch (err) {
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
const newContent = joinTextLinesForPatch(updatedLines);
|
|
382
|
+
if (options.dryRun) {
|
|
383
|
+
return {
|
|
384
|
+
ok: true,
|
|
385
|
+
dryRun: true,
|
|
386
|
+
displayPath: displayDest,
|
|
387
|
+
linesChanged: section.hunks.reduce((n, h) => n + (h.lines?.length || 0), 0),
|
|
388
|
+
srcFull,
|
|
389
|
+
destFull,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const originalContent = readFileSync(srcFull);
|
|
393
|
+
let destBefore = null;
|
|
394
|
+
try {
|
|
395
|
+
destBefore = readFileSync(destFull);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
if (err?.code !== 'ENOENT') throw err;
|
|
398
|
+
}
|
|
399
|
+
mkdirSync(pathDirname(destFull), { recursive: true });
|
|
400
|
+
try {
|
|
401
|
+
await atomicWrite(destFull, newContent, { sessionId: options.readStateScope });
|
|
402
|
+
await unlink(srcFull);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
try {
|
|
405
|
+
if (destBefore === null) {
|
|
406
|
+
try { await unlink(destFull); } catch {}
|
|
407
|
+
} else {
|
|
408
|
+
await atomicWrite(destFull, destBefore, { sessionId: options.readStateScope });
|
|
409
|
+
}
|
|
410
|
+
} catch {}
|
|
411
|
+
try {
|
|
412
|
+
await atomicWrite(srcFull, originalContent, { sessionId: options.readStateScope });
|
|
413
|
+
} catch {}
|
|
414
|
+
throw new Error(`apply_patch: V4A rename failed for ${displaySrc} → ${displayDest} (${err?.message || String(err)})`);
|
|
415
|
+
}
|
|
416
|
+
invalidateBuiltinResultCache([srcFull, destFull]);
|
|
417
|
+
markCodeGraphDirtyPaths([srcFull, destFull]);
|
|
418
|
+
clearReadSnapshotForPath(srcFull, options.readStateScope);
|
|
419
|
+
clearReadSnapshotForPath(destFull, options.readStateScope);
|
|
420
|
+
return {
|
|
421
|
+
ok: true,
|
|
422
|
+
displayPath: displayDest,
|
|
423
|
+
fromPath: displaySrc,
|
|
424
|
+
linesChanged: section.hunks.reduce((n, h) => n + (h.lines?.length || 0), 0),
|
|
425
|
+
srcFull,
|
|
426
|
+
destFull,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function formatV4ARenameSuccessLines(results) {
|
|
431
|
+
return (results || [])
|
|
432
|
+
.filter((r) => r?.ok && !r.skipped)
|
|
433
|
+
.map((r) => `OK ${r.displayPath} (renamed from ${r.fromPath}, ~${r.linesChanged} lines touched, engine=v4a-rename)`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export async function planV4ARenameSections(sections, basePath) {
|
|
437
|
+
const renameSections = (sections || []).filter(isV4ARenameSection);
|
|
438
|
+
const remainingSections = (sections || []).filter((s) => !isV4ARenameSection(s));
|
|
439
|
+
if (renameSections.length === 0) {
|
|
440
|
+
return { renameSections: [], remainingSections };
|
|
441
|
+
}
|
|
442
|
+
if (renameSections.length > 1) {
|
|
443
|
+
throw new Error('apply_patch: only one V4A rename (*** Move to:) per patch is supported; split into separate patches.');
|
|
444
|
+
}
|
|
445
|
+
if (remainingSections.length > 0) {
|
|
446
|
+
throw new Error('apply_patch: V4A rename cannot be combined with other add/update/delete sections in the same patch; apply file edits in a separate patch first.');
|
|
447
|
+
}
|
|
448
|
+
await assertPathReachable(basePath);
|
|
449
|
+
const renameReachPaths = renameSections.flatMap((section) => [
|
|
450
|
+
resolveV4AEntryPath(basePath, section.path),
|
|
451
|
+
resolveV4AEntryPath(basePath, section.movePath),
|
|
452
|
+
]);
|
|
453
|
+
await assertPathsReachable(renameReachPaths);
|
|
454
|
+
let realBase;
|
|
455
|
+
try {
|
|
456
|
+
realBase = realpathSync(pathResolve(basePath));
|
|
457
|
+
} catch (err) {
|
|
458
|
+
throw new Error(`apply_patch: base_path unreadable (${err?.code || err?.message || String(err)}): ${basePath}`);
|
|
459
|
+
}
|
|
460
|
+
const seenDestKeys = new Set();
|
|
461
|
+
for (const section of renameSections) {
|
|
462
|
+
const errText = validateV4ARenameSection(section, basePath, seenDestKeys, realBase);
|
|
463
|
+
if (errText) throw new Error(errText);
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
renameSections,
|
|
467
|
+
remainingSections,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export async function applyV4ARenameSections(renameSections, basePath, options = {}) {
|
|
472
|
+
const linesCache = new Map();
|
|
473
|
+
const results = [];
|
|
474
|
+
for (const section of renameSections || []) {
|
|
475
|
+
results.push(await applyV4ARenameSection(section, basePath, { ...options, linesCache }));
|
|
476
|
+
}
|
|
477
|
+
return results;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function convertUnifiedBareV4AToUnifiedPatch(patchStr, basePath, options = {}) {
|
|
481
|
+
return convertV4ASectionsToUnifiedPatch(parseUnifiedBareV4APatch(patchStr), basePath, options);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function convertUnifiedCountedToUnifiedPatchViaV4A(patchStr, basePath, options = {}) {
|
|
485
|
+
return convertV4ASectionsToUnifiedPatch(parseUnifiedCountedAsV4APatch(patchStr), basePath, options);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Lexical path-escape guard for V4A section paths.
|
|
489
|
+
function v4aSectionPathEscapeError(section, basePath) {
|
|
490
|
+
const raw = section?.path;
|
|
491
|
+
if (!raw) return null;
|
|
492
|
+
const norm = normalizeInputPath(raw);
|
|
493
|
+
const segs = norm.split(/[\\/]+/);
|
|
494
|
+
if (segs.some((part) => part === '..')) {
|
|
495
|
+
return `apply_patch: header ${normalizeOutputPath(raw)} is unsupported (path escapes base_path or contains \`..\`).`;
|
|
496
|
+
}
|
|
497
|
+
if (isAbsolute(norm) || /^[A-Za-z]:[\\/]/.test(norm)) {
|
|
498
|
+
if (!basePath) return `apply_patch: header ${normalizeOutputPath(raw)} is unsupported (path escapes base_path or contains \`..\`).`;
|
|
499
|
+
const absHeader = pathResolve(norm);
|
|
500
|
+
const absBase = pathResolve(basePath);
|
|
501
|
+
const rel = pathRelative(absBase, absHeader);
|
|
502
|
+
if (!rel || rel.startsWith('..') || isAbsolute(rel) || rel.split(/[\\/]+/).some((part) => part === '..')) {
|
|
503
|
+
return `apply_patch: header ${normalizeOutputPath(raw)} is unsupported (path escapes base_path or contains \`..\`).`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function readRawBufForV4AConversion(fullPath) {
|
|
510
|
+
const st = statSync(fullPath);
|
|
511
|
+
const cached = rawContentCacheGet(fullPath, st);
|
|
512
|
+
if (cached) return cached;
|
|
513
|
+
const rawBuf = readFileSync(fullPath);
|
|
514
|
+
const buf = Buffer.isBuffer(rawBuf) ? rawBuf : Buffer.from(rawBuf);
|
|
515
|
+
rawContentCacheSet(fullPath, st, buf);
|
|
516
|
+
return buf;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function v4aConversionSourceLines(fullPath, linesCache) {
|
|
520
|
+
if (linesCache.has(fullPath)) return linesCache.get(fullPath);
|
|
521
|
+
const lines = splitTextLinesForPatch(readRawBufForV4AConversion(fullPath).toString('utf-8'));
|
|
522
|
+
linesCache.set(fullPath, lines);
|
|
523
|
+
return lines;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// options.rejectPartial (default true) — see original patch.mjs commentary.
|
|
527
|
+
export async function convertV4ASectionsToUnifiedPatch(sections, basePath, options = {}) {
|
|
528
|
+
{
|
|
529
|
+
const reachPaths = [];
|
|
530
|
+
const _seenReach = new Set();
|
|
531
|
+
for (const s of (sections || [])) {
|
|
532
|
+
if (!s || s.kind === 'add' || typeof s.path !== 'string' || !s.path) continue;
|
|
533
|
+
const fp = resolveV4AEntryPath(basePath, s.path);
|
|
534
|
+
if (_seenReach.has(fp)) continue;
|
|
535
|
+
_seenReach.add(fp);
|
|
536
|
+
reachPaths.push(fp);
|
|
537
|
+
}
|
|
538
|
+
await assertPathsReachable(reachPaths);
|
|
539
|
+
}
|
|
540
|
+
const rejectPartial = options.rejectPartial !== false;
|
|
541
|
+
const rejectedHunks = Array.isArray(options.rejectedHunks) ? options.rejectedHunks : null;
|
|
542
|
+
const fuzzy = options.fuzzy !== false;
|
|
543
|
+
const out = [];
|
|
544
|
+
const v4aLinesCache = new Map();
|
|
545
|
+
for (const section of sections) {
|
|
546
|
+
const escapeErr = v4aSectionPathEscapeError(section, basePath);
|
|
547
|
+
if (escapeErr) throw new Error(escapeErr);
|
|
548
|
+
const displayPath = section.path.replace(/\\/g, '/');
|
|
549
|
+
if (section.kind === 'add') {
|
|
550
|
+
out.push('--- /dev/null');
|
|
551
|
+
out.push(`+++ b/${displayPath}`);
|
|
552
|
+
out.push(`@@ -0,0 +1,${section.lines.length} @@`);
|
|
553
|
+
for (const line of section.lines) out.push(`+${line}`);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (section.kind === 'delete') {
|
|
557
|
+
const fullPath = resolveV4AEntryPath(basePath, section.path);
|
|
558
|
+
let fileLines = [];
|
|
559
|
+
try {
|
|
560
|
+
const _delRaw = readFileSync(fullPath);
|
|
561
|
+
if (decodeValidUtf8OrNull(_delRaw) !== null) {
|
|
562
|
+
fileLines = v4aConversionSourceLines(fullPath, v4aLinesCache);
|
|
563
|
+
}
|
|
564
|
+
} catch {
|
|
565
|
+
fileLines = [];
|
|
566
|
+
}
|
|
567
|
+
out.push(`--- a/${displayPath}`);
|
|
568
|
+
out.push('+++ /dev/null');
|
|
569
|
+
if (fileLines.length > 0) {
|
|
570
|
+
out.push(`@@ -1,${fileLines.length} +0,0 @@`);
|
|
571
|
+
for (const line of fileLines) out.push(`-${line}`);
|
|
572
|
+
}
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const fullPath = resolveV4AEntryPath(basePath, section.path);
|
|
577
|
+
let sourceLines;
|
|
578
|
+
try {
|
|
579
|
+
sourceLines = v4aConversionSourceLines(fullPath, v4aLinesCache);
|
|
580
|
+
} catch (err) {
|
|
581
|
+
throw new Error(`V4A update target unreadable: ${section.path} (${err?.code || err?.message || String(err)}).`);
|
|
582
|
+
}
|
|
583
|
+
const sectionHunks = [];
|
|
584
|
+
const orderedHunks = orderV4AHunksByFilePosition(sourceLines, section.hunks, fuzzy);
|
|
585
|
+
let nextSearchLine = 0;
|
|
586
|
+
for (const hunk of orderedHunks) {
|
|
587
|
+
const stats = v4AHunkLineStats(hunk);
|
|
588
|
+
if (stats.oldCount === 0 && stats.newCount === 0) continue;
|
|
589
|
+
const loc = resolveV4AHunkPosition(sourceLines, hunk, nextSearchLine, { fuzzy });
|
|
590
|
+
if (loc.skip) continue;
|
|
591
|
+
if (loc.error) {
|
|
592
|
+
const msg = `${loc.error.replace(/^V4A hunk /, `V4A hunk ${section.path}: `)}`;
|
|
593
|
+
if (rejectPartial) throw new Error(msg);
|
|
594
|
+
if (rejectedHunks) rejectedHunks.push({ file: section.path, hunk, reason: msg });
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const oldStart = stats.oldCount === 0 ? loc.oldStartIdx : loc.oldStartIdx + 1;
|
|
598
|
+
const newStart = oldStart;
|
|
599
|
+
const tail = (hunk.anchors || []).filter(Boolean).join(' ');
|
|
600
|
+
const oldCount = stats.oldCount === 0 ? 0 : loc.matchLen;
|
|
601
|
+
const newCount = stats.newCount - (loc.trimmedTrailingNew || 0);
|
|
602
|
+
sectionHunks.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@${tail ? ` ${tail}` : ''}`);
|
|
603
|
+
let dropOldAt = -1;
|
|
604
|
+
let dropNewAt = -1;
|
|
605
|
+
if (loc.trimmedTrailing) {
|
|
606
|
+
for (let i = hunk.lines.length - 1; i >= 0; i--) {
|
|
607
|
+
const ln = hunk.lines[i];
|
|
608
|
+
if (isV4AEndOfFileMarker(ln)) continue;
|
|
609
|
+
const p = ln[0];
|
|
610
|
+
if (dropOldAt < 0 && (p === ' ' || p === '-')) dropOldAt = i;
|
|
611
|
+
if (dropNewAt < 0 && loc.trimmedTrailingNew && (p === ' ' || p === '+')) dropNewAt = i;
|
|
612
|
+
if (dropOldAt >= 0 && (!loc.trimmedTrailingNew || dropNewAt >= 0)) break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
let srcIdx = loc.oldStartIdx;
|
|
616
|
+
const srcEnd = loc.oldStartIdx + loc.matchLen;
|
|
617
|
+
for (let i = 0; i < hunk.lines.length; i++) {
|
|
618
|
+
const line = hunk.lines[i];
|
|
619
|
+
if (isV4AEndOfFileMarker(line)) continue;
|
|
620
|
+
const prefix = line[0];
|
|
621
|
+
if (prefix === ' ' || prefix === '-') {
|
|
622
|
+
if (i === dropOldAt || i === dropNewAt) continue;
|
|
623
|
+
if (srcIdx < srcEnd && srcIdx < sourceLines.length) {
|
|
624
|
+
sectionHunks.push(prefix + sourceLines[srcIdx]);
|
|
625
|
+
} else {
|
|
626
|
+
sectionHunks.push(line);
|
|
627
|
+
}
|
|
628
|
+
srcIdx++;
|
|
629
|
+
} else {
|
|
630
|
+
if (i === dropNewAt) continue;
|
|
631
|
+
sectionHunks.push(line);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
nextSearchLine = loc.nextSearchLine;
|
|
635
|
+
}
|
|
636
|
+
if (sectionHunks.length > 0) {
|
|
637
|
+
out.push(`--- a/${displayPath}`);
|
|
638
|
+
out.push(`+++ b/${displayPath}`);
|
|
639
|
+
for (const line of sectionHunks) out.push(line);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return out.join('\n') + '\n';
|
|
643
|
+
}
|