@iinm/plain-agent 1.14.4 → 1.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Plain Agent
2
2
 
3
3
  [![CodeQL](https://github.com/iinm/plain-agent/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/iinm/plain-agent/actions/workflows/github-code-scanning/codeql)
4
- [![Socket Badge](https://badge.socket.dev/npm/package/@iinm/plain-agent/1.14.4)](https://socket.dev/npm/package/@iinm/plain-agent)
4
+ [![Socket Badge](https://badge.socket.dev/npm/package/@iinm/plain-agent/1.14.6)](https://socket.dev/npm/package/@iinm/plain-agent)
5
5
  [![install size](https://packagephobia.com/badge?p=@iinm/plain-agent)](https://packagephobia.com/result?p=@iinm/plain-agent)
6
6
 
7
7
  A lightweight terminal-based coding agent focused on safety and low token cost
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.14.4",
3
+ "version": "1.14.6",
4
4
  "description": "A lightweight terminal-based coding agent focused on safety and low token cost",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,9 +44,9 @@
44
44
  },
45
45
  "dependencies": {},
46
46
  "devDependencies": {
47
- "@biomejs/biome": "^2.4.12",
47
+ "@biomejs/biome": "^2.5.2",
48
48
  "@types/node": "^22.19.17",
49
49
  "c8": "^11.0.0",
50
- "typescript": "^6.0.2"
50
+ "typescript": "^7.0.2"
51
51
  }
52
52
  }
@@ -11,8 +11,7 @@
11
11
 
12
12
  import fs from "node:fs/promises";
13
13
  import { styleText } from "node:util";
14
- import { parseBlocks } from "../tools/patchFile.mjs";
15
- import { diffLines } from "../utils/diffLines.mjs";
14
+ import { parseBlocks, renderPatchBlock } from "../tools/patchFile.mjs";
16
15
  import { noThrow } from "../utils/noThrow.mjs";
17
16
 
18
17
  /** Length above which a single-line arg forces block-form rendering. */
@@ -863,61 +862,16 @@ async function renderPatch(filePath, patch) {
863
862
  }
864
863
 
865
864
  return blocks
866
- .map((block) => renderPatchBlock(block, originalLines, nonce))
865
+ .map((block) =>
866
+ renderPatchBlock(block, originalLines, nonce, {
867
+ header: (text) => styleText("cyan", text),
868
+ del: (text) => styleText("magenta", text),
869
+ add: (text) => styleText("green", text),
870
+ }),
871
+ )
867
872
  .join("\n\n");
868
873
  }
869
874
 
870
- /**
871
- * @param {PatchBlock} block
872
- * @param {string[] | null} originalLines
873
- * @param {string} nonce
874
- * @returns {string}
875
- */
876
- function renderPatchBlock(block, originalLines, nonce) {
877
- /** @type {string[]} */
878
- const out = [];
879
- if (block.op === "replace") {
880
- out.push(
881
- styleText(
882
- "cyan",
883
- `REPLACE ${nonce} ${block.start}:${block.startHash}-${block.end}:${block.endHash}`,
884
- ),
885
- );
886
- if (originalLines) {
887
- const safeStart = Math.max(1, block.start);
888
- const safeEnd = Math.min(originalLines.length, block.end);
889
- const oldSlice = originalLines.slice(safeStart - 1, safeEnd);
890
- // Use a real line diff so unchanged lines render as context
891
- // (no color, " " prefix) instead of being shown as both "- " and
892
- // "+ ".
893
- for (const op of diffLines(oldSlice, block.body)) {
894
- if (op.type === "-") {
895
- out.push(styleText("magenta", `- ${op.line}`));
896
- } else if (op.type === "+") {
897
- out.push(styleText("green", `+ ${op.line}`));
898
- } else {
899
- out.push(` ${op.line}`);
900
- }
901
- }
902
- } else {
903
- // No file context available — fall back to listing the body as
904
- // additions so the user can still see the new content.
905
- for (const line of block.body) {
906
- out.push(styleText("green", `+ ${line}`));
907
- }
908
- }
909
- } else {
910
- const afterSuffix = block.afterHash ? `:${block.afterHash}` : "";
911
- out.push(
912
- styleText("cyan", `INSERT_AFTER ${nonce} ${block.after}${afterSuffix}`),
913
- );
914
- for (const line of block.body) {
915
- out.push(styleText("green", `+ ${line}`));
916
- }
917
- }
918
- return out.join("\n");
919
- }
920
-
921
875
  /**
922
876
  * Verbatim highlighter used as fallback when block-aware rendering is not
923
877
  * possible (parse error, missing nonce, etc.).
@@ -89,7 +89,7 @@ export async function callAnthropicModel(
89
89
  /** @type {AnthropicRequestInput} */
90
90
  const request = {
91
91
  ...platformRequest,
92
- system: messages
92
+ system: cacheEnabledMessages
93
93
  .filter((m) => m.role === "system")
94
94
  .flatMap((m) => m.content),
95
95
  messages: cacheEnabledMessages.filter((m) => m.role !== "system"),
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import fs from "node:fs/promises";
7
+ import { diffLines } from "../utils/diffLines.mjs";
7
8
  import { lineHash } from "../utils/lineHash.mjs";
8
9
  import { noThrow } from "../utils/noThrow.mjs";
9
10
 
@@ -66,9 +67,14 @@ content at beginning of file
66
67
  }
67
68
 
68
69
  const original = await fs.readFile(filePath, "utf8");
70
+ const originalLines = splitLines(original);
69
71
  const newContent = applyBlocks(original, blocks);
70
72
  await fs.writeFile(filePath, newContent);
71
- return `Patched file: ${filePath}`;
73
+
74
+ const diff = blocks
75
+ .map((block) => renderPatchBlock(block, originalLines, nonce))
76
+ .join("\n\n");
77
+ return `Patched file: ${filePath}\n${diff}`;
72
78
  }),
73
79
 
74
80
  /**
@@ -234,6 +240,71 @@ export function applyBlocks(original, blocks) {
234
240
  return result;
235
241
  }
236
242
 
243
+ /**
244
+ * @typedef {(text: string) => string} DiffStyler
245
+ */
246
+
247
+ /**
248
+ * Render a single patch block as a unified-style diff of the change against
249
+ * the original file: the block header, then removed lines ("- "), added lines
250
+ * ("+ "), and unchanged context (" ") for its range only.
251
+ *
252
+ * The optional `style` callbacks colorize the header and change lines; they
253
+ * default to identity so the output is plain text suitable for tool results,
254
+ * while the CLI passes styleText-based stylers for colored display.
255
+ *
256
+ * @param {PatchBlock} block
257
+ * @param {string[] | null} originalLines
258
+ * @param {string} nonce
259
+ * @param {{ header?: DiffStyler, del?: DiffStyler, add?: DiffStyler }} [style]
260
+ * @returns {string}
261
+ */
262
+ export function renderPatchBlock(block, originalLines, nonce, style = {}) {
263
+ const header = style.header ?? ((text) => text);
264
+ const del = style.del ?? ((text) => text);
265
+ const add = style.add ?? ((text) => text);
266
+
267
+ /** @type {string[]} */
268
+ const out = [];
269
+ if (block.op === "replace") {
270
+ out.push(
271
+ header(
272
+ `REPLACE ${nonce} ${block.start}:${block.startHash}-${block.end}:${block.endHash}`,
273
+ ),
274
+ );
275
+ if (originalLines) {
276
+ const safeStart = Math.max(1, block.start);
277
+ const safeEnd = Math.min(originalLines.length, block.end);
278
+ const oldSlice = originalLines.slice(safeStart - 1, safeEnd);
279
+ // Use a real line diff so unchanged lines render as context
280
+ // (no color, " " prefix) instead of being shown as both "- " and
281
+ // "+ ".
282
+ for (const op of diffLines(oldSlice, block.body)) {
283
+ if (op.type === "-") {
284
+ out.push(del(`- ${op.line}`));
285
+ } else if (op.type === "+") {
286
+ out.push(add(`+ ${op.line}`));
287
+ } else {
288
+ out.push(` ${op.line}`);
289
+ }
290
+ }
291
+ } else {
292
+ // No file context available — fall back to listing the body as
293
+ // additions so the new content is still visible.
294
+ for (const line of block.body) {
295
+ out.push(add(`+ ${line}`));
296
+ }
297
+ }
298
+ } else {
299
+ const afterSuffix = block.afterHash ? `:${block.afterHash}` : "";
300
+ out.push(header(`INSERT_AFTER ${nonce} ${block.after}${afterSuffix}`));
301
+ for (const line of block.body) {
302
+ out.push(add(`+ ${line}`));
303
+ }
304
+ }
305
+ return out.join("\n");
306
+ }
307
+
237
308
  /**
238
309
  * @param {string} headerArgs
239
310
  * @param {"replace" | "insert"} op
@@ -374,3 +445,18 @@ function detectConflicts(blocks) {
374
445
  }
375
446
  }
376
447
  }
448
+
449
+ /**
450
+ * Split file content into lines the same way applyBlocks does: drop the
451
+ * trailing empty element produced by split() when the content ends with a
452
+ * newline (or is empty), so line numbers match read_file.
453
+ * @param {string} content
454
+ * @returns {string[]}
455
+ */
456
+ function splitLines(content) {
457
+ const lines = content.split("\n");
458
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
459
+ lines.pop();
460
+ }
461
+ return lines;
462
+ }
@@ -31,7 +31,7 @@ export function matchValue(value, pattern) {
31
31
  typeof value === "object" &&
32
32
  value !== null &&
33
33
  Object.entries(pattern).every(([k, p]) =>
34
- matchValue(value[/** @type {keyof value} */ (k)], p),
34
+ matchValue(/** @type {Record<string, unknown>} */ (value)[k], p),
35
35
  )
36
36
  );
37
37
  }