@cydm/pie 1.0.42 → 1.0.44

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.
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createAskUserCapability
4
- } from "../../../chunks/chunk-2GVFMYIV.js";
4
+ } from "../../../chunks/chunk-7C5E245X.js";
5
5
  import "../../../chunks/chunk-JVLAKTAS.js";
6
6
  import "../../../chunks/chunk-NGQ3PUOC.js";
7
7
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  resolveCliProjectRoot
4
- } from "../../../chunks/chunk-NTYHFBUA.js";
4
+ } from "../../../chunks/chunk-WLFOBECN.js";
5
5
  import "../../../chunks/chunk-LZQXQOPJ.js";
6
6
 
7
7
  // builtin/extensions/init/index.ts
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-2GVFMYIV.js";
10
+ } from "../../../chunks/chunk-7C5E245X.js";
11
11
  import "../../../chunks/chunk-JVLAKTAS.js";
12
12
  import {
13
13
  Type
@@ -1,11 +1,11 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createCliHostCapabilities
4
- } from "../../../chunks/chunk-VYAW6RAX.js";
4
+ } from "../../../chunks/chunk-DSVPYK5S.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-2GVFMYIV.js";
8
+ } from "../../../chunks/chunk-7C5E245X.js";
9
9
  import "../../../chunks/chunk-JVLAKTAS.js";
10
10
  import "../../../chunks/chunk-NGQ3PUOC.js";
11
11
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -7,7 +7,7 @@ import {
7
7
  executeManageTodoList,
8
8
  executionStateToTodos,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-2GVFMYIV.js";
10
+ } from "../../../chunks/chunk-7C5E245X.js";
11
11
  import "../../../chunks/chunk-JVLAKTAS.js";
12
12
  import "../../../chunks/chunk-NGQ3PUOC.js";
13
13
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -1286,6 +1286,7 @@ function formatSkillSummariesForPrompt(skills) {
1286
1286
  const lines = [
1287
1287
  "\n\nThe following skills provide specialized instructions for specific tasks.",
1288
1288
  "Use read_file to load a skill's entry file when the task matches its description.",
1289
+ "Skills that include an inline <content> block are already fully loaded; no read_file is needed for them.",
1289
1290
  "When a skill file references a relative path, resolve it against that skill's baseDir and use the resulting ordinary path with file or bash tools.",
1290
1291
  "Skills are ordinary files in the current runtime filesystem; do not use special skill/resource tool protocols.",
1291
1292
  "",
@@ -1295,7 +1296,13 @@ function formatSkillSummariesForPrompt(skills) {
1295
1296
  lines.push(" <skill>");
1296
1297
  lines.push(` <name>${escapeXml(skill.name)}</name>`);
1297
1298
  lines.push(` <description>${escapeXml(skill.description)}</description>`);
1298
- if (skill.filePath) {
1299
+ if (skill.inlineContent !== void 0) {
1300
+ lines.push(" <content>");
1301
+ for (const contentLine of skill.inlineContent.replace(/\r\n/g, "\n").split("\n")) {
1302
+ lines.push(` ${escapeXml(contentLine)}`);
1303
+ }
1304
+ lines.push(" </content>");
1305
+ } else if (skill.filePath) {
1299
1306
  lines.push(` <entry>${escapeXml(skill.filePath)}</entry>`);
1300
1307
  }
1301
1308
  if (skill.baseDir) {
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-2GVFMYIV.js";
11
+ } from "./chunk-7C5E245X.js";
12
12
  import {
13
13
  Type
14
14
  } from "./chunk-NGQ3PUOC.js";
@@ -7,7 +7,12 @@ function hasGitBoundary(directory) {
7
7
  const gitPath = path.join(directory, ".git");
8
8
  try {
9
9
  const stat = fs.statSync(gitPath);
10
- return stat.isDirectory() || stat.isFile();
10
+ if (stat.isDirectory()) return true;
11
+ if (!stat.isFile()) return false;
12
+ const content = fs.readFileSync(gitPath, "utf8");
13
+ const firstLine = content.split("\n", 1)[0] ?? "";
14
+ const gitdir = firstLine.trim().replace(/^gitdir:/, "").trim();
15
+ return firstLine.trim().startsWith("gitdir:") && gitdir.length > 0;
11
16
  } catch {
12
17
  return false;
13
18
  }
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  getThemesDir,
20
20
  migrateConfigFromAgentDir,
21
21
  truncateHeadTail
22
- } from "./chunks/chunk-VYAW6RAX.js";
22
+ } from "./chunks/chunk-DSVPYK5S.js";
23
23
  import {
24
24
  AGENTS_CONTEXT_FILE_NAME,
25
25
  AgentSessionController,
@@ -69,7 +69,7 @@ import {
69
69
  setExtensionEnabledInSettings,
70
70
  shouldPreserveExecutionStateForUserText,
71
71
  supersedeExecutionState
72
- } from "./chunks/chunk-2GVFMYIV.js";
72
+ } from "./chunks/chunk-7C5E245X.js";
73
73
  import "./chunks/chunk-JVLAKTAS.js";
74
74
  import {
75
75
  Deref,
@@ -109,7 +109,7 @@ import {
109
109
  } from "./chunks/chunk-NGQ3PUOC.js";
110
110
  import {
111
111
  resolveCliProjectRoot
112
- } from "./chunks/chunk-NTYHFBUA.js";
112
+ } from "./chunks/chunk-WLFOBECN.js";
113
113
  import {
114
114
  __commonJS,
115
115
  __require,
@@ -46314,6 +46314,24 @@ var SettingsManager = class {
46314
46314
  getBellNotificationsEnabled() {
46315
46315
  return this.settings.notifications?.bell !== false;
46316
46316
  }
46317
+ setBellNotificationsEnabled(enabled) {
46318
+ this.settings.notifications = { ...this.settings.notifications, bell: enabled };
46319
+ this.writeSettings();
46320
+ }
46321
+ getDesktopNotificationsEnabled() {
46322
+ return this.settings.notifications?.desktop !== false;
46323
+ }
46324
+ setDesktopNotificationsEnabled(enabled) {
46325
+ this.settings.notifications = { ...this.settings.notifications, desktop: enabled };
46326
+ this.writeSettings();
46327
+ }
46328
+ getCalmModeEnabled() {
46329
+ return this.settings.calmMode === true;
46330
+ }
46331
+ setCalmModeEnabled(enabled) {
46332
+ this.settings.calmMode = enabled;
46333
+ this.writeSettings();
46334
+ }
46317
46335
  // ==========================================================================
46318
46336
  // Extension enablement (user ~/.pie/settings.json + project ./.pie/settings.json)
46319
46337
  // ==========================================================================
@@ -49807,10 +49825,15 @@ var Text = class {
49807
49825
  for (const line of wrappedLines) {
49808
49826
  const lineWithMargins = leftMargin + line + rightMargin;
49809
49827
  if (this.customBgFn) {
49810
- contentLines.push(applyBackgroundToLine(lineWithMargins, width, this.customBgFn));
49828
+ const clamped = visibleWidth(lineWithMargins) > width ? truncateToWidth(lineWithMargins, width, "") : lineWithMargins;
49829
+ contentLines.push(applyBackgroundToLine(clamped, width, this.customBgFn));
49811
49830
  } else {
49812
49831
  const visibleLen = visibleWidth(lineWithMargins);
49813
- const paddingNeeded = Math.max(0, width - visibleLen);
49832
+ if (visibleLen > width) {
49833
+ contentLines.push(truncateToWidth(lineWithMargins, width, ""));
49834
+ continue;
49835
+ }
49836
+ const paddingNeeded = width - visibleLen;
49814
49837
  contentLines.push(lineWithMargins + " ".repeat(paddingNeeded));
49815
49838
  }
49816
49839
  }
@@ -50369,6 +50392,8 @@ var TUI = class extends Container {
50369
50392
  // Track previous viewport top for resize-aware cursor moves
50370
50393
  fullRedrawCount = 0;
50371
50394
  stopped = false;
50395
+ /** Guard: never paint before terminal.start() has run (modes like DECAWM-off are set there). */
50396
+ started = false;
50372
50397
  constructor(terminal, showHardwareCursor) {
50373
50398
  super();
50374
50399
  this.terminal = terminal;
@@ -50431,6 +50456,7 @@ var TUI = class extends Container {
50431
50456
  }
50432
50457
  start() {
50433
50458
  this.stopped = false;
50459
+ this.started = true;
50434
50460
  this.terminal.start(
50435
50461
  (data) => this.handleInput(data),
50436
50462
  () => this.requestRender()
@@ -50470,6 +50496,7 @@ var TUI = class extends Container {
50470
50496
  this.previousViewportTop = 0;
50471
50497
  }
50472
50498
  if (this.renderRequested) return;
50499
+ if (!this.started || this.stopped) return;
50473
50500
  this.renderRequested = true;
50474
50501
  process.nextTick(() => {
50475
50502
  this.renderRequested = false;
@@ -50809,7 +50836,7 @@ var SelectList = class {
50809
50836
  render(width) {
50810
50837
  const lines = [];
50811
50838
  if (this.filteredItems.length === 0) {
50812
- lines.push(this.theme.noMatch(" No matching commands"));
50839
+ lines.push(truncateToWidth(this.theme.noMatch(" No matching commands"), width));
50813
50840
  return lines;
50814
50841
  }
50815
50842
  const { startIndex, endIndex } = this.getVisibleRange();
@@ -51133,8 +51160,8 @@ var Editor = class {
51133
51160
  const rightPadding = leftPadding;
51134
51161
  if (this.scrollOffset > 0) {
51135
51162
  const indicator = `\u2500\u2500\u2500 \u2191 ${this.scrollOffset} more `;
51136
- const remaining = width - visibleWidth(indicator);
51137
- result.push(this.borderColor(indicator + "\u2500".repeat(Math.max(0, remaining))));
51163
+ const fill = "\u2500".repeat(Math.max(0, width - visibleWidth(indicator)));
51164
+ result.push(this.borderColor(truncateToWidth(indicator + fill, width, "")));
51138
51165
  } else {
51139
51166
  result.push(horizontal.repeat(width));
51140
51167
  }
@@ -51164,13 +51191,13 @@ var Editor = class {
51164
51191
  }
51165
51192
  const padding = " ".repeat(Math.max(0, contentWidth - lineVisibleWidth));
51166
51193
  const lineRightPadding = cursorInPadding ? rightPadding.slice(1) : rightPadding;
51167
- result.push(`${leftPadding}${displayText}${padding}${lineRightPadding}`);
51194
+ result.push(truncateToWidth(`${leftPadding}${displayText}${padding}${lineRightPadding}`, width, ""));
51168
51195
  }
51169
51196
  const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
51170
51197
  if (linesBelow > 0) {
51171
51198
  const indicator = `\u2500\u2500\u2500 \u2193 ${linesBelow} more `;
51172
- const remaining = width - visibleWidth(indicator);
51173
- result.push(this.borderColor(indicator + "\u2500".repeat(Math.max(0, remaining))));
51199
+ const fill = "\u2500".repeat(Math.max(0, width - visibleWidth(indicator)));
51200
+ result.push(this.borderColor(truncateToWidth(indicator + fill, width, "")));
51174
51201
  } else {
51175
51202
  result.push(horizontal.repeat(width));
51176
51203
  }
@@ -51728,26 +51755,55 @@ var Editor = class {
51728
51755
  /**
51729
51756
  * Move cursor to a target visual line, applying sticky column logic.
51730
51757
  * Shared by moveCursor() and pageScroll().
51758
+ *
51759
+ * Column math is done in terminal columns (visibleWidth), not UTF-16 code
51760
+ * units, so CJK/emoji text keeps the cursor visually aligned when moving
51761
+ * up/down across wrapped segments.
51731
51762
  */
51732
51763
  moveToVisualLine(visualLines, currentVisualLine, targetVisualLine) {
51733
51764
  const currentVL = visualLines[currentVisualLine];
51734
51765
  const targetVL = visualLines[targetVisualLine];
51735
51766
  if (currentVL && targetVL) {
51736
- const currentVisualCol = this.state.cursorCol - currentVL.startCol;
51767
+ const sourceLine = this.state.lines[currentVL.logicalLine] || "";
51768
+ const sourceText = sourceLine.slice(currentVL.startUnit, currentVL.startUnit + currentVL.lengthUnits);
51769
+ const currentVisualCol = visibleWidth(sourceLine.slice(currentVL.startUnit, this.state.cursorCol));
51737
51770
  const isLastSourceSegment = currentVisualLine === visualLines.length - 1 || visualLines[currentVisualLine + 1]?.logicalLine !== currentVL.logicalLine;
51738
- const sourceMaxVisualCol = isLastSourceSegment ? currentVL.length : Math.max(0, currentVL.length - 1);
51771
+ const sourceMaxVisualCol = isLastSourceSegment ? currentVL.widthCols : Math.max(0, currentVL.widthCols - this.lastGraphemeColumns(sourceText));
51772
+ const targetLine = this.state.lines[targetVL.logicalLine] || "";
51773
+ const targetText = targetLine.slice(targetVL.startUnit, targetVL.startUnit + targetVL.lengthUnits);
51739
51774
  const isLastTargetSegment = targetVisualLine === visualLines.length - 1 || visualLines[targetVisualLine + 1]?.logicalLine !== targetVL.logicalLine;
51740
- const targetMaxVisualCol = isLastTargetSegment ? targetVL.length : Math.max(0, targetVL.length - 1);
51775
+ const targetMaxVisualCol = isLastTargetSegment ? targetVL.widthCols : Math.max(0, targetVL.widthCols - this.lastGraphemeColumns(targetText));
51741
51776
  const moveToVisualCol = this.computeVerticalMoveColumn(
51742
51777
  currentVisualCol,
51743
51778
  sourceMaxVisualCol,
51744
51779
  targetMaxVisualCol
51745
51780
  );
51746
51781
  this.state.cursorLine = targetVL.logicalLine;
51747
- const targetCol = targetVL.startCol + moveToVisualCol;
51748
- const logicalLine = this.state.lines[targetVL.logicalLine] || "";
51749
- this.state.cursorCol = Math.min(targetCol, logicalLine.length);
51782
+ const targetCol = targetVL.startUnit + this.columnToUnit(targetText, moveToVisualCol);
51783
+ this.state.cursorCol = Math.min(targetCol, targetLine.length);
51784
+ }
51785
+ }
51786
+ /** Map a terminal-column offset to the closest code-unit offset at or before it. */
51787
+ columnToUnit(text, col) {
51788
+ if (col <= 0) return 0;
51789
+ let units = 0;
51790
+ let cols = 0;
51791
+ for (const seg of segmenter2.segment(text)) {
51792
+ const width = visibleWidth(seg.segment);
51793
+ if (cols + width > col) break;
51794
+ cols += width;
51795
+ units += seg.segment.length;
51796
+ }
51797
+ return units;
51798
+ }
51799
+ /** Visible width (columns) of the last grapheme of a non-empty string. */
51800
+ lastGraphemeColumns(text) {
51801
+ if (!text) return 0;
51802
+ let width = 0;
51803
+ for (const seg of segmenter2.segment(text)) {
51804
+ width = visibleWidth(seg.segment);
51750
51805
  }
51806
+ return width;
51751
51807
  }
51752
51808
  /**
51753
51809
  * Compute the target visual column for vertical cursor movement.
@@ -51937,11 +51993,8 @@ var Editor = class {
51937
51993
  }
51938
51994
  }
51939
51995
  /**
51940
- * Build a mapping from visual lines to logical positions.
51941
- * Returns an array where each element represents a visual line with:
51942
- * - logicalLine: index into this.state.lines
51943
- * - startCol: starting column in the logical line
51944
- * - length: length of this visual line segment
51996
+ * A wrapped segment of a logical line: both UTF-16 offsets (for editing)
51997
+ * and terminal columns (for visual cursor movement) are tracked.
51945
51998
  */
51946
51999
  buildVisualLineMap(width) {
51947
52000
  const visualLines = [];
@@ -51949,17 +52002,22 @@ var Editor = class {
51949
52002
  const line = this.state.lines[i] || "";
51950
52003
  const lineVisWidth = visibleWidth(line);
51951
52004
  if (line.length === 0) {
51952
- visualLines.push({ logicalLine: i, startCol: 0, length: 0 });
52005
+ visualLines.push({ logicalLine: i, startUnit: 0, lengthUnits: 0, startCol: 0, widthCols: 0 });
51953
52006
  } else if (lineVisWidth <= width) {
51954
- visualLines.push({ logicalLine: i, startCol: 0, length: line.length });
52007
+ visualLines.push({ logicalLine: i, startUnit: 0, lengthUnits: line.length, startCol: 0, widthCols: lineVisWidth });
51955
52008
  } else {
51956
52009
  const chunks = wordWrapLine(line, width);
52010
+ let col = 0;
51957
52011
  for (const chunk of chunks) {
52012
+ const chunkWidth = visibleWidth(chunk.text);
51958
52013
  visualLines.push({
51959
52014
  logicalLine: i,
51960
- startCol: chunk.startIndex,
51961
- length: chunk.endIndex - chunk.startIndex
52015
+ startUnit: chunk.startIndex,
52016
+ lengthUnits: chunk.endIndex - chunk.startIndex,
52017
+ startCol: col,
52018
+ widthCols: chunkWidth
51962
52019
  });
52020
+ col += chunkWidth;
51963
52021
  }
51964
52022
  }
51965
52023
  }
@@ -51973,9 +52031,9 @@ var Editor = class {
51973
52031
  const vl = visualLines[i];
51974
52032
  if (!vl) continue;
51975
52033
  if (vl.logicalLine === this.state.cursorLine) {
51976
- const colInSegment = this.state.cursorCol - vl.startCol;
52034
+ const unitInSegment = this.state.cursorCol - vl.startUnit;
51977
52035
  const isLastSegmentOfLine = i === visualLines.length - 1 || visualLines[i + 1]?.logicalLine !== vl.logicalLine;
51978
- if (colInSegment >= 0 && (colInSegment < vl.length || isLastSegmentOfLine && colInSegment <= vl.length)) {
52036
+ if (unitInSegment >= 0 && (unitInSegment < vl.lengthUnits || isLastSegmentOfLine && unitInSegment <= vl.lengthUnits)) {
51979
52037
  return i;
51980
52038
  }
51981
52039
  }
@@ -52361,7 +52419,14 @@ var Input = class {
52361
52419
  }
52362
52420
  setValue(value) {
52363
52421
  this.value = value;
52364
- this.cursor = Math.min(this.cursor, value.length);
52422
+ const clamped = Math.min(this.cursor, value.length);
52423
+ let units = 0;
52424
+ for (const seg of segmenter3.segment(value)) {
52425
+ const segLength = seg.segment.length;
52426
+ if (units + segLength > clamped) break;
52427
+ units += segLength;
52428
+ }
52429
+ this.cursor = units;
52365
52430
  }
52366
52431
  handleInput(data) {
52367
52432
  if (data.includes("\x1B[200~")) {
@@ -52646,48 +52711,54 @@ var Input = class {
52646
52711
  if (availableWidth <= 0) {
52647
52712
  return [prompt];
52648
52713
  }
52649
- let visibleText = "";
52650
- let cursorDisplay = this.cursor;
52651
- if (this.value.length < availableWidth) {
52652
- visibleText = this.value;
52653
- } else {
52654
- const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;
52714
+ const cursorAtEnd = this.cursor >= this.value.length;
52715
+ const scrollWidth = cursorAtEnd ? Math.max(0, availableWidth - 1) : availableWidth;
52716
+ const valueGraphemes = [];
52717
+ let totalWidth = 0;
52718
+ for (const seg of segmenter3.segment(this.value)) {
52719
+ const text = seg.segment;
52720
+ const gWidth = visibleWidth(text);
52721
+ valueGraphemes.push({ text, startUnit: seg.index, startCol: totalWidth, width: gWidth });
52722
+ totalWidth += gWidth;
52723
+ }
52724
+ let cursorCol = 0;
52725
+ let cursorGraphemeIndex = valueGraphemes.length;
52726
+ for (let i = 0; i < valueGraphemes.length; i++) {
52727
+ const g2 = valueGraphemes[i];
52728
+ if (g2.startUnit >= this.cursor) break;
52729
+ cursorCol = g2.startCol + g2.width;
52730
+ cursorGraphemeIndex = i + 1;
52731
+ }
52732
+ const cursorCellWidth = valueGraphemes[cursorGraphemeIndex]?.width ?? 1;
52733
+ const cursorEndCol = cursorAtEnd ? cursorCol + 1 : cursorCol + cursorCellWidth;
52734
+ let startCol = 0;
52735
+ if (cursorEndCol > availableWidth) {
52655
52736
  const halfWidth = Math.floor(scrollWidth / 2);
52656
- const findValidStart = (start) => {
52657
- while (start < this.value.length) {
52658
- const charCode = this.value.charCodeAt(start);
52659
- if (charCode >= 56320 && charCode < 57344) {
52660
- start++;
52661
- continue;
52662
- }
52663
- break;
52664
- }
52665
- return start;
52666
- };
52667
- const findValidEnd = (end) => {
52668
- while (end > 0) {
52669
- const charCode = this.value.charCodeAt(end - 1);
52670
- if (charCode >= 55296 && charCode < 56320) {
52671
- end--;
52672
- continue;
52673
- }
52674
- break;
52675
- }
52676
- return end;
52677
- };
52678
- if (this.cursor < halfWidth) {
52679
- visibleText = this.value.slice(0, findValidEnd(scrollWidth));
52680
- cursorDisplay = this.cursor;
52681
- } else if (this.cursor > this.value.length - halfWidth) {
52682
- const start = findValidStart(this.value.length - scrollWidth);
52683
- visibleText = this.value.slice(start);
52684
- cursorDisplay = this.cursor - start;
52737
+ if (cursorCol < halfWidth) {
52738
+ startCol = 0;
52739
+ } else if (cursorEndCol > totalWidth - halfWidth) {
52740
+ startCol = Math.max(0, totalWidth - scrollWidth);
52685
52741
  } else {
52686
- const start = findValidStart(this.cursor - halfWidth);
52687
- visibleText = this.value.slice(start, findValidEnd(start + scrollWidth));
52688
- cursorDisplay = halfWidth;
52742
+ startCol = cursorCol - halfWidth;
52743
+ }
52744
+ startCol = Math.max(0, Math.min(startCol, cursorEndCol - scrollWidth));
52745
+ }
52746
+ const windowEnd = startCol + scrollWidth;
52747
+ let visibleText = "";
52748
+ let cursorDisplay = 0;
52749
+ let cursorPlaced = false;
52750
+ for (const g2 of valueGraphemes) {
52751
+ if (g2.startCol >= startCol && g2.startCol + g2.width <= windowEnd) {
52752
+ if (!cursorPlaced && this.cursor <= g2.startUnit) {
52753
+ cursorDisplay = visibleText.length;
52754
+ cursorPlaced = true;
52755
+ }
52756
+ visibleText += g2.text;
52689
52757
  }
52690
52758
  }
52759
+ if (!cursorPlaced) {
52760
+ cursorDisplay = visibleText.length;
52761
+ }
52691
52762
  const graphemes = [...segmenter3.segment(visibleText.slice(cursorDisplay))];
52692
52763
  const cursorGrapheme = graphemes[0];
52693
52764
  const beforeCursor = visibleText.slice(0, cursorDisplay);
@@ -53981,10 +54052,15 @@ var Markdown = class {
53981
54052
  }
53982
54053
  const lineWithMargins = leftMargin + line + rightMargin;
53983
54054
  if (bgFn) {
53984
- contentLines.push(applyBackgroundToLine(lineWithMargins, width, bgFn));
54055
+ const clamped = visibleWidth(lineWithMargins) > width ? truncateToWidth(lineWithMargins, width, "") : lineWithMargins;
54056
+ contentLines.push(applyBackgroundToLine(clamped, width, bgFn));
53985
54057
  } else {
53986
54058
  const visibleLen = visibleWidth(lineWithMargins);
53987
- const paddingNeeded = Math.max(0, width - visibleLen);
54059
+ if (visibleLen > width) {
54060
+ contentLines.push(truncateToWidth(lineWithMargins, width, ""));
54061
+ continue;
54062
+ }
54063
+ const paddingNeeded = width - visibleLen;
53988
54064
  contentLines.push(lineWithMargins + " ".repeat(paddingNeeded));
53989
54065
  }
53990
54066
  }
@@ -54513,7 +54589,7 @@ var SettingsList = class {
54513
54589
  lines.push("");
54514
54590
  }
54515
54591
  if (this.items.length === 0) {
54516
- lines.push(this.theme.hint(" No settings available"));
54592
+ lines.push(truncateToWidth(this.theme.hint(" No settings available"), width));
54517
54593
  if (this.searchEnabled) {
54518
54594
  this.addHintLine(lines, width);
54519
54595
  }
@@ -54554,7 +54630,7 @@ var SettingsList = class {
54554
54630
  lines.push("");
54555
54631
  const wrappedDesc = wrapTextWithAnsi(selectedItem.description, width - 4);
54556
54632
  for (const line of wrappedDesc) {
54557
- lines.push(this.theme.description(` ${line}`));
54633
+ lines.push(truncateToWidth(this.theme.description(` ${line}`), width));
54558
54634
  }
54559
54635
  }
54560
54636
  this.addHintLine(lines, width);
@@ -54919,6 +54995,7 @@ var ProcessTerminal = class {
54919
54995
  process.stdin.setEncoding("utf8");
54920
54996
  process.stdin.resume();
54921
54997
  process.stdout.write("\x1B[?2004h");
54998
+ process.stdout.write("\x1B[?7l");
54922
54999
  process.stdout.on("resize", this.resizeHandler);
54923
55000
  if (process.platform !== "win32") {
54924
55001
  process.kill(process.pid, "SIGWINCH");
@@ -55030,6 +55107,7 @@ var ProcessTerminal = class {
55030
55107
  }
55031
55108
  stop() {
55032
55109
  process.stdout.write("\x1B[?2004l");
55110
+ process.stdout.write("\x1B[?7h");
55033
55111
  if (this._kittyProtocolActive && !process.env.PIE_DISABLE_KITTY_PROTOCOL) {
55034
55112
  process.stdout.write("\x1B[<u");
55035
55113
  this._kittyProtocolActive = false;
@@ -55093,7 +55171,26 @@ var ProcessTerminal = class {
55093
55171
  setTitle(title) {
55094
55172
  process.stdout.write(`\x1B]0;${title}\x07`);
55095
55173
  }
55174
+ notify(title, body) {
55175
+ if (!process.stdout.isTTY) return;
55176
+ for (const sequence of buildDesktopNotificationSequences(title, body)) {
55177
+ process.stdout.write(sequence);
55178
+ }
55179
+ }
55096
55180
  };
55181
+ var NOTIFICATION_UNSAFE_CHARS = /[\x00-\x1f\x7f-\x9f;]/g;
55182
+ function sanitizeNotificationText(text) {
55183
+ return text.replace(NOTIFICATION_UNSAFE_CHARS, "");
55184
+ }
55185
+ function buildDesktopNotificationSequences(title, body) {
55186
+ const sanitizedTitle = sanitizeNotificationText(title);
55187
+ const sanitizedBody = sanitizeNotificationText(body);
55188
+ return [
55189
+ `\x1B]9;${sanitizedTitle}: ${sanitizedBody}\x07`,
55190
+ `\x1B]777;notify;${sanitizedTitle};${sanitizedBody}\x07`,
55191
+ `\x1B]99;i=1:d=0;${sanitizedBody}\x07`
55192
+ ];
55193
+ }
55097
55194
 
55098
55195
  // src/extensions/runner.ts
55099
55196
  function createLoggedProxy(obj, contextPrefix) {
@@ -61079,6 +61176,27 @@ var SettingsSelectorComponent = class extends Container {
61079
61176
  }
61080
61177
  )
61081
61178
  },
61179
+ {
61180
+ id: "calm",
61181
+ label: "Calm Mode",
61182
+ description: "Show only questions and answers; hide tool runs and thinking",
61183
+ currentValue: config.calmMode ? "on" : "off",
61184
+ values: ["on", "off"]
61185
+ },
61186
+ {
61187
+ id: "notificationsBell",
61188
+ label: "Bell",
61189
+ description: "Ring the terminal bell when a turn needs you",
61190
+ currentValue: config.notificationsBell ? "on" : "off",
61191
+ values: ["on", "off"]
61192
+ },
61193
+ {
61194
+ id: "notificationsDesktop",
61195
+ label: "Desktop Notify",
61196
+ description: "Desktop notification (banner / dock badge) via terminal OSC",
61197
+ currentValue: config.notificationsDesktop ? "on" : "off",
61198
+ values: ["on", "off"]
61199
+ },
61082
61200
  {
61083
61201
  id: "extensions",
61084
61202
  label: "Extensions",
@@ -61126,6 +61244,15 @@ var SettingsSelectorComponent = class extends Container {
61126
61244
  case "thinking":
61127
61245
  callbacks.onThinkingLevelChange(newValue);
61128
61246
  break;
61247
+ case "calm":
61248
+ callbacks.onCalmToggle(newValue === "on");
61249
+ break;
61250
+ case "notificationsBell":
61251
+ callbacks.onBellNotificationsChange(newValue === "on");
61252
+ break;
61253
+ case "notificationsDesktop":
61254
+ callbacks.onDesktopNotificationsChange(newValue === "on");
61255
+ break;
61129
61256
  }
61130
61257
  },
61131
61258
  callbacks.onCancel,
@@ -61698,6 +61825,7 @@ function handleExtensionsCommand(host, args) {
61698
61825
 
61699
61826
  // src/slash-commands.ts
61700
61827
  var BUILTIN_SLASH_COMMANDS = [
61828
+ { name: "sessions", description: "Pick a saved session to switch to" },
61701
61829
  { name: "sessions/compact", description: "Summarize history and add a compact checkpoint to the session" },
61702
61830
  { name: "sessions/new", description: "Start a new session" },
61703
61831
  { name: "sessions/resume", description: "Resume a different session" },
@@ -61711,6 +61839,8 @@ var BUILTIN_SLASH_COMMANDS = [
61711
61839
  { name: "settings/model", description: "Select AI model" },
61712
61840
  { name: "settings/theme", description: "Change theme" },
61713
61841
  { name: "settings/thinking", description: "Set thinking level" },
61842
+ { name: "settings/calm", description: "Toggle calm mode: show only questions and answers" },
61843
+ { name: "calm", description: "Alias for /settings/calm" },
61714
61844
  { name: "settings/yolo", description: "Toggle or set YOLO mode" },
61715
61845
  { name: "settings/debugs/cache-test", description: "Run provider cache diagnostics with a dedicated 2-request probe" },
61716
61846
  { name: "settings/debugs/context", description: "Append a context summary to ~/.pie/logs/pie-cli.log" },
@@ -62181,11 +62311,8 @@ var SessionSelectorComponent = class extends Container {
62181
62311
  var RESET = "\x1B[0m";
62182
62312
  var INVERSE = "\x1B[7m";
62183
62313
  var DIM = "\x1B[2m";
62184
- function stripAnsi(str2) {
62185
- return str2.replace(/\x1b\[[0-9;]*m/g, "");
62186
- }
62187
62314
  function visibleLength(str2) {
62188
- return stripAnsi(str2).length;
62315
+ return visibleWidth(str2);
62189
62316
  }
62190
62317
  var TREE_CHARS = {
62191
62318
  branch: "\u251C\u2500\u2500 ",
@@ -62625,6 +62752,7 @@ var TreeSelectorComponent = class {
62625
62752
  var AssistantMessageComponent = class extends Container {
62626
62753
  contentContainer;
62627
62754
  hideThinkingBlock;
62755
+ calmMode = false;
62628
62756
  markdownTheme;
62629
62757
  lastMessage;
62630
62758
  constructor(message, hideThinkingBlock = false, markdownTheme = getMarkdownTheme()) {
@@ -62646,12 +62774,26 @@ var AssistantMessageComponent = class extends Container {
62646
62774
  setHideThinkingBlock(hide) {
62647
62775
  this.hideThinkingBlock = hide;
62648
62776
  }
62777
+ /**
62778
+ * Calm mode: suppress thinking traces entirely (no "Thinking..." label,
62779
+ * no surrounding spacers). Errors and text still render.
62780
+ */
62781
+ setCalmMode(calm) {
62782
+ if (this.calmMode === calm) return;
62783
+ this.calmMode = calm;
62784
+ if (this.lastMessage) {
62785
+ this.updateContent(this.lastMessage);
62786
+ }
62787
+ }
62788
+ isCalmMode() {
62789
+ return this.calmMode;
62790
+ }
62649
62791
  updateContent(message) {
62650
62792
  this.lastMessage = message;
62651
62793
  this.contentContainer.clear();
62652
- const hasVisibleContent = message.content.some(
62653
- (c) => c.type === "text" && c.text.trim() || c.type === "thinking" && c.thinking.trim()
62654
- );
62794
+ const hasTextContent = message.content.some((c) => c.type === "text" && c.text.trim());
62795
+ const hasThinkingContent = message.content.some((c) => c.type === "thinking" && c.thinking.trim());
62796
+ const hasVisibleContent = this.calmMode ? hasTextContent : hasTextContent || hasThinkingContent;
62655
62797
  if (hasVisibleContent) {
62656
62798
  this.contentContainer.addChild(new Spacer(1));
62657
62799
  }
@@ -62660,6 +62802,9 @@ var AssistantMessageComponent = class extends Container {
62660
62802
  if (content.type === "text" && content.text.trim()) {
62661
62803
  this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));
62662
62804
  } else if (content.type === "thinking" && content.thinking.trim()) {
62805
+ if (this.calmMode) {
62806
+ continue;
62807
+ }
62663
62808
  const hasVisibleContentAfter = message.content.slice(i + 1).some((c) => c.type === "text" && c.text.trim() || c.type === "thinking" && c.thinking.trim());
62664
62809
  if (this.hideThinkingBlock) {
62665
62810
  this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 1, 0));
@@ -63874,76 +64019,7 @@ async function loadSession(host, sessionId) {
63874
64019
  host.lastStatusText = void 0;
63875
64020
  host.agentSessionController?.drainQueuedInputsForEditor();
63876
64021
  host.updatePendingMessagesDisplay();
63877
- const messagesToShow = messages.slice(-50);
63878
- const toolResultMap = /* @__PURE__ */ new Map();
63879
- for (const msg of messages) {
63880
- if (msg.role === "toolResult") {
63881
- const toolMsg = msg;
63882
- toolResultMap.set(toolMsg.toolCallId, toolMsg);
63883
- }
63884
- }
63885
- for (const msg of messagesToShow) {
63886
- if (msg.role === "user") {
63887
- let content = "";
63888
- if (typeof msg.content === "string") {
63889
- content = msg.content;
63890
- } else if (Array.isArray(msg.content)) {
63891
- content = msg.content.map(textFromContentBlock).join("");
63892
- }
63893
- host.chatContainer.addChild(new UserMessageComponent(content));
63894
- } else if (msg.role === "assistant") {
63895
- const assistantMsg = msg;
63896
- const contentBlocks = [];
63897
- if (Array.isArray(assistantMsg.content)) {
63898
- for (const c of assistantMsg.content) {
63899
- if (typeof c !== "object" || !c || !("type" in c)) continue;
63900
- if (c.type === "text" && "text" in c) {
63901
- const text = c.text;
63902
- if (text.trim()) {
63903
- contentBlocks.push({ type: "text", text });
63904
- }
63905
- } else if (c.type === "thinking" && "thinking" in c) {
63906
- const thinking = c.thinking;
63907
- if (thinking.trim()) {
63908
- contentBlocks.push({ type: "thinking", thinking });
63909
- }
63910
- }
63911
- }
63912
- }
63913
- if (contentBlocks.length > 0) {
63914
- const msgComponent = new AssistantMessageComponent(void 0, host.thinkingLevel === "off");
63915
- msgComponent.updateContent({
63916
- role: "assistant",
63917
- content: contentBlocks
63918
- });
63919
- host.chatContainer.addChild(msgComponent);
63920
- }
63921
- if (Array.isArray(assistantMsg.content)) {
63922
- for (const c of assistantMsg.content) {
63923
- if (typeof c === "object" && c && "type" in c && c.type === "toolCall") {
63924
- const toolCall = c;
63925
- const toolId = toolCall.id || toolCall.name;
63926
- const toolComponent = new ToolExecutionComponent(
63927
- toolCall.name,
63928
- toolCall.arguments,
63929
- { expanded: host.toolOutputExpanded },
63930
- host.ui
63931
- );
63932
- const toolResult = toolResultMap.get(toolId);
63933
- if (toolResult) {
63934
- toolComponent.updateResult({
63935
- content: toolResult.content,
63936
- isError: toolResult.isError || false,
63937
- details: toolResult.details
63938
- }, false);
63939
- }
63940
- host.chatContainer.addChild(toolComponent);
63941
- host.pendingTools.set(toolId, toolComponent);
63942
- }
63943
- }
63944
- }
63945
- }
63946
- }
64022
+ renderChatHistory(host, messages);
63947
64023
  if (messages.length > 50) {
63948
64024
  host.chatContainer.addChild(new Spacer(1));
63949
64025
  host.chatContainer.addChild(
@@ -64170,36 +64246,81 @@ async function navigateToEntry(host, entryId) {
64170
64246
  );
64171
64247
  }
64172
64248
  }
64173
- function renderMessageHistory(host, messages) {
64174
- for (const message of messages) {
64175
- const msg = message;
64249
+ function renderChatHistory(host, messages) {
64250
+ const messagesToShow = messages.slice(-50);
64251
+ const toolResultMap = /* @__PURE__ */ new Map();
64252
+ for (const msg of messages) {
64253
+ if (msg.role === "toolResult") {
64254
+ const toolMsg = msg;
64255
+ toolResultMap.set(toolMsg.toolCallId, toolMsg);
64256
+ }
64257
+ }
64258
+ for (const msg of messagesToShow) {
64176
64259
  if (msg.role === "user") {
64177
- let text = "";
64260
+ let content = "";
64178
64261
  if (typeof msg.content === "string") {
64179
- text = msg.content;
64262
+ content = msg.content;
64180
64263
  } else if (Array.isArray(msg.content)) {
64181
- text = msg.content.filter((c) => typeof c === "object" && c !== null && "type" in c && c.type === "text").map((c) => c.text || "").join("");
64182
- }
64183
- if (text.trim()) {
64184
- host.chatContainer.addChild(new UserMessageComponent(text));
64264
+ content = msg.content.map(textFromContentBlock).join("");
64185
64265
  }
64266
+ host.chatContainer.addChild(new UserMessageComponent(content));
64186
64267
  } else if (msg.role === "assistant") {
64187
- const content = msg.content || [];
64188
- const assistantMsg = {
64189
- role: "assistant",
64190
- content: content.map((c) => {
64191
- if (c.type === "text") return { type: "text", text: c.text || "" };
64192
- if (c.type === "thinking") return { type: "thinking", thinking: c.thinking || "" };
64193
- if (c.type === "toolCall") return { type: "toolCall", name: c.name || "", arguments: c.arguments, id: c.id };
64194
- return { type: "text", text: "" };
64195
- }),
64196
- stopReason: msg.stopReason,
64197
- errorMessage: msg.errorMessage
64198
- };
64199
- host.chatContainer.addChild(new AssistantMessageComponent(assistantMsg, host.thinkingLevel === "off"));
64268
+ const assistantMsg = msg;
64269
+ const contentBlocks = [];
64270
+ if (Array.isArray(assistantMsg.content)) {
64271
+ for (const c of assistantMsg.content) {
64272
+ if (typeof c !== "object" || !c || !("type" in c)) continue;
64273
+ if (c.type === "text" && "text" in c) {
64274
+ const text = c.text;
64275
+ if (text.trim()) {
64276
+ contentBlocks.push({ type: "text", text });
64277
+ }
64278
+ } else if (c.type === "thinking" && "thinking" in c) {
64279
+ const thinking = c.thinking;
64280
+ if (thinking.trim()) {
64281
+ contentBlocks.push({ type: "thinking", thinking });
64282
+ }
64283
+ }
64284
+ }
64285
+ }
64286
+ if (contentBlocks.length > 0) {
64287
+ const msgComponent = new AssistantMessageComponent(void 0, host.thinkingLevel === "off");
64288
+ msgComponent.setCalmMode(host.calmMode);
64289
+ msgComponent.updateContent({
64290
+ role: "assistant",
64291
+ content: contentBlocks
64292
+ });
64293
+ host.chatContainer.addChild(msgComponent);
64294
+ }
64295
+ if (Array.isArray(assistantMsg.content) && !host.calmMode) {
64296
+ for (const c of assistantMsg.content) {
64297
+ if (typeof c === "object" && c && "type" in c && c.type === "toolCall") {
64298
+ const toolCall = c;
64299
+ const toolId = toolCall.id || toolCall.name;
64300
+ const toolComponent = new ToolExecutionComponent(
64301
+ toolCall.name,
64302
+ toolCall.arguments,
64303
+ { expanded: host.toolOutputExpanded },
64304
+ host.ui
64305
+ );
64306
+ const toolResult = toolResultMap.get(toolId);
64307
+ if (toolResult) {
64308
+ toolComponent.updateResult({
64309
+ content: toolResult.content,
64310
+ isError: toolResult.isError || false,
64311
+ details: toolResult.details
64312
+ }, false);
64313
+ }
64314
+ host.chatContainer.addChild(toolComponent);
64315
+ host.pendingTools.set(toolId, toolComponent);
64316
+ }
64317
+ }
64318
+ }
64200
64319
  }
64201
64320
  }
64202
- host.ui.requestRender();
64321
+ }
64322
+ function renderMessageHistory(host, messages) {
64323
+ renderChatHistory(host, messages);
64203
64324
  }
64204
64325
  async function handleForkCommand(host) {
64205
64326
  try {
@@ -64565,7 +64686,10 @@ function showSettingsSelector(host) {
64565
64686
  availableThemes: getAvailableThemes(),
64566
64687
  currentModel: currentFullId,
64567
64688
  availableModels,
64568
- yoloMode: host.yoloMode
64689
+ yoloMode: host.yoloMode,
64690
+ calmMode: host.calmMode,
64691
+ notificationsBell: host.options.settingsManager.getBellNotificationsEnabled(),
64692
+ notificationsDesktop: host.options.settingsManager.getDesktopNotificationsEnabled()
64569
64693
  },
64570
64694
  {
64571
64695
  onThinkingLevelChange: (level) => {
@@ -64598,6 +64722,17 @@ function showSettingsSelector(host) {
64598
64722
  await host.handleYoloCommand();
64599
64723
  done();
64600
64724
  },
64725
+ onCalmToggle: (enabled) => {
64726
+ host.setCalmMode(enabled);
64727
+ },
64728
+ onBellNotificationsChange: (enabled) => {
64729
+ host.options.settingsManager.setBellNotificationsEnabled(enabled);
64730
+ host.showStatus(`Bell notifications ${enabled ? "on" : "off"}`);
64731
+ },
64732
+ onDesktopNotificationsChange: (enabled) => {
64733
+ host.options.settingsManager.setDesktopNotificationsEnabled(enabled);
64734
+ host.showStatus(`Desktop notifications ${enabled ? "on" : "off"}`);
64735
+ },
64601
64736
  createExtensionsSubmenu: (onCancel) => createExtensionSelector(host, "user", onCancel),
64602
64737
  onHelp: () => {
64603
64738
  host.handleHotkeysCommand();
@@ -64791,6 +64926,23 @@ function handleThinkingCommand(host, level) {
64791
64926
  }
64792
64927
  host.setThinkingLevel(level);
64793
64928
  }
64929
+ async function handleCalmCommand(host, args) {
64930
+ const mode = (args || "").trim().toLowerCase();
64931
+ if (mode === "status") {
64932
+ host.showStatus(`Calm mode is ${host.calmMode ? "on" : "off"}`);
64933
+ return;
64934
+ }
64935
+ if (mode && !["on", "off", "enable", "enabled", "disable", "disabled", "true", "false", "1", "0"].includes(mode)) {
64936
+ host.showStatus("Usage: /settings/calm [on|off|status]");
64937
+ return;
64938
+ }
64939
+ const nextCalmMode = mode ? ["on", "enable", "enabled", "true", "1"].includes(mode) : !host.calmMode;
64940
+ if (host.calmMode === nextCalmMode) {
64941
+ host.showStatus(`Calm mode already ${host.calmMode ? "on" : "off"}`);
64942
+ return;
64943
+ }
64944
+ host.setCalmMode(nextCalmMode);
64945
+ }
64794
64946
  async function handleYoloCommand(host, args) {
64795
64947
  const mode = (args || "").trim().toLowerCase();
64796
64948
  if (mode === "status") {
@@ -65060,6 +65212,10 @@ function createExtensionContextForCommands(host) {
65060
65212
  };
65061
65213
  }
65062
65214
  function registerBuiltinCommands(host) {
65215
+ commandTree.register(["sessions"], {
65216
+ description: "Pick a saved session to switch to (tree/fork/new below)",
65217
+ handler: () => host.handleResumeCommand()
65218
+ });
65063
65219
  commandTree.register(["sessions", "compact"], {
65064
65220
  description: "Summarize history and replace it with a compact checkpoint",
65065
65221
  handler: () => host.handleCompactCommand()
@@ -65112,6 +65268,14 @@ function registerBuiltinCommands(host) {
65112
65268
  description: "Toggle or set YOLO mode (on/off/status)",
65113
65269
  handler: (_2, args) => host.handleYoloCommand(args)
65114
65270
  });
65271
+ commandTree.register(["settings", "calm"], {
65272
+ description: "Toggle calm mode: show only questions and answers (on/off/status)",
65273
+ handler: (_2, args) => host.handleCalmCommand(args)
65274
+ });
65275
+ commandTree.register(["calm"], {
65276
+ description: "Alias for /settings/calm (questions and answers only)",
65277
+ handler: (_2, args) => host.handleCalmCommand(args)
65278
+ });
65115
65279
  commandTree.register(["settings", "extensions"], {
65116
65280
  description: "List extensions and manage enable/disable toggles",
65117
65281
  handler: (_2, args) => host.handleExtensionsCommand(args)
@@ -65307,7 +65471,37 @@ function ringTerminalBell(options = {}) {
65307
65471
  function ringTurnSettledBell(host) {
65308
65472
  if ((host.agentSessionController?.pendingMessageCount ?? 0) > 0) return;
65309
65473
  if (host.executionState.read().awaitingContinuation) return;
65310
- host.ringAttentionBell?.();
65474
+ host.ringAttentionBell?.("turn-settled");
65475
+ }
65476
+ function attentionMessage(reason) {
65477
+ switch (reason) {
65478
+ case "interaction":
65479
+ return { title: "Pie", body: "Waiting for your input" };
65480
+ case "turn-settled":
65481
+ return { title: "Pie", body: "Response ready" };
65482
+ case "turn-failed":
65483
+ return { title: "Pie", body: "Turn failed" };
65484
+ case "stall-recovery":
65485
+ return { title: "Pie", body: "Recovered from a stalled turn" };
65486
+ }
65487
+ }
65488
+ var DEFAULT_NOTIFICATION_MIN_INTERVAL_MS = 1e3;
65489
+ var lastNotificationAtMs = 0;
65490
+ function sendDesktopNotification(reason, options = {}) {
65491
+ const {
65492
+ enabled = true,
65493
+ terminal,
65494
+ now = Date.now,
65495
+ minIntervalMs = DEFAULT_NOTIFICATION_MIN_INTERVAL_MS
65496
+ } = options;
65497
+ if (!enabled) return false;
65498
+ if (!terminal || typeof terminal.notify !== "function") return false;
65499
+ const atMs = now();
65500
+ if (atMs - lastNotificationAtMs < minIntervalMs) return false;
65501
+ lastNotificationAtMs = atMs;
65502
+ const { title, body } = attentionMessage(reason);
65503
+ terminal.notify(title, body);
65504
+ return true;
65311
65505
  }
65312
65506
 
65313
65507
  // src/runtime/interactive-runtime-events.ts
@@ -65515,7 +65709,7 @@ function handleSemanticAgentEvent(host, event) {
65515
65709
  host.appendWarningSummary(outcome.terminalSummary);
65516
65710
  }
65517
65711
  host.sessionTrace.completeTurn(event.message, true);
65518
- host.ringAttentionBell?.();
65712
+ host.ringAttentionBell?.("turn-failed");
65519
65713
  host._semanticTurnIndex++;
65520
65714
  break;
65521
65715
  }
@@ -65556,6 +65750,7 @@ function handleMessageStart(host, event) {
65556
65750
  if (event.message?.role === "assistant") {
65557
65751
  host.clearContinuationTimeout("assistant_message_start");
65558
65752
  host.streamingComponent = new AssistantMessageComponent(void 0, host.thinkingLevel === "off");
65753
+ host.streamingComponent.setCalmMode(host.calmMode);
65559
65754
  host.chatContainer.addChild(host.streamingComponent);
65560
65755
  host.sessionTrace.updateAssistantMessage(event.message);
65561
65756
  }
@@ -65584,6 +65779,9 @@ function handleMessageUpdate(host, event) {
65584
65779
  thinkingContent += content2.thinking || "";
65585
65780
  } else if (content2.type === "toolCall") {
65586
65781
  toolCallCount += 1;
65782
+ if (host.calmMode) {
65783
+ continue;
65784
+ }
65587
65785
  const toolCall = content2;
65588
65786
  const contentIndex = message.content.indexOf(content2);
65589
65787
  const partialToolId = `__partial_tool__:${contentIndex}`;
@@ -65649,7 +65847,7 @@ function findToolRenderHint(host, toolName) {
65649
65847
  function handleToolStart(host, event) {
65650
65848
  host.logDebug("tool:start", { toolName: event.toolName, toolCallId: event.toolCallId });
65651
65849
  host.sessionTrace.startTool(event.toolCallId, event.toolName, event.args);
65652
- if (event.toolCallId) {
65850
+ if (event.toolCallId && !host.calmMode) {
65653
65851
  const toolComponent = host.pendingTools.get(event.toolCallId);
65654
65852
  if (toolComponent) {
65655
65853
  toolComponent.updateArgs(event.args);
@@ -65740,6 +65938,22 @@ function handleMessageEnd(host, event) {
65740
65938
  host.safeRequestRender();
65741
65939
  }
65742
65940
 
65941
+ // src/runtime/interactive-calm.ts
65942
+ function applyCalmMode(host, enabled) {
65943
+ const streaming = host.streamingComponent;
65944
+ if (streaming) {
65945
+ streaming.setCalmMode(enabled);
65946
+ }
65947
+ host.chatContainer.clear();
65948
+ host.pendingTools.clear();
65949
+ host.lastStatusSpacer = void 0;
65950
+ host.lastStatusText = void 0;
65951
+ host.renderMessageHistory(host.sessionManager.getMessages());
65952
+ if (streaming) {
65953
+ host.chatContainer.addChild(streaming);
65954
+ }
65955
+ }
65956
+
65743
65957
  // src/input/interactive-input-pipeline.ts
65744
65958
  function shouldQueueUserInput(host) {
65745
65959
  return host.submitLock || (host.agentSessionController?.hasQueuedMessages ?? false);
@@ -67880,6 +68094,13 @@ function isCommandLikeInput(text) {
67880
68094
  // src/components/extension-widgets.ts
67881
68095
  var ExtensionWidgetsComponent = class extends Container {
67882
68096
  slots = /* @__PURE__ */ new Map();
68097
+ calmMode = false;
68098
+ /** Calm mode hides ambient extension widgets (slots keep their data). */
68099
+ setCalmMode(calm) {
68100
+ if (this.calmMode === calm) return;
68101
+ this.calmMode = calm;
68102
+ this.rebuild();
68103
+ }
67883
68104
  setWidget(key, lines) {
67884
68105
  const normalized = (lines ?? []).map((line) => String(line ?? "")).filter((line) => line.trim().length > 0);
67885
68106
  if (normalized.length === 0) {
@@ -67896,6 +68117,9 @@ var ExtensionWidgetsComponent = class extends Container {
67896
68117
  }
67897
68118
  rebuild() {
67898
68119
  this.clear();
68120
+ if (this.calmMode) {
68121
+ return;
68122
+ }
67899
68123
  for (const lines of this.slots.values()) {
67900
68124
  for (const line of lines) {
67901
68125
  this.addChild(new Text(theme.fg("dim", `\xB7 ${line}`), 1, 0));
@@ -68007,14 +68231,24 @@ var FooterComponent = class {
68007
68231
  if (visibleWidth(pwd) > width) {
68008
68232
  const half = Math.floor(width / 2) - 2;
68009
68233
  if (half > 1) {
68010
- const start = pwd.slice(0, half);
68011
- const end = pwd.slice(-(half - 1));
68012
- pwd = `${start}...${end}`;
68234
+ const total = visibleWidth(pwd);
68235
+ const head = sliceByColumn(pwd, 0, half);
68236
+ const tail = sliceByColumn(pwd, Math.max(0, total - (half - 1)), half - 1);
68237
+ pwd = `${head}...${tail}`;
68013
68238
  } else {
68014
68239
  pwd = truncateToWidth(pwd, width);
68015
68240
  }
68016
68241
  }
68017
68242
  const statsParts = [];
68243
+ if (state.planMode) {
68244
+ statsParts.push(theme.fg("accent", "[PLAN]"));
68245
+ }
68246
+ if (state.calmMode) {
68247
+ statsParts.push(theme.fg("dim", "[CALM]"));
68248
+ }
68249
+ if (state.yoloMode) {
68250
+ statsParts.push(theme.fg("warning", "[YOLO]"));
68251
+ }
68018
68252
  if (state.inputTokens) statsParts.push(`\u2191${formatTokens(state.inputTokens)}`);
68019
68253
  if (state.outputTokens) statsParts.push(`\u2193${formatTokens(state.outputTokens)}`);
68020
68254
  if (state.cacheRead) statsParts.push(`R${formatTokens(state.cacheRead)}`);
@@ -68032,12 +68266,6 @@ var FooterComponent = class {
68032
68266
  statsParts.push(percentStr);
68033
68267
  }
68034
68268
  }
68035
- if (state.planMode) {
68036
- statsParts.push(theme.fg("accent", "[PLAN]"));
68037
- }
68038
- if (state.yoloMode) {
68039
- statsParts.push(theme.fg("warning", "[YOLO]"));
68040
- }
68041
68269
  const statusColor = {
68042
68270
  ready: "dim",
68043
68271
  thinking: "accent",
@@ -68078,8 +68306,8 @@ var FooterComponent = class {
68078
68306
  const availableForRight = width - statsLeftWidth - minPadding;
68079
68307
  if (availableForRight > 3) {
68080
68308
  const plainRight = rightSide.replace(/\x1b\[[0-9;]*m/g, "");
68081
- const truncated = plainRight.substring(0, availableForRight);
68082
- const padding = " ".repeat(width - statsLeftWidth - truncated.length);
68309
+ const truncated = truncateToWidth(plainRight, availableForRight, "");
68310
+ const padding = " ".repeat(Math.max(0, width - statsLeftWidth - visibleWidth(truncated)));
68083
68311
  statsLine = statsLeft + padding + truncated;
68084
68312
  } else {
68085
68313
  statsLine = truncateToWidth(statsLeft, width);
@@ -68096,13 +68324,27 @@ var FooterComponent = class {
68096
68324
  // src/components/todo-widget.ts
68097
68325
  var TodoWidgetComponent = class extends Container {
68098
68326
  state;
68327
+ calmMode = false;
68099
68328
  constructor(state) {
68100
68329
  super();
68101
68330
  this.state = state;
68102
68331
  void this.refresh();
68103
68332
  }
68333
+ /** Calm mode hides the progress widget (questions/answers only). */
68334
+ setCalmMode(calm) {
68335
+ if (this.calmMode === calm) return;
68336
+ this.calmMode = calm;
68337
+ if (calm) {
68338
+ this.clear();
68339
+ } else {
68340
+ void this.refresh();
68341
+ }
68342
+ }
68104
68343
  async refresh() {
68105
68344
  this.clear();
68345
+ if (this.calmMode) {
68346
+ return;
68347
+ }
68106
68348
  const lines = renderExecutionWidgetLines(this.state.getWidgetSteps());
68107
68349
  if (lines.length === 0) {
68108
68350
  return;
@@ -68390,6 +68632,7 @@ function setupUI(host) {
68390
68632
  modelName: host.options.model ? `${host.options.model.provider}/${host.options.model.id}` : "unknown",
68391
68633
  status: "ready",
68392
68634
  planMode: host.planModeEnabled,
68635
+ calmMode: host.calmMode,
68393
68636
  yoloMode: Boolean(host.yoloMode),
68394
68637
  thinkingLevel: host.thinkingLevel,
68395
68638
  contextWindow: host.options.model?.contextWindow ?? 0
@@ -68918,7 +69161,7 @@ async function recoverHardStalledTurn(host, stalledMs) {
68918
69161
  void host.refreshTodoWidget();
68919
69162
  host.footer.setState({ status: "ready" });
68920
69163
  host.safeRequestRender();
68921
- host.ringAttentionBell?.();
69164
+ host.ringAttentionBell?.("stall-recovery");
68922
69165
  appendWarningSummary(host, "Recovered from a stalled turn (no progress for " + Math.round(stalledMs / 1e3) + "s). The previous turn was interrupted; review its partial output before continuing.");
68923
69166
  if (abortOutcome.terminalSummary) {
68924
69167
  appendWarningSummary(host, abortOutcome.terminalSummary);
@@ -69175,7 +69418,7 @@ function createInteractiveAgentSessionController(host) {
69175
69418
  host.showStatus("Todo list preserved. Send 'continue' when you want to resume it.");
69176
69419
  }
69177
69420
  host.showError(event.failure.message);
69178
- host.ringAttentionBell?.();
69421
+ host.ringAttentionBell?.("turn-failed");
69179
69422
  host.setControllerBusy(false);
69180
69423
  } else if (event.type === "turn_cancelled") {
69181
69424
  const outcome = host.executionState.abort();
@@ -69554,7 +69797,7 @@ function installInteractiveInteractionHandler(host) {
69554
69797
  host.activeInteractionCount++;
69555
69798
  host.setWaitingForUser(true);
69556
69799
  host.footer.setState({ lifecycleEvent: "waiting_for_user_input" });
69557
- host.ringAttentionBell?.();
69800
+ host.ringAttentionBell?.("interaction");
69558
69801
  host.safeRequestRender();
69559
69802
  };
69560
69803
  const endWaitingForUser = () => {
@@ -70164,6 +70407,8 @@ var InteractiveMode = class {
70164
70407
  mentionPickerOptions = [];
70165
70408
  attachmentResolver;
70166
70409
  yoloMode = false;
70410
+ /** Calm mode: render only questions and answers (display-only; persisted). */
70411
+ calmMode = false;
70167
70412
  originalTools = [];
70168
70413
  isInterrupting = false;
70169
70414
  isCompacting = false;
@@ -70214,9 +70459,17 @@ var InteractiveMode = class {
70214
70459
  * interaction prompts, turn completion/failure, and stall recovery. The
70215
70460
  * bell itself enforces a cross-call-site cooldown; the settings gate here
70216
70461
  * is the single opt-out point (settings.json `notifications.bell`).
70462
+ *
70463
+ * A desktop notification (OSC 9/777/99, gated by `notifications.desktop`)
70464
+ * rides along so the host terminal app can badge its dock icon / post a
70465
+ * notification-center banner — the persistent marker the bell can't give.
70217
70466
  */
70218
- ringAttentionBell() {
70467
+ ringAttentionBell(reason) {
70219
70468
  ringTerminalBell({ enabled: this.options.settingsManager.getBellNotificationsEnabled() });
70469
+ sendDesktopNotification(reason ?? "turn-settled", {
70470
+ enabled: this.options.settingsManager.getDesktopNotificationsEnabled(),
70471
+ terminal: this.ui.terminal
70472
+ });
70220
70473
  }
70221
70474
  getContinuationTimeoutMs() {
70222
70475
  return getContinuationTimeoutMs();
@@ -70266,6 +70519,7 @@ var InteractiveMode = class {
70266
70519
  this.originalTools = [...options.allTools];
70267
70520
  this.skillsSection = formatSkillsForPrompt(options.skills);
70268
70521
  this.knowledgeSection = buildCliProjectContextSection(options.cwd);
70522
+ this.calmMode = options.settingsManager.getCalmModeEnabled();
70269
70523
  }
70270
70524
  options;
70271
70525
  get runtimeEventHost() {
@@ -70664,6 +70918,25 @@ var InteractiveMode = class {
70664
70918
  async handleYoloCommand(args) {
70665
70919
  return handleYoloCommand(this, args);
70666
70920
  }
70921
+ /**
70922
+ * Toggle calm mode and rebuild the chat so already-rendered rows follow
70923
+ * the new mode. Delivery, tool execution, and session storage are
70924
+ * unaffected — this is purely a presentation change.
70925
+ */
70926
+ setCalmMode(enabled) {
70927
+ if (this.calmMode === enabled) return;
70928
+ this.calmMode = enabled;
70929
+ this.options.settingsManager.setCalmModeEnabled(enabled);
70930
+ this.footer.setState({ calmMode: enabled });
70931
+ applyCalmMode(this, enabled);
70932
+ this.todoWidget.setCalmMode(enabled);
70933
+ this.extensionWidgets.setCalmMode(enabled);
70934
+ this.showStatus(`Calm mode ${enabled ? "on \u2014 showing questions and answers only" : "off"}`);
70935
+ this.safeRequestRender();
70936
+ }
70937
+ async handleCalmCommand(args) {
70938
+ return handleCalmCommand(this, args);
70939
+ }
70667
70940
  setThinkingLevel(level) {
70668
70941
  return setThinkingLevel(this, level);
70669
70942
  }
@@ -72925,6 +73198,7 @@ async function startChat(initialPrompt, testCommand) {
72925
73198
  }
72926
73199
  flushEarlyLogs();
72927
73200
  if (testCommand) {
73201
+ await interactive.run();
72928
73202
  console.log(`[TEST] Auto-executing command: ${testCommand}`);
72929
73203
  await new Promise((resolve7) => setTimeout(resolve7, 500));
72930
73204
  await interactive.executeCommand(testCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,9 +18,9 @@
18
18
  "test": "npm run test:offline",
19
19
  "pretest:offline": "npm run build --workspace @pie/agent-framework && npm run build --workspace @pie/shared-headless-capabilities && npm run build",
20
20
  "posttest:offline": "node --test --import tsx test/remote-model-catalog.test.ts",
21
- "test:offline": "node --test --import tsx test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-extension.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/extensions-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/daily-agent-smoke.terminal.test.ts",
21
+ "test:offline": "node --test --import tsx test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-extension.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/extensions-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/calm-mode.test.ts test/project-root.test.ts test/daily-agent-smoke.terminal.test.ts",
22
22
  "test:all": "npm run test:offline && npm run test:terminal:vitest",
23
- "test:terminal:vitest": "vitest run test/mention-file-select.terminal.test.ts test/tool-partial-display.terminal.test.ts test/viewport-position-final.test.ts --no-file-parallelism",
23
+ "test:terminal:vitest": "vitest run test/mention-file-select.terminal.test.ts test/tool-partial-display.terminal.test.ts test/viewport-position-final.test.ts test/resume-header.terminal.test.ts --no-file-parallelism",
24
24
  "test:todo-tui": "PIE_REAL_TODO_TUI=1 node --test --import tsx test/todo-closure.terminal.test.ts",
25
25
  "test:daily-tui": "npm run test:daily-tui:quick",
26
26
  "test:daily-tui:quick": "PIE_REAL_DAILY_TUI=1 PIE_DAILY_TUI_LEVEL=quick node --test --import tsx test/daily-agent-smoke.terminal.test.ts",