mixdog 0.9.90 → 0.9.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/scripts/tool-overhead-microbench.mjs +60 -0
- package/src/agents/debugger/agent.json +1 -1
- package/src/agents/explore/agent.json +1 -1
- package/src/agents/heavy-worker/agent.json +1 -1
- package/src/agents/maintainer/agent.json +1 -1
- package/src/agents/reviewer/agent.json +1 -1
- package/src/agents/worker/agent.json +1 -1
- package/src/lib/rules-builder.cjs +5 -5
- package/src/output-styles/detailed.md +27 -0
- package/src/output-styles/extreme-minimal.md +8 -8
- package/src/output-styles/minimal.md +7 -10
- package/src/output-styles/simple.md +12 -20
- package/src/rules/lead/01-general.md +9 -1
- package/src/rules/lead/lead-brief.md +15 -14
- package/src/rules/shared/01-tool.md +32 -37
- package/src/runtime/agent/orchestrator/agent-runtime/commit-message-completion.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +47 -1
- package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +28 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +176 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +74 -11
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +16 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +176 -16
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +5 -0
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +30 -1
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +96 -5
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +72 -1
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +48 -9
- package/src/runtime/channels/backends/discord.mjs +21 -1
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/lib/trace-store.mjs +25 -3
- package/src/runtime/memory/tool-defs.mjs +1 -3
- package/src/runtime/search/tool-defs.mjs +2 -18
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/session-runtime/output-styles.mjs +4 -6
- package/src/session-runtime/tool-defs.mjs +0 -1
- package/src/session-runtime/tool-surface.mjs +9 -0
- package/src/session-runtime/workflow.mjs +25 -8
- package/src/tui/dist/index.mjs +32 -1
- package/src/tui/engine/session-api.mjs +17 -0
- package/src/tui/engine/tui-steering-persist.mjs +24 -1
- package/src/workflows/default/WORKFLOW.md +7 -17
- package/src/workflows/solo/WORKFLOW.md +7 -5
- package/src/output-styles/default.md +0 -40
|
@@ -65,7 +65,7 @@ export const BUILTIN_TOOLS = [
|
|
|
65
65
|
description: 'File path, or {path,offset,limit}[] regions. Pass real arrays, not JSON strings.',
|
|
66
66
|
},
|
|
67
67
|
offset: { type: 'number', minimum: 0, description: 'Lines to skip.' },
|
|
68
|
-
limit: { type: 'number', minimum: 1, description: 'Max lines after offset.' },
|
|
68
|
+
limit: { type: 'number', minimum: 1, description: 'Max lines after offset. Defaults to 2000.' },
|
|
69
69
|
},
|
|
70
70
|
required: ['path'],
|
|
71
71
|
},
|
|
@@ -74,7 +74,7 @@ export const BUILTIN_TOOLS = [
|
|
|
74
74
|
name: 'shell',
|
|
75
75
|
title: 'Mixdog Shell',
|
|
76
76
|
annotations: { title: 'Mixdog Shell', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
77
|
-
description: '
|
|
77
|
+
description: 'Runs a shell command and returns its output. Use async for sleep/watch/dev loops.'
|
|
78
78
|
+ `${_shellSyntaxCheat} ${TOOL_ASYNC_EXECUTION_CONTRACT}`,
|
|
79
79
|
inputSchema: {
|
|
80
80
|
type: 'object',
|
|
@@ -139,7 +139,7 @@ export const BUILTIN_TOOLS = [
|
|
|
139
139
|
description: 'Glob filter.',
|
|
140
140
|
},
|
|
141
141
|
output_mode: { type: 'string', enum: ['content_with_context', 'files_with_matches', 'count'], description: 'content_with_context (default); files_with_matches/count for existence.' },
|
|
142
|
-
head_limit: { type: 'number', minimum: 0, description: 'Max results.' },
|
|
142
|
+
head_limit: { type: 'number', minimum: 0, description: 'Max results. Defaults to 250; 0 = unlimited.' },
|
|
143
143
|
offset: { type: 'number', minimum: 0, description: 'Skip results for paging.' },
|
|
144
144
|
'-C': { type: 'number', minimum: 0, description: 'Lines before/after each match.' },
|
|
145
145
|
},
|
|
@@ -171,7 +171,7 @@ export const BUILTIN_TOOLS = [
|
|
|
171
171
|
],
|
|
172
172
|
description: 'Base directory(ies); path[] batches.',
|
|
173
173
|
},
|
|
174
|
-
head_limit: { type: 'number', description: 'Max entries.' },
|
|
174
|
+
head_limit: { type: 'number', description: 'Max entries. Defaults to 100; 0 = unlimited.' },
|
|
175
175
|
offset: { type: 'number', description: 'Skip entries.' },
|
|
176
176
|
},
|
|
177
177
|
required: ['pattern'],
|
|
@@ -193,7 +193,7 @@ export const BUILTIN_TOOLS = [
|
|
|
193
193
|
description: 'Partial path/name words; query[] batches.',
|
|
194
194
|
},
|
|
195
195
|
path: { type: 'string', description: 'Base directory.' },
|
|
196
|
-
head_limit: { type: 'number', description: 'Max paths.' },
|
|
196
|
+
head_limit: { type: 'number', description: 'Max paths. Defaults to 25.' },
|
|
197
197
|
},
|
|
198
198
|
required: ['query'],
|
|
199
199
|
},
|
|
@@ -213,7 +213,7 @@ export const BUILTIN_TOOLS = [
|
|
|
213
213
|
],
|
|
214
214
|
description: 'Directory; path[] batches.',
|
|
215
215
|
},
|
|
216
|
-
head_limit: { type: 'number', description: 'Max entries.' },
|
|
216
|
+
head_limit: { type: 'number', description: 'Max entries. Defaults to 200; 0 = no cap.' },
|
|
217
217
|
offset: { type: 'number', description: 'Skip N entries for paging.' },
|
|
218
218
|
},
|
|
219
219
|
required: [],
|
|
@@ -40,6 +40,18 @@ const FIND_WALK_TIMEOUT_MS = 20_000;
|
|
|
40
40
|
const LIST_WALK_TIMEOUT_MS = 20_000;
|
|
41
41
|
const LIST_ABSOLUTE_CAP = 50_000;
|
|
42
42
|
|
|
43
|
+
// A/B override surface for the default result caps (stock: list/tree 200,
|
|
44
|
+
// fuzzy find 25). Env-gated so bench variants can match competitor-style
|
|
45
|
+
// generous caps without changing the shipped defaults.
|
|
46
|
+
function _listDefaultHeadLimit(fallback) {
|
|
47
|
+
const parsed = parseInt(process.env.MIXDOG_LIST_DEFAULT_HEAD_LIMIT ?? '', 10);
|
|
48
|
+
return parsed > 0 ? parsed : fallback;
|
|
49
|
+
}
|
|
50
|
+
function _findDefaultHeadLimit(fallback) {
|
|
51
|
+
const parsed = parseInt(process.env.MIXDOG_FIND_DEFAULT_HEAD_LIMIT ?? '', 10);
|
|
52
|
+
return parsed > 0 ? parsed : fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
export async function executeListTool(args, workDir, options = {}) {
|
|
44
56
|
args.path = coerceReadFamilyPathArg(args.path, workDir);
|
|
45
57
|
if (Array.isArray(args.path)) {
|
|
@@ -108,7 +120,7 @@ export async function executeListTool(args, workDir, options = {}) {
|
|
|
108
120
|
const hidden = Boolean(args.hidden);
|
|
109
121
|
const sort = ['name', 'mtime', 'size'].includes(args.sort) ? args.sort : 'name';
|
|
110
122
|
const typeFilter = ['any', 'file', 'dir'].includes(args.type) ? args.type : 'any';
|
|
111
|
-
const headLimit = normalizeListHeadLimit(args.head_limit, 200);
|
|
123
|
+
const headLimit = normalizeListHeadLimit(args.head_limit, _listDefaultHeadLimit(200));
|
|
112
124
|
const offset = typeof args.offset === 'number' && args.offset > 0 ? args.offset : 0;
|
|
113
125
|
const needsGlobalStat = sort === 'mtime' || sort === 'size';
|
|
114
126
|
const includeNoise = Boolean(args.include_noise);
|
|
@@ -246,7 +258,7 @@ export async function executeTreeTool(args, workDir, options = {}) {
|
|
|
246
258
|
const inputPath = args.path || '.';
|
|
247
259
|
const depth = Math.min(Math.max(parseInt(args.depth ?? 3, 10) || 3, 1), 6);
|
|
248
260
|
const hidden = Boolean(args.hidden);
|
|
249
|
-
const headLimit = normalizeListHeadLimit(args.head_limit, 200);
|
|
261
|
+
const headLimit = normalizeListHeadLimit(args.head_limit, _listDefaultHeadLimit(200));
|
|
250
262
|
const offset = typeof args.offset === 'number' && args.offset > 0 ? args.offset : 0;
|
|
251
263
|
const includeNoise = Boolean(args.include_noise);
|
|
252
264
|
const _treeGuard = listGuardPath(inputPath);
|
|
@@ -589,7 +601,7 @@ export async function executeFuzzyFindTool(args, workDir, options = {}) {
|
|
|
589
601
|
const includeNoise = Boolean(args.include_noise);
|
|
590
602
|
// head_limit:0 means "no cap" per list semantics; default is intentionally
|
|
591
603
|
// compact so ambiguous discovery does not dump a huge candidate list.
|
|
592
|
-
const headLimit = normalizeListHeadLimit(args.head_limit, 25);
|
|
604
|
+
const headLimit = normalizeListHeadLimit(args.head_limit, _findDefaultHeadLimit(25));
|
|
593
605
|
const depth = args.depth != null ? Math.max(parseInt(args.depth, 10) || 1, 1) : null;
|
|
594
606
|
const cacheKey = buildListCacheKey({
|
|
595
607
|
mode: 'fuzzy_find',
|
|
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { accessSync, constants, statSync } from 'node:fs';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import os from 'node:os';
|
|
7
8
|
import {
|
|
8
9
|
acquire as acquireChildSpawnSlot,
|
|
@@ -181,6 +182,14 @@ export async function _resolveRgExecutable() {
|
|
|
181
182
|
if (existsSync(candidate) && _usableRgCandidate(candidate, isWin)) return candidate;
|
|
182
183
|
}
|
|
183
184
|
}
|
|
185
|
+
// Vendored fallback: the optional @vscode/ripgrep dependency ships a
|
|
186
|
+
// platform rg binary with the package, so a machine without system
|
|
187
|
+
// ripgrep still gets a working search family (grep/find/explore). System
|
|
188
|
+
// rg on PATH always wins above; this only fills the gap.
|
|
189
|
+
try {
|
|
190
|
+
const { rgPath } = createRequire(import.meta.url)('@vscode/ripgrep');
|
|
191
|
+
if (rgPath && _usableRgCandidate(String(rgPath), isWin)) return String(rgPath);
|
|
192
|
+
} catch { /* optional dependency absent — continue to where/which */ }
|
|
184
193
|
try {
|
|
185
194
|
const cmd = isWin ? 'where' : 'which';
|
|
186
195
|
const { stdout: out } = await execFileAsync(cmd, ['rg'], { encoding: 'utf8', windowsHide: true });
|
|
@@ -103,6 +103,20 @@ import {
|
|
|
103
103
|
} from './lib/grep-output.mjs';
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
// Default grep result cap when head_limit is unspecified. 250 matches the
|
|
107
|
+
// Claude Code default (GrepTool DEFAULT_HEAD_LIMIT); the tool-result offload
|
|
108
|
+
// layer still bounds oversized results. MIXDOG_GREP_DEFAULT_HEAD_LIMIT
|
|
109
|
+
// overrides for A/B runs.
|
|
110
|
+
function _grepDefaultHeadLimit() {
|
|
111
|
+
const parsed = parseInt(process.env.MIXDOG_GREP_DEFAULT_HEAD_LIMIT ?? '', 10);
|
|
112
|
+
return parsed > 0 ? parsed : 250;
|
|
113
|
+
}
|
|
114
|
+
// Same A/B override surface for glob (stock default 100).
|
|
115
|
+
function _globDefaultHeadLimit() {
|
|
116
|
+
const parsed = parseInt(process.env.MIXDOG_GLOB_DEFAULT_HEAD_LIMIT ?? '', 10);
|
|
117
|
+
return parsed > 0 ? parsed : 100;
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
export async function executeGrepTool(args, workDir, executeChildBuiltinTool, readStateScope = null, options = {}) {
|
|
107
121
|
args = normalizeGrepArgs(args);
|
|
108
122
|
args.path = coerceReadFamilyPathArg(args.path, workDir);
|
|
@@ -290,7 +304,7 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
290
304
|
return `Error: invalid head_limit ${JSON.stringify(headLimitRaw)}; expected a non-negative integer (0 = unlimited)`;
|
|
291
305
|
}
|
|
292
306
|
const headLimit = headLimitCoerced === null
|
|
293
|
-
?
|
|
307
|
+
? _grepDefaultHeadLimit()
|
|
294
308
|
: (headLimitCoerced === 0 ? Infinity : headLimitCoerced);
|
|
295
309
|
const offsetCoerced = coerceNonNegInt(args.offset);
|
|
296
310
|
if (Number.isNaN(offsetCoerced)) {
|
|
@@ -988,7 +1002,7 @@ export async function executeGlobTool(args, workDir, options = {}) {
|
|
|
988
1002
|
return `Error: invalid head_limit ${JSON.stringify(headLimitRaw)}; expected a non-negative integer (0 = unlimited)`;
|
|
989
1003
|
}
|
|
990
1004
|
const headLimit = headLimitCoerced === null
|
|
991
|
-
?
|
|
1005
|
+
? _globDefaultHeadLimit()
|
|
992
1006
|
: (headLimitCoerced === 0 ? Infinity : headLimitCoerced);
|
|
993
1007
|
const offsetCoerced = coerceNonNegInt(args.offset);
|
|
994
1008
|
if (Number.isNaN(offsetCoerced)) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { statSync } from 'fs';
|
|
1
|
+
import { closeSync, openSync, readSync, statSync, unlinkSync } from 'fs';
|
|
2
2
|
import { assertPathReachable, assertPathsReachable } from './fs-reachability.mjs';
|
|
3
|
-
import { isAbsolute, resolve } from 'path';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
4
6
|
import { WRAPPER_NAMES } from '../shell-policy.mjs';
|
|
5
7
|
import {
|
|
6
8
|
cwdRelativePath,
|
|
@@ -761,7 +763,10 @@ export async function analyzeShellCommandEffects(command, cwd) {
|
|
|
761
763
|
// its deadline. Only the first top-level segment is considered — sleeps inside
|
|
762
764
|
// pipelines/subshells/scripts are legitimate pacing and pass through. Float
|
|
763
765
|
// durations (sleep 0.5) are allowed, mirroring the reference CLI.
|
|
764
|
-
|
|
766
|
+
// Exported: bash-tool promotes a detected sleep-chain to a background task
|
|
767
|
+
// BEFORE foregroundLongCommandHint runs, so in the one-shot shell path this
|
|
768
|
+
// deny only survives for callers that do not promote (task-tool preflight).
|
|
769
|
+
export function detectBlockedSleepPattern(command) {
|
|
765
770
|
const cmd = String(command || '').trim();
|
|
766
771
|
if (!cmd) return null;
|
|
767
772
|
const sep = cmd.match(/&&|\|\||;|\|/);
|
|
@@ -782,22 +787,62 @@ function detectBlockedSleepPattern(command) {
|
|
|
782
787
|
return rest ? `sleep ${secs} followed by: ${rest.slice(0, 80)}` : `standalone sleep ${secs}`;
|
|
783
788
|
}
|
|
784
789
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
790
|
+
// Codex-parity shell interception (core maybe_parse_apply_patch_verified):
|
|
791
|
+
// codex-trained models type `apply_patch <<'EOF' … EOF` INTO THE SHELL. No
|
|
792
|
+
// such binary exists here, so the invocation is extracted and routed to the
|
|
793
|
+
// internal apply_patch engine instead of dying as "command not found".
|
|
794
|
+
// Returns null when the command is not an apply_patch invocation, { patch }
|
|
795
|
+
// when one was extracted, or { error } for a recognized-but-malformed call.
|
|
796
|
+
export function extractShellApplyPatchInvocation(command) {
|
|
797
|
+
let cmd = String(command || '').trim();
|
|
798
|
+
if (!cmd) return null;
|
|
799
|
+
// Unwrap one `bash|sh|zsh -lc '<script>'` / `-c "<script>"` layer.
|
|
800
|
+
const wrap = /^(?:bash|sh|zsh)\s+-l?c\s+(['"])([\s\S]*)\1\s*$/.exec(cmd);
|
|
801
|
+
if (wrap) cmd = wrap[2].trim();
|
|
802
|
+
// A bare patch pasted into the shell (codex ImplicitInvocation case) is
|
|
803
|
+
// unambiguous — route it to the engine directly.
|
|
804
|
+
if (cmd.startsWith('*** Begin Patch')) return { patch: cmd };
|
|
805
|
+
if (!/^apply_patch(?:\s|$)/.test(cmd)) return null;
|
|
806
|
+
const rest = cmd.slice('apply_patch'.length).trim();
|
|
807
|
+
// Heredoc form: apply_patch <<'EOF' \n <patch> \n EOF
|
|
808
|
+
const heredoc = /^<<-?\s*(['"]?)(\w+)\1\s*\n([\s\S]*)$/.exec(rest);
|
|
809
|
+
if (heredoc) {
|
|
810
|
+
const terminator = heredoc[2];
|
|
811
|
+
const lines = heredoc[3].split('\n');
|
|
812
|
+
const endIdx = lines.findIndex((l) => l.trim() === terminator);
|
|
813
|
+
const patch = (endIdx === -1 ? lines : lines.slice(0, endIdx)).join('\n').trim();
|
|
814
|
+
if (patch) return { patch };
|
|
815
|
+
return { error: 'apply_patch heredoc contained no patch body' };
|
|
816
|
+
}
|
|
817
|
+
// Single-argument form: apply_patch '<patch>' / "<patch>" / bare.
|
|
818
|
+
if (rest) {
|
|
819
|
+
const q = rest[0];
|
|
820
|
+
let patch = rest;
|
|
821
|
+
if ((q === "'" || q === '"') && rest.length >= 2 && rest.endsWith(q)) {
|
|
822
|
+
patch = rest.slice(1, -1);
|
|
823
|
+
if (q === '"') patch = patch.replace(/\\([\\"$`])/g, '$1');
|
|
824
|
+
else patch = patch.replace(/'\\''/g, "'");
|
|
795
825
|
}
|
|
826
|
+
patch = patch.trim();
|
|
827
|
+
if (patch.startsWith('*** Begin Patch')) return { patch };
|
|
828
|
+
return { error: 'apply_patch argument did not contain a V4A patch (expected "*** Begin Patch")' };
|
|
796
829
|
}
|
|
797
|
-
|
|
830
|
+
return { error: 'apply_patch requires the patch text (heredoc or single argument)' };
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Watch-like / long-blocking-sleep detection shared by bash-tool's auto-async
|
|
834
|
+
// promotion and foregroundLongCommandHint. Returns a short human reason when
|
|
835
|
+
// the command would block the foreground for a long time (dev server, watcher,
|
|
836
|
+
// or a 30s+ sleep anywhere in the chain), else null. bash-tool promotes these
|
|
837
|
+
// to a background task instead of failing the call — measured 2026-08: the
|
|
838
|
+
// old hard deny fired ~22×/14d and every hit was a wasted turn.
|
|
839
|
+
export function detectLongForegroundReason(command) {
|
|
840
|
+
const cmd = String(command || '').trim();
|
|
841
|
+
if (!cmd) return null;
|
|
798
842
|
const watchLike = /^\s*gh\s+run\s+watch(?:\s|$)/i.test(cmd)
|
|
799
843
|
|| /^\s*(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|watch|serve)(?:\s|$)/i.test(cmd)
|
|
800
844
|
|| /^\s*(?:vite|webpack-dev-server|next|nuxt|astro)\s+(?:dev|start)(?:\s|$)/i.test(cmd);
|
|
845
|
+
if (watchLike) return 'watch-like long-running command (dev server / watcher)';
|
|
801
846
|
const longPowerShellSleep = [...cmd.matchAll(/\bStart-Sleep\b([^;&|\r\n]*)/gi)].some((m) => {
|
|
802
847
|
const part = String(m[1] || '');
|
|
803
848
|
const ms = part.match(/-(?:Milliseconds|m)\s+(\d+)/i);
|
|
@@ -806,8 +851,23 @@ export function foregroundLongCommandHint(command, timeoutMs, args = {}, opts =
|
|
|
806
851
|
return sec ? Number(sec[1]) >= 30 : false;
|
|
807
852
|
});
|
|
808
853
|
const longSleep = /\bsleep\s+(?:[3-9]\d|\d{3,}|\d+[mh])\b/i.test(cmd) || longPowerShellSleep;
|
|
809
|
-
if (
|
|
810
|
-
|
|
854
|
+
if (longSleep) return 'long blocking sleep (30s+) in command';
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export function foregroundLongCommandHint(command, timeoutMs, args = {}, opts = {}) {
|
|
859
|
+
if (args.run_in_background === true) return '';
|
|
860
|
+
const cmd = String(command || '').trim();
|
|
861
|
+
if (!cmd) return '';
|
|
862
|
+
// CC validateInput parity: block only while background tasks are enabled —
|
|
863
|
+
// the remedy we point at (async mode / completion notification) must exist.
|
|
864
|
+
if (opts.backgroundTasksDisabled !== true) {
|
|
865
|
+
const blocked = detectBlockedSleepPattern(cmd);
|
|
866
|
+
if (blocked) {
|
|
867
|
+
return `Error: blocked — ${blocked}. Run blocking commands as a background task (mode:"async") and act on the completion notification, or use task wait on an existing task_id. If you genuinely need a delay (rate limiting, pacing), keep it under 2 seconds.`;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (!detectLongForegroundReason(cmd)) return '';
|
|
811
871
|
return 'Error: long foreground command detected. Use mode:"async" (background task) or task wait instead of blocking sleeps/watch loops.';
|
|
812
872
|
}
|
|
813
873
|
|
|
@@ -870,3 +930,103 @@ export function isAutobackgroundingAllowed(command, shellType) {
|
|
|
870
930
|
}
|
|
871
931
|
return true;
|
|
872
932
|
}
|
|
933
|
+
|
|
934
|
+
// ---------------------------------------------------------------------------
|
|
935
|
+
// Filter-swallow rescue (PowerShell one-shot path). Measured 2026-08: ~37
|
|
936
|
+
// failures/14d were `<producer> 2>&1 | Select-String … | Select-Object …`
|
|
937
|
+
// pipelines where the producer failed but the trailing filters matched
|
|
938
|
+
// nothing — the model saw `[exit code: 1]` + `(no output)` and burned turns
|
|
939
|
+
// re-running with different capture tricks. Plan: insert a Tee-Object spill
|
|
940
|
+
// of the UNFILTERED producer stream so a failing run can attach the original
|
|
941
|
+
// tail in the SAME call. Applies only to the exactly-recognized shape; any
|
|
942
|
+
// ambiguity returns null and the original command runs untouched:
|
|
943
|
+
// - single-line command, no backticks / $( ) / ${ } / here-strings / procsub
|
|
944
|
+
// - no existing Tee-Object
|
|
945
|
+
// - last top-level segment is a byte-exact suffix of the command
|
|
946
|
+
// - >= 2 pipeline stages; stage 0 is NOT a pure filter; stages 1..n are ALL
|
|
947
|
+
// known pure pass-through filters; no stage redirects to a file
|
|
948
|
+
// Tee-Object passes objects through unchanged and does not touch
|
|
949
|
+
// $LASTEXITCODE, so success-path output and exit semantics are identical.
|
|
950
|
+
const _PS_PURE_FILTER_HEADS = new Set([
|
|
951
|
+
'select-string', 'sls', 'select-object', 'select',
|
|
952
|
+
'where-object', 'where', 'foreach-object', '%', 'findstr',
|
|
953
|
+
]);
|
|
954
|
+
const _TEE_AMBIGUOUS_RE = /[`]|\$\(|\$\{|@['"]|<\(|>\(/;
|
|
955
|
+
|
|
956
|
+
function _teeStageHead(tokens) {
|
|
957
|
+
return String(tokens?.[0] || '').toLowerCase().replace(/^.*[\\/]/, '').replace(/\.exe$/, '');
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function _stageRedirectsToFile(tokens) {
|
|
961
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
962
|
+
if (tokens[i] === '>' || tokens[i] === '>>') {
|
|
963
|
+
const next = String(tokens[i + 1] || '');
|
|
964
|
+
if (!next.startsWith('&')) return true; // `> file` (2>&1-style stays allowed)
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function buildPowerShellFilterTeePlan(command) {
|
|
971
|
+
const cmd = String(command || '');
|
|
972
|
+
if (!cmd.trim()) return null;
|
|
973
|
+
if (/\r|\n/.test(cmd)) return null;
|
|
974
|
+
if (/tee-object/i.test(cmd)) return null;
|
|
975
|
+
if (_TEE_AMBIGUOUS_RE.test(cmd)) return null;
|
|
976
|
+
const segments = shellSplitSegments(cmd);
|
|
977
|
+
if (segments.length === 0) return null;
|
|
978
|
+
const last = segments[segments.length - 1];
|
|
979
|
+
const trimmed = cmd.trimEnd();
|
|
980
|
+
if (!trimmed.endsWith(last)) return null;
|
|
981
|
+
const stages = shellSplitPipelineSegments(last);
|
|
982
|
+
if (stages.length < 2) return null;
|
|
983
|
+
const tokenized = [];
|
|
984
|
+
for (const stage of stages) {
|
|
985
|
+
const tokens = shellTokenize(stage);
|
|
986
|
+
if (!tokens || tokens.length === 0) return null;
|
|
987
|
+
if (_stageRedirectsToFile(tokens)) return null;
|
|
988
|
+
const stripped = stripShellProbeWrappers(tokens);
|
|
989
|
+
if (stripped.length === 0) return null;
|
|
990
|
+
tokenized.push(stripped);
|
|
991
|
+
}
|
|
992
|
+
if (_PS_PURE_FILTER_HEADS.has(_teeStageHead(tokenized[0]))) return null;
|
|
993
|
+
for (let i = 1; i < tokenized.length; i++) {
|
|
994
|
+
if (!_PS_PURE_FILTER_HEADS.has(_teeStageHead(tokenized[i]))) return null;
|
|
995
|
+
}
|
|
996
|
+
const teePath = join(tmpdir(), `mixdog-tee-${randomUUID().slice(0, 8)}.log`);
|
|
997
|
+
if (teePath.includes("'")) return null;
|
|
998
|
+
const head = trimmed.slice(0, trimmed.length - last.length);
|
|
999
|
+
const rewritten = `${head}${stages[0]} | Tee-Object -FilePath '${teePath}' | ${stages.slice(1).join(' | ')}`;
|
|
1000
|
+
return { command: rewritten, teePath };
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Read the tail of a tee spill file (BOM-sniffed: Windows PowerShell 5.1
|
|
1004
|
+
// writes UTF-16LE, pwsh 7 writes UTF-8) and ALWAYS delete the file. Returns
|
|
1005
|
+
// null when the file is missing/empty/unreadable.
|
|
1006
|
+
export function consumeFilterTeeCapture(teePath, { maxBytes = 16384 } = {}) {
|
|
1007
|
+
let out = null;
|
|
1008
|
+
try {
|
|
1009
|
+
const st = statSync(teePath);
|
|
1010
|
+
if (st.size > 0) {
|
|
1011
|
+
const fd = openSync(teePath, 'r');
|
|
1012
|
+
try {
|
|
1013
|
+
const bom = Buffer.alloc(2);
|
|
1014
|
+
readSync(fd, bom, 0, 2, 0);
|
|
1015
|
+
const utf16 = bom[0] === 0xff && bom[1] === 0xfe;
|
|
1016
|
+
let start = Math.max(0, st.size - maxBytes);
|
|
1017
|
+
if (utf16 && start % 2 === 1) start += 1;
|
|
1018
|
+
const len = st.size - start;
|
|
1019
|
+
const buf = Buffer.alloc(len);
|
|
1020
|
+
readSync(fd, buf, 0, len, start);
|
|
1021
|
+
out = buf.toString(utf16 ? 'utf16le' : 'utf8');
|
|
1022
|
+
if (start === 0 && out.charCodeAt(0) === 0xfeff) out = out.slice(1);
|
|
1023
|
+
} finally {
|
|
1024
|
+
closeSync(fd);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
} catch {
|
|
1028
|
+
out = null;
|
|
1029
|
+
}
|
|
1030
|
+
try { unlinkSync(teePath); } catch { /* best-effort cleanup */ }
|
|
1031
|
+
return out && out.trim() ? out : null;
|
|
1032
|
+
}
|
|
@@ -206,6 +206,11 @@ export async function executeTaskTool(args, options = {}) {
|
|
|
206
206
|
// consumed synchronously, so no re-arm. Drop the persisted ctx here
|
|
207
207
|
// or it leaks (cleanup only runs on a real watcher settle, which
|
|
208
208
|
// never happens for a never-re-armed entry).
|
|
209
|
+
// EXCEPT when this wait's tool call was aborted: its result was
|
|
210
|
+
// discarded, so nobody consumed the outcome — re-arm so the
|
|
211
|
+
// watcher delivers the completion notification instead of
|
|
212
|
+
// swallowing it.
|
|
213
|
+
else if (options?.signal?.aborted) watchBackgroundShellJob(taskId);
|
|
209
214
|
else clearShellJobNotifyCtx(taskId);
|
|
210
215
|
}
|
|
211
216
|
}
|
|
@@ -48,6 +48,12 @@ const EXPECTED_ARGS = Object.freeze(['-NoLogo', '-NoProfile', '-NonInteractive',
|
|
|
48
48
|
// with the same `$?`→0/1 exit mapping pwsh -Command applies implicitly, then
|
|
49
49
|
// emits `<<<MIXDOG_DONE_<nonce>:<code>>>>` on stdout and
|
|
50
50
|
// `<<<MIXDOG_DONE_<nonce>>>>` on stderr (both streams provably drained).
|
|
51
|
+
// Each command pipes through Microsoft.PowerShell.Core\Out-Default: the whole
|
|
52
|
+
// marker loop is ONE top-level pipeline, so without an explicit per-command
|
|
53
|
+
// formatting terminator Format-Table kept object output buffered PAST the done
|
|
54
|
+
// sentinel and captures came back empty (`Get-ChildItem | Select-Object` →
|
|
55
|
+
// no output). The per-command Out-Default flushes formatted output before the
|
|
56
|
+
// sentinel is written, restoring one-shot `pwsh -Command` parity.
|
|
51
57
|
// stdin EOF exits the process with the LAST command's code — that is exactly
|
|
52
58
|
// what background-promotion relies on for its exit-file. A user script that
|
|
53
59
|
// calls `exit N` terminates the whole process with N (identical code to the
|
|
@@ -63,6 +69,20 @@ function _standbyBootScript(nonce) {
|
|
|
63
69
|
'$__baseEnv = @{}',
|
|
64
70
|
'Get-ChildItem env: | ForEach-Object { $__baseEnv[$_.Name] = $_.Value }',
|
|
65
71
|
'$__baseLoc = (Get-Location).ProviderPath',
|
|
72
|
+
// Session-isolation baselines: env+cwd alone leaked GLOBAL functions,
|
|
73
|
+
// aliases, variables, modules, and preference values across commands
|
|
74
|
+
// (the pool is daemon-global, so across SESSIONS too). Only global
|
|
75
|
+
// scope can leak — a scriptblock's local defs die with `& $__sc`.
|
|
76
|
+
'$__baseFn = @{}',
|
|
77
|
+
'Get-ChildItem function: | ForEach-Object { $__baseFn[$_.Name] = $true }',
|
|
78
|
+
'$__baseAlias = @{}',
|
|
79
|
+
'Get-ChildItem alias: | ForEach-Object { $__baseAlias[$_.Name] = $_.Definition }',
|
|
80
|
+
'$__baseVar = @{}',
|
|
81
|
+
'Get-Variable -Scope Global -ErrorAction SilentlyContinue | ForEach-Object { $__baseVar[$_.Name] = $true }',
|
|
82
|
+
'$__baseMod = @{}',
|
|
83
|
+
'Get-Module | ForEach-Object { $__baseMod[$_.Name] = $true }',
|
|
84
|
+
'$__basePref = @{}',
|
|
85
|
+
"foreach ($__p in @('ErrorActionPreference','ProgressPreference','VerbosePreference','WarningPreference','InformationPreference','DebugPreference','ConfirmPreference')) { $__basePref[$__p] = Get-Variable -Name $__p -ValueOnly -Scope Global -ErrorAction SilentlyContinue }",
|
|
66
86
|
'$__code = 0',
|
|
67
87
|
'while ($true) {',
|
|
68
88
|
' $__buf = New-Object System.Text.StringBuilder',
|
|
@@ -74,12 +94,21 @@ function _standbyBootScript(nonce) {
|
|
|
74
94
|
' }',
|
|
75
95
|
' foreach ($__n in @(Get-ChildItem env: | ForEach-Object Name)) { if (-not $__baseEnv.ContainsKey($__n)) { Remove-Item ("env:$__n") -ErrorAction SilentlyContinue } }',
|
|
76
96
|
' foreach ($__k in $__baseEnv.Keys) { Set-Item ("env:$__k") $__baseEnv[$__k] }',
|
|
97
|
+
// Strip global state the previous command left behind; restore
|
|
98
|
+
// overwritten alias definitions and preference values. `__*` variable
|
|
99
|
+
// names are the loop's own internals (created after baseline) and are
|
|
100
|
+
// never removed.
|
|
101
|
+
' foreach ($__n in @(Get-ChildItem function: | ForEach-Object Name)) { if (-not $__baseFn.ContainsKey($__n)) { Remove-Item ("function:$__n") -Force -ErrorAction SilentlyContinue } }',
|
|
102
|
+
' foreach ($__a in @(Get-ChildItem alias:)) { if (-not $__baseAlias.ContainsKey($__a.Name)) { Remove-Item ("alias:$($__a.Name)") -Force -ErrorAction SilentlyContinue } elseif ($__a.Definition -ne $__baseAlias[$__a.Name]) { Set-Alias -Name $__a.Name -Value $__baseAlias[$__a.Name] -Scope Global -Force -ErrorAction SilentlyContinue } }',
|
|
103
|
+
' foreach ($__n in @(Get-Variable -Scope Global -ErrorAction SilentlyContinue | ForEach-Object Name)) { if ($__n -notlike "__*" -and -not $__baseVar.ContainsKey($__n)) { Remove-Variable -Name $__n -Scope Global -Force -ErrorAction SilentlyContinue } }',
|
|
104
|
+
' foreach ($__n in @(Get-Module | ForEach-Object Name)) { if (-not $__baseMod.ContainsKey($__n)) { Remove-Module -Name $__n -Force -ErrorAction SilentlyContinue } }',
|
|
105
|
+
' foreach ($__n in @($__basePref.Keys)) { Set-Variable -Name $__n -Value $__basePref[$__n] -Scope Global -Force -ErrorAction SilentlyContinue }',
|
|
77
106
|
' Set-Location -LiteralPath $__baseLoc',
|
|
78
107
|
' $global:LASTEXITCODE = 0',
|
|
79
108
|
' $global:__MIXDOG_RC = 1',
|
|
80
109
|
' try {',
|
|
81
110
|
" $__sc = [ScriptBlock]::Create($__buf.ToString() + [Environment]::NewLine + 'if ($?) { $global:__MIXDOG_RC = 0 } else { $global:__MIXDOG_RC = 1 }')",
|
|
82
|
-
|
|
111
|
+
' & $__sc | Microsoft.PowerShell.Core\\Out-Default',
|
|
83
112
|
' } catch {',
|
|
84
113
|
' $global:__MIXDOG_RC = 1',
|
|
85
114
|
" [Console]::Error.WriteLine('Exception: ' + $_.Exception.Message)",
|
|
@@ -383,7 +383,7 @@ export function splitBufferLinesForPatch(buf) {
|
|
|
383
383
|
return lines;
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
function longestCommonSubstringLen(a, b, cap = 4000) {
|
|
386
|
+
export function longestCommonSubstringLen(a, b, cap = 4000) {
|
|
387
387
|
if (!a || !b) return 0;
|
|
388
388
|
const A = a.length > cap ? a.slice(0, cap) : a;
|
|
389
389
|
const B = b.length > cap ? b.slice(0, cap) : b;
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
withBuiltinPathLocks,
|
|
15
15
|
} from '../builtin.mjs';
|
|
16
16
|
import { withAdvisoryLocks } from '../builtin/advisory-lock.mjs';
|
|
17
|
-
import { wrapMutationRouteOutput } from '../mutation-planner.mjs';
|
|
18
17
|
import { getPluginData } from '../../config.mjs';
|
|
19
18
|
import { markCodeGraphDirtyPaths } from '../code-graph-state.mjs';
|
|
20
19
|
import { prepareInput, isV4APatchInput, hasUnifiedBareV4AHunk, canFallbackCountedUnified, parseV4APatch, parseUnifiedBareV4APatch, parseUnifiedCountedAsV4APatch, isCompactedPlaceholderPatch, salvageV4AOpening } from './parsing.mjs';
|
|
@@ -509,9 +508,100 @@ function planApplyPatchMutationRoute(args, patchStr, requestedFormat) {
|
|
|
509
508
|
};
|
|
510
509
|
}
|
|
511
510
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
511
|
+
// Model-surface success outputs deliberately DROP the `mutation_route:`
|
|
512
|
+
// diagnostic header (codex parity: `Success. Updated the following files:`).
|
|
513
|
+
// Errors were never wrapped, the UI diff side-channel is independent, and no
|
|
514
|
+
// script/UI parses the header — measured saving ~30 tokens x ~125 successful
|
|
515
|
+
// patch calls/day. Route diagnostics stay available via plan/extras callers.
|
|
516
|
+
function wrapPatchMutationOutput(text, _plan, _extras = {}) {
|
|
517
|
+
return text;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Post-patch excerpt: a successful patch result carries the changed span's
|
|
521
|
+
// CURRENT on-disk lines (numbered, verbatim). This embeds the follow-up
|
|
522
|
+
// look-up INSIDE the mutation turn — the next patch to the same region has
|
|
523
|
+
// byte-exact context in-session without spending a read/grep turn, which
|
|
524
|
+
// removes the fuel for stale-own-edit context misses (the measured top
|
|
525
|
+
// remaining failure class).
|
|
526
|
+
//
|
|
527
|
+
// REPEAT-GATED: batched one-shot patches (the common case under the
|
|
528
|
+
// all-edits-in-one-patch rule) rarely get a follow-up, so the FIRST patch of
|
|
529
|
+
// a file in a session appends nothing — zero cost. Only a repeat patch of
|
|
530
|
+
// the same file (the iterative fix-loop signal, exactly where stale-own-edit
|
|
531
|
+
// misses happen) pays for the excerpt.
|
|
532
|
+
const _patchedFilesByScope = new Map(); // scopeKey -> Map(fileKey -> ts)
|
|
533
|
+
const PATCHED_FILES_SCOPE_CAP = 64;
|
|
534
|
+
const PATCHED_FILES_PER_SCOPE_CAP = 300;
|
|
535
|
+
function _scopePatchedFiles(scopeKey) {
|
|
536
|
+
let seen = _patchedFilesByScope.get(scopeKey);
|
|
537
|
+
if (!seen) {
|
|
538
|
+
seen = new Map();
|
|
539
|
+
_patchedFilesByScope.set(scopeKey, seen);
|
|
540
|
+
while (_patchedFilesByScope.size > PATCHED_FILES_SCOPE_CAP) {
|
|
541
|
+
_patchedFilesByScope.delete(_patchedFilesByScope.keys().next().value);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return seen;
|
|
545
|
+
}
|
|
546
|
+
const POST_PATCH_EXCERPT_MAX_FILES = 3;
|
|
547
|
+
const POST_PATCH_EXCERPT_MAX_LINES = 10;
|
|
548
|
+
const POST_PATCH_EXCERPT_MAX_CHARS = 1400;
|
|
549
|
+
export function appendPostPatchExcerpts(outputText, patchStr, requestedFormat, basePath, readStateScope) {
|
|
550
|
+
try {
|
|
551
|
+
if (isPatchErrorText(outputText)) return outputText;
|
|
552
|
+
if (!isV4APatchInput(patchStr, requestedFormat)) return outputText;
|
|
553
|
+
const sections = parseV4APatch(patchStr).filter((s) => s.kind === 'update' && s.hunks?.length);
|
|
554
|
+
if (!sections.length) return outputText;
|
|
555
|
+
const seen = _scopePatchedFiles(String(readStateScope || 'global'));
|
|
556
|
+
const repeats = [];
|
|
557
|
+
for (const section of sections) {
|
|
558
|
+
const target = section.movePath || section.path;
|
|
559
|
+
let fileKey;
|
|
560
|
+
try { fileKey = resolveV4AEntryPath(basePath, target); } catch { continue; }
|
|
561
|
+
if (process.platform === 'win32') fileKey = fileKey.toLowerCase();
|
|
562
|
+
if (seen.has(fileKey)) repeats.push(section);
|
|
563
|
+
seen.set(fileKey, Date.now());
|
|
564
|
+
while (seen.size > PATCHED_FILES_PER_SCOPE_CAP) seen.delete(seen.keys().next().value);
|
|
565
|
+
}
|
|
566
|
+
if (!repeats.length) return outputText;
|
|
567
|
+
const chunks = [];
|
|
568
|
+
let chars = 0;
|
|
569
|
+
for (const section of repeats.slice(0, POST_PATCH_EXCERPT_MAX_FILES)) {
|
|
570
|
+
const target = section.movePath || section.path;
|
|
571
|
+
let fileLines;
|
|
572
|
+
try {
|
|
573
|
+
fileLines = readFileSync(resolveV4AEntryPath(basePath, target), 'utf8').replace(/\r\n/g, '\n').split('\n');
|
|
574
|
+
} catch { continue; }
|
|
575
|
+
const hunk = section.hunks[0];
|
|
576
|
+
const newSide = (hunk.lines || [])
|
|
577
|
+
.filter((l) => l && (l[0] === ' ' || l[0] === '+'))
|
|
578
|
+
.map((l) => l.slice(1));
|
|
579
|
+
if (!newSide.length) continue;
|
|
580
|
+
let at = -1;
|
|
581
|
+
outer: for (let i = 0; i + newSide.length <= fileLines.length; i++) {
|
|
582
|
+
for (let k = 0; k < newSide.length; k++) {
|
|
583
|
+
if (fileLines[i + k] !== newSide[k]) continue outer;
|
|
584
|
+
}
|
|
585
|
+
at = i; break;
|
|
586
|
+
}
|
|
587
|
+
if (at < 0) continue;
|
|
588
|
+
const shown = Math.min(newSide.length, POST_PATCH_EXCERPT_MAX_LINES);
|
|
589
|
+
const rows = [];
|
|
590
|
+
for (let i = 0; i < shown; i++) {
|
|
591
|
+
rows.push(`${String(at + i + 1).padStart(5, ' ')}| ${fileLines[at + i]}`);
|
|
592
|
+
}
|
|
593
|
+
if (newSide.length > shown) rows.push(' | …');
|
|
594
|
+
const more = section.hunks.length > 1 ? ` (+${section.hunks.length - 1} more hunk${section.hunks.length > 2 ? 's' : ''})` : '';
|
|
595
|
+
const block = `${String(target).replace(/\\/g, '/')} lines ${at + 1}-${at + shown}${more}:\n${rows.join('\n')}`;
|
|
596
|
+
chars += block.length;
|
|
597
|
+
if (chars > POST_PATCH_EXCERPT_MAX_CHARS) break;
|
|
598
|
+
chunks.push(block);
|
|
599
|
+
}
|
|
600
|
+
if (!chunks.length) return outputText;
|
|
601
|
+
return `${outputText}\npost-patch state (verbatim — use for follow-up patches):\n${chunks.join('\n')}`;
|
|
602
|
+
} catch {
|
|
603
|
+
return outputText;
|
|
604
|
+
}
|
|
515
605
|
}
|
|
516
606
|
|
|
517
607
|
const APPLY_PATCH_SCHEMA_KEYS = new Set(['patch', 'format', 'base_path', 'dry_run', 'reject_partial', 'fuzzy', 'sequence', 'mode']);
|
|
@@ -583,11 +673,12 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
583
673
|
const legacyBulkMode = args?.sequence === false
|
|
584
674
|
|| ['atomic', 'bulk'].includes(patchMode);
|
|
585
675
|
if (!legacyBulkMode) {
|
|
586
|
-
|
|
676
|
+
const seqOut = await applyPatchSequence(patchStr, requestedFormat, basePath, {
|
|
587
677
|
v4aConvertOpts, dryRun, fuzz, fuzzy, rejectPartial,
|
|
588
678
|
readStateScope, abortSignal, mutationPlan,
|
|
589
679
|
rollbackOnFailure: patchMode !== 'partial',
|
|
590
680
|
});
|
|
681
|
+
return dryRun ? seqOut : appendPostPatchExcerpts(seqOut, patchStr, requestedFormat, basePath, readStateScope);
|
|
591
682
|
}
|
|
592
683
|
let v4aRenamePlan = null;
|
|
593
684
|
if (isV4APatchInput(patchStr, requestedFormat)) {
|