pi-ui-extend 0.1.36 → 0.1.37

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.
Files changed (70) hide show
  1. package/dist/app/app.d.ts +7 -0
  2. package/dist/app/app.js +40 -5
  3. package/dist/app/commands/command-controller.js +1 -0
  4. package/dist/app/commands/command-registry.d.ts +1 -0
  5. package/dist/app/commands/command-registry.js +8 -0
  6. package/dist/app/commands/command-session-actions.d.ts +2 -0
  7. package/dist/app/commands/command-session-actions.js +79 -1
  8. package/dist/app/extensions/extension-actions-controller.d.ts +4 -1
  9. package/dist/app/extensions/extension-actions-controller.js +31 -2
  10. package/dist/app/input/input-controller.d.ts +1 -0
  11. package/dist/app/input/input-controller.js +23 -2
  12. package/dist/app/input/terminal-edit-shortcuts.d.ts +1 -0
  13. package/dist/app/input/terminal-edit-shortcuts.js +7 -0
  14. package/dist/app/input/voice-controller.js +1 -1
  15. package/dist/app/popup/popup-action-controller.d.ts +1 -3
  16. package/dist/app/popup/popup-action-controller.js +1 -5
  17. package/dist/app/rendering/message-content.js +4 -3
  18. package/dist/app/rendering/render-controller.js +21 -38
  19. package/dist/app/rendering/status-line-renderer.d.ts +1 -0
  20. package/dist/app/rendering/status-line-renderer.js +14 -2
  21. package/dist/app/runtime.js +12 -2
  22. package/dist/app/screen/mouse-controller.js +2 -0
  23. package/dist/app/session/session-event-controller.d.ts +7 -0
  24. package/dist/app/session/session-event-controller.js +10 -13
  25. package/dist/app/terminal/terminal-controller.js +1 -0
  26. package/dist/app/terminal/terminal-output-buffer.d.ts +8 -6
  27. package/dist/app/terminal/terminal-output-buffer.js +24 -16
  28. package/dist/bundled-extensions/terminal-bell/index.js +118 -33
  29. package/dist/markdown-format.d.ts +1 -0
  30. package/dist/markdown-format.js +30 -16
  31. package/dist/schemas/pi-tools-suite-schema.d.ts +5 -0
  32. package/dist/schemas/pi-tools-suite-schema.js +5 -0
  33. package/dist/tool-renderers/apply-patch.js +6 -1
  34. package/dist/tool-renderers/patch-normalize.d.ts +24 -0
  35. package/dist/tool-renderers/patch-normalize.js +163 -0
  36. package/external/pi-tools-suite/README.md +3 -2
  37. package/external/pi-tools-suite/package.json +3 -3
  38. package/external/pi-tools-suite/src/antigravity-auth/index.ts +15 -2
  39. package/external/pi-tools-suite/src/antigravity-auth/status.ts +36 -19
  40. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +5 -2
  41. package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -2
  42. package/external/pi-tools-suite/src/async-subagents/core/config.ts +8 -3
  43. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +63 -28
  44. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +9 -4
  45. package/external/pi-tools-suite/src/comment-checker/config.ts +98 -0
  46. package/external/pi-tools-suite/src/comment-checker/detect.ts +215 -0
  47. package/external/pi-tools-suite/src/comment-checker/index.ts +294 -0
  48. package/external/pi-tools-suite/src/dcp/commands.ts +29 -15
  49. package/external/pi-tools-suite/src/dcp/compress-tool.ts +111 -60
  50. package/external/pi-tools-suite/src/dcp/config.ts +10 -6
  51. package/external/pi-tools-suite/src/dcp/debug-log.ts +235 -0
  52. package/external/pi-tools-suite/src/dcp/index.ts +204 -27
  53. package/external/pi-tools-suite/src/dcp/prompts.ts +25 -28
  54. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +6 -10
  55. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +19 -1
  56. package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +36 -58
  57. package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +18 -0
  58. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
  59. package/external/pi-tools-suite/src/dcp/pruner.ts +4 -2
  60. package/external/pi-tools-suite/src/dcp/state-persistence.ts +31 -2
  61. package/external/pi-tools-suite/src/dcp/state.ts +62 -4
  62. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +18 -0
  63. package/external/pi-tools-suite/src/index.ts +1 -0
  64. package/external/pi-tools-suite/src/model-tools/index.ts +11 -3
  65. package/external/pi-tools-suite/src/telegram-mirror/index.ts +1 -1
  66. package/external/pi-tools-suite/src/todo/index.ts +24 -0
  67. package/external/pi-tools-suite/src/tool-descriptions.ts +3 -3
  68. package/external/pi-tools-suite/src/usage/index.ts +18 -4
  69. package/package.json +4 -4
  70. package/schemas/pi-tools-suite.json +24 -0
@@ -75,24 +75,10 @@ export class AppRenderController {
75
75
  this.deps.mouseController.statusVoiceMicTarget = undefined;
76
76
  this.deps.mouseController.statusVoiceLanguageTarget = undefined;
77
77
  this.deps.mouseController.tabLineTargets.length = 0;
78
- const frameLines = {
79
- tabs: [],
80
- conversation: [],
81
- inputStatus: [],
82
- };
83
- const inputStatusStartRow = toScreenRow(inputSeparatorRow);
84
- const regionForOverlayRow = (row) => {
85
- if (row >= statusRow)
86
- return "inputStatus";
87
- if (topReservedRows > 0 && row <= topReservedRows)
88
- return "tabs";
89
- if (row >= inputStatusStartRow)
90
- return "inputStatus";
91
- return "conversation";
92
- };
93
- const appendFrameOutput = (region, row, output) => {
78
+ const frameRows = new Map();
79
+ const appendFrameOutput = (row, output) => {
94
80
  if (row >= 1 && row <= rows)
95
- frameLines[region].push(output);
81
+ frameRows.set(row, `${frameRows.get(row) ?? ""}${output}`);
96
82
  };
97
83
  const setRenderedBackground = (row, background) => {
98
84
  if (background !== undefined)
@@ -101,14 +87,14 @@ export class AppRenderController {
101
87
  if (topReservedRows > 0) {
102
88
  this.deps.mouseController.tabLineTargets.push(...tabLayout.targets.map((target) => ({ ...target, row: tabRow })));
103
89
  this.deps.mouseController.renderedRowTexts.set(tabRow, tabLayout.text);
104
- appendFrameOutput("tabs", tabRow, this.renderFrameRow(tabRow, this.deps.tabLineRenderer.render(tabRow, tabLayout, columns)));
90
+ appendFrameOutput(tabRow, this.renderFrameRow(tabRow, this.deps.tabLineRenderer.render(tabRow, tabLayout, columns)));
105
91
  if (topReservedRows > 1) {
106
92
  this.deps.mouseController.tabLineTargets.push(...tabLayout.targets
107
93
  .filter((target) => target.kind === "new-tab")
108
94
  .map((target) => ({ ...target, row: tabBottomRow })));
109
95
  const bottomText = this.deps.tabLineRenderer.bottomText(tabLayout, columns);
110
96
  this.deps.mouseController.renderedRowTexts.set(tabBottomRow, bottomText);
111
- appendFrameOutput("tabs", tabBottomRow, this.renderFrameRow(tabBottomRow, this.deps.tabLineRenderer.renderBottom(tabBottomRow, tabLayout, columns)));
97
+ appendFrameOutput(tabBottomRow, this.renderFrameRow(tabBottomRow, this.deps.tabLineRenderer.renderBottom(tabBottomRow, tabLayout, columns)));
112
98
  }
113
99
  }
114
100
  else {
@@ -125,12 +111,12 @@ export class AppRenderController {
125
111
  this.deps.mouseController.renderedImageTargets.set(row, rendered.imageTargets);
126
112
  this.deps.mouseController.renderedRowTexts.set(row, rendered?.text ?? "");
127
113
  setRenderedBackground(row, rendered?.backgroundOverride);
128
- appendFrameOutput("conversation", row, this.renderFrameRow(row, this.deps.screenStyler.styleBaseLine(row, rendered, conversationColumns)));
114
+ appendFrameOutput(row, this.renderFrameRow(row, this.deps.screenStyler.styleBaseLine(row, rendered, conversationColumns)));
129
115
  }
130
116
  const loadingConversationOverlay = this.renderConversationLoadingOverlay(this.deps.loadingConversationOverlayText?.(), conversationColumns, topReservedRows, bodyHeight);
131
117
  if (loadingConversationOverlay) {
132
118
  this.deps.mouseController.renderedRowTexts.set(loadingConversationOverlay.row, loadingConversationOverlay.text);
133
- appendFrameOutput("conversation", loadingConversationOverlay.row, this.renderFrameRow(loadingConversationOverlay.row, loadingConversationOverlay.output));
119
+ appendFrameOutput(loadingConversationOverlay.row, this.renderFrameRow(loadingConversationOverlay.row, loadingConversationOverlay.output));
134
120
  }
135
121
  const aboveEditorStartRow = inputSeparatorRow + 1;
136
122
  for (let index = 0; index < aboveEditorLines.length; index += 1) {
@@ -144,14 +130,14 @@ export class AppRenderController {
144
130
  this.deps.mouseController.renderedImageTargets.set(row, rendered.line.imageTargets);
145
131
  this.deps.mouseController.renderedRowTexts.set(row, rendered.text);
146
132
  setRenderedBackground(row, rendered.line?.backgroundOverride);
147
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, rendered.output(row)));
133
+ appendFrameOutput(row, this.renderFrameRow(row, rendered.output(row)));
148
134
  }
149
135
  if (inputSeparatorRow > 1) {
150
136
  const separatorText = inputFrameLine(columns, "top");
151
137
  const row = toScreenRow(inputSeparatorRow);
152
138
  if (row < statusRow) {
153
139
  this.deps.mouseController.renderedRowTexts.set(row, separatorText);
154
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
140
+ appendFrameOutput(row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
155
141
  foreground: this.deps.theme.colors.tabBorder,
156
142
  })));
157
143
  }
@@ -164,7 +150,7 @@ export class AppRenderController {
164
150
  this.deps.mouseController.renderedRowTexts.set(row, inputLine);
165
151
  const tagColor = this.deps.theme.colors.accent;
166
152
  const styledLine = this.deps.screenStyler.styleInputLine(row, inputLine, tagSpans, suggestionSpans, columns, tagColor, this.deps.theme.colors.muted);
167
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, styledLine));
153
+ appendFrameOutput(row, this.renderFrameRow(row, styledLine));
168
154
  }
169
155
  if (renderedInput.scrollBar && columns > 0) {
170
156
  const scrollBar = renderedInput.scrollBar;
@@ -172,7 +158,7 @@ export class AppRenderController {
172
158
  const row = toScreenRow(inputStartRow + renderedInput.editorStartRowOffset + offset);
173
159
  const isThumb = offset >= scrollBar.top && offset < scrollBar.top + scrollBar.height;
174
160
  const marker = isThumb ? " " : "│";
175
- appendFrameOutput("inputStatus", row, `\x1b[${row};${columns}H${colorize(marker, {
161
+ appendFrameOutput(row, `\x1b[${row};${columns}H${colorize(marker, {
176
162
  foreground: this.deps.theme.colors.inputBorder,
177
163
  ...(isThumb ? { background: this.deps.theme.colors.inputBorder } : {}),
178
164
  })}`);
@@ -182,7 +168,7 @@ export class AppRenderController {
182
168
  const separatorText = inputFrameLine(columns, "bottom");
183
169
  const row = toScreenRow(inputBottomSeparatorRow);
184
170
  this.deps.mouseController.renderedRowTexts.set(row, separatorText);
185
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
171
+ appendFrameOutput(row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
186
172
  foreground: this.deps.theme.colors.tabBorder,
187
173
  })));
188
174
  }
@@ -198,16 +184,16 @@ export class AppRenderController {
198
184
  this.deps.mouseController.renderedImageTargets.set(row, rendered.line.imageTargets);
199
185
  this.deps.mouseController.renderedRowTexts.set(row, rendered.text);
200
186
  setRenderedBackground(row, rendered.line?.backgroundOverride);
201
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, rendered.output(row)));
187
+ appendFrameOutput(row, this.renderFrameRow(row, rendered.output(row)));
202
188
  }
203
189
  const statusLayout = this.deps.statusLineRenderer.layout(columns);
204
190
  this.updateStatusMouseState(statusLayout, statusRow);
205
- appendFrameOutput("inputStatus", statusRow, this.renderFrameRow(statusRow, this.deps.statusLineRenderer.render(statusRow, statusLayout, columns)));
191
+ appendFrameOutput(statusRow, this.renderFrameRow(statusRow, this.deps.statusLineRenderer.render(statusRow, statusLayout, columns)));
206
192
  const voiceProgressOverlay = this.renderVoiceProgressOverlay(this.deps.voiceProgressOverlayText(), columns, statusRow);
207
193
  if (voiceProgressOverlay) {
208
194
  this.deps.mouseController.renderedRowTexts.set(voiceProgressOverlay.row, voiceProgressOverlay.text);
209
195
  setRenderedBackground(voiceProgressOverlay.row, this.deps.theme.colors.info);
210
- appendFrameOutput(regionForOverlayRow(voiceProgressOverlay.row), voiceProgressOverlay.row, this.renderFrameRow(voiceProgressOverlay.row, voiceProgressOverlay.output));
196
+ appendFrameOutput(voiceProgressOverlay.row, this.renderFrameRow(voiceProgressOverlay.row, voiceProgressOverlay.output));
211
197
  }
212
198
  if (defaultOverlayLines.length > 0 && popupMenuPlacement === "default") {
213
199
  const overlayStartRow = Math.max(1, inputSeparatorRow - defaultOverlayLines.length);
@@ -219,7 +205,7 @@ export class AppRenderController {
219
205
  this.deps.mouseController.renderedTargets.set(row, line?.target ?? fallbackTarget);
220
206
  this.deps.mouseController.renderedRowTexts.set(row, this.deps.popupMenus.overlayPlainText(line ?? { text: "" }, columns));
221
207
  setRenderedBackground(row, line?.backgroundOverride);
222
- appendFrameOutput(regionForOverlayRow(row), row, this.renderFrameRow(row, this.deps.popupMenus.styleOverlayLine(row, line ?? { text: "" }, columns)));
208
+ appendFrameOutput(row, this.renderFrameRow(row, this.deps.popupMenus.styleOverlayLine(row, line ?? { text: "" }, columns)));
223
209
  }
224
210
  }
225
211
  if (underTabsOverlayLines.length > 0 && popupMenuPlacement === "under-tabs") {
@@ -231,7 +217,7 @@ export class AppRenderController {
231
217
  this.deps.mouseController.renderedTargets.set(row, line?.target ?? fallbackTarget);
232
218
  this.deps.mouseController.renderedRowTexts.set(row, this.deps.popupMenus.overlayPlainText(line ?? { text: "" }, columns));
233
219
  setRenderedBackground(row, line?.backgroundOverride);
234
- appendFrameOutput(regionForOverlayRow(row), row, this.renderFrameRow(row, this.deps.popupMenus.styleOverlayLine(row, line ?? { text: "" }, columns)));
220
+ appendFrameOutput(row, this.renderFrameRow(row, this.deps.popupMenus.styleOverlayLine(row, line ?? { text: "" }, columns)));
235
221
  }
236
222
  }
237
223
  for (const toastOverlay of renderToastOverlays(visibleToastStates(this.deps.toastController), columns, Math.max(0, statusRow - topReservedRows - 1), this.deps.theme)) {
@@ -240,7 +226,7 @@ export class AppRenderController {
240
226
  if (toastOverlay.target)
241
227
  this.deps.mouseController.renderedTargets.set(row, toastOverlay.target);
242
228
  this.deps.mouseController.renderedRowTexts.set(row, overlayText(rowText, toastOverlay.column, toastOverlay.text));
243
- appendFrameOutput(regionForOverlayRow(row), row, `\x1b[${row};${toastOverlay.column}H${toastOverlay.output}`);
229
+ appendFrameOutput(row, `\x1b[${row};${toastOverlay.column}H${toastOverlay.output}`);
244
230
  }
245
231
  if (topReservedRows === 0) {
246
232
  const newTabTarget = tabLayout.targets.find((target) => target.kind === "new-tab");
@@ -248,7 +234,7 @@ export class AppRenderController {
248
234
  const plusColumn = newTabTarget.endColumn - stringDisplayWidth(APP_ICONS.plus);
249
235
  const rowText = this.deps.mouseController.renderedRowTexts.get(tabRow) ?? "";
250
236
  this.deps.mouseController.renderedRowTexts.set(tabRow, overlayText(rowText, plusColumn, APP_ICONS.plus));
251
- appendFrameOutput(regionForOverlayRow(tabRow), tabRow, `\x1b[${tabRow};${plusColumn}H${colorize(APP_ICONS.plus, {
237
+ appendFrameOutput(tabRow, `\x1b[${tabRow};${plusColumn}H${colorize(APP_ICONS.plus, {
252
238
  foreground: this.deps.theme.colors.info,
253
239
  bold: true,
254
240
  })}`);
@@ -258,11 +244,8 @@ export class AppRenderController {
258
244
  const cursor = renderedInput.cursorVisible ? `\x1b[${cursorRow};${renderedInput.cursorColumn}H${SHOW_CURSOR}` : "";
259
245
  if (this.deps.mouseController.consumeClickFlashDirty?.())
260
246
  this.outputBuffer.reset();
261
- const output = this.outputBuffer.diffFrame({
262
- tabs: frameLines.tabs.join(""),
263
- conversation: frameLines.conversation.join(""),
264
- inputStatus: frameLines.inputStatus.join(""),
265
- });
247
+ const frame = [...frameRows.entries()].map(([row, output]) => ({ row, output }));
248
+ const output = this.outputBuffer.diffFrame(frame);
266
249
  process.stdout.write(`${DISABLE_TERMINAL_WRAP}${HIDE_CURSOR}${output}${this.renderClickFlashOverlay(columns, rows)}${cursor}`);
267
250
  }
268
251
  renderFrameRow(row, output) {
@@ -71,6 +71,7 @@ export declare class StatusLineRenderer {
71
71
  private pushVoiceWidgetSegment;
72
72
  private pushWorkspaceSegments;
73
73
  private pushModelUsageSegments;
74
+ private pushResetDurationSegments;
74
75
  private modelUsageHasWarning;
75
76
  private modelUsageResetLength;
76
77
  private pushSegment;
@@ -501,8 +501,20 @@ export class StatusLineRenderer {
501
501
  if (warningStart !== undefined) {
502
502
  this.pushSegment(segments, warningStart, APP_ICONS.alert.length, this.host.theme.colors.warning);
503
503
  }
504
- const resetLength = this.modelUsageResetLength(modelUsageLabel, resetStart - labelStart);
505
- this.pushSegment(segments, resetStart, resetLength, this.host.theme.colors.muted);
504
+ const localResetStart = resetStart - labelStart;
505
+ const resetLength = this.modelUsageResetLength(modelUsageLabel, localResetStart);
506
+ const resetText = modelUsageLabel.slice(localResetStart, localResetStart + resetLength);
507
+ this.pushResetDurationSegments(segments, resetStart, resetText);
508
+ }
509
+ }
510
+ pushResetDurationSegments(segments, resetStart, resetText) {
511
+ const colors = this.host.theme.colors;
512
+ const muted = colors.muted;
513
+ const bright = colors.statusForeground;
514
+ for (const match of resetText.matchAll(/\d+|\D+/gu)) {
515
+ const text = match[0];
516
+ const localStart = match.index ?? 0;
517
+ this.pushSegment(segments, resetStart + localStart, text.length, /\d/.test(text) ? bright : muted);
506
518
  }
507
519
  }
508
520
  modelUsageHasWarning(modelUsageLabel, localStart) {
@@ -226,7 +226,12 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
226
226
  const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
227
227
  const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
228
228
  const initialThinkingLevel = resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config);
229
- const services = reusableServices && sameRuntimeServiceTarget(reusableServices, cwd, agentDir)
229
+ // Only reuse services for the initial session. Session replacements
230
+ // (switchSession/newSession/fork) must get fresh services so extensions
231
+ // are re-loaded with a fresh pi — otherwise handlers capture the old,
232
+ // invalidated pi and throw stale-ctx errors on the next session_start.
233
+ const isInitialSession = !sessionStartEvent || sessionStartEvent.reason === "startup";
234
+ const services = isInitialSession && reusableServices && sameRuntimeServiceTarget(reusableServices, cwd, agentDir)
230
235
  ? reusableServices
231
236
  : await createPixRuntimeServices({
232
237
  cwd,
@@ -266,7 +271,12 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
266
271
  return {
267
272
  ...created,
268
273
  services,
269
- diagnostics: services.diagnostics,
274
+ // Snapshot diagnostics per-runtime. When services are reused across
275
+ // tabs (reuseServicesFrom), services.diagnostics is a shared array —
276
+ // giving each runtime its own copy prevents one tab's creation
277
+ // errors (model-not-found, extension flags, provider registration)
278
+ // from leaking into another tab's diagnostics surface.
279
+ diagnostics: [...(services.diagnostics ?? [])],
270
280
  };
271
281
  };
272
282
  return await createAgentSessionRuntime(createRuntime, {
@@ -992,6 +992,8 @@ function selectedConversationLineText(rendered, text, startColumn, endColumn) {
992
992
  const selectsWholeLine = startColumn <= 1 && endColumn >= text.length + 1;
993
993
  if (selectsWholeLine && rendered?.copyText !== undefined)
994
994
  return rendered.copyText;
995
+ if (rendered?.copyText !== undefined)
996
+ return sliceByDisplayColumns(rendered.copyText, startColumn, endColumn).trimEnd();
995
997
  return sliceByDisplayColumns(text, startColumn, endColumn).trimEnd();
996
998
  }
997
999
  function orderedConversationSelection(anchor, current) {
@@ -41,6 +41,13 @@ export type AppSessionEventControllerHost = {
41
41
  setSessionActivity(activity: SessionActivity): void;
42
42
  updateQueuedMessageStatus(): void;
43
43
  flushAutoUserMessages(): void;
44
+ /**
45
+ * Emit a signal onto the active runtime's extension event bus. The SDK
46
+ * strips retry/streaming state from the events it forwards to extensions,
47
+ * so this is how the renderer informs extensions (terminal-bell, todo)
48
+ * about lifecycle state such as an auto-retry being in progress.
49
+ */
50
+ emitExtensionEvent(channel: string, data: unknown): void;
44
51
  prepareWorkspaceMutation(toolName: string, args: unknown): WorkspaceMutationPreparation | undefined;
45
52
  recordWorkspaceMutationForUserEntry(entryId: string, mutation: WorkspaceMutation): void;
46
53
  workspaceMutationFromToolExecution(input: {
@@ -4,8 +4,12 @@ import { customMessageEntry, loadSessionHistoryEntries, loadSessionHistoryEntrie
4
4
  import { sessionHistoryDisplayMessages, sessionHistoryDisplayMessagesFromEntries, sessionHistoryFullBranchEntries } from "./pix-system-message.js";
5
5
  import { THINKING_TOOL_NAME } from "../constants.js";
6
6
  import { isRecord } from "../guards.js";
7
- const DCP_MESSAGE_REFERENCE_PREFIX = "[dcp-id]: # (m";
8
- const DCP_BLOCK_REFERENCE_PREFIX = "[dcp-block-id]: # (b";
7
+ /**
8
+ * Event bus channel the renderer emits to inform extensions that the session is
9
+ * in an auto-retry cycle. Payload: `{ active: boolean }`. Extensions cannot see
10
+ * retry state via the SDK's extension events, so the renderer relays it here.
11
+ */
12
+ const RETRY_ACTIVE_EVENT = "pix:retry-active";
9
13
  const MAX_HISTORY_WINDOW_ENTRIES = 360;
10
14
  const HISTORY_WINDOW_TARGET_ENTRIES = 300;
11
15
  const HISTORY_WINDOW_SHIFT_ENTRIES = 50;
@@ -265,11 +269,13 @@ export class AppSessionEventController {
265
269
  case "auto_retry_start":
266
270
  this.host.setSessionActivity("running");
267
271
  this.host.setStatus(`retry ${event.attempt}/${event.maxAttempts}`);
272
+ this.host.emitExtensionEvent(RETRY_ACTIVE_EVENT, { active: true });
268
273
  break;
269
274
  case "auto_retry_end":
270
275
  this.host.setSessionActivity(this.host.runtime()?.session.isStreaming ? "running" : "idle");
271
276
  this.host.restoreSessionStatus();
272
277
  this.host.flushAutoUserMessages();
278
+ this.host.emitExtensionEvent(RETRY_ACTIVE_EVENT, { active: false });
273
279
  this.host.showToast(event.success ? "Retry succeeded" : `Retry failed: ${event.finalError}`, event.success ? "success" : "error");
274
280
  break;
275
281
  default:
@@ -1021,23 +1027,14 @@ function shouldDropAssistantStreamLine(line, hasVisibleText) {
1021
1027
  function shouldHoldAssistantStreamTail(text, hasVisibleText) {
1022
1028
  if (text.trim().length === 0)
1023
1029
  return !hasVisibleText;
1024
- return isPotentialDcpMetadataLine(text);
1030
+ return false;
1025
1031
  }
1026
1032
  function shouldHoldAssistantStreamWhitespaceTail(text, hasVisibleText) {
1027
1033
  return hasVisibleText && text.trim().length === 0;
1028
1034
  }
1029
1035
  function isHiddenMarkdownMetadataLine(line) {
1030
- return isMarkdownReferenceDefinition(line) || isPotentialDcpMetadataLine(line);
1036
+ return isMarkdownReferenceDefinition(line);
1031
1037
  }
1032
1038
  function isMarkdownReferenceDefinition(line) {
1033
1039
  return /^ {0,3}\[[^\]\n]+\]:[ \t]*\S.*$/u.test(line);
1034
1040
  }
1035
- function isPotentialDcpMetadataLine(line) {
1036
- const content = line.replace(/^ {0,3}/u, "");
1037
- if (content.length === 0)
1038
- return false;
1039
- return isPotentialDcpReference(content, DCP_MESSAGE_REFERENCE_PREFIX) || isPotentialDcpReference(content, DCP_BLOCK_REFERENCE_PREFIX);
1040
- }
1041
- function isPotentialDcpReference(content, markerPrefix) {
1042
- return markerPrefix.startsWith(content) || (content.startsWith(markerPrefix) && /^\d*\)?$/u.test(content.slice(markerPrefix.length)));
1043
- }
@@ -138,6 +138,7 @@ export class AppTerminalController {
138
138
  timer.unref();
139
139
  }
140
140
  onResize = () => {
141
+ this.host.resetRenderOutputBuffer();
141
142
  this.host.render();
142
143
  };
143
144
  onInputData = (chunk) => {
@@ -1,20 +1,22 @@
1
1
  export declare const DISABLE_TERMINAL_OUTPUT_BUFFER_ENV = "PIX_DISABLE_TERMINAL_OUTPUT_BUFFER";
2
2
  export declare const TERMINAL_OUTPUT_BUFFER_ENV = "PIX_TERMINAL_OUTPUT_BUFFER";
3
- declare const FRAME_REGIONS: readonly ["tabs", "conversation", "inputStatus"];
4
- export type TerminalOutputFrameRegion = typeof FRAME_REGIONS[number];
5
- export type TerminalOutputRegion = TerminalOutputFrameRegion | "statusLine";
6
- export type TerminalOutputFrame = Partial<Record<TerminalOutputFrameRegion, string>>;
3
+ export type TerminalOutputFrameRow = {
4
+ row: number;
5
+ output: string;
6
+ };
7
+ export type TerminalOutputFrame = readonly TerminalOutputFrameRow[];
8
+ export type TerminalOutputRegion = "statusLine";
7
9
  export type TerminalOutputBufferOptions = {
8
10
  enabled?: boolean;
9
11
  env?: Record<string, string | undefined>;
10
12
  };
11
13
  export declare class TerminalOutputBuffer {
12
14
  private readonly enabled;
13
- private readonly previousByRegion;
15
+ private readonly previousByRow;
16
+ private previousStatusLine;
14
17
  constructor(options?: TerminalOutputBufferOptions);
15
18
  diffFrame(frame: TerminalOutputFrame): string;
16
19
  diff(region: TerminalOutputRegion, output: string): string;
17
20
  reset(): void;
18
21
  }
19
22
  export declare function terminalOutputBufferDisabled(env?: Record<string, string | undefined>): boolean;
20
- export {};
@@ -1,41 +1,49 @@
1
1
  export const DISABLE_TERMINAL_OUTPUT_BUFFER_ENV = "PIX_DISABLE_TERMINAL_OUTPUT_BUFFER";
2
2
  export const TERMINAL_OUTPUT_BUFFER_ENV = "PIX_TERMINAL_OUTPUT_BUFFER";
3
- const FRAME_REGIONS = ["tabs", "conversation", "inputStatus"];
3
+ const ANSI_RESET = "\x1b[0m";
4
+ const CLEAR_LINE_PREFIX = (row) => `\x1b[${row};1H${ANSI_RESET}\x1b[2K`;
4
5
  export class TerminalOutputBuffer {
5
6
  enabled;
6
- previousByRegion = new Map();
7
+ previousByRow = new Map();
8
+ previousStatusLine;
7
9
  constructor(options = {}) {
8
10
  this.enabled = options.enabled ?? !terminalOutputBufferDisabled(options.env ?? process.env);
9
11
  }
10
12
  diffFrame(frame) {
11
- const outputByRegion = {
12
- tabs: frame.tabs ?? "",
13
- conversation: frame.conversation ?? "",
14
- inputStatus: frame.inputStatus ?? "",
15
- };
16
13
  if (!this.enabled)
17
- return FRAME_REGIONS.map((region) => outputByRegion[region]).join("");
14
+ return frame.map((entry) => entry.output).join("");
18
15
  const chunks = [];
19
- for (const region of FRAME_REGIONS) {
20
- const output = outputByRegion[region];
21
- if (this.previousByRegion.get(region) === output)
16
+ const seenRows = new Set();
17
+ for (const { row, output } of frame) {
18
+ seenRows.add(row);
19
+ if (this.previousByRow.get(row) === output)
22
20
  continue;
23
- this.previousByRegion.set(region, output);
21
+ this.previousByRow.set(row, output);
24
22
  if (output.length > 0)
25
23
  chunks.push(output);
26
24
  }
25
+ for (const row of this.previousByRow.keys()) {
26
+ if (seenRows.has(row))
27
+ continue;
28
+ this.previousByRow.delete(row);
29
+ chunks.push(CLEAR_LINE_PREFIX(row));
30
+ }
27
31
  return chunks.join("");
28
32
  }
29
33
  diff(region, output) {
30
34
  if (!this.enabled)
31
35
  return output;
32
- if (this.previousByRegion.get(region) === output)
33
- return "";
34
- this.previousByRegion.set(region, output);
36
+ if (region === "statusLine") {
37
+ if (this.previousStatusLine === output)
38
+ return "";
39
+ this.previousStatusLine = output;
40
+ return output;
41
+ }
35
42
  return output;
36
43
  }
37
44
  reset() {
38
- this.previousByRegion.clear();
45
+ this.previousByRow.clear();
46
+ this.previousStatusLine = undefined;
39
47
  }
40
48
  }
41
49
  export function terminalOutputBufferDisabled(env = process.env) {