reasonix 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2663,6 +2663,11 @@ var CacheFirstLoop = class {
2663
2663
  };
2664
2664
  }
2665
2665
  if (repairedCalls.length === 0) {
2666
+ const allSuppressed = report.stormsBroken > 0 && toolCalls.length > 0;
2667
+ if (allSuppressed) {
2668
+ yield* this.forceSummaryAfterIterLimit({ reason: "stuck" });
2669
+ return;
2670
+ }
2666
2671
  this.autoCompactToolResultsOnTurnEnd();
2667
2672
  yield { turn: this._turn, role: "done", content: assistantContent };
2668
2673
  return;
@@ -2931,11 +2936,15 @@ function reasonPrefixFor(reason, iterCap) {
2931
2936
  if (reason === "context-guard") {
2932
2937
  return "[context budget running low \u2014 summarizing before the next call would overflow]";
2933
2938
  }
2939
+ if (reason === "stuck") {
2940
+ return "[stuck on a repeated tool call \u2014 explaining what was tried and what's blocking progress]";
2941
+ }
2934
2942
  return `[tool-call budget (${iterCap}) reached \u2014 forcing summary from what I found]`;
2935
2943
  }
2936
2944
  function errorLabelFor(reason, iterCap) {
2937
2945
  if (reason === "aborted") return "aborted by user";
2938
2946
  if (reason === "context-guard") return "context-guard triggered (prompt > 80% of window)";
2947
+ if (reason === "stuck") return "stuck (repeated tool call suppressed by storm-breaker)";
2939
2948
  return `tool-call budget (${iterCap}) reached`;
2940
2949
  }
2941
2950
  function summarizeBranch(chosen, samples) {
@@ -5035,7 +5044,15 @@ async function runCommand(cmd, opts) {
5035
5044
  shell: false,
5036
5045
  // no shell-expansion — see header comment
5037
5046
  windowsHide: true,
5038
- env: process.env
5047
+ // PYTHONIOENCODING + PYTHONUTF8 force any spawned Python child
5048
+ // (run_command running `python script.py`, etc.) to emit UTF-8
5049
+ // on stdout/stderr. Without this, Chinese-Windows defaults
5050
+ // Python's stdout encoder to GBK and `print("…")` raises
5051
+ // UnicodeEncodeError on emoji / non-GBK chars — the model then
5052
+ // sees a Python traceback instead of the script's real output
5053
+ // and goes around in circles trying to fix the wrong problem.
5054
+ // Harmless on non-Python processes (env vars they don't read).
5055
+ env: { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" }
5039
5056
  };
5040
5057
  const { bin, args, spawnOverrides } = prepareSpawn(argv);
5041
5058
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
@@ -5484,7 +5501,7 @@ function registerWebTools(registry, opts = {}) {
5484
5501
  const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
5485
5502
  registry.register({
5486
5503
  name: "web_search",
5487
- description: "Search the public web. Returns ranked results with title, url, and snippet. Use this when the question needs information more current than your training data, when you're unsure of a factual detail, or when the user asks about a specific webpage/library/release you haven't seen.",
5504
+ description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this.",
5488
5505
  readOnly: true,
5489
5506
  parameters: {
5490
5507
  type: "object",
@@ -7099,7 +7116,7 @@ import { render } from "ink";
7099
7116
  import React23, { useState as useState12 } from "react";
7100
7117
 
7101
7118
  // src/cli/ui/App.tsx
7102
- import { Box as Box19, Static, useApp, useInput as useInput5 } from "ink";
7119
+ import { Box as Box19, Static, useApp, useInput as useInput5, useStdout as useStdout4 } from "ink";
7103
7120
  import React20, { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef5, useState as useState10 } from "react";
7104
7121
 
7105
7122
  // src/code/pending-edits.ts
@@ -7853,13 +7870,136 @@ function toSubscript(s) {
7853
7870
  return out;
7854
7871
  }
7855
7872
  function stripMath(s) {
7856
- return s.replace(/\\\(\s*/g, "").replace(/\s*\\\)/g, "").replace(/\\\[\s*/g, "\n").replace(/\s*\\\]/g, "\n").replace(
7873
+ return s.replace(/\$\$([\s\S]+?)\$\$/g, (_m, c) => `
7874
+
7875
+ ${c.trim()}
7876
+
7877
+ `).replace(/(?<!\$)\$(?!\s)([^$\n]+?)(?<!\s)\$(?!\$)/g, "$1").replace(/\\\(\s*/g, "").replace(/\s*\\\)/g, "").replace(/\\\[\s*/g, "\n").replace(/\s*\\\]/g, "\n").replace(
7857
7878
  /\\[dt]?frac\s*\{((?:[^{}]|\{[^{}]*\})+)\}\s*\{((?:[^{}]|\{[^{}]*\})+)\}/g,
7858
7879
  (_m, num, den) => `(${num.trim()})/(${den.trim()})`
7859
7880
  ).replace(
7860
7881
  /\\binom\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g,
7861
7882
  (_m, n, k) => `C(${n.trim()},${k.trim()})`
7862
- ).replace(/\\sqrt\s*\{([^{}]+)\}/g, (_m, g) => `\u221A(${g.trim()})`).replace(/\\boxed\s*\{([^{}]+)\}/g, (_m, g) => `\u3010${g.trim()}\u3011`).replace(/\\text\s*\{([^{}]+)\}/g, (_m, g) => g.trim()).replace(/\\overline\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0304`).replace(/\\hat\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0302`).replace(/\\vec\s*\{([^{}]+)\}/g, (_m, g) => `\u2192${g.trim()}`).replace(/\\cdot/g, "\xB7").replace(/\\times/g, "\xD7").replace(/\\div/g, "\xF7").replace(/\\pm/g, "\xB1").replace(/\\mp/g, "\u2213").replace(/\\leq/g, "\u2264").replace(/\\geq/g, "\u2265").replace(/\\neq/g, "\u2260").replace(/\\approx/g, "\u2248").replace(/\\in\b/g, "\u2208").replace(/\\notin\b/g, "\u2209").replace(/\\infty/g, "\u221E").replace(/\\sum\b/g, "\u03A3").replace(/\\prod\b/g, "\u03A0").replace(/\\int\b/g, "\u222B").replace(/\\alpha/g, "\u03B1").replace(/\\beta/g, "\u03B2").replace(/\\gamma/g, "\u03B3").replace(/\\delta/g, "\u03B4").replace(/\\theta/g, "\u03B8").replace(/\\lambda/g, "\u03BB").replace(/\\mu/g, "\u03BC").replace(/\\pi/g, "\u03C0").replace(/\\sigma/g, "\u03C3").replace(/\\phi/g, "\u03C6").replace(/\\omega/g, "\u03C9").replace(/\\implies\b/g, "\u21D2").replace(/\\iff\b/g, "\u21D4").replace(/\\to\b/g, "\u2192").replace(/\\rightarrow/g, "\u2192").replace(/\\Rightarrow/g, "\u21D2").replace(/\\leftarrow/g, "\u2190").replace(/\\Leftarrow/g, "\u21D0").replace(/\\ldots/g, "\u2026").replace(/\\cdots/g, "\u22EF").replace(/\\quad/g, " ").replace(/\\qquad/g, " ").replace(/\\,/g, " ").replace(/\\;/g, " ").replace(/\\!/g, "").replace(/\\\\/g, "\n").replace(/\^\{([\w+-]+)\}/g, (_m, g) => toSuperscript(g)).replace(/\^([0-9+\-n])/g, (_m, g) => toSuperscript(g)).replace(/_\{([\w+-]+)\}/g, (_m, g) => toSubscript(g)).replace(/_([0-9+\-])/g, (_m, g) => toSubscript(g)).replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, "($1)/($2)").replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}/g, "$1").replace(/\\[a-zA-Z]+/g, "").replace(/[ \t]{2,}/g, " ");
7883
+ ).replace(/\\sqrt\s*\{([^{}]+)\}/g, (_m, g) => `\u221A(${g.trim()})`).replace(/\\boxed\s*\{([^{}]+)\}/g, (_m, g) => `\u3010${g.trim()}\u3011`).replace(/\\text\s*\{([^{}]+)\}/g, (_m, g) => g.trim()).replace(/\\overline\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0304`).replace(/\\hat\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0302`).replace(/\\vec\s*\{([^{}]+)\}/g, (_m, g) => `\u2192${g.trim()}`).replace(/\\cdot/g, "\xB7").replace(/\\times/g, "\xD7").replace(/\\div/g, "\xF7").replace(/\\pm/g, "\xB1").replace(/\\mp/g, "\u2213").replace(/\\leq/g, "\u2264").replace(/\\geq/g, "\u2265").replace(/\\neq/g, "\u2260").replace(/\\approx/g, "\u2248").replace(/\\in(?![a-zA-Z])/g, "\u2208").replace(/\\notin(?![a-zA-Z])/g, "\u2209").replace(/\\infty/g, "\u221E").replace(/\\sum(?![a-zA-Z])/g, "\u03A3").replace(/\\prod(?![a-zA-Z])/g, "\u03A0").replace(/\\int(?![a-zA-Z])/g, "\u222B").replace(/\\alpha/g, "\u03B1").replace(/\\beta/g, "\u03B2").replace(/\\gamma/g, "\u03B3").replace(/\\delta/g, "\u03B4").replace(/\\theta/g, "\u03B8").replace(/\\lambda/g, "\u03BB").replace(/\\mu/g, "\u03BC").replace(/\\pi/g, "\u03C0").replace(/\\sigma/g, "\u03C3").replace(/\\phi/g, "\u03C6").replace(/\\omega/g, "\u03C9").replace(/\\implies(?![a-zA-Z])/g, "\u21D2").replace(/\\iff(?![a-zA-Z])/g, "\u21D4").replace(/\\to(?![a-zA-Z])/g, "\u2192").replace(/\\rightarrow/g, "\u2192").replace(/\\Rightarrow/g, "\u21D2").replace(/\\leftarrow/g, "\u2190").replace(/\\Leftarrow/g, "\u21D0").replace(/\\ldots/g, "\u2026").replace(/\\cdots/g, "\u22EF").replace(/\\quad/g, " ").replace(/\\qquad/g, " ").replace(/\\,/g, " ").replace(/\\;/g, " ").replace(/\\!/g, "").replace(/\\\\/g, "\n").replace(/\^([A-Za-z0-9+\-]+)\^/g, (m, g) => {
7884
+ for (const c of g) if (SUPERSCRIPT[c] === void 0) return m;
7885
+ return toSuperscript(g);
7886
+ }).replace(/(?<!~)~(?!~)([A-Za-z0-9+\-]+)~(?!~)/g, (m, g) => {
7887
+ for (const c of g) if (SUBSCRIPT[c] === void 0) return m;
7888
+ return toSubscript(g);
7889
+ }).replace(/\^\{([\w+-]+)\}/g, (_m, g) => toSuperscript(g)).replace(/\^([0-9+\-n])/g, (_m, g) => toSuperscript(g)).replace(/_\{([\w+-]+)\}/g, (_m, g) => toSubscript(g)).replace(/_([0-9+\-])/g, (_m, g) => toSubscript(g)).replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, "($1)/($2)").replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}/g, "$1").replace(/\\[a-zA-Z]+/g, "").replace(/[ \t]{2,}/g, " ");
7890
+ }
7891
+ var EMOJI_MAP = {
7892
+ // faces
7893
+ smile: "\u{1F604}",
7894
+ smiley: "\u{1F603}",
7895
+ grin: "\u{1F601}",
7896
+ grinning: "\u{1F600}",
7897
+ joy: "\u{1F602}",
7898
+ laughing: "\u{1F606}",
7899
+ heart_eyes: "\u{1F60D}",
7900
+ blush: "\u{1F60A}",
7901
+ sunglasses: "\u{1F60E}",
7902
+ thinking: "\u{1F914}",
7903
+ neutral_face: "\u{1F610}",
7904
+ confused: "\u{1F615}",
7905
+ cry: "\u{1F622}",
7906
+ sob: "\u{1F62D}",
7907
+ rage: "\u{1F621}",
7908
+ angry: "\u{1F620}",
7909
+ scream: "\u{1F631}",
7910
+ wink: "\u{1F609}",
7911
+ kissing_heart: "\u{1F618}",
7912
+ // hearts
7913
+ heart: "\u2764\uFE0F",
7914
+ orange_heart: "\u{1F9E1}",
7915
+ yellow_heart: "\u{1F49B}",
7916
+ green_heart: "\u{1F49A}",
7917
+ blue_heart: "\u{1F499}",
7918
+ purple_heart: "\u{1F49C}",
7919
+ black_heart: "\u{1F5A4}",
7920
+ white_heart: "\u{1F90D}",
7921
+ broken_heart: "\u{1F494}",
7922
+ sparkling_heart: "\u{1F496}",
7923
+ two_hearts: "\u{1F495}",
7924
+ // gestures
7925
+ "+1": "\u{1F44D}",
7926
+ "-1": "\u{1F44E}",
7927
+ thumbsup: "\u{1F44D}",
7928
+ thumbsdown: "\u{1F44E}",
7929
+ wave: "\u{1F44B}",
7930
+ clap: "\u{1F44F}",
7931
+ muscle: "\u{1F4AA}",
7932
+ ok_hand: "\u{1F44C}",
7933
+ pray: "\u{1F64F}",
7934
+ fist: "\u270A",
7935
+ point_up: "\u261D\uFE0F",
7936
+ raised_hands: "\u{1F64C}",
7937
+ handshake: "\u{1F91D}",
7938
+ // symbols / signals
7939
+ rocket: "\u{1F680}",
7940
+ fire: "\u{1F525}",
7941
+ star: "\u2B50",
7942
+ star2: "\u{1F31F}",
7943
+ sparkles: "\u2728",
7944
+ boom: "\u{1F4A5}",
7945
+ zap: "\u26A1",
7946
+ tada: "\u{1F389}",
7947
+ bulb: "\u{1F4A1}",
7948
+ warning: "\u26A0\uFE0F",
7949
+ x: "\u274C",
7950
+ white_check_mark: "\u2705",
7951
+ heavy_check_mark: "\u2714\uFE0F",
7952
+ ballot_box_with_check: "\u2611\uFE0F",
7953
+ no_entry: "\u26D4",
7954
+ question: "\u2753",
7955
+ exclamation: "\u2757",
7956
+ bangbang: "\u203C\uFE0F",
7957
+ bell: "\u{1F514}",
7958
+ mute: "\u{1F515}",
7959
+ hundred: "\u{1F4AF}",
7960
+ "100": "\u{1F4AF}",
7961
+ eyes: "\u{1F440}",
7962
+ // tech / productivity
7963
+ computer: "\u{1F4BB}",
7964
+ iphone: "\u{1F4F1}",
7965
+ hammer: "\u{1F528}",
7966
+ wrench: "\u{1F527}",
7967
+ gear: "\u2699\uFE0F",
7968
+ package: "\u{1F4E6}",
7969
+ floppy_disk: "\u{1F4BE}",
7970
+ key: "\u{1F511}",
7971
+ lock: "\u{1F512}",
7972
+ unlock: "\u{1F513}",
7973
+ mag: "\u{1F50D}",
7974
+ memo: "\u{1F4DD}",
7975
+ pencil: "\u270F\uFE0F",
7976
+ bookmark: "\u{1F516}",
7977
+ // charts / time
7978
+ chart_with_upwards_trend: "\u{1F4C8}",
7979
+ chart_with_downwards_trend: "\u{1F4C9}",
7980
+ bar_chart: "\u{1F4CA}",
7981
+ hourglass: "\u23F3",
7982
+ calendar: "\u{1F4C5}",
7983
+ // misc
7984
+ robot: "\u{1F916}",
7985
+ ghost: "\u{1F47B}",
7986
+ bug: "\u{1F41B}",
7987
+ coffee: "\u2615",
7988
+ beer: "\u{1F37A}",
7989
+ sun: "\u2600\uFE0F",
7990
+ cloud: "\u2601\uFE0F",
7991
+ rainbow: "\u{1F308}",
7992
+ speech_balloon: "\u{1F4AC}",
7993
+ thought_balloon: "\u{1F4AD}",
7994
+ construction: "\u{1F6A7}"
7995
+ };
7996
+ function expandAutolinks(s) {
7997
+ return s.replace(/<((?:https?|ftp|mailto):[^\s<>]+)>/g, "[$1]($1)");
7998
+ }
7999
+ function expandEmoji(s) {
8000
+ return s.replace(/:([a-z0-9_+-]+):/gi, (m, name) => {
8001
+ return EMOJI_MAP[name.toLowerCase()] ?? m;
8002
+ });
7863
8003
  }
7864
8004
  function isExternalUrl(url) {
7865
8005
  return /^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith("mailto:") || url.startsWith("//");
@@ -7914,18 +8054,25 @@ function validateCitation(url, projectRoot) {
7914
8054
  }
7915
8055
  return { ok: true };
7916
8056
  }
8057
+ function shouldValidateAsCitation(url) {
8058
+ if (url.startsWith("#")) return false;
8059
+ if (url === "/" || url === "\\" || url === "") return false;
8060
+ if (!/[/\\.]/.test(url)) return false;
8061
+ return true;
8062
+ }
7917
8063
  function collectCitations(text, projectRoot) {
7918
8064
  const map = /* @__PURE__ */ new Map();
7919
8065
  const re = /\[([^\]\n]+)\]\(([^)\n]+)\)/g;
7920
8066
  for (const m of text.matchAll(re)) {
7921
8067
  const url = m[2] ?? "";
7922
8068
  if (!url || isExternalUrl(url)) continue;
8069
+ if (!shouldValidateAsCitation(url)) continue;
7923
8070
  if (map.has(url)) continue;
7924
8071
  map.set(url, validateCitation(url, projectRoot));
7925
8072
  }
7926
8073
  return map;
7927
8074
  }
7928
- var INLINE_RE = /(\[([^\]\n]+)\]\(([^)\n]+)\)|\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
8075
+ var INLINE_RE = /(\[([^\]\n]+)\]\(([^)\n]+)\)|\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|~~([^~\n]+?)~~|(?<![*\w])\*([^*\n]+?)\*(?!\w)|\\([*_~`[\](){}#+\-.!\\]))/g;
7929
8076
  function InlineMd({
7930
8077
  text,
7931
8078
  padTo,
@@ -7960,21 +8107,31 @@ function InlineMd({
7960
8107
  }
7961
8108
  } else if (m[4] !== void 0) {
7962
8109
  parts.push(
7963
- /* @__PURE__ */ React7.createElement(Text7, { key: `b${idx++}`, bold: true }, m[4])
8110
+ /* @__PURE__ */ React7.createElement(Text7, { key: `bi${idx++}`, bold: true, italic: true }, m[4])
7964
8111
  );
7965
8112
  } else if (m[5] !== void 0) {
7966
- const stripped = m[5].replace(/^(\w+)\s+/, "");
7967
8113
  parts.push(
7968
- /* @__PURE__ */ React7.createElement(Text7, { key: `c${idx++}`, color: "yellow" }, stripped)
8114
+ /* @__PURE__ */ React7.createElement(Text7, { key: `b${idx++}`, bold: true }, m[5])
7969
8115
  );
7970
8116
  } else if (m[6] !== void 0) {
8117
+ const stripped = m[6].replace(/^(\w+)\s+/, "");
7971
8118
  parts.push(
7972
- /* @__PURE__ */ React7.createElement(Text7, { key: `c${idx++}`, color: "yellow" }, m[6])
8119
+ /* @__PURE__ */ React7.createElement(Text7, { key: `c${idx++}`, color: "yellow" }, stripped)
7973
8120
  );
7974
8121
  } else if (m[7] !== void 0) {
7975
8122
  parts.push(
7976
- /* @__PURE__ */ React7.createElement(Text7, { key: `i${idx++}`, italic: true }, m[7])
8123
+ /* @__PURE__ */ React7.createElement(Text7, { key: `c${idx++}`, color: "yellow" }, m[7])
8124
+ );
8125
+ } else if (m[8] !== void 0) {
8126
+ parts.push(
8127
+ /* @__PURE__ */ React7.createElement(Text7, { key: `s${idx++}`, strikethrough: true, dimColor: true }, m[8])
8128
+ );
8129
+ } else if (m[9] !== void 0) {
8130
+ parts.push(
8131
+ /* @__PURE__ */ React7.createElement(Text7, { key: `i${idx++}`, italic: true }, m[9])
7977
8132
  );
8133
+ } else if (m[10] !== void 0) {
8134
+ parts.push(/* @__PURE__ */ React7.createElement(Text7, { key: `esc${idx++}` }, m[10]));
7978
8135
  }
7979
8136
  last = start + m[0].length;
7980
8137
  }
@@ -7990,7 +8147,20 @@ function InlineMd({
7990
8147
  return /* @__PURE__ */ React7.createElement(Text7, null, parts);
7991
8148
  }
7992
8149
  function stripInlineMarkup(s) {
7993
- return s.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, "$1").replace(/\*\*([^*\n]+?)\*\*/g, "$1").replace(/```([^\n]+?)```/g, (_m, c) => c.replace(/^(\w+)\s+/, "")).replace(/`([^`\n]+?)`/g, "$1").replace(/(?<![*\w])\*([^*\n]+?)\*(?!\w)/g, "$1");
8150
+ return s.replace(
8151
+ INLINE_RE,
8152
+ (match, _alt, linkText, _url, boldItalic, bold, code3, code1, strike, italic, escapeChar) => {
8153
+ if (linkText !== void 0) return linkText;
8154
+ if (boldItalic !== void 0) return boldItalic;
8155
+ if (bold !== void 0) return bold;
8156
+ if (code3 !== void 0) return code3.replace(/^(\w+)\s+/, "");
8157
+ if (code1 !== void 0) return code1;
8158
+ if (strike !== void 0) return strike;
8159
+ if (italic !== void 0) return italic;
8160
+ if (escapeChar !== void 0) return escapeChar;
8161
+ return match;
8162
+ }
8163
+ );
7994
8164
  }
7995
8165
  function visibleWidth(s) {
7996
8166
  return displayWidth(stripInlineMarkup(s));
@@ -8005,10 +8175,16 @@ function parseBlocks(raw) {
8005
8175
  let listBuf = null;
8006
8176
  let codeFence = "";
8007
8177
  const flushPara = () => {
8008
- if (para.length) {
8009
- out.push({ kind: "paragraph", text: para.join(" ") });
8010
- para = [];
8178
+ if (para.length === 0) return;
8179
+ let joined = "";
8180
+ for (let k = 0; k < para.length; k++) {
8181
+ joined += para[k].text;
8182
+ if (k < para.length - 1) {
8183
+ joined += para[k].hardBreak ? "\n" : " ";
8184
+ }
8011
8185
  }
8186
+ out.push({ kind: "paragraph", text: joined });
8187
+ para = [];
8012
8188
  };
8013
8189
  const flushList = () => {
8014
8190
  if (listBuf) {
@@ -8020,7 +8196,7 @@ function parseBlocks(raw) {
8020
8196
  const rawLine = lines[i];
8021
8197
  const line = rawLine.replace(/\s+$/g, "");
8022
8198
  if (!inCode && /^<{7} SEARCH\s*$/.test(line)) {
8023
- const filename = para.pop()?.trim();
8199
+ const filename = para.pop()?.text.trim();
8024
8200
  if (filename) {
8025
8201
  flushPara();
8026
8202
  flushList();
@@ -8046,7 +8222,7 @@ function parseBlocks(raw) {
8046
8222
  i = k;
8047
8223
  continue;
8048
8224
  }
8049
- para.push(filename);
8225
+ para.push({ text: filename, hardBreak: false });
8050
8226
  }
8051
8227
  }
8052
8228
  if (!inCode) {
@@ -8150,6 +8326,23 @@ function parseBlocks(raw) {
8150
8326
  continue;
8151
8327
  }
8152
8328
  }
8329
+ const quoteMatch = line.match(/^\s*>\s?(.*)$/);
8330
+ if (quoteMatch) {
8331
+ flushPara();
8332
+ flushList();
8333
+ const innerLines = [quoteMatch[1] ?? ""];
8334
+ let j = i + 1;
8335
+ while (j < lines.length) {
8336
+ const nxt = lines[j].replace(/\s+$/g, "");
8337
+ const m = nxt.match(/^\s*>\s?(.*)$/);
8338
+ if (!m) break;
8339
+ innerLines.push(m[1] ?? "");
8340
+ j++;
8341
+ }
8342
+ out.push({ kind: "quote", children: parseBlocks(innerLines.join("\n")) });
8343
+ i = j - 1;
8344
+ continue;
8345
+ }
8153
8346
  const bm = line.match(/^\s*[-*+]\s+(.+)$/);
8154
8347
  if (bm) {
8155
8348
  flushPara();
@@ -8157,7 +8350,7 @@ function parseBlocks(raw) {
8157
8350
  flushList();
8158
8351
  listBuf = { kind: "bullet", items: [], ordered: false, start: 1 };
8159
8352
  }
8160
- listBuf.items.push(bm[1]);
8353
+ listBuf.items.push(parseBulletItem(bm[1]));
8161
8354
  continue;
8162
8355
  }
8163
8356
  const om = line.match(/^\s*(\d+)\.\s+(.+)$/);
@@ -8167,11 +8360,12 @@ function parseBlocks(raw) {
8167
8360
  flushList();
8168
8361
  listBuf = { kind: "bullet", items: [], ordered: true, start: Number(om[1]) };
8169
8362
  }
8170
- listBuf.items.push(om[2]);
8363
+ listBuf.items.push(parseBulletItem(om[2]));
8171
8364
  continue;
8172
8365
  }
8173
8366
  flushList();
8174
- para.push(line);
8367
+ const hardBreak = / {2,}\r?$/.test(rawLine);
8368
+ para.push({ text: line, hardBreak });
8175
8369
  }
8176
8370
  if (inCode && codeBuf.length) {
8177
8371
  out.push({ kind: "code", lang: codeLang, text: codeBuf.join("\n") });
@@ -8180,15 +8374,26 @@ function parseBlocks(raw) {
8180
8374
  flushList();
8181
8375
  return out;
8182
8376
  }
8377
+ function parseBulletItem(raw) {
8378
+ const m = raw.match(/^\[([ xX])\]\s+(.*)$/);
8379
+ if (!m) return { text: raw };
8380
+ const done = m[1].toLowerCase() === "x";
8381
+ return { text: m[2] ?? "", task: done ? "done" : "todo" };
8382
+ }
8183
8383
  function BlockView({ block, citations }) {
8184
8384
  switch (block.kind) {
8185
8385
  case "heading":
8186
8386
  return /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, /* @__PURE__ */ React7.createElement(InlineMd, { text: block.text, citations }));
8187
8387
  case "paragraph":
8188
- return /* @__PURE__ */ React7.createElement(InlineMd, { text: block.text, citations });
8388
+ return /* @__PURE__ */ React7.createElement(ParagraphView, { text: block.text, citations });
8189
8389
  case "bullet":
8190
- return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React7.createElement(Box7, { key: `${i}-${item.slice(0, 24)}` }, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, block.ordered ? ` ${block.start + i}. ` : " \u2022 "), /* @__PURE__ */ React7.createElement(InlineMd, { text: item, citations }))));
8390
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React7.createElement(Box7, { key: `${i}-${item.text.slice(0, 24)}` }, /* @__PURE__ */ React7.createElement(Text7, { color: item.task === "done" ? "green" : "cyan" }, bulletPrefix(block, i, item)), item.task === "done" ? /* @__PURE__ */ React7.createElement(Text7, { strikethrough: true, dimColor: true }, /* @__PURE__ */ React7.createElement(InlineMd, { text: item.text, citations })) : /* @__PURE__ */ React7.createElement(InlineMd, { text: item.text, citations }))));
8391
+ case "quote":
8392
+ return /* @__PURE__ */ React7.createElement(BlockquoteView, { block, citations });
8191
8393
  case "code":
8394
+ if (DIAGRAM_LANGS.has(block.lang.toLowerCase())) {
8395
+ return /* @__PURE__ */ React7.createElement(DiagramCodeBlock, { lang: block.lang, text: block.text });
8396
+ }
8192
8397
  return /* @__PURE__ */ React7.createElement(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, block.text));
8193
8398
  case "edit-block":
8194
8399
  return /* @__PURE__ */ React7.createElement(EditBlockRow, { block });
@@ -8198,6 +8403,42 @@ function BlockView({ block, citations }) {
8198
8403
  return /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
8199
8404
  }
8200
8405
  }
8406
+ function ParagraphView({ text, citations }) {
8407
+ if (!text.includes("\n")) {
8408
+ return /* @__PURE__ */ React7.createElement(InlineMd, { text, citations });
8409
+ }
8410
+ const rows = text.split("\n");
8411
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, rows.map((row2, i) => (
8412
+ // biome-ignore lint/suspicious/noArrayIndexKey: hard-break rows are source-ordered and never reorder
8413
+ /* @__PURE__ */ React7.createElement(InlineMd, { key: `ln-${i}`, text: row2, citations })
8414
+ )));
8415
+ }
8416
+ function bulletPrefix(block, i, item) {
8417
+ if (block.ordered) return ` ${block.start + i}. `;
8418
+ if (item.task === "done") return " \u2612 ";
8419
+ if (item.task === "todo") return " \u2610 ";
8420
+ return " \u2022 ";
8421
+ }
8422
+ function BlockquoteView({
8423
+ block,
8424
+ citations
8425
+ }) {
8426
+ return /* @__PURE__ */ React7.createElement(
8427
+ Box7,
8428
+ {
8429
+ borderStyle: "single",
8430
+ borderColor: "gray",
8431
+ borderDimColor: true,
8432
+ borderTop: false,
8433
+ borderRight: false,
8434
+ borderBottom: false,
8435
+ paddingLeft: 1,
8436
+ flexDirection: "column",
8437
+ gap: 1
8438
+ },
8439
+ block.children.map((child, i) => /* @__PURE__ */ React7.createElement(BlockView, { key: `q-${i}-${child.kind}`, block: child, citations }))
8440
+ );
8441
+ }
8201
8442
  function splitTableRow(line) {
8202
8443
  const SENTINEL = "\0";
8203
8444
  const masked = line.replace(/\\\|/g, SENTINEL).replace(/│/g, "|");
@@ -8242,8 +8483,30 @@ function EditBlockRow({ block }) {
8242
8483
  const replaceLines = block.replace.split("\n");
8243
8484
  return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, block.filename), isNewFile ? /* @__PURE__ */ React7.createElement(Text7, { color: "green", bold: true }, " (new file)") : null), isNewFile ? null : /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, searchLines.map((line, i) => /* @__PURE__ */ React7.createElement(Text7, { key: `s-${i}-${line.length}`, color: "red" }, `- ${line}`))), /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: isNewFile ? 1 : 0 }, replaceLines.map((line, i) => /* @__PURE__ */ React7.createElement(Text7, { key: `r-${i}-${line.length}`, color: "green" }, `+ ${line}`))));
8244
8485
  }
8486
+ var DIAGRAM_LANGS = /* @__PURE__ */ new Set([
8487
+ "mermaid",
8488
+ "dot",
8489
+ "graphviz",
8490
+ "plantuml",
8491
+ "puml",
8492
+ "flowchart",
8493
+ "sequencediagram",
8494
+ "gantt",
8495
+ "erdiagram"
8496
+ ]);
8497
+ var DIAGRAM_VIEWER_HINT = {
8498
+ mermaid: "\u2192 paste at https://mermaid.live to view",
8499
+ plantuml: "\u2192 paste at https://www.plantuml.com/plantuml to view",
8500
+ puml: "\u2192 paste at https://www.plantuml.com/plantuml to view",
8501
+ dot: "\u2192 paste at https://dreampuf.github.io/GraphvizOnline to view",
8502
+ graphviz: "\u2192 paste at https://dreampuf.github.io/GraphvizOnline to view"
8503
+ };
8504
+ function DiagramCodeBlock({ lang, text }) {
8505
+ const hint = DIAGRAM_VIEWER_HINT[lang.toLowerCase()] ?? "\u2192 render with the matching viewer to view";
8506
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "double", borderColor: "magenta", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "magenta" }, `\u25C7 ${lang} diagram (source \u2014 terminal can't draw the graph)`), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, text)), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, hint)));
8507
+ }
8245
8508
  function Markdown({ text, projectRoot }) {
8246
- const cleaned = stripMath(text);
8509
+ const cleaned = expandAutolinks(expandEmoji(stripMath(text)));
8247
8510
  const root = projectRoot ?? process.cwd();
8248
8511
  const citations = React7.useMemo(() => collectCitations(cleaned, root), [cleaned, root]);
8249
8512
  const blocks = React7.useMemo(() => parseBlocks(cleaned), [cleaned]);
@@ -8910,12 +9173,27 @@ import React15, { useRef, useState as useState5 } from "react";
8910
9173
  // src/cli/ui/multiline-keys.ts
8911
9174
  var BACKSLASH_SUFFIX = /\\$/;
8912
9175
  var NOOP = { next: null, cursor: null, submit: false };
8913
- function processMultilineKey(value, cursor, key) {
8914
- if (key.tab || key.escape || key.pageUp || key.pageDown) {
9176
+ function rewriteRawArrowEscape(key) {
9177
+ if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) return key;
9178
+ if (key.input === "\x1B[A") return { ...key, upArrow: true, input: "" };
9179
+ if (key.input === "\x1B[B") return { ...key, downArrow: true, input: "" };
9180
+ if (key.input === "\x1B[C") return { ...key, rightArrow: true, input: "" };
9181
+ if (key.input === "\x1B[D") return { ...key, leftArrow: true, input: "" };
9182
+ return key;
9183
+ }
9184
+ function processMultilineKey(value, cursor, keyIn) {
9185
+ const key = rewriteRawArrowEscape(keyIn);
9186
+ if (key.tab || key.escape) {
8915
9187
  return NOOP;
8916
9188
  }
9189
+ if (key.pageUp) {
9190
+ return cursor === 0 ? NOOP : { next: null, cursor: 0, submit: false };
9191
+ }
9192
+ if (key.pageDown) {
9193
+ return cursor === value.length ? NOOP : { next: null, cursor: value.length, submit: false };
9194
+ }
8917
9195
  if (value.length === 0 && (key.upArrow || key.downArrow)) {
8918
- return NOOP;
9196
+ return { ...NOOP, historyHandoff: key.upArrow ? "prev" : "next" };
8919
9197
  }
8920
9198
  if (key.leftArrow) {
8921
9199
  return { next: null, cursor: Math.max(0, cursor - 1), submit: false };
@@ -8925,11 +9203,13 @@ function processMultilineKey(value, cursor, key) {
8925
9203
  }
8926
9204
  if (key.upArrow) {
8927
9205
  const moved = moveCursorUp(value, cursor);
8928
- return moved === cursor ? NOOP : { next: null, cursor: moved, submit: false };
9206
+ if (moved === cursor) return { ...NOOP, historyHandoff: "prev" };
9207
+ return { next: null, cursor: moved, submit: false };
8929
9208
  }
8930
9209
  if (key.downArrow) {
8931
9210
  const moved = moveCursorDown(value, cursor);
8932
- return moved === cursor ? NOOP : { next: null, cursor: moved, submit: false };
9211
+ if (moved === cursor) return { ...NOOP, historyHandoff: "next" };
9212
+ return { next: null, cursor: moved, submit: false };
8933
9213
  }
8934
9214
  if (key.ctrl && key.input === "a") {
8935
9215
  return { next: null, cursor: startOfLine(value, cursor), submit: false };
@@ -8937,6 +9217,29 @@ function processMultilineKey(value, cursor, key) {
8937
9217
  if (key.ctrl && key.input === "e") {
8938
9218
  return { next: null, cursor: endOfLine(value, cursor), submit: false };
8939
9219
  }
9220
+ if (key.ctrl && key.input === "u") {
9221
+ return value.length === 0 ? NOOP : { next: "", cursor: 0, submit: false };
9222
+ }
9223
+ if (key.ctrl && key.input === "w") {
9224
+ if (cursor === 0) return NOOP;
9225
+ const wordStart = previousWordStart(value, cursor);
9226
+ return {
9227
+ next: value.slice(0, wordStart) + value.slice(cursor),
9228
+ cursor: wordStart,
9229
+ submit: false
9230
+ };
9231
+ }
9232
+ const stripped = key.input.replaceAll("\x1B[200~", "").replaceAll("\x1B[201~", "");
9233
+ const looksLikePaste = stripped.length > 1 && (stripped.includes("\n") || stripped.includes("\r"));
9234
+ if (looksLikePaste) {
9235
+ const normalized = stripped.replace(/\r\n?/g, "\n");
9236
+ return {
9237
+ next: null,
9238
+ cursor: null,
9239
+ submit: false,
9240
+ pasteRequest: { content: normalized }
9241
+ };
9242
+ }
8940
9243
  if (key.input === "\n" || key.ctrl && key.input === "j") {
8941
9244
  return insertAt(value, cursor, "\n");
8942
9245
  }
@@ -8988,6 +9291,12 @@ function lineAndColumn(value, cursor) {
8988
9291
  function startOfLine(value, cursor) {
8989
9292
  return value.lastIndexOf("\n", cursor - 1) + 1;
8990
9293
  }
9294
+ function previousWordStart(value, cursor) {
9295
+ let i = cursor;
9296
+ while (i > 0 && /\s/.test(value[i - 1] ?? "")) i--;
9297
+ while (i > 0 && !/\s/.test(value[i - 1] ?? "")) i--;
9298
+ return i;
9299
+ }
8991
9300
  function endOfLine(value, cursor) {
8992
9301
  const nl = value.indexOf("\n", cursor);
8993
9302
  return nl === -1 ? value.length : nl;
@@ -9012,15 +9321,76 @@ function moveCursorDown(value, cursor) {
9012
9321
  return nextStart + Math.min(col, nextLen);
9013
9322
  }
9014
9323
 
9324
+ // src/cli/ui/paste-sentinels.ts
9325
+ var PASTE_SENTINEL_BASE = 57600;
9326
+ var PASTE_SENTINEL_RANGE = 256;
9327
+ var PASTE_SENTINEL_END = PASTE_SENTINEL_BASE + PASTE_SENTINEL_RANGE;
9328
+ function encodePasteSentinel(id) {
9329
+ if (id < 0 || id >= PASTE_SENTINEL_RANGE) {
9330
+ throw new Error(`paste sentinel id ${id} out of range [0, ${PASTE_SENTINEL_RANGE})`);
9331
+ }
9332
+ return String.fromCharCode(PASTE_SENTINEL_BASE + id);
9333
+ }
9334
+ function decodePasteSentinel(ch) {
9335
+ if (ch.length === 0) return null;
9336
+ const cp = ch.charCodeAt(0);
9337
+ if (cp < PASTE_SENTINEL_BASE || cp >= PASTE_SENTINEL_END) return null;
9338
+ return cp - PASTE_SENTINEL_BASE;
9339
+ }
9340
+ function makePasteEntry(id, content) {
9341
+ return {
9342
+ id,
9343
+ content,
9344
+ lineCount: content.split("\n").length,
9345
+ charCount: content.length
9346
+ };
9347
+ }
9348
+ function expandPasteSentinels(text, pastes) {
9349
+ let out = "";
9350
+ for (let i = 0; i < text.length; i++) {
9351
+ const ch = text[i];
9352
+ const id = decodePasteSentinel(ch);
9353
+ if (id === null) {
9354
+ out += ch;
9355
+ continue;
9356
+ }
9357
+ const entry = pastes.get(id);
9358
+ out += entry?.content ?? "";
9359
+ }
9360
+ return out;
9361
+ }
9362
+ function listPasteIdsInBuffer(text) {
9363
+ const ids = [];
9364
+ for (let i = 0; i < text.length; i++) {
9365
+ const id = decodePasteSentinel(text[i]);
9366
+ if (id !== null) ids.push(id);
9367
+ }
9368
+ return ids;
9369
+ }
9370
+ function formatBytesShort(n) {
9371
+ if (n < 1024) return `${n}B`;
9372
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(n < 1024 * 10 ? 1 : 0)}KB`;
9373
+ return `${(n / (1024 * 1024)).toFixed(1)}MB`;
9374
+ }
9375
+
9015
9376
  // src/cli/ui/PromptInput.tsx
9377
+ var PASTE_START_MARKER = "\x1B[200~";
9378
+ var PASTE_END_MARKER = "\x1B[201~";
9379
+ var PASTE_MERGE_WINDOW_MS = 30;
9016
9380
  function PromptInput({
9017
9381
  value,
9018
9382
  onChange,
9019
9383
  onSubmit,
9020
9384
  disabled,
9021
- placeholder
9385
+ placeholder,
9386
+ onHistoryPrev,
9387
+ onHistoryNext
9022
9388
  }) {
9023
9389
  const [cursor, setCursor] = useState5(value.length);
9390
+ const pastesRef = useRef(/* @__PURE__ */ new Map());
9391
+ const nextPasteIdRef = useRef(0);
9392
+ const pasteAccumRef = useRef(null);
9393
+ const lastPasteRef = useRef(null);
9024
9394
  const lastLocalValueRef = useRef(value);
9025
9395
  if (value !== lastLocalValueRef.current) {
9026
9396
  lastLocalValueRef.current = value;
@@ -9028,10 +9398,62 @@ function PromptInput({
9028
9398
  setCursor(value.length);
9029
9399
  }
9030
9400
  }
9401
+ const cursorRef = useRef(cursor);
9402
+ cursorRef.current = cursor;
9031
9403
  const tick = useTick();
9032
9404
  const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
9405
+ const registerPaste = (content) => {
9406
+ const v = lastLocalValueRef.current;
9407
+ const c = cursorRef.current;
9408
+ const now = Date.now();
9409
+ const last = lastPasteRef.current;
9410
+ const prevChar = c > 0 ? v[c - 1] : null;
9411
+ const prevId = prevChar ? decodePasteSentinel(prevChar) : null;
9412
+ const canMerge = last !== null && prevId === last.id && now - last.at < PASTE_MERGE_WINDOW_MS && pastesRef.current.has(last.id);
9413
+ if (canMerge && last) {
9414
+ const existing = pastesRef.current.get(last.id);
9415
+ if (existing) {
9416
+ const merged = existing.content + content;
9417
+ pastesRef.current.set(last.id, makePasteEntry(last.id, merged));
9418
+ lastPasteRef.current = { id: last.id, at: now };
9419
+ return;
9420
+ }
9421
+ }
9422
+ const id = nextPasteIdRef.current % PASTE_SENTINEL_RANGE;
9423
+ nextPasteIdRef.current = id + 1;
9424
+ pastesRef.current.set(id, makePasteEntry(id, content));
9425
+ const sentinel = encodePasteSentinel(id);
9426
+ const next = v.slice(0, c) + sentinel + v.slice(c);
9427
+ lastLocalValueRef.current = next;
9428
+ cursorRef.current = c + 1;
9429
+ onChange(next);
9430
+ setCursor(c + 1);
9431
+ lastPasteRef.current = { id, at: now };
9432
+ };
9033
9433
  useInput4(
9034
9434
  (input, key) => {
9435
+ if (pasteAccumRef.current !== null) {
9436
+ const endIdx = input.indexOf(PASTE_END_MARKER);
9437
+ if (endIdx === -1) {
9438
+ pasteAccumRef.current += input;
9439
+ return;
9440
+ }
9441
+ const content = pasteAccumRef.current + input.slice(0, endIdx);
9442
+ pasteAccumRef.current = null;
9443
+ registerPaste(content);
9444
+ return;
9445
+ }
9446
+ const startIdx = input.indexOf(PASTE_START_MARKER);
9447
+ if (startIdx !== -1) {
9448
+ const afterStart = input.slice(startIdx + PASTE_START_MARKER.length);
9449
+ const endIdx = afterStart.indexOf(PASTE_END_MARKER);
9450
+ if (endIdx !== -1) {
9451
+ registerPaste(afterStart.slice(0, endIdx));
9452
+ } else {
9453
+ pasteAccumRef.current = afterStart;
9454
+ }
9455
+ return;
9456
+ }
9035
9457
  const ke = {
9036
9458
  input,
9037
9459
  return: key.return,
@@ -9050,6 +9472,10 @@ function PromptInput({
9050
9472
  pageDown: key.pageDown
9051
9473
  };
9052
9474
  const action = processMultilineKey(value, cursor, ke);
9475
+ if (action.pasteRequest) {
9476
+ registerPaste(action.pasteRequest.content);
9477
+ return;
9478
+ }
9053
9479
  if (action.next !== null) {
9054
9480
  lastLocalValueRef.current = action.next;
9055
9481
  onChange(action.next);
@@ -9057,7 +9483,17 @@ function PromptInput({
9057
9483
  if (action.cursor !== null) {
9058
9484
  setCursor(action.cursor);
9059
9485
  }
9060
- if (action.submit) onSubmit(action.submitValue ?? value);
9486
+ if (action.submit) {
9487
+ const raw = action.submitValue ?? value;
9488
+ const expanded = expandPasteSentinels(raw, pastesRef.current);
9489
+ const reachable = new Set(listPasteIdsInBuffer(raw));
9490
+ for (const id of pastesRef.current.keys()) {
9491
+ if (!reachable.has(id)) pastesRef.current.delete(id);
9492
+ }
9493
+ onSubmit(expanded);
9494
+ }
9495
+ if (action.historyHandoff === "prev") onHistoryPrev?.();
9496
+ if (action.historyHandoff === "next") onHistoryNext?.();
9061
9497
  },
9062
9498
  { isActive: !disabled }
9063
9499
  );
@@ -9065,37 +9501,110 @@ function PromptInput({
9065
9501
  const lines = value.length > 0 ? value.split("\n") : [""];
9066
9502
  const borderColor = disabled ? "gray" : "cyan";
9067
9503
  const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
9068
- return /* @__PURE__ */ React15.createElement(Box14, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
9504
+ const renderItems = collapseLinesForDisplay(lines, cursorLine);
9505
+ const showHugeBufferHints = lines.length > 20;
9506
+ return /* @__PURE__ */ React15.createElement(Box14, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, renderItems.map((item, renderIdx) => {
9507
+ if (item.kind === "skip") {
9508
+ return (
9509
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable — skip markers are derived from a fixed-size window over `lines`
9510
+ /* @__PURE__ */ React15.createElement(Box14, { key: `skip-${renderIdx}` }, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " "), /* @__PURE__ */ React15.createElement(
9511
+ Text14,
9512
+ {
9513
+ dimColor: true
9514
+ },
9515
+ `[\u2026 ${item.linesHidden} line${item.linesHidden === 1 ? "" : "s"} hidden \u2014 full content kept, submitted on Enter \u2026]`
9516
+ ))
9517
+ );
9518
+ }
9519
+ const line = item.line;
9520
+ const i = item.originalIndex;
9069
9521
  const isFirst = i === 0;
9070
9522
  const showPlaceholder = isFirst && value.length === 0;
9071
9523
  const isCursorLine = i === cursorLine;
9072
- return (
9073
- // biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
9074
- /* @__PURE__ */ React15.createElement(Box14, { key: i }, isFirst ? /* @__PURE__ */ React15.createElement(Text14, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React15.createElement(Text14, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React15.createElement(
9075
- LineWithCursor,
9076
- {
9077
- line,
9078
- col: cursorCol,
9079
- showCursor,
9080
- borderColor
9081
- }
9082
- ) : /* @__PURE__ */ React15.createElement(Text14, null, line))
9524
+ return /* @__PURE__ */ React15.createElement(Box14, { key: `ln-${i}` }, isFirst ? /* @__PURE__ */ React15.createElement(Text14, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React15.createElement(Text14, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React15.createElement(
9525
+ LineWithCursor,
9526
+ {
9527
+ line,
9528
+ col: cursorCol,
9529
+ showCursor,
9530
+ borderColor,
9531
+ pastes: pastesRef.current
9532
+ }
9533
+ ) : /* @__PURE__ */ React15.createElement(RenderLine, { line, pastes: pastesRef.current }));
9534
+ }), showHugeBufferHints && !disabled ? /* @__PURE__ */ React15.createElement(Box14, null, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " "), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, `[${lines.length} lines \xB7 PageUp/PageDown jump to top/bottom \xB7 Ctrl+U clear \xB7 Ctrl+W del word]`)) : null);
9535
+ }
9536
+ var COLLAPSE_THRESHOLD = 20;
9537
+ var COLLAPSE_HEAD_LINES = 3;
9538
+ var COLLAPSE_TAIL_LINES = 2;
9539
+ function collapseLinesForDisplay(lines, cursorLine) {
9540
+ if (lines.length <= COLLAPSE_THRESHOLD) {
9541
+ return lines.map((line, i) => ({ kind: "line", line, originalIndex: i }));
9542
+ }
9543
+ const keep = /* @__PURE__ */ new Set();
9544
+ for (let i = 0; i < COLLAPSE_HEAD_LINES && i < lines.length; i++) keep.add(i);
9545
+ for (let i = Math.max(0, lines.length - COLLAPSE_TAIL_LINES); i < lines.length; i++) keep.add(i);
9546
+ if (cursorLine >= 0 && cursorLine < lines.length) keep.add(cursorLine);
9547
+ const sorted = [...keep].sort((a, b) => a - b);
9548
+ const out = [];
9549
+ let prev = -1;
9550
+ for (const idx of sorted) {
9551
+ if (idx - prev > 1) {
9552
+ out.push({ kind: "skip", linesHidden: idx - prev - 1 });
9553
+ }
9554
+ out.push({ kind: "line", line: lines[idx] ?? "", originalIndex: idx });
9555
+ prev = idx;
9556
+ }
9557
+ return out;
9558
+ }
9559
+ function RenderLine({
9560
+ line,
9561
+ pastes,
9562
+ inverse
9563
+ }) {
9564
+ const segments = [];
9565
+ let buf = "";
9566
+ let segIdx = 0;
9567
+ const flushBuf = () => {
9568
+ if (buf.length === 0) return;
9569
+ segments.push(
9570
+ /* @__PURE__ */ React15.createElement(Text14, { key: `t-${segIdx++}`, inverse }, buf)
9083
9571
  );
9084
- }));
9572
+ buf = "";
9573
+ };
9574
+ for (let i = 0; i < line.length; i++) {
9575
+ const ch = line[i];
9576
+ const id = decodePasteSentinel(ch);
9577
+ if (id === null) {
9578
+ buf += ch;
9579
+ continue;
9580
+ }
9581
+ flushBuf();
9582
+ const entry = pastes.get(id);
9583
+ const label = entry ? `[paste #${id + 1} \xB7 ${entry.lineCount}l \xB7 ${formatBytesShort(entry.charCount)}]` : `[paste #${id + 1} \xB7 (missing)]`;
9584
+ segments.push(
9585
+ /* @__PURE__ */ React15.createElement(Text14, { key: `p-${segIdx++}`, color: "magenta", bold: true, inverse }, label)
9586
+ );
9587
+ }
9588
+ flushBuf();
9589
+ if (segments.length === 0) {
9590
+ return /* @__PURE__ */ React15.createElement(Text14, null, " ");
9591
+ }
9592
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, segments);
9085
9593
  }
9086
9594
  function LineWithCursor({
9087
9595
  line,
9088
9596
  col,
9089
9597
  showCursor,
9090
- borderColor
9598
+ borderColor,
9599
+ pastes
9091
9600
  }) {
9092
9601
  const before = line.slice(0, col);
9093
9602
  const atCursor = line.slice(col, col + 1);
9094
9603
  const after = line.slice(col + 1);
9095
9604
  if (atCursor.length === 0) {
9096
- return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Text14, null, before), /* @__PURE__ */ React15.createElement(Text14, { color: borderColor }, showCursor ? "\u258C" : " "));
9605
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(RenderLine, { line: before, pastes }), /* @__PURE__ */ React15.createElement(Text14, { color: borderColor }, showCursor ? "\u258C" : " "));
9097
9606
  }
9098
- return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Text14, null, before), /* @__PURE__ */ React15.createElement(Text14, { inverse: showCursor }, atCursor), /* @__PURE__ */ React15.createElement(Text14, null, after));
9607
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(RenderLine, { line: before, pastes }), /* @__PURE__ */ React15.createElement(RenderLine, { line: atCursor, pastes, inverse: showCursor }), /* @__PURE__ */ React15.createElement(RenderLine, { line: after, pastes }));
9099
9608
  }
9100
9609
 
9101
9610
  // src/cli/ui/ShellConfirm.tsx
@@ -9275,43 +9784,54 @@ function StatsPanel({
9275
9784
  const columns = stdout2?.columns ?? 80;
9276
9785
  const narrow = columns < NARROW_BREAKPOINT;
9277
9786
  const coldStart = summary.turns <= COLD_START_TURNS;
9278
- return /* @__PURE__ */ React19.createElement(Box18, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React19.createElement(
9279
- Header,
9280
- {
9281
- model: model2,
9282
- prefixHash,
9283
- harvestOn,
9284
- branchOn,
9285
- branchBudget: branchBudget ?? 1,
9286
- reasoningEffort,
9287
- planMode,
9288
- editMode,
9289
- turns: summary.turns,
9290
- updateAvailable,
9291
- narrow,
9292
- busy: busy ?? false,
9293
- proArmed: proArmed ?? false,
9294
- escalated: escalated ?? false
9295
- }
9296
- ), narrow ? /* @__PURE__ */ React19.createElement(
9297
- StackedMetrics,
9298
- {
9299
- summary,
9300
- ctxRatio,
9301
- ctxMax,
9302
- balance,
9303
- coldStart
9304
- }
9305
- ) : /* @__PURE__ */ React19.createElement(
9306
- InlineMetrics,
9307
- {
9308
- summary,
9309
- ctxRatio,
9310
- ctxMax,
9311
- balance,
9312
- coldStart
9313
- }
9314
- ));
9787
+ return (
9788
+ // Explicit `width={columns}` pins the border frame to the exact
9789
+ // terminal width. Without this, Ink auto-flexes the Box to
9790
+ // container width, and on terminal resize the prior frame's
9791
+ // wrapped-overflow can leave tails in the scrollback (each
9792
+ // redraw stacks a slightly-wider-or-narrower frame). Fixing
9793
+ // width per-render doesn't eliminate the underlying Ink
9794
+ // limitation (eraseLines counts logical rows, not post-wrap
9795
+ // display rows) but makes each frame's dimensions exact so
9796
+ // there's no residual uncertainty in the erase.
9797
+ /* @__PURE__ */ React19.createElement(Box18, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, width: columns }, /* @__PURE__ */ React19.createElement(
9798
+ Header,
9799
+ {
9800
+ model: model2,
9801
+ prefixHash,
9802
+ harvestOn,
9803
+ branchOn,
9804
+ branchBudget: branchBudget ?? 1,
9805
+ reasoningEffort,
9806
+ planMode,
9807
+ editMode,
9808
+ turns: summary.turns,
9809
+ updateAvailable,
9810
+ narrow,
9811
+ busy: busy ?? false,
9812
+ proArmed: proArmed ?? false,
9813
+ escalated: escalated ?? false
9814
+ }
9815
+ ), narrow ? /* @__PURE__ */ React19.createElement(
9816
+ StackedMetrics,
9817
+ {
9818
+ summary,
9819
+ ctxRatio,
9820
+ ctxMax,
9821
+ balance,
9822
+ coldStart
9823
+ }
9824
+ ) : /* @__PURE__ */ React19.createElement(
9825
+ InlineMetrics,
9826
+ {
9827
+ summary,
9828
+ ctxRatio,
9829
+ ctxMax,
9830
+ balance,
9831
+ coldStart
9832
+ }
9833
+ ))
9834
+ );
9315
9835
  }
9316
9836
  function Header({
9317
9837
  model: model2,
@@ -10117,7 +10637,7 @@ var handlers = {
10117
10637
  var exit = () => ({ exit: true });
10118
10638
  var clear = () => ({
10119
10639
  clear: true,
10120
- info: "\u25B8 cleared visible scrollback only. Context (message log) is intact \u2014 next turn still sees everything. Use /new to start fresh, or /forget to delete the session entirely."
10640
+ info: "\u25B8 terminal cleared (viewport + scrollback). Context (message log) is intact \u2014 next turn still sees everything. Use /new to start fresh, or /forget to delete the session entirely."
10121
10641
  });
10122
10642
  var resetLog = (_args, loop) => {
10123
10643
  const { dropped } = loop.clearLog();
@@ -10133,8 +10653,11 @@ var keys = () => ({
10133
10653
  " Enter submit the current prompt",
10134
10654
  " Shift+Enter / Ctrl+J insert a newline (multi-line prompt)",
10135
10655
  " \\<Enter> bash-style line continuation",
10136
- " \u2190 \u2192 \u2191 \u2193 move cursor / recall history when buffer empty",
10137
- " Ctrl+A / Ctrl+E jump to start / end of the current line",
10656
+ " \u2190 \u2192 \u2191 \u2193 move cursor / recall history at buffer boundary",
10657
+ " PageUp / PageDown jump to top / bottom of the WHOLE buffer (handy after a big paste)",
10658
+ " Ctrl+A / Ctrl+E jump to start / end of the CURRENT line",
10659
+ " Ctrl+U clear the entire input buffer",
10660
+ " Ctrl+W delete the word before the cursor",
10138
10661
  " Backspace delete left; Delete delete under cursor",
10139
10662
  " Esc abort the in-flight turn",
10140
10663
  " y / n accept / reject pending edits (code mode)",
@@ -10220,7 +10743,13 @@ var help = () => ({
10220
10743
  "",
10221
10744
  "Sessions (auto-enabled by default, named 'default'):",
10222
10745
  " reasonix chat --session <name> use a different named session",
10223
- " reasonix chat --no-session disable persistence for this run"
10746
+ " reasonix chat --no-session disable persistence for this run",
10747
+ "",
10748
+ "Known limitation:",
10749
+ " Resizing the terminal mid-session may stack ghost header frames in",
10750
+ " scrollback (Ink library's live-region clear doesn't account for line",
10751
+ " re-wrapping at the new width). Scroll-up history is unaffected; the",
10752
+ " artifact is purely visual and clears the next time you /clear."
10224
10753
  ].join("\n")
10225
10754
  });
10226
10755
  var setup = () => ({
@@ -11754,6 +12283,14 @@ function App({
11754
12283
  const abortedThisTurn = useRef5(false);
11755
12284
  const [ongoingTool, setOngoingTool] = useState10(null);
11756
12285
  const [toolProgress, setToolProgress] = useState10(null);
12286
+ const { stdout: stdout2 } = useStdout4();
12287
+ useEffect5(() => {
12288
+ if (!stdout2 || !stdout2.isTTY) return;
12289
+ stdout2.write("\x1B[?2004h");
12290
+ return () => {
12291
+ stdout2.write("\x1B[?2004l");
12292
+ };
12293
+ }, [stdout2]);
11757
12294
  const { activity: subagentActivity, sinkRef: subagentSinkRef } = useSubagent({
11758
12295
  session,
11759
12296
  setHistorical
@@ -12114,24 +12651,21 @@ function App({
12114
12651
  return;
12115
12652
  }
12116
12653
  }
12117
- if (input.length === 0) {
12118
- const hist = promptHistory.current;
12119
- if (key.upArrow) {
12120
- if (hist.length === 0) return;
12121
- const nextCursor = Math.min(historyCursor.current + 1, hist.length - 1);
12122
- historyCursor.current = nextCursor;
12123
- setInput(hist[hist.length - 1 - nextCursor] ?? "");
12124
- return;
12125
- }
12126
- if (key.downArrow) {
12127
- if (historyCursor.current < 0) return;
12128
- const nextCursor = historyCursor.current - 1;
12129
- historyCursor.current = nextCursor;
12130
- setInput(nextCursor < 0 ? "" : hist[hist.length - 1 - nextCursor] ?? "");
12131
- return;
12132
- }
12133
- }
12134
12654
  });
12655
+ const recallPrev = useCallback4(() => {
12656
+ const hist = promptHistory.current;
12657
+ if (hist.length === 0) return;
12658
+ const nextCursor = Math.min(historyCursor.current + 1, hist.length - 1);
12659
+ historyCursor.current = nextCursor;
12660
+ setInput(hist[hist.length - 1 - nextCursor] ?? "");
12661
+ }, []);
12662
+ const recallNext = useCallback4(() => {
12663
+ if (historyCursor.current < 0) return;
12664
+ const hist = promptHistory.current;
12665
+ const nextCursor = historyCursor.current - 1;
12666
+ historyCursor.current = nextCursor;
12667
+ setInput(nextCursor < 0 ? "" : hist[hist.length - 1 - nextCursor] ?? "");
12668
+ }, []);
12135
12669
  useEffect5(() => {
12136
12670
  if (!tools || !codeMode) return;
12137
12671
  tools.setToolInterceptor(async (name, args) => {
@@ -12392,6 +12926,7 @@ function App({
12392
12926
  return;
12393
12927
  }
12394
12928
  if (result.clear && result.info) {
12929
+ stdout2?.write("\x1B[2J\x1B[3J\x1B[H");
12395
12930
  setHistorical([
12396
12931
  {
12397
12932
  id: `sys-${Date.now()}`,
@@ -12407,6 +12942,7 @@ function App({
12407
12942
  return;
12408
12943
  }
12409
12944
  if (result.clear) {
12945
+ stdout2?.write("\x1B[2J\x1B[3J\x1B[H");
12410
12946
  setHistorical([]);
12411
12947
  if (codeMode) {
12412
12948
  pendingEdits.current = [];
@@ -12895,7 +13431,8 @@ function App({
12895
13431
  refreshLatestVersion,
12896
13432
  refreshModels,
12897
13433
  proArmed,
12898
- persistPlanState
13434
+ persistPlanState,
13435
+ stdout2
12899
13436
  ]
12900
13437
  );
12901
13438
  const handleShellConfirm = useCallback4(
@@ -13426,7 +13963,9 @@ Continue executing from the next pending step. Call mark_step_complete after eac
13426
13963
  value: input,
13427
13964
  onChange: setInput,
13428
13965
  onSubmit: handleSubmit,
13429
- disabled: busy
13966
+ disabled: busy,
13967
+ onHistoryPrev: recallPrev,
13968
+ onHistoryNext: recallNext
13430
13969
  }
13431
13970
  ), /* @__PURE__ */ React20.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React20.createElement(
13432
13971
  AtMentionSuggestions,
@@ -14846,6 +15385,12 @@ Every factual claim about a codebase must be backed by evidence. Reasonix VALIDA
14846
15385
 
14847
15386
  Asserting absence without checking is how evaluative answers go wrong. Treat the urge to write "missing" as a red flag in your own reasoning.
14848
15387
 
15388
+ # Don't invent what changes \u2014 search instead
15389
+
15390
+ Your training data has a cutoff. When an answer's correctness depends on something that changes over time (the user is asking what's happening, not what's true) and a search tool is available, search first. Inventing currently-correct values from training memory is the most common way these answers go wrong, and the user usually can't tell until much later.
15391
+
15392
+ The signal isn't a topic list \u2014 it's: "if I'm wrong about this, is it because reality moved on?". If yes, ground the answer in fresh evidence; if no (definitions, mechanisms, well-established APIs), answer from memory.
15393
+
14849
15394
  ${ESCALATION_CONTRACT}`;
14850
15395
  var program = new Command();
14851
15396
  program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION);