pi-ui-extend 1.0.1 → 1.0.2

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.
@@ -27,39 +27,34 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
27
27
  const headerPrefix = headerLabel ? `${stateIcon} ${headerLabel}` : stateIcon;
28
28
  const headerArgs = formatToolHeaderArgs(entry.headerArgs);
29
29
  const headerArgsWidth = width - stringDisplayWidth(headerPrefix) - 1;
30
- const clippedHeaderArgs = headerArgsWidth > 0 ? sliceByDisplayWidth(headerArgs, headerArgsWidth) : "";
31
- const header = clippedHeaderArgs ? `${headerPrefix} ${clippedHeaderArgs}` : headerPrefix;
32
- const headerArgsStart = headerPrefix.length + 1;
33
- const clippedHeaderArgSegments = clippedHeaderArgs
34
- ? clipAndShiftSegments(entry.headerArgSegments, headerArgsStart, clippedHeaderArgs.length)
35
- : [];
36
- const headerArgBaseSegments = clippedHeaderArgs
37
- ? uncoveredSegments(headerArgsStart, header.length, toolOutputColor, clippedHeaderArgSegments)
38
- : [];
39
30
  const target = { kind: "tool", id: entry.id };
40
31
  const showGutter = options.showGutter ?? true;
41
- const headerLine = {
42
- text: header,
32
+ const headerLines = renderToolHeaderLines({
33
+ entry,
34
+ expanded,
35
+ headerPrefix,
36
+ headerArgs,
37
+ headerArgsWidth,
38
+ stateIcon,
39
+ width,
43
40
  target,
44
- colorOverride: toolColor,
45
- ...(options.backgroundOverride && !options.skipHeaderBackground ? { backgroundOverride: options.backgroundOverride } : {}),
46
- segments: [
47
- { start: 0, end: stateIcon.length, foreground: toolStatusIconColor(entry, colors), bold: true },
48
- { start: stateIcon.length, end: headerPrefix.length, bold: true },
49
- ...headerArgBaseSegments,
50
- ...clippedHeaderArgSegments,
51
- ],
52
- };
53
- const headerLines = [headerLine];
41
+ toolColor,
42
+ toolOutputColor,
43
+ statusIconColor: toolStatusIconColor(entry, colors),
44
+ backgroundOverride: options.backgroundOverride && !options.skipHeaderBackground ? options.backgroundOverride : undefined,
45
+ });
46
+ const headerLine = headerLines[0];
47
+ if (!headerLine)
48
+ return [];
54
49
  if (expanded) {
55
- headerLines.push(...renderToolBodyLines(entry.expandedText, width, target, toolOutputColor, entry.bodyStyle, colors, entry.syntaxHighlight, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi, showGutter));
56
- if (options.skipHeaderBackground && headerLines.length > 1) {
57
- applyBackground(headerLines.slice(1));
50
+ const bodyLines = renderToolBodyLines(entry.expandedText, width, target, toolOutputColor, entry.bodyStyle, colors, entry.syntaxHighlight, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi, showGutter);
51
+ if (options.skipHeaderBackground) {
52
+ applyBackground(bodyLines);
58
53
  }
59
54
  else {
60
- applyBackground(headerLines);
55
+ applyBackground([...headerLines, ...bodyLines]);
61
56
  }
62
- return headerLines;
57
+ return [...headerLines, ...bodyLines];
63
58
  }
64
59
  if (rule.compactHidden || (rule.defaultExpanded === true && !options.superCompact))
65
60
  return headerLines;
@@ -75,7 +70,7 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
75
70
  if (!preview.text)
76
71
  return headerLines;
77
72
  const separator = " — ";
78
- const availablePreviewWidth = width - stringDisplayWidth(header) - stringDisplayWidth(separator);
73
+ const availablePreviewWidth = width - stringDisplayWidth(headerLine.text) - stringDisplayWidth(separator);
79
74
  if (availablePreviewWidth <= 0)
80
75
  return headerLines;
81
76
  const markerPrefix = truncatedPreviewMarker();
@@ -83,8 +78,8 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
83
78
  const clippedPreview = sliceByDisplayWidth(previewText, availablePreviewWidth);
84
79
  if (!clippedPreview)
85
80
  return headerLines;
86
- headerLine.text = `${header}${separator}${clippedPreview}`;
87
- const previewStart = header.length + separator.length;
81
+ headerLine.text = `${headerLine.text}${separator}${clippedPreview}`;
82
+ const previewStart = headerLine.text.length - clippedPreview.length;
88
83
  const previewTextStart = previewStart + (preview.overflow ? markerPrefix.length : 0);
89
84
  headerLine.segments = [
90
85
  ...(headerLine.segments ?? []),
@@ -93,15 +88,54 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
93
88
  ];
94
89
  return headerLines;
95
90
  }
96
- function clipAndShiftSegments(segments, offset, length) {
97
- if (!segments || length <= 0)
91
+ function renderToolHeaderLines(input) {
92
+ const visibleArgs = input.expanded
93
+ ? indexedWrappedText(input.headerArgs, Math.max(1, input.headerArgsWidth))
94
+ : indexedClippedText(input.headerArgs, input.headerArgsWidth);
95
+ const chunks = visibleArgs.length > 0 ? visibleArgs : [{ text: "", start: 0, end: 0 }];
96
+ const continuationIndent = " ".repeat(Math.min(stringDisplayWidth(input.headerPrefix) + 1, Math.max(0, input.width - 1)));
97
+ return chunks.map((chunk, index) => {
98
+ let linePrefix = continuationIndent;
99
+ if (index === 0)
100
+ linePrefix = chunk.text ? `${input.headerPrefix} ` : input.headerPrefix;
101
+ const text = `${linePrefix}${chunk.text}`;
102
+ const argStart = linePrefix.length;
103
+ const customSegments = input.entry.headerArgSegments
104
+ ?.flatMap((segment) => shiftSegmentToRange(segment, chunk.start, chunk.end))
105
+ .map((segment) => ({ ...segment, start: segment.start + argStart, end: segment.end + argStart })) ?? [];
106
+ const baseSegments = uncoveredSegments(argStart, text.length, input.toolOutputColor, customSegments);
107
+ return {
108
+ text,
109
+ target: input.target,
110
+ colorOverride: input.toolColor,
111
+ ...(input.backgroundOverride ? { backgroundOverride: input.backgroundOverride } : {}),
112
+ segments: [
113
+ ...(index === 0 ? [
114
+ { start: 0, end: input.stateIcon.length, foreground: input.statusIconColor, bold: true },
115
+ { start: input.stateIcon.length, end: input.headerPrefix.length, bold: true },
116
+ ] : []),
117
+ ...baseSegments,
118
+ ...customSegments,
119
+ ],
120
+ };
121
+ });
122
+ }
123
+ function indexedClippedText(text, width) {
124
+ if (width <= 0)
125
+ return [];
126
+ const clipped = sliceByDisplayWidth(text, width);
127
+ return clipped ? [{ text: clipped, start: 0, end: clipped.length }] : [];
128
+ }
129
+ function indexedWrappedText(text, width) {
130
+ if (!text)
98
131
  return [];
99
- return segments.flatMap((segment) => {
100
- const start = Math.max(0, segment.start);
101
- const end = Math.min(length, segment.end);
102
- if (end <= start)
103
- return [];
104
- return [{ ...segment, start: offset + start, end: offset + end }];
132
+ let cursor = 0;
133
+ return wrapDisplayLineByWords(text, width).map((chunk) => {
134
+ const foundAt = text.indexOf(chunk, cursor);
135
+ const start = foundAt >= 0 ? foundAt : cursor;
136
+ const end = start + chunk.length;
137
+ cursor = end;
138
+ return { text: chunk, start, end };
105
139
  });
106
140
  }
107
141
  function uncoveredSegments(start, end, foreground, coveredSegments) {
@@ -81,6 +81,7 @@ export default function sessionTitle(pi) {
81
81
  const refreshTimers = new Set();
82
82
  let pendingGeneration;
83
83
  let forkTitleState;
84
+ let resourceCommandTitleSessionId;
84
85
  function abortCurrentRequest() {
85
86
  controller?.abort();
86
87
  controller = undefined;
@@ -349,6 +350,7 @@ export default function sessionTitle(pi) {
349
350
  sessionId = ctx.sessionManager.getSessionId();
350
351
  pendingGeneration = undefined;
351
352
  forkTitleState = undefined;
353
+ resourceCommandTitleSessionId = undefined;
352
354
  lastRenderedName = undefined;
353
355
  lastRenderedTitle = undefined;
354
356
  refreshSessionUi(ctx, { force: true });
@@ -360,6 +362,7 @@ export default function sessionTitle(pi) {
360
362
  clearRetryTimer();
361
363
  clearRefreshTimers();
362
364
  forkTitleState = undefined;
365
+ resourceCommandTitleSessionId = undefined;
363
366
  });
364
367
  function refreshOnEvent(ctx) {
365
368
  refreshSessionUi(ctx);
@@ -378,24 +381,33 @@ export default function sessionTitle(pi) {
378
381
  scheduleSessionUiRefresh(ctx);
379
382
  if (event.source === "extension")
380
383
  return { action: "continue" };
381
- const fallbackInput = fallbackTitleInputFromPrompt(event.text, event.images);
382
- if (!fallbackInput)
383
- return { action: "continue" };
384
- const titleInput = titleGenerationInputFromPrompt(event.text, event.images) ?? fallbackInput;
385
- if (event.text.trimStart().startsWith("/"))
386
- return { action: "continue" };
387
384
  const currentSessionId = ctx.sessionManager.getSessionId();
388
385
  sessionId = currentSessionId;
389
386
  const currentName = currentSessionName(ctx);
390
387
  const activeForkTitleState = forkTitleState?.sessionId === currentSessionId ? forkTitleState : undefined;
391
- if (!activeForkTitleState && hasExistingUserMessage(ctx)) {
388
+ if (event.text.trimStart().startsWith("/")) {
389
+ if (!activeForkTitleState
390
+ && !currentName
391
+ && (resourceCommandTitleSessionId === currentSessionId || !hasExistingUserMessage(ctx))) {
392
+ resourceCommandTitleSessionId = currentSessionId;
393
+ }
394
+ return { action: "continue" };
395
+ }
396
+ const fallbackInput = fallbackTitleInputFromPrompt(event.text, event.images);
397
+ if (!fallbackInput)
398
+ return { action: "continue" };
399
+ const titleInput = titleGenerationInputFromPrompt(event.text, event.images) ?? fallbackInput;
400
+ const titleEligibleAfterResourceCommand = resourceCommandTitleSessionId === currentSessionId;
401
+ if (!activeForkTitleState && !titleEligibleAfterResourceCommand && hasExistingUserMessage(ctx)) {
392
402
  forkTitleState = undefined;
393
403
  return { action: "continue" };
394
404
  }
395
405
  if (currentName && (!activeForkTitleState || currentName !== activeForkTitleState.inheritedSessionName)) {
396
406
  forkTitleState = undefined;
407
+ resourceCommandTitleSessionId = undefined;
397
408
  return { action: "continue" };
398
409
  }
410
+ resourceCommandTitleSessionId = undefined;
399
411
  if (!currentConfig.enabled) {
400
412
  applyFallbackSessionTitle(ctx, currentConfig, activeForkTitleState
401
413
  ? buildForkTitleInput(activeForkTitleState.parentTitle, fallbackInput)
@@ -43,9 +43,9 @@
43
43
  "vscode-languageserver-protocol": "^3.17.5"
44
44
  },
45
45
  "peerDependencies": {
46
- "@earendil-works/pi-ai": "0.82.1",
47
- "@earendil-works/pi-coding-agent": "0.82.1",
48
- "@earendil-works/pi-tui": "0.82.1",
46
+ "@earendil-works/pi-ai": "0.83.0",
47
+ "@earendil-works/pi-coding-agent": "0.83.0",
48
+ "@earendil-works/pi-tui": "0.83.0",
49
49
  "typebox": "*"
50
50
  },
51
51
  "devDependencies": {
@@ -132,7 +132,7 @@ export function streamAntigravity(model: AntigravityModel, context: Context, opt
132
132
  provider: model.provider,
133
133
  model: model.id,
134
134
  usage: baseUsage(),
135
- stopReason: "stop",
135
+ stopReason: "pending",
136
136
  timestamp: Date.now(),
137
137
  };
138
138
 
@@ -258,12 +258,16 @@ export function streamAntigravity(model: AntigravityModel, context: Context, opt
258
258
  stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
259
259
  }
260
260
  }
261
- if (candidate?.finishReason) output.stopReason = mapStopReason(candidate.finishReason);
262
- if (output.content.some((block) => block.type === "toolCall")) output.stopReason = "toolUse";
261
+ if (candidate?.finishReason) {
262
+ output.rawStopReason = candidate.finishReason;
263
+ output.stopReason = mapStopReason(candidate.finishReason);
264
+ if (output.content.some((block) => block.type === "toolCall")) output.stopReason = "toolUse";
265
+ }
263
266
  }
264
267
  closeOpenText();
265
268
  closeOpenThinking();
266
269
  if (options?.signal?.aborted) throw new Error("Request was aborted");
270
+ if (output.stopReason === "pending") throw new Error("Antigravity stream ended without a finish reason");
267
271
  if (output.stopReason === "error" || output.stopReason === "aborted") throw new Error("Antigravity stopped with an error finish reason");
268
272
  stream.push({ type: "done", reason: output.stopReason, message: output });
269
273
  stream.end();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extend",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Pix: a workspace-first terminal UI for Pi with tabs, readable tool activity, voice input, and bundled agent tools.",
5
5
  "private": false,
6
6
  "repository": {
@@ -75,9 +75,9 @@
75
75
  "prepublishOnly": "npm run check && npm run build:pix && npm run generate-schemas"
76
76
  },
77
77
  "dependencies": {
78
- "@earendil-works/pi-ai": "0.82.1",
79
- "@earendil-works/pi-coding-agent": "0.82.1",
80
- "@earendil-works/pi-tui": "0.82.1",
78
+ "@earendil-works/pi-ai": "0.83.0",
79
+ "@earendil-works/pi-coding-agent": "0.83.0",
80
+ "@earendil-works/pi-tui": "0.83.0",
81
81
  "@mariozechner/clipboard": "^0.3.9",
82
82
  "jsonc-parser": "3.3.1",
83
83
  "typebox": "1.1.38",