@mikode13/harness 9.2.0 → 9.3.1

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.
Files changed (38) hide show
  1. package/README.md +6 -3
  2. package/dist/engines/domain/model/historyFollower.d.ts +1 -1
  3. package/dist/engines/domain/model/historyFollower.js +14 -3
  4. package/dist/engines/domain/model/historyFollower.js.map +1 -1
  5. package/dist/factory/infrastructure/agentLLMFactory.d.ts +2 -2
  6. package/dist/factory/infrastructure/agentLLMFactory.js +5 -5
  7. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  8. package/dist/recovery/domain/history.d.ts +3 -2
  9. package/dist/recovery/domain/history.js +6 -2
  10. package/dist/recovery/domain/history.js.map +1 -1
  11. package/dist/recovery/domain/recoveryStore.d.ts +47 -5
  12. package/dist/recovery/domain/recoveryStore.js.map +1 -1
  13. package/dist/recovery/domain/runChanges.js +11 -2
  14. package/dist/recovery/domain/runChanges.js.map +1 -1
  15. package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +41 -9
  16. package/dist/recovery/infrastructure/fileRecoveryStore.js +217 -65
  17. package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -1
  18. package/dist/tools/domain/editResponse.d.ts +7 -2
  19. package/dist/tools/domain/editResponse.js +33 -11
  20. package/dist/tools/domain/editResponse.js.map +1 -1
  21. package/dist/tools/domain/v4aPatch.d.ts +6 -6
  22. package/dist/tools/domain/v4aPatch.js +59 -21
  23. package/dist/tools/domain/v4aPatch.js.map +1 -1
  24. package/dist/tools/infrastructure/commandCheckpoint.d.ts +3 -2
  25. package/dist/tools/infrastructure/commandCheckpoint.js +21 -9
  26. package/dist/tools/infrastructure/commandCheckpoint.js.map +1 -1
  27. package/dist/tools/infrastructure/commandTool.js +1 -1
  28. package/dist/tools/infrastructure/commandTool.js.map +1 -1
  29. package/dist/tools/infrastructure/fileTools.d.ts +1 -1
  30. package/dist/tools/infrastructure/fileTools.js +3 -3
  31. package/dist/tools/infrastructure/fileTools.js.map +1 -1
  32. package/dist/tools/infrastructure/trackedEdits.d.ts +3 -3
  33. package/dist/tools/infrastructure/trackedEdits.js +17 -11
  34. package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
  35. package/dist/tools/infrastructure/workspaceWrites.d.ts +8 -5
  36. package/dist/tools/infrastructure/workspaceWrites.js +19 -7
  37. package/dist/tools/infrastructure/workspaceWrites.js.map +1 -1
  38. package/package.json +1 -1
@@ -5,6 +5,7 @@ const maxDiffBytes = 512 * 1024;
5
5
  const maxHunks = 500;
6
6
  // What an error quotes of a hunk or of the file, to fix the call without flooding the context.
7
7
  const quotedLines = 8;
8
+ const nearLines = 16;
8
9
  /** The lines a hunk expects in the file: the ones it keeps and the ones it removes. */
9
10
  function expected(hunk) {
10
11
  return hunk.steps.filter(step => step.kind !== 'add').map(step => step.text);
@@ -119,19 +120,58 @@ function quote(lines) {
119
120
  shown.push(` … ${String(lines.length - quotedLines)} more`);
120
121
  return shown.join('\n');
121
122
  }
122
- /** The real lines near where a hunk's first line appears, so the model can copy them. */
123
- function near(lines, hunk) {
124
- const first = expected(hunk)
125
- .find(line => line.trim() !== '')
126
- ?.trim();
127
- const at = first === undefined ? -1 : lines.findIndex(line => line.trim() === first);
128
- if (at === -1)
123
+ /**
124
+ * The stretch of the file most like a hunk that did not match, so the model can copy its real
125
+ * lines: as long as the hunk, holding the most of its lines in any order, so a line changed or
126
+ * left out does not hide it. On a tie, the first at or after where the hunk was looked for.
127
+ */
128
+ function near(lines, hunk, from) {
129
+ const old = expected(hunk);
130
+ const wanted = new Set(old.map(line => line.trim()).filter(line => line !== ''));
131
+ // The stretch slides one line at a time, counting the hunk's lines in it as they enter and
132
+ // leave, so a large hunk that failed costs no more than reading the file once.
133
+ const inStretch = new Map();
134
+ let score = 0;
135
+ const slide = (line, by) => {
136
+ const text = line?.trim();
137
+ if (text === undefined || !wanted.has(text))
138
+ return;
139
+ const count = (inStretch.get(text) ?? 0) + by;
140
+ inStretch.set(text, count);
141
+ if (count === (by === 1 ? 1 : 0))
142
+ score += by;
143
+ };
144
+ for (let at = 0; at < old.length; at++)
145
+ slide(lines[at], 1);
146
+ let best = { at: -1, score: 0 };
147
+ for (let at = 0; at < lines.length; at++) {
148
+ if (at > 0) {
149
+ slide(lines[at - 1], -1);
150
+ slide(lines[at + old.length - 1], 1);
151
+ }
152
+ const later = best.at < from && at >= from;
153
+ if (score > best.score || (score > 0 && score === best.score && later))
154
+ best = { at, score };
155
+ }
156
+ if (best.at === -1)
129
157
  return '';
130
- const from = Math.max(0, at - 1);
158
+ // From the first of the hunk's lines in the stretch to the last, with a line around them.
159
+ const held = (line) => line !== undefined && wanted.has(line.trim());
160
+ let first = best.at;
161
+ while (!held(lines[first]))
162
+ first++;
163
+ let last = Math.min(best.at + old.length, lines.length) - 1;
164
+ while (!held(lines[last]))
165
+ last--;
166
+ const start = Math.max(0, first - 1);
167
+ const end = Math.min(lines.length, last + 2, start + nearLines);
131
168
  const shown = lines
132
- .slice(from, from + quotedLines)
133
- .map((line, offset) => ` ${String(from + offset + 1)}: ${line}`);
134
- return `\nThe file has, near line ${String(at + 1)}:\n${shown.join('\n')}`;
169
+ .slice(start, end)
170
+ .map((line, offset) => ` ${String(start + offset + 1)}: ${line}`);
171
+ const order = first < from
172
+ ? '\nThose lines come before where the hunk was looked for: each hunk is found after the one before it, and after its "@@" lines.'
173
+ : '';
174
+ return `\nThe file has, near line ${String(first + 1)}:\n${shown.join('\n')}${order}`;
135
175
  }
136
176
  /**
137
177
  * Where a hunk's anchor lines are, one after another, so its change is found after them. The
@@ -152,13 +192,13 @@ function seekAnchor(lines, hunk, from, path, n) {
152
192
  return cursor;
153
193
  }
154
194
  /**
155
- * Applies the hunks of a V4A `update_file` diff to `content`, in order, each after the one before
156
- * it. A hunk with no anchor must match exactly once in the part of the file it searches, or
157
- * nothing is changed: a hunk is never applied where the model did not mean it. After an anchor,
158
- * it is the first match: the anchor already says where. Each line the hunk does not change is
159
- * written back as it was, ending included, and an added line ends like the line before it.
195
+ * Applies the hunks of a V4A `update_file` diff to `content`, in order, each at its first match
196
+ * after the one before it and after its anchor, as Codex does: the order of the hunks says which
197
+ * of two alike places is meant. Each line the hunk does not change is written back as it was,
198
+ * ending included, and an added line ends like the line before it.
160
199
  *
161
- * @throws {EditRefusedError} naming the hunk, what it expected and what the file has near it.
200
+ * @throws {EditRefusedError} naming the hunk, what it expected and the lines of the file most
201
+ * like it.
162
202
  */
163
203
  export function applyUpdate(content, diff, path) {
164
204
  checkSize(diff, path);
@@ -181,11 +221,9 @@ export function applyUpdate(content, diff, path) {
181
221
  cursor = at + added.length;
182
222
  continue;
183
223
  }
184
- const at = seek(texts, old, { from, atEnd: hunk.atEnd, once: hunk.anchor === undefined });
224
+ const at = seek(texts, old, { from, atEnd: hunk.atEnd, once: false });
185
225
  if (typeof at !== 'number') {
186
- throw new EditRefusedError(at === null
187
- ? `Hunk ${String(n)} of the patch for "${path}" does not match the file. It expected:\n${quote(old)}${near(texts, hunk)}`
188
- : `Hunk ${String(n)} of the patch for "${path}" matches ${String(at.places)} places; add an "@@" line naming the function or more context`);
226
+ throw new EditRefusedError(`Hunk ${String(n)} of the patch for "${path}" does not match the file. It expected:\n${quote(old)}${near(texts, hunk, from)}`);
189
227
  }
190
228
  // A kept line stays as the file has it, even where the patch copied it with a space lost. An
191
229
  // added line that takes a removed one's place takes its ending too.
@@ -1 +1 @@
1
- {"version":3,"file":"v4aPatch.js","sourceRoot":"","sources":["../../../src/tools/domain/v4aPatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAa,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEtF,iFAAiF;AACjF,MAAM,YAAY,GAAG,GAAG,GAAG,IAAI,CAAC;AAChC,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,+FAA+F;AAC/F,MAAM,WAAW,GAAG,CAAC,CAAC;AAiBtB,uFAAuF;AACvF,SAAS,QAAQ,CAAC,IAAU;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,OAAO,CAAC,IAAU;IAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,IAAY;IAC5C,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,YAAY,EAAE,CAAC;QAC5C,MAAM,IAAI,gBAAgB,CACzB,kBAAkB,IAAI,oBAAoB,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,2BAA2B,CAChG,CAAC;IACH,CAAC;AACF,CAAC;AAED,oEAAoE;AACpE,SAAS,SAAS,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7F,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IACrC,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,IAAY;IAC7C,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,IAAI,OAAyB,CAAC;IAC9B,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACpC,iEAAiE;YACjE,IAAI,OAAO,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBACnD,OAAO,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;gBAClF,SAAS;YACV,CAAC;YACD,OAAO,GAAG,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YACnE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,SAAS;QACV,CAAC;QACD,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;YAChC,IAAI,OAAO;gBAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;YAClC,SAAS;QACV,CAAC;QACD,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YACpB,MAAM,IAAI,gBAAgB,CACzB,QAAQ,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,sBAAsB,IAAI,gFAAgF,CACnI,CAAC;QACH,CAAC;QACD,uDAAuD;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YACzD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,kEAAkE;QAClE,IAAI,IAAI,KAAK,EAAE,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;aAAM,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,gBAAgB,CACzB,QAAQ,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,sBAAsB,IAAI,kBAAkB,MAAM,IAAI,EAAE,oDAAoD,CACrI,CAAC;QACH,CAAC;IACF,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,gBAAgB,CAAC,kBAAkB,IAAI,mBAAmB,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,gBAAgB,CACzB,kBAAkB,IAAI,mBAAmB,MAAM,CAAC,QAAQ,CAAC,8BAA8B,CACvF,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,6FAA6F;AAC7F,MAAM,MAAM,GAAiC;IAC5C,IAAI,CAAC,EAAE,CAAC,IAAI;IACZ,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE;IACtB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;CACnB,CAAC;AAEF;;;;GAIG;AACH,SAAS,IAAI,CACZ,KAAe,EACf,OAAiB,EACjB,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAmD;IAEtE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC3C,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE,CACnC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC;QACjF,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,IAAI,IAAI,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YACjD,SAAS;QACV,CAAC;QACD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC/C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;gBAAE,SAAS;YAChC,IAAI,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAChD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,KAAK,CAAC,KAAe;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,MAAM,GAAG,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7F,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,yFAAyF;AACzF,SAAS,IAAI,CAAC,KAAe,EAAE,IAAU;IACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;QACjC,EAAE,IAAI,EAAE,CAAC;IACV,MAAM,EAAE,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,CAAC;IACrF,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,KAAK;SACjB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CAAC;SAC/B,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,6BAA6B,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,KAAe,EAAE,IAAU,EAAE,IAAY,EAAE,IAAY,EAAE,CAAS;IACrF,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1E,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC;QACpF,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,gBAAgB,CACzB,EAAE,KAAK,IAAI;gBACV,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,sBAAsB,IAAI,eAAe,MAAM,uDAAuD;gBACzH,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,sBAAsB,IAAI,eAAe,MAAM,oBAAoB,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,2FAA2F,CACnM,CAAC;QACH,CAAC;QACD,MAAM,GAAG,EAAE,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,IAAY,EAAE,IAAY;IACtE,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,KAAK,GAAW,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9D,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QACpB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACtD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,qDAAqD;YACrD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YACzE,MAAM,KAAK,GAAW,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC;YAC9B,MAAM,GAAG,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;YAC3B,SAAS;QACV,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC;QAC1F,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,gBAAgB,CACzB,EAAE,KAAK,IAAI;gBACV,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,sBAAsB,IAAI,4CAA4C,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE;gBACzH,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,sBAAsB,IAAI,aAAa,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,+DAA+D,CAC3I,CAAC;QACH,CAAC;QACD,6FAA6F;QAC7F,oEAAoE;QACpE,MAAM,WAAW,GAAW,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAW,EAAE,CAAC;QAC5B,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;gBACrC,IAAI,OAAO;oBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpC,SAAS;YACV,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC1B,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBACpB,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;gBAClC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7E,SAAS;YACV,CAAC;YACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACvE,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC;QAC7C,MAAM,GAAG,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC;IAClC,CAAC;IACD,OAAO,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;AAClG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IACxD,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC;IACzE,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,gBAAgB,CACzB,QAAQ,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,2BAA2B,IAAI,0DAA0D,CAClH,CAAC;QACH,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;QAChB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,GAAG,EAAE,IAAI;QACT,YAAY,EAAE,IAAI;KAClB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { EditRefusedError } from './fileEdits.ts';\nimport { addedLine, joinLines, type Line, splitLines, textsOf } from './textLines.ts';\n\n// A patch is a few changes, not a file: one far larger is more likely a mistake.\nconst maxDiffBytes = 512 * 1024;\nconst maxHunks = 500;\n// What an error quotes of a hunk or of the file, to fix the call without flooding the context.\nconst quotedLines = 8;\n\n/** One line of a hunk: kept as the file has it, removed, or added. */\ninterface Step {\n\tkind: 'keep' | 'remove' | 'add';\n\ttext: string;\n}\n\n/** One change of a V4A diff: the lines it expects, and the lines it puts in their place. */\ninterface Hunk {\n\t/** The text after `@@`, such as a function's first line, found before the change. */\n\tanchor: string | undefined;\n\tsteps: Step[];\n\t/** `*** End of File`: the change is at the end of the file. */\n\tatEnd: boolean;\n}\n\n/** The lines a hunk expects in the file: the ones it keeps and the ones it removes. */\nfunction expected(hunk: Hunk): string[] {\n\treturn hunk.steps.filter(step => step.kind !== 'add').map(step => step.text);\n}\n\nfunction changes(hunk: Hunk): boolean {\n\treturn hunk.steps.some(step => step.kind !== 'keep');\n}\n\nfunction checkSize(diff: string, path: string): void {\n\tif (Buffer.byteLength(diff) > maxDiffBytes) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" is larger than ${String(maxDiffBytes / 1024)} KB; send smaller patches`,\n\t\t);\n\t}\n}\n\n/** The diff's lines, without the newline that ends the last one. */\nfunction diffLines(diff: string): string[] {\n\tconst lines = diff.split('\\n').map(line => (line.endsWith('\\r') ? line.slice(0, -1) : line));\n\tif (lines.at(-1) === '') lines.pop();\n\treturn lines;\n}\n\nfunction parseHunks(diff: string, path: string): Hunk[] {\n\tconst hunks: Hunk[] = [];\n\tlet current: Hunk | undefined;\n\tfor (const [index, line] of diffLines(diff).entries()) {\n\t\tif (line.startsWith('@@')) {\n\t\t\tconst anchor = line.slice(2).trim();\n\t\t\t// Two `@@` lines in a row name an anchor and then one inside it.\n\t\t\tif (current?.steps.length === 0 && !current.atEnd) {\n\t\t\t\tcurrent.anchor = [current.anchor, anchor].filter(Boolean).join('\\n') || undefined;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent = { anchor: anchor || undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (line === '*** End of File') {\n\t\t\tif (current) current.atEnd = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (current?.atEnd) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" follows \"*** End of File\", which ends its hunk; start another hunk with \"@@\"`,\n\t\t\t);\n\t\t}\n\t\t// A diff may open with its first change, without `@@`.\n\t\tif (!current) {\n\t\t\tcurrent = { anchor: undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t}\n\t\tconst marker = line[0];\n\t\tconst text = line.slice(1);\n\t\t// An empty line is an empty context line whose space was trimmed.\n\t\tif (line === '' || marker === ' ') {\n\t\t\tcurrent.steps.push({ kind: 'keep', text });\n\t\t} else if (marker === '-') {\n\t\t\tcurrent.steps.push({ kind: 'remove', text });\n\t\t} else if (marker === '+') {\n\t\t\tcurrent.steps.push({ kind: 'add', text });\n\t\t} else {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" starts with \"${marker ?? ''}\"; each line of a hunk starts with \" \", \"-\" or \"+\"`,\n\t\t\t);\n\t\t}\n\t}\n\tconst changing = hunks.filter(changes);\n\tif (changing.length === 0) {\n\t\tthrow new EditRefusedError(`The patch for \"${path}\" changes nothing`);\n\t}\n\tif (changing.length > maxHunks) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" has more than ${String(maxHunks)} hunks; send smaller patches`,\n\t\t);\n\t}\n\treturn changing;\n}\n\n// Each pass forgives a little more: the model may copy a line with its trailing spaces lost.\nconst passes: ((line: string) => string)[] = [\n\tline => line,\n\tline => line.trimEnd(),\n\tline => line.trim(),\n];\n\n/**\n * Where `pattern` starts in `lines`, at or after `from`, by the first pass that finds it. `null`\n * when no pass does. With `once`, the first pass that matches must find one place, or the number\n * of places is returned; without it, the first place is taken.\n */\nfunction seek(\n\tlines: string[],\n\tpattern: string[],\n\t{ from, atEnd, once }: { from: number; atEnd: boolean; once: boolean },\n): number | null | { places: number } {\n\tconst last = lines.length - pattern.length;\n\tfor (const normalize of passes) {\n\t\tconst wanted = pattern.map(normalize);\n\t\tconst matchesAt = (start: number) =>\n\t\t\twanted.every((line, offset) => normalize(lines[start + offset] ?? '') === line);\n\t\tif (atEnd) {\n\t\t\tif (last >= from && matchesAt(last)) return last;\n\t\t\tcontinue;\n\t\t}\n\t\tconst found: number[] = [];\n\t\tfor (let start = from; start <= last; start++) {\n\t\t\tif (!matchesAt(start)) continue;\n\t\t\tif (!once) return start;\n\t\t\tfound.push(start);\n\t\t}\n\t\tif (found.length === 1) return found[0] ?? null;\n\t\tif (found.length > 1) return { places: found.length };\n\t}\n\treturn null;\n}\n\nfunction quote(lines: string[]): string {\n\tconst shown = lines.slice(0, quotedLines).map(line => ` ${line}`);\n\tif (lines.length > quotedLines) shown.push(` … ${String(lines.length - quotedLines)} more`);\n\treturn shown.join('\\n');\n}\n\n/** The real lines near where a hunk's first line appears, so the model can copy them. */\nfunction near(lines: string[], hunk: Hunk): string {\n\tconst first = expected(hunk)\n\t\t.find(line => line.trim() !== '')\n\t\t?.trim();\n\tconst at = first === undefined ? -1 : lines.findIndex(line => line.trim() === first);\n\tif (at === -1) return '';\n\tconst from = Math.max(0, at - 1);\n\tconst shown = lines\n\t\t.slice(from, from + quotedLines)\n\t\t.map((line, offset) => ` ${String(from + offset + 1)}: ${line}`);\n\treturn `\\nThe file has, near line ${String(at + 1)}:\\n${shown.join('\\n')}`;\n}\n\n/**\n * Where a hunk's anchor lines are, one after another, so its change is found after them. The\n * first must appear once; each one after it is the first that follows the one before, as V4A\n * reads `@@ class B:` then `@@ def __init__(self):`.\n */\nfunction seekAnchor(lines: string[], hunk: Hunk, from: number, path: string, n: number): number {\n\tlet cursor = from;\n\tfor (const [index, anchor] of (hunk.anchor?.split('\\n') ?? []).entries()) {\n\t\tconst at = seek(lines, [anchor], { from: cursor, atEnd: false, once: index === 0 });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tat === null\n\t\t\t\t\t? `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which is not in the file after the hunks before it`\n\t\t\t\t\t: `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which appears ${String(at.places)} times; name a line that appears once, or put an \"@@\" line before it naming what holds it`,\n\t\t\t);\n\t\t}\n\t\tcursor = at + 1;\n\t}\n\treturn cursor;\n}\n\n/**\n * Applies the hunks of a V4A `update_file` diff to `content`, in order, each after the one before\n * it. A hunk with no anchor must match exactly once in the part of the file it searches, or\n * nothing is changed: a hunk is never applied where the model did not mean it. After an anchor,\n * it is the first match: the anchor already says where. Each line the hunk does not change is\n * written back as it was, ending included, and an added line ends like the line before it.\n *\n * @throws {EditRefusedError} naming the hunk, what it expected and what the file has near it.\n */\nexport function applyUpdate(content: string, diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst text = splitLines(content);\n\tconst lines: Line[] = [...text.lines];\n\tlet cursor = 0;\n\tfor (const [index, hunk] of parseHunks(diff, path).entries()) {\n\t\tconst n = index + 1;\n\t\tconst texts = textsOf(lines);\n\t\tconst from = seekAnchor(texts, hunk, cursor, path, n);\n\t\tconst old = expected(hunk);\n\t\tif (old.length === 0) {\n\t\t\t// Only added lines: after the anchor, or at the end.\n\t\t\tconst at = hunk.anchor === undefined || hunk.atEnd ? lines.length : from;\n\t\t\tconst added: Line[] = [];\n\t\t\tfor (const step of hunk.steps) {\n\t\t\t\tadded.push(addedLine(step.text, added.at(-1) ?? lines[at - 1], text.eol));\n\t\t\t}\n\t\t\tlines.splice(at, 0, ...added);\n\t\t\tcursor = at + added.length;\n\t\t\tcontinue;\n\t\t}\n\t\tconst at = seek(texts, old, { from, atEnd: hunk.atEnd, once: hunk.anchor === undefined });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tat === null\n\t\t\t\t\t? `Hunk ${String(n)} of the patch for \"${path}\" does not match the file. It expected:\\n${quote(old)}${near(texts, hunk)}`\n\t\t\t\t\t: `Hunk ${String(n)} of the patch for \"${path}\" matches ${String(at.places)} places; add an \"@@\" line naming the function or more context`,\n\t\t\t);\n\t\t}\n\t\t// A kept line stays as the file has it, even where the patch copied it with a space lost. An\n\t\t// added line that takes a removed one's place takes its ending too.\n\t\tconst replacement: Line[] = [];\n\t\tconst replaced: Line[] = [];\n\t\tlet offset = 0;\n\t\tfor (const step of hunk.steps) {\n\t\t\tif (step.kind === 'remove') {\n\t\t\t\tconst removed = lines[at + offset++];\n\t\t\t\tif (removed) replaced.push(removed);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (step.kind === 'keep') {\n\t\t\t\treplaced.length = 0;\n\t\t\t\tconst kept = lines[at + offset++];\n\t\t\t\treplacement.push(kept ?? addedLine(step.text, replacement.at(-1), text.eol));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst before = replaced.shift() ?? replacement.at(-1) ?? lines[at - 1];\n\t\t\treplacement.push(addedLine(step.text, before, text.eol));\n\t\t}\n\t\tlines.splice(at, old.length, ...replacement);\n\t\tcursor = at + replacement.length;\n\t}\n\treturn joinLines({ ...text, lines, finalNewline: text.finalNewline || text.lines.length === 0 });\n}\n\n/**\n * The content of a file a V4A `create_file` diff makes: every line starts with `+`. It ends with\n * a newline, as a text file does.\n *\n * @throws {EditRefusedError} when a line is not an added one.\n */\nexport function createdContent(diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst lines = diffLines(diff).filter(line => line !== '*** End of File');\n\tfor (const [index, line] of lines.entries()) {\n\t\tif (!line.startsWith('+')) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch creating \"${path}\" does not start with \"+\"; every line of a new file does`,\n\t\t\t);\n\t\t}\n\t}\n\treturn joinLines({\n\t\tlines: lines.map(line => ({ text: line.slice(1), end: '\\n' })),\n\t\teol: '\\n',\n\t\tfinalNewline: true,\n\t});\n}\n"]}
1
+ {"version":3,"file":"v4aPatch.js","sourceRoot":"","sources":["../../../src/tools/domain/v4aPatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { EditRefusedError } from './fileEdits.ts';\nimport { addedLine, joinLines, type Line, splitLines, textsOf } from './textLines.ts';\n\n// A patch is a few changes, not a file: one far larger is more likely a mistake.\nconst maxDiffBytes = 512 * 1024;\nconst maxHunks = 500;\n// What an error quotes of a hunk or of the file, to fix the call without flooding the context.\nconst quotedLines = 8;\nconst nearLines = 16;\n\n/** One line of a hunk: kept as the file has it, removed, or added. */\ninterface Step {\n\tkind: 'keep' | 'remove' | 'add';\n\ttext: string;\n}\n\n/** One change of a V4A diff: the lines it expects, and the lines it puts in their place. */\ninterface Hunk {\n\t/** The text after `@@`, such as a function's first line, found before the change. */\n\tanchor: string | undefined;\n\tsteps: Step[];\n\t/** `*** End of File`: the change is at the end of the file. */\n\tatEnd: boolean;\n}\n\n/** The lines a hunk expects in the file: the ones it keeps and the ones it removes. */\nfunction expected(hunk: Hunk): string[] {\n\treturn hunk.steps.filter(step => step.kind !== 'add').map(step => step.text);\n}\n\nfunction changes(hunk: Hunk): boolean {\n\treturn hunk.steps.some(step => step.kind !== 'keep');\n}\n\nfunction checkSize(diff: string, path: string): void {\n\tif (Buffer.byteLength(diff) > maxDiffBytes) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" is larger than ${String(maxDiffBytes / 1024)} KB; send smaller patches`,\n\t\t);\n\t}\n}\n\n/** The diff's lines, without the newline that ends the last one. */\nfunction diffLines(diff: string): string[] {\n\tconst lines = diff.split('\\n').map(line => (line.endsWith('\\r') ? line.slice(0, -1) : line));\n\tif (lines.at(-1) === '') lines.pop();\n\treturn lines;\n}\n\nfunction parseHunks(diff: string, path: string): Hunk[] {\n\tconst hunks: Hunk[] = [];\n\tlet current: Hunk | undefined;\n\tfor (const [index, line] of diffLines(diff).entries()) {\n\t\tif (line.startsWith('@@')) {\n\t\t\tconst anchor = line.slice(2).trim();\n\t\t\t// Two `@@` lines in a row name an anchor and then one inside it.\n\t\t\tif (current?.steps.length === 0 && !current.atEnd) {\n\t\t\t\tcurrent.anchor = [current.anchor, anchor].filter(Boolean).join('\\n') || undefined;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent = { anchor: anchor || undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (line === '*** End of File') {\n\t\t\tif (current) current.atEnd = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (current?.atEnd) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" follows \"*** End of File\", which ends its hunk; start another hunk with \"@@\"`,\n\t\t\t);\n\t\t}\n\t\t// A diff may open with its first change, without `@@`.\n\t\tif (!current) {\n\t\t\tcurrent = { anchor: undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t}\n\t\tconst marker = line[0];\n\t\tconst text = line.slice(1);\n\t\t// An empty line is an empty context line whose space was trimmed.\n\t\tif (line === '' || marker === ' ') {\n\t\t\tcurrent.steps.push({ kind: 'keep', text });\n\t\t} else if (marker === '-') {\n\t\t\tcurrent.steps.push({ kind: 'remove', text });\n\t\t} else if (marker === '+') {\n\t\t\tcurrent.steps.push({ kind: 'add', text });\n\t\t} else {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" starts with \"${marker ?? ''}\"; each line of a hunk starts with \" \", \"-\" or \"+\"`,\n\t\t\t);\n\t\t}\n\t}\n\tconst changing = hunks.filter(changes);\n\tif (changing.length === 0) {\n\t\tthrow new EditRefusedError(`The patch for \"${path}\" changes nothing`);\n\t}\n\tif (changing.length > maxHunks) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" has more than ${String(maxHunks)} hunks; send smaller patches`,\n\t\t);\n\t}\n\treturn changing;\n}\n\n// Each pass forgives a little more: the model may copy a line with its trailing spaces lost.\nconst passes: ((line: string) => string)[] = [\n\tline => line,\n\tline => line.trimEnd(),\n\tline => line.trim(),\n];\n\n/**\n * Where `pattern` starts in `lines`, at or after `from`, by the first pass that finds it. `null`\n * when no pass does. With `once`, the first pass that matches must find one place, or the number\n * of places is returned; without it, the first place is taken.\n */\nfunction seek(\n\tlines: string[],\n\tpattern: string[],\n\t{ from, atEnd, once }: { from: number; atEnd: boolean; once: boolean },\n): number | null | { places: number } {\n\tconst last = lines.length - pattern.length;\n\tfor (const normalize of passes) {\n\t\tconst wanted = pattern.map(normalize);\n\t\tconst matchesAt = (start: number) =>\n\t\t\twanted.every((line, offset) => normalize(lines[start + offset] ?? '') === line);\n\t\tif (atEnd) {\n\t\t\tif (last >= from && matchesAt(last)) return last;\n\t\t\tcontinue;\n\t\t}\n\t\tconst found: number[] = [];\n\t\tfor (let start = from; start <= last; start++) {\n\t\t\tif (!matchesAt(start)) continue;\n\t\t\tif (!once) return start;\n\t\t\tfound.push(start);\n\t\t}\n\t\tif (found.length === 1) return found[0] ?? null;\n\t\tif (found.length > 1) return { places: found.length };\n\t}\n\treturn null;\n}\n\nfunction quote(lines: string[]): string {\n\tconst shown = lines.slice(0, quotedLines).map(line => ` ${line}`);\n\tif (lines.length > quotedLines) shown.push(` … ${String(lines.length - quotedLines)} more`);\n\treturn shown.join('\\n');\n}\n\n/**\n * The stretch of the file most like a hunk that did not match, so the model can copy its real\n * lines: as long as the hunk, holding the most of its lines in any order, so a line changed or\n * left out does not hide it. On a tie, the first at or after where the hunk was looked for.\n */\nfunction near(lines: string[], hunk: Hunk, from: number): string {\n\tconst old = expected(hunk);\n\tconst wanted = new Set(old.map(line => line.trim()).filter(line => line !== ''));\n\t// The stretch slides one line at a time, counting the hunk's lines in it as they enter and\n\t// leave, so a large hunk that failed costs no more than reading the file once.\n\tconst inStretch = new Map<string, number>();\n\tlet score = 0;\n\tconst slide = (line: string | undefined, by: 1 | -1) => {\n\t\tconst text = line?.trim();\n\t\tif (text === undefined || !wanted.has(text)) return;\n\t\tconst count = (inStretch.get(text) ?? 0) + by;\n\t\tinStretch.set(text, count);\n\t\tif (count === (by === 1 ? 1 : 0)) score += by;\n\t};\n\tfor (let at = 0; at < old.length; at++) slide(lines[at], 1);\n\tlet best = { at: -1, score: 0 };\n\tfor (let at = 0; at < lines.length; at++) {\n\t\tif (at > 0) {\n\t\t\tslide(lines[at - 1], -1);\n\t\t\tslide(lines[at + old.length - 1], 1);\n\t\t}\n\t\tconst later = best.at < from && at >= from;\n\t\tif (score > best.score || (score > 0 && score === best.score && later)) best = { at, score };\n\t}\n\tif (best.at === -1) return '';\n\t// From the first of the hunk's lines in the stretch to the last, with a line around them.\n\tconst held = (line: string | undefined) => line !== undefined && wanted.has(line.trim());\n\tlet first = best.at;\n\twhile (!held(lines[first])) first++;\n\tlet last = Math.min(best.at + old.length, lines.length) - 1;\n\twhile (!held(lines[last])) last--;\n\tconst start = Math.max(0, first - 1);\n\tconst end = Math.min(lines.length, last + 2, start + nearLines);\n\tconst shown = lines\n\t\t.slice(start, end)\n\t\t.map((line, offset) => ` ${String(start + offset + 1)}: ${line}`);\n\tconst order =\n\t\tfirst < from\n\t\t\t? '\\nThose lines come before where the hunk was looked for: each hunk is found after the one before it, and after its \"@@\" lines.'\n\t\t\t: '';\n\treturn `\\nThe file has, near line ${String(first + 1)}:\\n${shown.join('\\n')}${order}`;\n}\n\n/**\n * Where a hunk's anchor lines are, one after another, so its change is found after them. The\n * first must appear once; each one after it is the first that follows the one before, as V4A\n * reads `@@ class B:` then `@@ def __init__(self):`.\n */\nfunction seekAnchor(lines: string[], hunk: Hunk, from: number, path: string, n: number): number {\n\tlet cursor = from;\n\tfor (const [index, anchor] of (hunk.anchor?.split('\\n') ?? []).entries()) {\n\t\tconst at = seek(lines, [anchor], { from: cursor, atEnd: false, once: index === 0 });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tat === null\n\t\t\t\t\t? `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which is not in the file after the hunks before it`\n\t\t\t\t\t: `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which appears ${String(at.places)} times; name a line that appears once, or put an \"@@\" line before it naming what holds it`,\n\t\t\t);\n\t\t}\n\t\tcursor = at + 1;\n\t}\n\treturn cursor;\n}\n\n/**\n * Applies the hunks of a V4A `update_file` diff to `content`, in order, each at its first match\n * after the one before it and after its anchor, as Codex does: the order of the hunks says which\n * of two alike places is meant. Each line the hunk does not change is written back as it was,\n * ending included, and an added line ends like the line before it.\n *\n * @throws {EditRefusedError} naming the hunk, what it expected and the lines of the file most\n * like it.\n */\nexport function applyUpdate(content: string, diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst text = splitLines(content);\n\tconst lines: Line[] = [...text.lines];\n\tlet cursor = 0;\n\tfor (const [index, hunk] of parseHunks(diff, path).entries()) {\n\t\tconst n = index + 1;\n\t\tconst texts = textsOf(lines);\n\t\tconst from = seekAnchor(texts, hunk, cursor, path, n);\n\t\tconst old = expected(hunk);\n\t\tif (old.length === 0) {\n\t\t\t// Only added lines: after the anchor, or at the end.\n\t\t\tconst at = hunk.anchor === undefined || hunk.atEnd ? lines.length : from;\n\t\t\tconst added: Line[] = [];\n\t\t\tfor (const step of hunk.steps) {\n\t\t\t\tadded.push(addedLine(step.text, added.at(-1) ?? lines[at - 1], text.eol));\n\t\t\t}\n\t\t\tlines.splice(at, 0, ...added);\n\t\t\tcursor = at + added.length;\n\t\t\tcontinue;\n\t\t}\n\t\tconst at = seek(texts, old, { from, atEnd: hunk.atEnd, once: false });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Hunk ${String(n)} of the patch for \"${path}\" does not match the file. It expected:\\n${quote(old)}${near(texts, hunk, from)}`,\n\t\t\t);\n\t\t}\n\t\t// A kept line stays as the file has it, even where the patch copied it with a space lost. An\n\t\t// added line that takes a removed one's place takes its ending too.\n\t\tconst replacement: Line[] = [];\n\t\tconst replaced: Line[] = [];\n\t\tlet offset = 0;\n\t\tfor (const step of hunk.steps) {\n\t\t\tif (step.kind === 'remove') {\n\t\t\t\tconst removed = lines[at + offset++];\n\t\t\t\tif (removed) replaced.push(removed);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (step.kind === 'keep') {\n\t\t\t\treplaced.length = 0;\n\t\t\t\tconst kept = lines[at + offset++];\n\t\t\t\treplacement.push(kept ?? addedLine(step.text, replacement.at(-1), text.eol));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst before = replaced.shift() ?? replacement.at(-1) ?? lines[at - 1];\n\t\t\treplacement.push(addedLine(step.text, before, text.eol));\n\t\t}\n\t\tlines.splice(at, old.length, ...replacement);\n\t\tcursor = at + replacement.length;\n\t}\n\treturn joinLines({ ...text, lines, finalNewline: text.finalNewline || text.lines.length === 0 });\n}\n\n/**\n * The content of a file a V4A `create_file` diff makes: every line starts with `+`. It ends with\n * a newline, as a text file does.\n *\n * @throws {EditRefusedError} when a line is not an added one.\n */\nexport function createdContent(diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst lines = diffLines(diff).filter(line => line !== '*** End of File');\n\tfor (const [index, line] of lines.entries()) {\n\t\tif (!line.startsWith('+')) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch creating \"${path}\" does not start with \"+\"; every line of a new file does`,\n\t\t\t);\n\t\t}\n\t}\n\treturn joinLines({\n\t\tlines: lines.map(line => ({ text: line.slice(1), end: '\\n' })),\n\t\teol: '\\n',\n\t\tfinalNewline: true,\n\t});\n}\n"]}
@@ -43,8 +43,9 @@ export declare class CommandCheckpoint {
43
43
  * cancelled: once the command has run, what it changed must reach the history. Unless
44
44
  * `complete`, the observation stays open, because the command may still be changing files.
45
45
  *
46
- * @returns the changes, and the paths it could not record because they are now something the
47
- * history cannot copy, such as a file the command replaced with a folder, or made too large.
46
+ * @returns the changes, and the paths it could not record because they are now, or were before,
47
+ * something the history cannot copy, such as a file the command replaced with a folder, or one
48
+ * too large that it replaced with a small one. The journal names them, so undo leaves them.
48
49
  */
49
50
  end({ complete }: {
50
51
  complete: boolean;
@@ -64,7 +64,10 @@ export class CommandCheckpoint {
64
64
  if (state)
65
65
  files[path] = state;
66
66
  });
67
- const manifest = { files };
67
+ const manifest = {
68
+ files,
69
+ ...(uncopyable.size > 0 ? { uncopyable: [...uncopyable] } : {}),
70
+ };
68
71
  const hash = await journal.saveContent(Buffer.from(JSON.stringify(manifest)));
69
72
  signal.throwIfAborted();
70
73
  const sequence = await journal.observe(hash, by);
@@ -83,26 +86,35 @@ export class CommandCheckpoint {
83
86
  * cancelled: once the command has run, what it changed must reach the history. Unless
84
87
  * `complete`, the observation stays open, because the command may still be changing files.
85
88
  *
86
- * @returns the changes, and the paths it could not record because they are now something the
87
- * history cannot copy, such as a file the command replaced with a folder, or made too large.
89
+ * @returns the changes, and the paths it could not record because they are now, or were before,
90
+ * something the history cannot copy, such as a file the command replaced with a folder, or one
91
+ * too large that it replaced with a small one. The journal names them, so undo leaves them.
88
92
  */
89
93
  async end({ complete }) {
90
94
  const started = performance.now();
91
95
  const signal = new AbortController().signal;
92
- const paths = new Set([...Object.keys(this.manifest.files), ...(await this.covered(signal))]);
96
+ const paths = new Set([
97
+ ...Object.keys(this.manifest.files),
98
+ ...this.uncopyable,
99
+ ...(await this.covered(signal)),
100
+ ]);
93
101
  const changes = [];
94
102
  const unrecorded = [];
95
103
  let bytesRead = this.measure.bytesRead;
96
104
  for (const path of paths) {
97
- const before = this.manifest.files[path] ?? { exists: false };
98
105
  const read = await readState(path);
99
- if (!read) {
100
- // One it could not copy before either, such as a file over the size limit, is not
101
- // covered at all; one it could is now something no entry records.
102
- if (!this.uncopyable.has(path))
106
+ // One it could not copy before or after, such as a file over the size limit, is not
107
+ // covered at all. One it could copy only before, or only after, changed in a way the
108
+ // history cannot take back: what it held on the other side is unknown.
109
+ const copiedBefore = !this.uncopyable.has(path);
110
+ if (!read || !copiedBefore) {
111
+ if (read || copiedBefore) {
112
+ await this.journal.unrecorded(path, this.by);
103
113
  unrecorded.push(path);
114
+ }
104
115
  continue;
105
116
  }
117
+ const before = this.manifest.files[path] ?? { exists: false };
106
118
  let after = { exists: false };
107
119
  if (read.content) {
108
120
  bytesRead += read.content.length;
@@ -1 +1 @@
1
- {"version":3,"file":"commandCheckpoint.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandCheckpoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { lstat, readFile } from 'node:fs/promises';\nimport { sameState } from '#src/recovery/domain/fileChanges';\nimport type { FileState, Manifest, RunJournal } from '#src/recovery/domain/recoveryStore';\n\n// The same bound the history uses for a file it records: a larger one is not copied.\nconst maxCoveredBytes = 64 * 1024 * 1024;\nconst captureConcurrency = 16;\n\n/**\n * Every file a command's checkpoint covers now, by its real path, absolute: what `.gitignore`\n * does not exclude in the `write` roots, less what the access policy closes to writing by name.\n */\nexport type CoveredFiles = (signal: AbortSignal) => Promise<string[]>;\n\n/** A file a command changed, as the checkpoint found it. */\nexport interface ObservedChange {\n\tpath: string;\n\tbefore: FileState;\n\tafter: FileState;\n}\n\nfunction code(error: unknown): string | undefined {\n\treturn (error as NodeJS.ErrnoException).code;\n}\n\n/**\n * The files a command may change, captured right before it runs and compared right after, so\n * what it changed is in the run's history like any tool's change, and can be undone.\n *\n * `begin` stores the content of every covered file and a manifest of them, then records the\n * observation in the journal; if any of that fails, the command must not run. `end` lists the\n * covered files again, and records each one that differs from the manifest as a change made by\n * the command. This first version reads and hashes everything both times, trusting neither size\n * nor modification time, and keeps what that cost with the observation.\n */\nexport class CommandCheckpoint {\n\tprivate readonly journal: RunJournal;\n\tprivate readonly covered: CoveredFiles;\n\tprivate readonly by: string;\n\tprivate readonly manifest: Manifest;\n\t/** Covered files it could not copy before, which it leaves out after too. */\n\tprivate readonly uncopyable: ReadonlySet<string>;\n\tprivate readonly sequence: number;\n\tprivate readonly measure: { bytesRead: number; durationMs: number; storedBefore: number };\n\n\tprivate constructor(fields: {\n\t\tjournal: RunJournal;\n\t\tcovered: CoveredFiles;\n\t\tby: string;\n\t\tmanifest: Manifest;\n\t\tuncopyable: ReadonlySet<string>;\n\t\tsequence: number;\n\t\tmeasure: { bytesRead: number; durationMs: number; storedBefore: number };\n\t}) {\n\t\tthis.journal = fields.journal;\n\t\tthis.covered = fields.covered;\n\t\tthis.by = fields.by;\n\t\tthis.manifest = fields.manifest;\n\t\tthis.uncopyable = fields.uncopyable;\n\t\tthis.sequence = fields.sequence;\n\t\tthis.measure = fields.measure;\n\t}\n\n\t/** Captures every covered file. Cancelling it, or any failure, leaves the command unstarted. */\n\tstatic async begin({\n\t\tjournal,\n\t\tcovered,\n\t\tby,\n\t\tsignal,\n\t}: {\n\t\tjournal: RunJournal;\n\t\tcovered: CoveredFiles;\n\t\t/** The command, as the history will name it. */\n\t\tby: string;\n\t\tsignal: AbortSignal;\n\t}): Promise<CommandCheckpoint> {\n\t\tconst started = performance.now();\n\t\tconst storedBefore = journal.storedBytes;\n\t\tconst paths = await covered(signal);\n\t\tconst states: (FileState | undefined)[] = [];\n\t\tconst uncopyable = new Set<string>();\n\t\tlet bytesRead = 0;\n\t\t// Several at once: each stored content waits for its own sync, which dominates the time.\n\t\tawait eachAtOnce(paths, captureConcurrency, async (path, index) => {\n\t\t\tsignal.throwIfAborted();\n\t\t\tconst read = await readState(path);\n\t\t\t// A folder, a link or a file too large: no entry records it, so it is not covered.\n\t\t\tif (!read) {\n\t\t\t\tuncopyable.add(path);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (read.content) {\n\t\t\t\tbytesRead += read.content.length;\n\t\t\t\tconst hash = await journal.saveContent(read.content);\n\t\t\t\tstates[index] = { exists: true, hash, mode: read.mode };\n\t\t\t}\n\t\t});\n\t\tconst files: Manifest['files'] = {};\n\t\tpaths.forEach((path, index) => {\n\t\t\tconst state = states[index];\n\t\t\tif (state) files[path] = state;\n\t\t});\n\t\tconst manifest: Manifest = { files };\n\t\tconst hash = await journal.saveContent(Buffer.from(JSON.stringify(manifest)));\n\t\tsignal.throwIfAborted();\n\t\tconst sequence = await journal.observe(hash, by);\n\t\treturn new CommandCheckpoint({\n\t\t\tjournal,\n\t\t\tcovered,\n\t\t\tby,\n\t\t\tmanifest,\n\t\t\tuncopyable,\n\t\t\tsequence,\n\t\t\tmeasure: { bytesRead, durationMs: performance.now() - started, storedBefore },\n\t\t});\n\t}\n\n\t/**\n\t * Compares the covered files with the capture and records every change found. It is never\n\t * cancelled: once the command has run, what it changed must reach the history. Unless\n\t * `complete`, the observation stays open, because the command may still be changing files.\n\t *\n\t * @returns the changes, and the paths it could not record because they are now something the\n\t * history cannot copy, such as a file the command replaced with a folder, or made too large.\n\t */\n\tasync end({ complete }: { complete: boolean }): Promise<{\n\t\tchanges: ObservedChange[];\n\t\tunrecorded: string[];\n\t}> {\n\t\tconst started = performance.now();\n\t\tconst signal = new AbortController().signal;\n\t\tconst paths = new Set([...Object.keys(this.manifest.files), ...(await this.covered(signal))]);\n\t\tconst changes: ObservedChange[] = [];\n\t\tconst unrecorded: string[] = [];\n\t\tlet bytesRead = this.measure.bytesRead;\n\n\t\tfor (const path of paths) {\n\t\t\tconst before: FileState = this.manifest.files[path] ?? { exists: false };\n\t\t\tconst read = await readState(path);\n\t\t\tif (!read) {\n\t\t\t\t// One it could not copy before either, such as a file over the size limit, is not\n\t\t\t\t// covered at all; one it could is now something no entry records.\n\t\t\t\tif (!this.uncopyable.has(path)) unrecorded.push(path);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet after: FileState = { exists: false };\n\t\t\tif (read.content) {\n\t\t\t\tbytesRead += read.content.length;\n\t\t\t\tconst hash = createHash('sha256').update(read.content).digest('hex');\n\t\t\t\tafter = { exists: true, hash, mode: read.mode };\n\t\t\t}\n\t\t\tif (sameState(before, after)) continue;\n\t\t\t// Stored only now that it changed, so an unchanged file costs no write.\n\t\t\tif (read.content) await this.journal.saveContent(read.content);\n\t\t\tconst sequence = await this.journal.prepare({ path, before, after, observedBy: this.by });\n\t\t\tawait this.journal.applied(sequence);\n\t\t\tchanges.push({ path, before, after });\n\t\t}\n\n\t\t// Left open when something the command started may still be running: the record then says\n\t\t// that what it changed may not all be here.\n\t\tif (!complete) return { changes, unrecorded };\n\t\tawait this.journal.observed(this.sequence, {\n\t\t\tfiles: paths.size,\n\t\t\tbytesRead,\n\t\t\tbytesStored: this.journal.storedBytes - this.measure.storedBefore,\n\t\t\tdurationMs: Math.round(this.measure.durationMs + performance.now() - started),\n\t\t});\n\t\treturn { changes, unrecorded };\n\t}\n}\n\n/**\n * What `path` holds: nothing, or a regular file's content and mode. `undefined` for anything an\n * entry cannot record: a folder, a link, or a file too large to copy.\n */\nasync function readState(path: string): Promise<{ content?: Buffer; mode: number } | undefined> {\n\tlet stats;\n\ttry {\n\t\tstats = await lstat(path);\n\t} catch (error) {\n\t\tif (code(error) === 'ENOENT') return { mode: 0 };\n\t\tthrow error;\n\t}\n\tif (!stats.isFile() || stats.size > maxCoveredBytes) return undefined;\n\ttry {\n\t\treturn { content: await readFile(path), mode: stats.mode & 0o7777 };\n\t} catch (error) {\n\t\t// Deleted between the two calls.\n\t\tif (code(error) === 'ENOENT') return { mode: 0 };\n\t\tthrow error;\n\t}\n}\n\n/** Runs `task` on every item, at most `limit` at a time, and fails with the first failure. */\nasync function eachAtOnce<Item>(\n\titems: readonly Item[],\n\tlimit: number,\n\ttask: (item: Item, index: number) => Promise<void>,\n): Promise<void> {\n\tlet next = 0;\n\tlet failed = false;\n\tconst worker = async () => {\n\t\twhile (!failed && next < items.length) {\n\t\t\tconst index = next++;\n\t\t\ttry {\n\t\t\t\tawait task(items[index] as Item, index);\n\t\t\t} catch (error) {\n\t\t\t\tfailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t};\n\tawait Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n}\n"]}
1
+ {"version":3,"file":"commandCheckpoint.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandCheckpoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { lstat, readFile } from 'node:fs/promises';\nimport { sameState } from '#src/recovery/domain/fileChanges';\nimport type { FileState, Manifest, RunJournal } from '#src/recovery/domain/recoveryStore';\n\n// The same bound the history uses for a file it records: a larger one is not copied.\nconst maxCoveredBytes = 64 * 1024 * 1024;\nconst captureConcurrency = 16;\n\n/**\n * Every file a command's checkpoint covers now, by its real path, absolute: what `.gitignore`\n * does not exclude in the `write` roots, less what the access policy closes to writing by name.\n */\nexport type CoveredFiles = (signal: AbortSignal) => Promise<string[]>;\n\n/** A file a command changed, as the checkpoint found it. */\nexport interface ObservedChange {\n\tpath: string;\n\tbefore: FileState;\n\tafter: FileState;\n}\n\nfunction code(error: unknown): string | undefined {\n\treturn (error as NodeJS.ErrnoException).code;\n}\n\n/**\n * The files a command may change, captured right before it runs and compared right after, so\n * what it changed is in the run's history like any tool's change, and can be undone.\n *\n * `begin` stores the content of every covered file and a manifest of them, then records the\n * observation in the journal; if any of that fails, the command must not run. `end` lists the\n * covered files again, and records each one that differs from the manifest as a change made by\n * the command. This first version reads and hashes everything both times, trusting neither size\n * nor modification time, and keeps what that cost with the observation.\n */\nexport class CommandCheckpoint {\n\tprivate readonly journal: RunJournal;\n\tprivate readonly covered: CoveredFiles;\n\tprivate readonly by: string;\n\tprivate readonly manifest: Manifest;\n\t/** Covered files it could not copy before, which it leaves out after too. */\n\tprivate readonly uncopyable: ReadonlySet<string>;\n\tprivate readonly sequence: number;\n\tprivate readonly measure: { bytesRead: number; durationMs: number; storedBefore: number };\n\n\tprivate constructor(fields: {\n\t\tjournal: RunJournal;\n\t\tcovered: CoveredFiles;\n\t\tby: string;\n\t\tmanifest: Manifest;\n\t\tuncopyable: ReadonlySet<string>;\n\t\tsequence: number;\n\t\tmeasure: { bytesRead: number; durationMs: number; storedBefore: number };\n\t}) {\n\t\tthis.journal = fields.journal;\n\t\tthis.covered = fields.covered;\n\t\tthis.by = fields.by;\n\t\tthis.manifest = fields.manifest;\n\t\tthis.uncopyable = fields.uncopyable;\n\t\tthis.sequence = fields.sequence;\n\t\tthis.measure = fields.measure;\n\t}\n\n\t/** Captures every covered file. Cancelling it, or any failure, leaves the command unstarted. */\n\tstatic async begin({\n\t\tjournal,\n\t\tcovered,\n\t\tby,\n\t\tsignal,\n\t}: {\n\t\tjournal: RunJournal;\n\t\tcovered: CoveredFiles;\n\t\t/** The command, as the history will name it. */\n\t\tby: string;\n\t\tsignal: AbortSignal;\n\t}): Promise<CommandCheckpoint> {\n\t\tconst started = performance.now();\n\t\tconst storedBefore = journal.storedBytes;\n\t\tconst paths = await covered(signal);\n\t\tconst states: (FileState | undefined)[] = [];\n\t\tconst uncopyable = new Set<string>();\n\t\tlet bytesRead = 0;\n\t\t// Several at once: each stored content waits for its own sync, which dominates the time.\n\t\tawait eachAtOnce(paths, captureConcurrency, async (path, index) => {\n\t\t\tsignal.throwIfAborted();\n\t\t\tconst read = await readState(path);\n\t\t\t// A folder, a link or a file too large: no entry records it, so it is not covered.\n\t\t\tif (!read) {\n\t\t\t\tuncopyable.add(path);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (read.content) {\n\t\t\t\tbytesRead += read.content.length;\n\t\t\t\tconst hash = await journal.saveContent(read.content);\n\t\t\t\tstates[index] = { exists: true, hash, mode: read.mode };\n\t\t\t}\n\t\t});\n\t\tconst files: Manifest['files'] = {};\n\t\tpaths.forEach((path, index) => {\n\t\t\tconst state = states[index];\n\t\t\tif (state) files[path] = state;\n\t\t});\n\t\tconst manifest: Manifest = {\n\t\t\tfiles,\n\t\t\t...(uncopyable.size > 0 ? { uncopyable: [...uncopyable] } : {}),\n\t\t};\n\t\tconst hash = await journal.saveContent(Buffer.from(JSON.stringify(manifest)));\n\t\tsignal.throwIfAborted();\n\t\tconst sequence = await journal.observe(hash, by);\n\t\treturn new CommandCheckpoint({\n\t\t\tjournal,\n\t\t\tcovered,\n\t\t\tby,\n\t\t\tmanifest,\n\t\t\tuncopyable,\n\t\t\tsequence,\n\t\t\tmeasure: { bytesRead, durationMs: performance.now() - started, storedBefore },\n\t\t});\n\t}\n\n\t/**\n\t * Compares the covered files with the capture and records every change found. It is never\n\t * cancelled: once the command has run, what it changed must reach the history. Unless\n\t * `complete`, the observation stays open, because the command may still be changing files.\n\t *\n\t * @returns the changes, and the paths it could not record because they are now, or were before,\n\t * something the history cannot copy, such as a file the command replaced with a folder, or one\n\t * too large that it replaced with a small one. The journal names them, so undo leaves them.\n\t */\n\tasync end({ complete }: { complete: boolean }): Promise<{\n\t\tchanges: ObservedChange[];\n\t\tunrecorded: string[];\n\t}> {\n\t\tconst started = performance.now();\n\t\tconst signal = new AbortController().signal;\n\t\tconst paths = new Set([\n\t\t\t...Object.keys(this.manifest.files),\n\t\t\t...this.uncopyable,\n\t\t\t...(await this.covered(signal)),\n\t\t]);\n\t\tconst changes: ObservedChange[] = [];\n\t\tconst unrecorded: string[] = [];\n\t\tlet bytesRead = this.measure.bytesRead;\n\n\t\tfor (const path of paths) {\n\t\t\tconst read = await readState(path);\n\t\t\t// One it could not copy before or after, such as a file over the size limit, is not\n\t\t\t// covered at all. One it could copy only before, or only after, changed in a way the\n\t\t\t// history cannot take back: what it held on the other side is unknown.\n\t\t\tconst copiedBefore = !this.uncopyable.has(path);\n\t\t\tif (!read || !copiedBefore) {\n\t\t\t\tif (read || copiedBefore) {\n\t\t\t\t\tawait this.journal.unrecorded(path, this.by);\n\t\t\t\t\tunrecorded.push(path);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst before: FileState = this.manifest.files[path] ?? { exists: false };\n\t\t\tlet after: FileState = { exists: false };\n\t\t\tif (read.content) {\n\t\t\t\tbytesRead += read.content.length;\n\t\t\t\tconst hash = createHash('sha256').update(read.content).digest('hex');\n\t\t\t\tafter = { exists: true, hash, mode: read.mode };\n\t\t\t}\n\t\t\tif (sameState(before, after)) continue;\n\t\t\t// Stored only now that it changed, so an unchanged file costs no write.\n\t\t\tif (read.content) await this.journal.saveContent(read.content);\n\t\t\tconst sequence = await this.journal.prepare({ path, before, after, observedBy: this.by });\n\t\t\tawait this.journal.applied(sequence);\n\t\t\tchanges.push({ path, before, after });\n\t\t}\n\n\t\t// Left open when something the command started may still be running: the record then says\n\t\t// that what it changed may not all be here.\n\t\tif (!complete) return { changes, unrecorded };\n\t\tawait this.journal.observed(this.sequence, {\n\t\t\tfiles: paths.size,\n\t\t\tbytesRead,\n\t\t\tbytesStored: this.journal.storedBytes - this.measure.storedBefore,\n\t\t\tdurationMs: Math.round(this.measure.durationMs + performance.now() - started),\n\t\t});\n\t\treturn { changes, unrecorded };\n\t}\n}\n\n/**\n * What `path` holds: nothing, or a regular file's content and mode. `undefined` for anything an\n * entry cannot record: a folder, a link, or a file too large to copy.\n */\nasync function readState(path: string): Promise<{ content?: Buffer; mode: number } | undefined> {\n\tlet stats;\n\ttry {\n\t\tstats = await lstat(path);\n\t} catch (error) {\n\t\tif (code(error) === 'ENOENT') return { mode: 0 };\n\t\tthrow error;\n\t}\n\tif (!stats.isFile() || stats.size > maxCoveredBytes) return undefined;\n\ttry {\n\t\treturn { content: await readFile(path), mode: stats.mode & 0o7777 };\n\t} catch (error) {\n\t\t// Deleted between the two calls.\n\t\tif (code(error) === 'ENOENT') return { mode: 0 };\n\t\tthrow error;\n\t}\n}\n\n/** Runs `task` on every item, at most `limit` at a time, and fails with the first failure. */\nasync function eachAtOnce<Item>(\n\titems: readonly Item[],\n\tlimit: number,\n\ttask: (item: Item, index: number) => Promise<void>,\n): Promise<void> {\n\tlet next = 0;\n\tlet failed = false;\n\tconst worker = async () => {\n\t\twhile (!failed && next < items.length) {\n\t\t\tconst index = next++;\n\t\t\ttry {\n\t\t\t\tawait task(items[index] as Item, index);\n\t\t\t} catch (error) {\n\t\t\t\tfailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t};\n\tawait Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n}\n"]}
@@ -180,7 +180,7 @@ export async function createCommandTool({ commands, policy, writes, covered, bas
180
180
  ? 'It changed no file the history covers.'
181
181
  : `Files it changed, recorded in the history: ${changes.map(change => shown(change.path)).join(', ')}.`);
182
182
  if (unrecorded.length > 0) {
183
- lines.push(`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB): ${unrecorded.map(shown).join(', ')}.`);
183
+ lines.push(`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB) before or after: ${unrecorded.map(shown).join(', ')}. Undo leaves them as they are.`);
184
184
  }
185
185
  const limit = definition.outputBytes ?? defaultCommandOutputBytes;
186
186
  if (result.printedBytes === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"commandTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { z } from 'zod';\nimport type { CommandLine } from '#src/agent/domain/approval';\nimport type { RunContext } from '#src/agent/domain/runContext';\nimport { displayPath } from '#src/recovery/domain/runChanges';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { AccessPolicy } from '../domain/accessPolicy.ts';\nimport {\n\tbuildArguments,\n\ttype CommandDefinition,\n\ttype CommandParameter,\n\ttype Commands,\n\tdefaultCommandOutputBytes,\n\tdefaultCommandTimeoutMs,\n\ttype ParameterValue,\n\tparametersOf,\n\tvalidateCommands,\n} from '../domain/commands.ts';\nimport { EditRefusedError, EditUnconfirmedError } from '../domain/fileEdits.ts';\nimport {\n\ttype CommandEnding,\n\tToolExecutionError,\n\ttype PreparingTool,\n} from '../domain/preparedCall.ts';\nimport { CommandCheckpoint, type CoveredFiles } from './commandCheckpoint.ts';\nimport { OutputFingerprint, withoutColors } from './commandOutput.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport type { WorkspaceWrites } from './workspaceWrites.ts';\n\n// Only what a toolchain needs to find itself and its locale. Everything else, the provider\n// keys included, stays in this process.\nconst inheritedVariables = ['PATH', 'HOME', 'TMPDIR', 'USER', 'SHELL', 'LANG'];\n// Long enough for a slow disk, short enough not to hold up building an agent.\nconst cwdCheckMs = 10_000;\n\n/** A command ready to run: its definition, and where it runs as a real path. */\ninterface ResolvedCommand {\n\tdefinition: CommandDefinition;\n\tcwd: string;\n}\n\n/**\n * `runCommand`, which runs one of the commands the consumer configured, by name, with the\n * parameters each one declares. Every call asks the approver, showing the program exactly as\n * it will run; `autoApprove` skips that.\n *\n * Around each command, the covered files are captured before and compared after, so what it\n * changed is in the run's history and can be undone: see `CommandCheckpoint`. If the capture\n * fails, the command does not run. A timeout or a cancellation stops its whole process group,\n * and is never reported as a completed command. If the group cannot be confirmed stopped, what\n * changed so far is recorded, the observation stays open, and writing stops for the run.\n *\n * This is not a sandbox: a command runs the repository's code, with everything this user can\n * reach. See \"Configured commands are trusted, not sandboxed\" in decisions.md.\n *\n * @throws {InvalidAgentConfigError} for an invalid configuration, a working directory outside\n * every `write` root, or on Windows, where a process group cannot be stopped.\n */\nexport async function createCommandTool({\n\tcommands,\n\tpolicy,\n\twrites,\n\tcovered,\n\tbase,\n\tplatform = process.platform,\n\trunProcess = runCommandProcess,\n}: {\n\tcommands: Commands;\n\tpolicy: AccessPolicy;\n\twrites: WorkspaceWrites;\n\tcovered: CoveredFiles;\n\t/** The real path of the first root, where a relative `cwd` starts. */\n\tbase: string;\n\tplatform?: NodeJS.Platform;\n\t/** Runs the program. Replaced in tests, to reach what a real process rarely does. */\n\trunProcess?: typeof runCommandProcess;\n}): Promise<PreparingTool> {\n\tif (platform === 'win32') {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t'Configured commands are not supported on Windows yet: the harness cannot stop a command and everything it started there',\n\t\t);\n\t}\n\tvalidateCommands(commands);\n\tconst resolved = new Map<string, ResolvedCommand>();\n\tfor (const [name, definition] of Object.entries(commands)) {\n\t\tresolved.set(name, {\n\t\t\tdefinition,\n\t\t\tcwd: await resolveCwd(`Command \"${name}\"`, definition.cwd, policy, base),\n\t\t});\n\t}\n\n\treturn definePreparingTool({\n\t\tname: 'runCommand',\n\t\tdescription: describeTool(commands),\n\t\tinput: inputSchema(commands),\n\t\tsummarize: ({ command: { name, ...values } }) => {\n\t\t\tconst definition = commands[name];\n\t\t\t// The schema only permits configured names, but keep this defensive for callers that\n\t\t\t// use a tool outside LLMAgent. This description never uses the resolved cwd.\n\t\t\treturn definition ? visibleCommand(definition, values) : name;\n\t\t},\n\t\tprepare: async ({ command: { name, ...values } }, signal, { run }) => {\n\t\t\tconst command = resolved.get(name);\n\t\t\t// The schema allows only the configured names.\n\t\t\tif (!command) throw new Error(`There is no command named \"${name}\"`);\n\t\t\tconst paramValues: Record<string, ParameterValue> = {};\n\t\t\tfor (const parameter of parametersOf(command.definition)) {\n\t\t\t\tconst value =\n\t\t\t\t\t(values as Record<string, ParameterValue | undefined>)[parameter.name] ?? null;\n\t\t\t\tparamValues[parameter.name] =\n\t\t\t\t\tparameter.type === 'paths' && Array.isArray(value)\n\t\t\t\t\t\t? await resolvePaths(value, command.cwd, policy, signal)\n\t\t\t\t\t\t: value;\n\t\t\t}\n\t\t\tconst line: CommandLine = Object.freeze({\n\t\t\t\texecutable: command.definition.executable,\n\t\t\t\targs: Object.freeze(buildArguments(command.definition, paramValues)) as string[],\n\t\t\t\tcwd: command.cwd,\n\t\t\t});\n\n\t\t\tlet ending: CommandEnding | undefined;\n\t\t\treturn {\n\t\t\t\t// A command can change anything its user can: it is always asked about.\n\t\t\t\trisk: 'destructive',\n\t\t\t\tcommand: line,\n\t\t\t\tending: () => ending,\n\t\t\t\trun: runSignal =>\n\t\t\t\t\trunCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tline,\n\t\t\t\t\t\tdefinition: command.definition,\n\t\t\t\t\t\tcontext: run,\n\t\t\t\t\t\tsignal: runSignal,\n\t\t\t\t\t\tended: end => {\n\t\t\t\t\t\t\tending = end;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t});\n\n\tasync function runCommand({\n\t\tname,\n\t\tline,\n\t\tdefinition,\n\t\tcontext,\n\t\tsignal,\n\t\tended,\n\t}: {\n\t\tname: string;\n\t\tline: CommandLine;\n\t\tdefinition: CommandDefinition;\n\t\tcontext: RunContext;\n\t\tsignal: AbortSignal;\n\t\t/** Told how the command ended, once it ran to an end and everything it changed is recorded. */\n\t\tended: (ending: CommandEnding) => void;\n\t}): Promise<string> {\n\t\tsignal.throwIfAborted();\n\t\tconst session = writes.sessionFor(context);\n\t\tconst journal = await session.journalForChange();\n\t\tlet checkpoint: CommandCheckpoint;\n\t\ttry {\n\t\t\tcheckpoint = await CommandCheckpoint.begin({ journal, covered, by: name, signal });\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The harness could not capture the files before \"${name}\", so it did not run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tlet result: CommandProcessResult | undefined;\n\t\tlet startFailure: unknown;\n\t\tconst fingerprint = new OutputFingerprint();\n\t\ttry {\n\t\t\tresult = await runProcess(line.executable, line.args, {\n\t\t\t\tcwd: line.cwd,\n\t\t\t\tenv: environmentFor(definition),\n\t\t\t\ttimeoutMs: definition.timeoutMs ?? defaultCommandTimeoutMs,\n\t\t\t\toutputBytes: definition.outputBytes ?? defaultCommandOutputBytes,\n\t\t\t\tsignal,\n\t\t\t\tonOutput: (stream, chunk) => {\n\t\t\t\t\tfingerprint.push(stream, chunk);\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tstartFailure = error;\n\t\t}\n\n\t\tconst complete = result?.stopped ?? true;\n\t\tlet observed: Awaited<ReturnType<CommandCheckpoint['end']>>;\n\t\ttry {\n\t\t\tobserved = await checkpoint.end({ complete });\n\t\t} catch (error) {\n\t\t\tsession.stop('Writing stopped: the harness could not record what a command changed');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`\"${name}\" ran, but the harness could not record what it changed; writing has stopped for this run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tif (!result) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(`\"${name}\" could not be started`, { cause: startFailure });\n\t\t}\n\t\tconst report = describeRun(name, definition, result, observed);\n\t\tif (!complete) {\n\t\t\tsession.stop('Writing stopped: a command could not be stopped');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`${report}\\n\"${name}\" could not be stopped: something it started may still be changing files, and the history may not hold all of it. Writing has stopped for this run.`,\n\t\t\t);\n\t\t}\n\t\t// After its changes are recorded: a cancelled command is never reported as finished.\n\t\tif (result.outcome === 'cancelled') signal.throwIfAborted();\n\t\tended({\n\t\t\tpassed: result.outcome === 'exited' && result.exitCode === 0,\n\t\t\thow: howItEnded(result),\n\t\t\toutput: withoutColors(result.output),\n\t\t\tfingerprint: fingerprint.digest(),\n\t\t\t...(observed.unrecorded.length === 0 ? { state: journal.contentState() } : {}),\n\t\t\tchanged: journal.appliedPaths.map(path => displayPath(base, path)),\n\t\t});\n\t\tif (result.outcome !== 'exited') throw new ToolExecutionError(report);\n\t\tif (result.exitCode !== 0) {\n\t\t\tthrow new ToolExecutionError(report, {\n\t\t\t\t...(result.exitCode === null ? {} : { exitCode: result.exitCode }),\n\t\t\t});\n\t\t}\n\t\treturn report;\n\t}\n\n\tfunction describeRun(\n\t\tname: string,\n\t\tdefinition: CommandDefinition,\n\t\tresult: CommandProcessResult,\n\t\t{ changes, unrecorded }: Awaited<ReturnType<CommandCheckpoint['end']>>,\n\t): string {\n\t\tconst seconds = (result.durationMs / 1000).toFixed(1);\n\t\tconst lines: string[] = [];\n\t\tif (result.outcome === 'timedOut') {\n\t\t\tconst limit = (definition.timeoutMs ?? defaultCommandTimeoutMs) / 1000;\n\t\t\tlines.push(`\"${name}\" ran past its limit of ${String(limit)} s and was stopped.`);\n\t\t} else if (result.outcome === 'cancelled') {\n\t\t\tlines.push(`\"${name}\" was stopped: the run was cancelled.`);\n\t\t} else if (result.exitCode !== null) {\n\t\t\tlines.push(`\"${name}\" exited with code ${String(result.exitCode)} after ${seconds} s.`);\n\t\t} else {\n\t\t\tlines.push(`\"${name}\" was killed by ${result.signal ?? 'a signal'} after ${seconds} s.`);\n\t\t}\n\n\t\tconst shown = (path: string) => displayPath(base, path);\n\t\tlines.push(\n\t\t\tchanges.length === 0\n\t\t\t\t? 'It changed no file the history covers.'\n\t\t\t\t: `Files it changed, recorded in the history: ${changes.map(change => shown(change.path)).join(', ')}.`,\n\t\t);\n\t\tif (unrecorded.length > 0) {\n\t\t\tlines.push(\n\t\t\t\t`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB): ${unrecorded.map(shown).join(', ')}.`,\n\t\t\t);\n\t\t}\n\n\t\tconst limit = definition.outputBytes ?? defaultCommandOutputBytes;\n\t\tif (result.printedBytes === 0) {\n\t\t\tlines.push('It printed nothing.');\n\t\t} else {\n\t\t\tlines.push(\n\t\t\t\tresult.printedBytes > limit\n\t\t\t\t\t? `Output, the last ${kilobytes(limit)} of ${kilobytes(result.printedBytes)}:`\n\t\t\t\t\t: 'Output:',\n\t\t\t\tresult.output,\n\t\t\t);\n\t\t}\n\t\treturn lines.join('\\n');\n\t}\n}\n\nfunction howItEnded(result: CommandProcessResult): string {\n\tif (result.outcome === 'timedOut') return 'ran past its time limit';\n\tif (result.outcome === 'cancelled') return 'was cancelled';\n\tif (result.exitCode !== null) return `exited with code ${String(result.exitCode)}`;\n\treturn `was killed by ${result.signal ?? 'a signal'}`;\n}\n\nfunction kilobytes(bytes: number): string {\n\treturn `${String(Math.ceil(bytes / 1024))} KB`;\n}\n\n/** The command line the model can see, before paths are resolved against the host workspace. */\nfunction visibleCommand(\n\tdefinition: CommandDefinition,\n\tvalues: Record<string, ParameterValue | undefined>,\n): string {\n\tconst args: string[] = [];\n\tfor (const argument of definition.args ?? []) {\n\t\tif (typeof argument === 'string') {\n\t\t\targs.push(argument);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst value = values[argument.name] ?? null;\n\t\tif (argument.type === 'paths') {\n\t\t\tif (Array.isArray(value)) args.push(...value);\n\t\t} else if (value !== null && typeof value === 'string') {\n\t\t\targs.push(value);\n\t\t}\n\t}\n\n\treturn [definition.executable, ...args].join(' ');\n}\n\n/**\n * Where a configured program runs, as a real path inside a `write` root. `program` names it in\n * the error, as `Command \"test\"`.\n */\nexport async function resolveCwd(\n\tprogram: string,\n\tcwd: string | undefined,\n\tpolicy: AccessPolicy,\n\tbase: string,\n): Promise<string> {\n\tconst named = resolve(base, cwd ?? '.');\n\tlet real: string;\n\ttry {\n\t\treal = await realpath(named);\n\t\tawait policy.check(real, 'write', AbortSignal.timeout(cwdCheckMs));\n\t} catch (error) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${program} needs a cwd inside a write root of the workspace`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\treturn real;\n}\n\n/**\n * Each path, once the policy lets the agent read it, relative to where the command runs. One\n * that would read as a flag is given as `./` and its name.\n */\nasync function resolvePaths(\n\tpaths: string[],\n\tcwd: string,\n\tpolicy: AccessPolicy,\n\tsignal: AbortSignal,\n): Promise<string[]> {\n\tconst resolved: string[] = [];\n\tfor (const path of paths) {\n\t\tconst { absolute } = await policy.check(path, 'read', signal);\n\t\tconst fromCwd = relative(cwd, absolute) || '.';\n\t\tresolved.push(fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd);\n\t}\n\treturn resolved;\n}\n\n/** A small environment of its own: the inherited few, then what the consumer set. */\nexport function environmentFor(\n\tdefinition: Pick<CommandDefinition, 'env'>,\n\tenv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [variable, value] of Object.entries(env)) {\n\t\tif (value === undefined) continue;\n\t\tif (inheritedVariables.includes(variable) || variable.startsWith('LC_')) {\n\t\t\tinherited[variable] = value;\n\t\t}\n\t}\n\treturn { ...inherited, ...definition.env };\n}\n\nfunction describeTool(commands: Commands): string {\n\tconst list = Object.entries(commands)\n\t\t.map(([name, { description }]) => `- ${name}: ${description}`)\n\t\t.join('\\n');\n\treturn [\n\t\t\"Runs one of the project's configured commands, by name, such as its tests or its formatter.\",\n\t\t'Files it changes are recorded in the history, so they can be undone. Its output is cut to its end.',\n\t\t'Commands:',\n\t\tlist,\n\t].join('\\n');\n}\n\n/**\n * One object per command, told apart by `name`, each with the parameters that command takes.\n * Strict mode needs every key present, so a parameter that may be left out takes `null`.\n */\nfunction inputSchema(commands: Commands) {\n\tconst variants = Object.entries(commands).map(([name, definition]) =>\n\t\tz.object({\n\t\t\tname: z.enum([name]),\n\t\t\t...Object.fromEntries(\n\t\t\t\tparametersOf(definition).map(parameter => [parameter.name, parameterSchema(parameter)]),\n\t\t\t),\n\t\t}),\n\t);\n\tconst [first, ...rest] = variants;\n\t// `validateCommands` refuses an empty configuration.\n\tif (!first) throw new InvalidAgentConfigError('No command is configured');\n\treturn z.object({\n\t\tcommand: (rest.length === 0 ? first : z.union([first, ...rest])).describe(\n\t\t\t'The command to run, and its parameters.',\n\t\t),\n\t});\n}\n\nfunction parameterSchema(parameter: CommandParameter): z.ZodType {\n\tswitch (parameter.type) {\n\t\tcase 'paths':\n\t\t\treturn z\n\t\t\t\t.array(z.string())\n\t\t\t\t.describe(`${parameter.description} Paths inside the workspace; [] for none.`);\n\t\tcase 'choice': {\n\t\t\tconst values = z.enum(parameter.values as [string, ...string[]]);\n\t\t\treturn parameter.required === true\n\t\t\t\t? values.describe(parameter.description)\n\t\t\t\t: values.nullable().describe(`${parameter.description} null to leave it out.`);\n\t\t}\n\t\tcase 'text': {\n\t\t\tconst text = z.string();\n\t\t\tconst description = `${parameter.description} Must match the regular expression ${parameter.pattern}, and not start with \"-\".`;\n\t\t\treturn parameter.required === true\n\t\t\t\t? text.describe(description)\n\t\t\t\t: text.nullable().describe(`${description} null to leave it out.`);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"commandTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { z } from 'zod';\nimport type { CommandLine } from '#src/agent/domain/approval';\nimport type { RunContext } from '#src/agent/domain/runContext';\nimport { displayPath } from '#src/recovery/domain/runChanges';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { AccessPolicy } from '../domain/accessPolicy.ts';\nimport {\n\tbuildArguments,\n\ttype CommandDefinition,\n\ttype CommandParameter,\n\ttype Commands,\n\tdefaultCommandOutputBytes,\n\tdefaultCommandTimeoutMs,\n\ttype ParameterValue,\n\tparametersOf,\n\tvalidateCommands,\n} from '../domain/commands.ts';\nimport { EditRefusedError, EditUnconfirmedError } from '../domain/fileEdits.ts';\nimport {\n\ttype CommandEnding,\n\tToolExecutionError,\n\ttype PreparingTool,\n} from '../domain/preparedCall.ts';\nimport { CommandCheckpoint, type CoveredFiles } from './commandCheckpoint.ts';\nimport { OutputFingerprint, withoutColors } from './commandOutput.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport type { WorkspaceWrites } from './workspaceWrites.ts';\n\n// Only what a toolchain needs to find itself and its locale. Everything else, the provider\n// keys included, stays in this process.\nconst inheritedVariables = ['PATH', 'HOME', 'TMPDIR', 'USER', 'SHELL', 'LANG'];\n// Long enough for a slow disk, short enough not to hold up building an agent.\nconst cwdCheckMs = 10_000;\n\n/** A command ready to run: its definition, and where it runs as a real path. */\ninterface ResolvedCommand {\n\tdefinition: CommandDefinition;\n\tcwd: string;\n}\n\n/**\n * `runCommand`, which runs one of the commands the consumer configured, by name, with the\n * parameters each one declares. Every call asks the approver, showing the program exactly as\n * it will run; `autoApprove` skips that.\n *\n * Around each command, the covered files are captured before and compared after, so what it\n * changed is in the run's history and can be undone: see `CommandCheckpoint`. If the capture\n * fails, the command does not run. A timeout or a cancellation stops its whole process group,\n * and is never reported as a completed command. If the group cannot be confirmed stopped, what\n * changed so far is recorded, the observation stays open, and writing stops for the run.\n *\n * This is not a sandbox: a command runs the repository's code, with everything this user can\n * reach. See \"Configured commands are trusted, not sandboxed\" in decisions.md.\n *\n * @throws {InvalidAgentConfigError} for an invalid configuration, a working directory outside\n * every `write` root, or on Windows, where a process group cannot be stopped.\n */\nexport async function createCommandTool({\n\tcommands,\n\tpolicy,\n\twrites,\n\tcovered,\n\tbase,\n\tplatform = process.platform,\n\trunProcess = runCommandProcess,\n}: {\n\tcommands: Commands;\n\tpolicy: AccessPolicy;\n\twrites: WorkspaceWrites;\n\tcovered: CoveredFiles;\n\t/** The real path of the first root, where a relative `cwd` starts. */\n\tbase: string;\n\tplatform?: NodeJS.Platform;\n\t/** Runs the program. Replaced in tests, to reach what a real process rarely does. */\n\trunProcess?: typeof runCommandProcess;\n}): Promise<PreparingTool> {\n\tif (platform === 'win32') {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t'Configured commands are not supported on Windows yet: the harness cannot stop a command and everything it started there',\n\t\t);\n\t}\n\tvalidateCommands(commands);\n\tconst resolved = new Map<string, ResolvedCommand>();\n\tfor (const [name, definition] of Object.entries(commands)) {\n\t\tresolved.set(name, {\n\t\t\tdefinition,\n\t\t\tcwd: await resolveCwd(`Command \"${name}\"`, definition.cwd, policy, base),\n\t\t});\n\t}\n\n\treturn definePreparingTool({\n\t\tname: 'runCommand',\n\t\tdescription: describeTool(commands),\n\t\tinput: inputSchema(commands),\n\t\tsummarize: ({ command: { name, ...values } }) => {\n\t\t\tconst definition = commands[name];\n\t\t\t// The schema only permits configured names, but keep this defensive for callers that\n\t\t\t// use a tool outside LLMAgent. This description never uses the resolved cwd.\n\t\t\treturn definition ? visibleCommand(definition, values) : name;\n\t\t},\n\t\tprepare: async ({ command: { name, ...values } }, signal, { run }) => {\n\t\t\tconst command = resolved.get(name);\n\t\t\t// The schema allows only the configured names.\n\t\t\tif (!command) throw new Error(`There is no command named \"${name}\"`);\n\t\t\tconst paramValues: Record<string, ParameterValue> = {};\n\t\t\tfor (const parameter of parametersOf(command.definition)) {\n\t\t\t\tconst value =\n\t\t\t\t\t(values as Record<string, ParameterValue | undefined>)[parameter.name] ?? null;\n\t\t\t\tparamValues[parameter.name] =\n\t\t\t\t\tparameter.type === 'paths' && Array.isArray(value)\n\t\t\t\t\t\t? await resolvePaths(value, command.cwd, policy, signal)\n\t\t\t\t\t\t: value;\n\t\t\t}\n\t\t\tconst line: CommandLine = Object.freeze({\n\t\t\t\texecutable: command.definition.executable,\n\t\t\t\targs: Object.freeze(buildArguments(command.definition, paramValues)) as string[],\n\t\t\t\tcwd: command.cwd,\n\t\t\t});\n\n\t\t\tlet ending: CommandEnding | undefined;\n\t\t\treturn {\n\t\t\t\t// A command can change anything its user can: it is always asked about.\n\t\t\t\trisk: 'destructive',\n\t\t\t\tcommand: line,\n\t\t\t\tending: () => ending,\n\t\t\t\trun: runSignal =>\n\t\t\t\t\trunCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tline,\n\t\t\t\t\t\tdefinition: command.definition,\n\t\t\t\t\t\tcontext: run,\n\t\t\t\t\t\tsignal: runSignal,\n\t\t\t\t\t\tended: end => {\n\t\t\t\t\t\t\tending = end;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t});\n\n\tasync function runCommand({\n\t\tname,\n\t\tline,\n\t\tdefinition,\n\t\tcontext,\n\t\tsignal,\n\t\tended,\n\t}: {\n\t\tname: string;\n\t\tline: CommandLine;\n\t\tdefinition: CommandDefinition;\n\t\tcontext: RunContext;\n\t\tsignal: AbortSignal;\n\t\t/** Told how the command ended, once it ran to an end and everything it changed is recorded. */\n\t\tended: (ending: CommandEnding) => void;\n\t}): Promise<string> {\n\t\tsignal.throwIfAborted();\n\t\tconst session = writes.sessionFor(context);\n\t\tconst journal = await session.journalForChange();\n\t\tlet checkpoint: CommandCheckpoint;\n\t\ttry {\n\t\t\tcheckpoint = await CommandCheckpoint.begin({ journal, covered, by: name, signal });\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The harness could not capture the files before \"${name}\", so it did not run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tlet result: CommandProcessResult | undefined;\n\t\tlet startFailure: unknown;\n\t\tconst fingerprint = new OutputFingerprint();\n\t\ttry {\n\t\t\tresult = await runProcess(line.executable, line.args, {\n\t\t\t\tcwd: line.cwd,\n\t\t\t\tenv: environmentFor(definition),\n\t\t\t\ttimeoutMs: definition.timeoutMs ?? defaultCommandTimeoutMs,\n\t\t\t\toutputBytes: definition.outputBytes ?? defaultCommandOutputBytes,\n\t\t\t\tsignal,\n\t\t\t\tonOutput: (stream, chunk) => {\n\t\t\t\t\tfingerprint.push(stream, chunk);\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tstartFailure = error;\n\t\t}\n\n\t\tconst complete = result?.stopped ?? true;\n\t\tlet observed: Awaited<ReturnType<CommandCheckpoint['end']>>;\n\t\ttry {\n\t\t\tobserved = await checkpoint.end({ complete });\n\t\t} catch (error) {\n\t\t\tsession.stop('Writing stopped: the harness could not record what a command changed');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`\"${name}\" ran, but the harness could not record what it changed; writing has stopped for this run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tif (!result) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(`\"${name}\" could not be started`, { cause: startFailure });\n\t\t}\n\t\tconst report = describeRun(name, definition, result, observed);\n\t\tif (!complete) {\n\t\t\tsession.stop('Writing stopped: a command could not be stopped');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`${report}\\n\"${name}\" could not be stopped: something it started may still be changing files, and the history may not hold all of it. Writing has stopped for this run.`,\n\t\t\t);\n\t\t}\n\t\t// After its changes are recorded: a cancelled command is never reported as finished.\n\t\tif (result.outcome === 'cancelled') signal.throwIfAborted();\n\t\tended({\n\t\t\tpassed: result.outcome === 'exited' && result.exitCode === 0,\n\t\t\thow: howItEnded(result),\n\t\t\toutput: withoutColors(result.output),\n\t\t\tfingerprint: fingerprint.digest(),\n\t\t\t...(observed.unrecorded.length === 0 ? { state: journal.contentState() } : {}),\n\t\t\tchanged: journal.appliedPaths.map(path => displayPath(base, path)),\n\t\t});\n\t\tif (result.outcome !== 'exited') throw new ToolExecutionError(report);\n\t\tif (result.exitCode !== 0) {\n\t\t\tthrow new ToolExecutionError(report, {\n\t\t\t\t...(result.exitCode === null ? {} : { exitCode: result.exitCode }),\n\t\t\t});\n\t\t}\n\t\treturn report;\n\t}\n\n\tfunction describeRun(\n\t\tname: string,\n\t\tdefinition: CommandDefinition,\n\t\tresult: CommandProcessResult,\n\t\t{ changes, unrecorded }: Awaited<ReturnType<CommandCheckpoint['end']>>,\n\t): string {\n\t\tconst seconds = (result.durationMs / 1000).toFixed(1);\n\t\tconst lines: string[] = [];\n\t\tif (result.outcome === 'timedOut') {\n\t\t\tconst limit = (definition.timeoutMs ?? defaultCommandTimeoutMs) / 1000;\n\t\t\tlines.push(`\"${name}\" ran past its limit of ${String(limit)} s and was stopped.`);\n\t\t} else if (result.outcome === 'cancelled') {\n\t\t\tlines.push(`\"${name}\" was stopped: the run was cancelled.`);\n\t\t} else if (result.exitCode !== null) {\n\t\t\tlines.push(`\"${name}\" exited with code ${String(result.exitCode)} after ${seconds} s.`);\n\t\t} else {\n\t\t\tlines.push(`\"${name}\" was killed by ${result.signal ?? 'a signal'} after ${seconds} s.`);\n\t\t}\n\n\t\tconst shown = (path: string) => displayPath(base, path);\n\t\tlines.push(\n\t\t\tchanges.length === 0\n\t\t\t\t? 'It changed no file the history covers.'\n\t\t\t\t: `Files it changed, recorded in the history: ${changes.map(change => shown(change.path)).join(', ')}.`,\n\t\t);\n\t\tif (unrecorded.length > 0) {\n\t\t\tlines.push(\n\t\t\t\t`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB) before or after: ${unrecorded.map(shown).join(', ')}. Undo leaves them as they are.`,\n\t\t\t);\n\t\t}\n\n\t\tconst limit = definition.outputBytes ?? defaultCommandOutputBytes;\n\t\tif (result.printedBytes === 0) {\n\t\t\tlines.push('It printed nothing.');\n\t\t} else {\n\t\t\tlines.push(\n\t\t\t\tresult.printedBytes > limit\n\t\t\t\t\t? `Output, the last ${kilobytes(limit)} of ${kilobytes(result.printedBytes)}:`\n\t\t\t\t\t: 'Output:',\n\t\t\t\tresult.output,\n\t\t\t);\n\t\t}\n\t\treturn lines.join('\\n');\n\t}\n}\n\nfunction howItEnded(result: CommandProcessResult): string {\n\tif (result.outcome === 'timedOut') return 'ran past its time limit';\n\tif (result.outcome === 'cancelled') return 'was cancelled';\n\tif (result.exitCode !== null) return `exited with code ${String(result.exitCode)}`;\n\treturn `was killed by ${result.signal ?? 'a signal'}`;\n}\n\nfunction kilobytes(bytes: number): string {\n\treturn `${String(Math.ceil(bytes / 1024))} KB`;\n}\n\n/** The command line the model can see, before paths are resolved against the host workspace. */\nfunction visibleCommand(\n\tdefinition: CommandDefinition,\n\tvalues: Record<string, ParameterValue | undefined>,\n): string {\n\tconst args: string[] = [];\n\tfor (const argument of definition.args ?? []) {\n\t\tif (typeof argument === 'string') {\n\t\t\targs.push(argument);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst value = values[argument.name] ?? null;\n\t\tif (argument.type === 'paths') {\n\t\t\tif (Array.isArray(value)) args.push(...value);\n\t\t} else if (value !== null && typeof value === 'string') {\n\t\t\targs.push(value);\n\t\t}\n\t}\n\n\treturn [definition.executable, ...args].join(' ');\n}\n\n/**\n * Where a configured program runs, as a real path inside a `write` root. `program` names it in\n * the error, as `Command \"test\"`.\n */\nexport async function resolveCwd(\n\tprogram: string,\n\tcwd: string | undefined,\n\tpolicy: AccessPolicy,\n\tbase: string,\n): Promise<string> {\n\tconst named = resolve(base, cwd ?? '.');\n\tlet real: string;\n\ttry {\n\t\treal = await realpath(named);\n\t\tawait policy.check(real, 'write', AbortSignal.timeout(cwdCheckMs));\n\t} catch (error) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${program} needs a cwd inside a write root of the workspace`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\treturn real;\n}\n\n/**\n * Each path, once the policy lets the agent read it, relative to where the command runs. One\n * that would read as a flag is given as `./` and its name.\n */\nasync function resolvePaths(\n\tpaths: string[],\n\tcwd: string,\n\tpolicy: AccessPolicy,\n\tsignal: AbortSignal,\n): Promise<string[]> {\n\tconst resolved: string[] = [];\n\tfor (const path of paths) {\n\t\tconst { absolute } = await policy.check(path, 'read', signal);\n\t\tconst fromCwd = relative(cwd, absolute) || '.';\n\t\tresolved.push(fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd);\n\t}\n\treturn resolved;\n}\n\n/** A small environment of its own: the inherited few, then what the consumer set. */\nexport function environmentFor(\n\tdefinition: Pick<CommandDefinition, 'env'>,\n\tenv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [variable, value] of Object.entries(env)) {\n\t\tif (value === undefined) continue;\n\t\tif (inheritedVariables.includes(variable) || variable.startsWith('LC_')) {\n\t\t\tinherited[variable] = value;\n\t\t}\n\t}\n\treturn { ...inherited, ...definition.env };\n}\n\nfunction describeTool(commands: Commands): string {\n\tconst list = Object.entries(commands)\n\t\t.map(([name, { description }]) => `- ${name}: ${description}`)\n\t\t.join('\\n');\n\treturn [\n\t\t\"Runs one of the project's configured commands, by name, such as its tests or its formatter.\",\n\t\t'Files it changes are recorded in the history, so they can be undone. Its output is cut to its end.',\n\t\t'Commands:',\n\t\tlist,\n\t].join('\\n');\n}\n\n/**\n * One object per command, told apart by `name`, each with the parameters that command takes.\n * Strict mode needs every key present, so a parameter that may be left out takes `null`.\n */\nfunction inputSchema(commands: Commands) {\n\tconst variants = Object.entries(commands).map(([name, definition]) =>\n\t\tz.object({\n\t\t\tname: z.enum([name]),\n\t\t\t...Object.fromEntries(\n\t\t\t\tparametersOf(definition).map(parameter => [parameter.name, parameterSchema(parameter)]),\n\t\t\t),\n\t\t}),\n\t);\n\tconst [first, ...rest] = variants;\n\t// `validateCommands` refuses an empty configuration.\n\tif (!first) throw new InvalidAgentConfigError('No command is configured');\n\treturn z.object({\n\t\tcommand: (rest.length === 0 ? first : z.union([first, ...rest])).describe(\n\t\t\t'The command to run, and its parameters.',\n\t\t),\n\t});\n}\n\nfunction parameterSchema(parameter: CommandParameter): z.ZodType {\n\tswitch (parameter.type) {\n\t\tcase 'paths':\n\t\t\treturn z\n\t\t\t\t.array(z.string())\n\t\t\t\t.describe(`${parameter.description} Paths inside the workspace; [] for none.`);\n\t\tcase 'choice': {\n\t\t\tconst values = z.enum(parameter.values as [string, ...string[]]);\n\t\t\treturn parameter.required === true\n\t\t\t\t? values.describe(parameter.description)\n\t\t\t\t: values.nullable().describe(`${parameter.description} null to leave it out.`);\n\t\t}\n\t\tcase 'text': {\n\t\t\tconst text = z.string();\n\t\t\tconst description = `${parameter.description} Must match the regular expression ${parameter.pattern}, and not start with \"-\".`;\n\t\t\treturn parameter.required === true\n\t\t\t\t? text.describe(description)\n\t\t\t\t: text.nullable().describe(`${description} null to leave it out.`);\n\t\t}\n\t}\n}\n"]}
@@ -59,7 +59,7 @@ export declare function stateDirectoryOf({ stateDirectory }: WorkspaceOptions):
59
59
  * @throws {InvalidAgentConfigError} when the workspace has no root.
60
60
  * @throws {UnrecoverableError} when the store cannot be opened.
61
61
  */
62
- export declare function openStore(workspace: WorkspaceOptions, directory: string): Promise<RecoveryStore>;
62
+ export declare function openStore(workspace: WorkspaceOptions, directory: string, logger: ILogger): Promise<RecoveryStore>;
63
63
  /**
64
64
  * Opens what the file tools of one workspace share. With `formatter`, its edits format each file
65
65
  * they write.
@@ -38,12 +38,12 @@ export async function stateDirectoryOf({ stateDirectory }) {
38
38
  * @throws {InvalidAgentConfigError} when the workspace has no root.
39
39
  * @throws {UnrecoverableError} when the store cannot be opened.
40
40
  */
41
- export async function openStore(workspace, directory) {
41
+ export async function openStore(workspace, directory, logger) {
42
42
  const [first] = workspace.roots;
43
43
  if (!first)
44
44
  throw new InvalidAgentConfigError('A workspace needs at least one root');
45
45
  try {
46
- return await FileRecoveryStore.open({ root: resolve(first.path), directory });
46
+ return await FileRecoveryStore.open({ root: resolve(first.path), directory, logger });
47
47
  }
48
48
  catch (error) {
49
49
  throw new UnrecoverableError('The workspace history could not be opened', {
@@ -67,7 +67,7 @@ export async function openWorkspace(workspace, logger, { formatter } = {}) {
67
67
  // The history holds the source a run replaced: no tool may read or write it.
68
68
  protectedPaths: [stateDirectory],
69
69
  });
70
- const store = await openStore(workspace, stateDirectory);
70
+ const store = await openStore(workspace, stateDirectory, logger);
71
71
  // Real, as the policy's paths are, so a file's path can be judged by name.
72
72
  const root = await realpath(resolve(roots[0]?.path ?? '.'));
73
73
  const inner = await chooseWorkspace({