msapling 2.3.6-beta.61 → 2.3.6-beta.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +419 -148
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -19262,6 +19262,105 @@ var init_src3 = __esm({
19262
19262
  }
19263
19263
  });
19264
19264
 
19265
+ // src/runtime/terminalScreen.ts
19266
+ function sanitizeTerminalText(value, preserveNewlines = true) {
19267
+ const text = String(value ?? "");
19268
+ const escape = String.fromCharCode(27);
19269
+ const bell = String.fromCharCode(7);
19270
+ const withoutEscapes = text.replace(new RegExp(`${escape}\\](?:[^${bell}${escape}]|${escape}(?!\\\\))*?(?:${bell}|${escape}\\\\)`, "g"), "").replace(new RegExp(`${escape}P(?:[^${escape}]|${escape}(?!\\\\))*?(?:${escape}\\\\)`, "g"), "").replace(new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, "g"), "").replace(new RegExp(`${escape}[()][0-2A-Z]`, "g"), "").replace(new RegExp(`${escape}.`, "g"), "");
19271
+ let safe = "";
19272
+ for (const character of withoutEscapes) {
19273
+ const code = character.codePointAt(0) ?? 0;
19274
+ const isNewline = code === 10;
19275
+ const isControl = code < 32 || code === 127;
19276
+ if (isControl && (!preserveNewlines || !isNewline)) continue;
19277
+ safe += character;
19278
+ }
19279
+ return safe;
19280
+ }
19281
+ function enterAlternateScreen(output = process.stdout, env = process.env) {
19282
+ if (alternateScreenActive || !output.isTTY || env.MSAPLING_NO_ALT_SCREEN === "1" || env.TERM === "dumb") return false;
19283
+ output.write(ENTER_ALTERNATE_SCREEN + (env.MSAPLING_NO_MOUSE_SCROLL === "1" ? "" : ENABLE_MOUSE_SCROLL));
19284
+ alternateScreenActive = true;
19285
+ return true;
19286
+ }
19287
+ function isSgrMouseReport(input) {
19288
+ const escape = String.fromCharCode(27);
19289
+ return new RegExp(`${escape}\\[<[0-9]+;[0-9]+;[0-9]+[mM]`).test(input);
19290
+ }
19291
+ function parseMouseWheel(input) {
19292
+ const escape = String.fromCharCode(27);
19293
+ const match = new RegExp(`${escape}\\[<([0-9]+);[0-9]+;[0-9]+[mM]`).exec(input);
19294
+ if (!match) return null;
19295
+ const button = Number(match[1]);
19296
+ if (button === 64) return "up";
19297
+ if (button === 65) return "down";
19298
+ return null;
19299
+ }
19300
+ function leaveAlternateScreen(output = process.stdout) {
19301
+ if (!alternateScreenActive) return false;
19302
+ output.write(LEAVE_ALTERNATE_SCREEN);
19303
+ alternateScreenActive = false;
19304
+ return true;
19305
+ }
19306
+ var ENTER_ALTERNATE_SCREEN, ENABLE_MOUSE_SCROLL, LEAVE_ALTERNATE_SCREEN, alternateScreenActive, MAX_PROTOCOL_BUFFER, TerminalInputDecoder;
19307
+ var init_terminalScreen = __esm({
19308
+ "src/runtime/terminalScreen.ts"() {
19309
+ "use strict";
19310
+ init_esm_shims();
19311
+ ENTER_ALTERNATE_SCREEN = "\x1B[?1049h\x1B[H";
19312
+ ENABLE_MOUSE_SCROLL = "\x1B[?1000h\x1B[?1006h";
19313
+ LEAVE_ALTERNATE_SCREEN = "\x1B[?1006l\x1B[?1000l\x1B[?1049l\x1B[?25h";
19314
+ alternateScreenActive = false;
19315
+ MAX_PROTOCOL_BUFFER = 64 * 1024;
19316
+ TerminalInputDecoder = class {
19317
+ pending = "";
19318
+ inBracketedPaste = false;
19319
+ feed(input) {
19320
+ const escape = String.fromCharCode(27);
19321
+ if (this.inBracketedPaste) {
19322
+ const candidate2 = this.pending + input;
19323
+ const endMarker = `${escape}[201~`;
19324
+ const end = candidate2.indexOf(endMarker);
19325
+ if (end < 0) {
19326
+ if (candidate2.length > MAX_PROTOCOL_BUFFER) this.reset();
19327
+ else this.pending = candidate2;
19328
+ return { protocol: true, text: "", mouseWheel: null };
19329
+ }
19330
+ const text = candidate2.slice(0, end);
19331
+ const remainder = candidate2.slice(end + endMarker.length);
19332
+ this.reset();
19333
+ return { protocol: true, text: text + remainder, mouseWheel: null };
19334
+ }
19335
+ const candidate = this.pending + input;
19336
+ this.pending = "";
19337
+ const pasteStart = `${escape}[200~`;
19338
+ if (candidate.startsWith(pasteStart)) {
19339
+ this.inBracketedPaste = true;
19340
+ const remainder = candidate.slice(pasteStart.length);
19341
+ return this.feed(remainder);
19342
+ }
19343
+ if (!candidate.startsWith(escape)) return { protocol: false, text: input, mouseWheel: null };
19344
+ const wheel = parseMouseWheel(candidate);
19345
+ if (wheel || isSgrMouseReport(candidate)) {
19346
+ return { protocol: true, text: "", mouseWheel: wheel };
19347
+ }
19348
+ const sgrPrefix = `${escape}[<`;
19349
+ const isPartialSgr = sgrPrefix.startsWith(candidate) || candidate.startsWith(sgrPrefix);
19350
+ if (isPartialSgr && candidate.length <= MAX_PROTOCOL_BUFFER) {
19351
+ this.pending = candidate;
19352
+ return { protocol: true, text: "", mouseWheel: null };
19353
+ }
19354
+ return { protocol: true, text: "", mouseWheel: null };
19355
+ }
19356
+ reset() {
19357
+ this.pending = "";
19358
+ this.inBracketedPaste = false;
19359
+ }
19360
+ };
19361
+ }
19362
+ });
19363
+
19265
19364
  // src/runtime/errorPresentation.ts
19266
19365
  function errorRecord(error) {
19267
19366
  return error && typeof error === "object" ? error : {};
@@ -19385,19 +19484,20 @@ function renderCliError(error) {
19385
19484
  tool: "Local tool guardrail",
19386
19485
  cli: "Local CLI"
19387
19486
  }[error.surface];
19388
- return [
19487
+ return sanitizeTerminalText([
19389
19488
  `[${error.code}] ${error.summary}`,
19390
19489
  `Surface: ${surface}`,
19391
19490
  `Why: ${error.explanation}`,
19392
19491
  `Next: ${error.action}`,
19393
19492
  ...error.detail ? [`Detail: ${error.detail}`] : []
19394
- ].join("\n");
19493
+ ].join("\n"));
19395
19494
  }
19396
19495
  var init_errorPresentation = __esm({
19397
19496
  "src/runtime/errorPresentation.ts"() {
19398
19497
  "use strict";
19399
19498
  init_esm_shims();
19400
19499
  init_src3();
19500
+ init_terminalScreen();
19401
19501
  }
19402
19502
  });
19403
19503
 
@@ -19851,7 +19951,6 @@ var init_toggles = __esm({
19851
19951
  sessionToggles = /* @__PURE__ */ new Map([
19852
19952
  ["effort", "medium"],
19853
19953
  // 'low' | 'medium' | 'high'
19854
- ["vimMode", false],
19855
19954
  ["fastMode", false],
19856
19955
  ["simpleMode", false],
19857
19956
  ["filterRegex", null]
@@ -19906,12 +20005,18 @@ var init_toggles = __esm({
19906
20005
  context.addMessage("system", `effort set to: ${arg}`);
19907
20006
  }
19908
20007
  };
19909
- vimCommand = makeToggle(
19910
- "vim",
19911
- "vimMode",
19912
- "vim mode",
19913
- "Toggle vim mode for REPL input (vim [on|off] to set explicitly)"
19914
- );
20008
+ vimCommand = {
20009
+ name: "vim",
20010
+ args: "[on|off]",
20011
+ description: "Show Vim editor-mode availability (not yet implemented)",
20012
+ category: "config",
20013
+ handler: (_args, context) => {
20014
+ context.addMessage(
20015
+ "system",
20016
+ "Vim input mode is not available yet. /vim does not change editor behavior; use the standard cursor and history keys shown by /shortcuts."
20017
+ );
20018
+ }
20019
+ };
19915
20020
  fastCommand = makeToggle(
19916
20021
  "fast",
19917
20022
  "fastMode",
@@ -23665,7 +23770,7 @@ var init_version = __esm({
23665
23770
  description: "Show version information for CLI and core packages",
23666
23771
  category: "debug",
23667
23772
  handler: async (_args, context) => {
23668
- const cliVersion = true ? "2.3.6-beta.61" : "(dev)";
23773
+ const cliVersion = true ? "2.3.6-beta.62" : "(dev)";
23669
23774
  const coreVersion = true ? "2.3.6-beta.43" : "(dev)";
23670
23775
  const runtime = process.version;
23671
23776
  context.addMessage("system", "MSapling Version Info");
@@ -23939,7 +24044,7 @@ var init_shortcuts = __esm({
23939
24044
  shortcutsCommand = {
23940
24045
  name: "shortcuts",
23941
24046
  aliases: ["sc"],
23942
- description: "List all registered slash commands with descriptions and aliases",
24047
+ description: "List keyboard controls and registered slash commands",
23943
24048
  category: "debug",
23944
24049
  handler: (args2, context) => {
23945
24050
  const { commands: commands2 } = (init_commands(), __toCommonJS(commands_exports));
@@ -23975,7 +24080,19 @@ ${matching.length} command(s) found.`);
23975
24080
  ...CATEGORY_ORDER2.filter((c) => grouped[c]),
23976
24081
  ...Object.keys(grouped).filter((c) => !CATEGORY_ORDER2.includes(c)).sort()
23977
24082
  ];
23978
- context.addMessage("system", "Registered Slash Commands");
24083
+ context.addMessage("system", [
24084
+ "Keyboard Controls",
24085
+ " \u2191/\u2193 (empty prompt) Scroll transcript one row",
24086
+ " PageUp/PageDown Scroll transcript one page",
24087
+ " Ctrl+E Return to newest transcript row",
24088
+ " \u2190/\u2192 Move the prompt cursor",
24089
+ " Ctrl+A Move to start of prompt",
24090
+ " Ctrl+P/Ctrl+N Previous/next prompt history",
24091
+ " Shift+Enter Insert a newline",
24092
+ " Escape/Ctrl+C Cancel current operation",
24093
+ "",
24094
+ "Registered Slash Commands"
24095
+ ].join("\n"));
23979
24096
  context.addMessage("system", tableSep());
23980
24097
  for (const cat of cats) {
23981
24098
  const cmds = grouped[cat].sort((a, b) => a.name.localeCompare(b.name));
@@ -29692,7 +29809,7 @@ import { render } from "ink";
29692
29809
 
29693
29810
  // src/App.tsx
29694
29811
  init_esm_shims();
29695
- import { useState as useState5, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
29812
+ import { useState as useState5, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2 } from "react";
29696
29813
  import { randomUUID as randomUUID10 } from "crypto";
29697
29814
  import { Box as Box7, Text as Text7, useApp, useInput as useInput4, useStdout } from "ink";
29698
29815
 
@@ -29702,11 +29819,11 @@ import { Box, Text } from "ink";
29702
29819
  import { jsx, jsxs } from "react/jsx-runtime";
29703
29820
  var Header = ({ compact: compact2 = false }) => compact2 ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
29704
29821
  "\u25CF MSapling v",
29705
- "2.3.6-beta.61"
29706
- ] }) : /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
29822
+ "2.3.6-beta.62"
29823
+ ] }) : /* @__PURE__ */ jsxs(Box, { width: "100%", borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
29707
29824
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
29708
29825
  "\u25CF MSapling CLI v",
29709
- "2.3.6-beta.61"
29826
+ "2.3.6-beta.62"
29710
29827
  ] }),
29711
29828
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
29712
29829
  ] });
@@ -29714,6 +29831,7 @@ var Header = ({ compact: compact2 = false }) => compact2 ? /* @__PURE__ */ jsxs(
29714
29831
  // src/components/Footer.tsx
29715
29832
  init_esm_shims();
29716
29833
  init_src3();
29834
+ init_terminalScreen();
29717
29835
  import { Box as Box2, Text as Text2 } from "ink";
29718
29836
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
29719
29837
  var PRO_TIERS2 = /* @__PURE__ */ new Set(["pro", "monthly", "lifetime", "enterprise", "admin", "superadmin"]);
@@ -29759,18 +29877,21 @@ var Footer = ({ user, options, model, project, chatId, cwd, mode, lastCost, sess
29759
29877
  const showCapMeter = user ? shouldShowCapMeter(user) : false;
29760
29878
  const dailyPct = user && showCapMeter ? Math.min(100, Math.round(user.daily_tokens_used / user.daily_tokens_limit * 100)) : 0;
29761
29879
  const standaloneSearchCost = getSessionStats().snapshot().standaloneSearch.estimatedCostUsd;
29762
- return /* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, flexDirection: "column", children: [
29880
+ const safeModel = sanitizeTerminalText(model, false);
29881
+ const safeProject = sanitizeTerminalText(project, false);
29882
+ const safeChatId = chatId ? sanitizeTerminalText(chatId, false) : null;
29883
+ return /* @__PURE__ */ jsxs2(Box2, { width: "100%", borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, flexDirection: "column", children: [
29763
29884
  /* @__PURE__ */ jsxs2(Box2, { justifyContent: "space-between", children: [
29764
29885
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: "CLI telemetry" }),
29765
29886
  /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: formatAccountSummary(user, { billingProfile, isStale, usageError }) })
29766
29887
  ] }),
29767
29888
  expanded && options.chat && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
29768
29889
  "project: ",
29769
- project,
29890
+ safeProject,
29770
29891
  " chat: ",
29771
- chatId ?? "none",
29892
+ safeChatId ?? "none",
29772
29893
  " model: ",
29773
- model
29894
+ safeModel
29774
29895
  ] }),
29775
29896
  options.location && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
29776
29897
  "cwd: ",
@@ -29808,6 +29929,7 @@ function promptLabel(user) {
29808
29929
 
29809
29930
  // src/components/ApprovalDialog.tsx
29810
29931
  init_esm_shims();
29932
+ init_terminalScreen();
29811
29933
  import { useState, useMemo } from "react";
29812
29934
  import { Box as Box3, Text as Text3, useInput } from "ink";
29813
29935
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -29873,7 +29995,11 @@ var ApprovalDialog = ({
29873
29995
  onResolve
29874
29996
  }) => {
29875
29997
  const [showFullDiff, setShowFullDiff] = useState(false);
29876
- const { hasDiff, diffText, title } = useMemo(() => extractDiffFromCommand(command, diff), [command, diff]);
29998
+ const safeCommand = sanitizeTerminalText(command, false);
29999
+ const safeReason = sanitizeTerminalText(reason);
30000
+ const safePath = targetPath ? sanitizeTerminalText(targetPath, false) : void 0;
30001
+ const safeDiff = diff ? sanitizeTerminalText(diff) : void 0;
30002
+ const { hasDiff, diffText, title } = useMemo(() => extractDiffFromCommand(safeCommand, safeDiff), [safeCommand, safeDiff]);
29877
30003
  const diffLines = useMemo(() => parseDiffLines(diffText), [diffText]);
29878
30004
  useInput((input, key) => {
29879
30005
  if (key.escape) {
@@ -29895,16 +30021,16 @@ var ApprovalDialog = ({
29895
30021
  const isTruncated = !showFullDiff && diffLines.length > MAX_DIFF_COLLAPSED_LINES;
29896
30022
  return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "double", borderColor: "yellow", paddingX: 1, marginY: 1, flexDirection: "column", children: [
29897
30023
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: "SECURITY APPROVAL REQUIRED" }),
29898
- /* @__PURE__ */ jsx3(Text3, { italic: true, dimColor: true, children: reason }),
30024
+ /* @__PURE__ */ jsx3(Text3, { italic: true, dimColor: true, children: safeReason }),
29899
30025
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "gray", children: /* @__PURE__ */ jsxs3(Text3, { color: "white", children: [
29900
30026
  "$ ",
29901
- command
30027
+ safeCommand
29902
30028
  ] }) }),
29903
30029
  hasDiff && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [
29904
30030
  /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", children: [
29905
30031
  /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "cyan", children: [
29906
30032
  "DIFF REVIEW ",
29907
- targetPath || title ? `(${targetPath || title})` : "",
30033
+ safePath || title ? `(${safePath || sanitizeTerminalText(title, false)})` : "",
29908
30034
  ":"
29909
30035
  ] }),
29910
30036
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
@@ -29955,6 +30081,7 @@ var ApprovalDialog = ({
29955
30081
 
29956
30082
  // src/components/AskUserQuestion.tsx
29957
30083
  init_esm_shims();
30084
+ init_terminalScreen();
29958
30085
  import { useState as useState2 } from "react";
29959
30086
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
29960
30087
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -29989,7 +30116,7 @@ var AskUserQuestion = ({ question, options, multiSelect, onResolve }) => {
29989
30116
  });
29990
30117
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
29991
30118
  /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "QUESTION FROM AGENT" }),
29992
- /* @__PURE__ */ jsx4(Text4, { children: question }),
30119
+ /* @__PURE__ */ jsx4(Text4, { children: sanitizeTerminalText(question) }),
29993
30120
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: options.map((opt, i) => {
29994
30121
  const isHighlighted = i === selectedIndex;
29995
30122
  const isSelected = selectedIndices.has(i);
@@ -30000,9 +30127,9 @@ var AskUserQuestion = ({ question, options, multiSelect, onResolve }) => {
30000
30127
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
30001
30128
  /* @__PURE__ */ jsxs4(Text4, { color: isHighlighted ? "blue" : "white", bold: isHighlighted, children: [
30002
30129
  marker,
30003
- opt.text
30130
+ sanitizeTerminalText(opt.text, false)
30004
30131
  ] }),
30005
- isHighlighted && opt.preview && /* @__PURE__ */ jsx4(Box4, { marginLeft: 4, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: opt.preview }) })
30132
+ isHighlighted && opt.preview && /* @__PURE__ */ jsx4(Box4, { marginLeft: 4, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: sanitizeTerminalText(opt.preview) }) })
30006
30133
  ] }, i);
30007
30134
  }) }),
30008
30135
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
@@ -30015,37 +30142,12 @@ var AskUserQuestion = ({ question, options, multiSelect, onResolve }) => {
30015
30142
 
30016
30143
  // src/components/VirtualizedMessageList.tsx
30017
30144
  init_esm_shims();
30145
+ init_terminalScreen();
30018
30146
  import React3 from "react";
30019
30147
  import { Box as Box5, Text as Text5 } from "ink";
30148
+ import stringWidth from "string-width";
30149
+ import stripAnsi2 from "strip-ansi";
30020
30150
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
30021
- function estimateLineCount(text, cols) {
30022
- const effectiveCols = cols > 0 ? cols : 80;
30023
- const physicalLines = text.split("\n");
30024
- let total = 0;
30025
- for (const line of physicalLines) {
30026
- total += line.length === 0 ? 1 : Math.ceil(line.length / effectiveCols);
30027
- }
30028
- return total;
30029
- }
30030
- function computeViewport(messages, visibleLines, termColumns, offsetFromEnd = 0) {
30031
- const cols = termColumns > 0 ? termColumns : 80;
30032
- const budget = Math.max(visibleLines, 1);
30033
- let linesUsed = 0;
30034
- const endIdx = Math.max(0, messages.length - Math.max(0, Math.floor(offsetFromEnd)));
30035
- let startIdx = endIdx;
30036
- for (let i = endIdx - 1; i >= 0; i--) {
30037
- const contentLines = estimateLineCount(messages[i].content, cols);
30038
- const msgCost = contentLines + 2;
30039
- if (linesUsed + msgCost > budget) break;
30040
- linesUsed += msgCost;
30041
- startIdx = i;
30042
- }
30043
- return {
30044
- displayMessages: messages.slice(startIdx, endIdx),
30045
- hiddenCount: startIdx,
30046
- hiddenAfter: messages.length - endIdx
30047
- };
30048
- }
30049
30151
  var ROLE_COLOR = {
30050
30152
  user: "green",
30051
30153
  assistant: "cyan",
@@ -30058,6 +30160,96 @@ var ROLE_LABEL = {
30058
30160
  error: "Error: ",
30059
30161
  system: ""
30060
30162
  };
30163
+ function cleanTerminalText(value) {
30164
+ return sanitizeTerminalText(stripAnsi2(value)).replace(/\r\n?/g, "\n").replace(/\t/g, " ");
30165
+ }
30166
+ function graphemes(value) {
30167
+ if (typeof Intl.Segmenter === "function") {
30168
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
30169
+ return Array.from(segmenter.segment(value), (segment) => segment.segment);
30170
+ }
30171
+ return Array.from(value);
30172
+ }
30173
+ function wrapTerminalLine(value, columns) {
30174
+ const width = Math.max(1, Math.floor(columns));
30175
+ if (value.length === 0) return [""];
30176
+ const rows = [];
30177
+ let row3 = "";
30178
+ let rowWidth = 0;
30179
+ for (const grapheme of graphemes(value)) {
30180
+ const cellWidth = Math.max(0, stringWidth(grapheme));
30181
+ if (row3.length > 0 && rowWidth + cellWidth > width) {
30182
+ rows.push(row3);
30183
+ row3 = "";
30184
+ rowWidth = 0;
30185
+ }
30186
+ row3 += grapheme;
30187
+ rowWidth += cellWidth;
30188
+ }
30189
+ rows.push(row3);
30190
+ return rows;
30191
+ }
30192
+ function buildTranscriptRows(messages, termColumns, compact2 = false) {
30193
+ const columns = termColumns > 0 ? Math.floor(termColumns) : 80;
30194
+ const separator = "\u2500".repeat(Math.max(1, Math.min(columns, 60)));
30195
+ const rows = [];
30196
+ messages.forEach((message2, messageIndex) => {
30197
+ if (compact2 && messageIndex > 0) {
30198
+ rows.push({ messageIndex, role: message2.role, prefix: "", content: separator, separator: true });
30199
+ }
30200
+ const label = ROLE_LABEL[message2.role];
30201
+ const labelWidth = stringWidth(label);
30202
+ const contentWidth = Math.max(1, columns - labelWidth);
30203
+ const continuationPrefix = " ".repeat(labelWidth);
30204
+ let firstVisualRow = true;
30205
+ for (const physicalLine of cleanTerminalText(message2.content).split("\n")) {
30206
+ const wrapped = wrapTerminalLine(physicalLine, contentWidth);
30207
+ for (const content of wrapped) {
30208
+ rows.push({
30209
+ messageIndex,
30210
+ role: message2.role,
30211
+ prefix: firstVisualRow ? label : continuationPrefix,
30212
+ content
30213
+ });
30214
+ firstVisualRow = false;
30215
+ }
30216
+ }
30217
+ if (!compact2 && message2.role === "assistant") {
30218
+ rows.push({ messageIndex, role: message2.role, prefix: "", content: "", margin: true });
30219
+ }
30220
+ });
30221
+ return rows;
30222
+ }
30223
+ function uniqueMessageCount(rows) {
30224
+ return new Set(rows.map((row3) => row3.messageIndex)).size;
30225
+ }
30226
+ function computeViewport(messages, visibleLines, termColumns, offsetFromEnd = 0, compact2 = false) {
30227
+ const allRows = buildTranscriptRows(messages, termColumns, compact2);
30228
+ const totalRows = allRows.length;
30229
+ const budget = Math.max(1, Math.floor(visibleLines));
30230
+ const hiddenRowsAfter = Math.min(totalRows, Math.max(0, Math.floor(offsetFromEnd)));
30231
+ const end = totalRows - hiddenRowsAfter;
30232
+ let contentBudget = budget;
30233
+ let start = Math.max(0, end - contentBudget);
30234
+ if (start > 0 || hiddenRowsAfter > 0) {
30235
+ contentBudget = Math.max(0, budget - 1);
30236
+ start = Math.max(0, end - contentBudget);
30237
+ }
30238
+ const displayRows = allRows.slice(start, end);
30239
+ const visibleIndices = [...new Set(displayRows.map((row3) => row3.messageIndex))];
30240
+ const displayMessages = visibleIndices.map((index) => messages[index]);
30241
+ const beforeRows = allRows.slice(0, start);
30242
+ const afterRows = allRows.slice(end);
30243
+ return {
30244
+ displayMessages,
30245
+ displayRows,
30246
+ hiddenCount: uniqueMessageCount(beforeRows),
30247
+ hiddenAfter: uniqueMessageCount(afterRows),
30248
+ hiddenRowsBefore: start,
30249
+ hiddenRowsAfter,
30250
+ totalRows
30251
+ };
30252
+ }
30061
30253
  var VirtualizedMessageList = ({
30062
30254
  messages,
30063
30255
  visibleLines,
@@ -30065,45 +30257,27 @@ var VirtualizedMessageList = ({
30065
30257
  compact: compact2 = false,
30066
30258
  offsetFromEnd = 0
30067
30259
  }) => {
30068
- const { displayMessages, hiddenCount, hiddenAfter } = React3.useMemo(
30069
- () => computeViewport(messages, visibleLines, termColumns, offsetFromEnd),
30070
- [messages, visibleLines, termColumns, offsetFromEnd]
30260
+ const { displayRows, hiddenRowsBefore, hiddenRowsAfter } = React3.useMemo(
30261
+ () => computeViewport(messages, visibleLines, termColumns, offsetFromEnd, compact2),
30262
+ [messages, visibleLines, termColumns, offsetFromEnd, compact2]
30071
30263
  );
30072
- const separatorWidth = termColumns > 0 ? Math.min(termColumns, 60) : 60;
30073
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, children: [
30074
- (hiddenCount > 0 || hiddenAfter > 0) && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `[\u2191 ${hiddenCount} older \xB7 \u2193 ${hiddenAfter} newer \xB7 \u2191/\u2193 or PageUp/PageDown \xB7 Ctrl+E latest]` }),
30075
- displayMessages.map((msg, i) => /* @__PURE__ */ jsxs5(
30076
- Box5,
30077
- {
30078
- flexDirection: "column",
30079
- marginBottom: compact2 ? 0 : msg.role === "assistant" ? 1 : 0,
30080
- children: [
30081
- compact2 && i > 0 && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500".repeat(separatorWidth) }),
30082
- /* @__PURE__ */ jsxs5(Box5, { children: [
30083
- /* @__PURE__ */ jsx5(
30084
- Text5,
30085
- {
30086
- color: ROLE_COLOR[msg.role],
30087
- bold: msg.role !== "assistant",
30088
- children: ROLE_LABEL[msg.role]
30089
- }
30090
- ),
30091
- /* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: msg.content })
30092
- ] })
30093
- ]
30094
- },
30095
- i
30096
- ))
30264
+ return /* @__PURE__ */ jsxs5(Box5, { width: "100%", flexDirection: "column", flexGrow: 1, children: [
30265
+ (hiddenRowsBefore > 0 || hiddenRowsAfter > 0) && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `[\u2191 ${hiddenRowsBefore} rows \xB7 \u2193 ${hiddenRowsAfter} \xB7 PgUp/PgDn \xB7 Ctrl+E latest]` }),
30266
+ displayRows.map((row3, index) => row3.separator ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: row3.content }, `${row3.messageIndex}-${index}`) : row3.margin ? /* @__PURE__ */ jsx5(Text5, { children: " " }, `${row3.messageIndex}-${index}`) : /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate-end", children: [
30267
+ /* @__PURE__ */ jsx5(Text5, { color: ROLE_COLOR[row3.role], bold: row3.role !== "assistant", children: row3.prefix }),
30268
+ row3.content
30269
+ ] }, `${row3.messageIndex}-${index}`))
30097
30270
  ] });
30098
30271
  };
30099
30272
 
30100
30273
  // src/App.tsx
30274
+ init_terminalScreen();
30101
30275
  init_src();
30102
30276
  init_src3();
30103
30277
 
30104
30278
  // src/ui/TextInput.tsx
30105
30279
  init_esm_shims();
30106
- import { useState as useState3, useEffect } from "react";
30280
+ import React4, { useState as useState3, useEffect } from "react";
30107
30281
  import { Box as Box6, Text as Text6, useInput as useInput3 } from "ink";
30108
30282
 
30109
30283
  // src/state/commandHandler.ts
@@ -30690,7 +30864,42 @@ ${finalCmd}`;
30690
30864
  }
30691
30865
 
30692
30866
  // src/ui/TextInput.tsx
30867
+ init_terminalScreen();
30693
30868
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
30869
+ function sanitizePromptInput(value) {
30870
+ return sanitizeTerminalText(value);
30871
+ }
30872
+ function cursorBoundaries(value) {
30873
+ if (typeof Intl.Segmenter !== "function") {
30874
+ return Array.from({ length: value.length + 1 }, (_, index) => index);
30875
+ }
30876
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
30877
+ return [0, ...Array.from(segmenter.segment(value), (segment) => segment.index + segment.segment.length)];
30878
+ }
30879
+ function moveCursorLeft(value, cursor) {
30880
+ const boundaries = cursorBoundaries(value);
30881
+ return boundaries[Math.max(0, boundaries.findIndex((boundary) => boundary >= cursor) - 1)] ?? 0;
30882
+ }
30883
+ function moveCursorRight(value, cursor) {
30884
+ const boundaries = cursorBoundaries(value);
30885
+ return boundaries.find((boundary) => boundary > cursor) ?? value.length;
30886
+ }
30887
+ function moveCursorWordLeft(value, cursor) {
30888
+ const boundaries = cursorBoundaries(value);
30889
+ let index = boundaries.findIndex((boundary) => boundary >= cursor);
30890
+ if (index < 0) index = boundaries.length - 1;
30891
+ while (index > 0 && /\s/.test(value.slice(boundaries[index - 1], boundaries[index]))) index--;
30892
+ while (index > 0 && !/\s/.test(value.slice(boundaries[index - 1], boundaries[index]))) index--;
30893
+ return boundaries[index] ?? 0;
30894
+ }
30895
+ function moveCursorWordRight(value, cursor) {
30896
+ const boundaries = cursorBoundaries(value);
30897
+ let index = boundaries.findIndex((boundary) => boundary > cursor);
30898
+ if (index < 0) return value.length;
30899
+ while (index < boundaries.length - 1 && !/\s/.test(value.slice(boundaries[index], boundaries[index + 1]))) index++;
30900
+ while (index < boundaries.length - 1 && /\s/.test(value.slice(boundaries[index], boundaries[index + 1]))) index++;
30901
+ return boundaries[index] ?? value.length;
30902
+ }
30694
30903
  var TextInput = ({
30695
30904
  value,
30696
30905
  onChange,
@@ -30702,11 +30911,24 @@ var TextInput = ({
30702
30911
  transcriptNavigationActive = false
30703
30912
  }) => {
30704
30913
  const [history, setHistory] = useState3([]);
30914
+ const terminalInputDecoder = React4.useRef(new TerminalInputDecoder());
30705
30915
  const [historyIndex, setHistoryIndex] = useState3(-1);
30916
+ const [cursor, setCursor] = useState3(value.length);
30706
30917
  useEffect(() => {
30707
30918
  storage.loadHistory().then((entries) => setHistory(filterSafeHistory(entries)));
30708
30919
  }, [storage]);
30920
+ useEffect(() => {
30921
+ setCursor((current) => Math.min(current, value.length));
30922
+ }, [value]);
30709
30923
  useInput3((input, key) => {
30924
+ const decoded = terminalInputDecoder.current.feed(input);
30925
+ if (decoded.protocol && key.escape) {
30926
+ terminalInputDecoder.current.reset();
30927
+ } else if (decoded.protocol) {
30928
+ if (!decoded.text) return;
30929
+ input = decoded.text;
30930
+ }
30931
+ const extendedKey = key;
30710
30932
  if (key.ctrl && input === "c" || key.escape) {
30711
30933
  onCancel?.();
30712
30934
  return;
@@ -30714,39 +30936,70 @@ var TextInput = ({
30714
30936
  if (disabled) return;
30715
30937
  if (key.return) {
30716
30938
  if (key.shift) {
30717
- onChange(value + "\n");
30939
+ onChange(value.slice(0, cursor) + "\n" + value.slice(cursor));
30940
+ setCursor(cursor + 1);
30718
30941
  } else {
30719
30942
  onSubmit(value);
30720
30943
  onChange("");
30944
+ setCursor(0);
30721
30945
  setHistoryIndex(-1);
30722
30946
  }
30723
- } else if (key.backspace || key.delete) {
30724
- onChange(value.slice(0, -1));
30947
+ } else if (key.backspace) {
30948
+ if (cursor > 0) {
30949
+ onChange(value.slice(0, cursor - 1) + value.slice(cursor));
30950
+ setCursor(cursor - 1);
30951
+ }
30952
+ } else if (key.delete) {
30953
+ if (cursor < value.length) onChange(value.slice(0, cursor) + value.slice(cursor + 1));
30954
+ } else if (extendedKey.home || key.ctrl && input === "a") {
30955
+ setCursor(0);
30956
+ } else if (extendedKey.end) {
30957
+ setCursor(value.length);
30958
+ } else if (key.leftArrow && key.ctrl) {
30959
+ setCursor((current) => moveCursorWordLeft(value, current));
30960
+ } else if (key.rightArrow && key.ctrl) {
30961
+ setCursor((current) => moveCursorWordRight(value, current));
30962
+ } else if (key.leftArrow) {
30963
+ setCursor((current) => moveCursorLeft(value, current));
30964
+ } else if (key.rightArrow) {
30965
+ setCursor((current) => moveCursorRight(value, current));
30725
30966
  } else if (key.ctrl && input === "p" || key.upArrow && !transcriptNavigationActive) {
30726
30967
  const nextIndex = historyIndex + 1;
30727
30968
  if (nextIndex < history.length) {
30728
30969
  setHistoryIndex(nextIndex);
30729
- onChange(history[history.length - 1 - nextIndex]);
30970
+ const entry = history[history.length - 1 - nextIndex];
30971
+ const safeEntry = sanitizePromptInput(entry);
30972
+ onChange(safeEntry);
30973
+ setCursor(safeEntry.length);
30730
30974
  }
30731
30975
  } else if (key.ctrl && input === "n" || key.downArrow && !transcriptNavigationActive) {
30732
30976
  const nextIndex = historyIndex - 1;
30733
30977
  if (nextIndex >= 0) {
30734
30978
  setHistoryIndex(nextIndex);
30735
- onChange(history[history.length - 1 - nextIndex]);
30979
+ const entry = history[history.length - 1 - nextIndex];
30980
+ const safeEntry = sanitizePromptInput(entry);
30981
+ onChange(safeEntry);
30982
+ setCursor(safeEntry.length);
30736
30983
  } else {
30737
30984
  setHistoryIndex(-1);
30738
30985
  onChange("");
30986
+ setCursor(0);
30739
30987
  }
30740
30988
  } else if ((key.upArrow || key.downArrow) && transcriptNavigationActive) {
30741
30989
  return;
30742
30990
  } else if (input && !key.ctrl && !key.meta) {
30743
- onChange(value + input);
30991
+ const safeInput = sanitizePromptInput(input);
30992
+ if (safeInput.length > 0) {
30993
+ onChange(value.slice(0, cursor) + safeInput + value.slice(cursor));
30994
+ setCursor(cursor + safeInput.length);
30995
+ }
30744
30996
  }
30745
30997
  });
30746
- return /* @__PURE__ */ jsxs6(Box6, { children: [
30998
+ return /* @__PURE__ */ jsxs6(Box6, { width: "100%", children: [
30747
30999
  /* @__PURE__ */ jsx6(Text6, { bold: true, color: promptColor, children: "\u276F " }),
30748
- /* @__PURE__ */ jsx6(Text6, { children: value }),
30749
- !disabled && /* @__PURE__ */ jsx6(Text6, { backgroundColor: "white", color: "black", children: " " })
31000
+ /* @__PURE__ */ jsx6(Text6, { children: value.slice(0, cursor) }),
31001
+ !disabled && /* @__PURE__ */ jsx6(Text6, { backgroundColor: "white", color: "black", children: value[cursor] ?? " " }),
31002
+ /* @__PURE__ */ jsx6(Text6, { children: disabled ? value.slice(cursor) : value.slice(cursor + 1) })
30750
31003
  ] });
30751
31004
  };
30752
31005
  var PROMPT_COLOR_BY_MODE = {
@@ -30929,19 +31182,29 @@ init_errorPresentation();
30929
31182
 
30930
31183
  // src/hooks/useTerminalResize.ts
30931
31184
  init_esm_shims();
30932
- import { useState as useState4, useEffect as useEffect2, useCallback } from "react";
30933
- function getCurrentDimensions() {
31185
+ import { useState as useState4, useEffect as useEffect2, useCallback, useRef } from "react";
31186
+ function normalizeTerminalDimensions(columns, rows) {
30934
31187
  return {
30935
- columns: process.stdout.columns ?? 80,
30936
- rows: process.stdout.rows ?? 24
31188
+ columns: Number.isFinite(columns) && columns > 0 ? Math.floor(columns) : 80,
31189
+ rows: Number.isFinite(rows) && rows > 0 ? Math.floor(rows) : 24
30937
31190
  };
30938
31191
  }
31192
+ function getCurrentDimensions() {
31193
+ return normalizeTerminalDimensions(process.stdout.columns ?? 80, process.stdout.rows ?? 24);
31194
+ }
30939
31195
  function useTerminalResize() {
30940
31196
  const [dimensions, setDimensions] = useState4(
30941
31197
  getCurrentDimensions
30942
31198
  );
31199
+ const resizeTimer = useRef(null);
31200
+ const resizeEpoch = useRef(0);
30943
31201
  const handleResize = useCallback(() => {
30944
- setDimensions(getCurrentDimensions());
31202
+ const epoch = ++resizeEpoch.current;
31203
+ if (resizeTimer.current !== null) clearTimeout(resizeTimer.current);
31204
+ resizeTimer.current = setTimeout(() => {
31205
+ resizeTimer.current = null;
31206
+ if (epoch === resizeEpoch.current) setDimensions(getCurrentDimensions());
31207
+ }, 16);
30945
31208
  }, []);
30946
31209
  useEffect2(() => {
30947
31210
  process.stdout.on("resize", handleResize);
@@ -30949,6 +31212,8 @@ function useTerminalResize() {
30949
31212
  process.on("SIGWINCH", onSigwinch);
30950
31213
  handleResize();
30951
31214
  return () => {
31215
+ if (resizeTimer.current !== null) clearTimeout(resizeTimer.current);
31216
+ resizeTimer.current = null;
30952
31217
  process.stdout.off("resize", handleResize);
30953
31218
  process.off("SIGWINCH", onSigwinch);
30954
31219
  };
@@ -31021,6 +31286,7 @@ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
31021
31286
  var App = ({ compact: compact2 = false, continueSession: continueSession2 = false, executionMode: executionMode2 = "remote" }) => {
31022
31287
  const [user, setUser] = useState5(null);
31023
31288
  const [input, setInput] = useState5("");
31289
+ const terminalInputDecoder = useRef2(new TerminalInputDecoder());
31024
31290
  const [history, setHistory] = useState5([]);
31025
31291
  const [historyView, setHistoryView] = useState5(null);
31026
31292
  const [historyOffset, setHistoryOffset] = useState5(0);
@@ -31046,20 +31312,20 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
31046
31312
  const [billingProfile, setBillingProfile] = useState5(executionMode2 === "local" ? "ollama" : "account-metered");
31047
31313
  const [continuityProfile, setContinuityProfile] = useState5(executionMode2 === "local" ? "standalone-private" : "connected-mirrored");
31048
31314
  const [bootstrapState, setBootstrapState] = useState5("loading");
31049
- const submissionLockRef = useRef(false);
31315
+ const submissionLockRef = useRef2(false);
31050
31316
  const { exit } = useApp();
31051
31317
  const { stdout: termStdout } = useStdout();
31052
31318
  const { columns: termResizeCols, rows: termResizeRows } = useTerminalResize();
31053
- const storage = useRef(new StorageManager()).current;
31054
- const client = useRef(new MSaplingClient()).current;
31319
+ const storage = useRef2(new StorageManager()).current;
31320
+ const client = useRef2(new MSaplingClient()).current;
31055
31321
  const remoteEnvironment = describeRemoteEnvironment(client.getApiUrl());
31056
- const modeContract = useRef(new CliModeRuntime({
31322
+ const modeContract = useRef2(new CliModeRuntime({
31057
31323
  mode: executionMode2,
31058
31324
  provider: process.env.MSAPLING_LOCAL_LLM_PROVIDER ?? "ollama"
31059
31325
  })).current;
31060
- const sessionRecovery = useRef(new CliSessionRecovery()).current;
31061
- const checkpointTurns = useRef(/* @__PURE__ */ new Map()).current;
31062
- const agentRef = useRef(null);
31326
+ const sessionRecovery = useRef2(new CliSessionRecovery()).current;
31327
+ const checkpointTurns = useRef2(/* @__PURE__ */ new Map()).current;
31328
+ const agentRef = useRef2(null);
31063
31329
  const requestApproval = useCallback2((request) => {
31064
31330
  agentRef.current?.fireLifecycleHook(
31065
31331
  "notification",
@@ -31070,7 +31336,7 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
31070
31336
  setPendingApproval({ request, resolve: resolve31 });
31071
31337
  });
31072
31338
  }, []);
31073
- const agent = useRef(new Agent(client, process.cwd(), requestApproval, { executionMode: executionMode2, modeContract })).current;
31339
+ const agent = useRef2(new Agent(client, process.cwd(), requestApproval, { executionMode: executionMode2, modeContract })).current;
31074
31340
  agentRef.current = agent;
31075
31341
  const applyStandaloneSyncState = (state) => {
31076
31342
  const localChatId = "local-cli-chat";
@@ -31091,9 +31357,9 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
31091
31357
  setContinuityProfile(profile);
31092
31358
  if (profile === "standalone-private") agent.configureStandaloneSync(null);
31093
31359
  };
31094
- const trustStore = useRef(new TrustStore()).current;
31095
- const lastActivityRef = useRef(Date.now());
31096
- const pollingIntervalRef = useRef(null);
31360
+ const trustStore = useRef2(new TrustStore()).current;
31361
+ const lastActivityRef = useRef2(Date.now());
31362
+ const pollingIntervalRef = useRef2(null);
31097
31363
  useEffect3(() => {
31098
31364
  agent.setApprovalCallback(requestApproval);
31099
31365
  }, [agent, requestApproval]);
@@ -31147,7 +31413,7 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
31147
31413
  setContextBudgetSnap(snapshotBudget(agent.getContextBudget()));
31148
31414
  }, [agent]);
31149
31415
  const getModel = useCallback2(() => activeModel, [activeModel]);
31150
- const cliProjectRef = useRef(null);
31416
+ const cliProjectRef = useRef2(null);
31151
31417
  const setProjectId = useCallback2((id) => {
31152
31418
  if (cliProjectRef.current && id !== cliProjectRef.current) return;
31153
31419
  setActiveProjectId(id);
@@ -31472,7 +31738,7 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31472
31738
  return { journalEvents, checkpoints };
31473
31739
  }
31474
31740
  });
31475
- const relayCommandRef = useRef(handleCommand);
31741
+ const relayCommandRef = useRef2(handleCommand);
31476
31742
  relayCommandRef.current = handleCommand;
31477
31743
  useEffect3(() => {
31478
31744
  if (executionMode2 !== "remote" || process.env.MSAPLING_RELAY_ENABLED !== "1" || !activeProjectId) return;
@@ -31511,21 +31777,45 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31511
31777
  const activityRows = pendingApproval ? 6 : pendingAskUser ? 10 : isRunning ? 2 : 0;
31512
31778
  const visibleLines = Math.max(termHeight - (terminalLayout.fixedRows + footerRows + modeRows + activityRows), 1);
31513
31779
  const displayedHistory = historyView ?? history;
31514
- const historyPageStep = Math.max(1, Math.floor(visibleLines / 3));
31515
- const viewport = computeViewport(displayedHistory, visibleLines, termColumns, historyOffset);
31516
- const canScrollOlder = viewport.hiddenCount > 0;
31780
+ const historyPageStep = Math.max(1, visibleLines - 1);
31781
+ const viewport = computeViewport(displayedHistory, visibleLines, termColumns, historyOffset, compactPresentation);
31782
+ const safeStatus = sanitizeTerminalText(status2, false);
31783
+ const safeProjectId = sanitizeTerminalText(activeProjectId || "none", false);
31784
+ const safeModel = sanitizeTerminalText(activeModel, false);
31785
+ const safeEnvironment = sanitizeTerminalText(remoteEnvironment.label, false);
31786
+ const canScrollOlder = viewport.hiddenRowsBefore > 0;
31517
31787
  const canScrollNewer = historyOffset > 0;
31788
+ const previousTranscriptRowsRef = useRef2(viewport.totalRows);
31789
+ useEffect3(() => {
31790
+ const previousRows = previousTranscriptRowsRef.current;
31791
+ previousTranscriptRowsRef.current = viewport.totalRows;
31792
+ const rowDelta = viewport.totalRows - previousRows;
31793
+ if (historyOffset > 0 && rowDelta !== 0) {
31794
+ setHistoryOffset((current) => Math.min(
31795
+ Math.max(0, viewport.totalRows - 1),
31796
+ Math.max(0, current + rowDelta)
31797
+ ));
31798
+ }
31799
+ }, [historyOffset, viewport.totalRows]);
31518
31800
  const scrollTranscript = useCallback2((direction, page = false) => {
31519
31801
  const amount = page ? historyPageStep : 1;
31520
31802
  if (direction === "up") {
31521
- setHistoryOffset((current) => Math.min(Math.max(0, displayedHistory.length - 1), current + amount));
31803
+ setHistoryOffset((current) => Math.min(Math.max(0, viewport.totalRows - 1), current + amount));
31522
31804
  } else {
31523
31805
  setHistoryOffset((current) => Math.max(0, current - amount));
31524
31806
  }
31525
- }, [displayedHistory.length, historyPageStep]);
31807
+ }, [historyPageStep, viewport.totalRows]);
31526
31808
  useInput4((input2, key) => {
31527
31809
  if (pendingApproval || pendingAskUser) return;
31528
- if (key.pageUp) {
31810
+ const decoded = terminalInputDecoder.current.feed(input2);
31811
+ if (decoded.protocol && !decoded.mouseWheel) return;
31812
+ if (isSgrMouseReport(input2) && !parseMouseWheel(input2)) return;
31813
+ const mouseWheel = decoded.mouseWheel ?? parseMouseWheel(input2);
31814
+ if (mouseWheel === "up") {
31815
+ scrollTranscript("up");
31816
+ } else if (mouseWheel === "down") {
31817
+ scrollTranscript("down");
31818
+ } else if (key.pageUp) {
31529
31819
  scrollTranscript("up", true);
31530
31820
  } else if (key.pageDown) {
31531
31821
  scrollTranscript("down", true);
@@ -31538,9 +31828,9 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31538
31828
  setHistoryView(null);
31539
31829
  }
31540
31830
  });
31541
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", padding: terminalLayout.outerPadding, children: [
31831
+ return /* @__PURE__ */ jsxs7(Box7, { width: termColumns, height: termHeight, flexDirection: "column", padding: terminalLayout.outerPadding, overflow: "hidden", children: [
31542
31832
  /* @__PURE__ */ jsx7(Header, { compact: !terminalLayout.showFramedHeader }),
31543
- /* @__PURE__ */ jsx7(Box7, { marginBottom: compactPresentation ? 0 : 1, children: /* @__PURE__ */ jsx7(
31833
+ /* @__PURE__ */ jsx7(Box7, { width: "100%", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx7(
31544
31834
  VirtualizedMessageList,
31545
31835
  {
31546
31836
  messages: displayedHistory,
@@ -31615,7 +31905,7 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31615
31905
  /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
31616
31906
  /* @__PURE__ */ jsxs7(Text7, { color: executionMode2 === "local" ? "yellow" : "cyan", children: [
31617
31907
  "Execution: ",
31618
- executionMode2 === "local" ? "STANDALONE LOCAL" : remoteEnvironment.label
31908
+ executionMode2 === "local" ? "STANDALONE LOCAL" : safeEnvironment
31619
31909
  ] }),
31620
31910
  /* @__PURE__ */ jsxs7(Text7, { color: billingProfile === "account-metered" ? "cyan" : "yellow", children: [
31621
31911
  "Billing: ",
@@ -31628,15 +31918,15 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31628
31918
  ] }),
31629
31919
  /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
31630
31920
  "Status: ",
31631
- status2
31921
+ safeStatus
31632
31922
  ] }),
31633
31923
  /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
31634
31924
  "Project: ",
31635
- activeProjectId || "none"
31925
+ safeProjectId
31636
31926
  ] }),
31637
31927
  /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
31638
31928
  "Model: ",
31639
- activeModel
31929
+ safeModel
31640
31930
  ] })
31641
31931
  ] }),
31642
31932
  /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", alignItems: "flex-end", children: [
@@ -31651,9 +31941,9 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31651
31941
  contextBudgetSnap !== null && /* @__PURE__ */ jsx7(Text7, { color: contextBudgetColor(contextBudgetSnap.usedPct), children: formatContextBudgetLabel(contextBudgetSnap) })
31652
31942
  ] })
31653
31943
  ] }),
31654
- terminalLayout.density === "condensed" && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
31944
+ terminalLayout.density === "condensed" && /* @__PURE__ */ jsxs7(Box7, { width: "100%", flexDirection: "column", children: [
31655
31945
  /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate-end", color: executionMode2 === "local" ? "yellow" : "cyan", children: [
31656
- executionMode2 === "local" ? "LOCAL" : remoteEnvironment.label.replace("CONNECTED ", ""),
31946
+ executionMode2 === "local" ? "LOCAL" : safeEnvironment.replace("CONNECTED ", ""),
31657
31947
  " \xB7 ",
31658
31948
  mode,
31659
31949
  " \xB7 ",
@@ -31662,17 +31952,17 @@ ${renderCliError(classifyCliError(error, { executionMode: executionMode2 }))}`);
31662
31952
  continuityProfile
31663
31953
  ] }),
31664
31954
  /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate-end", dimColor: true, children: [
31665
- activeModel,
31955
+ safeModel,
31666
31956
  contextBudgetSnap ? ` \xB7 ${formatContextBudgetLabel(contextBudgetSnap)}` : "",
31667
31957
  lastCost > 0 ? ` \xB7 last $${lastCost.toFixed(4)}` : ""
31668
31958
  ] })
31669
31959
  ] }),
31670
31960
  terminalLayout.density === "minimal" && /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate-end", dimColor: true, children: [
31671
- executionMode2 === "local" ? "LOCAL" : remoteEnvironment.label.replace("CONNECTED ", ""),
31961
+ executionMode2 === "local" ? "LOCAL" : safeEnvironment.replace("CONNECTED ", ""),
31672
31962
  " \xB7 ",
31673
31963
  mode,
31674
31964
  " \xB7 ",
31675
- activeModel,
31965
+ safeModel,
31676
31966
  lastCost > 0 ? ` \xB7 $${lastCost.toFixed(4)}` : "",
31677
31967
  contextBudgetSnap ? ` \xB7 ctx ${contextBudgetSnap.usedPct}%` : ""
31678
31968
  ] }),
@@ -31891,26 +32181,7 @@ function handleCliArgs(args2) {
31891
32181
  // src/index.tsx
31892
32182
  init_src3();
31893
32183
  init_errorPresentation();
31894
-
31895
- // src/runtime/terminalScreen.ts
31896
- init_esm_shims();
31897
- var ENTER_ALTERNATE_SCREEN = "\x1B[?1049h\x1B[H";
31898
- var LEAVE_ALTERNATE_SCREEN = "\x1B[?1049l\x1B[?25h";
31899
- var alternateScreenActive = false;
31900
- function enterAlternateScreen(output = process.stdout, env = process.env) {
31901
- if (alternateScreenActive || !output.isTTY || env.MSAPLING_NO_ALT_SCREEN === "1" || env.TERM === "dumb") return false;
31902
- output.write(ENTER_ALTERNATE_SCREEN);
31903
- alternateScreenActive = true;
31904
- return true;
31905
- }
31906
- function leaveAlternateScreen(output = process.stdout) {
31907
- if (!alternateScreenActive) return false;
31908
- output.write(LEAVE_ALTERNATE_SCREEN);
31909
- alternateScreenActive = false;
31910
- return true;
31911
- }
31912
-
31913
- // src/index.tsx
32184
+ init_terminalScreen();
31914
32185
  import { jsx as jsx8 } from "react/jsx-runtime";
31915
32186
  var index_default = App;
31916
32187
  function restoreTerminalMode() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msapling",
3
- "version": "2.3.6-beta.61",
3
+ "version": "2.3.6-beta.62",
4
4
  "description": "Short-name distribution of the MSapling CLI.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "MSapling Team",
@@ -48,6 +48,8 @@
48
48
  "proper-lockfile": "^4.1.2",
49
49
  "react": "^18.3.1",
50
50
  "shell-quote": "^1.8.1",
51
+ "string-width": "^5.1.2",
52
+ "strip-ansi": "^7.1.0",
51
53
  "yaml": "^2.8.3"
52
54
  },
53
55
  "optionalDependencies": {