reasonix 0.15.0 → 0.16.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
@@ -12,7 +12,7 @@ import {
12
12
  memoryEnabled,
13
13
  readProjectMemory,
14
14
  sanitizeMemoryName
15
- } from "./chunk-7546PPEL.js";
15
+ } from "./chunk-3ALFOYE6.js";
16
16
 
17
17
  // src/cli/index.ts
18
18
  import { Command } from "commander";
@@ -1283,7 +1283,7 @@ function blockToString(block) {
1283
1283
  return `[unknown block: ${JSON.stringify(block)}]`;
1284
1284
  }
1285
1285
 
1286
- // src/memory.ts
1286
+ // src/memory/runtime.ts
1287
1287
  import { createHash } from "crypto";
1288
1288
  var ImmutablePrefix = class {
1289
1289
  system;
@@ -1375,6 +1375,120 @@ var VolatileScratch = class {
1375
1375
  }
1376
1376
  };
1377
1377
 
1378
+ // src/memory/session.ts
1379
+ import {
1380
+ appendFileSync,
1381
+ chmodSync as chmodSync2,
1382
+ existsSync as existsSync3,
1383
+ mkdirSync as mkdirSync2,
1384
+ readFileSync as readFileSync4,
1385
+ readdirSync,
1386
+ statSync,
1387
+ unlinkSync,
1388
+ writeFileSync as writeFileSync2
1389
+ } from "fs";
1390
+ import { homedir as homedir3 } from "os";
1391
+ import { dirname as dirname3, join as join4 } from "path";
1392
+ function sessionsDir() {
1393
+ return join4(homedir3(), ".reasonix", "sessions");
1394
+ }
1395
+ function sessionPath(name) {
1396
+ return join4(sessionsDir(), `${sanitizeName(name)}.jsonl`);
1397
+ }
1398
+ function sanitizeName(name) {
1399
+ const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
1400
+ return cleaned || "default";
1401
+ }
1402
+ function loadSessionMessages(name) {
1403
+ const path5 = sessionPath(name);
1404
+ if (!existsSync3(path5)) return [];
1405
+ try {
1406
+ const raw = readFileSync4(path5, "utf8");
1407
+ const out = [];
1408
+ for (const line of raw.split(/\r?\n/)) {
1409
+ const trimmed = line.trim();
1410
+ if (!trimmed) continue;
1411
+ try {
1412
+ const msg = JSON.parse(trimmed);
1413
+ if (msg && typeof msg === "object" && "role" in msg) out.push(msg);
1414
+ } catch {
1415
+ }
1416
+ }
1417
+ return out;
1418
+ } catch {
1419
+ return [];
1420
+ }
1421
+ }
1422
+ function appendSessionMessage(name, message) {
1423
+ const path5 = sessionPath(name);
1424
+ mkdirSync2(dirname3(path5), { recursive: true });
1425
+ appendFileSync(path5, `${JSON.stringify(message)}
1426
+ `, "utf8");
1427
+ try {
1428
+ chmodSync2(path5, 384);
1429
+ } catch {
1430
+ }
1431
+ }
1432
+ function listSessions() {
1433
+ const dir = sessionsDir();
1434
+ if (!existsSync3(dir)) return [];
1435
+ try {
1436
+ const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
1437
+ return files.map((file) => {
1438
+ const path5 = join4(dir, file);
1439
+ const stat2 = statSync(path5);
1440
+ const name = file.replace(/\.jsonl$/, "");
1441
+ const messageCount = countLines(path5);
1442
+ return { name, path: path5, size: stat2.size, messageCount, mtime: stat2.mtime };
1443
+ }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
1444
+ } catch {
1445
+ return [];
1446
+ }
1447
+ }
1448
+ function pruneStaleSessions(daysOld = 90) {
1449
+ const cutoff = Date.now() - daysOld * 24 * 60 * 60 * 1e3;
1450
+ const deleted = [];
1451
+ for (const s of listSessions()) {
1452
+ if (s.mtime.getTime() < cutoff) {
1453
+ if (deleteSession(s.name)) deleted.push(s.name);
1454
+ }
1455
+ }
1456
+ return deleted;
1457
+ }
1458
+ function deleteSession(name) {
1459
+ const path5 = sessionPath(name);
1460
+ try {
1461
+ unlinkSync(path5);
1462
+ const sidecar = path5.replace(/\.jsonl$/, ".pending.json");
1463
+ try {
1464
+ unlinkSync(sidecar);
1465
+ } catch {
1466
+ }
1467
+ return true;
1468
+ } catch {
1469
+ return false;
1470
+ }
1471
+ }
1472
+ function rewriteSession(name, messages) {
1473
+ const path5 = sessionPath(name);
1474
+ mkdirSync2(dirname3(path5), { recursive: true });
1475
+ const body = messages.map((m) => JSON.stringify(m)).join("\n");
1476
+ writeFileSync2(path5, body ? `${body}
1477
+ ` : "", "utf8");
1478
+ try {
1479
+ chmodSync2(path5, 384);
1480
+ } catch {
1481
+ }
1482
+ }
1483
+ function countLines(path5) {
1484
+ try {
1485
+ const raw = readFileSync4(path5, "utf8");
1486
+ return raw.split(/\r?\n/).filter((l) => l.trim()).length;
1487
+ } catch {
1488
+ return 0;
1489
+ }
1490
+ }
1491
+
1378
1492
  // src/repair/scavenge.ts
1379
1493
  function scavengeToolCalls(reasoningContent, opts) {
1380
1494
  if (!reasoningContent) return { calls: [], notes: [] };
@@ -1676,121 +1790,7 @@ function signature(call) {
1676
1790
  return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
1677
1791
  }
1678
1792
 
1679
- // src/session.ts
1680
- import {
1681
- appendFileSync,
1682
- chmodSync as chmodSync2,
1683
- existsSync as existsSync3,
1684
- mkdirSync as mkdirSync2,
1685
- readFileSync as readFileSync4,
1686
- readdirSync,
1687
- statSync,
1688
- unlinkSync,
1689
- writeFileSync as writeFileSync2
1690
- } from "fs";
1691
- import { homedir as homedir3 } from "os";
1692
- import { dirname as dirname3, join as join4 } from "path";
1693
- function sessionsDir() {
1694
- return join4(homedir3(), ".reasonix", "sessions");
1695
- }
1696
- function sessionPath(name) {
1697
- return join4(sessionsDir(), `${sanitizeName(name)}.jsonl`);
1698
- }
1699
- function sanitizeName(name) {
1700
- const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
1701
- return cleaned || "default";
1702
- }
1703
- function loadSessionMessages(name) {
1704
- const path5 = sessionPath(name);
1705
- if (!existsSync3(path5)) return [];
1706
- try {
1707
- const raw = readFileSync4(path5, "utf8");
1708
- const out = [];
1709
- for (const line of raw.split(/\r?\n/)) {
1710
- const trimmed = line.trim();
1711
- if (!trimmed) continue;
1712
- try {
1713
- const msg = JSON.parse(trimmed);
1714
- if (msg && typeof msg === "object" && "role" in msg) out.push(msg);
1715
- } catch {
1716
- }
1717
- }
1718
- return out;
1719
- } catch {
1720
- return [];
1721
- }
1722
- }
1723
- function appendSessionMessage(name, message) {
1724
- const path5 = sessionPath(name);
1725
- mkdirSync2(dirname3(path5), { recursive: true });
1726
- appendFileSync(path5, `${JSON.stringify(message)}
1727
- `, "utf8");
1728
- try {
1729
- chmodSync2(path5, 384);
1730
- } catch {
1731
- }
1732
- }
1733
- function listSessions() {
1734
- const dir = sessionsDir();
1735
- if (!existsSync3(dir)) return [];
1736
- try {
1737
- const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
1738
- return files.map((file) => {
1739
- const path5 = join4(dir, file);
1740
- const stat2 = statSync(path5);
1741
- const name = file.replace(/\.jsonl$/, "");
1742
- const messageCount = countLines(path5);
1743
- return { name, path: path5, size: stat2.size, messageCount, mtime: stat2.mtime };
1744
- }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
1745
- } catch {
1746
- return [];
1747
- }
1748
- }
1749
- function pruneStaleSessions(daysOld = 90) {
1750
- const cutoff = Date.now() - daysOld * 24 * 60 * 60 * 1e3;
1751
- const deleted = [];
1752
- for (const s of listSessions()) {
1753
- if (s.mtime.getTime() < cutoff) {
1754
- if (deleteSession(s.name)) deleted.push(s.name);
1755
- }
1756
- }
1757
- return deleted;
1758
- }
1759
- function deleteSession(name) {
1760
- const path5 = sessionPath(name);
1761
- try {
1762
- unlinkSync(path5);
1763
- const sidecar = path5.replace(/\.jsonl$/, ".pending.json");
1764
- try {
1765
- unlinkSync(sidecar);
1766
- } catch {
1767
- }
1768
- return true;
1769
- } catch {
1770
- return false;
1771
- }
1772
- }
1773
- function rewriteSession(name, messages) {
1774
- const path5 = sessionPath(name);
1775
- mkdirSync2(dirname3(path5), { recursive: true });
1776
- const body = messages.map((m) => JSON.stringify(m)).join("\n");
1777
- writeFileSync2(path5, body ? `${body}
1778
- ` : "", "utf8");
1779
- try {
1780
- chmodSync2(path5, 384);
1781
- } catch {
1782
- }
1783
- }
1784
- function countLines(path5) {
1785
- try {
1786
- const raw = readFileSync4(path5, "utf8");
1787
- return raw.split(/\r?\n/).filter((l) => l.trim()).length;
1788
- } catch {
1789
- return 0;
1790
- }
1791
- }
1792
-
1793
- // src/telemetry.ts
1793
+ // src/telemetry/stats.ts
1794
1794
  var DEEPSEEK_PRICING = {
1795
1795
  "deepseek-v4-flash": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
1796
1796
  "deepseek-v4-pro": { inputCacheHit: 0.139, inputCacheMiss: 1.667, output: 3.333 },
@@ -2795,7 +2795,7 @@ ${summary}`;
2795
2795
  assistantMessage(content, toolCalls, producingModel, reasoningContent) {
2796
2796
  const msg = { role: "assistant", content };
2797
2797
  if (toolCalls.length > 0) msg.tool_calls = toolCalls;
2798
- if (isThinkingModeModel(producingModel)) {
2798
+ if (isThinkingModeModel(producingModel) || reasoningContent && reasoningContent.length > 0) {
2799
2799
  msg.reasoning_content = reasoningContent ?? "";
2800
2800
  }
2801
2801
  return msg;
@@ -5706,7 +5706,7 @@ function loadDotenv(path5 = ".env") {
5706
5706
  }
5707
5707
  }
5708
5708
 
5709
- // src/transcript.ts
5709
+ // src/transcript/log.ts
5710
5710
  import { createWriteStream, readFileSync as readFileSync7 } from "fs";
5711
5711
  function recordFromLoopEvent(ev, extra) {
5712
5712
  const rec = {
@@ -5788,7 +5788,7 @@ function parseTranscript(raw) {
5788
5788
  return out;
5789
5789
  }
5790
5790
 
5791
- // src/replay.ts
5791
+ // src/transcript/replay.ts
5792
5792
  function groupRecordsByTurn(records) {
5793
5793
  const byTurn = /* @__PURE__ */ new Map();
5794
5794
  for (const rec of records) {
@@ -5895,7 +5895,7 @@ function round2(n, digits) {
5895
5895
  return Math.round(n * f) / f;
5896
5896
  }
5897
5897
 
5898
- // src/diff.ts
5898
+ // src/transcript/diff.ts
5899
5899
  function findNextDivergence(pairs, fromIdx) {
5900
5900
  for (let i = fromIdx + 1; i < pairs.length; i++) {
5901
5901
  if (pairs[i].kind !== "match") return i;
@@ -7208,7 +7208,7 @@ function sep() {
7208
7208
  return process.platform === "win32" ? "\\" : "/";
7209
7209
  }
7210
7210
 
7211
- // src/usage.ts
7211
+ // src/telemetry/usage.ts
7212
7212
  import {
7213
7213
  appendFileSync as appendFileSync2,
7214
7214
  existsSync as existsSync8,
@@ -7845,305 +7845,48 @@ var Eventizer = class {
7845
7845
  errorEvent(turn, message, recoverable) {
7846
7846
  return {
7847
7847
  id: ++this.nextId,
7848
- ts: (/* @__PURE__ */ new Date()).toISOString(),
7849
- turn,
7850
- type: "error",
7851
- message,
7852
- recoverable
7853
- };
7854
- }
7855
- /** Pattern-match warning text since LoopEvent doesn't carry a typed kind. */
7856
- classifyWarning(ev) {
7857
- const c = ev.content;
7858
- if (/\bauto-escalating to\b|\barmed\b.*pro|NEEDS_PRO/.test(c)) {
7859
- return {
7860
- id: ++this.nextId,
7861
- ts: (/* @__PURE__ */ new Date()).toISOString(),
7862
- turn: ev.turn,
7863
- type: "policy.escalated",
7864
- fromModel: "",
7865
- toModel: "",
7866
- reason: c.includes("armed") ? "user-request" : "self-report"
7867
- };
7868
- }
7869
- if (/budget\b.*\$|\$\d.*\/\s*\$\d/.test(c)) {
7870
- const blocked = /blocked|exceeded|refus/i.test(c);
7871
- return {
7872
- id: ++this.nextId,
7873
- ts: (/* @__PURE__ */ new Date()).toISOString(),
7874
- turn: ev.turn,
7875
- type: blocked ? "policy.budget.blocked" : "policy.budget.warning",
7876
- spentUsd: 0,
7877
- capUsd: 0
7878
- };
7879
- }
7880
- return this.errorEvent(ev.turn, c, true);
7881
- }
7882
- };
7883
- function looksLikeToolError(content, _toolName) {
7884
- if (!content) return false;
7885
- if (content.startsWith("ERROR:")) return true;
7886
- if (content.startsWith("[hook block]")) return true;
7887
- if (/^\{"error"\s*:/.test(content)) return true;
7888
- if (/\bConfirmationError:|\bNeedsConfirmationError\b/.test(content)) return true;
7889
- return false;
7890
- }
7891
-
7892
- // src/frame/width.ts
7893
- import stringWidthLib from "string-width";
7894
- var segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
7895
- function graphemes(s) {
7896
- return Array.from(segmenter.segment(s), (seg) => seg.segment);
7897
- }
7898
- function graphemeWidth(g) {
7899
- if (g.length === 0) return 0;
7900
- const w = stringWidthLib(g);
7901
- if (w <= 0) return 0;
7902
- if (w >= 2) return 2;
7903
- return 1;
7904
- }
7905
-
7906
- // src/frame/frame.ts
7907
- var SPACE = { char: " ", width: 1 };
7908
- var TAIL = { char: "", width: 1, tail: true };
7909
- function empty(width = 0) {
7910
- return { width, rows: [] };
7911
- }
7912
- function text(s, opts) {
7913
- const { width, fg, bg, bold, dim, italic, underline, inverse, href } = opts;
7914
- if (width <= 0) return empty(0);
7915
- const styleOf = (g, w) => {
7916
- const base = { char: g, width: w };
7917
- if (fg !== void 0) base.fg = fg;
7918
- if (bg !== void 0) base.bg = bg;
7919
- if (bold) base.bold = true;
7920
- if (dim) base.dim = true;
7921
- if (italic) base.italic = true;
7922
- if (underline) base.underline = true;
7923
- if (inverse) base.inverse = true;
7924
- if (href !== void 0) base.href = href;
7925
- return base;
7926
- };
7927
- const rows = [];
7928
- const lines = s.split("\n");
7929
- for (const line of lines) {
7930
- if (line.length === 0) {
7931
- rows.push(padRowRight([], width));
7932
- continue;
7933
- }
7934
- let buf = [];
7935
- let bufWidth = 0;
7936
- for (const g of graphemes(line)) {
7937
- const w = graphemeWidth(g);
7938
- if (w === 0) continue;
7939
- if (bufWidth + w > width) {
7940
- rows.push(padRowRight(buf, width - bufWidth));
7941
- buf = [];
7942
- bufWidth = 0;
7943
- }
7944
- buf.push(styleOf(g, w));
7945
- if (w === 2) buf.push(TAIL);
7946
- bufWidth += w;
7947
- }
7948
- rows.push(padRowRight(buf, width - bufWidth));
7949
- }
7950
- return { width, rows };
7951
- }
7952
- function padRowRight(cells, extraSpaces) {
7953
- if (extraSpaces <= 0) return cells.slice();
7954
- const out = cells.slice();
7955
- for (let i = 0; i < extraSpaces; i++) out.push(SPACE);
7956
- return out;
7957
- }
7958
- function spacerRow(width) {
7959
- if (width <= 0) return [];
7960
- return Array.from({ length: width }, () => SPACE);
7961
- }
7962
- function vstack(...frames) {
7963
- if (frames.length === 0) return empty(0);
7964
- const w = Math.max(...frames.map((f) => f.width));
7965
- const rows = [];
7966
- for (const f of frames) {
7967
- if (f.width === w) {
7968
- rows.push(...f.rows);
7969
- } else {
7970
- const extra = w - f.width;
7971
- for (const r of f.rows) rows.push(padRowRight(r, extra));
7972
- }
7973
- }
7974
- return { width: w, rows };
7975
- }
7976
- function hstack(...frames) {
7977
- if (frames.length === 0) return empty(0);
7978
- const h = Math.max(...frames.map((f) => f.rows.length));
7979
- const w = frames.reduce((a, f) => a + f.width, 0);
7980
- const rows = [];
7981
- for (let i = 0; i < h; i++) {
7982
- const cells = [];
7983
- for (const f of frames) {
7984
- const r = f.rows[i] ?? spacerRow(f.width);
7985
- cells.push(...r);
7986
- }
7987
- rows.push(cells);
7988
- }
7989
- return { width: w, rows };
7990
- }
7991
- function pad(f, top, right, bottom2, left) {
7992
- const newWidth = f.width + Math.max(0, left) + Math.max(0, right);
7993
- const tPad = Math.max(0, top);
7994
- const bPad = Math.max(0, bottom2);
7995
- const blank2 = spacerRow(newWidth);
7996
- const rows = [];
7997
- for (let i = 0; i < tPad; i++) rows.push(blank2);
7998
- if (left <= 0 && right <= 0) {
7999
- rows.push(...f.rows);
8000
- } else {
8001
- const lPad = spacerRow(Math.max(0, left));
8002
- const rPad = spacerRow(Math.max(0, right));
8003
- for (const r of f.rows) rows.push([...lPad, ...r, ...rPad]);
8004
- }
8005
- for (let i = 0; i < bPad; i++) rows.push(blank2);
8006
- return { width: newWidth, rows };
8007
- }
8008
- function borderLeft(f, color2, char = "\u2502") {
8009
- const bar = { char, width: 1, fg: color2 };
8010
- const newWidth = f.width + 1;
8011
- const rows = [];
8012
- for (const r of f.rows) rows.push([bar, ...r]);
8013
- return { width: newWidth, rows };
8014
- }
8015
-
8016
- // src/frame/ansi.ts
8017
- var ESC = "\x1B";
8018
- var RESET = `${ESC}[0m`;
8019
- function sameStyle(a, b) {
8020
- return a.fg === b.fg && a.bg === b.bg && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.italic === !!b.italic && !!a.underline === !!b.underline && !!a.inverse === !!b.inverse && a.href === b.href;
8021
- }
8022
- function fgEscape(color2) {
8023
- if (!color2) return null;
8024
- const rgb = parseColor(color2);
8025
- if (rgb) return `38;2;${rgb[0]};${rgb[1]};${rgb[2]}`;
8026
- const named = NAMED_FG[color2.toLowerCase()];
8027
- if (named !== void 0) return String(named);
8028
- return null;
8029
- }
8030
- function bgEscape(color2) {
8031
- if (!color2) return null;
8032
- const rgb = parseColor(color2);
8033
- if (rgb) return `48;2;${rgb[0]};${rgb[1]};${rgb[2]}`;
8034
- const named = NAMED_BG[color2.toLowerCase()];
8035
- if (named !== void 0) return String(named);
8036
- return null;
8037
- }
8038
- function parseColor(s) {
8039
- if (!s.startsWith("#")) return null;
8040
- const hex = s.slice(1);
8041
- if (hex.length !== 6) return null;
8042
- const r = Number.parseInt(hex.slice(0, 2), 16);
8043
- const g = Number.parseInt(hex.slice(2, 4), 16);
8044
- const b = Number.parseInt(hex.slice(4, 6), 16);
8045
- if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null;
8046
- return [r, g, b];
8047
- }
8048
- var NAMED_FG = {
8049
- black: 30,
8050
- red: 31,
8051
- green: 32,
8052
- yellow: 33,
8053
- blue: 34,
8054
- magenta: 35,
8055
- cyan: 36,
8056
- white: 37,
8057
- gray: 90,
8058
- grey: 90,
8059
- brightred: 91,
8060
- brightgreen: 92,
8061
- brightyellow: 93,
8062
- brightblue: 94,
8063
- brightmagenta: 95,
8064
- brightcyan: 96,
8065
- brightwhite: 97
8066
- };
8067
- var NAMED_BG = {
8068
- black: 40,
8069
- red: 41,
8070
- green: 42,
8071
- yellow: 43,
8072
- blue: 44,
8073
- magenta: 45,
8074
- cyan: 46,
8075
- white: 47,
8076
- gray: 100,
8077
- grey: 100
8078
- };
8079
- function styleToAnsi(s) {
8080
- const codes = [];
8081
- if (s.bold) codes.push("1");
8082
- if (s.dim) codes.push("2");
8083
- if (s.italic) codes.push("3");
8084
- if (s.underline) codes.push("4");
8085
- if (s.inverse) codes.push("7");
8086
- const fg = fgEscape(s.fg);
8087
- if (fg) codes.push(fg);
8088
- const bg = bgEscape(s.bg);
8089
- if (bg) codes.push(bg);
8090
- if (codes.length === 0) return "";
8091
- return `${ESC}[${codes.join(";")}m`;
8092
- }
8093
- var EMPTY_STYLE = {};
8094
- function frameToAnsi(f, opts = {}) {
8095
- const out = [];
8096
- for (let i = 0; i < f.rows.length; i++) {
8097
- out.push(rowToAnsi(f.rows[i], opts));
8098
- }
8099
- return out.join("\n");
8100
- }
8101
- function rowToAnsi(row2, opts) {
8102
- if (opts.plain) {
8103
- let s = "";
8104
- for (const c of row2) {
8105
- if (c.tail) continue;
8106
- s += c.char;
8107
- }
8108
- return s;
8109
- }
8110
- let result = "";
8111
- let curStyle = EMPTY_STYLE;
8112
- let inHref = false;
8113
- let curHref;
8114
- for (const c of row2) {
8115
- if (c.tail) continue;
8116
- const cellStyle = {
8117
- fg: c.fg,
8118
- bg: c.bg,
8119
- bold: c.bold,
8120
- dim: c.dim,
8121
- italic: c.italic,
8122
- underline: c.underline,
8123
- inverse: c.inverse,
8124
- href: c.href
7848
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7849
+ turn,
7850
+ type: "error",
7851
+ message,
7852
+ recoverable
8125
7853
  };
8126
- if (cellStyle.href !== curHref) {
8127
- if (inHref) {
8128
- result += `${ESC}]8;;${ESC}\\`;
8129
- inHref = false;
8130
- }
8131
- if (cellStyle.href !== void 0) {
8132
- result += `${ESC}]8;;${cellStyle.href}${ESC}\\`;
8133
- inHref = true;
8134
- }
8135
- curHref = cellStyle.href;
7854
+ }
7855
+ /** Pattern-match warning text since LoopEvent doesn't carry a typed kind. */
7856
+ classifyWarning(ev) {
7857
+ const c = ev.content;
7858
+ if (/\bauto-escalating to\b|\barmed\b.*pro|NEEDS_PRO/.test(c)) {
7859
+ return {
7860
+ id: ++this.nextId,
7861
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7862
+ turn: ev.turn,
7863
+ type: "policy.escalated",
7864
+ fromModel: "",
7865
+ toModel: "",
7866
+ reason: c.includes("armed") ? "user-request" : "self-report"
7867
+ };
8136
7868
  }
8137
- if (!sameStyle(curStyle, cellStyle)) {
8138
- result += RESET;
8139
- result += styleToAnsi(cellStyle);
8140
- curStyle = cellStyle;
7869
+ if (/budget\b.*\$|\$\d.*\/\s*\$\d/.test(c)) {
7870
+ const blocked = /blocked|exceeded|refus/i.test(c);
7871
+ return {
7872
+ id: ++this.nextId,
7873
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7874
+ turn: ev.turn,
7875
+ type: blocked ? "policy.budget.blocked" : "policy.budget.warning",
7876
+ spentUsd: 0,
7877
+ capUsd: 0
7878
+ };
8141
7879
  }
8142
- result += c.char;
7880
+ return this.errorEvent(ev.turn, c, true);
8143
7881
  }
8144
- if (inHref) result += `${ESC}]8;;${ESC}\\`;
8145
- result += RESET;
8146
- return result;
7882
+ };
7883
+ function looksLikeToolError(content, _toolName) {
7884
+ if (!content) return false;
7885
+ if (content.startsWith("ERROR:")) return true;
7886
+ if (content.startsWith("[hook block]")) return true;
7887
+ if (/^\{"error"\s*:/.test(content)) return true;
7888
+ if (/\bConfirmationError:|\bNeedsConfirmationError\b/.test(content)) return true;
7889
+ return false;
8147
7890
  }
8148
7891
 
8149
7892
  // src/server/index.ts
@@ -11444,6 +11187,14 @@ var StdinReader = class {
11444
11187
  this.dispatch({ input: "", mouseClick: true, mouseRow: row2, mouseCol: col });
11445
11188
  return;
11446
11189
  }
11190
+ if (tail === "M" && btn === 32) {
11191
+ this.dispatch({ input: "", mouseDrag: true, mouseRow: row2, mouseCol: col });
11192
+ return;
11193
+ }
11194
+ if (tail === "m") {
11195
+ this.dispatch({ input: "", mouseRelease: true, mouseRow: row2, mouseCol: col });
11196
+ return;
11197
+ }
11447
11198
  return;
11448
11199
  }
11449
11200
  }
@@ -11707,7 +11458,7 @@ var ChoiceConfirm = React5.memo(ChoiceConfirmInner);
11707
11458
  // src/cli/ui/ChromeBar.tsx
11708
11459
  import { Box as Box5, Text as Text5, useStdout as useStdout3 } from "ink";
11709
11460
  import React7 from "react";
11710
- import stringWidth2 from "string-width";
11461
+ import stringWidth from "string-width";
11711
11462
 
11712
11463
  // src/cli/ui/primitives.tsx
11713
11464
  import { Text as Text4, useStdout as useStdout2 } from "ink";
@@ -11751,19 +11502,19 @@ function ChromeBar(props) {
11751
11502
  const cachePct = Math.round(props.summary.cacheHitRatio * 100);
11752
11503
  const cacheLabel = `cache ${"\u2588".repeat(CACHE_BAR_CELLS)} 100%`;
11753
11504
  const costLabel = `$${props.summary.totalCostUsd.toFixed(4)}${props.budgetUsd && props.budgetUsd > 0 ? ` / $${props.budgetUsd.toFixed(2)}` : ""}`;
11754
- const brandW = stringWidth2("\u25C8 reasonix");
11755
- const projectW = projectName ? SEP + stringWidth2(projectName) : 0;
11505
+ const brandW = stringWidth("\u25C8 reasonix");
11506
+ const projectW = projectName ? SEP + stringWidth(projectName) : 0;
11756
11507
  const fixedLeft = brandW + projectW;
11757
- const scrollW = scrollLabel ? stringWidth2(scrollLabel) + GAP : 0;
11758
- const modeW = mode2 ? stringWidth2(`[${mode2.label}]`) + GAP : 0;
11759
- const proW = proPill ? stringWidth2(`[${proPill.label}]`) + GAP : 0;
11760
- const costW = stringWidth2(costLabel);
11508
+ const scrollW = scrollLabel ? stringWidth(scrollLabel) + GAP : 0;
11509
+ const modeW = mode2 ? stringWidth(`[${mode2.label}]`) + GAP : 0;
11510
+ const proW = proPill ? stringWidth(`[${proPill.label}]`) + GAP : 0;
11511
+ const costW = stringWidth(costLabel);
11761
11512
  const fixedRight = scrollW + modeW + proW + costW;
11762
11513
  let budget3 = cols - fixedLeft - fixedRight;
11763
- const balanceOptW = balanceLabel ? GAP + stringWidth2(balanceLabel) : 0;
11764
- const cacheOptW = GAP + stringWidth2(cacheLabel);
11765
- const sessionOptW = props.sessionName ? SEP + stringWidth2(props.sessionName) : 0;
11766
- const updateOptW = updateLabel ? stringWidth2(updateLabel) + GAP : 0;
11514
+ const balanceOptW = balanceLabel ? GAP + stringWidth(balanceLabel) : 0;
11515
+ const cacheOptW = GAP + stringWidth(cacheLabel);
11516
+ const sessionOptW = props.sessionName ? SEP + stringWidth(props.sessionName) : 0;
11517
+ const updateOptW = updateLabel ? stringWidth(updateLabel) + GAP : 0;
11767
11518
  const showBalance = balanceOptW > 0 && budget3 >= balanceOptW;
11768
11519
  if (showBalance) budget3 -= balanceOptW;
11769
11520
  const showCache = budget3 >= cacheOptW;
@@ -11827,7 +11578,7 @@ function basename(path5) {
11827
11578
  // src/cli/ui/CtxFooter.tsx
11828
11579
  import { Box as Box7, Text as Text7, useStdout as useStdout4 } from "ink";
11829
11580
  import React9, { useMemo } from "react";
11830
- import stringWidth3 from "string-width";
11581
+ import stringWidth2 from "string-width";
11831
11582
 
11832
11583
  // src/cli/ui/ctx-breakdown.tsx
11833
11584
  import { Box as Box6, Text as Text6 } from "ink";
@@ -11918,21 +11669,21 @@ function CtxFooter({ loop: loop2, summary }) {
11918
11669
  const totalStr = `${formatTokens(total)}/${formatTokens(data.ctxMax)}`;
11919
11670
  const pctStr = ` \xB7 ${pct2}%`;
11920
11671
  const warnStr = pct2 >= 80 ? " \xB7 /compact" : "";
11921
- const fixedW = stringWidth3(labelStr) + BAR_CELLS + 1 + // space after bar
11922
- stringWidth3(totalStr) + stringWidth3(pctStr) + stringWidth3(warnStr);
11672
+ const fixedW = stringWidth2(labelStr) + BAR_CELLS + 1 + // space after bar
11673
+ stringWidth2(totalStr) + stringWidth2(pctStr) + stringWidth2(warnStr);
11923
11674
  let budget3 = cols - fixedW;
11924
11675
  const sysSeg = ` \xB7 sys ${formatTokens(data.systemTokens)}`;
11925
11676
  const toolsSeg = ` \xB7 tools ${formatTokens(data.toolsTokens)}`;
11926
11677
  const logSeg = ` \xB7 log ${formatTokens(data.logTokens)}`;
11927
11678
  const inputSeg = data.inputTokens > 0 ? ` \xB7 input ${formatTokens(data.inputTokens)}` : "";
11928
- const showSys = budget3 >= stringWidth3(sysSeg);
11929
- if (showSys) budget3 -= stringWidth3(sysSeg);
11930
- const showTools = budget3 >= stringWidth3(toolsSeg);
11931
- if (showTools) budget3 -= stringWidth3(toolsSeg);
11932
- const showLog = budget3 >= stringWidth3(logSeg);
11933
- if (showLog) budget3 -= stringWidth3(logSeg);
11934
- const showInput = inputSeg !== "" && budget3 >= stringWidth3(inputSeg);
11935
- if (showInput) budget3 -= stringWidth3(inputSeg);
11679
+ const showSys = budget3 >= stringWidth2(sysSeg);
11680
+ if (showSys) budget3 -= stringWidth2(sysSeg);
11681
+ const showTools = budget3 >= stringWidth2(toolsSeg);
11682
+ if (showTools) budget3 -= stringWidth2(toolsSeg);
11683
+ const showLog = budget3 >= stringWidth2(logSeg);
11684
+ if (showLog) budget3 -= stringWidth2(logSeg);
11685
+ const showInput = inputSeg !== "" && budget3 >= stringWidth2(inputSeg);
11686
+ if (showInput) budget3 -= stringWidth2(inputSeg);
11936
11687
  return /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(ChromeRule, null), /* @__PURE__ */ React9.createElement(Text7, null, /* @__PURE__ */ React9.createElement(Text7, { color: COLOR.info, dimColor: true }, labelStr), /* @__PURE__ */ React9.createElement(Bar, { ratio, color: severity, cells: BAR_CELLS }), /* @__PURE__ */ React9.createElement(Text7, null, " "), /* @__PURE__ */ React9.createElement(Text7, { color: severity, bold: true }, totalStr), /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, pctStr), showSys ? /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, sysSeg) : null, showTools ? /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, toolsSeg) : null, showLog ? /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, logSeg) : null, showInput ? /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, inputSeg) : null, warnStr ? /* @__PURE__ */ React9.createElement(Text7, { color: COLOR.err, bold: true }, warnStr) : null));
11937
11688
  }
11938
11689
 
@@ -14548,7 +14299,7 @@ function PromptInput({
14548
14299
  disabled: disabled === true
14549
14300
  }
14550
14301
  );
14551
- }), showHugeBufferHints && !disabled ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, `[${lines.length} lines \xB7 PageUp/PageDown jump to top/bottom \xB7 Ctrl+U clear \xB7 Ctrl+W del word]`)) : null, !disabled && !narrow && value.length > 0 && !value.includes("\n") ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[Ctrl+J] newline \xB7 [Enter] submit \xB7 ends with \\ for line continuation")) : null, !disabled && !narrow && value.length === 0 ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[PgUp/PgDn] scroll log \xB7 [End] jump to latest \xB7 /copy to dump log for selection")) : null, disabled ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[Esc] to stop")) : null);
14302
+ }), showHugeBufferHints && !disabled ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, `[${lines.length} lines \xB7 PageUp/PageDown jump to top/bottom \xB7 Ctrl+U clear \xB7 Ctrl+W del word]`)) : null, !disabled && !narrow && value.length > 0 && !value.includes("\n") ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[Ctrl+J] newline \xB7 [Enter] submit \xB7 ends with \\ for line continuation")) : null, !disabled && !narrow && value.length === 0 ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[PgUp/PgDn] scroll log \xB7 [End] jump to latest \xB7 drag to select \xB7 auto-scrolls at edge")) : null, disabled ? /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text20, { color: barColorAt(0) }, BAR), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, continuationIndent.slice(BAR.length)), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "[Esc] to stop")) : null);
14552
14303
  }
14553
14304
  function PromptLine({
14554
14305
  line,
@@ -14921,12 +14672,10 @@ import { useEffect as useEffect3 } from "react";
14921
14672
  function isInteractiveTty() {
14922
14673
  return Boolean(process.stdout?.isTTY);
14923
14674
  }
14924
- var mouseTrackingOn = false;
14925
14675
  function useAltScreen() {
14926
14676
  useEffect3(() => {
14927
14677
  if (!isInteractiveTty()) return;
14928
- process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?1000h\x1B[?1006h");
14929
- mouseTrackingOn = true;
14678
+ process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?1002h\x1B[?1006h");
14930
14679
  let restored = false;
14931
14680
  const restore2 = () => {
14932
14681
  if (restored) return;
@@ -14948,19 +14697,6 @@ function useAltScreen() {
14948
14697
  return restore2;
14949
14698
  }, []);
14950
14699
  }
14951
- function setMouseTracking(on) {
14952
- if (!isInteractiveTty()) return;
14953
- if (on === mouseTrackingOn) return;
14954
- if (on) {
14955
- process.stdout.write("\x1B[?1000h\x1B[?1006h");
14956
- } else {
14957
- process.stdout.write("\x1B[?1006l\x1B[?1002l\x1B[?1000l");
14958
- }
14959
- mouseTrackingOn = on;
14960
- }
14961
- function isMouseTrackingOn() {
14962
- return mouseTrackingOn;
14963
- }
14964
14700
 
14965
14701
  // src/cli/ui/bang.ts
14966
14702
  function detectBangCommand(text2) {
@@ -14974,6 +14710,28 @@ function formatBangUserMessage(cmd, output) {
14974
14710
  ${output}`;
14975
14711
  }
14976
14712
 
14713
+ // src/cli/ui/clipboard.ts
14714
+ import { writeFileSync as writeFileSync12 } from "fs";
14715
+ import { tmpdir } from "os";
14716
+ import { join as join18 } from "path";
14717
+ var OSC_52_LIMIT = 75e3;
14718
+ function writeClipboard(text2) {
14719
+ const filePath = join18(tmpdir(), `reasonix-clip-${Date.now()}.txt`);
14720
+ let osc52 = false;
14721
+ if (text2.length <= OSC_52_LIMIT) {
14722
+ const b64 = Buffer.from(text2, "utf8").toString("base64");
14723
+ process.stdout.write(`\x1B]52;c;${b64}\x1B\\`);
14724
+ osc52 = true;
14725
+ }
14726
+ let writtenPath = null;
14727
+ try {
14728
+ writeFileSync12(filePath, text2, "utf8");
14729
+ writtenPath = filePath;
14730
+ } catch {
14731
+ }
14732
+ return { osc52, filePath: writtenPath, size: text2.length };
14733
+ }
14734
+
14977
14735
  // src/cli/ui/edit-history.ts
14978
14736
  function isEntryFullyUndone(e) {
14979
14737
  return e.snapshots.length > 0 && e.snapshots.every((s) => e.undoneFiles.has(s.path));
@@ -15054,75 +14812,332 @@ function describeRepair(repair) {
15054
14812
  return parts.length ? `[repair] ${parts.join(", ")}` : "";
15055
14813
  }
15056
14814
 
15057
- // src/cli/ui/hash-memory.ts
15058
- import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
15059
- import { homedir as homedir9 } from "os";
15060
- import { dirname as dirname16, join as join18 } from "path";
15061
- var PROJECT_HEADER = `# Reasonix project memory
15062
-
15063
- Notes the user pinned via the \`#\` prompt prefix. The whole file is
15064
- loaded into the immutable system prefix every session \u2014 keep it terse.
15065
-
15066
- `;
15067
- var GLOBAL_HEADER = `# Reasonix global memory
15068
-
15069
- Cross-project notes the user pinned via the \`#g\` prompt prefix. Loaded
15070
- into every Reasonix session's prefix regardless of working directory.
15071
- Private to this machine \u2014 not committed anywhere.
15072
-
15073
- `;
15074
- function detectHashMemory(text2) {
15075
- if (text2.startsWith("\\#")) {
15076
- return { kind: "escape", text: text2.slice(1) };
15077
- }
15078
- if (!text2.startsWith("#")) return null;
15079
- if (text2.startsWith("##")) return null;
15080
- if (/^#g\s*$/.test(text2)) return null;
15081
- const globalMatch = /^#g\s+(.+)$/s.exec(text2);
15082
- if (globalMatch) {
15083
- const body2 = globalMatch[1].trim();
15084
- if (!body2) return null;
15085
- return { kind: "memory-global", note: body2 };
15086
- }
15087
- const body = text2.slice(1).trim();
15088
- if (!body) return null;
15089
- return { kind: "memory", note: body };
14815
+ // src/cli/ui/hash-memory.ts
14816
+ import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
14817
+ import { homedir as homedir9 } from "os";
14818
+ import { dirname as dirname16, join as join19 } from "path";
14819
+ var PROJECT_HEADER = `# Reasonix project memory
14820
+
14821
+ Notes the user pinned via the \`#\` prompt prefix. The whole file is
14822
+ loaded into the immutable system prefix every session \u2014 keep it terse.
14823
+
14824
+ `;
14825
+ var GLOBAL_HEADER = `# Reasonix global memory
14826
+
14827
+ Cross-project notes the user pinned via the \`#g\` prompt prefix. Loaded
14828
+ into every Reasonix session's prefix regardless of working directory.
14829
+ Private to this machine \u2014 not committed anywhere.
14830
+
14831
+ `;
14832
+ function detectHashMemory(text2) {
14833
+ if (text2.startsWith("\\#")) {
14834
+ return { kind: "escape", text: text2.slice(1) };
14835
+ }
14836
+ if (!text2.startsWith("#")) return null;
14837
+ if (text2.startsWith("##")) return null;
14838
+ if (/^#g\s*$/.test(text2)) return null;
14839
+ const globalMatch = /^#g\s+(.+)$/s.exec(text2);
14840
+ if (globalMatch) {
14841
+ const body2 = globalMatch[1].trim();
14842
+ if (!body2) return null;
14843
+ return { kind: "memory-global", note: body2 };
14844
+ }
14845
+ const body = text2.slice(1).trim();
14846
+ if (!body) return null;
14847
+ return { kind: "memory", note: body };
14848
+ }
14849
+ function appendProjectMemory(rootDir, note) {
14850
+ return appendBulletToFile(join19(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
14851
+ }
14852
+ var GLOBAL_MEMORY_DIR = ".reasonix";
14853
+ var GLOBAL_MEMORY_FILE = "REASONIX.md";
14854
+ function globalMemoryPath(homeDir = homedir9()) {
14855
+ return join19(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
14856
+ }
14857
+ function appendGlobalMemory(note, homeDir) {
14858
+ return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
14859
+ }
14860
+ function appendBulletToFile(path5, note, newFileHeader) {
14861
+ const trimmed = note.trim();
14862
+ if (!trimmed) throw new Error("note body cannot be empty");
14863
+ const bullet = `- ${trimmed}
14864
+ `;
14865
+ if (!existsSync18(path5)) {
14866
+ mkdirSync13(dirname16(path5), { recursive: true });
14867
+ writeFileSync13(path5, `${newFileHeader}${bullet}`, "utf8");
14868
+ return { path: path5, created: true };
14869
+ }
14870
+ let prefix = "";
14871
+ try {
14872
+ const existing = readFileSync20(path5, "utf8");
14873
+ if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
14874
+ } catch {
14875
+ }
14876
+ appendFileSync3(path5, `${prefix}${bullet}`, "utf8");
14877
+ return { path: path5, created: false };
14878
+ }
14879
+
14880
+ // src/cli/ui/log-frame.tsx
14881
+ import { Box as Box27, Text as Text26 } from "ink";
14882
+ import React30 from "react";
14883
+
14884
+ // src/frame/width.ts
14885
+ import stringWidthLib from "string-width";
14886
+ var segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
14887
+ function graphemes(s) {
14888
+ return Array.from(segmenter.segment(s), (seg) => seg.segment);
14889
+ }
14890
+ function graphemeWidth(g) {
14891
+ if (g.length === 0) return 0;
14892
+ const w = stringWidthLib(g);
14893
+ if (w <= 0) return 0;
14894
+ if (w >= 2) return 2;
14895
+ return 1;
14896
+ }
14897
+
14898
+ // src/frame/frame.ts
14899
+ var SPACE = { char: " ", width: 1 };
14900
+ var TAIL = { char: "", width: 1, tail: true };
14901
+ function empty(width = 0) {
14902
+ return { width, rows: [] };
14903
+ }
14904
+ function text(s, opts) {
14905
+ const { width, fg, bg, bold, dim, italic, underline, inverse, href } = opts;
14906
+ if (width <= 0) return empty(0);
14907
+ const styleOf = (g, w) => {
14908
+ const base = { char: g, width: w };
14909
+ if (fg !== void 0) base.fg = fg;
14910
+ if (bg !== void 0) base.bg = bg;
14911
+ if (bold) base.bold = true;
14912
+ if (dim) base.dim = true;
14913
+ if (italic) base.italic = true;
14914
+ if (underline) base.underline = true;
14915
+ if (inverse) base.inverse = true;
14916
+ if (href !== void 0) base.href = href;
14917
+ return base;
14918
+ };
14919
+ const rows = [];
14920
+ const lines = s.split("\n");
14921
+ for (const line of lines) {
14922
+ if (line.length === 0) {
14923
+ rows.push(padRowRight([], width));
14924
+ continue;
14925
+ }
14926
+ let buf = [];
14927
+ let bufWidth = 0;
14928
+ for (const g of graphemes(line)) {
14929
+ const w = graphemeWidth(g);
14930
+ if (w === 0) continue;
14931
+ if (bufWidth + w > width) {
14932
+ rows.push(padRowRight(buf, width - bufWidth));
14933
+ buf = [];
14934
+ bufWidth = 0;
14935
+ }
14936
+ buf.push(styleOf(g, w));
14937
+ if (w === 2) buf.push(TAIL);
14938
+ bufWidth += w;
14939
+ }
14940
+ rows.push(padRowRight(buf, width - bufWidth));
14941
+ }
14942
+ return { width, rows };
14943
+ }
14944
+ function padRowRight(cells, extraSpaces) {
14945
+ if (extraSpaces <= 0) return cells.slice();
14946
+ const out = cells.slice();
14947
+ for (let i = 0; i < extraSpaces; i++) out.push(SPACE);
14948
+ return out;
14949
+ }
14950
+ function spacerRow(width) {
14951
+ if (width <= 0) return [];
14952
+ return Array.from({ length: width }, () => SPACE);
14953
+ }
14954
+ function vstack(...frames) {
14955
+ if (frames.length === 0) return empty(0);
14956
+ const w = Math.max(...frames.map((f) => f.width));
14957
+ const rows = [];
14958
+ for (const f of frames) {
14959
+ if (f.width === w) {
14960
+ rows.push(...f.rows);
14961
+ } else {
14962
+ const extra = w - f.width;
14963
+ for (const r of f.rows) rows.push(padRowRight(r, extra));
14964
+ }
14965
+ }
14966
+ return { width: w, rows };
14967
+ }
14968
+ function hstack(...frames) {
14969
+ if (frames.length === 0) return empty(0);
14970
+ const h = Math.max(...frames.map((f) => f.rows.length));
14971
+ const w = frames.reduce((a, f) => a + f.width, 0);
14972
+ const rows = [];
14973
+ for (let i = 0; i < h; i++) {
14974
+ const cells = [];
14975
+ for (const f of frames) {
14976
+ const r = f.rows[i] ?? spacerRow(f.width);
14977
+ cells.push(...r);
14978
+ }
14979
+ rows.push(cells);
14980
+ }
14981
+ return { width: w, rows };
14982
+ }
14983
+ function pad(f, top, right, bottom2, left) {
14984
+ const newWidth = f.width + Math.max(0, left) + Math.max(0, right);
14985
+ const tPad = Math.max(0, top);
14986
+ const bPad = Math.max(0, bottom2);
14987
+ const blank2 = spacerRow(newWidth);
14988
+ const rows = [];
14989
+ for (let i = 0; i < tPad; i++) rows.push(blank2);
14990
+ if (left <= 0 && right <= 0) {
14991
+ rows.push(...f.rows);
14992
+ } else {
14993
+ const lPad = spacerRow(Math.max(0, left));
14994
+ const rPad = spacerRow(Math.max(0, right));
14995
+ for (const r of f.rows) rows.push([...lPad, ...r, ...rPad]);
14996
+ }
14997
+ for (let i = 0; i < bPad; i++) rows.push(blank2);
14998
+ return { width: newWidth, rows };
14999
+ }
15000
+ function borderLeft(f, color2, char = "\u2502") {
15001
+ const bar = { char, width: 1, fg: color2 };
15002
+ const newWidth = f.width + 1;
15003
+ const rows = [];
15004
+ for (const r of f.rows) rows.push([bar, ...r]);
15005
+ return { width: newWidth, rows };
15006
+ }
15007
+
15008
+ // src/frame/ansi.ts
15009
+ var ESC = "\x1B";
15010
+ var RESET = `${ESC}[0m`;
15011
+ function sameStyle(a, b) {
15012
+ return a.fg === b.fg && a.bg === b.bg && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.italic === !!b.italic && !!a.underline === !!b.underline && !!a.inverse === !!b.inverse && a.href === b.href;
15090
15013
  }
15091
- function appendProjectMemory(rootDir, note) {
15092
- return appendBulletToFile(join18(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
15014
+ function fgEscape(color2) {
15015
+ if (!color2) return null;
15016
+ const rgb = parseColor(color2);
15017
+ if (rgb) return `38;2;${rgb[0]};${rgb[1]};${rgb[2]}`;
15018
+ const named = NAMED_FG[color2.toLowerCase()];
15019
+ if (named !== void 0) return String(named);
15020
+ return null;
15093
15021
  }
15094
- var GLOBAL_MEMORY_DIR = ".reasonix";
15095
- var GLOBAL_MEMORY_FILE = "REASONIX.md";
15096
- function globalMemoryPath(homeDir = homedir9()) {
15097
- return join18(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
15022
+ function bgEscape(color2) {
15023
+ if (!color2) return null;
15024
+ const rgb = parseColor(color2);
15025
+ if (rgb) return `48;2;${rgb[0]};${rgb[1]};${rgb[2]}`;
15026
+ const named = NAMED_BG[color2.toLowerCase()];
15027
+ if (named !== void 0) return String(named);
15028
+ return null;
15098
15029
  }
15099
- function appendGlobalMemory(note, homeDir) {
15100
- return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
15030
+ function parseColor(s) {
15031
+ if (!s.startsWith("#")) return null;
15032
+ const hex = s.slice(1);
15033
+ if (hex.length !== 6) return null;
15034
+ const r = Number.parseInt(hex.slice(0, 2), 16);
15035
+ const g = Number.parseInt(hex.slice(2, 4), 16);
15036
+ const b = Number.parseInt(hex.slice(4, 6), 16);
15037
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null;
15038
+ return [r, g, b];
15101
15039
  }
15102
- function appendBulletToFile(path5, note, newFileHeader) {
15103
- const trimmed = note.trim();
15104
- if (!trimmed) throw new Error("note body cannot be empty");
15105
- const bullet = `- ${trimmed}
15106
- `;
15107
- if (!existsSync18(path5)) {
15108
- mkdirSync13(dirname16(path5), { recursive: true });
15109
- writeFileSync12(path5, `${newFileHeader}${bullet}`, "utf8");
15110
- return { path: path5, created: true };
15040
+ var NAMED_FG = {
15041
+ black: 30,
15042
+ red: 31,
15043
+ green: 32,
15044
+ yellow: 33,
15045
+ blue: 34,
15046
+ magenta: 35,
15047
+ cyan: 36,
15048
+ white: 37,
15049
+ gray: 90,
15050
+ grey: 90,
15051
+ brightred: 91,
15052
+ brightgreen: 92,
15053
+ brightyellow: 93,
15054
+ brightblue: 94,
15055
+ brightmagenta: 95,
15056
+ brightcyan: 96,
15057
+ brightwhite: 97
15058
+ };
15059
+ var NAMED_BG = {
15060
+ black: 40,
15061
+ red: 41,
15062
+ green: 42,
15063
+ yellow: 43,
15064
+ blue: 44,
15065
+ magenta: 45,
15066
+ cyan: 46,
15067
+ white: 47,
15068
+ gray: 100,
15069
+ grey: 100
15070
+ };
15071
+ function styleToAnsi(s) {
15072
+ const codes = [];
15073
+ if (s.bold) codes.push("1");
15074
+ if (s.dim) codes.push("2");
15075
+ if (s.italic) codes.push("3");
15076
+ if (s.underline) codes.push("4");
15077
+ if (s.inverse) codes.push("7");
15078
+ const fg = fgEscape(s.fg);
15079
+ if (fg) codes.push(fg);
15080
+ const bg = bgEscape(s.bg);
15081
+ if (bg) codes.push(bg);
15082
+ if (codes.length === 0) return "";
15083
+ return `${ESC}[${codes.join(";")}m`;
15084
+ }
15085
+ var EMPTY_STYLE = {};
15086
+ function frameToAnsi(f, opts = {}) {
15087
+ const out = [];
15088
+ for (let i = 0; i < f.rows.length; i++) {
15089
+ out.push(rowToAnsi(f.rows[i], opts));
15111
15090
  }
15112
- let prefix = "";
15113
- try {
15114
- const existing = readFileSync20(path5, "utf8");
15115
- if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
15116
- } catch {
15091
+ return out.join("\n");
15092
+ }
15093
+ function rowToAnsi(row2, opts) {
15094
+ if (opts.plain) {
15095
+ let s = "";
15096
+ for (const c of row2) {
15097
+ if (c.tail) continue;
15098
+ s += c.char;
15099
+ }
15100
+ return s;
15117
15101
  }
15118
- appendFileSync3(path5, `${prefix}${bullet}`, "utf8");
15119
- return { path: path5, created: false };
15102
+ let result = "";
15103
+ let curStyle = EMPTY_STYLE;
15104
+ let inHref = false;
15105
+ let curHref;
15106
+ for (const c of row2) {
15107
+ if (c.tail) continue;
15108
+ const cellStyle = {
15109
+ fg: c.fg,
15110
+ bg: c.bg,
15111
+ bold: c.bold,
15112
+ dim: c.dim,
15113
+ italic: c.italic,
15114
+ underline: c.underline,
15115
+ inverse: c.inverse,
15116
+ href: c.href
15117
+ };
15118
+ if (cellStyle.href !== curHref) {
15119
+ if (inHref) {
15120
+ result += `${ESC}]8;;${ESC}\\`;
15121
+ inHref = false;
15122
+ }
15123
+ if (cellStyle.href !== void 0) {
15124
+ result += `${ESC}]8;;${cellStyle.href}${ESC}\\`;
15125
+ inHref = true;
15126
+ }
15127
+ curHref = cellStyle.href;
15128
+ }
15129
+ if (!sameStyle(curStyle, cellStyle)) {
15130
+ result += RESET;
15131
+ result += styleToAnsi(cellStyle);
15132
+ curStyle = cellStyle;
15133
+ }
15134
+ result += c.char;
15135
+ }
15136
+ if (inHref) result += `${ESC}]8;;${ESC}\\`;
15137
+ result += RESET;
15138
+ return result;
15120
15139
  }
15121
15140
 
15122
- // src/cli/ui/log-frame.tsx
15123
- import { Box as Box27, Text as Text26 } from "ink";
15124
- import React30 from "react";
15125
-
15126
15141
  // src/cli/ui/markdown-frame.ts
15127
15142
  var INLINE_RE2 = /(\[([^\]\n]+)\]\(([^)\n]+)\)|\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|~~([^~\n]+?)~~|(?<![*\w])\*([^*\n]+?)\*(?!\w)|\\([*_~`[\](){}#+\-.!\\]))/g;
15128
15143
  function parseInline(input, baseOpts) {
@@ -15942,7 +15957,14 @@ function estimatedHeight(e) {
15942
15957
  }
15943
15958
  function viewportLog(atoms, scrollOffset, available) {
15944
15959
  if (atoms.length === 0 || available <= 0) {
15945
- return { atoms: [], topSkip: 0, bottomSkip: 0, totalRows: 0, maxScrollRows: 0 };
15960
+ return {
15961
+ atoms: [],
15962
+ topSkip: 0,
15963
+ bottomSkip: 0,
15964
+ totalRows: 0,
15965
+ maxScrollRows: 0,
15966
+ firstRowAbs: 0
15967
+ };
15946
15968
  }
15947
15969
  const heights = atoms.map(atomRows);
15948
15970
  const totalRows = heights.reduce((a, b) => a + b, 0);
@@ -15974,7 +15996,8 @@ function viewportLog(atoms, scrollOffset, available) {
15974
15996
  topSkip: 0,
15975
15997
  bottomSkip: 0,
15976
15998
  totalRows,
15977
- maxScrollRows
15999
+ maxScrollRows,
16000
+ firstRowAbs: Math.max(0, totalRows - heights[atoms.length - 1])
15978
16001
  };
15979
16002
  }
15980
16003
  const sliced = atoms.slice(firstIdx, lastIdx + 1);
@@ -15988,19 +16011,95 @@ function viewportLog(atoms, scrollOffset, available) {
15988
16011
  if (lastAtom.kind === "frame") {
15989
16012
  bottomSkip = Math.max(0, lastEnd - viewportBottom);
15990
16013
  }
15991
- return { atoms: sliced, topSkip, bottomSkip, totalRows, maxScrollRows };
16014
+ return {
16015
+ atoms: sliced,
16016
+ topSkip,
16017
+ bottomSkip,
16018
+ totalRows,
16019
+ maxScrollRows,
16020
+ firstRowAbs: firstStart + topSkip
16021
+ };
16022
+ }
16023
+ function highlightRow(row2, fromCol, toCol) {
16024
+ if (fromCol >= toCol) return row2;
16025
+ const out = [];
16026
+ let col = 0;
16027
+ for (const cell of row2) {
16028
+ const cellEnd = col + (cell.tail ? 0 : cell.width);
16029
+ const inRange = col < toCol && cellEnd > fromCol;
16030
+ out.push(inRange ? { ...cell, inverse: true } : cell);
16031
+ col = cellEnd;
16032
+ }
16033
+ return out;
15992
16034
  }
15993
- function renderViewport(v) {
16035
+ function rowSelectionRange(sel, absRow, rowWidth) {
16036
+ if (sel.startRow === sel.endRow) {
16037
+ if (absRow !== sel.startRow) return null;
16038
+ return { from: Math.min(sel.startCol, sel.endCol), to: Math.max(sel.startCol, sel.endCol) };
16039
+ }
16040
+ const topRow = sel.startRow < sel.endRow ? sel.startRow : sel.endRow;
16041
+ const topCol = sel.startRow < sel.endRow ? sel.startCol : sel.endCol;
16042
+ const botRow = sel.startRow < sel.endRow ? sel.endRow : sel.startRow;
16043
+ const botCol = sel.startRow < sel.endRow ? sel.endCol : sel.startCol;
16044
+ if (absRow < topRow || absRow > botRow) return null;
16045
+ if (absRow === topRow) return { from: topCol, to: rowWidth };
16046
+ if (absRow === botRow) return { from: 0, to: botCol };
16047
+ return { from: 0, to: rowWidth };
16048
+ }
16049
+ function renderViewport(v, selection) {
16050
+ let absRow = v.firstRowAbs;
15994
16051
  return /* @__PURE__ */ React30.createElement(React30.Fragment, null, v.atoms.map((atom, i) => {
15995
16052
  if (atom.kind === "ink") {
16053
+ absRow += atom.rows;
15996
16054
  return /* @__PURE__ */ React30.createElement(React30.Fragment, { key: atom.id }, atom.element);
15997
16055
  }
15998
16056
  const start = i === 0 ? v.topSkip : 0;
15999
16057
  const end = i === v.atoms.length - 1 ? atom.frame.rows.length - v.bottomSkip : atom.frame.rows.length;
16000
16058
  const rows = atom.frame.rows.slice(start, end);
16001
- return /* @__PURE__ */ React30.createElement(React30.Fragment, { key: atom.id }, rows.map((row2, ri) => /* @__PURE__ */ React30.createElement(Box27, { key: `${atom.id}/${start + ri}`, height: 1, flexShrink: 0 }, /* @__PURE__ */ React30.createElement(Text26, null, frameToAnsi({ width: atom.frame.width, rows: [row2] })))));
16059
+ const node = /* @__PURE__ */ React30.createElement(React30.Fragment, { key: atom.id }, rows.map((row2, ri) => {
16060
+ const rowAbs = absRow + ri;
16061
+ const range = selection ? rowSelectionRange(selection, rowAbs, atom.frame.width) : null;
16062
+ const painted = range ? highlightRow(row2, range.from, range.to) : row2;
16063
+ return /* @__PURE__ */ React30.createElement(Box27, { key: `${atom.id}/${start + ri}`, height: 1, flexShrink: 0 }, /* @__PURE__ */ React30.createElement(Text26, null, frameToAnsi({ width: atom.frame.width, rows: [painted] })));
16064
+ }));
16065
+ absRow += atom.frame.rows.length - (i === 0 ? v.topSkip : 0) - (i === v.atoms.length - 1 ? v.bottomSkip : 0);
16066
+ return node;
16002
16067
  }));
16003
16068
  }
16069
+ function extractSelection(atoms, selection) {
16070
+ const lines = [];
16071
+ let cursor = 0;
16072
+ for (const atom of atoms) {
16073
+ const atomStart = cursor;
16074
+ const atomEnd = cursor + atomRows(atom);
16075
+ cursor = atomEnd;
16076
+ const top = Math.min(selection.startRow, selection.endRow);
16077
+ const bot = Math.max(selection.startRow, selection.endRow);
16078
+ if (atomEnd <= top || atomStart > bot) continue;
16079
+ if (atom.kind === "ink") {
16080
+ lines.push("(non-text block)");
16081
+ continue;
16082
+ }
16083
+ const w = atom.frame.width;
16084
+ for (let r = 0; r < atom.frame.rows.length; r++) {
16085
+ const absRow = atomStart + r;
16086
+ const range = rowSelectionRange(selection, absRow, w);
16087
+ if (!range) continue;
16088
+ lines.push(rowSliceText(atom.frame.rows[r], range.from, range.to));
16089
+ }
16090
+ }
16091
+ return lines.join("\n").replace(/[ \t]+$/gm, "");
16092
+ }
16093
+ function rowSliceText(row2, fromCol, toCol) {
16094
+ let col = 0;
16095
+ let out = "";
16096
+ for (const cell of row2) {
16097
+ const cellEnd = col + (cell.tail ? 0 : cell.width);
16098
+ if (col >= fromCol && cellEnd <= toCol && !cell.tail) out += cell.char;
16099
+ col = cellEnd;
16100
+ }
16101
+ return out;
16102
+ }
16004
16103
  function eventsToAtoms(events, projectRoot, width) {
16005
16104
  const out = [];
16006
16105
  for (const e of events) out.push(eventToAtom(e, projectRoot, width));
@@ -16446,10 +16545,6 @@ var SLASH_COMMANDS = [
16446
16545
  cmd: "stats",
16447
16546
  summary: "cross-session cost dashboard (today / week / month / all-time \xB7 cache hit \xB7 vs Claude)"
16448
16547
  },
16449
- {
16450
- cmd: "copy",
16451
- summary: "freeze + dump the rendered log to main screen so terminal scrollback + drag-select can copy across viewports \xB7 any key returns"
16452
- },
16453
16548
  { cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
16454
16549
  {
16455
16550
  cmd: "context",
@@ -16900,20 +16995,12 @@ var cwd = (args, _loop, ctx) => {
16900
16995
  }
16901
16996
  return { info: lines.join("\n") };
16902
16997
  };
16903
- var copy = (_args, _loop, ctx) => {
16904
- if (!ctx.enterCopyMode) {
16905
- return { info: "/copy is not available in this context (TUI-internal)." };
16906
- }
16907
- ctx.enterCopyMode();
16908
- return {};
16909
- };
16910
16998
  var handlers = {
16911
16999
  hook: hooks,
16912
17000
  hooks,
16913
17001
  cwd,
16914
17002
  update,
16915
- stats,
16916
- copy
17003
+ stats
16917
17004
  };
16918
17005
 
16919
17006
  // src/cli/ui/slash/handlers/basic.ts
@@ -17162,20 +17249,20 @@ var dashboard2 = (args, _loop, ctx) => {
17162
17249
  var handlers3 = { dashboard: dashboard2 };
17163
17250
 
17164
17251
  // src/code/checkpoints.ts
17165
- import { existsSync as existsSync21, mkdirSync as mkdirSync14, readFileSync as readFileSync22, rmSync as rmSync2, writeFileSync as writeFileSync13 } from "fs";
17252
+ import { existsSync as existsSync21, mkdirSync as mkdirSync14, readFileSync as readFileSync22, rmSync as rmSync2, writeFileSync as writeFileSync14 } from "fs";
17166
17253
  import { homedir as homedir10 } from "os";
17167
- import { dirname as dirname17, join as join20, relative as relative3, resolve as resolve10, sep as sep3 } from "path";
17254
+ import { dirname as dirname17, join as join21, relative as relative3, resolve as resolve10, sep as sep3 } from "path";
17168
17255
  function sanitizeRoot(rootDir) {
17169
17256
  return resolve10(rootDir).replace(/[\\/:]+/g, "_").replace(/^_+/, "");
17170
17257
  }
17171
17258
  function storeRoot(rootDir) {
17172
- return join20(homedir10(), ".reasonix", "sessions", sanitizeRoot(rootDir), "checkpoints");
17259
+ return join21(homedir10(), ".reasonix", "sessions", sanitizeRoot(rootDir), "checkpoints");
17173
17260
  }
17174
17261
  function indexPath(rootDir) {
17175
- return join20(storeRoot(rootDir), "index.json");
17262
+ return join21(storeRoot(rootDir), "index.json");
17176
17263
  }
17177
17264
  function snapshotPath(rootDir, id) {
17178
- return join20(storeRoot(rootDir), `${id}.json`);
17265
+ return join21(storeRoot(rootDir), `${id}.json`);
17179
17266
  }
17180
17267
  function listCheckpoints(rootDir) {
17181
17268
  const path5 = indexPath(rootDir);
@@ -17194,7 +17281,7 @@ function listCheckpoints(rootDir) {
17194
17281
  function writeIndex(rootDir, items) {
17195
17282
  const path5 = indexPath(rootDir);
17196
17283
  mkdirSync14(dirname17(path5), { recursive: true });
17197
- writeFileSync13(path5, JSON.stringify(items, null, 2), "utf8");
17284
+ writeFileSync14(path5, JSON.stringify(items, null, 2), "utf8");
17198
17285
  }
17199
17286
  function loadCheckpoint(rootDir, id) {
17200
17287
  const path5 = snapshotPath(rootDir, id);
@@ -17245,7 +17332,7 @@ function createCheckpoint(opts) {
17245
17332
  };
17246
17333
  const cpPath = snapshotPath(absRoot, id);
17247
17334
  mkdirSync14(dirname17(cpPath), { recursive: true });
17248
- writeFileSync13(cpPath, JSON.stringify(checkpoint2), "utf8");
17335
+ writeFileSync14(cpPath, JSON.stringify(checkpoint2), "utf8");
17249
17336
  const meta = {
17250
17337
  id,
17251
17338
  name: opts.name,
@@ -17288,7 +17375,7 @@ function restoreCheckpoint(rootDir, id) {
17288
17375
  }
17289
17376
  } else {
17290
17377
  mkdirSync14(dirname17(abs), { recursive: true });
17291
- writeFileSync13(abs, f.content, "utf8");
17378
+ writeFileSync14(abs, f.content, "utf8");
17292
17379
  result.restored.push(f.path);
17293
17380
  }
17294
17381
  } catch (err) {
@@ -19518,8 +19605,13 @@ function App({
19518
19605
  useAltScreen();
19519
19606
  const [historical, setHistorical] = useState11([]);
19520
19607
  const [streaming, setStreaming] = useState11(null);
19521
- const [copyMode, setCopyMode] = useState11(false);
19522
19608
  const [ctxFooterVisible, setCtxFooterVisible] = useState11(true);
19609
+ const [logSelection, setLogSelection] = useState11(null);
19610
+ const dragAnchorRef = useRef6(null);
19611
+ const autoScrollTimerRef = useRef6(null);
19612
+ const autoScrollDirRef = useRef6(0);
19613
+ const lastDragColRef = useRef6(0);
19614
+ const logGeomRef = useRef6({ contentTopRow: 1, contentBottomRow: 1, firstRowAbs: 0, visibleRows: 0, cols: 80 });
19523
19615
  const [logScrollOffset, setLogScrollOffset] = useState11(0);
19524
19616
  const scrollAnimRef = useRef6(null);
19525
19617
  const scrollTargetRef = useRef6(0);
@@ -19881,49 +19973,6 @@ function App({
19881
19973
  useEffect7(() => {
19882
19974
  currentRootDirRef.current = currentRootDir;
19883
19975
  }, [currentRootDir]);
19884
- useEffect7(() => {
19885
- if (!copyMode) return;
19886
- const cols = (process.stdout?.columns ?? 80) - 2;
19887
- const atoms = eventsToAtoms(historicalRef.current, currentRootDirRef.current, cols);
19888
- const lines = [];
19889
- for (const atom of atoms) {
19890
- if (atom.kind === "frame") {
19891
- const ansi = frameToAnsi(atom.frame);
19892
- if (ansi) lines.push(ansi);
19893
- } else {
19894
- lines.push(`(${atom.id ?? "ink-block"} omitted from copy dump)`);
19895
- }
19896
- }
19897
- const wasMouseOn = isMouseTrackingOn();
19898
- setMouseTracking(false);
19899
- process.stdout.write("\x1B[?1049l");
19900
- process.stdout.write("\n=== reasonix \xB7 copy mode \u2014 terminal scrollback active ===\n\n");
19901
- process.stdout.write(lines.join("\n"));
19902
- process.stdout.write("\n\n=== end of dump \xB7 press any key to return to reasonix ===\n");
19903
- const reader = getStdinReader();
19904
- let exited = false;
19905
- const restore2 = () => {
19906
- if (exited) return;
19907
- exited = true;
19908
- process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
19909
- if (wasMouseOn) setMouseTracking(true);
19910
- setCopyMode(false);
19911
- };
19912
- const unsub = reader.subscribe(() => {
19913
- unsub();
19914
- restore2();
19915
- });
19916
- return () => {
19917
- unsub();
19918
- if (!exited) {
19919
- process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
19920
- if (wasMouseOn) setMouseTracking(true);
19921
- }
19922
- };
19923
- }, [copyMode]);
19924
- const enterCopyMode = useCallback4(() => {
19925
- setCopyMode(true);
19926
- }, []);
19927
19976
  const toggleCtxFooter = useCallback4(
19928
19977
  (force) => {
19929
19978
  let next = false;
@@ -20183,6 +20232,76 @@ function App({
20183
20232
  transcriptRef.current?.end();
20184
20233
  process.exit(0);
20185
20234
  }, []);
20235
+ const stopAutoScroll = useCallback4(() => {
20236
+ if (autoScrollTimerRef.current) {
20237
+ clearInterval(autoScrollTimerRef.current);
20238
+ autoScrollTimerRef.current = null;
20239
+ }
20240
+ autoScrollDirRef.current = 0;
20241
+ }, []);
20242
+ const mouseToAbsRow = useCallback4((mouseRow) => {
20243
+ const { contentTopRow, contentBottomRow, firstRowAbs, visibleRows } = logGeomRef.current;
20244
+ if (visibleRows <= 0) return null;
20245
+ const clamped = Math.max(contentTopRow, Math.min(contentBottomRow, mouseRow));
20246
+ return firstRowAbs + (clamped - contentTopRow);
20247
+ }, []);
20248
+ const startAutoScroll = useCallback4(
20249
+ (dir) => {
20250
+ if (autoScrollDirRef.current === dir) return;
20251
+ stopAutoScroll();
20252
+ autoScrollDirRef.current = dir;
20253
+ autoScrollTimerRef.current = setInterval(() => {
20254
+ const max = scrollMaxRowsRef.current;
20255
+ const cur = scrollTargetRef.current;
20256
+ const next = dir > 0 ? Math.max(0, cur - 1) : Math.min(max, cur + 1);
20257
+ if (next === cur) return;
20258
+ scrollTargetRef.current = next;
20259
+ scrollDisplayedRef.current = next;
20260
+ setLogScrollOffset(next);
20261
+ const anchor = dragAnchorRef.current;
20262
+ if (anchor) {
20263
+ const { firstRowAbs, visibleRows } = logGeomRef.current;
20264
+ const focusRow = dir > 0 ? firstRowAbs + visibleRows - 1 : firstRowAbs;
20265
+ setLogSelection({
20266
+ startRow: anchor.row,
20267
+ startCol: anchor.col,
20268
+ endRow: focusRow,
20269
+ endCol: lastDragColRef.current
20270
+ });
20271
+ }
20272
+ }, 60);
20273
+ },
20274
+ [stopAutoScroll]
20275
+ );
20276
+ const finishSelection = useCallback4(() => {
20277
+ stopAutoScroll();
20278
+ const sel = logSelection;
20279
+ dragAnchorRef.current = null;
20280
+ if (!sel) return;
20281
+ if (sel.startRow === sel.endRow && sel.startCol === sel.endCol) {
20282
+ setLogSelection(null);
20283
+ return;
20284
+ }
20285
+ const cols = stdout4?.columns ?? 80;
20286
+ const atoms = eventsToAtoms(historical, currentRootDir, cols);
20287
+ const text2 = extractSelection(atoms, sel);
20288
+ if (!text2.trim()) {
20289
+ setLogSelection(null);
20290
+ return;
20291
+ }
20292
+ const result = writeClipboard(text2);
20293
+ const noun = result.osc52 ? "copied" : "saved";
20294
+ const fileNote = result.filePath ? ` \xB7 ${result.filePath}` : "";
20295
+ setHistorical((prev) => [
20296
+ ...prev,
20297
+ {
20298
+ id: `clip-${Date.now()}`,
20299
+ role: "info",
20300
+ text: `\u25B8 ${noun} ${result.size} char${result.size === 1 ? "" : "s"}${fileNote}`
20301
+ }
20302
+ ]);
20303
+ setLogSelection(null);
20304
+ }, [historical, logSelection, stdout4?.columns, currentRootDir, stopAutoScroll]);
20186
20305
  useEffect7(() => {
20187
20306
  process.on("SIGINT", quitProcess);
20188
20307
  return () => {
@@ -20207,6 +20326,48 @@ function App({
20207
20326
  animateScrollTo((prev) => Math.max(0, prev - 3));
20208
20327
  return;
20209
20328
  }
20329
+ if (ev.mouseClick && ev.mouseRow !== void 0 && ev.mouseCol !== void 0) {
20330
+ const { contentTopRow, contentBottomRow } = logGeomRef.current;
20331
+ if (ev.mouseRow < contentTopRow || ev.mouseRow > contentBottomRow) {
20332
+ if (logSelection) setLogSelection(null);
20333
+ dragAnchorRef.current = null;
20334
+ return;
20335
+ }
20336
+ const absRow = mouseToAbsRow(ev.mouseRow);
20337
+ if (absRow === null) return;
20338
+ const col = Math.max(0, ev.mouseCol - 1);
20339
+ dragAnchorRef.current = { row: absRow, col };
20340
+ lastDragColRef.current = col;
20341
+ setLogSelection({ startRow: absRow, startCol: col, endRow: absRow, endCol: col });
20342
+ return;
20343
+ }
20344
+ if (ev.mouseDrag && ev.mouseRow !== void 0 && ev.mouseCol !== void 0) {
20345
+ const anchor = dragAnchorRef.current;
20346
+ if (!anchor) return;
20347
+ const { contentTopRow, contentBottomRow } = logGeomRef.current;
20348
+ const col = Math.max(0, ev.mouseCol - 1);
20349
+ lastDragColRef.current = col;
20350
+ if (ev.mouseRow <= contentTopRow) {
20351
+ startAutoScroll(-1);
20352
+ } else if (ev.mouseRow >= contentBottomRow) {
20353
+ startAutoScroll(1);
20354
+ } else {
20355
+ stopAutoScroll();
20356
+ }
20357
+ const absRow = mouseToAbsRow(ev.mouseRow);
20358
+ if (absRow === null) return;
20359
+ setLogSelection({
20360
+ startRow: anchor.row,
20361
+ startCol: anchor.col,
20362
+ endRow: absRow,
20363
+ endCol: col
20364
+ });
20365
+ return;
20366
+ }
20367
+ if (ev.mouseRelease) {
20368
+ finishSelection();
20369
+ return;
20370
+ }
20210
20371
  if (ev.pageUp) {
20211
20372
  if (maxOffset === 0) return;
20212
20373
  animateScrollTo((prev) => Math.min(maxOffset, prev + (viewportRows - 2)));
@@ -21029,7 +21190,6 @@ function App({
21029
21190
  return fresh.length;
21030
21191
  },
21031
21192
  setCwd: (newRoot) => applyCwdChange(newRoot),
21032
- enterCopyMode,
21033
21193
  toggleCtxFooter,
21034
21194
  latestVersion,
21035
21195
  refreshLatestVersion,
@@ -21677,7 +21837,6 @@ function App({
21677
21837
  broadcastDashboardEvent,
21678
21838
  applyCwdChange,
21679
21839
  touchedPaths,
21680
- enterCopyMode,
21681
21840
  toggleCtxFooter,
21682
21841
  model2,
21683
21842
  prefixHash
@@ -22191,7 +22350,6 @@ Continue executing from the next pending step. Call mark_step_complete after eac
22191
22350
  async (choice) => handleReviseConfirmRef.current(choice),
22192
22351
  []
22193
22352
  );
22194
- if (copyMode) return null;
22195
22353
  return /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(
22196
22354
  TickerProvider,
22197
22355
  {
@@ -22253,7 +22411,21 @@ Continue executing from the next pending step. Call mark_step_complete after eac
22253
22411
  const v = viewportLog(atoms, logScrollOffset, available);
22254
22412
  scrollMaxRowsRef.current = v.maxScrollRows;
22255
22413
  lastTotalRowsRef.current = v.totalRows;
22256
- return renderViewport(v);
22414
+ const logTopRow = 3;
22415
+ const logBottomRow = logTopRow + logHeight - 1;
22416
+ const isScrolled = logScrollOffset > 0;
22417
+ const contentBoxBottom = logBottomRow - (isScrolled ? 1 : 0);
22418
+ const renderedRows = Math.min(available, v.totalRows);
22419
+ const contentTopRow = isScrolled ? logTopRow : Math.max(logTopRow, contentBoxBottom - renderedRows + 1);
22420
+ const contentBottomRow = isScrolled ? logTopRow + renderedRows - 1 : contentBoxBottom;
22421
+ logGeomRef.current = {
22422
+ contentTopRow,
22423
+ contentBottomRow,
22424
+ firstRowAbs: v.firstRowAbs,
22425
+ visibleRows: renderedRows,
22426
+ cols
22427
+ };
22428
+ return renderViewport(v, logSelection);
22257
22429
  })(),
22258
22430
  !historical.some((e) => e.role === "user" || e.role === "assistant") && !busy && !streaming ? /* @__PURE__ */ React32.createElement(WelcomeBanner, { inCodeMode: !!codeMode, dashboardUrl }) : null,
22259
22431
  !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && streaming ? /* @__PURE__ */ React32.createElement(Box29, { marginY: 1 }, /* @__PURE__ */ React32.createElement(EventRow, { event: streaming, projectRoot: currentRootDir })) : null,
@@ -22669,7 +22841,7 @@ async function chatCommand(opts) {
22669
22841
  // src/cli/commands/code.tsx
22670
22842
  import { basename as basename3, resolve as resolve12 } from "path";
22671
22843
  async function codeCommand(opts = {}) {
22672
- const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-XPEUBA46.js");
22844
+ const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MAHJTS7Q.js");
22673
22845
  const rootDir = resolve12(opts.dir ?? process.cwd());
22674
22846
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename3(rootDir))}`;
22675
22847
  const tools = new ToolRegistry();
@@ -22720,9 +22892,9 @@ async function codeCommand(opts = {}) {
22720
22892
 
22721
22893
  // src/cli/commands/commit.ts
22722
22894
  import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
22723
- import { mkdtempSync, readFileSync as readFileSync23, unlinkSync as unlinkSync6, writeFileSync as writeFileSync14 } from "fs";
22724
- import { tmpdir } from "os";
22725
- import { join as join23 } from "path";
22895
+ import { mkdtempSync, readFileSync as readFileSync23, unlinkSync as unlinkSync6, writeFileSync as writeFileSync15 } from "fs";
22896
+ import { tmpdir as tmpdir2 } from "os";
22897
+ import { join as join24 } from "path";
22726
22898
  import { stdin as stdin2, stdout } from "process";
22727
22899
  import { createInterface } from "readline/promises";
22728
22900
  var DEFAULT_MODEL = "deepseek-v4-flash";
@@ -22865,9 +23037,9 @@ function editInExternal(initial) {
22865
23037
  );
22866
23038
  return null;
22867
23039
  }
22868
- const dir = mkdtempSync(join23(tmpdir(), "reasonix-commit-"));
22869
- const path5 = join23(dir, "COMMIT_EDITMSG");
22870
- writeFileSync14(path5, initial, "utf8");
23040
+ const dir = mkdtempSync(join24(tmpdir2(), "reasonix-commit-"));
23041
+ const path5 = join24(dir, "COMMIT_EDITMSG");
23042
+ writeFileSync15(path5, initial, "utf8");
22871
23043
  const result = spawnSync3(`${editor} "${path5}"`, {
22872
23044
  stdio: "inherit",
22873
23045
  shell: true
@@ -22993,7 +23165,7 @@ async function commitCommand(opts = {}) {
22993
23165
  }
22994
23166
 
22995
23167
  // src/cli/commands/diff.ts
22996
- import { writeFileSync as writeFileSync15 } from "fs";
23168
+ import { writeFileSync as writeFileSync16 } from "fs";
22997
23169
  import { basename as basename4 } from "path";
22998
23170
  import { render as render2 } from "ink";
22999
23171
  import React38 from "react";
@@ -23140,7 +23312,7 @@ async function diffCommand(opts) {
23140
23312
  if (wantMarkdown) {
23141
23313
  console.log(renderSummaryTable(report));
23142
23314
  const md = renderMarkdown(report);
23143
- writeFileSync15(opts.mdPath, md, "utf8");
23315
+ writeFileSync16(opts.mdPath, md, "utf8");
23144
23316
  console.log(`
23145
23317
  markdown report written to ${opts.mdPath}`);
23146
23318
  return;
@@ -23159,7 +23331,7 @@ markdown report written to ${opts.mdPath}`);
23159
23331
  // src/cli/commands/doctor.ts
23160
23332
  import { existsSync as existsSync24, statSync as statSync14 } from "fs";
23161
23333
  import { homedir as homedir11 } from "os";
23162
- import { dirname as dirname18, join as join24, resolve as resolve13 } from "path";
23334
+ import { dirname as dirname18, join as join25, resolve as resolve13 } from "path";
23163
23335
  var TTY = process.stdout.isTTY && process.env.TERM !== "dumb";
23164
23336
  function color(text2, code) {
23165
23337
  if (!TTY) return text2;
@@ -23282,7 +23454,7 @@ async function checkApiReach() {
23282
23454
  }
23283
23455
  async function checkTokenizer() {
23284
23456
  const candidates = [
23285
- join24(
23457
+ join25(
23286
23458
  dirname18(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")),
23287
23459
  "..",
23288
23460
  "..",
@@ -23290,7 +23462,7 @@ async function checkTokenizer() {
23290
23462
  "data",
23291
23463
  "deepseek-tokenizer.json.gz"
23292
23464
  ),
23293
- join24(process.cwd(), "data", "deepseek-tokenizer.json.gz")
23465
+ join25(process.cwd(), "data", "deepseek-tokenizer.json.gz")
23294
23466
  ];
23295
23467
  for (const p of candidates) {
23296
23468
  if (existsSync24(p)) {
@@ -23413,7 +23585,7 @@ async function checkOllama(projectRoot) {
23413
23585
  }
23414
23586
  async function checkProject(projectRoot) {
23415
23587
  const markers = [".git", "REASONIX.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
23416
- const found = markers.filter((m) => existsSync24(join24(projectRoot, m)));
23588
+ const found = markers.filter((m) => existsSync24(join25(projectRoot, m)));
23417
23589
  if (found.length === 0) {
23418
23590
  return {
23419
23591
  label: "project ",