@mtreeai/msapling-cli 2.3.6-beta.61 → 2.3.6-beta.63

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