mixdog 0.9.95 → 0.9.97
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/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -26,18 +26,51 @@
|
|
|
26
26
|
import { spawn } from 'node:child_process';
|
|
27
27
|
import { basename } from 'node:path';
|
|
28
28
|
import { createHash, randomBytes } from 'node:crypto';
|
|
29
|
+
import { availableParallelism, freemem } from 'node:os';
|
|
29
30
|
import { killProcessTree, windowsPidHasDescendants } from '../builtin/shell-job-process.mjs';
|
|
30
31
|
|
|
32
|
+
const MB = 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
export function _resolvePwshPoolTarget({
|
|
35
|
+
env = process.env,
|
|
36
|
+
platform = process.platform,
|
|
37
|
+
parallelism = null,
|
|
38
|
+
freeMemoryBytes = null,
|
|
39
|
+
} = {}) {
|
|
40
|
+
const configured = env?.MIXDOG_PWSH_STANDBY_POOL;
|
|
41
|
+
if (configured != null && configured !== '') {
|
|
42
|
+
const n = Math.floor(Number(configured));
|
|
43
|
+
if (Number.isFinite(n) && n >= 0) return Math.min(8, n);
|
|
44
|
+
}
|
|
45
|
+
if (platform !== 'win32') return 0;
|
|
46
|
+
// Tools now execute in the machine-global daemon. Keep client/test
|
|
47
|
+
// processes at zero by default, but let that ONE host maintain a short-lived
|
|
48
|
+
// warm burst sized to the machine instead of making every shell call pay the
|
|
49
|
+
// ~300ms pwsh startup cost.
|
|
50
|
+
if (env?.MIXDOG_DAEMON_HOST !== '1') return 0;
|
|
51
|
+
let cores = Number(parallelism);
|
|
52
|
+
if (!Number.isFinite(cores) || cores < 1) {
|
|
53
|
+
try { cores = availableParallelism(); } catch { cores = 1; }
|
|
54
|
+
}
|
|
55
|
+
let free = freeMemoryBytes == null ? Number.NaN : Number(freeMemoryBytes);
|
|
56
|
+
if (!Number.isFinite(free) || free < 0) {
|
|
57
|
+
try { free = freemem(); } catch { free = 0; }
|
|
58
|
+
}
|
|
59
|
+
const cpuTarget = Math.max(1, Math.min(4, Math.floor(cores) - 1 || 1));
|
|
60
|
+
if (free < 768 * MB) return 0;
|
|
61
|
+
if (free < 1536 * MB) return Math.min(1, cpuTarget);
|
|
62
|
+
if (free < 3072 * MB) return Math.min(2, cpuTarget);
|
|
63
|
+
return cpuTarget;
|
|
64
|
+
}
|
|
65
|
+
|
|
31
66
|
function _poolTarget() {
|
|
32
|
-
|
|
33
|
-
if (Number.isFinite(n) && n >= 0) return Math.min(8, n);
|
|
34
|
-
return 2;
|
|
67
|
+
return _resolvePwshPoolTarget();
|
|
35
68
|
}
|
|
36
69
|
const POOL_TARGET = _poolTarget();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
70
|
+
const _envIdleMs = Math.floor(Number(process.env.MIXDOG_PWSH_STANDBY_IDLE_MS));
|
|
71
|
+
const STANDBY_IDLE_MS = Number.isFinite(_envIdleMs) && _envIdleMs >= 0
|
|
72
|
+
? _envIdleMs
|
|
73
|
+
: 60_000;
|
|
41
74
|
const EXPECTED_ARGS = Object.freeze(['-NoLogo', '-NoProfile', '-NonInteractive', '-Command']);
|
|
42
75
|
|
|
43
76
|
// Marker-loop bootstrap: the standby serves MANY commands over its lifetime.
|
|
@@ -132,6 +165,7 @@ function _standbyBootScript(nonce) {
|
|
|
132
165
|
}
|
|
133
166
|
|
|
134
167
|
const _idle = [];
|
|
168
|
+
const _taken = new Set();
|
|
135
169
|
let _envSig = null;
|
|
136
170
|
let _exitHookInstalled = false;
|
|
137
171
|
|
|
@@ -144,6 +178,10 @@ function _envSignature(env) {
|
|
|
144
178
|
}
|
|
145
179
|
|
|
146
180
|
function _killEntry(entry) {
|
|
181
|
+
if (entry.idleTimer) {
|
|
182
|
+
clearTimeout(entry.idleTimer);
|
|
183
|
+
entry.idleTimer = null;
|
|
184
|
+
}
|
|
147
185
|
try { entry.child.kill(); } catch { /* already down */ }
|
|
148
186
|
}
|
|
149
187
|
|
|
@@ -152,6 +190,19 @@ function _discardIdle(entry) {
|
|
|
152
190
|
if (i !== -1) _idle.splice(i, 1);
|
|
153
191
|
}
|
|
154
192
|
|
|
193
|
+
function _armIdleExpiry(entry) {
|
|
194
|
+
if (entry.idleTimer) clearTimeout(entry.idleTimer);
|
|
195
|
+
entry.idleTimer = null;
|
|
196
|
+
if (STANDBY_IDLE_MS <= 0) return;
|
|
197
|
+
entry.idleTimer = setTimeout(() => {
|
|
198
|
+
entry.idleTimer = null;
|
|
199
|
+
if (entry.taken) return;
|
|
200
|
+
_discardIdle(entry);
|
|
201
|
+
_killEntry(entry);
|
|
202
|
+
}, STANDBY_IDLE_MS);
|
|
203
|
+
entry.idleTimer.unref?.();
|
|
204
|
+
}
|
|
205
|
+
|
|
155
206
|
function _installExitHook() {
|
|
156
207
|
if (_exitHookInstalled) return;
|
|
157
208
|
_exitHookInstalled = true;
|
|
@@ -173,7 +224,7 @@ function _spawnStandby(shell, env, sig) {
|
|
|
173
224
|
detached: false,
|
|
174
225
|
});
|
|
175
226
|
} catch { return; }
|
|
176
|
-
const entry = { child, sig, nonce,
|
|
227
|
+
const entry = { child, sig, nonce, taken: false, consumed: false, idleTimer: null };
|
|
177
228
|
child.on('error', () => { if (!entry.taken) { _discardIdle(entry); _killEntry(entry); } });
|
|
178
229
|
child.on('exit', () => { if (!entry.taken) _discardIdle(entry); });
|
|
179
230
|
// A dead standby's pending stdin write must never crash the daemon.
|
|
@@ -187,10 +238,25 @@ function _spawnStandby(shell, env, sig) {
|
|
|
187
238
|
child.stderr?.unref?.();
|
|
188
239
|
} catch { /* best-effort */ }
|
|
189
240
|
_idle.push(entry);
|
|
241
|
+
_armIdleExpiry(entry);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function _poolSizeForSignature(sig) {
|
|
245
|
+
let count = _idle.reduce((total, entry) => total + (entry.sig === sig ? 1 : 0), 0);
|
|
246
|
+
for (const entry of _taken) {
|
|
247
|
+
if (entry.sig === sig) count += 1;
|
|
248
|
+
}
|
|
249
|
+
return count;
|
|
190
250
|
}
|
|
191
251
|
|
|
192
252
|
function _topUp(shell, env, sig) {
|
|
193
|
-
while (
|
|
253
|
+
while (_poolSizeForSignature(sig) < POOL_TARGET) _spawnStandby(shell, env, sig);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function _releaseTaken(entry) {
|
|
257
|
+
if (!_taken.delete(entry)) return false;
|
|
258
|
+
entry.taken = false;
|
|
259
|
+
return true;
|
|
194
260
|
}
|
|
195
261
|
|
|
196
262
|
/**
|
|
@@ -215,11 +281,9 @@ export function takePwshStandby({ shell, shellArgs, env }) {
|
|
|
215
281
|
_envSig = sig;
|
|
216
282
|
}
|
|
217
283
|
_topUp(shell, env, sig);
|
|
218
|
-
const now = Date.now();
|
|
219
284
|
let entry = null;
|
|
220
285
|
for (let i = 0; i < _idle.length; i += 1) {
|
|
221
286
|
const cand = _idle[i];
|
|
222
|
-
if (now - cand.spawnedAt < STANDBY_READY_AGE_MS) continue;
|
|
223
287
|
if (cand.child.exitCode !== null || cand.child.signalCode !== null) continue;
|
|
224
288
|
if (!cand.child.stdin || cand.child.stdin.destroyed) continue;
|
|
225
289
|
_idle.splice(i, 1);
|
|
@@ -229,6 +293,11 @@ export function takePwshStandby({ shell, shellArgs, env }) {
|
|
|
229
293
|
if (!entry) return null;
|
|
230
294
|
entry.taken = true;
|
|
231
295
|
entry.consumed = false;
|
|
296
|
+
_taken.add(entry);
|
|
297
|
+
if (entry.idleTimer) {
|
|
298
|
+
clearTimeout(entry.idleTimer);
|
|
299
|
+
entry.idleTimer = null;
|
|
300
|
+
}
|
|
232
301
|
try {
|
|
233
302
|
entry.child.ref();
|
|
234
303
|
entry.child.stdin?.ref?.();
|
|
@@ -271,6 +340,7 @@ export function takePwshStandby({ shell, shellArgs, env }) {
|
|
|
271
340
|
// job's close-wired exit file then records the REAL command exit.
|
|
272
341
|
endStdin() {
|
|
273
342
|
entry.consumed = true;
|
|
343
|
+
_releaseTaken(entry);
|
|
274
344
|
try { entry.child.stdin.end(); } catch { /* already gone */ }
|
|
275
345
|
},
|
|
276
346
|
// Post-command recycle: return the standby to the pool ONLY when the
|
|
@@ -281,6 +351,7 @@ export function takePwshStandby({ shell, shellArgs, env }) {
|
|
|
281
351
|
async recycle() {
|
|
282
352
|
if (entry.consumed) return false;
|
|
283
353
|
entry.consumed = true;
|
|
354
|
+
_releaseTaken(entry);
|
|
284
355
|
const c = entry.child;
|
|
285
356
|
const alive = () => c.exitCode === null && c.signalCode === null;
|
|
286
357
|
let dirty = true;
|
|
@@ -309,9 +380,7 @@ export function takePwshStandby({ shell, shellArgs, env }) {
|
|
|
309
380
|
process.stderr.write(`[pwsh-standby] recycle drained ${_staleBytes}B stale output (pid=${c.pid}) — late flush escaped the previous command's capture\n`);
|
|
310
381
|
} catch { /* diagnostics only */ }
|
|
311
382
|
}
|
|
312
|
-
entry.taken = false;
|
|
313
383
|
entry.consumed = false;
|
|
314
|
-
entry.spawnedAt = Date.now() - STANDBY_READY_AGE_MS;
|
|
315
384
|
try {
|
|
316
385
|
c.unref();
|
|
317
386
|
c.stdin?.unref?.();
|
|
@@ -319,6 +388,7 @@ export function takePwshStandby({ shell, shellArgs, env }) {
|
|
|
319
388
|
c.stderr?.unref?.();
|
|
320
389
|
} catch { /* best-effort */ }
|
|
321
390
|
_idle.push(entry);
|
|
391
|
+
_armIdleExpiry(entry);
|
|
322
392
|
return true;
|
|
323
393
|
},
|
|
324
394
|
};
|
|
@@ -334,7 +334,10 @@ function rustTrim(s) {
|
|
|
334
334
|
return s.replace(RUST_TRIM_RE, '');
|
|
335
335
|
}
|
|
336
336
|
export function normalizeTypographic(s) {
|
|
337
|
-
|
|
337
|
+
// NFC first: a decomposed sequence (Hangul jamo, or "e" + combining acute
|
|
338
|
+
// from a file authored on another platform) is visually identical to its
|
|
339
|
+
// composed form but byte-different, which failed an otherwise exact context.
|
|
340
|
+
return rustTrim(String(s ?? '').normalize('NFC'))
|
|
338
341
|
.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, '-')
|
|
339
342
|
.replace(/[\u2018\u2019\u201A\u201B]/g, "'")
|
|
340
343
|
.replace(/[\u201C\u201D\u201E\u201F]/g, '"')
|
|
@@ -405,6 +408,41 @@ export function longestCommonSubstringLen(a, b, cap = 4000) {
|
|
|
405
408
|
return best;
|
|
406
409
|
}
|
|
407
410
|
|
|
411
|
+
// Total character drift tolerated per context line by the edit-distance tier.
|
|
412
|
+
// Deliberately tiny: 3 context lines buy exactly 1 character of slack, so the
|
|
413
|
+
// tier rescues a typo, never a genuinely different block.
|
|
414
|
+
export const EDIT_DISTANCE_ALLOWANCE_PER_LINE = 0.34;
|
|
415
|
+
|
|
416
|
+
// Levenshtein distance with an early exit: returns the exact distance while it
|
|
417
|
+
// stays within `max`, otherwise `max + 1` as soon as that is certain. The
|
|
418
|
+
// length pre-check and the per-row minimum keep a whole-file scan cheap.
|
|
419
|
+
export function boundedEditDistance(a, b, max) {
|
|
420
|
+
const s = String(a ?? '');
|
|
421
|
+
const t = String(b ?? '');
|
|
422
|
+
if (s === t) return 0;
|
|
423
|
+
if (!(max > 0)) return 1;
|
|
424
|
+
if (Math.abs(s.length - t.length) > max) return max + 1;
|
|
425
|
+
const n = s.length;
|
|
426
|
+
const m = t.length;
|
|
427
|
+
let prev = new Int32Array(m + 1);
|
|
428
|
+
let curr = new Int32Array(m + 1);
|
|
429
|
+
for (let j = 0; j <= m; j++) prev[j] = j;
|
|
430
|
+
for (let i = 1; i <= n; i++) {
|
|
431
|
+
curr[0] = i;
|
|
432
|
+
let rowMin = curr[0];
|
|
433
|
+
const ci = s.charCodeAt(i - 1);
|
|
434
|
+
for (let j = 1; j <= m; j++) {
|
|
435
|
+
const cost = ci === t.charCodeAt(j - 1) ? 0 : 1;
|
|
436
|
+
const value = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
437
|
+
curr[j] = value;
|
|
438
|
+
if (value < rowMin) rowMin = value;
|
|
439
|
+
}
|
|
440
|
+
if (rowMin > max) return max + 1;
|
|
441
|
+
const tmp = prev; prev = curr; curr = tmp;
|
|
442
|
+
}
|
|
443
|
+
return prev[m] > max ? max + 1 : prev[m];
|
|
444
|
+
}
|
|
445
|
+
|
|
408
446
|
export function findLineSequence(lines, needle, fromLine, preferredLine = 0, options = {}) {
|
|
409
447
|
if (!Array.isArray(needle) || needle.length === 0) return Math.max(0, preferredLine || fromLine || 0);
|
|
410
448
|
const eof = options?.eof === true;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// mode gating, binary resolution, prewarm/idle lifecycle, and the char-indexed
|
|
3
3
|
// EDIT client. Split out of patch.mjs; behavior is identical.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// Executor: NATIVE-ONLY. Every supported apply/edit case is dispatched to the
|
|
6
6
|
// mixdog-patch Rust engine via the persistent stdio server. There is NO JS
|
|
7
7
|
// apply fallback: unsupported / unsafe input returns a clean Error string.
|
|
8
8
|
|
|
@@ -144,8 +144,8 @@ function restorePatchRollbackState(snapshots, readStateScope) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Apply one "wave" (a set of unique-target parsed entries) via the native
|
|
147
|
-
// (+ JS out-of-base) split. Returns {
|
|
148
|
-
// {
|
|
147
|
+
// (+ JS out-of-base) split. Returns { executor, text } on success or
|
|
148
|
+
// { executor, error } so the caller decides whether earlier waves already
|
|
149
149
|
// committed to disk. Extracted verbatim from the inline applyWave closure so
|
|
150
150
|
// both the default wave loop and sequence mode share identical apply
|
|
151
151
|
// semantics.
|
|
@@ -156,7 +156,7 @@ async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewri
|
|
|
156
156
|
const parsedInside = (wparsed || []).filter(
|
|
157
157
|
(entry) => !isResolvedPathOutsideBase(parsedEntryResolvedPath(entry, basePath), basePath),
|
|
158
158
|
);
|
|
159
|
-
const
|
|
159
|
+
const executor = outsideEntries.length > 0
|
|
160
160
|
? (insideEntries.length > 0 ? 'native+js-patch' : 'js-patch')
|
|
161
161
|
: 'native-patch';
|
|
162
162
|
const resultParts = [];
|
|
@@ -173,7 +173,7 @@ async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewri
|
|
|
173
173
|
signal: abortSignal,
|
|
174
174
|
parsed: parsedInside,
|
|
175
175
|
});
|
|
176
|
-
if (isPatchErrorText(nativeResult)) return {
|
|
176
|
+
if (isPatchErrorText(nativeResult)) return { executor, error: nativeResult };
|
|
177
177
|
resultParts.push(nativeResult);
|
|
178
178
|
}
|
|
179
179
|
if (outsideEntries.length > 0) {
|
|
@@ -187,10 +187,10 @@ async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewri
|
|
|
187
187
|
fuzzy,
|
|
188
188
|
readStateScope,
|
|
189
189
|
});
|
|
190
|
-
if (isPatchErrorText(jsResult)) return {
|
|
190
|
+
if (isPatchErrorText(jsResult)) return { executor, error: jsResult };
|
|
191
191
|
resultParts.push(jsResult);
|
|
192
192
|
}
|
|
193
|
-
return {
|
|
193
|
+
return { executor, text: resultParts.join('\n') };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// Default ordered section mode. Apply each file section in listed order,
|
|
@@ -384,7 +384,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
384
384
|
const skipped = [];
|
|
385
385
|
let failed = null;
|
|
386
386
|
let failedIndex = -1;
|
|
387
|
-
let
|
|
387
|
+
let executor = 'native-patch';
|
|
388
388
|
for (let i = 0; i < units.length; i++) {
|
|
389
389
|
const unit = units[i];
|
|
390
390
|
if (failed) { skipped.push(unit.displayPath); continue; }
|
|
@@ -421,7 +421,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
423
|
const res = await applyParsedWave(wave, basePath, waveOpts);
|
|
424
|
-
|
|
424
|
+
executor = res.executor;
|
|
425
425
|
if (res.error) {
|
|
426
426
|
failed = { displayPath: unit.displayPath, error: res.error };
|
|
427
427
|
failedIndex = i;
|
|
@@ -459,7 +459,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
459
459
|
}
|
|
460
460
|
const head = `apply_patch: ${verb} ${units.length} section(s)`;
|
|
461
461
|
const body = (appliedTexts ? `${head}\n${appliedTexts}` : head) + dryNote + rejectedTail;
|
|
462
|
-
return wrapPatchMutationOutput(body, mutationPlan, {
|
|
462
|
+
return wrapPatchMutationOutput(body, mutationPlan, { executor });
|
|
463
463
|
}
|
|
464
464
|
const failMsg = failed.error.replace(/^Error:\s*/, '');
|
|
465
465
|
const rollbackErrors = (!dryRun && rollbackOnFailure)
|
|
@@ -499,7 +499,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
499
499
|
if (skipped.length > 0) {
|
|
500
500
|
lines.push(`--- skipped (not attempted): ${skipped.join(', ')} ---`);
|
|
501
501
|
}
|
|
502
|
-
return wrapPatchMutationOutput(lines.join('\n') + dryNote + rejectedTail, mutationPlan, {
|
|
502
|
+
return wrapPatchMutationOutput(lines.join('\n') + dryNote + rejectedTail, mutationPlan, { executor });
|
|
503
503
|
}));
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -703,7 +703,7 @@ export function appendPostPatchExcerpts(outputText, patchStr, requestedFormat, b
|
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
-
const APPLY_PATCH_SCHEMA_KEYS = new Set(['patch', 'format', 'base_path', 'dry_run', 'reject_partial', 'fuzzy', 'sequence', 'mode']);
|
|
706
|
+
const APPLY_PATCH_SCHEMA_KEYS = new Set(['patch', 'format', 'base_path', 'root', 'dry_run', 'reject_partial', 'fuzzy', 'sequence', 'mode']);
|
|
707
707
|
function salvageShatteredV4APatchArgs(args) {
|
|
708
708
|
if (!args || typeof args !== 'object') return args;
|
|
709
709
|
const rawPatch = typeof args.patch === 'string' ? args.patch : '';
|
|
@@ -723,9 +723,41 @@ function salvageShatteredV4APatchArgs(args) {
|
|
|
723
723
|
return cleaned;
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
+
function extractInlinePatchRoot(patchText) {
|
|
727
|
+
const text = String(patchText || '');
|
|
728
|
+
const match = /^(\*\*\* Begin Patch\r?\n)\*\*\* Root:[ \t]*(.*?)\r?\n/.exec(text);
|
|
729
|
+
if (!match) return { patch: text, root: null };
|
|
730
|
+
const root = String(match[2] || '').trim();
|
|
731
|
+
if (!root) throw new Error('apply_patch: "*** Root:" requires a containing directory');
|
|
732
|
+
return {
|
|
733
|
+
patch: `${match[1]}${text.slice(match[0].length)}`,
|
|
734
|
+
root,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function isFilesystemRootSpecifier(value, cwd) {
|
|
739
|
+
const raw = String(value || '').trim();
|
|
740
|
+
if (!raw) return false;
|
|
741
|
+
// Keep Windows semantics testable on every host and reject the ambiguous
|
|
742
|
+
// drive-relative spelling (`C:`) alongside an actual drive root.
|
|
743
|
+
if (/^[A-Za-z]:(?:[\\/]?)$/.test(raw)) return true;
|
|
744
|
+
const resolved = resolveBasePath(cwd, raw);
|
|
745
|
+
return pathDirname(resolved) === resolved;
|
|
746
|
+
}
|
|
747
|
+
|
|
726
748
|
async function apply_patch(args, cwd, options = {}) {
|
|
727
749
|
args = salvageShatteredV4APatchArgs(args);
|
|
728
|
-
|
|
750
|
+
let patchStr = salvageV4AOpening((typeof args?.patch === 'string' ? args.patch : '').replace(/^\uFEFF/, ''));
|
|
751
|
+
const inlineRoot = extractInlinePatchRoot(patchStr);
|
|
752
|
+
patchStr = inlineRoot.patch;
|
|
753
|
+
if (inlineRoot.root) {
|
|
754
|
+
const argumentRoot = typeof args?.root === 'string' ? args.root.trim() : '';
|
|
755
|
+
if (argumentRoot
|
|
756
|
+
&& pathResolve(resolveBasePath(cwd, argumentRoot)) !== pathResolve(resolveBasePath(cwd, inlineRoot.root))) {
|
|
757
|
+
throw new Error('apply_patch: root argument conflicts with the "*** Root:" directive');
|
|
758
|
+
}
|
|
759
|
+
args = { ...(args || {}), root: inlineRoot.root };
|
|
760
|
+
}
|
|
729
761
|
if (!patchStr.trim()) {
|
|
730
762
|
throw new Error('apply_patch: "patch" is required (unified diff or V4A patch string)');
|
|
731
763
|
}
|
|
@@ -755,6 +787,41 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
755
787
|
} catch (err) {
|
|
756
788
|
return `Error: ${err?.message || String(err)}`;
|
|
757
789
|
}
|
|
790
|
+
// Write-root gate: a patch may only touch paths inside a DECLARED root —
|
|
791
|
+
// the session directory by default, or `root`/`base_path` when the caller
|
|
792
|
+
// names one. Anything outside is refused before a single byte is written, so
|
|
793
|
+
// a mis-scoped working directory can never silently rewrite another tree.
|
|
794
|
+
// Editing outside stays possible, but only as a deliberate act: name the
|
|
795
|
+
// root that contains those targets in the same call.
|
|
796
|
+
const explicitRoot = typeof args?.root === 'string' && args.root.trim() ? args.root.trim() : null;
|
|
797
|
+
if (explicitRoot && isFilesystemRootSpecifier(explicitRoot, cwd)) {
|
|
798
|
+
throw new Error(`apply_patch: refusing filesystem root as write root: ${normalizeOutputPath(explicitRoot)}`);
|
|
799
|
+
}
|
|
800
|
+
const writeRoot = resolveBasePath(cwd, explicitRoot ?? args?.base_path ?? null);
|
|
801
|
+
if (explicitRoot) {
|
|
802
|
+
try {
|
|
803
|
+
await assertPathReachable(writeRoot);
|
|
804
|
+
if (!statSync(writeRoot).isDirectory()) {
|
|
805
|
+
throw new Error(`not a directory — ${normalizeOutputPath(writeRoot)}`);
|
|
806
|
+
}
|
|
807
|
+
} catch (err) {
|
|
808
|
+
throw new Error(`apply_patch: invalid write root ${normalizeOutputPath(writeRoot)}: ${err?.message || String(err)}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const outsideTargets = [];
|
|
812
|
+
for (const rel of patchTargetPaths(patchStr, basePath)) {
|
|
813
|
+
const abs = isAbsolute(rel) ? pathResolve(rel) : pathResolve(basePath, rel);
|
|
814
|
+
if (isResolvedPathOutsideBase(abs, writeRoot)) outsideTargets.push(normalizeOutputPath(abs));
|
|
815
|
+
}
|
|
816
|
+
if (outsideTargets.length > 0) {
|
|
817
|
+
const shown = [...new Set(outsideTargets)];
|
|
818
|
+
const head = shown.slice(0, 3).join(', ');
|
|
819
|
+
const more = shown.length > 3 ? ` (+${shown.length - 3} more)` : '';
|
|
820
|
+
throw new Error(
|
|
821
|
+
`apply_patch: ${shown.length} target(s) fall outside the write root ${normalizeOutputPath(writeRoot)}: ${head}${more}. `
|
|
822
|
+
+ 'Check the paths first; if intended, set root (JSON) or add "*** Root: <containing directory>" after "*** Begin Patch" (freeform).',
|
|
823
|
+
);
|
|
824
|
+
}
|
|
758
825
|
const rejectPartial = args?.reject_partial !== false;
|
|
759
826
|
const dryRun = args?.dry_run === true;
|
|
760
827
|
const fuzzy = args?.fuzzy !== false;
|
|
@@ -896,17 +963,17 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
896
963
|
if (v4aRenameOnly) {
|
|
897
964
|
const lines = formatV4ARenameSuccessLines(v4aRenameResults);
|
|
898
965
|
if (lines.length === 0) return 'Error: patch contained no applicable file sections';
|
|
899
|
-
return wrapPatchMutationOutput(`${lines.join('\n')}\n`, mutationPlan, {
|
|
966
|
+
return wrapPatchMutationOutput(`${lines.join('\n')}\n`, mutationPlan, { executor: 'v4a-rename' });
|
|
900
967
|
}
|
|
901
968
|
// Apply one wave (a set of unique targets) via applyParsedWave (native +
|
|
902
|
-
// JS split). Returns {
|
|
969
|
+
// JS split). Returns { executor, text } on success or { executor, error }.
|
|
903
970
|
const applyWave = (wave) => applyParsedWave(wave, basePath, { fuzz, rejectPartial, dryRun, fuzzy, readStateScope, abortSignal });
|
|
904
971
|
|
|
905
972
|
// Duplicate-target blocks were split into contiguous sequential groups
|
|
906
973
|
// (listed order preserved); apply them in order, each against the prior
|
|
907
974
|
// group's on-disk result.
|
|
908
975
|
const waveTexts = [];
|
|
909
|
-
let
|
|
976
|
+
let executor = 'native-patch';
|
|
910
977
|
// dry_run never writes, so a later group would validate against unchanged
|
|
911
978
|
// disk and false-fail on any block that depends on an earlier edit. Only
|
|
912
979
|
// the first group is validated under dry_run; the rest are reported as
|
|
@@ -914,9 +981,9 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
914
981
|
const groupCount = (dryRun && waveDispatch.length > 1) ? 1 : waveDispatch.length;
|
|
915
982
|
for (let w = 0; w < groupCount; w++) {
|
|
916
983
|
const res = await applyWave(waveDispatch[w]);
|
|
917
|
-
|
|
984
|
+
executor = res.executor;
|
|
918
985
|
if (res.error) {
|
|
919
|
-
if (w === 0) return wrapPatchMutationOutput(res.error, mutationPlan, {
|
|
986
|
+
if (w === 0) return wrapPatchMutationOutput(res.error, mutationPlan, { executor });
|
|
920
987
|
// A later group failed. rejectPartial makes each group all-or-nothing,
|
|
921
988
|
// so every block in groups 1..w is fully committed to disk and left in
|
|
922
989
|
// place. List them all so the caller knows the true on-disk state.
|
|
@@ -927,7 +994,7 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
927
994
|
'--- failing block ---',
|
|
928
995
|
failMsg,
|
|
929
996
|
].join('\n');
|
|
930
|
-
return wrapPatchMutationOutput(note, mutationPlan, {
|
|
997
|
+
return wrapPatchMutationOutput(note, mutationPlan, { executor });
|
|
931
998
|
}
|
|
932
999
|
waveTexts.push(res.text);
|
|
933
1000
|
}
|
|
@@ -949,9 +1016,9 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
949
1016
|
`hunk-level rejected (rejectPartial=false, V4A): ${rejectedV4AHunks.length}`,
|
|
950
1017
|
...rejectedV4AHunks.map((r) => ` REJECT ${r.file || '(unknown)'} — ${String(r.reason || '').split(';')[0].trim()}`),
|
|
951
1018
|
];
|
|
952
|
-
return wrapPatchMutationOutput(`${combined}\n${tail.join('\n')}`, mutationPlan, {
|
|
1019
|
+
return wrapPatchMutationOutput(`${combined}\n${tail.join('\n')}`, mutationPlan, { executor });
|
|
953
1020
|
}
|
|
954
|
-
return wrapPatchMutationOutput(combined, mutationPlan, {
|
|
1021
|
+
return wrapPatchMutationOutput(combined, mutationPlan, { executor });
|
|
955
1022
|
};
|
|
956
1023
|
|
|
957
1024
|
return withBuiltinPathLocks(_lockPaths, () =>
|
|
@@ -1051,6 +1118,13 @@ function patchTargetPaths(patchStr, basePath) {
|
|
|
1051
1118
|
const rel = m[1].trim();
|
|
1052
1119
|
if (rel) out.push(rel);
|
|
1053
1120
|
}
|
|
1121
|
+
// A rename WRITES its destination, so the destination is a target too — the
|
|
1122
|
+
// write-root gate and the replay snapshot both need it.
|
|
1123
|
+
const mre = /^\*\*\* Move to:\s*(.+)$/gm;
|
|
1124
|
+
while ((m = mre.exec(String(patchStr || '')))) {
|
|
1125
|
+
const rel = m[1].trim();
|
|
1126
|
+
if (rel) out.push(rel);
|
|
1127
|
+
}
|
|
1054
1128
|
const ure = /^\+\+\+ (?:b\/)?(.+)$/gm;
|
|
1055
1129
|
while ((m = ure.exec(String(patchStr || '')))) {
|
|
1056
1130
|
const rel = m[1].trim();
|
|
@@ -283,6 +283,15 @@ export function parseV4APatch(patchStr) {
|
|
|
283
283
|
if (rawLine.startsWith('@@')) {
|
|
284
284
|
const anchor = normaliseV4AAnchor(rawLine.slice(2));
|
|
285
285
|
if (currentHunk && currentHunk.lines.length > 0) finishHunk();
|
|
286
|
+
// Consecutive `@@` lines narrow ONE hunk's position (`@@ class Foo`
|
|
287
|
+
// then `@@ def bar`): append to the open hunk's anchor chain instead
|
|
288
|
+
// of replacing the hunk, which dropped every anchor but the last and
|
|
289
|
+
// let the edit resolve against the wrong occurrence.
|
|
290
|
+
if (currentHunk && currentHunk.lines.length === 0) {
|
|
291
|
+
if (anchor) currentHunk.anchors.push(anchor);
|
|
292
|
+
pendingAnchors = [];
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
286
295
|
pendingAnchors.push(anchor);
|
|
287
296
|
currentHunk = { anchors: pendingAnchors.slice(), lines: [] };
|
|
288
297
|
pendingAnchors = [];
|
|
@@ -374,6 +383,13 @@ function parseUnifiedAsV4APatch(patchStr, { label, resolveAnchor }) {
|
|
|
374
383
|
if (rawLine.startsWith('@@')) {
|
|
375
384
|
const anchor = resolveAnchor(rawLine);
|
|
376
385
|
if (currentHunk && currentHunk.lines.length > 0) finishHunk();
|
|
386
|
+
// Anchor chain (see the V4A parser above): consecutive `@@` lines refine
|
|
387
|
+
// one hunk's position and must all be kept.
|
|
388
|
+
if (currentHunk && currentHunk.lines.length === 0) {
|
|
389
|
+
if (anchor) currentHunk.anchors.push(anchor);
|
|
390
|
+
pendingAnchors = [];
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
377
393
|
pendingAnchors.push(anchor);
|
|
378
394
|
currentHunk = { anchors: pendingAnchors.slice(), lines: [] };
|
|
379
395
|
pendingAnchors = [];
|