@howaboua/pi-codex-conversion 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/patch/core.ts +44 -12
- package/src/patch/types.ts +9 -2
- package/src/tools/apply-patch-tool.ts +41 -39
package/package.json
CHANGED
package/src/patch/core.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname } from "node:path";
|
|
|
3
3
|
import { linesMatch } from "./matching.ts";
|
|
4
4
|
import { parsePatchActions, parseUpdateFile } from "./parser.ts";
|
|
5
5
|
import { openFileAtPath, pathExists, removeFileAtPath, resolvePatchPath, writeFileAtPath } from "./paths.ts";
|
|
6
|
-
import { DiffError, ExecutePatchError, type ExecutePatchResult, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
6
|
+
import { DiffError, ExecutePatchError, type ExecutePatchFailure, type ExecutePatchResult, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
7
7
|
|
|
8
8
|
export const patchFsOps = {
|
|
9
9
|
mkdirSync: fs.mkdirSync,
|
|
@@ -218,6 +218,14 @@ function applyAction({
|
|
|
218
218
|
return fuzz;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
function getActionPaths(action: ParsedPatchAction): string[] {
|
|
222
|
+
return [action.path, action.type === "update" ? action.movePath : undefined].filter((path): path is string => typeof path === "string");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getCanonicalActionPaths({ cwd, action }: { cwd: string; action: ParsedPatchAction }): string[] {
|
|
226
|
+
return getActionPaths(action).map((path) => resolvePatchPath({ cwd, patchPath: path }));
|
|
227
|
+
}
|
|
228
|
+
|
|
221
229
|
export function executePatch({ cwd, patchText }: { cwd: string; patchText: string }): ExecutePatchResult {
|
|
222
230
|
if (!patchText.startsWith("*** Begin Patch")) {
|
|
223
231
|
throw new DiffError("Patch must start with '*** Begin Patch'");
|
|
@@ -228,9 +236,22 @@ export function executePatch({ cwd, patchText }: { cwd: string; patchText: strin
|
|
|
228
236
|
const createdFiles = new Set<string>();
|
|
229
237
|
const deletedFiles = new Set<string>();
|
|
230
238
|
const movedFiles = new Set<string>();
|
|
239
|
+
const blockedPaths = new Set<string>();
|
|
240
|
+
const failures: ExecutePatchFailure[] = [];
|
|
231
241
|
let fuzz = 0;
|
|
232
242
|
|
|
233
243
|
for (const action of actions) {
|
|
244
|
+
const actionPaths = getActionPaths(action);
|
|
245
|
+
const canonicalActionPaths = getCanonicalActionPaths({ cwd, action });
|
|
246
|
+
const overlappingPaths = canonicalActionPaths.filter((path) => blockedPaths.has(path));
|
|
247
|
+
if (overlappingPaths.length > 0) {
|
|
248
|
+
failures.push({
|
|
249
|
+
action,
|
|
250
|
+
message: `Skipped because an earlier failed action affected ${actionPaths.filter((_, index) => overlappingPaths.includes(canonicalActionPaths[index])).join(", ")}`,
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
234
255
|
try {
|
|
235
256
|
fuzz += applyAction({
|
|
236
257
|
cwd,
|
|
@@ -242,20 +263,31 @@ export function executePatch({ cwd, patchText }: { cwd: string; patchText: strin
|
|
|
242
263
|
});
|
|
243
264
|
} catch (error) {
|
|
244
265
|
const message = error instanceof Error ? error.message : String(error);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
createdFiles,
|
|
250
|
-
deletedFiles,
|
|
251
|
-
movedFiles,
|
|
252
|
-
fuzz,
|
|
253
|
-
}),
|
|
254
|
-
action,
|
|
255
|
-
);
|
|
266
|
+
for (const path of canonicalActionPaths) {
|
|
267
|
+
blockedPaths.add(path);
|
|
268
|
+
}
|
|
269
|
+
failures.push({ action, message });
|
|
256
270
|
}
|
|
257
271
|
}
|
|
258
272
|
|
|
273
|
+
if (failures.length > 0) {
|
|
274
|
+
const message =
|
|
275
|
+
failures.length === 1
|
|
276
|
+
? failures[0].message
|
|
277
|
+
: failures.map(({ action, message: failureMessage }) => `${action.path}: ${failureMessage}`).join("\n");
|
|
278
|
+
throw new ExecutePatchError(
|
|
279
|
+
message,
|
|
280
|
+
buildExecutePatchResult({
|
|
281
|
+
changedFiles,
|
|
282
|
+
createdFiles,
|
|
283
|
+
deletedFiles,
|
|
284
|
+
movedFiles,
|
|
285
|
+
fuzz,
|
|
286
|
+
}),
|
|
287
|
+
failures,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
259
291
|
return buildExecutePatchResult({
|
|
260
292
|
changedFiles,
|
|
261
293
|
createdFiles,
|
package/src/patch/types.ts
CHANGED
|
@@ -36,6 +36,11 @@ export interface ExecutePatchResult {
|
|
|
36
36
|
fuzz: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export interface ExecutePatchFailure {
|
|
40
|
+
action: ParsedPatchAction;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
export class DiffError extends Error {
|
|
40
45
|
constructor(message: string) {
|
|
41
46
|
super(message);
|
|
@@ -46,12 +51,14 @@ export class DiffError extends Error {
|
|
|
46
51
|
export class ExecutePatchError extends DiffError {
|
|
47
52
|
result: ExecutePatchResult;
|
|
48
53
|
failedAction?: ParsedPatchAction;
|
|
54
|
+
failures: ExecutePatchFailure[];
|
|
49
55
|
|
|
50
|
-
constructor(message: string, result: ExecutePatchResult,
|
|
56
|
+
constructor(message: string, result: ExecutePatchResult, failures: ExecutePatchFailure[] = []) {
|
|
51
57
|
super(message);
|
|
52
58
|
this.name = "ExecutePatchError";
|
|
53
59
|
this.result = result;
|
|
54
|
-
this.
|
|
60
|
+
this.failures = failures;
|
|
61
|
+
this.failedAction = failures[0]?.action;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
hasPartialSuccess(): boolean {
|
|
@@ -17,7 +17,7 @@ interface ApplyPatchRenderState {
|
|
|
17
17
|
collapsed: string;
|
|
18
18
|
expanded: string;
|
|
19
19
|
status: "pending" | "partial_failure" | "failed";
|
|
20
|
-
|
|
20
|
+
failedTargets?: string[];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface ApplyPatchSuccessDetails {
|
|
@@ -29,7 +29,7 @@ interface ApplyPatchPartialFailureDetails {
|
|
|
29
29
|
status: "partial_failure";
|
|
30
30
|
result: ExecutePatchResult;
|
|
31
31
|
error: string;
|
|
32
|
-
|
|
32
|
+
failedTargets?: string[];
|
|
33
33
|
appliedFiles: string[];
|
|
34
34
|
failedFiles: string[];
|
|
35
35
|
recoveryInstructions: {
|
|
@@ -65,7 +65,7 @@ function setApplyPatchRenderState(
|
|
|
65
65
|
patchText: string,
|
|
66
66
|
cwd: string,
|
|
67
67
|
status: "pending" | "partial_failure" | "failed" = "pending",
|
|
68
|
-
|
|
68
|
+
failedTargets?: string[],
|
|
69
69
|
): void {
|
|
70
70
|
const collapsed = formatApplyPatchSummary(patchText, cwd);
|
|
71
71
|
const expanded = renderApplyPatchCall(patchText, cwd);
|
|
@@ -75,15 +75,15 @@ function setApplyPatchRenderState(
|
|
|
75
75
|
collapsed,
|
|
76
76
|
expanded,
|
|
77
77
|
status,
|
|
78
|
-
|
|
78
|
+
failedTargets,
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function markApplyPatchPartialFailure(toolCallId: string,
|
|
83
|
-
markApplyPatchFailure(toolCallId, "partial_failure",
|
|
82
|
+
function markApplyPatchPartialFailure(toolCallId: string, failedTargets?: string[]): void {
|
|
83
|
+
markApplyPatchFailure(toolCallId, "partial_failure", failedTargets);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function markApplyPatchFailure(toolCallId: string, status: "partial_failure" | "failed",
|
|
86
|
+
function markApplyPatchFailure(toolCallId: string, status: "partial_failure" | "failed", failedTargets?: string[]): void {
|
|
87
87
|
const existing = applyPatchRenderStates.get(toolCallId);
|
|
88
88
|
if (!existing) {
|
|
89
89
|
return;
|
|
@@ -91,14 +91,14 @@ function markApplyPatchFailure(toolCallId: string, status: "partial_failure" | "
|
|
|
91
91
|
applyPatchRenderStates.set(toolCallId, {
|
|
92
92
|
...existing,
|
|
93
93
|
status,
|
|
94
|
-
|
|
94
|
+
failedTargets,
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function renderPartialFailureCall(
|
|
99
99
|
text: string,
|
|
100
100
|
theme: { fg(role: string, text: string): string },
|
|
101
|
-
|
|
101
|
+
failedTargets?: string[],
|
|
102
102
|
): string {
|
|
103
103
|
const lines = text.split("\n");
|
|
104
104
|
if (lines.length === 0) {
|
|
@@ -106,12 +106,15 @@ function renderPartialFailureCall(
|
|
|
106
106
|
}
|
|
107
107
|
lines[0] = lines[0].replace(/^• (Added|Edited|Deleted)\b/, "• Edit partially failed");
|
|
108
108
|
const failedLineIndexes = new Set<number>();
|
|
109
|
-
if (
|
|
109
|
+
if (failedTargets) {
|
|
110
110
|
for (let i = 0; i < lines.length; i += 1) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
for (const failedTarget of failedTargets) {
|
|
112
|
+
const failedLine = markFailedTargetLine(lines[i], failedTarget);
|
|
113
|
+
if (failedLine) {
|
|
114
|
+
lines[i] = failedLine;
|
|
115
|
+
failedLineIndexes.add(i);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
}
|
|
@@ -131,7 +134,7 @@ function renderPartialFailureCall(
|
|
|
131
134
|
function renderFailedCall(
|
|
132
135
|
text: string,
|
|
133
136
|
theme: { fg(role: string, text: string): string },
|
|
134
|
-
|
|
137
|
+
failedTargets?: string[],
|
|
135
138
|
): string {
|
|
136
139
|
const lines = text.split("\n");
|
|
137
140
|
if (lines.length === 0) {
|
|
@@ -139,12 +142,15 @@ function renderFailedCall(
|
|
|
139
142
|
}
|
|
140
143
|
lines[0] = lines[0].replace(/^• (Added|Edited|Deleted)\b/, "• Edit failed");
|
|
141
144
|
const failedLineIndexes = new Set<number>();
|
|
142
|
-
if (
|
|
145
|
+
if (failedTargets) {
|
|
143
146
|
for (let i = 0; i < lines.length; i += 1) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
for (const failedTarget of failedTargets) {
|
|
148
|
+
const failedLine = markFailedTargetLine(lines[i], failedTarget);
|
|
149
|
+
if (failedLine) {
|
|
150
|
+
lines[i] = failedLine;
|
|
151
|
+
failedLineIndexes.add(i);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
156
|
}
|
|
@@ -188,13 +194,9 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
function getFailedPaths(error: ExecutePatchError): string[] {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return uniqueStrings([
|
|
195
|
-
error.failedAction.path,
|
|
196
|
-
error.failedAction.type === "update" ? error.failedAction.movePath : undefined,
|
|
197
|
-
]);
|
|
197
|
+
return uniqueStrings(
|
|
198
|
+
error.failures.flatMap(({ action }) => [action.path, action.type === "update" ? action.movePath : undefined]),
|
|
199
|
+
);
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
function getAppliedPaths(result: ExecutePatchResult, failedFiles: string[]): string[] {
|
|
@@ -214,11 +216,10 @@ function buildPartialFailureMessage(message: string, failedFiles: string[], appl
|
|
|
214
216
|
return lines.join("\n");
|
|
215
217
|
}
|
|
216
218
|
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return formatPatchTarget(error.failedAction.path, error.failedAction.type === "update" ? error.failedAction.movePath : undefined, cwd);
|
|
219
|
+
function describeFailedActions(error: ExecutePatchError, cwd: string): string[] {
|
|
220
|
+
return uniqueStrings(
|
|
221
|
+
error.failures.map(({ action }) => formatPatchTarget(action.path, action.type === "update" ? action.movePath : undefined, cwd)),
|
|
222
|
+
);
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
export type { ExecutePatchResult } from "../patch/types.ts";
|
|
@@ -253,9 +254,9 @@ const renderApplyPatchCallWithOptionalContext: any = (
|
|
|
253
254
|
}
|
|
254
255
|
const text =
|
|
255
256
|
cached?.status === "partial_failure"
|
|
256
|
-
? renderPartialFailureCall(baseText, theme, cached.
|
|
257
|
+
? renderPartialFailureCall(baseText, theme, cached.failedTargets)
|
|
257
258
|
: cached?.status === "failed"
|
|
258
|
-
? renderFailedCall(baseText, theme, cached.
|
|
259
|
+
? renderFailedCall(baseText, theme, cached.failedTargets)
|
|
259
260
|
: baseText;
|
|
260
261
|
return new Text(text, 0, 0);
|
|
261
262
|
};
|
|
@@ -284,23 +285,24 @@ export function registerApplyPatchTool(pi: ExtensionAPI): void {
|
|
|
284
285
|
} catch (error) {
|
|
285
286
|
if (error instanceof ExecutePatchError) {
|
|
286
287
|
const partial = error.hasPartialSuccess();
|
|
287
|
-
const
|
|
288
|
+
const failedTargets = describeFailedActions(error, ctx.cwd);
|
|
289
|
+
const failedTargetSummary = failedTargets.join(", ");
|
|
288
290
|
const prefix = partial
|
|
289
291
|
? `apply_patch partially failed after ${summarizePatchCounts(error.result)}`
|
|
290
292
|
: "apply_patch failed";
|
|
291
|
-
const message =
|
|
293
|
+
const message = failedTargetSummary ? `${prefix} while patching ${failedTargetSummary}: ${error.message}` : `${prefix}: ${error.message}`;
|
|
292
294
|
if (partial) {
|
|
293
295
|
const failedFiles = getFailedPaths(error);
|
|
294
296
|
const appliedFiles = getAppliedPaths(error.result, failedFiles);
|
|
295
297
|
const recoveryMessage = buildPartialFailureMessage(message, failedFiles, appliedFiles);
|
|
296
|
-
markApplyPatchPartialFailure(toolCallId,
|
|
298
|
+
markApplyPatchPartialFailure(toolCallId, failedTargets);
|
|
297
299
|
return {
|
|
298
300
|
content: [{ type: "text", text: recoveryMessage }],
|
|
299
301
|
details: {
|
|
300
302
|
status: "partial_failure",
|
|
301
303
|
result: error.result,
|
|
302
304
|
error: recoveryMessage,
|
|
303
|
-
|
|
305
|
+
failedTargets,
|
|
304
306
|
appliedFiles,
|
|
305
307
|
failedFiles,
|
|
306
308
|
recoveryInstructions: {
|
|
@@ -310,7 +312,7 @@ export function registerApplyPatchTool(pi: ExtensionAPI): void {
|
|
|
310
312
|
} satisfies ApplyPatchPartialFailureDetails,
|
|
311
313
|
};
|
|
312
314
|
}
|
|
313
|
-
markApplyPatchFailure(toolCallId, "failed",
|
|
315
|
+
markApplyPatchFailure(toolCallId, "failed", failedTargets);
|
|
314
316
|
throw new Error(message);
|
|
315
317
|
}
|
|
316
318
|
markApplyPatchFailure(toolCallId, "failed");
|