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 +1011 -168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +121 -7
- package/dist/index.js +157 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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-
|
|
1673
|
-
"deepseek-
|
|
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-
|
|
1678
|
-
"deepseek-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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,
|
|
2629
|
-
|
|
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
|
|
2633
|
-
const lower =
|
|
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({
|
|
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) =>
|
|
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
|
|
5739
|
+
import { existsSync as existsSync11, statSync as statSync6 } from "fs";
|
|
5608
5740
|
import { render } from "ink";
|
|
5609
|
-
import
|
|
5741
|
+
import React17, { useState as useState7 } from "react";
|
|
5610
5742
|
|
|
5611
5743
|
// src/cli/ui/App.tsx
|
|
5612
|
-
import { Box as
|
|
5613
|
-
import
|
|
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
|
|
5737
|
-
import { isAbsolute as isAbsolute4, join as
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
6480
|
-
const
|
|
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/
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
6826
|
-
import
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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 (!
|
|
7553
|
+
if (!existsSync10(path)) {
|
|
7028
7554
|
console.error(`no such transcript: ${path}`);
|
|
7029
7555
|
process.exit(1);
|
|
7030
7556
|
}
|
|
7031
|
-
const lines =
|
|
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
|
-
{
|
|
7135
|
-
|
|
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
|
-
|
|
7307
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
{
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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
|
|
9198
|
-
import
|
|
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__ */
|
|
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__ */
|
|
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
|
|
10081
|
+
import { Box as Box15, Text as Text15, useApp as useApp2 } from "ink";
|
|
9245
10082
|
import TextInput from "ink-text-input";
|
|
9246
|
-
import
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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 =
|
|
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__ */
|
|
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-
|
|
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
|
|
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
|
|
10326
|
+
import React20 from "react";
|
|
9489
10327
|
|
|
9490
10328
|
// src/cli/ui/DiffApp.tsx
|
|
9491
|
-
import { Box as
|
|
9492
|
-
import
|
|
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
|
|
9496
|
-
import
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
9584
|
-
|
|
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__ */
|
|
9593
|
-
records.length === 0 ? /* @__PURE__ */
|
|
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__ */
|
|
10436
|
+
return /* @__PURE__ */ React19.createElement(Text17, { color: "green" }, "\u2713 match");
|
|
9599
10437
|
}
|
|
9600
10438
|
if (kind === "diverge") {
|
|
9601
|
-
return /* @__PURE__ */
|
|
10439
|
+
return /* @__PURE__ */ React19.createElement(Text17, { color: "yellow" }, "\u2605 diverge");
|
|
9602
10440
|
}
|
|
9603
10441
|
if (kind === "only_in_a") {
|
|
9604
|
-
return /* @__PURE__ */
|
|
10442
|
+
return /* @__PURE__ */ React19.createElement(Text17, { color: "blue" }, "\u2190 only in A");
|
|
9605
10443
|
}
|
|
9606
|
-
return /* @__PURE__ */
|
|
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
|
-
|
|
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(
|
|
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
|
|
10616
|
+
import React22 from "react";
|
|
9779
10617
|
|
|
9780
10618
|
// src/cli/ui/ReplayApp.tsx
|
|
9781
|
-
import { Box as
|
|
9782
|
-
import
|
|
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__ */
|
|
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__ */
|
|
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(
|
|
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
|
|
10983
|
+
import React24 from "react";
|
|
10146
10984
|
|
|
10147
10985
|
// src/cli/ui/Wizard.tsx
|
|
10148
|
-
import { Box as
|
|
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
|
|
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-
|
|
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
|
|
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: "
|
|
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: "
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
10268
|
-
)), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
11266
|
+
/* @__PURE__ */ React24.createElement(
|
|
10424
11267
|
Wizard,
|
|
10425
11268
|
{
|
|
10426
11269
|
existingApiKey: existingKey,
|