@carboncode/cli 0.1.0 → 0.1.2
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 +15 -24
- package/README.zh-CN.md +13 -11
- package/dist/cli/{acp-35C4ME6Y.js → acp-6J54TVVC.js} +17 -16
- package/dist/cli/acp-6J54TVVC.js.map +1 -0
- package/dist/cli/{chat-A6UJDPGV.js → chat-636MFZ7W.js} +21 -21
- package/dist/cli/{chunk-JKGYMRX5.js → chunk-3N7FTZVE.js} +2 -2
- package/dist/cli/{chunk-4TVNJWMA.js → chunk-ACHQFKW2.js} +178 -18
- package/dist/cli/chunk-ACHQFKW2.js.map +1 -0
- package/dist/cli/{chunk-7L2WTRNU.js → chunk-ANVEA3RU.js} +2 -2
- package/dist/cli/{chunk-QVC75MR3.js → chunk-BXMMGFAL.js} +2 -2
- package/dist/cli/{chunk-UI66BH6D.js → chunk-COTWTQQZ.js} +2 -2
- package/dist/cli/{chunk-J5BYPUB5.js → chunk-CZCPIK5K.js} +1508 -1176
- package/dist/cli/chunk-CZCPIK5K.js.map +1 -0
- package/dist/cli/{chunk-XJ5SRLKK.js → chunk-D3ACJ6D5.js} +2 -2
- package/dist/cli/{chunk-WRN65TRD.js → chunk-DSQNSP7F.js} +2 -2
- package/dist/cli/{chunk-QJG7OF27.js → chunk-FKSYTVWZ.js} +27 -10
- package/dist/cli/chunk-FKSYTVWZ.js.map +1 -0
- package/dist/cli/{chunk-BSINVTTL.js → chunk-FXG7CSGY.js} +7 -7
- package/dist/cli/{chunk-4MQ3VURH.js → chunk-K43DXH3G.js} +52 -83
- package/dist/cli/chunk-K43DXH3G.js.map +1 -0
- package/dist/cli/{chunk-BSGCXZQN.js → chunk-LNU3CR7X.js} +2 -2
- package/dist/cli/{chunk-TH756VLN.js → chunk-MXUSER5C.js} +240 -191
- package/dist/cli/chunk-MXUSER5C.js.map +1 -0
- package/dist/cli/{chunk-3T6VBZCL.js → chunk-NQJYZKEU.js} +2 -2
- package/dist/cli/{chunk-IX6XI2RG.js → chunk-OB5XR5HG.js} +2 -2
- package/dist/cli/{chunk-ILJOIQ5W.js → chunk-OY5GGU6D.js} +2 -2
- package/dist/cli/{chunk-IAUOP25G.js → chunk-R677DIFU.js} +38 -22
- package/dist/cli/chunk-R677DIFU.js.map +1 -0
- package/dist/cli/{chunk-CPKCNHRR.js → chunk-RSQMO6CF.js} +5 -5
- package/dist/cli/{chunk-3OAR6NVL.js → chunk-RUPXIRNL.js} +2 -2
- package/dist/cli/{chunk-S2KIUQKQ.js → chunk-S4YD3N3X.js} +7 -6
- package/dist/cli/{chunk-S2KIUQKQ.js.map → chunk-S4YD3N3X.js.map} +1 -1
- package/dist/cli/{chunk-4IBIPQVB.js → chunk-T6SBUSG2.js} +3 -3
- package/dist/cli/{chunk-D5NFKRGO.js → chunk-UGPC4LPM.js} +2 -2
- package/dist/cli/{chunk-T5TQ4NDT.js → chunk-X4UJ6Q6M.js} +3 -3
- package/dist/cli/{code-4TUTAGO5.js → code-TBC3K5AZ.js} +24 -33
- package/dist/cli/code-TBC3K5AZ.js.map +1 -0
- package/dist/cli/{commands-KMOZEYCF.js → commands-HMQPRVNT.js} +4 -4
- package/dist/cli/{commit-DTFA56VQ.js → commit-WIY4B3X4.js} +3 -3
- package/dist/cli/{desktop-7N3MHNBD.js → desktop-MGOG3AWV.js} +17 -17
- package/dist/cli/{diff-E5OWTF4C.js → diff-57LRKCB7.js} +8 -8
- package/dist/cli/{doctor-IEJQRJMN.js → doctor-5FDRBIXE.js} +8 -8
- package/dist/cli/index.js +32 -32
- package/dist/cli/{mcp-PDI2PDLG.js → mcp-HJHTNRZF.js} +2 -2
- package/dist/cli/{mcp-browse-OSPXOFPZ.js → mcp-browse-C2PJRQBO.js} +2 -2
- package/dist/cli/{mcp-inspect-QRFVTHMF.js → mcp-inspect-JBFXV2II.js} +2 -2
- package/dist/cli/{prompt-3CDII3UO.js → prompt-U62OVZNY.js} +3 -3
- package/dist/cli/{replay-HYOSRQIV.js → replay-M3YKBVAM.js} +8 -8
- package/dist/cli/{run-2ZHADOUP.js → run-V6X5GXCR.js} +13 -13
- package/dist/cli/{server-X75PAZG5.js → server-5WVJQUOR.js} +10 -10
- package/dist/cli/{sessions-POOZA5CQ.js → sessions-B266WVM3.js} +12 -12
- package/dist/cli/{setup-YLPFI3OH.js → setup-SWX5E3W2.js} +5 -5
- package/dist/cli/{stats-NXJ3TO2D.js → stats-VPPKS6UF.js} +6 -6
- package/dist/cli/{version-NXXWE3WN.js → version-TVHAEHWY.js} +12 -12
- package/dist/index.d.ts +17 -2
- package/dist/index.js +360 -150
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/cli/acp-35C4ME6Y.js.map +0 -1
- package/dist/cli/chunk-4MQ3VURH.js.map +0 -1
- package/dist/cli/chunk-4TVNJWMA.js.map +0 -1
- package/dist/cli/chunk-IAUOP25G.js.map +0 -1
- package/dist/cli/chunk-J5BYPUB5.js.map +0 -1
- package/dist/cli/chunk-QJG7OF27.js.map +0 -1
- package/dist/cli/chunk-TH756VLN.js.map +0 -1
- package/dist/cli/code-4TUTAGO5.js.map +0 -1
- /package/dist/cli/{chat-A6UJDPGV.js.map → chat-636MFZ7W.js.map} +0 -0
- /package/dist/cli/{chunk-JKGYMRX5.js.map → chunk-3N7FTZVE.js.map} +0 -0
- /package/dist/cli/{chunk-7L2WTRNU.js.map → chunk-ANVEA3RU.js.map} +0 -0
- /package/dist/cli/{chunk-QVC75MR3.js.map → chunk-BXMMGFAL.js.map} +0 -0
- /package/dist/cli/{chunk-UI66BH6D.js.map → chunk-COTWTQQZ.js.map} +0 -0
- /package/dist/cli/{chunk-XJ5SRLKK.js.map → chunk-D3ACJ6D5.js.map} +0 -0
- /package/dist/cli/{chunk-WRN65TRD.js.map → chunk-DSQNSP7F.js.map} +0 -0
- /package/dist/cli/{chunk-BSINVTTL.js.map → chunk-FXG7CSGY.js.map} +0 -0
- /package/dist/cli/{chunk-BSGCXZQN.js.map → chunk-LNU3CR7X.js.map} +0 -0
- /package/dist/cli/{chunk-3T6VBZCL.js.map → chunk-NQJYZKEU.js.map} +0 -0
- /package/dist/cli/{chunk-IX6XI2RG.js.map → chunk-OB5XR5HG.js.map} +0 -0
- /package/dist/cli/{chunk-ILJOIQ5W.js.map → chunk-OY5GGU6D.js.map} +0 -0
- /package/dist/cli/{chunk-CPKCNHRR.js.map → chunk-RSQMO6CF.js.map} +0 -0
- /package/dist/cli/{chunk-3OAR6NVL.js.map → chunk-RUPXIRNL.js.map} +0 -0
- /package/dist/cli/{chunk-4IBIPQVB.js.map → chunk-T6SBUSG2.js.map} +0 -0
- /package/dist/cli/{chunk-D5NFKRGO.js.map → chunk-UGPC4LPM.js.map} +0 -0
- /package/dist/cli/{chunk-T5TQ4NDT.js.map → chunk-X4UJ6Q6M.js.map} +0 -0
- /package/dist/cli/{commands-KMOZEYCF.js.map → commands-HMQPRVNT.js.map} +0 -0
- /package/dist/cli/{commit-DTFA56VQ.js.map → commit-WIY4B3X4.js.map} +0 -0
- /package/dist/cli/{desktop-7N3MHNBD.js.map → desktop-MGOG3AWV.js.map} +0 -0
- /package/dist/cli/{diff-E5OWTF4C.js.map → diff-57LRKCB7.js.map} +0 -0
- /package/dist/cli/{doctor-IEJQRJMN.js.map → doctor-5FDRBIXE.js.map} +0 -0
- /package/dist/cli/{mcp-PDI2PDLG.js.map → mcp-HJHTNRZF.js.map} +0 -0
- /package/dist/cli/{mcp-browse-OSPXOFPZ.js.map → mcp-browse-C2PJRQBO.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-QRFVTHMF.js.map → mcp-inspect-JBFXV2II.js.map} +0 -0
- /package/dist/cli/{prompt-3CDII3UO.js.map → prompt-U62OVZNY.js.map} +0 -0
- /package/dist/cli/{replay-HYOSRQIV.js.map → replay-M3YKBVAM.js.map} +0 -0
- /package/dist/cli/{run-2ZHADOUP.js.map → run-V6X5GXCR.js.map} +0 -0
- /package/dist/cli/{server-X75PAZG5.js.map → server-5WVJQUOR.js.map} +0 -0
- /package/dist/cli/{sessions-POOZA5CQ.js.map → sessions-B266WVM3.js.map} +0 -0
- /package/dist/cli/{setup-YLPFI3OH.js.map → setup-SWX5E3W2.js.map} +0 -0
- /package/dist/cli/{stats-NXJ3TO2D.js.map → stats-VPPKS6UF.js.map} +0 -0
- /package/dist/cli/{version-NXXWE3WN.js.map → version-TVHAEHWY.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
|
|
3
3
|
import {
|
|
4
4
|
addProjectShellAllowed
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-K43DXH3G.js";
|
|
6
6
|
|
|
7
7
|
// src/core/pause-gate.ts
|
|
8
8
|
var PauseGate = class {
|
|
@@ -128,23 +128,211 @@ function safeCancelVerdict(kind) {
|
|
|
128
128
|
}
|
|
129
129
|
var pauseGate = new PauseGate();
|
|
130
130
|
|
|
131
|
+
// src/tools/fs/edit.ts
|
|
132
|
+
import { promises as fs } from "fs";
|
|
133
|
+
import * as pathMod from "path";
|
|
134
|
+
function displayRel(rootDir, full) {
|
|
135
|
+
return pathMod.relative(rootDir, full).replaceAll("\\", "/");
|
|
136
|
+
}
|
|
137
|
+
async function applyEdit(rootDir, abs, args) {
|
|
138
|
+
if (args.search.length === 0) {
|
|
139
|
+
throw new Error("edit_file: search cannot be empty");
|
|
140
|
+
}
|
|
141
|
+
const before = await fs.readFile(abs, "utf8");
|
|
142
|
+
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
143
|
+
const adaptedSearch = args.search.replace(/\r?\n/g, le);
|
|
144
|
+
const adaptedReplace = args.replace.replace(/\r?\n/g, le);
|
|
145
|
+
const firstIdx = before.indexOf(adaptedSearch);
|
|
146
|
+
if (firstIdx < 0) {
|
|
147
|
+
throw new Error(`edit_file: search text not found in ${displayRel(rootDir, abs)}`);
|
|
148
|
+
}
|
|
149
|
+
const nextIdx = before.indexOf(adaptedSearch, firstIdx + 1);
|
|
150
|
+
if (nextIdx >= 0) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`edit_file: search text appears multiple times in ${displayRel(rootDir, abs)} \u2014 include more context to disambiguate`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const after = before.slice(0, firstIdx) + adaptedReplace + before.slice(firstIdx + adaptedSearch.length);
|
|
156
|
+
await fs.writeFile(abs, after, "utf8");
|
|
157
|
+
const rel = displayRel(rootDir, abs);
|
|
158
|
+
const header = `edited ${rel} (${adaptedSearch.length}\u2192${adaptedReplace.length} chars)`;
|
|
159
|
+
const startLine = before.slice(0, firstIdx).split(/\r?\n/).length;
|
|
160
|
+
const diff = renderEditDiff(adaptedSearch, adaptedReplace, startLine);
|
|
161
|
+
return `${header}
|
|
162
|
+
${diff}`;
|
|
163
|
+
}
|
|
164
|
+
async function applyMultiEdit(rootDir, edits) {
|
|
165
|
+
if (edits.length === 0) {
|
|
166
|
+
throw new Error("multi_edit: edits must contain at least one entry");
|
|
167
|
+
}
|
|
168
|
+
const filesByPath = /* @__PURE__ */ new Map();
|
|
169
|
+
for (let i = 0; i < edits.length; i++) {
|
|
170
|
+
const e = edits[i];
|
|
171
|
+
if (typeof e.abs !== "string" || e.abs.length === 0) {
|
|
172
|
+
throw new Error(`multi_edit: edit #${i + 1} requires a string \`path\` (no edits applied)`);
|
|
173
|
+
}
|
|
174
|
+
if (typeof e.search !== "string") {
|
|
175
|
+
throw new Error(`multi_edit: edit #${i + 1} requires a string \`search\` (no edits applied)`);
|
|
176
|
+
}
|
|
177
|
+
if (typeof e.replace !== "string") {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`multi_edit: edit #${i + 1} requires a string \`replace\` (no edits applied)`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const rel = displayRel(rootDir, e.abs);
|
|
183
|
+
let state = filesByPath.get(e.abs);
|
|
184
|
+
if (!state) {
|
|
185
|
+
let before;
|
|
186
|
+
if (e.search.length === 0) {
|
|
187
|
+
try {
|
|
188
|
+
await fs.readFile(e.abs, "utf8");
|
|
189
|
+
throw new Error("empty SEARCH only creates new files \u2014 this file already exists");
|
|
190
|
+
} catch (err) {
|
|
191
|
+
const code = err.code;
|
|
192
|
+
if (code !== "ENOENT") {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`multi_edit: edit #${i + 1} cannot create ${rel}: ${err.message} (no edits applied)`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
state = { buf: "", le: "\n", hunks: [], deltaChars: 0, touched: 0, created: true };
|
|
199
|
+
filesByPath.set(e.abs, state);
|
|
200
|
+
} else {
|
|
201
|
+
try {
|
|
202
|
+
before = await fs.readFile(e.abs, "utf8");
|
|
203
|
+
} catch (err) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`multi_edit: edit #${i + 1} cannot read ${rel}: ${err.message} (no edits applied)`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
209
|
+
state = { buf: before, le, hunks: [], deltaChars: 0, touched: 0, created: false };
|
|
210
|
+
filesByPath.set(e.abs, state);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (e.search.length === 0 && (!state.created || state.touched > 0)) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`multi_edit: edit #${i + 1} (${rel}) empty search only creates new files (no edits applied)`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const adaptedSearch = e.search.replace(/\r?\n/g, state.le);
|
|
219
|
+
const adaptedReplace = e.replace.replace(/\r?\n/g, state.le);
|
|
220
|
+
if (adaptedSearch.length === 0) {
|
|
221
|
+
state.buf = adaptedReplace;
|
|
222
|
+
state.hunks.push(`# ${rel}
|
|
223
|
+
${renderCreateDiff(adaptedReplace)}`);
|
|
224
|
+
state.deltaChars += adaptedReplace.length;
|
|
225
|
+
state.touched++;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const firstIdx = state.buf.indexOf(adaptedSearch);
|
|
229
|
+
if (firstIdx < 0) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`multi_edit: edit #${i + 1} search text not found in ${rel} \u2014 no edits applied (multi_edit is atomic)`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const nextIdx = state.buf.indexOf(adaptedSearch, firstIdx + 1);
|
|
235
|
+
if (nextIdx >= 0) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`multi_edit: edit #${i + 1} search text appears multiple times in ${rel} \u2014 include more context to disambiguate (no edits applied)`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const startLine = state.buf.slice(0, firstIdx).split(/\r?\n/).length;
|
|
241
|
+
state.buf = state.buf.slice(0, firstIdx) + adaptedReplace + state.buf.slice(firstIdx + adaptedSearch.length);
|
|
242
|
+
state.hunks.push(`# ${rel}
|
|
243
|
+
${renderEditDiff(adaptedSearch, adaptedReplace, startLine)}`);
|
|
244
|
+
state.deltaChars += adaptedReplace.length - adaptedSearch.length;
|
|
245
|
+
state.touched++;
|
|
246
|
+
}
|
|
247
|
+
for (const [abs, state] of filesByPath) {
|
|
248
|
+
if (state.created) await fs.mkdir(pathMod.dirname(abs), { recursive: true });
|
|
249
|
+
await fs.writeFile(abs, state.buf, "utf8");
|
|
250
|
+
}
|
|
251
|
+
const fileCount = filesByPath.size;
|
|
252
|
+
const editCount = edits.length;
|
|
253
|
+
let totalDelta = 0;
|
|
254
|
+
const allHunks = [];
|
|
255
|
+
for (const state of filesByPath.values()) {
|
|
256
|
+
totalDelta += state.deltaChars;
|
|
257
|
+
allHunks.push(...state.hunks);
|
|
258
|
+
}
|
|
259
|
+
const sign = totalDelta >= 0 ? "+" : "";
|
|
260
|
+
const editNoun = editCount === 1 ? "edit" : "edits";
|
|
261
|
+
const fileNoun = fileCount === 1 ? "file" : "files";
|
|
262
|
+
const header = `multi_edit: applied ${editCount} ${editNoun} across ${fileCount} ${fileNoun} (${sign}${totalDelta} chars)`;
|
|
263
|
+
return `${header}
|
|
264
|
+
${allHunks.join("\n")}`;
|
|
265
|
+
}
|
|
266
|
+
function renderEditDiff(search, replace, startLine) {
|
|
267
|
+
const a = search.split(/\r?\n/);
|
|
268
|
+
const b = replace.split(/\r?\n/);
|
|
269
|
+
const diff = lineDiff(a, b);
|
|
270
|
+
const hunk = `@@ -${startLine},${a.length} +${startLine},${b.length} @@`;
|
|
271
|
+
const body = diff.map((d) => `${d.op === " " ? " " : d.op} ${d.line}`).join("\n");
|
|
272
|
+
return `${hunk}
|
|
273
|
+
${body}`;
|
|
274
|
+
}
|
|
275
|
+
function renderCreateDiff(replace) {
|
|
276
|
+
const lines = replace.length === 0 ? [] : replace.split(/\r?\n/);
|
|
277
|
+
const hunk = `@@ -1,0 +1,${lines.length} @@`;
|
|
278
|
+
const body = lines.map((line) => `+ ${line}`).join("\n");
|
|
279
|
+
return body ? `${hunk}
|
|
280
|
+
${body}` : hunk;
|
|
281
|
+
}
|
|
282
|
+
function lineDiff(a, b) {
|
|
283
|
+
const n = a.length;
|
|
284
|
+
const m = b.length;
|
|
285
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
286
|
+
for (let i2 = 1; i2 <= n; i2++) {
|
|
287
|
+
for (let j2 = 1; j2 <= m; j2++) {
|
|
288
|
+
if (a[i2 - 1] === b[j2 - 1]) dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
289
|
+
else dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const out = [];
|
|
293
|
+
let i = n;
|
|
294
|
+
let j = m;
|
|
295
|
+
while (i > 0 && j > 0) {
|
|
296
|
+
if (a[i - 1] === b[j - 1]) {
|
|
297
|
+
out.unshift({ op: " ", line: a[i - 1] });
|
|
298
|
+
i--;
|
|
299
|
+
j--;
|
|
300
|
+
} else if ((dp[i - 1][j] ?? 0) > (dp[i][j - 1] ?? 0)) {
|
|
301
|
+
out.unshift({ op: "-", line: a[i - 1] });
|
|
302
|
+
i--;
|
|
303
|
+
} else {
|
|
304
|
+
out.unshift({ op: "+", line: b[j - 1] });
|
|
305
|
+
j--;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
while (i > 0) {
|
|
309
|
+
out.unshift({ op: "-", line: a[i - 1] });
|
|
310
|
+
i--;
|
|
311
|
+
}
|
|
312
|
+
while (j > 0) {
|
|
313
|
+
out.unshift({ op: "+", line: b[j - 1] });
|
|
314
|
+
j--;
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
|
|
131
319
|
// src/tools/jobs.ts
|
|
132
320
|
import { spawn as spawn3 } from "child_process";
|
|
133
|
-
import * as
|
|
321
|
+
import * as pathMod6 from "path";
|
|
134
322
|
|
|
135
323
|
// src/tools/shell.ts
|
|
136
|
-
import * as
|
|
324
|
+
import * as pathMod5 from "path";
|
|
137
325
|
|
|
138
326
|
// src/tools/shell/exec.ts
|
|
139
327
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
140
328
|
import { existsSync, statSync } from "fs";
|
|
141
|
-
import * as
|
|
329
|
+
import * as pathMod4 from "path";
|
|
142
330
|
|
|
143
331
|
// src/tools/shell-chain.ts
|
|
144
332
|
import { spawn } from "child_process";
|
|
145
333
|
import { closeSync, openSync } from "fs";
|
|
146
334
|
import { devNull } from "os";
|
|
147
|
-
import * as
|
|
335
|
+
import * as pathMod2 from "path";
|
|
148
336
|
var UnsupportedSyntaxError = class extends Error {
|
|
149
337
|
constructor(detail) {
|
|
150
338
|
super(`run_command: ${detail}`);
|
|
@@ -417,7 +605,7 @@ function openRedirects(redirects, cwd) {
|
|
|
417
605
|
let bothFd = null;
|
|
418
606
|
const toClose = [];
|
|
419
607
|
const open = (target, flags) => {
|
|
420
|
-
const resolved = isNullDeviceAlias(target) ? devNull :
|
|
608
|
+
const resolved = isNullDeviceAlias(target) ? devNull : pathMod2.resolve(cwd, target);
|
|
421
609
|
const fd = openSync(resolved, flags);
|
|
422
610
|
toClose.push(fd);
|
|
423
611
|
return fd;
|
|
@@ -572,7 +760,7 @@ var OutputBuffer = class {
|
|
|
572
760
|
|
|
573
761
|
// src/tools/shell/parse.ts
|
|
574
762
|
import { homedir } from "os";
|
|
575
|
-
import * as
|
|
763
|
+
import * as pathMod3 from "path";
|
|
576
764
|
var BUILTIN_ALLOWLIST = [
|
|
577
765
|
// Repo inspection
|
|
578
766
|
"git status",
|
|
@@ -772,16 +960,16 @@ function resolveSensitivePath(token, projectRoot) {
|
|
|
772
960
|
return null;
|
|
773
961
|
let expanded = token;
|
|
774
962
|
if (expanded.startsWith("~")) {
|
|
775
|
-
expanded =
|
|
963
|
+
expanded = pathMod3.join(homedir(), expanded.slice(1));
|
|
776
964
|
}
|
|
777
|
-
return
|
|
965
|
+
return pathMod3.resolve(projectRoot, expanded);
|
|
778
966
|
}
|
|
779
967
|
function expandPrefix(prefix) {
|
|
780
|
-
if (prefix.startsWith("~")) return
|
|
781
|
-
return
|
|
968
|
+
if (prefix.startsWith("~")) return pathMod3.join(homedir(), prefix.slice(1));
|
|
969
|
+
return pathMod3.resolve(prefix);
|
|
782
970
|
}
|
|
783
971
|
function pathStartsWithPrefix(normalized, prefix) {
|
|
784
|
-
return normalized === prefix || normalized.startsWith(`${prefix}${
|
|
972
|
+
return normalized === prefix || normalized.startsWith(`${prefix}${pathMod3.sep}`);
|
|
785
973
|
}
|
|
786
974
|
function matchesGlob(name, pattern) {
|
|
787
975
|
const regex = new RegExp(
|
|
@@ -796,18 +984,18 @@ function hasSensitivePathArgs(argv, projectRoot, extraPrefixes = [], extraPatter
|
|
|
796
984
|
for (const token of argv) {
|
|
797
985
|
const resolved = resolveSensitivePath(token, projectRoot);
|
|
798
986
|
if (!resolved) continue;
|
|
799
|
-
const normalized =
|
|
987
|
+
const normalized = pathMod3.normalize(resolved);
|
|
800
988
|
for (const pfx of prefixes) {
|
|
801
989
|
if (pathStartsWithPrefix(normalized, pfx)) return true;
|
|
802
990
|
}
|
|
803
|
-
const base =
|
|
991
|
+
const base = pathMod3.basename(normalized);
|
|
804
992
|
for (const pat of patterns) {
|
|
805
993
|
if (matchesGlob(base, pat)) return true;
|
|
806
994
|
}
|
|
807
995
|
}
|
|
808
996
|
return false;
|
|
809
997
|
}
|
|
810
|
-
function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
998
|
+
function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig, opts = {}) {
|
|
811
999
|
let argv;
|
|
812
1000
|
try {
|
|
813
1001
|
argv = tokenizeCommand(cmd);
|
|
@@ -815,7 +1003,7 @@ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
|
815
1003
|
return false;
|
|
816
1004
|
}
|
|
817
1005
|
if (argv.length === 0) return false;
|
|
818
|
-
const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
|
|
1006
|
+
const allowlist = [...opts.includeBuiltin === false ? [] : BUILTIN_ALLOWLIST, ...extra];
|
|
819
1007
|
for (const prefix of allowlist) {
|
|
820
1008
|
const prefixTokens = prefix.split(" ");
|
|
821
1009
|
if (argv.length < prefixTokens.length) continue;
|
|
@@ -840,15 +1028,18 @@ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
|
840
1028
|
}
|
|
841
1029
|
return false;
|
|
842
1030
|
}
|
|
843
|
-
function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
1031
|
+
function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig, opts = {}) {
|
|
844
1032
|
let chain;
|
|
845
1033
|
try {
|
|
846
1034
|
chain = parseCommandChain(cmd);
|
|
847
1035
|
} catch {
|
|
848
1036
|
return false;
|
|
849
1037
|
}
|
|
850
|
-
if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig);
|
|
851
|
-
return chainAllowed(
|
|
1038
|
+
if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig, opts);
|
|
1039
|
+
return chainAllowed(
|
|
1040
|
+
chain,
|
|
1041
|
+
(seg) => isAllowed(seg, extra, projectRoot, sensitivePathConfig, opts)
|
|
1042
|
+
);
|
|
852
1043
|
}
|
|
853
1044
|
|
|
854
1045
|
// src/tools/shell/exec.ts
|
|
@@ -992,16 +1183,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
992
1183
|
const platform = opts.platform ?? process.platform;
|
|
993
1184
|
if (platform !== "win32") return cmd;
|
|
994
1185
|
if (!cmd) return cmd;
|
|
995
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
996
|
-
if (
|
|
1186
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod4.isAbsolute(cmd)) return cmd;
|
|
1187
|
+
if (pathMod4.extname(cmd)) return cmd;
|
|
997
1188
|
const env = opts.env ?? process.env;
|
|
998
1189
|
const pathExt = (getEnvCaseInsensitive(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
999
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
1190
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod4.delimiter);
|
|
1000
1191
|
const pathDirs = (getEnvCaseInsensitive(env, "PATH") ?? "").split(delimiter2).filter(Boolean);
|
|
1001
1192
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
1002
1193
|
for (const dir of pathDirs) {
|
|
1003
1194
|
for (const ext of pathExt) {
|
|
1004
|
-
const full =
|
|
1195
|
+
const full = pathMod4.win32.join(dir, cmd + ext);
|
|
1005
1196
|
if (isFile(full)) return full;
|
|
1006
1197
|
}
|
|
1007
1198
|
}
|
|
@@ -1117,8 +1308,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
1117
1308
|
function isBareWindowsName(s) {
|
|
1118
1309
|
if (!s) return false;
|
|
1119
1310
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
1120
|
-
if (
|
|
1121
|
-
if (
|
|
1311
|
+
if (pathMod4.isAbsolute(s)) return false;
|
|
1312
|
+
if (pathMod4.extname(s)) return false;
|
|
1122
1313
|
return true;
|
|
1123
1314
|
}
|
|
1124
1315
|
function quoteForCmdExe(arg) {
|
|
@@ -1129,7 +1320,7 @@ function quoteForCmdExe(arg) {
|
|
|
1129
1320
|
|
|
1130
1321
|
// src/tools/shell.ts
|
|
1131
1322
|
function registerShellTools(registry, opts) {
|
|
1132
|
-
const rootDir =
|
|
1323
|
+
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
1133
1324
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
1134
1325
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
1135
1326
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -1138,9 +1329,19 @@ function registerShellTools(registry, opts) {
|
|
|
1138
1329
|
return () => snapshot2;
|
|
1139
1330
|
})();
|
|
1140
1331
|
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
1332
|
+
const isAutoAllowed = (cmd) => isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths, {
|
|
1333
|
+
includeBuiltin: opts.requireApprovalForBuiltin !== true
|
|
1334
|
+
});
|
|
1335
|
+
const approvalPolicy = opts.requireApprovalForBuiltin ? "Model-requested shell commands ask the user before they run unless the user has explicitly always-allowed a project prefix or yolo mode is active. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify." : "Allowlisted read-only / test / lint / typecheck commands run immediately; anything that could mutate state, install deps, or touch the network is gated by user confirmation. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.";
|
|
1141
1336
|
registry.register({
|
|
1142
1337
|
name: "run_command",
|
|
1143
|
-
description:
|
|
1338
|
+
description: `Run a shell command in the project root; returns combined stdout+stderr. ${approvalPolicy}
|
|
1339
|
+
|
|
1340
|
+
Constraints (no real shell \u2014 argv is parsed natively for cross-platform parity):
|
|
1341
|
+
\u2022 Supported: chain ops \`|\` / \`||\` / \`&&\` / \`;\` (each segment allowlist-checked individually), file redirects \`>\` / \`>>\` / \`<\` / \`2>\` / \`2>>\` / \`2>&1\` / \`&>\` (target paths resolve relative to project root, max one redirect per fd per segment).
|
|
1342
|
+
\u2022 NOT supported: background \`&\`, heredoc \`<<\`, command substitution \`$(\u2026)\`, subshells \`(\u2026)\`, process substitution \`<(\u2026)\`, \`$VAR\` env expansion, glob expansion. To pass an operator char as literal arg, quote it (\`grep "a|b" file\`).
|
|
1343
|
+
\u2022 \`cd\` does NOT persist \u2014 between calls OR within a chain like \`cd dir && cmd\`. Use the binary's own cwd flag: \`npm --prefix <dir>\`, \`git -C <dir>\`, \`cargo -C <dir>\`, \`pytest <dir>/tests\`.
|
|
1344
|
+
\u2022 Filter at source \u2014 unbounded output (\`netstat -ano\`, \`find /\`) wastes tokens. Use \`grep -c\`, \`wc -l\`, narrower paths, etc.`,
|
|
1144
1345
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
1145
1346
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
1146
1347
|
// during planning. Anything that would otherwise trigger a
|
|
@@ -1169,12 +1370,14 @@ function registerShellTools(registry, opts) {
|
|
|
1169
1370
|
const cmd = args.command.trim();
|
|
1170
1371
|
if (!cmd) throw new Error("run_command: empty command");
|
|
1171
1372
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
1172
|
-
if (!isAllowAll() && !
|
|
1373
|
+
if (!isAllowAll() && !isAutoAllowed(cmd)) {
|
|
1173
1374
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
1375
|
+
const waitStartedAt = Date.now();
|
|
1174
1376
|
const choice = await gate.ask({
|
|
1175
1377
|
kind: "run_command",
|
|
1176
1378
|
payload: { command: cmd, cwd: rootDir, timeoutSec: effectiveTimeout }
|
|
1177
1379
|
});
|
|
1380
|
+
ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
|
|
1178
1381
|
if (choice.type === "deny") {
|
|
1179
1382
|
throw new Error(
|
|
1180
1383
|
`user denied: ${cmd}${choice.denyContext ? ` \u2014 ${choice.denyContext}` : ""}`
|
|
@@ -1218,12 +1421,14 @@ function registerShellTools(registry, opts) {
|
|
|
1218
1421
|
const cmd = args.command.trim();
|
|
1219
1422
|
if (!cmd) throw new Error("run_background: empty command");
|
|
1220
1423
|
const cwd = resolveCwdInsideRoot(rootDir, args.cwd);
|
|
1221
|
-
if (!isAllowAll() && !
|
|
1424
|
+
if (!isAllowAll() && !isAutoAllowed(cmd)) {
|
|
1222
1425
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
1426
|
+
const waitStartedAt = Date.now();
|
|
1223
1427
|
const choice = await gate.ask({
|
|
1224
1428
|
kind: "run_background",
|
|
1225
1429
|
payload: { command: cmd, cwd, waitSec: args.waitSec }
|
|
1226
1430
|
});
|
|
1431
|
+
ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
|
|
1227
1432
|
if (choice.type === "deny") {
|
|
1228
1433
|
throw new Error(
|
|
1229
1434
|
`user denied: ${cmd}${choice.denyContext ? ` \u2014 ${choice.denyContext}` : ""}`
|
|
@@ -1342,11 +1547,11 @@ function registerShellTools(registry, opts) {
|
|
|
1342
1547
|
return registry;
|
|
1343
1548
|
}
|
|
1344
1549
|
function resolveCwdInsideRoot(rootDir, raw) {
|
|
1345
|
-
const root =
|
|
1550
|
+
const root = pathMod5.resolve(rootDir);
|
|
1346
1551
|
if (!raw || !raw.trim()) return root;
|
|
1347
|
-
const resolved =
|
|
1348
|
-
const rel =
|
|
1349
|
-
if (rel.startsWith("..") ||
|
|
1552
|
+
const resolved = pathMod5.resolve(root, raw);
|
|
1553
|
+
const rel = pathMod5.relative(root, resolved);
|
|
1554
|
+
if (rel.startsWith("..") || pathMod5.isAbsolute(rel)) {
|
|
1350
1555
|
throw new Error(
|
|
1351
1556
|
`run_background: cwd "${raw}" resolves outside the workspace root (${root}). Pass a workspace-relative path.`
|
|
1352
1557
|
);
|
|
@@ -1453,7 +1658,7 @@ var JobRegistry = class {
|
|
|
1453
1658
|
const maxBytes = opts.maxBufferBytes ?? DEFAULT_OUTPUT_CAP_BYTES;
|
|
1454
1659
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
1455
1660
|
const spawnOpts = {
|
|
1456
|
-
cwd:
|
|
1661
|
+
cwd: pathMod6.resolve(opts.cwd),
|
|
1457
1662
|
shell: false,
|
|
1458
1663
|
windowsHide: true,
|
|
1459
1664
|
env: process.env,
|
|
@@ -1754,162 +1959,6 @@ function latestOutputSince(before, after) {
|
|
|
1754
1959
|
return after;
|
|
1755
1960
|
}
|
|
1756
1961
|
|
|
1757
|
-
// src/tools/fs/edit.ts
|
|
1758
|
-
import { promises as fs } from "fs";
|
|
1759
|
-
import * as pathMod6 from "path";
|
|
1760
|
-
function displayRel(rootDir, full) {
|
|
1761
|
-
return pathMod6.relative(rootDir, full).replaceAll("\\", "/");
|
|
1762
|
-
}
|
|
1763
|
-
async function applyEdit(rootDir, abs, args) {
|
|
1764
|
-
if (args.search.length === 0) {
|
|
1765
|
-
throw new Error("edit_file: search cannot be empty");
|
|
1766
|
-
}
|
|
1767
|
-
const before = await fs.readFile(abs, "utf8");
|
|
1768
|
-
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
1769
|
-
const adaptedSearch = args.search.replace(/\r?\n/g, le);
|
|
1770
|
-
const adaptedReplace = args.replace.replace(/\r?\n/g, le);
|
|
1771
|
-
const firstIdx = before.indexOf(adaptedSearch);
|
|
1772
|
-
if (firstIdx < 0) {
|
|
1773
|
-
throw new Error(`edit_file: search text not found in ${displayRel(rootDir, abs)}`);
|
|
1774
|
-
}
|
|
1775
|
-
const nextIdx = before.indexOf(adaptedSearch, firstIdx + 1);
|
|
1776
|
-
if (nextIdx >= 0) {
|
|
1777
|
-
throw new Error(
|
|
1778
|
-
`edit_file: search text appears multiple times in ${displayRel(rootDir, abs)} \u2014 include more context to disambiguate`
|
|
1779
|
-
);
|
|
1780
|
-
}
|
|
1781
|
-
const after = before.slice(0, firstIdx) + adaptedReplace + before.slice(firstIdx + adaptedSearch.length);
|
|
1782
|
-
await fs.writeFile(abs, after, "utf8");
|
|
1783
|
-
const rel = displayRel(rootDir, abs);
|
|
1784
|
-
const header = `edited ${rel} (${adaptedSearch.length}\u2192${adaptedReplace.length} chars)`;
|
|
1785
|
-
const startLine = before.slice(0, firstIdx).split(/\r?\n/).length;
|
|
1786
|
-
const diff = renderEditDiff(adaptedSearch, adaptedReplace, startLine);
|
|
1787
|
-
return `${header}
|
|
1788
|
-
${diff}`;
|
|
1789
|
-
}
|
|
1790
|
-
async function applyMultiEdit(rootDir, edits) {
|
|
1791
|
-
if (edits.length === 0) {
|
|
1792
|
-
throw new Error("multi_edit: edits must contain at least one entry");
|
|
1793
|
-
}
|
|
1794
|
-
const filesByPath = /* @__PURE__ */ new Map();
|
|
1795
|
-
for (let i = 0; i < edits.length; i++) {
|
|
1796
|
-
const e = edits[i];
|
|
1797
|
-
if (typeof e.abs !== "string" || e.abs.length === 0) {
|
|
1798
|
-
throw new Error(`multi_edit: edit #${i + 1} requires a string \`path\` (no edits applied)`);
|
|
1799
|
-
}
|
|
1800
|
-
if (typeof e.search !== "string") {
|
|
1801
|
-
throw new Error(`multi_edit: edit #${i + 1} requires a string \`search\` (no edits applied)`);
|
|
1802
|
-
}
|
|
1803
|
-
if (typeof e.replace !== "string") {
|
|
1804
|
-
throw new Error(
|
|
1805
|
-
`multi_edit: edit #${i + 1} requires a string \`replace\` (no edits applied)`
|
|
1806
|
-
);
|
|
1807
|
-
}
|
|
1808
|
-
const rel = displayRel(rootDir, e.abs);
|
|
1809
|
-
if (e.search.length === 0) {
|
|
1810
|
-
throw new Error(
|
|
1811
|
-
`multi_edit: edit #${i + 1} (${rel}) search cannot be empty (no edits applied)`
|
|
1812
|
-
);
|
|
1813
|
-
}
|
|
1814
|
-
let state = filesByPath.get(e.abs);
|
|
1815
|
-
if (!state) {
|
|
1816
|
-
let before;
|
|
1817
|
-
try {
|
|
1818
|
-
before = await fs.readFile(e.abs, "utf8");
|
|
1819
|
-
} catch (err) {
|
|
1820
|
-
throw new Error(
|
|
1821
|
-
`multi_edit: edit #${i + 1} cannot read ${rel}: ${err.message} (no edits applied)`
|
|
1822
|
-
);
|
|
1823
|
-
}
|
|
1824
|
-
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
1825
|
-
state = { buf: before, le, hunks: [], deltaChars: 0, touched: 0 };
|
|
1826
|
-
filesByPath.set(e.abs, state);
|
|
1827
|
-
}
|
|
1828
|
-
const adaptedSearch = e.search.replace(/\r?\n/g, state.le);
|
|
1829
|
-
const adaptedReplace = e.replace.replace(/\r?\n/g, state.le);
|
|
1830
|
-
const firstIdx = state.buf.indexOf(adaptedSearch);
|
|
1831
|
-
if (firstIdx < 0) {
|
|
1832
|
-
throw new Error(
|
|
1833
|
-
`multi_edit: edit #${i + 1} search text not found in ${rel} \u2014 no edits applied (multi_edit is atomic)`
|
|
1834
|
-
);
|
|
1835
|
-
}
|
|
1836
|
-
const nextIdx = state.buf.indexOf(adaptedSearch, firstIdx + 1);
|
|
1837
|
-
if (nextIdx >= 0) {
|
|
1838
|
-
throw new Error(
|
|
1839
|
-
`multi_edit: edit #${i + 1} search text appears multiple times in ${rel} \u2014 include more context to disambiguate (no edits applied)`
|
|
1840
|
-
);
|
|
1841
|
-
}
|
|
1842
|
-
const startLine = state.buf.slice(0, firstIdx).split(/\r?\n/).length;
|
|
1843
|
-
state.buf = state.buf.slice(0, firstIdx) + adaptedReplace + state.buf.slice(firstIdx + adaptedSearch.length);
|
|
1844
|
-
state.hunks.push(`# ${rel}
|
|
1845
|
-
${renderEditDiff(adaptedSearch, adaptedReplace, startLine)}`);
|
|
1846
|
-
state.deltaChars += adaptedReplace.length - adaptedSearch.length;
|
|
1847
|
-
state.touched++;
|
|
1848
|
-
}
|
|
1849
|
-
for (const [abs, state] of filesByPath) {
|
|
1850
|
-
await fs.writeFile(abs, state.buf, "utf8");
|
|
1851
|
-
}
|
|
1852
|
-
const fileCount = filesByPath.size;
|
|
1853
|
-
const editCount = edits.length;
|
|
1854
|
-
let totalDelta = 0;
|
|
1855
|
-
const allHunks = [];
|
|
1856
|
-
for (const state of filesByPath.values()) {
|
|
1857
|
-
totalDelta += state.deltaChars;
|
|
1858
|
-
allHunks.push(...state.hunks);
|
|
1859
|
-
}
|
|
1860
|
-
const sign = totalDelta >= 0 ? "+" : "";
|
|
1861
|
-
const editNoun = editCount === 1 ? "edit" : "edits";
|
|
1862
|
-
const fileNoun = fileCount === 1 ? "file" : "files";
|
|
1863
|
-
const header = `multi_edit: applied ${editCount} ${editNoun} across ${fileCount} ${fileNoun} (${sign}${totalDelta} chars)`;
|
|
1864
|
-
return `${header}
|
|
1865
|
-
${allHunks.join("\n")}`;
|
|
1866
|
-
}
|
|
1867
|
-
function renderEditDiff(search, replace, startLine) {
|
|
1868
|
-
const a = search.split(/\r?\n/);
|
|
1869
|
-
const b = replace.split(/\r?\n/);
|
|
1870
|
-
const diff = lineDiff(a, b);
|
|
1871
|
-
const hunk = `@@ -${startLine},${a.length} +${startLine},${b.length} @@`;
|
|
1872
|
-
const body = diff.map((d) => `${d.op === " " ? " " : d.op} ${d.line}`).join("\n");
|
|
1873
|
-
return `${hunk}
|
|
1874
|
-
${body}`;
|
|
1875
|
-
}
|
|
1876
|
-
function lineDiff(a, b) {
|
|
1877
|
-
const n = a.length;
|
|
1878
|
-
const m = b.length;
|
|
1879
|
-
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
1880
|
-
for (let i2 = 1; i2 <= n; i2++) {
|
|
1881
|
-
for (let j2 = 1; j2 <= m; j2++) {
|
|
1882
|
-
if (a[i2 - 1] === b[j2 - 1]) dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
1883
|
-
else dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
const out = [];
|
|
1887
|
-
let i = n;
|
|
1888
|
-
let j = m;
|
|
1889
|
-
while (i > 0 && j > 0) {
|
|
1890
|
-
if (a[i - 1] === b[j - 1]) {
|
|
1891
|
-
out.unshift({ op: " ", line: a[i - 1] });
|
|
1892
|
-
i--;
|
|
1893
|
-
j--;
|
|
1894
|
-
} else if ((dp[i - 1][j] ?? 0) > (dp[i][j - 1] ?? 0)) {
|
|
1895
|
-
out.unshift({ op: "-", line: a[i - 1] });
|
|
1896
|
-
i--;
|
|
1897
|
-
} else {
|
|
1898
|
-
out.unshift({ op: "+", line: b[j - 1] });
|
|
1899
|
-
j--;
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
while (i > 0) {
|
|
1903
|
-
out.unshift({ op: "-", line: a[i - 1] });
|
|
1904
|
-
i--;
|
|
1905
|
-
}
|
|
1906
|
-
while (j > 0) {
|
|
1907
|
-
out.unshift({ op: "+", line: b[j - 1] });
|
|
1908
|
-
j--;
|
|
1909
|
-
}
|
|
1910
|
-
return out;
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
1962
|
export {
|
|
1914
1963
|
pauseGate,
|
|
1915
1964
|
applyEdit,
|
|
@@ -1921,4 +1970,4 @@ export {
|
|
|
1921
1970
|
registerShellTools,
|
|
1922
1971
|
formatCommandResult
|
|
1923
1972
|
};
|
|
1924
|
-
//# sourceMappingURL=chunk-
|
|
1973
|
+
//# sourceMappingURL=chunk-MXUSER5C.js.map
|