@cydm/pie 2.7.14 → 2.7.15

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/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  migrateConfigFromAgentDir,
30
30
  setYoloGrantListener,
31
31
  truncateHeadTail
32
- } from "./chunks/chunk-FOE2AW4L.js";
32
+ } from "./chunks/chunk-CLEPSTWH.js";
33
33
  import {
34
34
  AGENTS_CONTEXT_FILE_NAME,
35
35
  AgentSessionController,
@@ -91,8 +91,8 @@ import {
91
91
  shouldPreserveExecutionStateForUserText,
92
92
  supersedeExecutionState,
93
93
  verifyPreparedCompactionCommit
94
- } from "./chunks/chunk-VPTLDLI6.js";
95
- import "./chunks/chunk-5YU6MU2N.js";
94
+ } from "./chunks/chunk-C5UTDL3S.js";
95
+ import "./chunks/chunk-TDHVB2FH.js";
96
96
  import {
97
97
  Deref,
98
98
  Errors,
@@ -115,13 +115,7 @@ import {
115
115
  sortToolModelCandidatesByCapability,
116
116
  streamSimple,
117
117
  type_exports
118
- } from "./chunks/chunk-N4252GH3.js";
119
- import {
120
- readImageFormat
121
- } from "./chunks/chunk-F7EBZTZO.js";
122
- import {
123
- resolveCliProjectRoot
124
- } from "./chunks/chunk-WLFOBECN.js";
118
+ } from "./chunks/chunk-NM2JLPJ6.js";
125
119
  import {
126
120
  IsArray,
127
121
  IsBigInt,
@@ -136,6 +130,12 @@ import {
136
130
  TypeBoxError,
137
131
  TypeSystemPolicy
138
132
  } from "./chunks/chunk-XXGYUYXI.js";
133
+ import {
134
+ readImageFormat
135
+ } from "./chunks/chunk-F7EBZTZO.js";
136
+ import {
137
+ resolveCliProjectRoot
138
+ } from "./chunks/chunk-WLFOBECN.js";
139
139
  import {
140
140
  __commonJS,
141
141
  __require,
@@ -34276,7 +34276,7 @@ var require_taggerscript = __commonJS({
34276
34276
  begin: /%[_a-zA-Z0-9:]*/,
34277
34277
  end: "%"
34278
34278
  };
34279
- const ESCAPE_SEQUENCE = {
34279
+ const ESCAPE_SEQUENCE2 = {
34280
34280
  className: "symbol",
34281
34281
  begin: /\\./
34282
34282
  };
@@ -34286,7 +34286,7 @@ var require_taggerscript = __commonJS({
34286
34286
  COMMENT,
34287
34287
  FUNCTION,
34288
34288
  VARIABLE,
34289
- ESCAPE_SEQUENCE
34289
+ ESCAPE_SEQUENCE2
34290
34290
  ]
34291
34291
  };
34292
34292
  }
@@ -57148,9 +57148,6 @@ function parseExtensionFields(raw) {
57148
57148
  return requested;
57149
57149
  }
57150
57150
 
57151
- // src/interactive-mode.ts
57152
- import * as os8 from "os";
57153
-
57154
57151
  // ../../packages/tui/src/fuzzy.ts
57155
57152
  function fuzzyMatch(query, text) {
57156
57153
  const queryLower = query.toLowerCase();
@@ -57369,6 +57366,41 @@ function graphemeWidth(segment) {
57369
57366
  }
57370
57367
  return width;
57371
57368
  }
57369
+ var ESCAPE_SEQUENCE = /\x1b\[[0-?]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[PX^_][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[ -\/]*[0-~]?/g;
57370
+ var CONTROL_CHARS = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
57371
+ var HAS_CONTROL_CHARS = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/;
57372
+ var SANITIZE_SEQUENCE = new RegExp(`${ESCAPE_SEQUENCE.source}|${CONTROL_CHARS.source}`, "g");
57373
+ function stripEscapeSequences(text) {
57374
+ return text.replace(ESCAPE_SEQUENCE, "");
57375
+ }
57376
+ var SGR_SEQUENCE = /^\x1b\[[0-9;:]*m$/;
57377
+ var OSC8_SEQUENCE = /^\x1b\]8;[^;\x07\x1b]*;[^\x07\x1b]*(?:\x07|\x1b\\)$/;
57378
+ var KITTY_IMAGE_SEQUENCE = /^\x1b_G[^\x07\x1b]*(?:\x07|\x1b\\)$/;
57379
+ var ITERM2_IMAGE_SEQUENCE = /^\x1b\]1337;[^\x07\x1b]*(?:\x07|\x1b\\)$/;
57380
+ var IMAGE_CURSOR_UP = /^\x1b\[\d*A$/;
57381
+ var CURSOR_MARKER_SEQUENCE = "\x1B_pi:c\x07";
57382
+ function sanitizeTerminalText(text, options = {}) {
57383
+ const needsLayout = options.normalizeLayout === true && /[\r\n\t]/.test(text);
57384
+ if (!HAS_CONTROL_CHARS.test(text) && !needsLayout) return text;
57385
+ const normalize2 = (segment) => options.normalizeLayout ? segment.replace(/\r\n?/g, " ").replace(/\n/g, " ").replace(/\t/g, " ") : segment;
57386
+ let out = "";
57387
+ let cursor = 0;
57388
+ SANITIZE_SEQUENCE.lastIndex = 0;
57389
+ for (let match = SANITIZE_SEQUENCE.exec(text); match; match = SANITIZE_SEQUENCE.exec(text)) {
57390
+ out += normalize2(text.slice(cursor, match.index));
57391
+ const sequence = match[0];
57392
+ if (SGR_SEQUENCE.test(sequence) || OSC8_SEQUENCE.test(sequence)) {
57393
+ out += sequence;
57394
+ } else if (options.preserveCursorMarker === true && sequence === CURSOR_MARKER_SEQUENCE) {
57395
+ out += sequence;
57396
+ } else if (options.preserveImages && (KITTY_IMAGE_SEQUENCE.test(sequence) || ITERM2_IMAGE_SEQUENCE.test(sequence) || IMAGE_CURSOR_UP.test(sequence))) {
57397
+ out += sequence;
57398
+ }
57399
+ cursor = match.index + sequence.length;
57400
+ }
57401
+ out += normalize2(text.slice(cursor));
57402
+ return out;
57403
+ }
57372
57404
  function visibleWidth(str2) {
57373
57405
  if (str2.length === 0) {
57374
57406
  return 0;
@@ -57393,9 +57425,7 @@ function visibleWidth(str2) {
57393
57425
  clean = clean.replace(/\t/g, " ");
57394
57426
  }
57395
57427
  if (clean.includes("\x1B")) {
57396
- clean = clean.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
57397
- clean = clean.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
57398
- clean = clean.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
57428
+ clean = stripEscapeSequences(clean);
57399
57429
  }
57400
57430
  let width = 0;
57401
57431
  for (const { segment } of segmenter.segment(clean)) {
@@ -58843,7 +58873,7 @@ var Container = class {
58843
58873
  function isFocusable(component) {
58844
58874
  return component !== null && "focused" in component;
58845
58875
  }
58846
- var CURSOR_MARKER = "\x1B_pi:c\x07";
58876
+ var CURSOR_MARKER = CURSOR_MARKER_SEQUENCE;
58847
58877
 
58848
58878
  // ../../packages/tui/src/focus-manager.ts
58849
58879
  var FocusManager = class {
@@ -59178,6 +59208,50 @@ function findChangedRange(previousLines, newLines) {
59178
59208
  return { firstChanged, lastChanged, appendedLines, appendStart };
59179
59209
  }
59180
59210
 
59211
+ // ../../packages/tui/src/render-line-cache.ts
59212
+ function prepareRenderLines(rawLines, previous, width) {
59213
+ let lines = rawLines;
59214
+ const reusedFinal = new Uint8Array(rawLines.length);
59215
+ let reusedLineCount = 0;
59216
+ const canReuse = previous !== null && previous.width === width;
59217
+ for (let index = 0; index < rawLines.length; index++) {
59218
+ const rawLine = rawLines[index];
59219
+ if (canReuse && index < previous.rawLines.length && index < previous.processedLines.length && !rawLine.includes(CURSOR_MARKER) && rawLine === previous.rawLines[index]) {
59220
+ reusedFinal[index] = 1;
59221
+ reusedLineCount++;
59222
+ const processed = previous.processedLines[index];
59223
+ if (processed !== rawLine) {
59224
+ if (lines === rawLines) lines = rawLines.slice();
59225
+ lines[index] = processed;
59226
+ }
59227
+ continue;
59228
+ }
59229
+ const image = isImageLine(rawLine);
59230
+ const clean = sanitizeTerminalText(rawLine, {
59231
+ preserveImages: image,
59232
+ normalizeLayout: true,
59233
+ preserveCursorMarker: true
59234
+ });
59235
+ if (clean !== rawLine) {
59236
+ if (lines === rawLines) lines = rawLines.slice();
59237
+ lines[index] = clean;
59238
+ }
59239
+ }
59240
+ return { lines, reusedFinal, reusedLineCount };
59241
+ }
59242
+ function truncatePreparedLines(prepared, width, onOverflow) {
59243
+ let lines = prepared.lines;
59244
+ for (let index = 0; index < lines.length; index++) {
59245
+ if (prepared.reusedFinal[index]) continue;
59246
+ const line = lines[index];
59247
+ if (isImageLine(line) || visibleWidth(line) <= width) continue;
59248
+ onOverflow(index, line, lines);
59249
+ if (lines === prepared.lines) lines = prepared.lines.slice();
59250
+ lines[index] = truncateToWidth(line, width);
59251
+ }
59252
+ return lines;
59253
+ }
59254
+
59181
59255
  // ../../packages/tui/src/terminal-protocol.ts
59182
59256
  var TerminalProtocol = class {
59183
59257
  constructor(terminal, onCellSizeChanged) {
@@ -59238,6 +59312,8 @@ function lineForWrite(line) {
59238
59312
  var TUI = class extends Container {
59239
59313
  terminal;
59240
59314
  previousLines = [];
59315
+ /** Raw lines paired with previousLines after a successful frame commit. */
59316
+ previousRawLines = null;
59241
59317
  previousWidth = 0;
59242
59318
  focusManager;
59243
59319
  inputRouter = new InputRouter();
@@ -59245,6 +59321,29 @@ var TUI = class extends Container {
59245
59321
  terminalProtocol;
59246
59322
  /** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
59247
59323
  onDebug;
59324
+ reportOverflowed = false;
59325
+ /** Record the first over-wide render of this process (evidence for the
59326
+ * component bug), then stay quiet. The line is truncated, not fatal. */
59327
+ reportOverflowOnce(lineIndex, line, width, lines) {
59328
+ if (this.reportOverflowed) return;
59329
+ this.reportOverflowed = true;
59330
+ if (process.env.PIE_CRASH_LOG_DISABLED === "1") return;
59331
+ try {
59332
+ const crashLogPath = path9.join(os3.homedir(), ".pie", "pie-crash.log");
59333
+ const crashData = [
59334
+ `Over-wide render at ${(/* @__PURE__ */ new Date()).toISOString()} (truncated, not fatal)`,
59335
+ `Terminal width: ${width}`,
59336
+ `Line ${lineIndex} visible width: ${visibleWidth(line)}`,
59337
+ "",
59338
+ "=== All rendered lines (control sequences sanitized) ===",
59339
+ ...lines.map((candidate, index) => `[${index}] (w=${visibleWidth(candidate)}) ${candidate}`),
59340
+ ""
59341
+ ].join("\n");
59342
+ fs10.mkdirSync(path9.dirname(crashLogPath), { recursive: true });
59343
+ fs10.writeFileSync(crashLogPath, crashData);
59344
+ } catch {
59345
+ }
59346
+ }
59248
59347
  renderRequested = false;
59249
59348
  cursorRow = 0;
59250
59349
  // Logical cursor row (end of rendered content)
@@ -59356,6 +59455,7 @@ var TUI = class extends Container {
59356
59455
  requestRender(force = false) {
59357
59456
  if (force) {
59358
59457
  this.previousLines = [];
59458
+ this.previousRawLines = null;
59359
59459
  this.previousWidth = -1;
59360
59460
  this.cursorRow = 0;
59361
59461
  this.hardwareCursorRow = 0;
@@ -59409,11 +59509,30 @@ var TUI = class extends Container {
59409
59509
  if (this.overlayManager.hasAny) {
59410
59510
  newLines = this.overlayManager.composite(newLines, width, height, this.maxLinesRendered);
59411
59511
  }
59412
- const cursorPos = extractCursorPosition(newLines, height);
59512
+ const rawLines = newLines;
59513
+ const previousFrame = this.previousRawLines === null ? null : {
59514
+ rawLines: this.previousRawLines,
59515
+ processedLines: this.previousLines,
59516
+ width: this.previousWidth
59517
+ };
59518
+ const prepared = prepareRenderLines(rawLines, previousFrame, width);
59519
+ newLines = prepared.lines;
59520
+ let cursorPos = extractCursorPosition(newLines, height);
59413
59521
  if (cursorPos) {
59414
59522
  newLines = newLines.slice();
59415
59523
  newLines[cursorPos.row] = cursorPos.strippedLine;
59416
59524
  }
59525
+ prepared.lines = newLines;
59526
+ newLines = truncatePreparedLines(prepared, width, (lineIndex, line, lines) => {
59527
+ this.reportOverflowOnce(lineIndex, line, width, lines);
59528
+ });
59529
+ if (cursorPos) {
59530
+ const emitted = newLines[cursorPos.row];
59531
+ if (emitted !== void 0) {
59532
+ const maxCol = Math.max(0, Math.min(visibleWidth(emitted), width - 1));
59533
+ if (cursorPos.col > maxCol) cursorPos = { ...cursorPos, col: maxCol };
59534
+ }
59535
+ }
59417
59536
  const widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;
59418
59537
  const fullRender = (clear) => {
59419
59538
  this.fullRedrawCount += 1;
@@ -59435,6 +59554,7 @@ var TUI = class extends Container {
59435
59554
  this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
59436
59555
  this.positionHardwareCursor(cursorPos, newLines.length);
59437
59556
  this.previousLines = newLines;
59557
+ this.previousRawLines = rawLines;
59438
59558
  this.previousWidth = width;
59439
59559
  };
59440
59560
  const debugRedraw = process.env.PIE_DEBUG_REDRAW === "1";
@@ -59464,6 +59584,9 @@ var TUI = class extends Container {
59464
59584
  const { firstChanged, lastChanged, appendStart } = findChangedRange(this.previousLines, newLines);
59465
59585
  if (firstChanged === -1) {
59466
59586
  this.positionHardwareCursor(cursorPos, newLines.length);
59587
+ this.previousLines = newLines;
59588
+ this.previousRawLines = rawLines;
59589
+ this.previousWidth = width;
59467
59590
  this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
59468
59591
  return;
59469
59592
  }
@@ -59498,6 +59621,7 @@ var TUI = class extends Container {
59498
59621
  }
59499
59622
  this.positionHardwareCursor(cursorPos, newLines.length);
59500
59623
  this.previousLines = newLines;
59624
+ this.previousRawLines = rawLines;
59501
59625
  this.previousWidth = width;
59502
59626
  this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
59503
59627
  return;
@@ -59534,33 +59658,7 @@ var TUI = class extends Container {
59534
59658
  for (let i = firstChanged; i <= renderEnd; i++) {
59535
59659
  if (i > firstChanged) buffer += "\r\n";
59536
59660
  buffer += "\x1B[2K";
59537
- const line = newLines[i];
59538
- const isImage = isImageLine(line);
59539
- if (!isImage && visibleWidth(line) > width) {
59540
- const crashLogPath = path9.join(os3.homedir(), ".pie", "pie-crash.log");
59541
- const crashData = [
59542
- `Crash at ${(/* @__PURE__ */ new Date()).toISOString()}`,
59543
- `Terminal width: ${width}`,
59544
- `Line ${i} visible width: ${visibleWidth(line)}`,
59545
- "",
59546
- "=== All rendered lines ===",
59547
- ...newLines.map((l, idx) => `[${idx}] (w=${visibleWidth(l)}) ${l}`),
59548
- ""
59549
- ].join("\n");
59550
- fs10.mkdirSync(path9.dirname(crashLogPath), { recursive: true });
59551
- fs10.writeFileSync(crashLogPath, crashData);
59552
- this.stop();
59553
- const errorMsg = [
59554
- `Rendered line ${i} exceeds terminal width (${visibleWidth(line)} > ${width}).`,
59555
- "",
59556
- "This is likely caused by a custom TUI component not truncating its output.",
59557
- "Use visibleWidth() to measure and truncateToWidth() to truncate lines.",
59558
- "",
59559
- `Debug log written to: ${crashLogPath}`
59560
- ].join("\n");
59561
- throw new Error(errorMsg);
59562
- }
59563
- buffer += lineForWrite(line);
59661
+ buffer += lineForWrite(newLines[i]);
59564
59662
  }
59565
59663
  let finalCursorRow = renderEnd;
59566
59664
  if (this.previousLines.length > newLines.length) {
@@ -59611,6 +59709,7 @@ var TUI = class extends Container {
59611
59709
  this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
59612
59710
  this.positionHardwareCursor(cursorPos, newLines.length);
59613
59711
  this.previousLines = newLines;
59712
+ this.previousRawLines = rawLines;
59614
59713
  this.previousWidth = width;
59615
59714
  }
59616
59715
  /**
@@ -66491,6 +66590,11 @@ var theme = new Proxy({}, {
66491
66590
  });
66492
66591
  function setGlobalTheme(t) {
66493
66592
  globalThis[THEME_KEY] = t;
66593
+ themeEpoch++;
66594
+ }
66595
+ var themeEpoch = 1;
66596
+ function getThemeEpoch() {
66597
+ return themeEpoch;
66494
66598
  }
66495
66599
  var currentThemeName;
66496
66600
  var themeWatcher;
@@ -67037,9 +67141,6 @@ function validateSkillName(name) {
67037
67141
  return { valid: true };
67038
67142
  }
67039
67143
 
67040
- // src/interactive-mode.ts
67041
- import * as path30 from "path";
67042
-
67043
67144
  // src/capabilities/read-file-understanding.ts
67044
67145
  import * as path21 from "node:path";
67045
67146
  import * as fs22 from "node:fs/promises";
@@ -70717,20 +70818,27 @@ var ExecutionStateManager = class {
70717
70818
  // src/session-trace.ts
70718
70819
  import * as fs23 from "node:fs";
70719
70820
  import * as path23 from "node:path";
70720
- function saveTraceFileBestEffort(tracePath, content) {
70721
- const tempPath = `${tracePath}.${process.pid}.${Date.now()}.tmp`;
70821
+ var traceTempSequence = 0;
70822
+ async function saveTraceFileBestEffort(tracePath, content) {
70823
+ const tempPath = `${tracePath}.${process.pid}.${Date.now()}.${traceTempSequence++}.tmp`;
70722
70824
  try {
70723
- fs23.writeFileSync(tempPath, content, "utf8");
70825
+ await fs23.promises.writeFile(tempPath, content, "utf8");
70724
70826
  try {
70725
- fs23.renameSync(tempPath, tracePath);
70827
+ await fs23.promises.rename(tempPath, tracePath);
70726
70828
  return;
70727
70829
  } catch {
70830
+ const stagedPath = `${tracePath}.${process.pid}.${Date.now()}.staged`;
70728
70831
  try {
70729
- fs23.copyFileSync(tempPath, tracePath);
70832
+ await fs23.promises.copyFile(tempPath, stagedPath);
70833
+ await fs23.promises.rename(stagedPath, tracePath);
70730
70834
  } catch {
70835
+ try {
70836
+ await fs23.promises.unlink(stagedPath);
70837
+ } catch {
70838
+ }
70731
70839
  } finally {
70732
70840
  try {
70733
- fs23.unlinkSync(tempPath);
70841
+ await fs23.promises.unlink(tempPath);
70734
70842
  } catch {
70735
70843
  }
70736
70844
  }
@@ -70751,10 +70859,11 @@ var SessionTraceWriter = class extends SessionTraceSnapshotWriter {
70751
70859
  return null;
70752
70860
  }
70753
70861
  },
70754
- saveTrace: (trace) => {
70755
- const tracePath = path23.join(sessionsDir, `${trace.sessionId}.trace.json`);
70756
- saveTraceFileBestEffort(tracePath, JSON.stringify(trace, null, 2));
70757
- }
70862
+ saveTraceJson: (json, sessionId) => {
70863
+ const tracePath = path23.join(sessionsDir, `${sessionId}.trace.json`);
70864
+ return saveTraceFileBestEffort(tracePath, json);
70865
+ },
70866
+ saveTrace: () => void 0
70758
70867
  });
70759
70868
  this.sessionsDir = sessionsDir;
70760
70869
  this.sessionManager = sessionManager;
@@ -72163,10 +72272,30 @@ var AssistantMessageComponent = class extends Container {
72163
72272
  calmMode = false;
72164
72273
  markdownTheme;
72165
72274
  lastMessage;
72275
+ streaming;
72276
+ highlightCache = /* @__PURE__ */ new Map();
72277
+ usedHighlightKeys = /* @__PURE__ */ new Set();
72278
+ pruneHighlightCache = false;
72166
72279
  constructor(message, hideThinkingBlock = false, markdownTheme = getMarkdownTheme()) {
72167
72280
  super();
72168
72281
  this.hideThinkingBlock = hideThinkingBlock;
72169
- this.markdownTheme = markdownTheme;
72282
+ this.streaming = !message;
72283
+ const highlightCode2 = markdownTheme.highlightCode;
72284
+ this.markdownTheme = highlightCode2 ? {
72285
+ ...markdownTheme,
72286
+ // Cache keys carry the theme epoch: highlighted lines embed theme
72287
+ // colors, so a theme swap must miss instead of serving stale colors,
72288
+ // even when a holder (calm-mode held component) skipped invalidate().
72289
+ highlightCode: (code, lang) => {
72290
+ const key = `${getThemeEpoch()}\0${lang ?? ""}\0${code}`;
72291
+ this.usedHighlightKeys.add(key);
72292
+ const cached = this.highlightCache.get(key);
72293
+ if (cached) return cached;
72294
+ const lines = highlightCode2(code, lang);
72295
+ this.highlightCache.set(key, lines);
72296
+ return lines;
72297
+ }
72298
+ } : markdownTheme;
72170
72299
  this.contentContainer = new Container();
72171
72300
  this.addChild(this.contentContainer);
72172
72301
  if (message) {
@@ -72174,6 +72303,8 @@ var AssistantMessageComponent = class extends Container {
72174
72303
  }
72175
72304
  }
72176
72305
  invalidate() {
72306
+ this.highlightCache.clear();
72307
+ this.usedHighlightKeys.clear();
72177
72308
  super.invalidate();
72178
72309
  if (this.lastMessage) {
72179
72310
  this.updateContent(this.lastMessage);
@@ -72196,8 +72327,35 @@ var AssistantMessageComponent = class extends Container {
72196
72327
  isCalmMode() {
72197
72328
  return this.calmMode;
72198
72329
  }
72330
+ /** The final frame may reuse streamed highlights; afterwards only the
72331
+ * Markdown line cache is needed, so release the duplicate code strings. */
72332
+ finalizeStreaming() {
72333
+ this.streaming = false;
72334
+ }
72335
+ render(width) {
72336
+ this.usedHighlightKeys.clear();
72337
+ try {
72338
+ const lines = super.render(width);
72339
+ if (!this.streaming) {
72340
+ this.highlightCache.clear();
72341
+ } else if (this.pruneHighlightCache) {
72342
+ for (const key of this.highlightCache.keys()) {
72343
+ if (!this.usedHighlightKeys.has(key)) this.highlightCache.delete(key);
72344
+ }
72345
+ }
72346
+ this.pruneHighlightCache = false;
72347
+ return lines;
72348
+ } catch (error) {
72349
+ this.highlightCache.clear();
72350
+ this.pruneHighlightCache = false;
72351
+ throw error;
72352
+ } finally {
72353
+ this.usedHighlightKeys.clear();
72354
+ }
72355
+ }
72199
72356
  updateContent(message) {
72200
72357
  this.lastMessage = message;
72358
+ this.pruneHighlightCache = true;
72201
72359
  this.contentContainer.clear();
72202
72360
  const hasTextContent = message.content.some((c) => c.type === "text" && c.text.trim());
72203
72361
  const hasThinkingContent = message.content.some((c) => c.type === "thinking" && c.thinking.trim());
@@ -73462,6 +73620,7 @@ function renderChatHistory(host, messages) {
73462
73620
  content: contentBlocks,
73463
73621
  ...stopReason ? { stopReason, errorMessage: assistantMsg.errorMessage } : {}
73464
73622
  });
73623
+ msgComponent.finalizeStreaming();
73465
73624
  host.chatContainer.addChild(msgComponent);
73466
73625
  }
73467
73626
  if (Array.isArray(assistantMsg.content) && !host.calmMode) {
@@ -74047,11 +74206,21 @@ function shutdownInteractiveHost(host, idleTimeoutMs = 5e3) {
74047
74206
  } finally {
74048
74207
  clearTimeout(timer);
74049
74208
  }
74209
+ try {
74210
+ await host.sessionTrace?.flushAsync();
74211
+ } catch (error) {
74212
+ errors.push(error);
74213
+ }
74050
74214
  try {
74051
74215
  await host.extensionRunner?.dispose("shutdown");
74052
74216
  } catch (error) {
74053
74217
  errors.push(error);
74054
74218
  }
74219
+ try {
74220
+ await host.sessionTrace?.flushAsync();
74221
+ } catch (error) {
74222
+ errors.push(error);
74223
+ }
74055
74224
  if (errors.length) throw new AggregateError(errors, "Interactive shutdown failed.");
74056
74225
  })().then(resolve7, reject);
74057
74226
  return shutdown;
@@ -74269,6 +74438,7 @@ function bindInteractiveRuntimeEvents(host) {
74269
74438
  }
74270
74439
  host.isProcessing = false;
74271
74440
  revealHeldCalmStreamingComponent(host);
74441
+ host.streamingComponent?.finalizeStreaming?.();
74272
74442
  host.streamingComponent = void 0;
74273
74443
  removeCalmToolLines(host.chatContainer, host.pendingTools);
74274
74444
  host.pendingTools.clear();
@@ -74653,6 +74823,7 @@ function handleMessageEnd(host, event) {
74653
74823
  content: assistantDisplayBlocks(message.content)
74654
74824
  });
74655
74825
  }
74826
+ streaming?.finalizeStreaming?.();
74656
74827
  host.streamingMessageHadToolCalls = false;
74657
74828
  host.streamingComponent = void 0;
74658
74829
  host.footer.setState({ lifecycleEvent: formatFooterLifecycleEvent(event) });
@@ -76087,10 +76258,12 @@ function aggregateToolStats(turns) {
76087
76258
  }
76088
76259
  function buildToolStatRows(turns) {
76089
76260
  const stats = aggregateToolStats(turns);
76261
+ const omitted = turns.reduce((sum, turn) => sum + (turn.toolCallsOmitted ?? 0), 0);
76262
+ const partialRow = omitted > 0 ? [{ id: "tools:omitted", label: "tool statistics are partial", estTokens: 0, note: `${omitted} earlier calls omitted` }] : [];
76090
76263
  if (stats.length === 0) {
76091
- return [{ id: "tools:none", label: "no tool calls in recent turns", estTokens: 0 }];
76264
+ return partialRow.length > 0 ? partialRow : [{ id: "tools:none", label: "no tool calls in recent turns", estTokens: 0 }];
76092
76265
  }
76093
- return stats.map((stat2) => ({
76266
+ return [...partialRow, ...stats.map((stat2) => ({
76094
76267
  id: `toolstat:${stat2.name}`,
76095
76268
  label: stat2.name,
76096
76269
  estTokens: 0,
@@ -76102,7 +76275,7 @@ function buildToolStatRows(turns) {
76102
76275
  rowKind: "plain",
76103
76276
  rows: collectCalls(turns, stat2.name)
76104
76277
  }
76105
- }));
76278
+ }))];
76106
76279
  }
76107
76280
  function collectCalls(turns, toolName) {
76108
76281
  const rows = [];
@@ -76145,7 +76318,7 @@ function buildTurnRows(turns) {
76145
76318
  label: `#${turn.turnIndex} ${turn.state}`,
76146
76319
  estTokens: 0,
76147
76320
  valueText: duration,
76148
- note: `${turn.toolCalls.length} tool calls`,
76321
+ note: `${turn.toolCalls.length} tool calls${turn.toolCallsOmitted ? ` (+${turn.toolCallsOmitted} omitted)` : ""}`,
76149
76322
  detail: turn.userText?.slice(0, 80) || void 0,
76150
76323
  marker: turn.state === "failed" ? "\u2716" : void 0,
76151
76324
  drill: {
@@ -76171,6 +76344,11 @@ ${turn.assistantText.slice(0, 2e3)}`);
76171
76344
  if (turn.errorMessage) parts.push(`
76172
76345
  # error
76173
76346
  ${turn.errorMessage}`);
76347
+ if (turn.toolCallsOmitted) {
76348
+ parts.push(`
76349
+ # tool calls
76350
+ (${turn.toolCallsOmitted} earlier calls omitted from the retention window)`);
76351
+ }
76174
76352
  if (turn.toolCalls.length > 0) {
76175
76353
  parts.push(
76176
76354
  `
@@ -76288,10 +76466,14 @@ function buildTraceOverviewText(snapshot, theme2) {
76288
76466
  lines.push(t.fg("dim", " no retries, compactions, or guard events"));
76289
76467
  }
76290
76468
  }
76291
- const stats = aggregateToolStats([...snapshot.recentTurns, ...snapshot.activeTurn ? [snapshot.activeTurn] : []]);
76469
+ const allTurns = [...snapshot.recentTurns, ...snapshot.activeTurn ? [snapshot.activeTurn] : []];
76470
+ const stats = aggregateToolStats(allTurns);
76471
+ const omitted = allTurns.reduce((sum, turn) => sum + (turn.toolCallsOmitted ?? 0), 0);
76292
76472
  if (stats.length > 0) {
76293
76473
  const top = stats.slice(0, 3).map((s) => `${s.name} ${formatDurationMs(s.totalMs)} (${s.calls})`);
76294
- lines.push(t.fg("dim", ` tools: ${top.join(" \xB7 ")}`));
76474
+ lines.push(t.fg("dim", ` tools: ${top.join(" \xB7 ")}${omitted > 0 ? ` (+${omitted} earlier calls omitted)` : ""}`));
76475
+ } else if (omitted > 0) {
76476
+ lines.push(t.fg("dim", ` tools: (+${omitted} earlier calls omitted)`));
76295
76477
  }
76296
76478
  lines.push(t.fg("dim", " /debugs/trace \xB7 Enter to drill, Esc to go back"));
76297
76479
  return lines.join("\n");
@@ -76601,6 +76783,7 @@ function handleInterrupt(host) {
76601
76783
  host.setSubmitLock(false);
76602
76784
  host.isInterrupting = false;
76603
76785
  revealHeldCalmStreamingComponent(host);
76786
+ host.streamingComponent?.finalizeStreaming?.();
76604
76787
  host.streamingComponent = void 0;
76605
76788
  host.footer.setState({ status: "ready" });
76606
76789
  host.safeRequestRender();
@@ -77130,10 +77313,7 @@ function registerBuiltinCommands(host) {
77130
77313
  });
77131
77314
  commandTree.register(["settings", "quit"], {
77132
77315
  description: "Exit Pie",
77133
- handler: () => {
77134
- host.ui.stop();
77135
- process.exit(0);
77136
- }
77316
+ handler: () => host.cleanupAndExit()
77137
77317
  });
77138
77318
  }
77139
77319
 
@@ -79807,7 +79987,7 @@ function setupInteractiveKeybindings(host) {
79807
79987
 
79808
79988
  // src/clipboard/read-clipboard-image.ts
79809
79989
  import { spawn as spawn2 } from "node:child_process";
79810
- import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync18, fchmodSync, mkdtempSync, openSync as openSync4, readFileSync as readFileSync17, rmdirSync, statSync as statSync12, unlinkSync as unlinkSync8, writeFileSync as writeFileSync12 } from "node:fs";
79990
+ import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync18, fchmodSync, mkdtempSync, openSync as openSync4, readFileSync as readFileSync17, rmdirSync, statSync as statSync12, unlinkSync as unlinkSync7, writeFileSync as writeFileSync11 } from "node:fs";
79811
79991
  import { tmpdir } from "node:os";
79812
79992
  import { join as join20 } from "node:path";
79813
79993
  var MAX_IMAGE_BYTES = 50 * 1024 * 1024;
@@ -79898,7 +80078,7 @@ var TempImageResources = class {
79898
80078
  cleanup = () => {
79899
80079
  for (const path34 of this.files) {
79900
80080
  try {
79901
- unlinkSync8(path34);
80081
+ unlinkSync7(path34);
79902
80082
  this.files.delete(path34);
79903
80083
  } catch (err) {
79904
80084
  if (err.code === "ENOENT") this.files.delete(path34);
@@ -80116,7 +80296,7 @@ function writeImageBytes(bytes, declaredMime, mkTempPath) {
80116
80296
  const mimeType = detectMimeFromMagic(bytes) ?? declaredMime;
80117
80297
  const targetPath = mkTempPath(extensionForMime(mimeType));
80118
80298
  try {
80119
- writeFileSync12(targetPath, bytes, { flag: "r+", mode: 384 });
80299
+ writeFileSync11(targetPath, bytes, { flag: "r+", mode: 384 });
80120
80300
  } catch (err) {
80121
80301
  return { ok: false, kind: "failed", message: `Failed to write temp image: ${errorMessage4(err)}` };
80122
80302
  }
@@ -80187,7 +80367,7 @@ function formatBytes2(bytes) {
80187
80367
  }
80188
80368
 
80189
80369
  // src/input/image-paste.ts
80190
- import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync13, rmSync as rmSync3 } from "node:fs";
80370
+ import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync12, rmSync as rmSync3 } from "node:fs";
80191
80371
  import { tmpdir as tmpdir2 } from "node:os";
80192
80372
  import { join as join21 } from "node:path";
80193
80373
  var pastingHosts = /* @__PURE__ */ new WeakSet();
@@ -80216,7 +80396,7 @@ function terminalResultToClipboardResult(result) {
80216
80396
  try {
80217
80397
  dir = mkdtempSync2(join21(tmpdir2(), "pie-osc5522-"));
80218
80398
  const path34 = join21(dir, formatPastedImageName(/* @__PURE__ */ new Date(), ext));
80219
- writeFileSync13(path34, result.bytes);
80399
+ writeFileSync12(path34, result.bytes);
80220
80400
  return {
80221
80401
  ok: true,
80222
80402
  path: path34,
@@ -80990,6 +81170,7 @@ function setupUI(host) {
80990
81170
  contextWindow: host.options.model?.contextWindow ?? 0
80991
81171
  });
80992
81172
  onThemeChange(() => {
81173
+ host.streamingComponent?.invalidate();
80993
81174
  host.ui.invalidate();
80994
81175
  host.ui.requestRender();
80995
81176
  });
@@ -81547,6 +81728,7 @@ async function recoverHardStalledTurn(host, stalledMs) {
81547
81728
  host.isProcessing = false;
81548
81729
  host.setSubmitLock(false);
81549
81730
  revealHeldCalmStreamingComponent(host);
81731
+ host.streamingComponent?.finalizeStreaming?.();
81550
81732
  host.streamingComponent = void 0;
81551
81733
  removeCalmToolLines(host.chatContainer, host.pendingTools);
81552
81734
  host.pendingTools.clear();
@@ -83056,6 +83238,74 @@ async function loadInteractiveExtensions(host) {
83056
83238
  }
83057
83239
  }
83058
83240
 
83241
+ // src/extensions/interactive-runtime-reload.ts
83242
+ import { AsyncLocalStorage } from "async_hooks";
83243
+ import * as os8 from "os";
83244
+ import * as path30 from "path";
83245
+ var runtimeReloadContext = new AsyncLocalStorage();
83246
+ var activeReloadTokens = /* @__PURE__ */ new WeakMap();
83247
+ async function reloadInteractiveRuntime(host) {
83248
+ if (host.isProcessing) {
83249
+ throw new Error("Cannot reload while a response is being processed. Wait for the turn to finish or abort it first.");
83250
+ }
83251
+ const ancestors = runtimeReloadContext.getStore();
83252
+ const activeToken = ancestors ? activeReloadTokens.get(host) : void 0;
83253
+ if (activeToken !== void 0 && ancestors?.get(host) === activeToken) {
83254
+ throw new Error("Cannot reenter runtime reload from its own extension callbacks.");
83255
+ }
83256
+ if (host.runtimeReloadPromise) return host.runtimeReloadPromise;
83257
+ const token = {};
83258
+ const context = new Map(ancestors ?? []);
83259
+ context.set(host, token);
83260
+ const pending = Promise.resolve().then(() => runtimeReloadContext.run(context, async () => {
83261
+ const acquiredReloadLock = !host.submitLock;
83262
+ if (acquiredReloadLock) host.submitLock = true;
83263
+ activeReloadTokens.set(host, token);
83264
+ try {
83265
+ await host.reloadRuntimeWhileLocked();
83266
+ } finally {
83267
+ if (activeReloadTokens.get(host) === token) activeReloadTokens.delete(host);
83268
+ if (acquiredReloadLock) host.submitLock = false;
83269
+ }
83270
+ }));
83271
+ host.runtimeReloadPromise = pending;
83272
+ try {
83273
+ await pending;
83274
+ } finally {
83275
+ if (host.runtimeReloadPromise === pending) host.runtimeReloadPromise = void 0;
83276
+ }
83277
+ }
83278
+ async function reloadInteractiveRuntimeWhileLocked(host) {
83279
+ const builtinDir = path30.join(getBuiltinDir(), "skills");
83280
+ const userDir = path30.join(os8.homedir(), ".pie", "skills");
83281
+ const projectDir = path30.join(host.options.cwd, ".pie", "skills");
83282
+ const { skills, diagnostics } = loadSkills({
83283
+ dirs: buildSkillDirs({ builtinDir, userDir, projectDir, cwd: host.options.cwd })
83284
+ });
83285
+ host.skills = skills;
83286
+ host.skillsSection = formatSkillsForPrompt(skills);
83287
+ for (const diag of diagnostics.filter((entry) => entry.type === "collision")) {
83288
+ host.chatContainer.addChild(new Text(theme.fg("dim", `[reload] ${diag.message}`), 1, 0));
83289
+ }
83290
+ host.allTools = [...host.options.allTools];
83291
+ host.originalTools = [...host.options.allTools];
83292
+ host.pristineTools = [...host.options.allTools];
83293
+ host.agent.state.tools = [...host.options.allTools];
83294
+ resetCliHostEnvelopeRegistry();
83295
+ const previousRunner = host.extensionRunner;
83296
+ host.extensionRunner = void 0;
83297
+ try {
83298
+ await previousRunner?.dispose("reload");
83299
+ } finally {
83300
+ host.extensionWidgets?.clearAll();
83301
+ }
83302
+ await host.loadExtensions();
83303
+ host.applyRuntimeToolMode();
83304
+ host.noteRuntimeModeChanged("extension_reload");
83305
+ await host.syncSessionScopedState();
83306
+ host.ui.requestRender();
83307
+ }
83308
+
83059
83309
  // src/interactive-mode.ts
83060
83310
  var InteractiveMode = class {
83061
83311
  agent;
@@ -83422,7 +83672,7 @@ var InteractiveMode = class {
83422
83672
  async init() {
83423
83673
  const activeSession = this.sessionManager.getActiveSession();
83424
83674
  this.thinkingLevel = resolveInitialThinkingLevel({ session: activeSession, settingsManager: this.options.settingsManager });
83425
- const { Agent } = await import("./chunks/src-4PG3KZUS.js");
83675
+ const { Agent } = await import("./chunks/src-IY6TEIL7.js");
83426
83676
  this.agent = new Agent({
83427
83677
  initialState: {
83428
83678
  systemPrompt: this.buildSystemPrompt(),
@@ -83518,51 +83768,11 @@ var InteractiveMode = class {
83518
83768
  });
83519
83769
  await loadInteractiveExtensions(this.extensionLoaderHost);
83520
83770
  }
83521
- /**
83522
- * Full runtime reload (extension API `ctx.reload()`): re-discover skills
83523
- * and extensions from disk and rebuild the tool surface and system prompt.
83524
- * Rejects while a turn is processing. Listener lifecycle contract: the old
83525
- * extension instances are dropped entirely (handlers, tools, commands),
83526
- * the tool surface is reset to the pristine composition baseline, and the
83527
- * fresh ExtensionRunner re-wraps from that baseline — no stale handlers
83528
- * and no double-wrapped tools survive a reload.
83529
- */
83530
83771
  async reloadRuntime() {
83531
- if (this.isProcessing) {
83532
- throw new Error("Cannot reload while a response is being processed. Wait for the turn to finish or abort it first.");
83533
- }
83534
- const acquiredReloadLock = !this.submitLock;
83535
- if (acquiredReloadLock) this.submitLock = true;
83536
- try {
83537
- await this.reloadRuntimeWhileLocked();
83538
- } finally {
83539
- if (acquiredReloadLock) this.submitLock = false;
83540
- }
83772
+ return reloadInteractiveRuntime(this);
83541
83773
  }
83542
83774
  async reloadRuntimeWhileLocked() {
83543
- const builtinDir = path30.join(getBuiltinDir(), "skills");
83544
- const userDir = path30.join(os8.homedir(), ".pie", "skills");
83545
- const projectDir = path30.join(this.options.cwd, ".pie", "skills");
83546
- const { skills, diagnostics } = loadSkills({
83547
- dirs: buildSkillDirs({ builtinDir, userDir, projectDir, cwd: this.options.cwd })
83548
- });
83549
- this.skills = skills;
83550
- this.skillsSection = formatSkillsForPrompt(skills);
83551
- for (const diag of diagnostics.filter((entry) => entry.type === "collision")) {
83552
- this.chatContainer.addChild(new Text(theme.fg("dim", `[reload] ${diag.message}`), 1, 0));
83553
- }
83554
- this.allTools = [...this.options.allTools];
83555
- this.originalTools = [...this.options.allTools];
83556
- this.pristineTools = [...this.options.allTools];
83557
- this.agent.state.tools = [...this.options.allTools];
83558
- resetCliHostEnvelopeRegistry();
83559
- await this.extensionRunner?.dispose("reload");
83560
- this.extensionWidgets?.clearAll();
83561
- await this.loadExtensions();
83562
- this.applyRuntimeToolMode();
83563
- this.noteRuntimeModeChanged("extension_reload");
83564
- await this.syncSessionScopedState();
83565
- this.ui.requestRender();
83775
+ return reloadInteractiveRuntimeWhileLocked(this);
83566
83776
  }
83567
83777
  calculateContextUsage() {
83568
83778
  return calculateContextUsage(this.contextUiHost);
@@ -84346,7 +84556,7 @@ async function runPrintMode(params) {
84346
84556
  `);
84347
84557
  console.log("Assistant: ");
84348
84558
  }
84349
- const { Agent: AgentClass } = await import("./chunks/src-4PG3KZUS.js");
84559
+ const { Agent: AgentClass } = await import("./chunks/src-IY6TEIL7.js");
84350
84560
  let agent;
84351
84561
  const printModeExtensions = await loadPrintModeExtensions({
84352
84562
  cwd: runtime.cwd,
@@ -84535,7 +84745,7 @@ async function runPrintMode(params) {
84535
84745
  if (!params.rawOutput && !params.jsonOutput) console.log("");
84536
84746
  agentSessionController.dispose();
84537
84747
  await runtime.sessionManager.save();
84538
- sessionTrace.flush();
84748
+ await sessionTrace.flushAsync();
84539
84749
  } finally {
84540
84750
  await printModeExtensions.runner.dispose().catch((error) => {
84541
84751
  console.error("Extension cleanup failed:", error);
@@ -85241,7 +85451,7 @@ async function runJsonEventsMode(params) {
85241
85451
  const sideTrace = trace;
85242
85452
  const sideApiKey = runtime.apiKey;
85243
85453
  const snapshot = structuredClone(parent.state.messages);
85244
- const { Agent: AgentClass } = await import("./chunks/src-4PG3KZUS.js");
85454
+ const { Agent: AgentClass } = await import("./chunks/src-IY6TEIL7.js");
85245
85455
  const sideAgent = new AgentClass({
85246
85456
  initialState: {
85247
85457
  systemPrompt: parent.state.systemPrompt,
@@ -85550,7 +85760,7 @@ async function runJsonEventsMode(params) {
85550
85760
  await runtime?.sessionManager.save();
85551
85761
  } catch {
85552
85762
  }
85553
- trace?.flush();
85763
+ await trace?.flushAsync();
85554
85764
  trace = void 0;
85555
85765
  cleanupBashOutputArtifacts();
85556
85766
  }
@@ -85605,7 +85815,7 @@ async function runJsonEventsMode(params) {
85605
85815
  if (runtime.initialModel.unconfigured || controller) {
85606
85816
  return;
85607
85817
  }
85608
- const { Agent: AgentClass } = await import("./chunks/src-4PG3KZUS.js");
85818
+ const { Agent: AgentClass } = await import("./chunks/src-IY6TEIL7.js");
85609
85819
  if (runtimeClosing) return;
85610
85820
  const compositionRegistry = createCliHostEnvelopeRegistry();
85611
85821
  const jsonModeExtensions = await loadJsonModeExtensions({ runtime, envelopeRegistry: compositionRegistry, getAgent: () => agent });
@@ -85972,7 +86182,7 @@ async function runJsonEventsMode(params) {
85972
86182
  if (activeTurn === turn) emitSettled(turn);
85973
86183
  } finally {
85974
86184
  clearInterval(progressTimer);
85975
- trace?.flush();
86185
+ await trace?.flushAsync();
85976
86186
  await activeRuntime?.sessionManager.save();
85977
86187
  if (activeTurn === turn) activeTurn = null;
85978
86188
  }
@@ -86293,7 +86503,7 @@ async function runWithJsonEventsConsoleIsolation(fn) {
86293
86503
  }
86294
86504
  async function createCliTestStreamFnFromEnv() {
86295
86505
  if (!process.env.PIE_TEST_MOCK_STREAM_SEQUENCE) return void 0;
86296
- return (await import("./chunks/test-stream-OTKTTZ72.js")).createTestStreamFnFromSequenceEnv();
86506
+ return (await import("./chunks/test-stream-EXSL5TIO.js")).createTestStreamFnFromSequenceEnv();
86297
86507
  }
86298
86508
  function resolveJsonEventsDetailFlag() {
86299
86509
  for (let index = 0; index < process.argv.length; index += 1) {