@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 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-IJMP3JTX.js";
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";
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-IJMP3JTX.js";
10
+ } from "../../../chunks/chunk-7RYOIXA2.js";
11
11
  import "../../../chunks/chunk-FKAO25XH.js";
12
12
  import {
13
13
  Type
@@ -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-U4B6RJ6Z.js";
4
+ } from "../../../chunks/chunk-J6PEH6ST.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-IJMP3JTX.js";
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-IJMP3JTX.js";
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: "One or more independent replacements matched against the original file",
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 independent replacements in one edits[] call. ${pathResolutionDescription}`,
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
- const planned = [];
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(normalizedContent, normalizedNewText);
8099
- const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
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 matches = replacement.replaceAll ? matchResult.matches : matchResult.matches.slice(0, 1);
8132
- const changingMatches = matches.filter(
8133
- (match2) => normalizedContent.slice(match2.index, match2.index + match2.matchLength) !== normalizedNewText
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
- planned.push({ ...match2, newText: normalizedNewText, matchKind: matchResult.matchKind });
8151
- }
8152
- }
8153
- const ordered = [...planned].sort((left, right) => left.index - right.index);
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
- const baseContent = normalizedContent;
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: planned.every((match2) => match2.matchKind === planned[0]?.matchKind) ? planned[0]?.matchKind ?? "none" : "mixed",
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 matchKinds = new Set(planned.map((match2) => match2.matchKind));
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"}, ${planned.length} replacement${planned.length === 1 ? "" : "s"}. First changed line: ${diffResult.firstChangedLine}.`
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: planned.length,
8393
+ replacedOccurrences,
8225
8394
  replacedBlocks: replacements.length
8226
8395
  }
8227
8396
  });
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-IJMP3JTX.js";
11
+ } from "./chunk-7RYOIXA2.js";
12
12
  import {
13
13
  Type
14
14
  } from "./chunk-K6ZOCJ4V.js";