mono-pilot 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -2
- package/dist/src/agents-paths.js +36 -0
- package/dist/src/brief/blocks.js +83 -0
- package/dist/src/brief/defaults.js +60 -0
- package/dist/src/brief/frontmatter.js +53 -0
- package/dist/src/brief/paths.js +10 -0
- package/dist/src/brief/reflection.js +27 -0
- package/dist/src/cli.js +62 -5
- package/dist/src/cluster/bus.js +102 -0
- package/dist/src/cluster/follower.js +137 -0
- package/dist/src/cluster/init.js +182 -0
- package/dist/src/cluster/leader.js +97 -0
- package/dist/src/cluster/log.js +49 -0
- package/dist/src/cluster/protocol.js +34 -0
- package/dist/src/cluster/services/bus.js +243 -0
- package/dist/src/cluster/services/embedding.js +12 -0
- package/dist/src/cluster/socket.js +86 -0
- package/dist/src/cluster/test-bus.js +175 -0
- package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
- package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
- package/dist/src/cluster_v2/connection.js +159 -0
- package/dist/src/cluster_v2/connection.test.js +55 -0
- package/dist/src/cluster_v2/events.js +102 -0
- package/dist/src/cluster_v2/index.js +2 -0
- package/dist/src/cluster_v2/observability.js +99 -0
- package/dist/src/cluster_v2/observability.test.js +46 -0
- package/dist/src/cluster_v2/rpc.js +389 -0
- package/dist/src/cluster_v2/rpc.test.js +110 -0
- package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
- package/dist/src/cluster_v2/runtime.js +531 -0
- package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
- package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
- package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
- package/dist/src/cluster_v2/services/bus.js +450 -0
- package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
- package/dist/src/cluster_v2/services/discord/collector.js +569 -0
- package/dist/src/cluster_v2/services/discord/index.js +1 -0
- package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
- package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
- package/dist/src/cluster_v2/services/embedding.js +66 -0
- package/dist/src/cluster_v2/services/registry-cache.js +107 -0
- package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
- package/dist/src/cluster_v2/services/registry.js +36 -0
- package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
- package/dist/src/cluster_v2/services/twitter/index.js +1 -0
- package/dist/src/config/digest.js +78 -0
- package/dist/src/config/discord.js +143 -0
- package/dist/src/config/image-gen.js +48 -0
- package/dist/src/config/mono-pilot.js +31 -0
- package/dist/src/config/twitter.js +100 -0
- package/dist/src/extensions/cluster.js +311 -0
- package/dist/src/extensions/commands/build-memory.js +76 -0
- package/dist/src/extensions/commands/digest/backfill.js +779 -0
- package/dist/src/extensions/commands/digest/index.js +1133 -0
- package/dist/src/extensions/commands/image-model.js +214 -0
- package/dist/src/extensions/game/bus-injection.js +47 -0
- package/dist/src/extensions/game/identity.js +83 -0
- package/dist/src/extensions/game/mailbox.js +61 -0
- package/dist/src/extensions/game/system-prompt.js +134 -0
- package/dist/src/extensions/game/tools.js +28 -0
- package/dist/src/extensions/lifecycle.js +337 -0
- package/dist/src/extensions/mode-runtime.js +26 -2
- package/dist/src/extensions/mono-game.js +66 -0
- package/dist/src/extensions/mono-pilot.js +100 -18
- package/dist/src/extensions/nvim.js +47 -0
- package/dist/src/extensions/session-hints.js +1 -2
- package/dist/src/extensions/sftp.js +897 -0
- package/dist/src/extensions/status.js +676 -0
- package/dist/src/extensions/system-events.js +478 -0
- package/dist/src/extensions/system-prompt.js +24 -14
- package/dist/src/extensions/user-message.js +70 -1
- package/dist/src/lsp/client.js +235 -0
- package/dist/src/lsp/index.js +165 -0
- package/dist/src/lsp/runtime.js +67 -0
- package/dist/src/lsp/server.js +242 -0
- package/dist/src/memory/build-memory.js +103 -0
- package/dist/src/memory/config/defaults.js +55 -0
- package/dist/src/memory/config/loader.js +29 -0
- package/dist/src/memory/config/paths.js +9 -0
- package/dist/src/memory/config/resolve.js +90 -0
- package/dist/src/memory/config/types.js +1 -0
- package/dist/src/memory/embeddings/batch-runner.js +39 -0
- package/dist/src/memory/embeddings/cache.js +47 -0
- package/dist/src/memory/embeddings/chunk-limits.js +26 -0
- package/dist/src/memory/embeddings/input-limits.js +48 -0
- package/dist/src/memory/embeddings/local.js +108 -0
- package/dist/src/memory/embeddings/types.js +1 -0
- package/dist/src/memory/index-manager.js +552 -0
- package/dist/src/memory/indexing/embeddings.js +67 -0
- package/dist/src/memory/indexing/files.js +180 -0
- package/dist/src/memory/indexing/index-file.js +105 -0
- package/dist/src/memory/log.js +38 -0
- package/dist/src/memory/paths.js +15 -0
- package/dist/src/memory/runtime/index.js +299 -0
- package/dist/src/memory/runtime/thread.js +116 -0
- package/dist/src/memory/search/fts.js +57 -0
- package/dist/src/memory/search/hybrid.js +50 -0
- package/dist/src/memory/search/text.js +30 -0
- package/dist/src/memory/search/vector.js +43 -0
- package/dist/src/memory/session/content-hash.js +7 -0
- package/dist/src/memory/session/entry.js +33 -0
- package/dist/src/memory/session/flush-policy.js +34 -0
- package/dist/src/memory/session/hook.js +191 -0
- package/dist/src/memory/session/paths.js +15 -0
- package/dist/src/memory/session/session-reader.js +88 -0
- package/dist/src/memory/session/transcript/content-hash.js +7 -0
- package/dist/src/memory/session/transcript/entry.js +28 -0
- package/dist/src/memory/session/transcript/flush.js +56 -0
- package/dist/src/memory/session/transcript/paths.js +28 -0
- package/dist/src/memory/session/transcript/reader.js +112 -0
- package/dist/src/memory/session/transcript/state.js +31 -0
- package/dist/src/memory/store/schema.js +89 -0
- package/dist/src/memory/store/sqlite.js +89 -0
- package/dist/src/memory/types.js +1 -0
- package/dist/src/memory/warm.js +25 -0
- package/dist/{tools → src/tools}/README.md +28 -2
- package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
- package/dist/{tools → src/tools}/apply-patch.js +174 -104
- package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
- package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
- package/dist/src/tools/ast-grep.js +357 -0
- package/dist/src/tools/brief-write.js +122 -0
- package/dist/src/tools/bus-send.js +100 -0
- package/dist/{tools → src/tools}/call-mcp-tool.js +20 -24
- package/dist/src/tools/codex-apply-patch-description.md +52 -0
- package/dist/src/tools/codex-apply-patch.js +540 -0
- package/dist/{tools → src/tools}/delete.js +24 -0
- package/dist/src/tools/exit-plan-mode.js +83 -0
- package/dist/{tools → src/tools}/fetch-mcp-resource.js +31 -3
- package/dist/src/tools/generate-image.js +567 -0
- package/dist/{tools → src/tools}/glob.js +55 -1
- package/dist/{tools → src/tools}/list-mcp-resources.js +32 -3
- package/dist/{tools → src/tools}/list-mcp-tools.js +38 -3
- package/dist/src/tools/ls.js +48 -0
- package/dist/src/tools/lsp-diagnostics.js +67 -0
- package/dist/src/tools/lsp-symbols.js +54 -0
- package/dist/src/tools/mailbox.js +85 -0
- package/dist/src/tools/memory-get.js +90 -0
- package/dist/src/tools/memory-search.js +180 -0
- package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
- package/dist/{tools → src/tools}/read-file.js +8 -19
- package/dist/{tools → src/tools}/rg.js +10 -20
- package/dist/{tools → src/tools}/shell.js +19 -42
- package/dist/{tools → src/tools}/subagent.js +255 -6
- package/dist/{tools → src/tools}/switch-mode.js +37 -6
- package/dist/{tools → src/tools}/web-fetch.js +105 -7
- package/dist/{tools → src/tools}/web-search.js +29 -1
- package/package.json +21 -9
- package/dist/src/utils/mcp-client.js +0 -282
- /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
- /package/dist/{tools → src/tools}/rg.test.js +0 -0
- /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
- /package/dist/{tools → src/tools}/semantic-search.js +0 -0
- /package/dist/{tools → src/tools}/shell-description.md +0 -0
- /package/dist/{tools → src/tools}/subagent-description.md +0 -0
|
@@ -13,7 +13,7 @@ const MOVE_TO = "*** Move to: ";
|
|
|
13
13
|
const END_OF_FILE = "*** End of File";
|
|
14
14
|
const MAX_RENDER_SUMMARY_PATH_CHARS = 96;
|
|
15
15
|
const MAX_RENDER_FIRST_CHANGED_LINES = 12;
|
|
16
|
-
const DESCRIPTION = readFileSync(fileURLToPath(new URL("./
|
|
16
|
+
const DESCRIPTION = readFileSync(fileURLToPath(new URL("./apply-patch-description.md", import.meta.url)), "utf-8").trim();
|
|
17
17
|
const applyPatchSchema = Type.Object({
|
|
18
18
|
patch: Type.String({
|
|
19
19
|
description: "Single-file patch document in ApplyPatch format. Must start with *** Begin Patch and end with *** End Patch.",
|
|
@@ -98,6 +98,13 @@ function buildRenderDetailLines(details) {
|
|
|
98
98
|
lines.push("patch:");
|
|
99
99
|
lines.push(details.patchText.length > 0 ? details.patchText : "(empty patch)");
|
|
100
100
|
}
|
|
101
|
+
if (details.sftp) {
|
|
102
|
+
const targets = details.sftp.targets.length > 0 ? details.sftp.targets.join(", ") : "(none)";
|
|
103
|
+
lines.push(`sftp uploaded: ${details.sftp.uploaded} to ${targets}`);
|
|
104
|
+
if (details.sftp.errors && details.sftp.errors.length > 0) {
|
|
105
|
+
lines.push(`sftp errors: ${details.sftp.errors.join("; ")}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
101
108
|
return lines;
|
|
102
109
|
}
|
|
103
110
|
function normalizeAddFileLines(lines) {
|
|
@@ -161,15 +168,18 @@ function parseUpdateFile(lines, startIndex) {
|
|
|
161
168
|
const hunks = [];
|
|
162
169
|
let currentHeaders;
|
|
163
170
|
let currentLines = [];
|
|
171
|
+
let currentLineHint;
|
|
164
172
|
const flushCurrentHunk = () => {
|
|
165
173
|
if (!currentHeaders)
|
|
166
174
|
return;
|
|
167
175
|
hunks.push({
|
|
168
176
|
headers: currentHeaders,
|
|
169
177
|
lines: currentLines,
|
|
178
|
+
lineHint: currentLineHint,
|
|
170
179
|
});
|
|
171
180
|
currentHeaders = undefined;
|
|
172
181
|
currentLines = [];
|
|
182
|
+
currentLineHint = undefined;
|
|
173
183
|
};
|
|
174
184
|
while (i < lines.length && lines[i] !== END_PATCH) {
|
|
175
185
|
const line = lines[i];
|
|
@@ -178,16 +188,19 @@ function parseUpdateFile(lines, startIndex) {
|
|
|
178
188
|
break;
|
|
179
189
|
}
|
|
180
190
|
if (line.startsWith("@@")) {
|
|
191
|
+
const { headerText, lineHint } = parseHunkHeaderLine(line);
|
|
181
192
|
if (!currentHeaders) {
|
|
182
|
-
currentHeaders = [
|
|
183
|
-
}
|
|
184
|
-
else if (currentLines.length === 0) {
|
|
185
|
-
// Support multiple consecutive @@ headers to narrow context.
|
|
186
|
-
currentHeaders.push(line);
|
|
193
|
+
currentHeaders = [];
|
|
187
194
|
}
|
|
188
|
-
else {
|
|
195
|
+
else if (currentLines.length > 0) {
|
|
189
196
|
flushCurrentHunk();
|
|
190
|
-
currentHeaders = [
|
|
197
|
+
currentHeaders = [];
|
|
198
|
+
}
|
|
199
|
+
if (headerText) {
|
|
200
|
+
currentHeaders.push(headerText);
|
|
201
|
+
}
|
|
202
|
+
if (lineHint && !currentLineHint) {
|
|
203
|
+
currentLineHint = lineHint;
|
|
191
204
|
}
|
|
192
205
|
i++;
|
|
193
206
|
continue;
|
|
@@ -274,6 +287,52 @@ function parseFirstChangedLine(details) {
|
|
|
274
287
|
const value = record.firstChangedLine;
|
|
275
288
|
return typeof value === "number" && Number.isInteger(value) ? value : undefined;
|
|
276
289
|
}
|
|
290
|
+
function toZeroBasedLine(line) {
|
|
291
|
+
return Math.max(0, line - 1);
|
|
292
|
+
}
|
|
293
|
+
function parseDiffHeader(header) {
|
|
294
|
+
const match = header.match(/^-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(?:\s+(.*))?$/);
|
|
295
|
+
if (!match)
|
|
296
|
+
return null;
|
|
297
|
+
const oldStart = Number(match[1]);
|
|
298
|
+
const oldCount = match[2] ? Number(match[2]) : undefined;
|
|
299
|
+
const contextText = match[5]?.trim();
|
|
300
|
+
if (!Number.isInteger(oldStart) || oldStart <= 0) {
|
|
301
|
+
return {
|
|
302
|
+
headerText: contextText || undefined,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const startLine = toZeroBasedLine(oldStart);
|
|
306
|
+
const endLine = oldCount && oldCount > 0 ? startLine + oldCount - 1 : undefined;
|
|
307
|
+
return {
|
|
308
|
+
lineHint: { startLine, endLine },
|
|
309
|
+
headerText: contextText || undefined,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function parseLineDirective(header) {
|
|
313
|
+
const match = header.match(/^line\s*(?::|=)?\s*(\d+)(?:\s*(?:-|\.\.)\s*(\d+))?$/i);
|
|
314
|
+
if (!match)
|
|
315
|
+
return null;
|
|
316
|
+
const start = Number(match[1]);
|
|
317
|
+
if (!Number.isInteger(start) || start <= 0)
|
|
318
|
+
return null;
|
|
319
|
+
const end = match[2] ? Number(match[2]) : undefined;
|
|
320
|
+
const startLine = toZeroBasedLine(start);
|
|
321
|
+
const endLine = end && end >= start ? toZeroBasedLine(end) : undefined;
|
|
322
|
+
return { lineHint: { startLine, endLine } };
|
|
323
|
+
}
|
|
324
|
+
function parseHunkHeaderLine(line) {
|
|
325
|
+
const header = line.replace(/^@@/, "").trim();
|
|
326
|
+
if (!header)
|
|
327
|
+
return {};
|
|
328
|
+
const diffParsed = parseDiffHeader(header);
|
|
329
|
+
if (diffParsed)
|
|
330
|
+
return diffParsed;
|
|
331
|
+
const lineParsed = parseLineDirective(header);
|
|
332
|
+
if (lineParsed)
|
|
333
|
+
return lineParsed;
|
|
334
|
+
return { headerText: header };
|
|
335
|
+
}
|
|
277
336
|
function normalizeHeaderText(header) {
|
|
278
337
|
const trimmed = header.replace(/^@@/, "").trim();
|
|
279
338
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
@@ -360,6 +419,106 @@ export function alignReplacement(fileContent, oldText, newText, options) {
|
|
|
360
419
|
}
|
|
361
420
|
return null;
|
|
362
421
|
}
|
|
422
|
+
export async function applyPatchToFilesystem(options) {
|
|
423
|
+
const toolCallId = options.toolCallId ?? "apply-patch";
|
|
424
|
+
const parsed = parsePatchDocument(options.patchText);
|
|
425
|
+
const patchLineCount = getNormalizedPatchLines(options.patchText).length;
|
|
426
|
+
const normalizedPatchText = normalizeLineEndings(options.patchText);
|
|
427
|
+
const writeTool = createWriteTool(options.cwd);
|
|
428
|
+
const editTool = createEditTool(options.cwd);
|
|
429
|
+
if (parsed.operation.kind === "add") {
|
|
430
|
+
const content = parsed.operation.lines.join("\n");
|
|
431
|
+
await writeTool.execute(`${toolCallId}:add`, {
|
|
432
|
+
path: parsed.operation.path,
|
|
433
|
+
content,
|
|
434
|
+
}, options.signal);
|
|
435
|
+
const details = {
|
|
436
|
+
operation: "add",
|
|
437
|
+
path: parsed.operation.path,
|
|
438
|
+
bytesWritten: Buffer.byteLength(content, "utf-8"),
|
|
439
|
+
patchLineCount,
|
|
440
|
+
patchText: normalizedPatchText,
|
|
441
|
+
};
|
|
442
|
+
return {
|
|
443
|
+
summary: `Applied patch: added ${parsed.operation.path}`,
|
|
444
|
+
details,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const firstChangedLines = [];
|
|
448
|
+
let appliedHunks = 0;
|
|
449
|
+
let noopHunks = 0;
|
|
450
|
+
for (let i = 0; i < parsed.operation.hunks.length; i++) {
|
|
451
|
+
const hunk = parsed.operation.hunks[i];
|
|
452
|
+
if (!hunkHasChanges(hunk)) {
|
|
453
|
+
noopHunks++;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
let { oldText, newText } = buildReplacementTexts(hunk);
|
|
457
|
+
if (oldText === newText) {
|
|
458
|
+
noopHunks++;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const absolutePath = isAbsolute(parsed.operation.path)
|
|
462
|
+
? parsed.operation.path
|
|
463
|
+
: resolve(options.cwd, parsed.operation.path);
|
|
464
|
+
if (existsSync(absolutePath)) {
|
|
465
|
+
try {
|
|
466
|
+
const fileContent = readFileSync(absolutePath, "utf-8");
|
|
467
|
+
const headerStart = findHeaderSearchStart(fileContent, hunk.headers);
|
|
468
|
+
const startLine = hunk.lineHint?.startLine ?? headerStart;
|
|
469
|
+
const endLine = hunk.lineHint?.endLine;
|
|
470
|
+
const alignOptions = startLine !== undefined || endLine !== undefined ? { startLine, endLine } : undefined;
|
|
471
|
+
const aligned = alignReplacement(fileContent, oldText, newText, alignOptions);
|
|
472
|
+
if (aligned) {
|
|
473
|
+
oldText = aligned.oldText;
|
|
474
|
+
newText = aligned.newText;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
// Ignore read errors, let editTool handle it
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const result = await editTool.execute(`${toolCallId}:hunk:${i + 1}`, {
|
|
482
|
+
path: parsed.operation.path,
|
|
483
|
+
oldText,
|
|
484
|
+
newText,
|
|
485
|
+
}, options.signal);
|
|
486
|
+
appliedHunks++;
|
|
487
|
+
const firstChangedLine = parseFirstChangedLine(result.details);
|
|
488
|
+
if (firstChangedLine !== undefined) {
|
|
489
|
+
firstChangedLines.push(firstChangedLine);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
let movedTo;
|
|
493
|
+
if (parsed.operation.moveTo && parsed.operation.moveTo !== parsed.operation.path) {
|
|
494
|
+
await mkdir(dirname(parsed.operation.moveTo), { recursive: true });
|
|
495
|
+
await rename(parsed.operation.path, parsed.operation.moveTo);
|
|
496
|
+
movedTo = parsed.operation.moveTo;
|
|
497
|
+
}
|
|
498
|
+
const details = {
|
|
499
|
+
operation: "update",
|
|
500
|
+
path: parsed.operation.path,
|
|
501
|
+
moveTo: movedTo,
|
|
502
|
+
hunkCount: parsed.operation.hunks.length,
|
|
503
|
+
appliedHunks,
|
|
504
|
+
noopHunks,
|
|
505
|
+
firstChangedLines,
|
|
506
|
+
patchLineCount,
|
|
507
|
+
patchText: normalizedPatchText,
|
|
508
|
+
};
|
|
509
|
+
const suffix = [];
|
|
510
|
+
if (noopHunks > 0) {
|
|
511
|
+
suffix.push(`skipped ${noopHunks} no-op hunk(s)`);
|
|
512
|
+
}
|
|
513
|
+
if (movedTo) {
|
|
514
|
+
suffix.push(`moved to ${movedTo}`);
|
|
515
|
+
}
|
|
516
|
+
const suffixText = suffix.length > 0 ? ` (${suffix.join(", ")})` : "";
|
|
517
|
+
return {
|
|
518
|
+
summary: `Applied patch: updated ${parsed.operation.path} with ${appliedHunks} hunk(s)${suffixText}.`,
|
|
519
|
+
details,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
363
522
|
export default function (pi) {
|
|
364
523
|
// System prompt injection is handled centrally by system-prompt extension.
|
|
365
524
|
pi.registerTool({
|
|
@@ -391,103 +550,14 @@ export default function (pi) {
|
|
|
391
550
|
return new Text(text, 0, 0);
|
|
392
551
|
},
|
|
393
552
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const content = parsed.operation.lines.join("\n");
|
|
401
|
-
await writeTool.execute(`${toolCallId}:add`, {
|
|
402
|
-
path: parsed.operation.path,
|
|
403
|
-
content,
|
|
404
|
-
}, signal);
|
|
405
|
-
const details = {
|
|
406
|
-
operation: "add",
|
|
407
|
-
path: parsed.operation.path,
|
|
408
|
-
bytesWritten: Buffer.byteLength(content, "utf-8"),
|
|
409
|
-
patchLineCount,
|
|
410
|
-
patchText,
|
|
411
|
-
};
|
|
412
|
-
return {
|
|
413
|
-
content: [{ type: "text", text: `Applied patch: added ${parsed.operation.path}` }],
|
|
414
|
-
details,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
const firstChangedLines = [];
|
|
418
|
-
let appliedHunks = 0;
|
|
419
|
-
let noopHunks = 0;
|
|
420
|
-
for (let i = 0; i < parsed.operation.hunks.length; i++) {
|
|
421
|
-
const hunk = parsed.operation.hunks[i];
|
|
422
|
-
if (!hunkHasChanges(hunk)) {
|
|
423
|
-
noopHunks++;
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
let { oldText, newText } = buildReplacementTexts(hunk);
|
|
427
|
-
if (oldText === newText) {
|
|
428
|
-
noopHunks++;
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
const absolutePath = isAbsolute(parsed.operation.path)
|
|
432
|
-
? parsed.operation.path
|
|
433
|
-
: resolve(ctx.cwd, parsed.operation.path);
|
|
434
|
-
if (existsSync(absolutePath)) {
|
|
435
|
-
try {
|
|
436
|
-
const fileContent = readFileSync(absolutePath, "utf-8");
|
|
437
|
-
const headerStart = findHeaderSearchStart(fileContent, hunk.headers);
|
|
438
|
-
const aligned = alignReplacement(fileContent, oldText, newText, headerStart !== undefined ? { startLine: headerStart } : undefined);
|
|
439
|
-
if (aligned) {
|
|
440
|
-
oldText = aligned.oldText;
|
|
441
|
-
newText = aligned.newText;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
// Ignore read errors, let editTool handle it
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
const result = await editTool.execute(`${toolCallId}:hunk:${i + 1}`, {
|
|
449
|
-
path: parsed.operation.path,
|
|
450
|
-
oldText,
|
|
451
|
-
newText,
|
|
452
|
-
}, signal);
|
|
453
|
-
appliedHunks++;
|
|
454
|
-
const firstChangedLine = parseFirstChangedLine(result.details);
|
|
455
|
-
if (firstChangedLine !== undefined) {
|
|
456
|
-
firstChangedLines.push(firstChangedLine);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
let movedTo;
|
|
460
|
-
if (parsed.operation.moveTo && parsed.operation.moveTo !== parsed.operation.path) {
|
|
461
|
-
await mkdir(dirname(parsed.operation.moveTo), { recursive: true });
|
|
462
|
-
await rename(parsed.operation.path, parsed.operation.moveTo);
|
|
463
|
-
movedTo = parsed.operation.moveTo;
|
|
464
|
-
}
|
|
465
|
-
const details = {
|
|
466
|
-
operation: "update",
|
|
467
|
-
path: parsed.operation.path,
|
|
468
|
-
moveTo: movedTo,
|
|
469
|
-
hunkCount: parsed.operation.hunks.length,
|
|
470
|
-
appliedHunks,
|
|
471
|
-
noopHunks,
|
|
472
|
-
firstChangedLines,
|
|
473
|
-
patchLineCount,
|
|
474
|
-
patchText,
|
|
475
|
-
};
|
|
476
|
-
const suffix = [];
|
|
477
|
-
if (noopHunks > 0) {
|
|
478
|
-
suffix.push(`skipped ${noopHunks} no-op hunk(s)`);
|
|
479
|
-
}
|
|
480
|
-
if (movedTo) {
|
|
481
|
-
suffix.push(`moved to ${movedTo}`);
|
|
482
|
-
}
|
|
483
|
-
const suffixText = suffix.length > 0 ? ` (${suffix.join(", ")})` : "";
|
|
553
|
+
const { summary, details } = await applyPatchToFilesystem({
|
|
554
|
+
patchText: params.patch,
|
|
555
|
+
cwd: ctx.cwd,
|
|
556
|
+
toolCallId,
|
|
557
|
+
signal,
|
|
558
|
+
});
|
|
484
559
|
return {
|
|
485
|
-
content: [
|
|
486
|
-
{
|
|
487
|
-
type: "text",
|
|
488
|
-
text: `Applied patch: updated ${parsed.operation.path} with ${appliedHunks} hunk(s)${suffixText}.`,
|
|
489
|
-
},
|
|
490
|
-
],
|
|
560
|
+
content: [{ type: "text", text: summary }],
|
|
491
561
|
details,
|
|
492
562
|
};
|
|
493
563
|
},
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { alignReplacement, applyPatchToFilesystem } from "./apply-patch.js";
|
|
7
|
+
const fixtureRoot = fileURLToPath(new URL("./apply-patch-fixtures", import.meta.url));
|
|
8
|
+
const fixtureOriginal = resolve(fixtureRoot, "original.md");
|
|
9
|
+
const fixturePatches = resolve(fixtureRoot, "patches");
|
|
10
|
+
const fixtureExpected = resolve(fixtureRoot, "expected");
|
|
11
|
+
const PATCH_TARGET = "{{FILE}}";
|
|
3
12
|
describe("ApplyPatch Robustness - alignReplacement", () => {
|
|
4
13
|
it("should align 2-space indentation to tabs", () => {
|
|
5
14
|
const fileContent = "function test() {\n\t\tfoo();\n\t\tbar();\n}";
|
|
@@ -87,3 +96,45 @@ describe("ApplyPatch Robustness - alignReplacement", () => {
|
|
|
87
96
|
expect(result?.newText).toBe(" bar();");
|
|
88
97
|
});
|
|
89
98
|
});
|
|
99
|
+
describe("ApplyPatch fixtures", () => {
|
|
100
|
+
const cases = [
|
|
101
|
+
{
|
|
102
|
+
name: "context-only",
|
|
103
|
+
patch: "00-context-only.patch",
|
|
104
|
+
expected: "00-context-only.md",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "line-hint",
|
|
108
|
+
patch: "01-line-hint.patch",
|
|
109
|
+
expected: "01-line-hint.md",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "line-range",
|
|
113
|
+
patch: "02-line-range.patch",
|
|
114
|
+
expected: "02-line-range.md",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "diff-header",
|
|
118
|
+
patch: "03-diff-header.patch",
|
|
119
|
+
expected: "03-diff-header.md",
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
for (const entry of cases) {
|
|
123
|
+
it(`applies fixture patch: ${entry.name}`, async () => {
|
|
124
|
+
const tempDir = await mkdtemp(`${tmpdir()}/apply-patch-fixture-`);
|
|
125
|
+
const targetPath = resolve(tempDir, "target.md");
|
|
126
|
+
const original = await readFile(fixtureOriginal, "utf-8");
|
|
127
|
+
await writeFile(targetPath, original, "utf-8");
|
|
128
|
+
const patchTemplate = await readFile(resolve(fixturePatches, entry.patch), "utf-8");
|
|
129
|
+
const patchText = patchTemplate.replaceAll(PATCH_TARGET, targetPath);
|
|
130
|
+
await applyPatchToFilesystem({
|
|
131
|
+
patchText,
|
|
132
|
+
cwd: tempDir,
|
|
133
|
+
toolCallId: "fixture",
|
|
134
|
+
});
|
|
135
|
+
const expected = await readFile(resolve(fixtureExpected, entry.expected), "utf-8");
|
|
136
|
+
const actual = await readFile(targetPath, "utf-8");
|
|
137
|
+
expect(actual).toBe(expected);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
@@ -212,11 +212,11 @@ function buildSuccessText(answers, questions) {
|
|
|
212
212
|
}
|
|
213
213
|
return lines.join("\n");
|
|
214
214
|
}
|
|
215
|
-
export default function
|
|
215
|
+
export default function askUserQuestionExtension(pi) {
|
|
216
216
|
// System prompt injection is handled centrally by system-prompt extension.
|
|
217
217
|
pi.registerTool({
|
|
218
|
-
name: "
|
|
219
|
-
label: "
|
|
218
|
+
name: "AskUserQuestion",
|
|
219
|
+
label: "AskUserQuestion",
|
|
220
220
|
description: DESCRIPTION,
|
|
221
221
|
parameters: askQuestionSchema,
|
|
222
222
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|