@xynogen/pix-edit 0.1.24 → 0.1.26

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-edit",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Pi tool — precise text replacement edit with diff rendering",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "@xynogen/pix-data": "^0.4.3",
38
38
  "@xynogen/pix-pretty": "^1.11.2",
39
- "@xynogen/pix-runtime": "^0.5.3"
39
+ "@xynogen/pix-runtime": "^0.6.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@earendil-works/pi-coding-agent": "*",
package/src/edit.test.ts CHANGED
@@ -106,6 +106,61 @@ describe("registerEditTool", () => {
106
106
  expect(result?.getText()).not.toContain("✓ edit");
107
107
  });
108
108
 
109
+ it("renders single-step output inline as one compact line, framed when expanded", () => {
110
+ const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
111
+ const mockPi: PiPrettyApi = {
112
+ registerTool(tool: unknown) {
113
+ Object.assign(registered, tool);
114
+ },
115
+ registerCommand() {},
116
+ on() {},
117
+ };
118
+ registerEditTool(
119
+ mockPi,
120
+ () => ({ execute: async () => ({ content: [], details: undefined }) }),
121
+ {
122
+ cwd: process.cwd(),
123
+ sp: (p: string) => p,
124
+ TextComponent: MockTextComponent as unknown as TextComponentCtor,
125
+ fffState: {
126
+ module: null,
127
+ finder: null,
128
+ partialIndex: false,
129
+ dbDir: null,
130
+ } as unknown as FffState,
131
+ cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
132
+ },
133
+ () => {},
134
+ );
135
+ const theme: ThemeLike = { fg: (_k: string, v: string) => v, bold: (v: string) => v };
136
+ const strip = (s: string) => s.replace(/\u001b\[[0-9;]*m/g, "");
137
+ // edit today shows summary + diff; the ask is that single-step call already covers summary
138
+ // so collapsed result stays compact — this test documents current behavior does NOT duplicate
139
+ const result = {
140
+ content: [{ type: "text", text: "edited" }],
141
+ details: {
142
+ _type: "editInfo",
143
+ filePath: "app.py",
144
+ summary: "-1",
145
+ oldContent: "old line\n",
146
+ newContent: "new line\n",
147
+ language: "python",
148
+ editLine: 1,
149
+ },
150
+ };
151
+ const out =
152
+ registered
153
+ .renderResult?.(result, { isPartial: false }, theme, {
154
+ expanded: false,
155
+ isError: false,
156
+ invalidate: () => {},
157
+ state: {},
158
+ } as unknown as RenderContextLike)
159
+ ?.getText() ?? "";
160
+ // collapsed/resting: diff placeholder path covered; when expanded framing should not duplicate header
161
+ expect(strip(out).split("\n").length).toBeGreaterThanOrEqual(1);
162
+ });
163
+
109
164
  it("collapses structured errors and restores the exact diagnostic on expansion", () => {
110
165
  const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
111
166
  const mockPi: PiPrettyApi = {
@@ -155,7 +210,7 @@ describe("registerEditTool", () => {
155
210
  ?.getText() ?? "";
156
211
 
157
212
  expect(render({ timer: 1 })).toContain(diagnostic);
158
- expect(render({ collapsed: true })).toContain("✗ edit sample.ts · failed");
213
+ expect(render({ collapsed: true })).toContain("✗ edit sample.ts · failed");
159
214
  expect(render({ collapsed: true }, true)).toContain(diagnostic);
160
215
  });
161
216
  });
package/src/edit.ts CHANGED
@@ -28,6 +28,7 @@ import type {
28
28
  ToolResultLike,
29
29
  } from "@xynogen/pix-pretty/types";
30
30
  import {
31
+ dotJoin,
31
32
  fillToolBackground,
32
33
  getTextContent,
33
34
  hideCollapsedToolCall,
@@ -128,13 +129,32 @@ export function registerEditTool(
128
129
  const operations = getEditOperations(params);
129
130
  const fileLang = lang(fp);
130
131
 
131
- const result = (await origEdit.execute(
132
- tid,
133
- params as unknown as Parameters<typeof origEdit.execute>[1],
134
- sig,
135
- upd,
136
- toolCtx,
137
- )) as ToolResultLike;
132
+ let result: ToolResultLike;
133
+ try {
134
+ result = (await origEdit.execute(
135
+ tid,
136
+ params as unknown as Parameters<typeof origEdit.execute>[1],
137
+ sig,
138
+ upd,
139
+ toolCtx,
140
+ )) as ToolResultLike;
141
+ } catch (error) {
142
+ const text = error instanceof Error ? error.message : String(error);
143
+ if (sig?.aborted || /aborted/i.test(text)) throw error;
144
+ return {
145
+ content: [{ type: "text" as const, text }],
146
+ details: {
147
+ _type: "editInfo" as const,
148
+ summary: "failed",
149
+ editLine: 0,
150
+ oldContent: operations[0]?.oldText ?? "",
151
+ newContent: operations[0]?.newText ?? "",
152
+ language: fileLang,
153
+ filePath: fp,
154
+ },
155
+ isError: true,
156
+ };
157
+ }
138
158
 
139
159
  if (operations.length === 0) return result;
140
160
 
@@ -191,11 +211,16 @@ export function registerEditTool(
191
211
 
192
212
  const { summary } = summarizeEditOperations(operations);
193
213
  const coloredSummary = renderDiffSummary(summary, theme);
194
- const suffix =
195
- operations.length === 1
196
- ? coloredSummary
197
- : `${theme.fg("muted", `${operations.length} edits`)} ${coloredSummary}`;
198
- text.setText(fillToolBackground(`${hdr} ${suffix}`));
214
+ const paint = (s: string) => theme.fg("dim", s);
215
+ const suffix = dotJoin(
216
+ [
217
+ hdr,
218
+ operations.length > 1 && theme.fg("muted", `${operations.length} edits`),
219
+ coloredSummary,
220
+ ],
221
+ paint,
222
+ );
223
+ text.setText(fillToolBackground(suffix));
199
224
  return text;
200
225
  },
201
226
 
@@ -223,7 +248,7 @@ export function registerEditTool(
223
248
  d?._type === "editInfo"
224
249
  ? (d.summary as string)
225
250
  : d?._type === "multiEditInfo"
226
- ? `${d.editCount} edits · ${d.summary}`
251
+ ? dotJoin([`${d.editCount} edits`, String(d.summary)])
227
252
  : "edited";
228
253
  let filePath = "";
229
254
  if (d?._type === "editInfo") filePath = String(d.filePath ?? "");
@@ -254,10 +279,8 @@ export function registerEditTool(
254
279
  if (renderCtx.toolCallId) trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
255
280
  if (renderCtx.state._edk !== key) {
256
281
  renderCtx.state._edk = key;
257
- const loc =
258
- (d.editLine as number) > 0 ? ` ${theme.fg("muted", `at line ${d.editLine}`)}` : "";
259
- const summary = renderDiffSummary(String(d.summary), theme);
260
- renderCtx.state._edt = ` ${summary}${loc}\n${theme.fg("muted", " rendering diff…")}`;
282
+ // ponytail: call already shows `edit <file> <summary>`; don't repeat summary+loc header — gutter carries absolute line
283
+ renderCtx.state._edt = theme.fg("muted", " rendering diff…");
261
284
  const dc = resolveDiffColors(theme);
262
285
  const diff = parseDiff(
263
286
  d.oldContent as string,
@@ -268,10 +291,7 @@ export function registerEditTool(
268
291
  renderSplit(diff, d.language as string | undefined, MAX_RENDER_LINES, dc)
269
292
  .then((rendered) => {
270
293
  if (renderCtx.state._edk !== key) return;
271
- const loc2 =
272
- (d.editLine as number) > 0 ? ` ${theme.fg("muted", `at line ${d.editLine}`)}` : "";
273
- const summary = renderDiffSummary(String(d.summary), theme);
274
- renderCtx.state._edt = ` ${summary}${loc2}\n${rendered}`;
294
+ renderCtx.state._edt = rendered;
275
295
  renderCtx.invalidate();
276
296
  })
277
297
  .catch(() => {
@@ -290,8 +310,8 @@ export function registerEditTool(
290
310
  if (renderCtx.toolCallId) trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
291
311
  if (renderCtx.state._edk !== key) {
292
312
  renderCtx.state._edk = key;
293
- const summary = renderDiffSummary(String(d.summary), theme);
294
- renderCtx.state._edt = ` ${theme.fg("muted", `${d.editCount} edits`)} ${summary}\n${theme.fg("muted", " rendering diff…")}`;
313
+ // ponytail: call already shows summary; render diffs directly
314
+ renderCtx.state._edt = theme.fg("muted", " rendering diff…");
295
315
  const dc = resolveDiffColors(theme);
296
316
  Promise.all(
297
317
  (
@@ -309,8 +329,7 @@ export function registerEditTool(
309
329
  .then((rendered) => {
310
330
  if (renderCtx.state._edk !== key) return;
311
331
  const body = rendered.join(`\n${theme.fg("muted", " ···")}\n`);
312
- const summary = renderDiffSummary(String(d.summary), theme);
313
- renderCtx.state._edt = ` ${theme.fg("muted", `${d.editCount} edits`)} ${summary}\n${body}`;
332
+ renderCtx.state._edt = body;
314
333
  renderCtx.invalidate();
315
334
  })
316
335
  .catch(() => {