reasonix 0.5.8 → 0.5.20

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
@@ -1642,6 +1642,11 @@ function deleteSession(name) {
1642
1642
  const path = sessionPath(name);
1643
1643
  try {
1644
1644
  unlinkSync(path);
1645
+ const sidecar = path.replace(/\.jsonl$/, ".pending.json");
1646
+ try {
1647
+ unlinkSync(sidecar);
1648
+ } catch {
1649
+ }
1645
1650
  return true;
1646
1651
  } catch {
1647
1652
  return false;
@@ -1669,13 +1674,18 @@ function countLines(path) {
1669
1674
 
1670
1675
  // src/telemetry.ts
1671
1676
  var DEEPSEEK_PRICING = {
1672
- "deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 },
1673
- "deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 }
1677
+ "deepseek-v4-flash": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
1678
+ "deepseek-v4-pro": { inputCacheHit: 0.139, inputCacheMiss: 1.667, output: 3.333 },
1679
+ // Compat aliases — priced as v4-flash per the deprecation notice.
1680
+ "deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
1681
+ "deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 }
1674
1682
  };
1675
1683
  var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
1676
1684
  var DEEPSEEK_CONTEXT_TOKENS = {
1677
- "deepseek-chat": 131072,
1678
- "deepseek-reasoner": 131072
1685
+ "deepseek-v4-flash": 1e6,
1686
+ "deepseek-v4-pro": 1e6,
1687
+ "deepseek-chat": 1e6,
1688
+ "deepseek-reasoner": 1e6
1679
1689
  };
1680
1690
  var DEFAULT_CONTEXT_TOKENS = 131072;
1681
1691
  function costUsd(model, usage) {
@@ -2003,7 +2013,7 @@ var CacheFirstLoop = class {
2003
2013
  content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)`
2004
2014
  };
2005
2015
  const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
2006
- this.appendAndPersist({ role: "assistant", content: stoppedMsg });
2016
+ this.appendAndPersist(this.syntheticAssistantMessage(stoppedMsg));
2007
2017
  yield {
2008
2018
  turn: this._turn,
2009
2019
  role: "assistant_final",
@@ -2139,6 +2149,7 @@ var CacheFirstLoop = class {
2139
2149
  };
2140
2150
  } else if (this.stream) {
2141
2151
  const callBuf = /* @__PURE__ */ new Map();
2152
+ const readyIndices = /* @__PURE__ */ new Set();
2142
2153
  for await (const chunk of this.client.stream({
2143
2154
  model: this.model,
2144
2155
  messages,
@@ -2174,13 +2185,18 @@ var CacheFirstLoop = class {
2174
2185
  if (d.argumentsDelta)
2175
2186
  cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
2176
2187
  callBuf.set(d.index, cur);
2188
+ if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
2189
+ readyIndices.add(d.index);
2190
+ }
2177
2191
  if (cur.function.name) {
2178
2192
  yield {
2179
2193
  turn: this._turn,
2180
2194
  role: "tool_call_delta",
2181
2195
  content: "",
2182
2196
  toolName: cur.function.name,
2183
- toolCallArgsChars: (cur.function.arguments ?? "").length
2197
+ toolCallArgsChars: (cur.function.arguments ?? "").length,
2198
+ toolCallIndex: d.index,
2199
+ toolCallReadyCount: readyIndices.size
2184
2200
  };
2185
2201
  }
2186
2202
  }
@@ -2231,7 +2247,9 @@ var CacheFirstLoop = class {
2231
2247
  reasoningContent || null,
2232
2248
  assistantContent || null
2233
2249
  );
2234
- this.appendAndPersist(this.assistantMessage(assistantContent, repairedCalls));
2250
+ this.appendAndPersist(
2251
+ this.assistantMessage(assistantContent, repairedCalls, reasoningContent)
2252
+ );
2235
2253
  yield {
2236
2254
  turn: this._turn,
2237
2255
  role: "assistant_final",
@@ -2393,7 +2411,7 @@ ${reason}`;
2393
2411
 
2394
2412
  ${summary}`;
2395
2413
  const summaryStats = this.stats.record(this._turn, this.model, resp.usage ?? new Usage());
2396
- this.appendAndPersist({ role: "assistant", content: summary });
2414
+ this.appendAndPersist(this.assistantMessage(summary, [], resp.reasoningContent ?? void 0));
2397
2415
  yield {
2398
2416
  turn: this._turn,
2399
2417
  role: "assistant_final",
@@ -2422,12 +2440,35 @@ ${summary}`;
2422
2440
  }
2423
2441
  return final;
2424
2442
  }
2425
- assistantMessage(content, toolCalls) {
2443
+ assistantMessage(content, toolCalls, reasoningContent) {
2426
2444
  const msg = { role: "assistant", content };
2427
2445
  if (toolCalls.length > 0) msg.tool_calls = toolCalls;
2446
+ if (reasoningContent && reasoningContent.length > 0) {
2447
+ msg.reasoning_content = reasoningContent;
2448
+ }
2449
+ return msg;
2450
+ }
2451
+ /**
2452
+ * Build a synthetic assistant message we insert into the log without
2453
+ * a real API round trip (abort notices, future system injections).
2454
+ * Reasoner models reject follow-up requests whose assistant history
2455
+ * is missing `reasoning_content`, so we stamp an empty-string
2456
+ * placeholder on reasoner sessions to satisfy the validator. V3
2457
+ * doesn't care — field stays absent there.
2458
+ */
2459
+ syntheticAssistantMessage(content) {
2460
+ const msg = { role: "assistant", content };
2461
+ if (isThinkingModeModel(this.model)) {
2462
+ msg.reasoning_content = "";
2463
+ }
2428
2464
  return msg;
2429
2465
  }
2430
2466
  };
2467
+ function isThinkingModeModel(model) {
2468
+ if (model.includes("reasoner")) return true;
2469
+ if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
2470
+ return false;
2471
+ }
2431
2472
  function stripHallucinatedToolMarkup(s) {
2432
2473
  let out = s;
2433
2474
  out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
@@ -2443,6 +2484,15 @@ function safeParseToolArgs(raw) {
2443
2484
  return raw;
2444
2485
  }
2445
2486
  }
2487
+ function looksLikeCompleteJson(s) {
2488
+ if (!s || !s.trim()) return false;
2489
+ try {
2490
+ JSON.parse(s);
2491
+ return true;
2492
+ } catch {
2493
+ return false;
2494
+ }
2495
+ }
2446
2496
  function* hookWarnings(outcomes, turn) {
2447
2497
  for (const o of outcomes) {
2448
2498
  if (o.decision === "pass") continue;
@@ -2589,7 +2639,7 @@ var DEFAULT_PICKER_IGNORE_DIRS = [
2589
2639
  "venv",
2590
2640
  "__pycache__"
2591
2641
  ];
2592
- function listFilesSync(root, opts = {}) {
2642
+ function listFilesWithStatsSync(root, opts = {}) {
2593
2643
  const maxResults = Math.max(1, opts.maxResults ?? 500);
2594
2644
  const ignore = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
2595
2645
  const rootAbs = resolve(root);
@@ -2610,7 +2660,12 @@ function listFilesSync(root, opts = {}) {
2610
2660
  if (ent.name.startsWith(".") || ignore.has(ent.name)) continue;
2611
2661
  walk2(join5(dirAbs, ent.name), relPath);
2612
2662
  } else if (ent.isFile()) {
2613
- out.push(relPath);
2663
+ let mtimeMs = 0;
2664
+ try {
2665
+ mtimeMs = statSync2(join5(dirAbs, ent.name)).mtimeMs;
2666
+ } catch {
2667
+ }
2668
+ out.push({ path: relPath, mtimeMs });
2614
2669
  }
2615
2670
  }
2616
2671
  };
@@ -2625,12 +2680,31 @@ function detectAtPicker(input) {
2625
2680
  const atOffset = input.length - query.length - 1;
2626
2681
  return { query, atOffset };
2627
2682
  }
2628
- function rankPickerCandidates(files, query, limit = 40) {
2629
- if (!query) return files.slice(0, limit);
2683
+ function rankPickerCandidates(files, query, limitOrOpts) {
2684
+ const opts = typeof limitOrOpts === "number" ? { limit: limitOrOpts } : limitOrOpts ?? {};
2685
+ const limit = opts.limit ?? 40;
2686
+ const recent = new Set(opts.recentlyUsed ?? []);
2687
+ const entries = files.map(
2688
+ (f) => typeof f === "string" ? { path: f, mtimeMs: 0 } : f
2689
+ );
2690
+ if (!query) {
2691
+ const anyMtime = entries.some((e) => e.mtimeMs > 0);
2692
+ if (!anyMtime && recent.size === 0) {
2693
+ return entries.slice(0, limit).map((e) => e.path);
2694
+ }
2695
+ const sorted = [...entries].sort((a, b) => {
2696
+ const aRecent = recent.has(a.path) ? 1 : 0;
2697
+ const bRecent = recent.has(b.path) ? 1 : 0;
2698
+ if (aRecent !== bRecent) return bRecent - aRecent;
2699
+ if (a.mtimeMs !== b.mtimeMs) return b.mtimeMs - a.mtimeMs;
2700
+ return a.path.localeCompare(b.path);
2701
+ });
2702
+ return sorted.slice(0, limit).map((e) => e.path);
2703
+ }
2630
2704
  const needle = query.toLowerCase();
2631
2705
  const scored = [];
2632
- for (const f of files) {
2633
- const lower = f.toLowerCase();
2706
+ for (const e of entries) {
2707
+ const lower = e.path.toLowerCase();
2634
2708
  const hit = lower.indexOf(needle);
2635
2709
  if (hit < 0) continue;
2636
2710
  const slash = lower.lastIndexOf("/");
@@ -2638,9 +2712,18 @@ function rankPickerCandidates(files, query, limit = 40) {
2638
2712
  let score = 2;
2639
2713
  if (base.startsWith(needle)) score = 0;
2640
2714
  else if (lower.startsWith(needle)) score = 1;
2641
- scored.push({ path: f, score: score * 1e4 + hit });
2715
+ scored.push({
2716
+ path: e.path,
2717
+ score: score * 1e4 + hit,
2718
+ mtimeMs: e.mtimeMs,
2719
+ recent: recent.has(e.path)
2720
+ });
2642
2721
  }
2643
- scored.sort((a, b) => a.score - b.score);
2722
+ scored.sort((a, b) => {
2723
+ if (a.score !== b.score) return a.score - b.score;
2724
+ if (a.recent !== b.recent) return a.recent ? -1 : 1;
2725
+ return b.mtimeMs - a.mtimeMs;
2726
+ });
2644
2727
  return scored.slice(0, limit).map((s) => s.path);
2645
2728
  }
2646
2729
  var AT_MENTION_PATTERN = /(?<=^|\s)@([a-zA-Z0-9_./\\-]+)/g;
@@ -3424,11 +3507,14 @@ async function spawnSubagent(opts) {
3424
3507
  const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
3425
3508
  const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
3426
3509
  const sink = opts.sink;
3510
+ const skillName = opts.skillName;
3427
3511
  const startedAt = Date.now();
3428
3512
  const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
3429
3513
  sink?.current?.({
3430
3514
  kind: "start",
3431
3515
  task: taskPreview,
3516
+ skillName,
3517
+ model,
3432
3518
  iter: 0,
3433
3519
  elapsedMs: 0
3434
3520
  });
@@ -3458,6 +3544,8 @@ async function spawnSubagent(opts) {
3458
3544
  sink?.current?.({
3459
3545
  kind: "progress",
3460
3546
  task: taskPreview,
3547
+ skillName,
3548
+ model,
3461
3549
  iter: toolIter,
3462
3550
  elapsedMs: Date.now() - startedAt
3463
3551
  });
@@ -3480,17 +3568,22 @@ async function spawnSubagent(opts) {
3480
3568
  const elapsedMs = Date.now() - startedAt;
3481
3569
  const turns = childLoop.stats.turns.length;
3482
3570
  const costUsd2 = childLoop.stats.totalCost;
3571
+ const usage = aggregateChildUsage(childLoop);
3483
3572
  const truncated = final.length > maxResultChars ? `${final.slice(0, maxResultChars)}
3484
3573
 
3485
3574
  [\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
3486
3575
  sink?.current?.({
3487
3576
  kind: "end",
3488
3577
  task: taskPreview,
3578
+ skillName,
3579
+ model,
3489
3580
  iter: toolIter,
3490
3581
  elapsedMs,
3491
3582
  summary: errorMessage ? void 0 : truncated.slice(0, 120),
3492
3583
  error: errorMessage,
3493
- turns
3584
+ turns,
3585
+ costUsd: costUsd2,
3586
+ usage
3494
3587
  });
3495
3588
  return {
3496
3589
  success: !errorMessage,
@@ -3499,9 +3592,23 @@ async function spawnSubagent(opts) {
3499
3592
  turns,
3500
3593
  toolIters: toolIter,
3501
3594
  elapsedMs,
3502
- costUsd: costUsd2
3595
+ costUsd: costUsd2,
3596
+ model,
3597
+ skillName,
3598
+ usage
3503
3599
  };
3504
3600
  }
3601
+ function aggregateChildUsage(loop) {
3602
+ const agg = new Usage();
3603
+ for (const t of loop.stats.turns) {
3604
+ agg.promptTokens += t.usage.promptTokens;
3605
+ agg.completionTokens += t.usage.completionTokens;
3606
+ agg.totalTokens += t.usage.totalTokens;
3607
+ agg.promptCacheHitTokens += t.usage.promptCacheHitTokens;
3608
+ agg.promptCacheMissTokens += t.usage.promptCacheMissTokens;
3609
+ }
3610
+ return agg;
3611
+ }
3505
3612
  function formatSubagentResult(r) {
3506
3613
  if (!r.success) {
3507
3614
  return JSON.stringify({
@@ -5496,6 +5603,8 @@ function appendUsage(input) {
5496
5603
  costUsd: costUsd(input.model, input.usage),
5497
5604
  claudeEquivUsd: claudeEquivalentCost(input.usage)
5498
5605
  };
5606
+ if (input.kind === "subagent") record.kind = "subagent";
5607
+ if (input.subagent) record.subagent = input.subagent;
5499
5608
  const path = input.path ?? defaultUsageLogPath();
5500
5609
  try {
5501
5610
  mkdirSync5(dirname7(path), { recursive: true });
@@ -5569,6 +5678,10 @@ function aggregateUsage(records, opts = {}) {
5569
5678
  const sessionCounts = /* @__PURE__ */ new Map();
5570
5679
  let firstSeen = null;
5571
5680
  let lastSeen = null;
5681
+ const skillCounts = /* @__PURE__ */ new Map();
5682
+ let subagentTotal = 0;
5683
+ let subagentCost = 0;
5684
+ let subagentDuration = 0;
5572
5685
  for (const r of records) {
5573
5686
  addToBucket(all, r);
5574
5687
  if (r.ts >= today.since) addToBucket(today, r);
@@ -5579,15 +5692,34 @@ function aggregateUsage(records, opts = {}) {
5579
5692
  sessionCounts.set(sessKey, (sessionCounts.get(sessKey) ?? 0) + 1);
5580
5693
  if (firstSeen === null || r.ts < firstSeen) firstSeen = r.ts;
5581
5694
  if (lastSeen === null || r.ts > lastSeen) lastSeen = r.ts;
5695
+ if (r.kind === "subagent") {
5696
+ subagentTotal += 1;
5697
+ subagentCost += r.costUsd;
5698
+ const dur = r.subagent?.durationMs ?? 0;
5699
+ subagentDuration += dur;
5700
+ const key = r.subagent?.skillName?.trim() || "(adhoc)";
5701
+ const prev = skillCounts.get(key) ?? { count: 0, costUsd: 0, durationMs: 0 };
5702
+ prev.count += 1;
5703
+ prev.costUsd += r.costUsd;
5704
+ prev.durationMs += dur;
5705
+ skillCounts.set(key, prev);
5706
+ }
5582
5707
  }
5583
5708
  const byModel = Array.from(modelCounts.entries()).map(([model, turns]) => ({ model, turns })).sort((a, b) => b.turns - a.turns);
5584
5709
  const bySession = Array.from(sessionCounts.entries()).map(([session, turns]) => ({ session, turns })).sort((a, b) => b.turns - a.turns);
5710
+ const subagents = subagentTotal > 0 ? {
5711
+ total: subagentTotal,
5712
+ costUsd: subagentCost,
5713
+ totalDurationMs: subagentDuration,
5714
+ bySkill: Array.from(skillCounts.entries()).map(([skillName, v]) => ({ skillName, ...v })).sort((a, b) => b.count - a.count)
5715
+ } : void 0;
5585
5716
  return {
5586
5717
  buckets: [today, week, month, all],
5587
5718
  byModel,
5588
5719
  bySession,
5589
5720
  firstSeen,
5590
- lastSeen
5721
+ lastSeen,
5722
+ subagents
5591
5723
  };
5592
5724
  }
5593
5725
  function formatLogSize(path = defaultUsageLogPath()) {
@@ -5604,13 +5736,134 @@ function formatLogSize(path = defaultUsageLogPath()) {
5604
5736
  }
5605
5737
 
5606
5738
  // src/cli/commands/chat.tsx
5607
- import { existsSync as existsSync10, statSync as statSync6 } from "fs";
5739
+ import { existsSync as existsSync11, statSync as statSync6 } from "fs";
5608
5740
  import { render } from "ink";
5609
- import React16, { useState as useState7 } from "react";
5741
+ import React17, { useState as useState7 } from "react";
5610
5742
 
5611
5743
  // src/cli/ui/App.tsx
5612
- import { Box as Box12, Static, Text as Text12, useApp, useInput as useInput4 } from "ink";
5613
- import React13, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
5744
+ import { Box as Box13, Static, Text as Text13, useApp, useInput as useInput4 } from "ink";
5745
+ import React14, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
5746
+
5747
+ // src/code/diff-preview.ts
5748
+ function formatEditBlockDiff(block, opts = {}) {
5749
+ const contextLines = Math.max(0, opts.contextLines ?? 2);
5750
+ const maxLines = Math.max(4, opts.maxLines ?? 20);
5751
+ const indent = opts.indent ?? " ";
5752
+ const search = block.search === "" ? [] : block.search.split("\n");
5753
+ const replace = block.replace.split("\n");
5754
+ if (search.length === 0) {
5755
+ return renderAllPlus(replace, indent, maxLines);
5756
+ }
5757
+ let leading = 0;
5758
+ while (leading < search.length && leading < replace.length && search[leading] === replace[leading]) {
5759
+ leading++;
5760
+ }
5761
+ let trailing = 0;
5762
+ while (trailing < search.length - leading && trailing < replace.length - leading && search[search.length - 1 - trailing] === replace[replace.length - 1 - trailing]) {
5763
+ trailing++;
5764
+ }
5765
+ const searchMiddle = search.slice(leading, search.length - trailing);
5766
+ const replaceMiddle = replace.slice(leading, replace.length - trailing);
5767
+ const leadShown = search.slice(Math.max(0, leading - contextLines), leading);
5768
+ const leadHidden = leading - leadShown.length;
5769
+ const trailShown = search.slice(
5770
+ search.length - trailing,
5771
+ search.length - trailing + contextLines
5772
+ );
5773
+ const trailHidden = trailing - trailShown.length;
5774
+ const out = [];
5775
+ if (leadHidden > 0) {
5776
+ out.push(`${indent} \u2026 ${leadHidden} unchanged line${leadHidden === 1 ? "" : "s"} above`);
5777
+ }
5778
+ for (const l of leadShown) out.push(`${indent} ${l}`);
5779
+ for (const l of searchMiddle) out.push(`${indent}- ${l}`);
5780
+ for (const l of replaceMiddle) out.push(`${indent}+ ${l}`);
5781
+ for (const l of trailShown) out.push(`${indent} ${l}`);
5782
+ if (trailHidden > 0) {
5783
+ out.push(`${indent} \u2026 ${trailHidden} unchanged line${trailHidden === 1 ? "" : "s"} below`);
5784
+ }
5785
+ return capLines(out, maxLines, indent);
5786
+ }
5787
+ function formatAllBlockDiffs(blocks, opts = {}) {
5788
+ const out = [];
5789
+ for (let i = 0; i < blocks.length; i++) {
5790
+ const b = blocks[i];
5791
+ const removed = b.search === "" ? 0 : countLines2(b.search);
5792
+ const added = countLines2(b.replace);
5793
+ const tag = b.search === "" ? "NEW " : " ";
5794
+ if (i > 0) out.push("");
5795
+ out.push(` ${tag}${b.path} (-${removed} +${added} lines)`);
5796
+ out.push(...formatEditBlockDiff(b, opts));
5797
+ }
5798
+ return out;
5799
+ }
5800
+ function countLines2(s) {
5801
+ if (s.length === 0) return 0;
5802
+ return (s.match(/\n/g)?.length ?? 0) + 1;
5803
+ }
5804
+ function renderAllPlus(lines, indent, maxLines) {
5805
+ const out = lines.map((l) => `${indent}+ ${l}`);
5806
+ return capLines(out, maxLines, indent);
5807
+ }
5808
+ function capLines(lines, maxLines, indent) {
5809
+ if (lines.length <= maxLines) return lines;
5810
+ const head = lines.slice(0, maxLines - 1);
5811
+ const hidden = lines.length - head.length;
5812
+ head.push(`${indent}\u2026 (${hidden} more diff lines \u2014 full content applies on /apply)`);
5813
+ return head;
5814
+ }
5815
+
5816
+ // src/code/pending-edits.ts
5817
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
5818
+ import { dirname as dirname8, join as join9 } from "path";
5819
+ function pendingEditsPath(sessionName) {
5820
+ return join9(sessionsDir(), `${sanitizeName(sessionName)}.pending.json`);
5821
+ }
5822
+ function savePendingEdits(sessionName, blocks) {
5823
+ if (!sessionName) return;
5824
+ const path = pendingEditsPath(sessionName);
5825
+ try {
5826
+ if (blocks.length === 0) {
5827
+ if (existsSync9(path)) unlinkSync3(path);
5828
+ return;
5829
+ }
5830
+ mkdirSync6(dirname8(path), { recursive: true });
5831
+ writeFileSync5(path, JSON.stringify(blocks, null, 2), "utf8");
5832
+ } catch {
5833
+ }
5834
+ }
5835
+ function loadPendingEdits(sessionName) {
5836
+ if (!sessionName) return null;
5837
+ const path = pendingEditsPath(sessionName);
5838
+ if (!existsSync9(path)) return null;
5839
+ let raw;
5840
+ try {
5841
+ raw = readFileSync11(path, "utf8");
5842
+ } catch {
5843
+ return null;
5844
+ }
5845
+ try {
5846
+ const parsed = JSON.parse(raw);
5847
+ if (!Array.isArray(parsed)) return null;
5848
+ const out = [];
5849
+ for (const item of parsed) {
5850
+ if (item && typeof item === "object" && typeof item.path === "string" && typeof item.search === "string" && typeof item.replace === "string" && typeof item.offset === "number") {
5851
+ out.push(item);
5852
+ }
5853
+ }
5854
+ return out;
5855
+ } catch {
5856
+ return null;
5857
+ }
5858
+ }
5859
+ function clearPendingEdits(sessionName) {
5860
+ if (!sessionName) return;
5861
+ const path = pendingEditsPath(sessionName);
5862
+ try {
5863
+ if (existsSync9(path)) unlinkSync3(path);
5864
+ } catch {
5865
+ }
5866
+ }
5614
5867
 
5615
5868
  // src/tools/skills.ts
5616
5869
  function registerSkillTools(registry, opts = {}) {
@@ -5733,8 +5986,8 @@ function PlanStateBlock({ planState }) {
5733
5986
  }
5734
5987
 
5735
5988
  // src/cli/ui/markdown.tsx
5736
- import { readFileSync as readFileSync11, statSync as statSync5 } from "fs";
5737
- import { isAbsolute as isAbsolute4, join as join9 } from "path";
5989
+ import { readFileSync as readFileSync12, statSync as statSync5 } from "fs";
5990
+ import { isAbsolute as isAbsolute4, join as join10 } from "path";
5738
5991
  import { Box as Box3, Text as Text3 } from "ink";
5739
5992
  import React3 from "react";
5740
5993
  var SUPERSCRIPT = {
@@ -5812,7 +6065,8 @@ function parseCitationUrl(url) {
5812
6065
  function validateCitation(url, projectRoot) {
5813
6066
  const parts = parseCitationUrl(url);
5814
6067
  if (!parts || !parts.path) return { ok: false, reason: "empty path" };
5815
- const fullPath = isAbsolute4(parts.path) ? parts.path : join9(projectRoot, parts.path);
6068
+ const normalized = parts.path.replace(/^[/\\]+/, "");
6069
+ const fullPath = isAbsolute4(normalized) ? normalized : join10(projectRoot, normalized);
5816
6070
  let stat;
5817
6071
  try {
5818
6072
  stat = statSync5(fullPath);
@@ -5823,7 +6077,7 @@ function validateCitation(url, projectRoot) {
5823
6077
  if (parts.startLine === void 0) return { ok: true };
5824
6078
  let lineCount;
5825
6079
  try {
5826
- lineCount = readFileSync11(fullPath, "utf8").split("\n").length;
6080
+ lineCount = readFileSync12(fullPath, "utf8").split("\n").length;
5827
6081
  } catch {
5828
6082
  return { ok: false, reason: "unreadable" };
5829
6083
  }
@@ -6315,13 +6569,15 @@ function StreamingAssistant({ event }) {
6315
6569
  label = `R1 reasoning \xB7 ${event.reasoning?.length ?? 0} chars of thought`;
6316
6570
  labelColor = "cyan";
6317
6571
  } else if (toolCallOnly) {
6318
- label = `assembling tool call <${toolCallBuild.name}> \xB7 ${toolCallBuild.chars} chars of arguments`;
6572
+ label = `assembling tool call${formatToolCallIndex(toolCallBuild)} <${toolCallBuild.name}> \xB7 ${toolCallBuild.chars} chars of arguments${formatReadyTail(toolCallBuild)}`;
6319
6573
  labelColor = "magenta";
6320
6574
  } else {
6321
6575
  const parts = [`writing response \xB7 ${event.text.length} chars`];
6322
6576
  if (event.reasoning) parts.push(`after ${event.reasoning.length} chars of reasoning`);
6323
6577
  if (toolCallBuild) {
6324
- parts.push(`building tool call <${toolCallBuild.name}> \xB7 ${toolCallBuild.chars} chars`);
6578
+ parts.push(
6579
+ `building tool call${formatToolCallIndex(toolCallBuild)} <${toolCallBuild.name}> \xB7 ${toolCallBuild.chars} chars${formatReadyTail(toolCallBuild)}`
6580
+ );
6325
6581
  }
6326
6582
  label = parts.join(" \xB7 ");
6327
6583
  labelColor = "green";
@@ -6337,6 +6593,16 @@ function Pulse() {
6337
6593
  const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
6338
6594
  return /* @__PURE__ */ React5.createElement(Text4, { color: "cyan" }, frames[Math.floor(tick / 4) % frames.length]);
6339
6595
  }
6596
+ function formatToolCallIndex(tb) {
6597
+ if (!tb || tb.index === void 0) return "";
6598
+ if (tb.index === 0 && (tb.readyCount ?? 0) === 0) return "";
6599
+ return ` (call ${tb.index + 1})`;
6600
+ }
6601
+ function formatReadyTail(tb) {
6602
+ const n = tb?.readyCount ?? 0;
6603
+ if (n <= 0) return "";
6604
+ return ` \xB7 ${n} ready`;
6605
+ }
6340
6606
  function lastLine(s, maxChars) {
6341
6607
  const flat = s.replace(/\s+/g, " ").trim();
6342
6608
  if (!flat) return "";
@@ -6474,12 +6740,34 @@ function findNextEnabled(items, from, step) {
6474
6740
 
6475
6741
  // src/cli/ui/PlanConfirm.tsx
6476
6742
  var DEFAULT_MAX_RENDERED = 2400;
6477
- function PlanConfirm({ plan, onChoose, maxRenderedChars, projectRoot }) {
6743
+ var PICKER_CHROME_ROWS = 18;
6744
+ var MARKDOWN_EXPANSION = 2;
6745
+ var MIN_BODY_ROWS = 4;
6746
+ function clampBodyByLines(text, maxLines) {
6747
+ const lines = text.split("\n");
6748
+ if (lines.length <= maxLines) return text;
6749
+ const kept = lines.slice(0, maxLines).join("\n");
6750
+ const dropped = lines.length - maxLines;
6751
+ return `${kept}
6752
+
6753
+ \u2026 (${dropped} more lines truncated \u2014 resize the terminal to see more, or /tool for the full proposal)`;
6754
+ }
6755
+ function PlanConfirmInner({
6756
+ plan,
6757
+ onChoose,
6758
+ maxRenderedChars,
6759
+ projectRoot,
6760
+ terminalRows
6761
+ }) {
6478
6762
  const cap = maxRenderedChars ?? DEFAULT_MAX_RENDERED;
6479
- const tooLong = plan.length > cap;
6480
- const visible = tooLong ? `${plan.slice(0, cap)}
6763
+ const charTrunc = plan.length > cap;
6764
+ const charCapped = charTrunc ? `${plan.slice(0, cap)}
6481
6765
 
6482
6766
  \u2026 (${plan.length - cap} chars truncated \u2014 use /tool to view the full proposal)` : plan;
6767
+ const rows = terminalRows ?? process.stdout?.rows ?? 24;
6768
+ const renderedBudget = Math.max(MIN_BODY_ROWS * MARKDOWN_EXPANSION, rows - PICKER_CHROME_ROWS);
6769
+ const sourceLineBudget = Math.max(MIN_BODY_ROWS, Math.floor(renderedBudget / MARKDOWN_EXPANSION));
6770
+ const visible = clampBodyByLines(charCapped, sourceLineBudget);
6483
6771
  const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
6484
6772
  return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Text6, { color: "cyan", 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Markdown, { text: visible, projectRoot })), hasOpenQuestions ? /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(
6485
6773
  SingleSelect,
@@ -6508,6 +6796,7 @@ function PlanConfirm({ plan, onChoose, maxRenderedChars, projectRoot }) {
6508
6796
  }
6509
6797
  )));
6510
6798
  }
6799
+ var PlanConfirm = React7.memo(PlanConfirmInner);
6511
6800
 
6512
6801
  // src/cli/ui/PlanRefineInput.tsx
6513
6802
  import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
@@ -6792,16 +7081,49 @@ function derivePrefix(command) {
6792
7081
  return TWO_TOKEN_WRAPPERS.has(first) ? `${first} ${tokens[1]}` : first;
6793
7082
  }
6794
7083
 
6795
- // src/cli/ui/SlashSuggestions.tsx
7084
+ // src/cli/ui/SlashArgPicker.tsx
6796
7085
  import { Box as Box10, Text as Text10 } from "ink";
6797
7086
  import React11 from "react";
7087
+ function SlashArgPicker({
7088
+ matches,
7089
+ selectedIndex,
7090
+ spec,
7091
+ kind,
7092
+ partial
7093
+ }) {
7094
+ if (kind === "hint") {
7095
+ return /* @__PURE__ */ React11.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary));
7096
+ }
7097
+ if (matches === null) return null;
7098
+ if (matches.length === 0) {
7099
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary), /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, ' no match for "', partial, '" \u2014 keep typing, or Backspace to edit'));
7100
+ }
7101
+ const MAX = 8;
7102
+ const total = matches.length;
7103
+ const windowStart = total <= MAX ? 0 : Math.max(0, Math.min(selectedIndex - Math.floor(MAX / 2), total - MAX));
7104
+ const shown = matches.slice(windowStart, windowStart + MAX);
7105
+ const hiddenAbove = windowStart;
7106
+ const hiddenBelow = total - windowStart - shown.length;
7107
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary), hiddenAbove > 0 ? /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((value, i) => /* @__PURE__ */ React11.createElement(ArgRow, { key: value, value, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
7108
+ }
7109
+ function ArgRow({ value, isSelected }) {
7110
+ const marker = isSelected ? "\u25B8" : " ";
7111
+ if (isSelected) {
7112
+ return /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: "cyan" }, marker, " ", value));
7113
+ }
7114
+ return /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, marker, " ", value));
7115
+ }
7116
+
7117
+ // src/cli/ui/SlashSuggestions.tsx
7118
+ import { Box as Box11, Text as Text11 } from "ink";
7119
+ import React12 from "react";
6798
7120
  function SlashSuggestions({
6799
7121
  matches,
6800
7122
  selectedIndex
6801
7123
  }) {
6802
7124
  if (matches === null) return null;
6803
7125
  if (matches.length === 0) {
6804
- return /* @__PURE__ */ React11.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
7126
+ return /* @__PURE__ */ React12.createElement(Box11, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
6805
7127
  }
6806
7128
  const MAX = 8;
6807
7129
  const total = matches.length;
@@ -6809,21 +7131,21 @@ function SlashSuggestions({
6809
7131
  const shown = matches.slice(windowStart, windowStart + MAX);
6810
7132
  const hiddenAbove = windowStart;
6811
7133
  const hiddenBelow = total - windowStart - shown.length;
6812
- return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React11.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
7134
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React12.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
6813
7135
  }
6814
7136
  function SuggestionRow({ spec, isSelected }) {
6815
7137
  const marker = isSelected ? "\u25B8" : " ";
6816
7138
  const name = `/${spec.cmd}`;
6817
7139
  const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
6818
7140
  if (isSelected) {
6819
- return /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, " ", spec.summary));
7141
+ return /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, " ", spec.summary));
6820
7142
  }
6821
- return /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
7143
+ return /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
6822
7144
  }
6823
7145
 
6824
7146
  // src/cli/ui/StatsPanel.tsx
6825
- import { Box as Box11, Text as Text11, useStdout as useStdout2 } from "ink";
6826
- import React12 from "react";
7147
+ import { Box as Box12, Text as Text12, useStdout as useStdout2 } from "ink";
7148
+ import React13 from "react";
6827
7149
  var WORDMARK_STYLES = [
6828
7150
  { ch: "\u25C8", color: "#5eead4", isLogo: true },
6829
7151
  // teal — brand mark
@@ -6849,7 +7171,7 @@ function Wordmark({ busy }) {
6849
7171
  const tick = useTick();
6850
7172
  const period = busy ? 5 : 12;
6851
7173
  const bright = Math.floor(tick / period) % 2 === 0;
6852
- return /* @__PURE__ */ React12.createElement(Text11, null, WORDMARK_STYLES.map((c) => /* @__PURE__ */ React12.createElement(Text11, { key: `${c.ch}-${c.color}`, color: c.color, bold: c.isLogo ? bright : true }, c.ch)));
7174
+ return /* @__PURE__ */ React13.createElement(Text12, null, WORDMARK_STYLES.map((c) => /* @__PURE__ */ React13.createElement(Text12, { key: `${c.ch}-${c.color}`, color: c.color, bold: c.isLogo ? bright : true }, c.ch)));
6853
7175
  }
6854
7176
  var NARROW_BREAKPOINT = 120;
6855
7177
  var COLD_START_TURNS = 3;
@@ -6871,7 +7193,7 @@ function StatsPanel({
6871
7193
  const columns = stdout2?.columns ?? 80;
6872
7194
  const narrow = columns < NARROW_BREAKPOINT;
6873
7195
  const coldStart = summary.turns <= COLD_START_TURNS;
6874
- return /* @__PURE__ */ React12.createElement(Box11, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React12.createElement(
7196
+ return /* @__PURE__ */ React13.createElement(Box12, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React13.createElement(
6875
7197
  Header,
6876
7198
  {
6877
7199
  model,
@@ -6885,7 +7207,7 @@ function StatsPanel({
6885
7207
  narrow,
6886
7208
  busy: busy ?? false
6887
7209
  }
6888
- ), narrow ? /* @__PURE__ */ React12.createElement(
7210
+ ), narrow ? /* @__PURE__ */ React13.createElement(
6889
7211
  StackedMetrics,
6890
7212
  {
6891
7213
  summary,
@@ -6894,7 +7216,7 @@ function StatsPanel({
6894
7216
  balance,
6895
7217
  coldStart
6896
7218
  }
6897
- ) : /* @__PURE__ */ React12.createElement(
7219
+ ) : /* @__PURE__ */ React13.createElement(
6898
7220
  InlineMetrics,
6899
7221
  {
6900
7222
  summary,
@@ -6917,7 +7239,7 @@ function Header({
6917
7239
  narrow,
6918
7240
  busy
6919
7241
  }) {
6920
- return /* @__PURE__ */ React12.createElement(Box11, { justifyContent: "space-between" }, /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Wordmark, { busy }), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, model), narrow ? null : /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React12.createElement(Text11, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, " \xB7 PLAN") : null), /* @__PURE__ */ React12.createElement(Text11, null, updateAvailable ? /* @__PURE__ */ React12.createElement(Text11, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
7242
+ return /* @__PURE__ */ React13.createElement(Box12, { justifyContent: "space-between" }, /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Wordmark, { busy }), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, model), narrow ? null : /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React13.createElement(Text12, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, " \xB7 PLAN") : null), /* @__PURE__ */ React13.createElement(Text12, null, updateAvailable ? /* @__PURE__ */ React13.createElement(Text12, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
6921
7243
  }
6922
7244
  function InlineMetrics({
6923
7245
  summary,
@@ -6926,7 +7248,7 @@ function InlineMetrics({
6926
7248
  balance,
6927
7249
  coldStart
6928
7250
  }) {
6929
- return /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React12.createElement(ContextCell, { ratio: ctxRatio, promptTokens: summary.lastPromptTokens, ctxMax }), /* @__PURE__ */ React12.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React12.createElement(CostCell, { summary, coldStart }), balance ? /* @__PURE__ */ React12.createElement(BalanceCell, { balance }) : null);
7251
+ return /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React13.createElement(ContextCell, { ratio: ctxRatio, promptTokens: summary.lastPromptTokens, ctxMax }), /* @__PURE__ */ React13.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React13.createElement(CostCell, { summary, coldStart }), balance ? /* @__PURE__ */ React13.createElement(BalanceCell, { balance }) : null);
6930
7252
  }
6931
7253
  function StackedMetrics({
6932
7254
  summary,
@@ -6935,7 +7257,7 @@ function StackedMetrics({
6935
7257
  balance,
6936
7258
  coldStart
6937
7259
  }) {
6938
- return /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(
7260
+ return /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React13.createElement(
6939
7261
  ContextCell,
6940
7262
  {
6941
7263
  ratio: ctxRatio,
@@ -6943,7 +7265,7 @@ function StackedMetrics({
6943
7265
  ctxMax,
6944
7266
  showBar: true
6945
7267
  }
6946
- ), balance ? /* @__PURE__ */ React12.createElement(BalanceCell, { balance }) : null, /* @__PURE__ */ React12.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React12.createElement(CostCell, { summary, coldStart }));
7268
+ ), balance ? /* @__PURE__ */ React13.createElement(BalanceCell, { balance }) : null, /* @__PURE__ */ React13.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React13.createElement(CostCell, { summary, coldStart }));
6947
7269
  }
6948
7270
  function ContextCell({
6949
7271
  ratio,
@@ -6952,11 +7274,11 @@ function ContextCell({
6952
7274
  showBar
6953
7275
  }) {
6954
7276
  if (promptTokens === 0) {
6955
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "ctx "), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "\u2014 (no turns yet)"));
7277
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "ctx "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\u2014 (no turns yet)"));
6956
7278
  }
6957
7279
  const color = ratio >= 0.8 ? "red" : ratio >= 0.6 ? "yellow" : "green";
6958
7280
  const pct2 = Math.round(ratio * 100);
6959
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "ctx "), showBar ? /* @__PURE__ */ React12.createElement(Bar, { ratio, color }) : null, showBar ? /* @__PURE__ */ React12.createElement(Text11, null, " ") : null, /* @__PURE__ */ React12.createElement(Text11, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, " \xB7 /compact") : null);
7281
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "ctx "), showBar ? /* @__PURE__ */ React13.createElement(Bar, { ratio, color }) : null, showBar ? /* @__PURE__ */ React13.createElement(Text12, null, " ") : null, /* @__PURE__ */ React13.createElement(Text12, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, " \xB7 /compact") : null);
6960
7282
  }
6961
7283
  function CacheCell({
6962
7284
  hitRatio,
@@ -6965,33 +7287,33 @@ function CacheCell({
6965
7287
  }) {
6966
7288
  const pct2 = (hitRatio * 100).toFixed(1);
6967
7289
  if (turns === 0) {
6968
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "cache "), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "\u2014"));
7290
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "cache "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\u2014"));
6969
7291
  }
6970
7292
  if (coldStart) {
6971
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "cache "), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true, italic: true }, "(cold start)"));
7293
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "cache "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true, italic: true }, "(cold start)"));
6972
7294
  }
6973
7295
  const color = hitRatio >= 0.7 ? "green" : hitRatio >= 0.4 ? "yellow" : "red";
6974
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "cache "), /* @__PURE__ */ React12.createElement(Text11, { color, bold: true }, pct2, "%"));
7296
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "cache "), /* @__PURE__ */ React13.createElement(Text12, { color, bold: true }, pct2, "%"));
6975
7297
  }
6976
7298
  function CostCell({
6977
7299
  summary,
6978
7300
  coldStart
6979
7301
  }) {
6980
7302
  if (summary.turns === 0) {
6981
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "cost "), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "\u2014"));
7303
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "cost "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\u2014"));
6982
7304
  }
6983
7305
  const primaryColor = coldStart ? void 0 : "green";
6984
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "cost "), /* @__PURE__ */ React12.createElement(Text11, { color: primaryColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")"));
7306
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "cost "), /* @__PURE__ */ React13.createElement(Text12, { color: primaryColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")"));
6985
7307
  }
6986
7308
  function BalanceCell({ balance }) {
6987
7309
  const color = balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green";
6988
- return /* @__PURE__ */ React12.createElement(Text11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "balance "), /* @__PURE__ */ React12.createElement(Text11, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
7310
+ return /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "balance "), /* @__PURE__ */ React13.createElement(Text12, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
6989
7311
  }
6990
7312
  function Bar({ ratio, color }) {
6991
7313
  const cells = 10;
6992
7314
  const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
6993
7315
  const bar = "\u2588".repeat(filled) + "\u2591".repeat(cells - filled);
6994
- return /* @__PURE__ */ React12.createElement(Text11, { color }, bar);
7316
+ return /* @__PURE__ */ React13.createElement(Text12, { color }, bar);
6995
7317
  }
6996
7318
  function formatTokens(n) {
6997
7319
  if (n < 1024) return String(n);
@@ -7011,11 +7333,215 @@ function formatBangUserMessage(cmd, output) {
7011
7333
  ${output}`;
7012
7334
  }
7013
7335
 
7336
+ // src/cli/ui/mcp-browse.ts
7337
+ function formatResourceList(servers) {
7338
+ const lines = [];
7339
+ let total = 0;
7340
+ for (const s of servers) {
7341
+ if (!s.report.resources.supported) continue;
7342
+ const items = s.report.resources.items;
7343
+ if (items.length === 0) continue;
7344
+ lines.push(`[${s.label}] ${items.length} resource(s):`);
7345
+ for (const r of items.slice(0, 20)) {
7346
+ const name = r.name && r.name !== r.uri ? ` ${r.name}` : "";
7347
+ const mime = r.mimeType ? ` \xB7 ${r.mimeType}` : "";
7348
+ lines.push(` \xB7 ${r.uri}${name}${mime}`);
7349
+ total++;
7350
+ }
7351
+ if (items.length > 20) lines.push(` (+${items.length - 20} more)`);
7352
+ lines.push("");
7353
+ }
7354
+ if (total === 0) {
7355
+ return "No resources on any connected MCP server (or no servers connected). `/mcp` shows the current set.";
7356
+ }
7357
+ lines.push("Read one: `/resource <uri>` \u2014 or use Tab in the picker.");
7358
+ return lines.join("\n");
7359
+ }
7360
+ function formatPromptList(servers) {
7361
+ const lines = [];
7362
+ let total = 0;
7363
+ for (const s of servers) {
7364
+ if (!s.report.prompts.supported) continue;
7365
+ const items = s.report.prompts.items;
7366
+ if (items.length === 0) continue;
7367
+ lines.push(`[${s.label}] ${items.length} prompt(s):`);
7368
+ for (const p of items.slice(0, 20)) {
7369
+ const desc = p.description ? ` \u2014 ${p.description}` : "";
7370
+ const argHint = p.arguments && p.arguments.length > 0 ? ` (args: ${p.arguments.map((a) => a.name + (a.required ? "*" : "?")).join(", ")})` : "";
7371
+ lines.push(` \xB7 ${p.name}${argHint}${desc}`);
7372
+ total++;
7373
+ }
7374
+ if (items.length > 20) lines.push(` (+${items.length - 20} more)`);
7375
+ lines.push("");
7376
+ }
7377
+ if (total === 0) {
7378
+ return "No prompts on any connected MCP server (or no servers connected). `/mcp` shows the current set.";
7379
+ }
7380
+ lines.push(
7381
+ "Fetch one: `/prompt <name>` \u2014 args are not supported yet; prompts with required args will surface an error from the server."
7382
+ );
7383
+ return lines.join("\n");
7384
+ }
7385
+ function findServerForResource(servers, uri) {
7386
+ for (const s of servers) {
7387
+ if (!s.report.resources.supported) continue;
7388
+ if (s.report.resources.items.some((r) => r.uri === uri)) return s;
7389
+ }
7390
+ return null;
7391
+ }
7392
+ function findServerForPrompt(servers, name) {
7393
+ for (const s of servers) {
7394
+ if (!s.report.prompts.supported) continue;
7395
+ if (s.report.prompts.items.some((p) => p.name === name)) return s;
7396
+ }
7397
+ return null;
7398
+ }
7399
+ function formatResourceContents(uri, result) {
7400
+ const lines = [`Resource ${uri} (${result.contents.length} content block(s)):`, ""];
7401
+ for (let i = 0; i < result.contents.length; i++) {
7402
+ const c = result.contents[i];
7403
+ const header2 = `\u2014 block ${i + 1}${c.mimeType ? ` \xB7 ${c.mimeType}` : ""}`;
7404
+ lines.push(header2);
7405
+ lines.push(formatOneResourceContent(c));
7406
+ lines.push("");
7407
+ }
7408
+ return lines.join("\n").trimEnd();
7409
+ }
7410
+ function formatOneResourceContent(c) {
7411
+ if ("text" in c) {
7412
+ const MAX = 8e3;
7413
+ if (c.text.length > MAX) {
7414
+ return `${c.text.slice(0, MAX)}
7415
+
7416
+ [\u2026truncated ${c.text.length - MAX} chars; full contents available via McpClient.readResource in library mode.]`;
7417
+ }
7418
+ return c.text;
7419
+ }
7420
+ const bytes = typeof c.blob === "string" ? approximateBase64ByteSize(c.blob) : 0;
7421
+ return `[binary \xB7 ~${bytes.toLocaleString()} bytes \xB7 base64]`;
7422
+ }
7423
+ function approximateBase64ByteSize(b64) {
7424
+ const padding = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
7425
+ return Math.floor(b64.length * 3 / 4) - padding;
7426
+ }
7427
+ function formatPromptMessages(name, result) {
7428
+ const lines = [
7429
+ `Prompt ${name}${result.description ? ` \u2014 ${result.description}` : ""}`,
7430
+ `(${result.messages.length} message(s))`,
7431
+ ""
7432
+ ];
7433
+ for (let i = 0; i < result.messages.length; i++) {
7434
+ const m = result.messages[i];
7435
+ lines.push(`\u2014 ${i + 1}. ${m.role}`);
7436
+ lines.push(formatOnePromptMessage(m));
7437
+ lines.push("");
7438
+ }
7439
+ return lines.join("\n").trimEnd();
7440
+ }
7441
+ function formatOnePromptMessage(m) {
7442
+ const block = m.content;
7443
+ if (block.type === "text" && typeof block.text === "string") return block.text;
7444
+ if (block.type === "resource" && block.resource) {
7445
+ return `[resource: ${block.resource.uri}]
7446
+ ${formatOneResourceContent(block.resource)}`;
7447
+ }
7448
+ return `[non-text content: ${block.type ?? "unknown"}]`;
7449
+ }
7450
+ async function handleMcpBrowseSlash(kind, arg, servers, setHistorical) {
7451
+ const ts = Date.now();
7452
+ const push = (role, text) => {
7453
+ setHistorical((prev) => [...prev, { id: `mcp-${role}-${ts}-${prev.length}`, role, text }]);
7454
+ };
7455
+ if (!arg) {
7456
+ push("info", kind === "resource" ? formatResourceList(servers) : formatPromptList(servers));
7457
+ return;
7458
+ }
7459
+ if (kind === "resource") {
7460
+ const server2 = findServerForResource(servers, arg);
7461
+ if (!server2) {
7462
+ push(
7463
+ "warning",
7464
+ `no server exposes resource "${arg}". \`/resource\` with no arg lists what's available.`
7465
+ );
7466
+ return;
7467
+ }
7468
+ const client2 = server2.client;
7469
+ if (!client2) {
7470
+ push(
7471
+ "warning",
7472
+ `server [${server2.label}] is not connected (display-only). Resource read requires a live MCP client.`
7473
+ );
7474
+ return;
7475
+ }
7476
+ try {
7477
+ const result = await client2.readResource(arg);
7478
+ push("info", formatResourceContents(arg, result));
7479
+ } catch (err) {
7480
+ push("warning", `readResource failed: ${err.message}`);
7481
+ }
7482
+ return;
7483
+ }
7484
+ const server = findServerForPrompt(servers, arg);
7485
+ if (!server) {
7486
+ push(
7487
+ "warning",
7488
+ `no server exposes prompt "${arg}". \`/prompt\` with no arg lists what's available.`
7489
+ );
7490
+ return;
7491
+ }
7492
+ const client = server.client;
7493
+ if (!client) {
7494
+ push(
7495
+ "warning",
7496
+ `server [${server.label}] is not connected (display-only). Prompt fetch requires a live MCP client.`
7497
+ );
7498
+ return;
7499
+ }
7500
+ try {
7501
+ const result = await client.getPrompt(arg);
7502
+ push("info", formatPromptMessages(arg, result));
7503
+ } catch (err) {
7504
+ push("warning", `getPrompt failed: ${err.message}`);
7505
+ }
7506
+ }
7507
+
7508
+ // src/cli/ui/paste-collapse.ts
7509
+ var DEFAULT_PASTE_LINE_THRESHOLD = 40;
7510
+ var DEFAULT_PASTE_CHAR_THRESHOLD = 2e3;
7511
+ var DEFAULT_PASTE_HEAD_LINES = 10;
7512
+ function formatLongPaste(input, opts = {}) {
7513
+ const lineCap = opts.lineThreshold ?? DEFAULT_PASTE_LINE_THRESHOLD;
7514
+ const charCap = opts.charThreshold ?? DEFAULT_PASTE_CHAR_THRESHOLD;
7515
+ const headN = Math.max(1, opts.headLines ?? DEFAULT_PASTE_HEAD_LINES);
7516
+ const originalChars = input.length;
7517
+ const lines = input.split("\n");
7518
+ const originalLines = lines.length;
7519
+ if (originalChars <= charCap && originalLines <= lineCap) {
7520
+ return { displayText: input, collapsed: false, originalChars, originalLines };
7521
+ }
7522
+ const header2 = `\u25B8 pasted ${formatBytes(originalChars)} (${originalLines} lines) \u2014 first ${Math.min(headN, originalLines)} shown, full text sent to model`;
7523
+ const head = lines.slice(0, headN).join("\n");
7524
+ const remaining = originalLines - headN;
7525
+ const footer = remaining > 0 ? `\u2026 (${remaining} more line${remaining === 1 ? "" : "s"})` : "";
7526
+ const displayText = footer ? `${header2}
7527
+ ${head}
7528
+ ${footer}` : `${header2}
7529
+ ${head}`;
7530
+ return { displayText, collapsed: true, originalChars, originalLines };
7531
+ }
7532
+ function formatBytes(n) {
7533
+ if (n < 1024) return `${n} B`;
7534
+ const kb = n / 1024;
7535
+ if (kb < 1024) return `${kb.toFixed(kb >= 10 ? 0 : 1)} KB`;
7536
+ const mb = kb / 1024;
7537
+ return `${mb.toFixed(mb >= 10 ? 0 : 1)} MB`;
7538
+ }
7539
+
7014
7540
  // src/cli/ui/slash.ts
7015
7541
  import { spawnSync } from "child_process";
7016
7542
 
7017
7543
  // src/cli/commands/stats.ts
7018
- import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
7544
+ import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
7019
7545
  function statsCommand(opts) {
7020
7546
  if (opts.transcript) {
7021
7547
  transcriptSummary(opts.transcript);
@@ -7024,11 +7550,11 @@ function statsCommand(opts) {
7024
7550
  dashboard(opts);
7025
7551
  }
7026
7552
  function transcriptSummary(path) {
7027
- if (!existsSync9(path)) {
7553
+ if (!existsSync10(path)) {
7028
7554
  console.error(`no such transcript: ${path}`);
7029
7555
  process.exit(1);
7030
7556
  }
7031
- const lines = readFileSync12(path, "utf8").split(/\r?\n/).filter(Boolean);
7557
+ const lines = readFileSync13(path, "utf8").split(/\r?\n/).filter(Boolean);
7032
7558
  let assistantTurns = 0;
7033
7559
  let toolCalls = 0;
7034
7560
  let lastTurn = 0;
@@ -7087,6 +7613,28 @@ function renderDashboard(agg, logPath) {
7087
7613
  if (agg.firstSeen) {
7088
7614
  lines.push(`tracked since: ${new Date(agg.firstSeen).toISOString().slice(0, 10)}`);
7089
7615
  }
7616
+ if (agg.subagents) {
7617
+ lines.push("");
7618
+ lines.push(renderSubagentSection(agg.subagents));
7619
+ }
7620
+ return lines.join("\n");
7621
+ }
7622
+ function renderSubagentSection(sub) {
7623
+ const lines = [];
7624
+ const seconds = (sub.totalDurationMs / 1e3).toFixed(1);
7625
+ lines.push(
7626
+ `subagent activity: ${sub.total} run(s) \xB7 $${sub.costUsd.toFixed(6)} \xB7 ${seconds}s total`
7627
+ );
7628
+ const top = sub.bySkill.slice(0, 5);
7629
+ for (const s of top) {
7630
+ const sec = (s.durationMs / 1e3).toFixed(1);
7631
+ lines.push(
7632
+ ` ${pad(s.skillName, 18)} ${pad(`${s.count}`, 4, "right")} $${s.costUsd.toFixed(6)} ${sec}s`
7633
+ );
7634
+ }
7635
+ if (sub.bySkill.length > top.length) {
7636
+ lines.push(` (+${sub.bySkill.length - top.length} more)`);
7637
+ }
7090
7638
  return lines.join("\n");
7091
7639
  }
7092
7640
  function header() {
@@ -7127,13 +7675,41 @@ var SLASH_COMMANDS = [
7127
7675
  {
7128
7676
  cmd: "preset",
7129
7677
  argsHint: "<fast|smart|max>",
7130
- summary: "one-tap model + harvest + branch bundle"
7678
+ summary: "one-tap model + harvest + branch bundle",
7679
+ argCompleter: ["fast", "smart", "max"]
7680
+ },
7681
+ {
7682
+ cmd: "model",
7683
+ argsHint: "<id>",
7684
+ summary: "switch DeepSeek model id",
7685
+ argCompleter: "models"
7131
7686
  },
7132
- { cmd: "model", argsHint: "<id>", summary: "switch DeepSeek model id" },
7133
7687
  { cmd: "models", summary: "list available models fetched from DeepSeek /models" },
7134
- { cmd: "harvest", argsHint: "[on|off]", summary: "toggle Pillar-2 plan-state extraction" },
7135
- { cmd: "branch", argsHint: "<N|off>", summary: "run N parallel samples per turn (N>=2)" },
7688
+ {
7689
+ cmd: "harvest",
7690
+ argsHint: "[on|off]",
7691
+ summary: "toggle Pillar-2 plan-state extraction",
7692
+ argCompleter: ["on", "off"]
7693
+ },
7694
+ {
7695
+ cmd: "branch",
7696
+ argsHint: "<N|off>",
7697
+ summary: "run N parallel samples per turn (N>=2)",
7698
+ argCompleter: ["off", "2", "3", "4", "5"]
7699
+ },
7136
7700
  { cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
7701
+ {
7702
+ cmd: "resource",
7703
+ argsHint: "[uri]",
7704
+ summary: "browse + read MCP resources (no arg \u2192 list URIs; <uri> \u2192 fetch contents)",
7705
+ argCompleter: "mcp-resources"
7706
+ },
7707
+ {
7708
+ cmd: "prompt",
7709
+ argsHint: "[name]",
7710
+ summary: "browse + fetch MCP prompts (no arg \u2192 list names; <name> \u2192 render prompt)",
7711
+ argCompleter: "mcp-prompts"
7712
+ },
7137
7713
  { cmd: "tool", argsHint: "[N]", summary: "dump full output of the Nth tool call (1=latest)" },
7138
7714
  {
7139
7715
  cmd: "memory",
@@ -7169,6 +7745,7 @@ var SLASH_COMMANDS = [
7169
7745
  argsHint: "[tokens]",
7170
7746
  summary: "shrink oversized tool results in the log (cap in tokens, default 4000)"
7171
7747
  },
7748
+ { cmd: "keys", summary: "show all keyboard shortcuts and prompt prefixes" },
7172
7749
  { cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
7173
7750
  { cmd: "forget", summary: "delete the current session from disk" },
7174
7751
  { cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
@@ -7189,7 +7766,8 @@ var SLASH_COMMANDS = [
7189
7766
  cmd: "plan",
7190
7767
  argsHint: "[on|off]",
7191
7768
  summary: "toggle read-only plan mode (writes bounced until submit_plan + approval)",
7192
- contextual: "code"
7769
+ contextual: "code",
7770
+ argCompleter: ["on", "off"]
7193
7771
  },
7194
7772
  {
7195
7773
  cmd: "apply-plan",
@@ -7204,6 +7782,27 @@ function suggestSlashCommands(prefix, codeMode = false) {
7204
7782
  return c.cmd.startsWith(p);
7205
7783
  });
7206
7784
  }
7785
+ function detectSlashArgContext(input, codeMode = false) {
7786
+ const m = /^\/(\S+) ([\s\S]*)$/.exec(input);
7787
+ if (!m) return null;
7788
+ const cmdName = m[1].toLowerCase();
7789
+ const tail = m[2] ?? "";
7790
+ const spec = SLASH_COMMANDS.find(
7791
+ (s) => s.cmd === cmdName && (s.contextual !== "code" || codeMode)
7792
+ );
7793
+ if (!spec) return null;
7794
+ const hasInternalSpace = /\s/.test(tail);
7795
+ const partialOffset = input.length - tail.length;
7796
+ if (hasInternalSpace) {
7797
+ return { spec, partial: tail, partialOffset, kind: "hint" };
7798
+ }
7799
+ return {
7800
+ spec,
7801
+ partial: tail,
7802
+ partialOffset,
7803
+ kind: spec.argCompleter ? "picker" : "hint"
7804
+ };
7805
+ }
7207
7806
  function parseSlash(text) {
7208
7807
  if (!text.startsWith("/")) return null;
7209
7808
  const parts = text.slice(1).trim().split(/\s+/);
@@ -7229,18 +7828,55 @@ function handleSlash(cmd, args, loop, ctx = {}) {
7229
7828
  info: `\u25B8 new conversation \u2014 dropped ${dropped} message(s) from context. Same session, fresh slate.`
7230
7829
  };
7231
7830
  }
7831
+ case "keys":
7832
+ return {
7833
+ info: [
7834
+ "Keyboard & prompt shortcuts:",
7835
+ "",
7836
+ " Enter submit the current prompt",
7837
+ " Shift+Enter / Ctrl+J insert a newline (multi-line prompt)",
7838
+ " \\<Enter> bash-style line continuation",
7839
+ " \u2190 \u2192 \u2191 \u2193 move cursor / recall history when buffer empty",
7840
+ " Ctrl+A / Ctrl+E jump to start / end of the current line",
7841
+ " Backspace delete left; Delete delete under cursor",
7842
+ " Esc abort the in-flight turn",
7843
+ " y / n accept / reject pending edits (code mode)",
7844
+ "",
7845
+ "Prompt prefixes:",
7846
+ " /<name> slash command; Tab/Enter picks from the suggestion list",
7847
+ " @<path> inline a file under [Referenced files] (code mode).",
7848
+ " Trailing `@\u2026` opens a file picker; \u2191/\u2193 navigate, Tab/Enter pick.",
7849
+ " !<cmd> run <cmd> as shell in the sandbox root; output goes into context",
7850
+ " so the model sees it next turn. No allowlist gate.",
7851
+ "",
7852
+ "Pickers (slash + @-mention):",
7853
+ " \u2191 / \u2193 navigate the suggestion list",
7854
+ " Tab insert the highlighted item without submitting",
7855
+ " Enter insert and (slash) run it, (@) keep editing",
7856
+ "",
7857
+ "MCP exploration:",
7858
+ " /mcp servers + tool/resource/prompt counts",
7859
+ " /resource [uri] browse & read resources exposed by your MCP servers",
7860
+ " /prompt [name] browse & fetch prompts exposed by your MCP servers",
7861
+ "",
7862
+ "Useful slashes: /help \xB7 /context \xB7 /stats \xB7 /compact \xB7 /new \xB7 /exit"
7863
+ ].join("\n")
7864
+ };
7232
7865
  case "help":
7233
7866
  case "?":
7234
7867
  return {
7235
7868
  info: [
7236
7869
  "Commands:",
7237
7870
  " /help this message",
7871
+ " /keys keyboard shortcuts + prompt prefixes (!, @, /)",
7238
7872
  " /status show current settings",
7239
7873
  " /preset <fast|smart|max> one-tap presets \u2014 see below",
7240
7874
  " /model <id> deepseek-chat or deepseek-reasoner",
7241
7875
  " /harvest [on|off] Pillar 2: structured plan-state extraction",
7242
7876
  " /branch <N|off> run N parallel samples (N>=2), pick most confident",
7243
7877
  " /mcp list MCP servers + tools attached to this session",
7878
+ " /resource [uri] browse + read MCP resources (no arg \u2192 list URIs; <uri> \u2192 fetch)",
7879
+ " /prompt [name] browse + fetch MCP prompts (no arg \u2192 list names; <name> \u2192 render)",
7244
7880
  " /setup (exit + reconfigure) \u2192 run `reasonix setup`",
7245
7881
  " /compact [tokens] shrink large tool results in history (default 4000 tokens/result)",
7246
7882
  " /think dump the most recent turn's full R1 reasoning (reasoner only)",
@@ -7293,6 +7929,8 @@ function handleSlash(cmd, args, loop, ctx = {}) {
7293
7929
  }
7294
7930
  if (servers.length > 0) {
7295
7931
  const lines2 = [];
7932
+ let anyResources = false;
7933
+ let anyPrompts = false;
7296
7934
  for (const s of servers) {
7297
7935
  const { report } = s;
7298
7936
  const serverName = report.serverInfo.name || "(unknown)";
@@ -7301,11 +7939,20 @@ function handleSlash(cmd, args, loop, ctx = {}) {
7301
7939
  lines2.push(` tools ${s.toolCount}`);
7302
7940
  appendSection(lines2, "resources", report.resources);
7303
7941
  appendSection(lines2, "prompts ", report.prompts);
7942
+ if (report.resources.supported && report.resources.items.length > 0) anyResources = true;
7943
+ if (report.prompts.supported && report.prompts.items.length > 0) anyPrompts = true;
7304
7944
  lines2.push("");
7305
7945
  }
7306
- lines2.push(
7307
- "Chat mode consumes tools today; resources+prompts are surfaced here for awareness."
7308
- );
7946
+ if (anyResources || anyPrompts) {
7947
+ const hints = [];
7948
+ if (anyResources) hints.push("`/resource` to browse+read");
7949
+ if (anyPrompts) hints.push("`/prompt` to browse+fetch");
7950
+ lines2.push(hints.join(" \xB7 "));
7951
+ } else {
7952
+ lines2.push(
7953
+ "Chat mode consumes tools today; resources+prompts are surfaced here for awareness."
7954
+ );
7955
+ }
7309
7956
  lines2.push(
7310
7957
  "Full catalog: `reasonix mcp list` \xB7 deeper diagnosis: `reasonix mcp inspect <spec>`."
7311
7958
  );
@@ -8178,11 +8825,19 @@ function App({
8178
8825
  const atFiles = useMemo(() => {
8179
8826
  if (!codeMode?.rootDir) return [];
8180
8827
  try {
8181
- return listFilesSync(codeMode.rootDir, { maxResults: 500 });
8828
+ return listFilesWithStatsSync(codeMode.rootDir, { maxResults: 500 });
8182
8829
  } catch {
8183
8830
  return [];
8184
8831
  }
8185
8832
  }, [codeMode?.rootDir]);
8833
+ const recentFilesRef = useRef2([]);
8834
+ const recordRecentFile = useCallback((p) => {
8835
+ const list = recentFilesRef.current;
8836
+ const i = list.indexOf(p);
8837
+ if (i >= 0) list.splice(i, 1);
8838
+ list.unshift(p);
8839
+ if (list.length > 20) list.length = 20;
8840
+ }, []);
8186
8841
  const atPicker = useMemo(() => {
8187
8842
  if (!codeMode?.rootDir) return null;
8188
8843
  if (slashMatches !== null) return null;
@@ -8190,7 +8845,10 @@ function App({
8190
8845
  }, [codeMode?.rootDir, input, slashMatches]);
8191
8846
  const atMatches = useMemo(() => {
8192
8847
  if (!atPicker) return null;
8193
- return rankPickerCandidates(atFiles, atPicker.query, 40);
8848
+ return rankPickerCandidates(atFiles, atPicker.query, {
8849
+ limit: 40,
8850
+ recentlyUsed: recentFilesRef.current
8851
+ });
8194
8852
  }, [atPicker, atFiles]);
8195
8853
  useEffect2(() => {
8196
8854
  setAtSelected((prev) => {
@@ -8207,6 +8865,67 @@ function App({
8207
8865
  },
8208
8866
  [atPicker, input]
8209
8867
  );
8868
+ const [slashArgSelected, setSlashArgSelected] = useState5(0);
8869
+ const slashArgContext = useMemo(() => {
8870
+ if (!input.startsWith("/")) return null;
8871
+ if (slashMatches !== null) return null;
8872
+ return detectSlashArgContext(input, !!codeMode);
8873
+ }, [input, slashMatches, codeMode]);
8874
+ const slashArgMatches = useMemo(() => {
8875
+ if (!slashArgContext || slashArgContext.kind !== "picker") return null;
8876
+ const completer = slashArgContext.spec.argCompleter;
8877
+ const partial = slashArgContext.partial;
8878
+ const needle = partial.toLowerCase();
8879
+ if (Array.isArray(completer)) {
8880
+ if (partial && completer.some((v) => v.toLowerCase() === needle)) return null;
8881
+ if (!partial) return completer.slice();
8882
+ return completer.filter((v) => v.toLowerCase().startsWith(needle));
8883
+ }
8884
+ if (completer === "models") {
8885
+ const all = models ?? [];
8886
+ if (partial && all.some((m) => m.toLowerCase() === needle)) return null;
8887
+ if (!partial) return all.slice(0, 40);
8888
+ return all.filter((m) => m.toLowerCase().includes(needle)).slice(0, 40);
8889
+ }
8890
+ if (completer === "mcp-resources") {
8891
+ const uris = [];
8892
+ const servers = mcpServers ?? [];
8893
+ for (const s of servers) {
8894
+ if (!s.report.resources.supported) continue;
8895
+ for (const r of s.report.resources.items) uris.push(r.uri);
8896
+ }
8897
+ if (partial && uris.some((u) => u.toLowerCase() === needle)) return null;
8898
+ if (!partial) return uris.slice(0, 40);
8899
+ return uris.filter((u) => u.toLowerCase().includes(needle)).slice(0, 40);
8900
+ }
8901
+ if (completer === "mcp-prompts") {
8902
+ const names = [];
8903
+ const servers = mcpServers ?? [];
8904
+ for (const s of servers) {
8905
+ if (!s.report.prompts.supported) continue;
8906
+ for (const p of s.report.prompts.items) names.push(p.name);
8907
+ }
8908
+ if (partial && names.some((n) => n.toLowerCase() === needle)) return null;
8909
+ if (!partial) return names.slice(0, 40);
8910
+ return names.filter((n) => n.toLowerCase().includes(needle)).slice(0, 40);
8911
+ }
8912
+ return null;
8913
+ }, [slashArgContext, models, mcpServers]);
8914
+ useEffect2(() => {
8915
+ setSlashArgSelected((prev) => {
8916
+ if (!slashArgMatches || slashArgMatches.length === 0) return 0;
8917
+ if (prev >= slashArgMatches.length) return slashArgMatches.length - 1;
8918
+ return prev;
8919
+ });
8920
+ }, [slashArgMatches]);
8921
+ const pickSlashArg = useCallback(
8922
+ (chosen) => {
8923
+ if (!slashArgContext) return;
8924
+ const before = input.slice(0, slashArgContext.partialOffset);
8925
+ setInput(`${before}${chosen}`);
8926
+ },
8927
+ [slashArgContext, input]
8928
+ );
8210
8929
  const loopRef = useRef2(null);
8211
8930
  const subagentSinkRef = useRef2({ current: null });
8212
8931
  const loop = useMemo(() => {
@@ -8226,7 +8945,10 @@ function App({
8226
8945
  // Per-skill model override (frontmatter `model: ...`),
8227
8946
  // else falls through to spawnSubagent's default.
8228
8947
  model: skill.model,
8229
- sink: subagentSinkRef.current
8948
+ sink: subagentSinkRef.current,
8949
+ // Stamped onto every event so the TUI sink + usage log can
8950
+ // attribute the run to a skill without extra bookkeeping.
8951
+ skillName: skill.name
8230
8952
  });
8231
8953
  return formatSubagentResult(result);
8232
8954
  }
@@ -8320,7 +9042,8 @@ function App({
8320
9042
  }
8321
9043
  setSubagentActivity(null);
8322
9044
  const seconds = ((ev.elapsedMs ?? 0) / 1e3).toFixed(1);
8323
- const summary2 = ev.error ? `\u232C subagent "${ev.task}" failed after ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \u2014 ${ev.error}` : `\u232C subagent "${ev.task}" done in ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \xB7 ${ev.turns ?? 0} turn(s)`;
9045
+ const costTail = ev.costUsd !== void 0 && ev.costUsd > 0 ? ` \xB7 $${ev.costUsd.toFixed(4)}` : "";
9046
+ const summary2 = ev.error ? `\u232C subagent "${ev.task}" failed after ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \u2014 ${ev.error}` : `\u232C subagent "${ev.task}" done in ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \xB7 ${ev.turns ?? 0} turn(s)${costTail}`;
8324
9047
  setHistorical((prev) => [
8325
9048
  ...prev,
8326
9049
  {
@@ -8329,11 +9052,25 @@ function App({
8329
9052
  text: summary2
8330
9053
  }
8331
9054
  ]);
9055
+ if (!ev.error && ev.usage && ev.model) {
9056
+ appendUsage({
9057
+ session: session ?? null,
9058
+ model: ev.model,
9059
+ usage: ev.usage,
9060
+ kind: "subagent",
9061
+ subagent: {
9062
+ skillName: ev.skillName,
9063
+ taskPreview: ev.task.slice(0, 60),
9064
+ toolIters: ev.iter ?? 0,
9065
+ durationMs: ev.elapsedMs ?? 0
9066
+ }
9067
+ });
9068
+ }
8332
9069
  };
8333
9070
  return () => {
8334
9071
  subagentSinkRef.current.current = null;
8335
9072
  };
8336
- }, []);
9073
+ }, [session]);
8337
9074
  const sessionBannerShown = useRef2(false);
8338
9075
  useEffect2(() => {
8339
9076
  if (sessionBannerShown.current) return;
@@ -8366,7 +9103,21 @@ function App({
8366
9103
  }
8367
9104
  ]);
8368
9105
  }
8369
- }, [session, loop]);
9106
+ if (session && codeMode) {
9107
+ const restored = loadPendingEdits(session);
9108
+ if (restored && restored.length > 0) {
9109
+ pendingEdits.current = restored;
9110
+ setHistorical((prev) => [
9111
+ ...prev,
9112
+ {
9113
+ id: `sys-pending-${Date.now()}`,
9114
+ role: "info",
9115
+ text: `\u25B8 restored ${restored.length} pending edit block(s) from an interrupted prior run \u2014 /apply to commit or /discard to drop.`
9116
+ }
9117
+ ]);
9118
+ }
9119
+ }
9120
+ }, [session, loop, codeMode]);
8370
9121
  useInput4((_input, key) => {
8371
9122
  if (key.escape && busy) {
8372
9123
  if (abortedThisTurn.current) return;
@@ -8391,6 +9142,21 @@ function App({
8391
9142
  return;
8392
9143
  }
8393
9144
  }
9145
+ if (slashArgMatches && slashArgMatches.length > 0) {
9146
+ if (key.upArrow) {
9147
+ setSlashArgSelected((i) => Math.max(0, i - 1));
9148
+ return;
9149
+ }
9150
+ if (key.downArrow) {
9151
+ setSlashArgSelected((i) => Math.min(slashArgMatches.length - 1, i + 1));
9152
+ return;
9153
+ }
9154
+ if (key.tab) {
9155
+ const sel = slashArgMatches[slashArgSelected] ?? slashArgMatches[0];
9156
+ if (sel) pickSlashArg(sel);
9157
+ return;
9158
+ }
9159
+ }
8394
9160
  if (slashMatches && slashMatches.length > 0) {
8395
9161
  if (key.upArrow) {
8396
9162
  setSlashSelected((i) => Math.max(0, i - 1));
@@ -8445,14 +9211,16 @@ function App({
8445
9211
  const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
8446
9212
  if (anyApplied) lastEditSnapshots.current = snaps;
8447
9213
  pendingEdits.current = [];
9214
+ clearPendingEdits(session ?? null);
8448
9215
  return formatEditResults(results);
8449
- }, [codeMode]);
9216
+ }, [codeMode, session]);
8450
9217
  const codeDiscard = useCallback(() => {
8451
9218
  const count = pendingEdits.current.length;
8452
9219
  if (count === 0) return "nothing pending to discard.";
8453
9220
  pendingEdits.current = [];
9221
+ clearPendingEdits(session ?? null);
8454
9222
  return `\u25B8 discarded ${count} pending edit block(s). Nothing was written to disk.`;
8455
- }, []);
9223
+ }, [session]);
8456
9224
  const prefixHash = loop.prefix.fingerprint;
8457
9225
  const writeTranscript = useCallback(
8458
9226
  (ev) => {
@@ -8483,6 +9251,13 @@ function App({
8483
9251
  return;
8484
9252
  }
8485
9253
  }
9254
+ if (slashArgMatches && slashArgMatches.length > 0 && slashArgContext) {
9255
+ const sel = slashArgMatches[slashArgSelected] ?? slashArgMatches[0];
9256
+ if (sel) {
9257
+ pickSlashArg(sel);
9258
+ return;
9259
+ }
9260
+ }
8486
9261
  if (text.startsWith("/") && !text.includes(" ")) {
8487
9262
  const typed = text.slice(1).toLowerCase();
8488
9263
  const matches = suggestSlashCommands(typed, !!codeMode);
@@ -8525,7 +9300,7 @@ function App({
8525
9300
  ...prev,
8526
9301
  { id: `bang-o-${Date.now()}`, role: "info", text: formatted }
8527
9302
  ]);
8528
- loop.log.append({
9303
+ loop.appendAndPersist({
8529
9304
  role: "user",
8530
9305
  content: formatBangUserMessage(bangCmd, formatted)
8531
9306
  });
@@ -8543,6 +9318,18 @@ function App({
8543
9318
  }
8544
9319
  return;
8545
9320
  }
9321
+ const mcpBrowseMatch = /^\/(resource|prompt)(?:\s+([\s\S]*))?$/.exec(text);
9322
+ if (mcpBrowseMatch) {
9323
+ const kind = mcpBrowseMatch[1];
9324
+ const arg = mcpBrowseMatch[2]?.trim() ?? "";
9325
+ promptHistory.current.push(text);
9326
+ setHistorical((prev) => [
9327
+ ...prev,
9328
+ { id: `mcp-u-${Date.now()}`, role: "user", text, leadSeparator: prev.length > 0 }
9329
+ ]);
9330
+ await handleMcpBrowseSlash(kind, arg, mcpServers ?? [], setHistorical);
9331
+ return;
9332
+ }
8546
9333
  const slash = parseSlash(text);
8547
9334
  if (slash) {
8548
9335
  const result = handleSlash(slash.cmd, slash.args, loop, {
@@ -8591,10 +9378,18 @@ function App({
8591
9378
  text: result.info
8592
9379
  }
8593
9380
  ]);
9381
+ if (codeMode) {
9382
+ pendingEdits.current = [];
9383
+ clearPendingEdits(session ?? null);
9384
+ }
8594
9385
  return;
8595
9386
  }
8596
9387
  if (result.clear) {
8597
9388
  setHistorical([]);
9389
+ if (codeMode) {
9390
+ pendingEdits.current = [];
9391
+ clearPendingEdits(session ?? null);
9392
+ }
8598
9393
  return;
8599
9394
  }
8600
9395
  if (result.info) {
@@ -8632,13 +9427,19 @@ function App({
8632
9427
  if (promptReport.blocked) return;
8633
9428
  }
8634
9429
  promptHistory.current.push(text);
9430
+ const pasteDisplay = formatLongPaste(text);
8635
9431
  setHistorical((prev) => [
8636
9432
  ...prev,
8637
9433
  // `leadSeparator`: thin rule above this user turn when history
8638
9434
  // isn't empty — visual pacing for multi-turn sessions. First
8639
9435
  // user message leaves it off so the UI doesn't open with a
8640
9436
  // dangling divider.
8641
- { id: `u-${Date.now()}`, role: "user", text, leadSeparator: prev.length > 0 }
9437
+ {
9438
+ id: `u-${Date.now()}`,
9439
+ role: "user",
9440
+ text: pasteDisplay.displayText,
9441
+ leadSeparator: prev.length > 0
9442
+ }
8642
9443
  ]);
8643
9444
  const assistantId = `a-${Date.now()}`;
8644
9445
  const streamRef = { id: assistantId, text: "", reasoning: "" };
@@ -8703,7 +9504,9 @@ function App({
8703
9504
  if (ev.toolName) {
8704
9505
  toolCallBuildBuf.current = {
8705
9506
  name: ev.toolName,
8706
- chars: ev.toolCallArgsChars ?? 0
9507
+ chars: ev.toolCallArgsChars ?? 0,
9508
+ index: ev.toolCallIndex,
9509
+ readyCount: ev.toolCallReadyCount
8707
9510
  };
8708
9511
  }
8709
9512
  } else if (ev.role === "branch_start") {
@@ -8762,6 +9565,7 @@ function App({
8762
9565
  const blocks = parseEditBlocks(finalText);
8763
9566
  if (blocks.length > 0) {
8764
9567
  pendingEdits.current = blocks;
9568
+ savePendingEdits(session ?? null, blocks);
8765
9569
  setHistorical((prev) => [
8766
9570
  ...prev,
8767
9571
  {
@@ -8775,6 +9579,19 @@ function App({
8775
9579
  } else if (ev.role === "tool_start") {
8776
9580
  setOngoingTool({ name: ev.toolName ?? "?", args: ev.toolArgs });
8777
9581
  setToolProgress(null);
9582
+ if (codeMode && ev.toolArgs) {
9583
+ try {
9584
+ const parsed = JSON.parse(ev.toolArgs);
9585
+ for (const k of ["path", "file_path", "file"]) {
9586
+ const v = parsed[k];
9587
+ if (typeof v === "string" && v.trim()) {
9588
+ recordRecentFile(v.trim());
9589
+ break;
9590
+ }
9591
+ }
9592
+ } catch {
9593
+ }
9594
+ }
8778
9595
  } else if (ev.role === "tool") {
8779
9596
  flush();
8780
9597
  setOngoingTool(null);
@@ -8884,6 +9701,11 @@ function App({
8884
9701
  atPicker,
8885
9702
  atSelected,
8886
9703
  pickAtMention,
9704
+ recordRecentFile,
9705
+ slashArgMatches,
9706
+ slashArgContext,
9707
+ slashArgSelected,
9708
+ pickSlashArg,
8887
9709
  togglePlanMode,
8888
9710
  writeTranscript
8889
9711
  ]
@@ -8981,6 +9803,14 @@ ${body}`;
8981
9803
  },
8982
9804
  [pendingPlan, togglePlanMode, busy, loop, handleSubmit]
8983
9805
  );
9806
+ const handlePlanConfirmRef = useRef2(handlePlanConfirm);
9807
+ useEffect2(() => {
9808
+ handlePlanConfirmRef.current = handlePlanConfirm;
9809
+ }, [handlePlanConfirm]);
9810
+ const stableHandlePlanConfirm = useCallback(
9811
+ async (choice) => handlePlanConfirmRef.current(choice),
9812
+ []
9813
+ );
8984
9814
  const handleStagedInputSubmit = useCallback(
8985
9815
  async (feedback) => {
8986
9816
  const staged = stagedInput;
@@ -9034,7 +9864,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
9034
9864
  if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
9035
9865
  setStagedInput(null);
9036
9866
  }, [stagedInput]);
9037
- return /* @__PURE__ */ React13.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(
9867
+ return /* @__PURE__ */ React14.createElement(TickerProvider, { disabled: PLAIN_UI || !!pendingPlan || !!pendingShell }, /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React14.createElement(
9038
9868
  StatsPanel,
9039
9869
  {
9040
9870
  summary,
@@ -9047,21 +9877,28 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
9047
9877
  busy,
9048
9878
  updateAvailable
9049
9879
  }
9050
- ), /* @__PURE__ */ React13.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React13.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React13.createElement(Box12, { marginY: 1 }, /* @__PURE__ */ React13.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React13.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && subagentActivity ? /* @__PURE__ */ React13.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !ongoingTool && statusLine ? /* @__PURE__ */ React13.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React13.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React13.createElement(
9880
+ ), /* @__PURE__ */ React14.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React14.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React14.createElement(Box13, { marginY: 1 }, /* @__PURE__ */ React14.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React14.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && subagentActivity ? /* @__PURE__ */ React14.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !ongoingTool && statusLine ? /* @__PURE__ */ React14.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React14.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React14.createElement(
9051
9881
  PlanRefineInput,
9052
9882
  {
9053
9883
  mode: stagedInput.mode,
9054
9884
  onSubmit: handleStagedInputSubmit,
9055
9885
  onCancel: handleStagedInputCancel
9056
9886
  }
9057
- ) : pendingPlan ? /* @__PURE__ */ React13.createElement(PlanConfirm, { plan: pendingPlan, onChoose: handlePlanConfirm, projectRoot: hookCwd }) : pendingShell ? /* @__PURE__ */ React13.createElement(
9887
+ ) : pendingPlan ? /* @__PURE__ */ React14.createElement(
9888
+ PlanConfirm,
9889
+ {
9890
+ plan: pendingPlan,
9891
+ onChoose: stableHandlePlanConfirm,
9892
+ projectRoot: hookCwd
9893
+ }
9894
+ ) : pendingShell ? /* @__PURE__ */ React14.createElement(
9058
9895
  ShellConfirm,
9059
9896
  {
9060
9897
  command: pendingShell,
9061
9898
  allowPrefix: derivePrefix(pendingShell),
9062
9899
  onChoose: handleShellConfirm
9063
9900
  }
9064
- ) : /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(
9901
+ ) : /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
9065
9902
  PromptInput,
9066
9903
  {
9067
9904
  value: input,
@@ -9069,27 +9906,36 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
9069
9906
  onSubmit: handleSubmit,
9070
9907
  disabled: busy
9071
9908
  }
9072
- ), /* @__PURE__ */ React13.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React13.createElement(
9909
+ ), /* @__PURE__ */ React14.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React14.createElement(
9073
9910
  AtMentionSuggestions,
9074
9911
  {
9075
9912
  matches: atMatches,
9076
9913
  selectedIndex: atSelected,
9077
9914
  query: atPicker?.query ?? ""
9078
9915
  }
9079
- ))));
9916
+ ), slashArgContext ? /* @__PURE__ */ React14.createElement(
9917
+ SlashArgPicker,
9918
+ {
9919
+ matches: slashArgMatches,
9920
+ selectedIndex: slashArgSelected,
9921
+ spec: slashArgContext.spec,
9922
+ kind: slashArgContext.kind,
9923
+ partial: slashArgContext.partial
9924
+ }
9925
+ ) : null)));
9080
9926
  }
9081
9927
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9082
9928
  function StatusRow({ text }) {
9083
9929
  const tick = useTick();
9084
9930
  const elapsed = useElapsedSeconds();
9085
- return /* @__PURE__ */ React13.createElement(Box12, { marginY: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` ${elapsed}s`));
9931
+ return /* @__PURE__ */ React14.createElement(Box13, { marginY: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React14.createElement(Text13, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` ${elapsed}s`));
9086
9932
  }
9087
9933
  function SubagentRow({
9088
9934
  activity
9089
9935
  }) {
9090
9936
  const tick = useTick();
9091
9937
  const seconds = (activity.elapsedMs / 1e3).toFixed(1);
9092
- return /* @__PURE__ */ React13.createElement(Box12, { paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, ` \u232C subagent: ${activity.task}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
9938
+ return /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React14.createElement(Text13, { color: "magenta" }, ` \u232C subagent: ${activity.task}`), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
9093
9939
  }
9094
9940
  function OngoingToolRow({
9095
9941
  tool,
@@ -9098,7 +9944,7 @@ function OngoingToolRow({
9098
9944
  const tick = useTick();
9099
9945
  const elapsed = useElapsedSeconds();
9100
9946
  const summary = summarizeToolArgs(tool.name, tool.args);
9101
- return /* @__PURE__ */ React13.createElement(Box12, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React13.createElement(Box12, { paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React13.createElement(Box12, { paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, summary)) : null);
9947
+ return /* @__PURE__ */ React14.createElement(Box13, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Text13, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, summary)) : null);
9102
9948
  }
9103
9949
  function renderProgressLine(p) {
9104
9950
  const msg = p.message ? ` ${p.message}` : "";
@@ -9164,18 +10010,9 @@ function formatEditResults(results) {
9164
10010
  return [header2, ...lines].join("\n");
9165
10011
  }
9166
10012
  function formatPendingPreview(blocks) {
9167
- const lines = blocks.map((b) => {
9168
- const removed = b.search === "" ? 0 : countLines2(b.search);
9169
- const added = countLines2(b.replace);
9170
- const tag = b.search === "" ? "NEW " : " ";
9171
- return ` ${tag}${b.path} (-${removed} +${added} lines)`;
9172
- });
9173
10013
  const header2 = `\u25B8 ${blocks.length} pending edit block(s) \u2014 /apply (or y) to commit \xB7 /discard (or n) to drop`;
9174
- return [header2, ...lines].join("\n");
9175
- }
9176
- function countLines2(s) {
9177
- if (s.length === 0) return 0;
9178
- return (s.match(/\n/g)?.length ?? 0) + 1;
10014
+ const diffLines = formatAllBlockDiffs(blocks);
10015
+ return [header2, ...diffLines].join("\n");
9179
10016
  }
9180
10017
  function formatUndoResults(results) {
9181
10018
  const lines = results.map((r) => {
@@ -9194,15 +10031,15 @@ function describeRepair(repair) {
9194
10031
  }
9195
10032
 
9196
10033
  // src/cli/ui/SessionPicker.tsx
9197
- import { Box as Box13, Text as Text13 } from "ink";
9198
- import React14 from "react";
10034
+ import { Box as Box14, Text as Text14 } from "ink";
10035
+ import React15 from "react";
9199
10036
  function SessionPicker({
9200
10037
  sessionName,
9201
10038
  messageCount,
9202
10039
  lastActive,
9203
10040
  onChoose
9204
10041
  }) {
9205
- return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React14.createElement(
10042
+ return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React15.createElement(Box14, { marginBottom: 1 }, /* @__PURE__ */ React15.createElement(Text14, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React15.createElement(
9206
10043
  SingleSelect,
9207
10044
  {
9208
10045
  initialValue: "new",
@@ -9225,7 +10062,7 @@ function SessionPicker({
9225
10062
  ],
9226
10063
  onSubmit: (v) => onChoose(v)
9227
10064
  }
9228
- ), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
10065
+ ), /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
9229
10066
  }
9230
10067
  function relativeTime(date) {
9231
10068
  const ms = Date.now() - date.getTime();
@@ -9241,9 +10078,9 @@ function relativeTime(date) {
9241
10078
  }
9242
10079
 
9243
10080
  // src/cli/ui/Setup.tsx
9244
- import { Box as Box14, Text as Text14, useApp as useApp2 } from "ink";
10081
+ import { Box as Box15, Text as Text15, useApp as useApp2 } from "ink";
9245
10082
  import TextInput from "ink-text-input";
9246
- import React15, { useState as useState6 } from "react";
10083
+ import React16, { useState as useState6 } from "react";
9247
10084
  function Setup({ onReady }) {
9248
10085
  const [value, setValue] = useState6("");
9249
10086
  const [error, setError] = useState6(null);
@@ -9267,7 +10104,7 @@ function Setup({ onReady }) {
9267
10104
  }
9268
10105
  onReady(trimmed);
9269
10106
  };
9270
- return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React15.createElement(Text14, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text14, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text14, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React15.createElement(
10107
+ return /* @__PURE__ */ React16.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React16.createElement(Text15, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React16.createElement(
9271
10108
  TextInput,
9272
10109
  {
9273
10110
  value,
@@ -9276,7 +10113,7 @@ function Setup({ onReady }) {
9276
10113
  mask: "\u2022",
9277
10114
  placeholder: "sk-..."
9278
10115
  }
9279
- )), error ? /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text14, { color: "red" }, error)) : value ? /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, "(Type /exit to abort.)")));
10116
+ )), error ? /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, { color: "red" }, error)) : value ? /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "(Type /exit to abort.)")));
9280
10117
  }
9281
10118
 
9282
10119
  // src/cli/commands/chat.tsx
@@ -9292,7 +10129,7 @@ function Root({
9292
10129
  const [key, setKey] = useState7(initialKey);
9293
10130
  const [pending, setPending] = useState7(sessionPreview);
9294
10131
  if (!key) {
9295
- return /* @__PURE__ */ React16.createElement(
10132
+ return /* @__PURE__ */ React17.createElement(
9296
10133
  Setup,
9297
10134
  {
9298
10135
  onReady: (k) => {
@@ -9304,7 +10141,7 @@ function Root({
9304
10141
  }
9305
10142
  process.env.DEEPSEEK_API_KEY = key;
9306
10143
  if (pending && appProps.session) {
9307
- return /* @__PURE__ */ React16.createElement(
10144
+ return /* @__PURE__ */ React17.createElement(
9308
10145
  SessionPicker,
9309
10146
  {
9310
10147
  sessionName: appProps.session,
@@ -9319,7 +10156,7 @@ function Root({
9319
10156
  }
9320
10157
  );
9321
10158
  }
9322
- return /* @__PURE__ */ React16.createElement(
10159
+ return /* @__PURE__ */ React17.createElement(
9323
10160
  App,
9324
10161
  {
9325
10162
  model: appProps.model,
@@ -9385,7 +10222,8 @@ async function chatCommand(opts) {
9385
10222
  label,
9386
10223
  spec: raw,
9387
10224
  toolCount: bridge.registeredNames.length,
9388
- report
10225
+ report,
10226
+ client: mcp2
9389
10227
  });
9390
10228
  } catch (err) {
9391
10229
  const reason = err.message;
@@ -9415,14 +10253,14 @@ async function chatCommand(opts) {
9415
10253
  const prior = loadSessionMessages(opts.session);
9416
10254
  if (prior.length > 0) {
9417
10255
  const p = sessionPath(opts.session);
9418
- const mtime = existsSync10(p) ? statSync6(p).mtime : /* @__PURE__ */ new Date();
10256
+ const mtime = existsSync11(p) ? statSync6(p).mtime : /* @__PURE__ */ new Date();
9419
10257
  sessionPreview = { messageCount: prior.length, lastActive: mtime };
9420
10258
  }
9421
10259
  } else if (opts.session && opts.forceNew) {
9422
10260
  rewriteSession(opts.session, []);
9423
10261
  }
9424
10262
  const { waitUntilExit } = render(
9425
- /* @__PURE__ */ React16.createElement(
10263
+ /* @__PURE__ */ React17.createElement(
9426
10264
  Root,
9427
10265
  {
9428
10266
  initialKey,
@@ -9469,7 +10307,7 @@ async function codeCommand(opts = {}) {
9469
10307
  `
9470
10308
  );
9471
10309
  await chatCommand({
9472
- model: opts.model ?? "deepseek-reasoner",
10310
+ model: opts.model ?? "deepseek-v4-pro",
9473
10311
  harvest: opts.harvest ?? false,
9474
10312
  system: codeSystemPrompt2(rootDir),
9475
10313
  transcript: opts.transcript,
@@ -9482,37 +10320,37 @@ async function codeCommand(opts = {}) {
9482
10320
  }
9483
10321
 
9484
10322
  // src/cli/commands/diff.ts
9485
- import { writeFileSync as writeFileSync5 } from "fs";
10323
+ import { writeFileSync as writeFileSync6 } from "fs";
9486
10324
  import { basename as basename2 } from "path";
9487
10325
  import { render as render2 } from "ink";
9488
- import React19 from "react";
10326
+ import React20 from "react";
9489
10327
 
9490
10328
  // src/cli/ui/DiffApp.tsx
9491
- import { Box as Box16, Static as Static2, Text as Text16, useApp as useApp3, useInput as useInput5 } from "ink";
9492
- import React18, { useState as useState8 } from "react";
10329
+ import { Box as Box17, Static as Static2, Text as Text17, useApp as useApp3, useInput as useInput5 } from "ink";
10330
+ import React19, { useState as useState8 } from "react";
9493
10331
 
9494
10332
  // src/cli/ui/RecordView.tsx
9495
- import { Box as Box15, Text as Text15 } from "ink";
9496
- import React17 from "react";
10333
+ import { Box as Box16, Text as Text16 } from "ink";
10334
+ import React18 from "react";
9497
10335
  function RecordView({ rec, compact = false }) {
9498
10336
  const toolArgsMax = compact ? 120 : 200;
9499
10337
  const toolContentMax = compact ? 200 : 400;
9500
10338
  if (rec.role === "user") {
9501
- return /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React17.createElement(Text15, null, rec.content));
10339
+ return /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React18.createElement(Text16, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React18.createElement(Text16, null, rec.content));
9502
10340
  }
9503
10341
  if (rec.role === "assistant_final") {
9504
- return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React17.createElement(Box15, null, /* @__PURE__ */ React17.createElement(Text15, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React17.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React17.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React17.createElement(Text15, null, rec.content) : /* @__PURE__ */ React17.createElement(Text15, { dimColor: true, italic: true }, "(tool-call response only)"));
10342
+ return /* @__PURE__ */ React18.createElement(Box16, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React18.createElement(Box16, null, /* @__PURE__ */ React18.createElement(Text16, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React18.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React18.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React18.createElement(Text16, null, rec.content) : /* @__PURE__ */ React18.createElement(Text16, { dimColor: true, italic: true }, "(tool-call response only)"));
9505
10343
  }
9506
10344
  if (rec.role === "tool") {
9507
- return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
10345
+ return /* @__PURE__ */ React18.createElement(Box16, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React18.createElement(Text16, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
9508
10346
  }
9509
10347
  if (rec.role === "error") {
9510
- return /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React17.createElement(Text15, { color: "red" }, rec.error ?? rec.content));
10348
+ return /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React18.createElement(Text16, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React18.createElement(Text16, { color: "red" }, rec.error ?? rec.content));
9511
10349
  }
9512
10350
  if (rec.role === "done" || rec.role === "assistant_delta") {
9513
10351
  return null;
9514
10352
  }
9515
- return /* @__PURE__ */ React17.createElement(Box15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "[", rec.role, "] ", rec.content));
10353
+ return /* @__PURE__ */ React18.createElement(Box16, null, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, "[", rec.role, "] ", rec.content));
9516
10354
  }
9517
10355
  function CacheBadge({ usage }) {
9518
10356
  const hit = usage.prompt_cache_hit_tokens ?? 0;
@@ -9521,7 +10359,7 @@ function CacheBadge({ usage }) {
9521
10359
  if (total === 0) return null;
9522
10360
  const pct2 = hit / total * 100;
9523
10361
  const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
9524
- return /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React17.createElement(Text15, { color }, pct2.toFixed(1), "%"));
10362
+ return /* @__PURE__ */ React18.createElement(Text16, null, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React18.createElement(Text16, { color }, pct2.toFixed(1), "%"));
9525
10363
  }
9526
10364
  function truncate3(s, max) {
9527
10365
  return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
@@ -9555,7 +10393,7 @@ function DiffApp({ report }) {
9555
10393
  }
9556
10394
  });
9557
10395
  const pair = report.pairs[idx];
9558
- return /* @__PURE__ */ React18.createElement(Box16, { flexDirection: "column" }, /* @__PURE__ */ React18.createElement(DiffHeader, { report }), /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React18.createElement(Text16, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React18.createElement(Text16, null, pair ? /* @__PURE__ */ React18.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React18.createElement(Box16, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React18.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React18.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React18.createElement(Text16, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React18.createElement(Text16, null, pair.divergenceNote)) : null, /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "j"), "/", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "k"), "/", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "N"), "/", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "g"), "/", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React18.createElement(Text16, { bold: true }, "q"), " ", "quit")));
10396
+ return /* @__PURE__ */ React19.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(DiffHeader, { report }), /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React19.createElement(Text17, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React19.createElement(Text17, null, pair ? /* @__PURE__ */ React19.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React19.createElement(Box17, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React19.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React19.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React19.createElement(Text17, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React19.createElement(Text17, null, pair.divergenceNote)) : null, /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "j"), "/", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "k"), "/", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "N"), "/", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "g"), "/", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React19.createElement(Text17, { bold: true }, "q"), " ", "quit")));
9559
10397
  }
9560
10398
  function DiffHeader({ report }) {
9561
10399
  const a = report.a;
@@ -9573,15 +10411,15 @@ function DiffHeader({ report }) {
9573
10411
  } else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
9574
10412
  prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
9575
10413
  }
9576
- return /* @__PURE__ */ React18.createElement(Box16, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React18.createElement(Box16, { justifyContent: "space-between" }, /* @__PURE__ */ React18.createElement(Text16, null, /* @__PURE__ */ React18.createElement(Text16, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React18.createElement(Text16, { color: "blue" }, a.label), /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " vs B="), /* @__PURE__ */ React18.createElement(Text16, { color: "magenta" }, b.label)), /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React18.createElement(Text16, null, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, "cache "), /* @__PURE__ */ React18.createElement(Text16, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React18.createElement(Text16, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React18.createElement(Text16, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React18.createElement(Text16, null, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, "cost "), /* @__PURE__ */ React18.createElement(Text16, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React18.createElement(Text16, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React18.createElement(Text16, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React18.createElement(Text16, null, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true }, "model calls "), /* @__PURE__ */ React18.createElement(Text16, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true, italic: true }, prefixLine)) : null);
10414
+ return /* @__PURE__ */ React19.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Box17, { justifyContent: "space-between" }, /* @__PURE__ */ React19.createElement(Text17, null, /* @__PURE__ */ React19.createElement(Text17, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React19.createElement(Text17, { color: "blue" }, a.label), /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " vs B="), /* @__PURE__ */ React19.createElement(Text17, { color: "magenta" }, b.label)), /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React19.createElement(Text17, null, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, "cache "), /* @__PURE__ */ React19.createElement(Text17, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React19.createElement(Text17, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React19.createElement(Text17, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React19.createElement(Text17, null, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, "cost "), /* @__PURE__ */ React19.createElement(Text17, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React19.createElement(Text17, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React19.createElement(Text17, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React19.createElement(Text17, null, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, "model calls "), /* @__PURE__ */ React19.createElement(Text17, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true, italic: true }, prefixLine)) : null);
9577
10415
  }
9578
10416
  function Pane({
9579
10417
  label,
9580
10418
  headerColor,
9581
10419
  records
9582
10420
  }) {
9583
- return /* @__PURE__ */ React18.createElement(
9584
- Box16,
10421
+ return /* @__PURE__ */ React19.createElement(
10422
+ Box17,
9585
10423
  {
9586
10424
  flexDirection: "column",
9587
10425
  flexGrow: 1,
@@ -9589,21 +10427,21 @@ function Pane({
9589
10427
  borderStyle: "single",
9590
10428
  borderColor: headerColor
9591
10429
  },
9592
- /* @__PURE__ */ React18.createElement(Text16, { color: headerColor, bold: true }, label),
9593
- records.length === 0 ? /* @__PURE__ */ React18.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React18.createElement(Text16, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React18.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React18.createElement(RecordView, { key, rec, compact: true }))
10430
+ /* @__PURE__ */ React19.createElement(Text17, { color: headerColor, bold: true }, label),
10431
+ records.length === 0 ? /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React19.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React19.createElement(RecordView, { key, rec, compact: true }))
9594
10432
  );
9595
10433
  }
9596
10434
  function KindBadge({ kind }) {
9597
10435
  if (kind === "match") {
9598
- return /* @__PURE__ */ React18.createElement(Text16, { color: "green" }, "\u2713 match");
10436
+ return /* @__PURE__ */ React19.createElement(Text17, { color: "green" }, "\u2713 match");
9599
10437
  }
9600
10438
  if (kind === "diverge") {
9601
- return /* @__PURE__ */ React18.createElement(Text16, { color: "yellow" }, "\u2605 diverge");
10439
+ return /* @__PURE__ */ React19.createElement(Text17, { color: "yellow" }, "\u2605 diverge");
9602
10440
  }
9603
10441
  if (kind === "only_in_a") {
9604
- return /* @__PURE__ */ React18.createElement(Text16, { color: "blue" }, "\u2190 only in A");
10442
+ return /* @__PURE__ */ React19.createElement(Text17, { color: "blue" }, "\u2190 only in A");
9605
10443
  }
9606
- return /* @__PURE__ */ React18.createElement(Text16, { color: "magenta" }, "\u2192 only in B");
10444
+ return /* @__PURE__ */ React19.createElement(Text17, { color: "magenta" }, "\u2192 only in B");
9607
10445
  }
9608
10446
  function paneRecords(pair, side) {
9609
10447
  if (!pair) return [];
@@ -9628,13 +10466,13 @@ async function diffCommand(opts) {
9628
10466
  if (wantMarkdown) {
9629
10467
  console.log(renderSummaryTable(report));
9630
10468
  const md = renderMarkdown(report);
9631
- writeFileSync5(opts.mdPath, md, "utf8");
10469
+ writeFileSync6(opts.mdPath, md, "utf8");
9632
10470
  console.log(`
9633
10471
  markdown report written to ${opts.mdPath}`);
9634
10472
  return;
9635
10473
  }
9636
10474
  if (wantTui) {
9637
- const { waitUntilExit } = render2(React19.createElement(DiffApp, { report }), {
10475
+ const { waitUntilExit } = render2(React20.createElement(DiffApp, { report }), {
9638
10476
  exitOnCtrlC: true,
9639
10477
  patchConsole: false
9640
10478
  });
@@ -9775,11 +10613,11 @@ function pad2(s, width) {
9775
10613
 
9776
10614
  // src/cli/commands/replay.ts
9777
10615
  import { render as render3 } from "ink";
9778
- import React21 from "react";
10616
+ import React22 from "react";
9779
10617
 
9780
10618
  // src/cli/ui/ReplayApp.tsx
9781
- import { Box as Box17, Static as Static3, Text as Text17, useApp as useApp4, useInput as useInput6 } from "ink";
9782
- import React20, { useMemo as useMemo2, useState as useState9 } from "react";
10619
+ import { Box as Box18, Static as Static3, Text as Text18, useApp as useApp4, useInput as useInput6 } from "ink";
10620
+ import React21, { useMemo as useMemo2, useState as useState9 } from "react";
9783
10621
  function ReplayApp({ meta, pages }) {
9784
10622
  const { exit } = useApp4();
9785
10623
  const maxIdx = Math.max(0, pages.length - 1);
@@ -9818,14 +10656,14 @@ function ReplayApp({ meta, pages }) {
9818
10656
  const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
9819
10657
  const currentPage = pages[idx];
9820
10658
  const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
9821
- return /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(
10659
+ return /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(
9822
10660
  StatsPanel,
9823
10661
  {
9824
10662
  summary,
9825
10663
  model: cumStats.models[0] ?? meta?.model ?? "?",
9826
10664
  prefixHash
9827
10665
  }
9828
- ), /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React20.createElement(Box17, { justifyContent: "space-between" }, /* @__PURE__ */ React20.createElement(Text17, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React20.createElement(Text17, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React20.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React20.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React20.createElement(Text17, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React20.createElement(Box17, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React20.createElement(Text17, { dimColor: true }, /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "j"), "/", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "k"), "/", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React20.createElement(Text17, { bold: true }, "q"), " quit")));
10666
+ ), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React21.createElement(Box18, { justifyContent: "space-between" }, /* @__PURE__ */ React21.createElement(Text18, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React21.createElement(Text18, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React21.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React21.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React21.createElement(Text18, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React21.createElement(Text18, { dimColor: true }, /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "j"), "/", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "k"), "/", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React21.createElement(Text18, { bold: true }, "q"), " quit")));
9829
10667
  }
9830
10668
 
9831
10669
  // src/cli/commands/replay.ts
@@ -9837,7 +10675,7 @@ async function replayCommand(opts) {
9837
10675
  }
9838
10676
  const { parsed } = replayFromFile(opts.path);
9839
10677
  const pages = groupRecordsByTurn(parsed.records);
9840
- const { waitUntilExit } = render3(React21.createElement(ReplayApp, { meta: parsed.meta, pages }), {
10678
+ const { waitUntilExit } = render3(React22.createElement(ReplayApp, { meta: parsed.meta, pages }), {
9841
10679
  exitOnCtrlC: true,
9842
10680
  patchConsole: false
9843
10681
  });
@@ -10142,30 +10980,35 @@ function truncate4(s, max) {
10142
10980
 
10143
10981
  // src/cli/commands/setup.tsx
10144
10982
  import { render as render4 } from "ink";
10145
- import React23 from "react";
10983
+ import React24 from "react";
10146
10984
 
10147
10985
  // src/cli/ui/Wizard.tsx
10148
- import { Box as Box18, Text as Text18, useApp as useApp5, useInput as useInput7 } from "ink";
10986
+ import { Box as Box19, Text as Text19, useApp as useApp5, useInput as useInput7 } from "ink";
10149
10987
  import TextInput2 from "ink-text-input";
10150
- import React22, { useState as useState10 } from "react";
10988
+ import React23, { useState as useState10 } from "react";
10151
10989
 
10152
10990
  // src/cli/ui/presets.ts
10153
10991
  var PRESETS = {
10992
+ // `deepseek-chat` / `deepseek-reasoner` are retained as the fast /
10993
+ // smart models because they're deprecated-but-working compat aliases
10994
+ // for v4-flash's non-thinking and thinking modes respectively. Same
10995
+ // billing, smaller config churn for existing users. `max` promotes
10996
+ // to v4-pro — 12× flash on input/output, reserved for hard tasks.
10154
10997
  fast: { model: "deepseek-chat", harvest: false, branch: 1 },
10155
10998
  smart: { model: "deepseek-reasoner", harvest: true, branch: 1 },
10156
- max: { model: "deepseek-reasoner", harvest: true, branch: 3 }
10999
+ max: { model: "deepseek-v4-pro", harvest: true, branch: 3 }
10157
11000
  };
10158
11001
  var PRESET_DESCRIPTIONS = {
10159
11002
  fast: {
10160
- headline: "deepseek-chat, no reasoning harvest, no branching",
11003
+ headline: "deepseek-chat (= v4-flash non-thinking), no harvest, no branching",
10161
11004
  cost: "~1\xA2 per 100 turns \xB7 default"
10162
11005
  },
10163
11006
  smart: {
10164
- headline: "deepseek-reasoner + Pillar 2 harvest",
10165
- cost: "~10\xD7 cost vs fast \xB7 slower \xB7 better on multi-step tasks"
11007
+ headline: "deepseek-reasoner (= v4-flash thinking) + Pillar 2 harvest",
11008
+ cost: "same price as fast \xB7 slower \xB7 better on multi-step tasks"
10166
11009
  },
10167
11010
  max: {
10168
- headline: "reasoner + harvest + self-consistency (3 branches)",
11011
+ headline: "deepseek-v4-pro + harvest + self-consistency (3 branches)",
10169
11012
  cost: "~30\xD7 cost vs fast \xB7 slowest \xB7 for hard single-shots"
10170
11013
  }
10171
11014
  };
@@ -10186,7 +11029,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10186
11029
  if (key.escape && step !== "saved" && onCancel) onCancel();
10187
11030
  });
10188
11031
  if (step === "apiKey") {
10189
- return /* @__PURE__ */ React22.createElement(
11032
+ return /* @__PURE__ */ React23.createElement(
10190
11033
  ApiKeyStep,
10191
11034
  {
10192
11035
  onSubmit: (key) => {
@@ -10200,7 +11043,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10200
11043
  );
10201
11044
  }
10202
11045
  if (step === "preset") {
10203
- return /* @__PURE__ */ React22.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React22.createElement(
11046
+ return /* @__PURE__ */ React23.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React23.createElement(
10204
11047
  SingleSelect,
10205
11048
  {
10206
11049
  items: presetItems(),
@@ -10210,10 +11053,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10210
11053
  setStep("mcp");
10211
11054
  }
10212
11055
  }
10213
- ), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
11056
+ ), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
10214
11057
  }
10215
11058
  if (step === "mcp") {
10216
- return /* @__PURE__ */ React22.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React22.createElement(
11059
+ return /* @__PURE__ */ React23.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React23.createElement(
10217
11060
  MultiSelect,
10218
11061
  {
10219
11062
  items: mcpItems(),
@@ -10238,7 +11081,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10238
11081
  }
10239
11082
  const currentName = pending[0];
10240
11083
  const entry = CATALOG_BY_NAME.get(currentName);
10241
- return /* @__PURE__ */ React22.createElement(
11084
+ return /* @__PURE__ */ React23.createElement(
10242
11085
  McpArgsStep,
10243
11086
  {
10244
11087
  entry,
@@ -10256,7 +11099,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10256
11099
  }
10257
11100
  if (step === "review") {
10258
11101
  const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
10259
- return /* @__PURE__ */ React22.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React22.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React22.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React22.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React22.createElement(
11102
+ return /* @__PURE__ */ React23.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React23.createElement(Box19, { flexDirection: "column" }, /* @__PURE__ */ React23.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React23.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React23.createElement(
10260
11103
  SummaryLine,
10261
11104
  {
10262
11105
  label: "MCP",
@@ -10264,8 +11107,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10264
11107
  }
10265
11108
  ), specs.map((spec, i) => (
10266
11109
  // biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
10267
- /* @__PURE__ */ React22.createElement(Box18, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "\xB7 ", spec))
10268
- )), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { color: "red" }, error)) : null, /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React22.createElement(
11110
+ /* @__PURE__ */ React23.createElement(Box19, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "\xB7 ", spec))
11111
+ )), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { color: "red" }, error)) : null, /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React23.createElement(
10269
11112
  ReviewConfirm,
10270
11113
  {
10271
11114
  onConfirm: () => {
@@ -10291,7 +11134,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
10291
11134
  }
10292
11135
  ));
10293
11136
  }
10294
- return /* @__PURE__ */ React22.createElement(Box18, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React22.createElement(ExitOnEnter, { onExit: exit }));
11137
+ return /* @__PURE__ */ React23.createElement(Box19, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React23.createElement(Text19, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React23.createElement(ExitOnEnter, { onExit: exit }));
10295
11138
  }
10296
11139
  function ApiKeyStep({
10297
11140
  onSubmit,
@@ -10299,7 +11142,7 @@ function ApiKeyStep({
10299
11142
  onError
10300
11143
  }) {
10301
11144
  const [value, setValue] = useState10("");
10302
- return /* @__PURE__ */ React22.createElement(Box18, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React22.createElement(
11145
+ return /* @__PURE__ */ React23.createElement(Box19, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React23.createElement(Text19, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React23.createElement(
10303
11146
  TextInput2,
10304
11147
  {
10305
11148
  value,
@@ -10316,7 +11159,7 @@ function ApiKeyStep({
10316
11159
  mask: "\u2022",
10317
11160
  placeholder: "sk-..."
10318
11161
  }
10319
- )), error ? /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { color: "red" }, error)) : value ? /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "preview: ", redactKey(value))) : null);
11162
+ )), error ? /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { color: "red" }, error)) : value ? /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "preview: ", redactKey(value))) : null);
10320
11163
  }
10321
11164
  function McpArgsStep({
10322
11165
  entry,
@@ -10325,7 +11168,7 @@ function McpArgsStep({
10325
11168
  onError
10326
11169
  }) {
10327
11170
  const [value, setValue] = useState10("");
10328
- return /* @__PURE__ */ React22.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React22.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React22.createElement(Text18, null, entry.summary), entry.note ? /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, null, "Required parameter: "), /* @__PURE__ */ React22.createElement(Text18, { bold: true }, entry.userArgs)), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React22.createElement(
11171
+ return /* @__PURE__ */ React23.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React23.createElement(Box19, { flexDirection: "column" }, /* @__PURE__ */ React23.createElement(Text19, null, entry.summary), entry.note ? /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, null, "Required parameter: "), /* @__PURE__ */ React23.createElement(Text19, { bold: true }, entry.userArgs)), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React23.createElement(
10329
11172
  TextInput2,
10330
11173
  {
10331
11174
  value,
@@ -10341,7 +11184,7 @@ function McpArgsStep({
10341
11184
  },
10342
11185
  placeholder: placeholderFor(entry)
10343
11186
  }
10344
- )), error ? /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text18, { color: "red" }, error)) : null));
11187
+ )), error ? /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text19, { color: "red" }, error)) : null));
10345
11188
  }
10346
11189
  function ReviewConfirm({ onConfirm }) {
10347
11190
  useInput7((_i, key) => {
@@ -10361,10 +11204,10 @@ function StepFrame({
10361
11204
  total,
10362
11205
  children
10363
11206
  }) {
10364
- return /* @__PURE__ */ React22.createElement(Box18, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React22.createElement(Box18, null, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React22.createElement(Box18, { marginTop: 1, flexDirection: "column" }, children));
11207
+ return /* @__PURE__ */ React23.createElement(Box19, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React23.createElement(Box19, null, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React23.createElement(Text19, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React23.createElement(Box19, { marginTop: 1, flexDirection: "column" }, children));
10365
11208
  }
10366
11209
  function SummaryLine({ label, value }) {
10367
- return /* @__PURE__ */ React22.createElement(Box18, null, /* @__PURE__ */ React22.createElement(Text18, null, label.padEnd(12)), /* @__PURE__ */ React22.createElement(Text18, { bold: true }, value));
11210
+ return /* @__PURE__ */ React23.createElement(Box19, null, /* @__PURE__ */ React23.createElement(Text19, null, label.padEnd(12)), /* @__PURE__ */ React23.createElement(Text19, { bold: true }, value));
10368
11211
  }
10369
11212
  function presetItems() {
10370
11213
  return ["fast", "smart", "max"].map((name) => ({
@@ -10420,7 +11263,7 @@ async function setupCommand(_opts = {}) {
10420
11263
  const existingKey = loadApiKey();
10421
11264
  const existing = readConfig();
10422
11265
  const { waitUntilExit, unmount } = render4(
10423
- /* @__PURE__ */ React23.createElement(
11266
+ /* @__PURE__ */ React24.createElement(
10424
11267
  Wizard,
10425
11268
  {
10426
11269
  existingApiKey: existingKey,