@cydm/pie 1.0.47 → 1.0.49
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 +21 -1
- package/dist/builtin/extensions/ask-user/index.js +1 -1
- package/dist/builtin/extensions/plan-mode/index.js +1 -1
- package/dist/builtin/extensions/subagent/index.js +2 -2
- package/dist/builtin/extensions/todo/index.js +1 -1
- package/dist/chunks/{chunk-IJMP3JTX.js → chunk-7RYOIXA2.js} +212 -43
- package/dist/chunks/{chunk-U4B6RJ6Z.js → chunk-J6PEH6ST.js} +1 -1
- package/dist/cli.js +446 -181
- package/package.json +2 -2
- package/dist/builtin/extensions/kimi-attachments/index.js +0 -47
- package/dist/builtin/extensions/kimi-attachments/package.json +0 -11
package/README.md
CHANGED
|
@@ -218,7 +218,10 @@ Pie uses a hierarchical slash-command menu. The current built-in commands includ
|
|
|
218
218
|
| `pie doctor [--json]` | Diagnose local config, model setup, writable paths, browser automation, and Unity bridge hints |
|
|
219
219
|
| `pie models init` | Create a starter `~/.pie/models.json` |
|
|
220
220
|
| `pie models validate` | Validate `~/.pie/models.json` |
|
|
221
|
-
| `pie permissions` | Explain tool and shell safety defaults |
|
|
221
|
+
| `pie permissions` | Explain tool and shell safety defaults (`--json` returns the structured safety model) |
|
|
222
|
+
| `pie capabilities [--json]` | Report the command surface, flags, exit codes, and output schema for agents |
|
|
223
|
+
| `pie sessions list/read/rename/rewind/fork` | Inspect and manage stored sessions non-interactively (each supports `--json` and `--help`) |
|
|
224
|
+
| `pie extensions list/enable/disable` | Inspect and toggle extensions non-interactively |
|
|
222
225
|
| `/sessions/compact` | Summarize history and create a compact checkpoint |
|
|
223
226
|
| `/sessions/new` | Start a new session |
|
|
224
227
|
| `/sessions/resume` | Resume a different session |
|
|
@@ -355,6 +358,23 @@ pie "summarize this file" --json-output
|
|
|
355
358
|
pie "continue previous task" --session-id my-session
|
|
356
359
|
```
|
|
357
360
|
|
|
361
|
+
### Machine-Readable Output (Agents And Hosts)
|
|
362
|
+
|
|
363
|
+
Every management command (`sessions`, `doctor`, `models`, `extensions`, `permissions`, `capabilities`) emits one compact JSON envelope when given `--json`:
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{"v":1,"ok":true,"kind":"sessions.list","data":[...],"meta":{"count":10,"total":42,"nextCursor":"..."},"help":["..."]}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
- `v` is a constant output-schema generation marker; `kind` identifies the command.
|
|
370
|
+
- `meta` carries pagination (`nextCursor`) and aggregates; `help[]` lists concrete next steps when they exist and never duplicates large payloads.
|
|
371
|
+
- Errors are structured on stdout: `{"v":1,"ok":false,"kind":"...","error":{"code":"session_not_found","message":"...","hint":"..."}}`.
|
|
372
|
+
- Exit codes: `0` success, `1` business failure, `2` usage error. Unknown flags fail loudly; every subcommand supports `--help`.
|
|
373
|
+
- `sessions list --json` defaults to the current project (`--all` lists every project). Rows are minimal (`id`, `name`, `updatedAt`, `count`) with `--fields` and `--full` opt-ins; `sessions read` truncates content by default with the same opt-ins.
|
|
374
|
+
- JSONL protocol mode (`--json-events`) identifies entries by `entryId` only (the legacy `checkpointId` alias is gone) and caps large tool results and turn summaries by default; pass `--event-detail full` for untruncated payloads. Truncation always carries `truncated`/`fullLength` markers.
|
|
375
|
+
|
|
376
|
+
Run `pie capabilities --json` to discover the full command surface, flags, and exit-code contract programmatically.
|
|
377
|
+
|
|
358
378
|
## Terminal Notes
|
|
359
379
|
|
|
360
380
|
For the best multiline editing experience, use a terminal with good modified-key support, such as:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
createAskUserCapability
|
|
4
|
-
} from "../../../chunks/chunk-
|
|
4
|
+
} from "../../../chunks/chunk-7RYOIXA2.js";
|
|
5
5
|
import "../../../chunks/chunk-FKAO25XH.js";
|
|
6
6
|
import "../../../chunks/chunk-K6ZOCJ4V.js";
|
|
7
7
|
import "../../../chunks/chunk-LZQXQOPJ.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
createCliHostCapabilities
|
|
4
|
-
} from "../../../chunks/chunk-
|
|
4
|
+
} from "../../../chunks/chunk-J6PEH6ST.js";
|
|
5
5
|
import {
|
|
6
6
|
createSharedFileSystemTools,
|
|
7
7
|
createSubagentCapability
|
|
8
|
-
} from "../../../chunks/chunk-
|
|
8
|
+
} from "../../../chunks/chunk-7RYOIXA2.js";
|
|
9
9
|
import "../../../chunks/chunk-FKAO25XH.js";
|
|
10
10
|
import "../../../chunks/chunk-K6ZOCJ4V.js";
|
|
11
11
|
import "../../../chunks/chunk-LZQXQOPJ.js";
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
executeManageTodoList,
|
|
8
8
|
executionStateToTodos,
|
|
9
9
|
restoreExecutionState
|
|
10
|
-
} from "../../../chunks/chunk-
|
|
10
|
+
} from "../../../chunks/chunk-7RYOIXA2.js";
|
|
11
11
|
import "../../../chunks/chunk-FKAO25XH.js";
|
|
12
12
|
import "../../../chunks/chunk-K6ZOCJ4V.js";
|
|
13
13
|
import "../../../chunks/chunk-LZQXQOPJ.js";
|
|
@@ -7869,6 +7869,193 @@ function fuzzyFindText(content, oldText) {
|
|
|
7869
7869
|
function stripBom(content) {
|
|
7870
7870
|
return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
|
|
7871
7871
|
}
|
|
7872
|
+
function findEditMatch(content, oldText, newText) {
|
|
7873
|
+
const direct = fuzzyFindText(content, oldText);
|
|
7874
|
+
if (direct.found) {
|
|
7875
|
+
return { found: true, occurrences: direct.occurrences, matches: direct.matches, matchKind: direct.matchKind };
|
|
7876
|
+
}
|
|
7877
|
+
return findWhitespaceRelaxedMatch(content, oldText, newText);
|
|
7878
|
+
}
|
|
7879
|
+
function leadingWhitespace(line) {
|
|
7880
|
+
const match2 = line.match(/^[ \t]*/);
|
|
7881
|
+
return match2 ? match2[0] : "";
|
|
7882
|
+
}
|
|
7883
|
+
function findRelaxedWindows(fileLines, qLines) {
|
|
7884
|
+
const firstNonBlank = qLines.findIndex((line) => line.trim() !== "");
|
|
7885
|
+
if (firstNonBlank === -1) return [];
|
|
7886
|
+
const anchor = qLines[firstNonBlank].trim();
|
|
7887
|
+
if (!anchor) return [];
|
|
7888
|
+
const windows = [];
|
|
7889
|
+
for (let anchorFileLine = 0; anchorFileLine < fileLines.length; anchorFileLine++) {
|
|
7890
|
+
if (fileLines[anchorFileLine].trim() !== anchor) continue;
|
|
7891
|
+
const startLine = anchorFileLine - firstNonBlank;
|
|
7892
|
+
if (startLine < 0) continue;
|
|
7893
|
+
let fileLine = startLine;
|
|
7894
|
+
let ok = true;
|
|
7895
|
+
let lastPaired = -1;
|
|
7896
|
+
const qLineToFileLine = /* @__PURE__ */ new Map();
|
|
7897
|
+
for (let qLine = 0; qLine < qLines.length; qLine++) {
|
|
7898
|
+
if (fileLine >= fileLines.length) {
|
|
7899
|
+
ok = false;
|
|
7900
|
+
break;
|
|
7901
|
+
}
|
|
7902
|
+
const q = qLines[qLine];
|
|
7903
|
+
const f = fileLines[fileLine];
|
|
7904
|
+
if (q.trim() === "") {
|
|
7905
|
+
if (f.trim() !== "") {
|
|
7906
|
+
ok = false;
|
|
7907
|
+
break;
|
|
7908
|
+
}
|
|
7909
|
+
} else {
|
|
7910
|
+
if (f.trim() !== q.trim()) {
|
|
7911
|
+
ok = false;
|
|
7912
|
+
break;
|
|
7913
|
+
}
|
|
7914
|
+
qLineToFileLine.set(qLine, fileLine);
|
|
7915
|
+
lastPaired = fileLine;
|
|
7916
|
+
}
|
|
7917
|
+
fileLine++;
|
|
7918
|
+
}
|
|
7919
|
+
if (ok && lastPaired !== -1) windows.push({ startLine, endLine: lastPaired, qLineToFileLine });
|
|
7920
|
+
}
|
|
7921
|
+
return windows;
|
|
7922
|
+
}
|
|
7923
|
+
function lineSpanToCharSpan(fileLines, startLine, endLine) {
|
|
7924
|
+
let index = 0;
|
|
7925
|
+
for (let i = 0; i < startLine; i++) index += fileLines[i].length + 1;
|
|
7926
|
+
let matchLength = 0;
|
|
7927
|
+
for (let i = startLine; i <= endLine; i++) matchLength += fileLines[i].length + (i < endLine ? 1 : 0);
|
|
7928
|
+
return { index, matchLength };
|
|
7929
|
+
}
|
|
7930
|
+
function reindentNewText(newText, qLines, fileLines, window) {
|
|
7931
|
+
const newLines = newText.split("\n");
|
|
7932
|
+
const qIndent = (qLine) => {
|
|
7933
|
+
const fileLine = window.qLineToFileLine.get(qLine);
|
|
7934
|
+
return fileLine === void 0 ? "" : leadingWhitespace(fileLines[fileLine]);
|
|
7935
|
+
};
|
|
7936
|
+
const qIdx = [];
|
|
7937
|
+
for (let i = 0; i < qLines.length; i++) if (qLines[i].trim() !== "") qIdx.push(i);
|
|
7938
|
+
const nIdx = [];
|
|
7939
|
+
const nPosOfLine = /* @__PURE__ */ new Map();
|
|
7940
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
7941
|
+
if (newLines[i].trim() === "") continue;
|
|
7942
|
+
nPosOfLine.set(i, nIdx.length);
|
|
7943
|
+
nIdx.push(i);
|
|
7944
|
+
}
|
|
7945
|
+
if (nIdx.length === 0) return newText;
|
|
7946
|
+
const counterpart = /* @__PURE__ */ new Map();
|
|
7947
|
+
let p = 0;
|
|
7948
|
+
while (p < qIdx.length && p < nIdx.length && qLines[qIdx[p]].trim() === newLines[nIdx[p]].trim()) {
|
|
7949
|
+
counterpart.set(p, p);
|
|
7950
|
+
p++;
|
|
7951
|
+
}
|
|
7952
|
+
let s = 0;
|
|
7953
|
+
while (s < qIdx.length - p && s < nIdx.length - p && qLines[qIdx[qIdx.length - 1 - s]].trim() === newLines[nIdx[nIdx.length - 1 - s]].trim()) {
|
|
7954
|
+
counterpart.set(nIdx.length - 1 - s, qIdx.length - 1 - s);
|
|
7955
|
+
s++;
|
|
7956
|
+
}
|
|
7957
|
+
const midQ = [];
|
|
7958
|
+
for (let pos = p; pos < qIdx.length - s; pos++) midQ.push(pos);
|
|
7959
|
+
const midN = [];
|
|
7960
|
+
for (let pos = p; pos < nIdx.length - s; pos++) midN.push(pos);
|
|
7961
|
+
const pairRegion = (qLo, qHi, nLo, nHi) => {
|
|
7962
|
+
const qCount = qHi - qLo;
|
|
7963
|
+
const nCount = nHi - nLo;
|
|
7964
|
+
const paired = Math.min(qCount, nCount);
|
|
7965
|
+
for (let k = 0; k < paired; k++) counterpart.set(nLo + k, qLo + k);
|
|
7966
|
+
const fallbackQ = qCount > 0 ? qHi - 1 : Math.max(qLo - 1, 0);
|
|
7967
|
+
for (let k = paired; k < nCount; k++) counterpart.set(nLo + k, fallbackQ);
|
|
7968
|
+
};
|
|
7969
|
+
const qTextCounts = /* @__PURE__ */ new Map();
|
|
7970
|
+
for (const qPos of midQ) {
|
|
7971
|
+
const t = qLines[qIdx[qPos]].trim();
|
|
7972
|
+
qTextCounts.set(t, (qTextCounts.get(t) ?? 0) + 1);
|
|
7973
|
+
}
|
|
7974
|
+
const qPosByText = /* @__PURE__ */ new Map();
|
|
7975
|
+
for (const qPos of midQ) {
|
|
7976
|
+
const t = qLines[qIdx[qPos]].trim();
|
|
7977
|
+
if (qTextCounts.get(t) === 1) qPosByText.set(t, qPos);
|
|
7978
|
+
}
|
|
7979
|
+
const nTextCounts = /* @__PURE__ */ new Map();
|
|
7980
|
+
for (const nPos of midN) {
|
|
7981
|
+
const t = newLines[nIdx[nPos]].trim();
|
|
7982
|
+
nTextCounts.set(t, (nTextCounts.get(t) ?? 0) + 1);
|
|
7983
|
+
}
|
|
7984
|
+
let cursorQ = p;
|
|
7985
|
+
let cursorN = p;
|
|
7986
|
+
for (const nPos of midN) {
|
|
7987
|
+
const t = newLines[nIdx[nPos]].trim();
|
|
7988
|
+
const qPos = qPosByText.get(t);
|
|
7989
|
+
if (nTextCounts.get(t) !== 1 || qPos === void 0 || qPos < cursorQ) continue;
|
|
7990
|
+
pairRegion(cursorQ, qPos, cursorN, nPos);
|
|
7991
|
+
counterpart.set(nPos, qPos);
|
|
7992
|
+
cursorQ = qPos + 1;
|
|
7993
|
+
cursorN = nPos + 1;
|
|
7994
|
+
}
|
|
7995
|
+
pairRegion(cursorQ, qIdx.length - s, cursorN, nIdx.length - s);
|
|
7996
|
+
const result = [];
|
|
7997
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
7998
|
+
if (newLines[i].trim() === "") {
|
|
7999
|
+
result.push("");
|
|
8000
|
+
continue;
|
|
8001
|
+
}
|
|
8002
|
+
const nPos = nPosOfLine.get(i) ?? 0;
|
|
8003
|
+
const qPos = counterpart.get(nPos) ?? 0;
|
|
8004
|
+
result.push(qIndent(qIdx[qPos] ?? qIdx[0]) + newLines[i].trim());
|
|
8005
|
+
}
|
|
8006
|
+
return result.join("\n");
|
|
8007
|
+
}
|
|
8008
|
+
function findWhitespaceRelaxedMatch(content, oldText, newText) {
|
|
8009
|
+
const fileLines = content.split("\n");
|
|
8010
|
+
const qLines = oldText.split("\n");
|
|
8011
|
+
const windows = findRelaxedWindows(fileLines, qLines);
|
|
8012
|
+
if (windows.length === 0) {
|
|
8013
|
+
return { found: false, occurrences: 0, matches: [], matchKind: "none" };
|
|
8014
|
+
}
|
|
8015
|
+
const matches = windows.map((window) => {
|
|
8016
|
+
const span = lineSpanToCharSpan(fileLines, window.startLine, window.endLine);
|
|
8017
|
+
return { ...span, replacementText: reindentNewText(newText, qLines, fileLines, window) };
|
|
8018
|
+
});
|
|
8019
|
+
return { found: true, occurrences: matches.length, matches, matchKind: "whitespace-relaxed" };
|
|
8020
|
+
}
|
|
8021
|
+
function describeClosestRegion(content, oldText) {
|
|
8022
|
+
const qLines = oldText.split("\n");
|
|
8023
|
+
const firstNonBlank = qLines.findIndex((line) => line.trim() !== "");
|
|
8024
|
+
if (firstNonBlank === -1) return void 0;
|
|
8025
|
+
const anchor = qLines[firstNonBlank].trim();
|
|
8026
|
+
const fileLines = content.split("\n");
|
|
8027
|
+
const clip = (text) => text.length > 90 ? `${text.slice(0, 90)}\u2026` : text;
|
|
8028
|
+
let best;
|
|
8029
|
+
for (let i = 0; i < fileLines.length; i++) {
|
|
8030
|
+
if (fileLines[i].trim() !== anchor) continue;
|
|
8031
|
+
let matched = 0;
|
|
8032
|
+
let fi = i;
|
|
8033
|
+
let firstDiff;
|
|
8034
|
+
for (let qi = firstNonBlank; qi < qLines.length; qi++) {
|
|
8035
|
+
const q = qLines[qi].trim();
|
|
8036
|
+
if (!q) continue;
|
|
8037
|
+
while (fi < fileLines.length && !fileLines[fi].trim()) fi++;
|
|
8038
|
+
if (fi >= fileLines.length || fileLines[fi].trim() !== q) {
|
|
8039
|
+
firstDiff = { fileNo: fi + 1, fileLine: fileLines[fi] ?? "<end of file>", qLine: qLines[qi] };
|
|
8040
|
+
break;
|
|
8041
|
+
}
|
|
8042
|
+
matched++;
|
|
8043
|
+
fi++;
|
|
8044
|
+
}
|
|
8045
|
+
if (!best || matched > best.matched) best = { start: i, matched, firstDiff };
|
|
8046
|
+
}
|
|
8047
|
+
const total = qLines.filter((line) => line.trim() !== "").length;
|
|
8048
|
+
if (!best) {
|
|
8049
|
+
return `No line matching ${JSON.stringify(clip(anchor))} exists in this file \u2014 the text may live in a different file, or the file changed since it was read.`;
|
|
8050
|
+
}
|
|
8051
|
+
const parts = [`Closest match: file lines ${best.start + 1}\u2013${best.start + total} (${best.matched}/${total} lines matched by trimmed content).`];
|
|
8052
|
+
if (best.firstDiff) {
|
|
8053
|
+
parts.push(`First difference at line ${best.firstDiff.fileNo}: file has ${JSON.stringify(clip(best.firstDiff.fileLine.trim()))}, edit expected ${JSON.stringify(clip(best.firstDiff.qLine.trim()))}.`);
|
|
8054
|
+
} else {
|
|
8055
|
+
parts.push("All non-empty lines match by trimmed content \u2014 the mismatch is leading whitespace or blank-line placement.");
|
|
8056
|
+
}
|
|
8057
|
+
return parts.join(" ");
|
|
8058
|
+
}
|
|
7872
8059
|
function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
7873
8060
|
const oldLines = normalizeToLF(stripBom(oldContent).text).split("\n");
|
|
7874
8061
|
const newLines = normalizeToLF(stripBom(newContent).text).split("\n");
|
|
@@ -7982,7 +8169,8 @@ function createEditErrorResult(params) {
|
|
|
7982
8169
|
const detailLines = [
|
|
7983
8170
|
params.message,
|
|
7984
8171
|
params.recoveryHint,
|
|
7985
|
-
params.alreadyAppliedCandidate ? "Diagnostic: newText already appears in the current file, but edit_file did not treat that as success because it may be an unrelated occurrence." : ""
|
|
8172
|
+
params.alreadyAppliedCandidate ? "Diagnostic: newText already appears in the current file, but edit_file did not treat that as success because it may be an unrelated occurrence." : "",
|
|
8173
|
+
params.closestMatch ? `Diagnostic: ${params.closestMatch}` : ""
|
|
7986
8174
|
].filter(Boolean);
|
|
7987
8175
|
return {
|
|
7988
8176
|
content: [{ type: "text", text: detailLines.join("\n") }],
|
|
@@ -8034,7 +8222,7 @@ function createEditTool(cwd, options) {
|
|
|
8034
8222
|
newText: Type.String({ description: "Replacement text", maxLength: MAX_EDIT_INPUT_CHARS }),
|
|
8035
8223
|
replaceAll: Type.Optional(Type.Boolean({ description: "Replace every occurrence" }))
|
|
8036
8224
|
}), {
|
|
8037
|
-
description: "
|
|
8225
|
+
description: "Replacements applied in order: each edit matches the file as left by the previous edit, so later edits can build on earlier ones. Whole-line indentation drift is tolerated \u2014 matches locate by trimmed content and the replacement adopts the file's indentation.",
|
|
8038
8226
|
minItems: 1,
|
|
8039
8227
|
maxItems: MAX_EDIT_BLOCKS
|
|
8040
8228
|
})
|
|
@@ -8043,7 +8231,7 @@ function createEditTool(cwd, options) {
|
|
|
8043
8231
|
return {
|
|
8044
8232
|
name: "edit",
|
|
8045
8233
|
label: "edit",
|
|
8046
|
-
description: `Make precise exact-text replacements in one file. For multiple changes in the same file, put all
|
|
8234
|
+
description: `Make precise exact-text replacements in one file. For multiple changes in the same file, put all replacements in one edits[] call \u2014 they apply in order, so later edits can target what earlier ones just changed. ${pathResolutionDescription}`,
|
|
8047
8235
|
parameters: editSchema,
|
|
8048
8236
|
execute: async (_toolCallId, params, signal) => {
|
|
8049
8237
|
const { path: path4, root } = params;
|
|
@@ -8087,7 +8275,9 @@ function createEditTool(cwd, options) {
|
|
|
8087
8275
|
const { bom, text: content } = stripBom(rawContent);
|
|
8088
8276
|
const originalEnding = detectLineEnding(content);
|
|
8089
8277
|
const normalizedContent = normalizeToLF(content);
|
|
8090
|
-
|
|
8278
|
+
let workingContent = normalizedContent;
|
|
8279
|
+
const plannedKinds = /* @__PURE__ */ new Set();
|
|
8280
|
+
let replacedOccurrences = 0;
|
|
8091
8281
|
let totalOldOccurrences = 0;
|
|
8092
8282
|
let totalNewOccurrences = 0;
|
|
8093
8283
|
let anyReplaceAll = false;
|
|
@@ -8095,8 +8285,8 @@ function createEditTool(cwd, options) {
|
|
|
8095
8285
|
throwIfAborted();
|
|
8096
8286
|
const normalizedOldText = normalizeToLF(replacement.oldText);
|
|
8097
8287
|
const normalizedNewText = normalizeToLF(replacement.newText);
|
|
8098
|
-
const newTextOccurrences = countOccurrences(
|
|
8099
|
-
const matchResult =
|
|
8288
|
+
const newTextOccurrences = countOccurrences(workingContent, normalizedNewText);
|
|
8289
|
+
const matchResult = findEditMatch(workingContent, normalizedOldText, normalizedNewText);
|
|
8100
8290
|
totalOldOccurrences += matchResult.occurrences;
|
|
8101
8291
|
totalNewOccurrences += newTextOccurrences;
|
|
8102
8292
|
anyReplaceAll ||= replacement.replaceAll;
|
|
@@ -8111,7 +8301,8 @@ function createEditTool(cwd, options) {
|
|
|
8111
8301
|
replaceAll: replacement.replaceAll,
|
|
8112
8302
|
staleBaseLikely: newTextOccurrences > 0,
|
|
8113
8303
|
alreadyAppliedCandidate: newTextOccurrences > 0,
|
|
8114
|
-
recoveryHint: newTextOccurrences > 0 ? "The replacement may already be present. Read the current file before retrying." : "Read the latest file and retry with exact text."
|
|
8304
|
+
recoveryHint: newTextOccurrences > 0 ? "The replacement may already be present. Read the current file before retrying." : "Read the latest file and retry with exact text.",
|
|
8305
|
+
closestMatch: describeClosestRegion(workingContent, normalizedOldText)
|
|
8115
8306
|
}));
|
|
8116
8307
|
return;
|
|
8117
8308
|
}
|
|
@@ -8128,9 +8319,9 @@ function createEditTool(cwd, options) {
|
|
|
8128
8319
|
}));
|
|
8129
8320
|
return;
|
|
8130
8321
|
}
|
|
8131
|
-
const
|
|
8132
|
-
const changingMatches =
|
|
8133
|
-
(match2) =>
|
|
8322
|
+
const selected = replacement.replaceAll ? matchResult.matches : matchResult.matches.slice(0, 1);
|
|
8323
|
+
const changingMatches = selected.filter(
|
|
8324
|
+
(match2) => workingContent.slice(match2.index, match2.index + match2.matchLength) !== (match2.replacementText ?? normalizedNewText)
|
|
8134
8325
|
);
|
|
8135
8326
|
if (changingMatches.length === 0) {
|
|
8136
8327
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -8146,42 +8337,20 @@ function createEditTool(cwd, options) {
|
|
|
8146
8337
|
}));
|
|
8147
8338
|
return;
|
|
8148
8339
|
}
|
|
8149
|
-
for (const match2 of changingMatches) {
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
for (let index = 1; index < ordered.length; index++) {
|
|
8155
|
-
throwIfAborted();
|
|
8156
|
-
const previous = ordered[index - 1];
|
|
8157
|
-
const current = ordered[index];
|
|
8158
|
-
if (current.index < previous.index + previous.matchLength) {
|
|
8159
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
8160
|
-
resolve3(createEditErrorResult({
|
|
8161
|
-
message: `Edits overlap in ${path4}; no changes were made.`,
|
|
8162
|
-
matchKind: "mixed",
|
|
8163
|
-
errorReason: "overlapping_edits",
|
|
8164
|
-
oldTextOccurrences: totalOldOccurrences,
|
|
8165
|
-
newTextOccurrences: totalNewOccurrences,
|
|
8166
|
-
replaceAll: anyReplaceAll,
|
|
8167
|
-
recoveryHint: "Merge overlapping replacements into one exact edit."
|
|
8168
|
-
}));
|
|
8169
|
-
return;
|
|
8340
|
+
for (const match2 of [...changingMatches].sort((left, right) => right.index - left.index)) {
|
|
8341
|
+
throwIfAborted();
|
|
8342
|
+
workingContent = workingContent.substring(0, match2.index) + (match2.replacementText ?? normalizedNewText) + workingContent.substring(match2.index + match2.matchLength);
|
|
8343
|
+
replacedOccurrences++;
|
|
8344
|
+
if (matchResult.matchKind !== "none") plannedKinds.add(matchResult.matchKind);
|
|
8170
8345
|
}
|
|
8171
8346
|
}
|
|
8172
8347
|
throwIfAborted();
|
|
8173
|
-
|
|
8174
|
-
let newContent = baseContent;
|
|
8175
|
-
for (const match2 of [...planned].sort((left, right) => right.index - left.index)) {
|
|
8176
|
-
throwIfAborted();
|
|
8177
|
-
newContent = newContent.substring(0, match2.index) + match2.newText + newContent.substring(match2.index + match2.matchLength);
|
|
8178
|
-
}
|
|
8179
|
-
if (baseContent === newContent) {
|
|
8348
|
+
if (workingContent === normalizedContent) {
|
|
8180
8349
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
8181
8350
|
resolve3(
|
|
8182
8351
|
createEditErrorResult({
|
|
8183
8352
|
message: `No changes made to ${path4}. The replacement produced identical content.`,
|
|
8184
|
-
matchKind:
|
|
8353
|
+
matchKind: plannedKinds.size === 1 ? [...plannedKinds][0] : "mixed",
|
|
8185
8354
|
errorReason: "replacement_no_change",
|
|
8186
8355
|
oldTextOccurrences: totalOldOccurrences,
|
|
8187
8356
|
newTextOccurrences: totalNewOccurrences,
|
|
@@ -8192,6 +8361,7 @@ function createEditTool(cwd, options) {
|
|
|
8192
8361
|
);
|
|
8193
8362
|
return;
|
|
8194
8363
|
}
|
|
8364
|
+
const newContent = workingContent;
|
|
8195
8365
|
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
8196
8366
|
throwIfAborted();
|
|
8197
8367
|
await fs2.writeFile(absolutePath, finalContent, "utf-8");
|
|
@@ -8201,13 +8371,12 @@ function createEditTool(cwd, options) {
|
|
|
8201
8371
|
signal.removeEventListener("abort", onAbort);
|
|
8202
8372
|
}
|
|
8203
8373
|
const diffResult = generateDiffString(content, restoreLineEndings(newContent, originalEnding));
|
|
8204
|
-
const
|
|
8205
|
-
const matchKind = matchKinds.size === 1 ? planned[0]?.matchKind ?? "none" : "mixed";
|
|
8374
|
+
const matchKind = plannedKinds.size === 1 ? [...plannedKinds][0] : "mixed";
|
|
8206
8375
|
resolve3({
|
|
8207
8376
|
content: [
|
|
8208
8377
|
{
|
|
8209
8378
|
type: "text",
|
|
8210
|
-
text: `Changed ${path4}: ${replacements.length} edit block${replacements.length === 1 ? "" : "s"}, ${
|
|
8379
|
+
text: `Changed ${path4}: ${replacements.length} edit block${replacements.length === 1 ? "" : "s"}, ${replacedOccurrences} replacement${replacedOccurrences === 1 ? "" : "s"}. First changed line: ${diffResult.firstChangedLine}.`
|
|
8211
8380
|
},
|
|
8212
8381
|
{ type: "text", text: boundedDiff(diffResult.diff) }
|
|
8213
8382
|
],
|
|
@@ -8221,7 +8390,7 @@ function createEditTool(cwd, options) {
|
|
|
8221
8390
|
oldTextOccurrences: totalOldOccurrences,
|
|
8222
8391
|
newTextOccurrences: totalNewOccurrences,
|
|
8223
8392
|
replaceAll: anyReplaceAll,
|
|
8224
|
-
replacedOccurrences
|
|
8393
|
+
replacedOccurrences,
|
|
8225
8394
|
replacedBlocks: replacements.length
|
|
8226
8395
|
}
|
|
8227
8396
|
});
|