reasonix 0.4.27 → 0.5.0
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/README.md +8 -0
- package/data/deepseek-tokenizer.json.gz +0 -0
- package/dist/cli/index.js +588 -52
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +26 -0
- package/dist/index.js +48 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -233,6 +233,28 @@ var DeepSeekClient = class {
|
|
|
233
233
|
return null;
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Fetch the model catalog DeepSeek currently exposes. Today this is
|
|
238
|
+
* `deepseek-chat` (V3) and `deepseek-reasoner` (R1), but querying is
|
|
239
|
+
* the only way to learn about new ones without a Reasonix release.
|
|
240
|
+
* Returns null on any network/auth failure so callers can degrade
|
|
241
|
+
* gracefully — e.g. `/models` falls back to the hardcoded hint.
|
|
242
|
+
*/
|
|
243
|
+
async listModels(opts = {}) {
|
|
244
|
+
try {
|
|
245
|
+
const resp = await this._fetch(`${this.baseUrl}/models`, {
|
|
246
|
+
method: "GET",
|
|
247
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
248
|
+
signal: opts.signal
|
|
249
|
+
});
|
|
250
|
+
if (!resp.ok) return null;
|
|
251
|
+
const data = await resp.json();
|
|
252
|
+
if (!data || !Array.isArray(data.data)) return null;
|
|
253
|
+
return data;
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
236
258
|
async chat(opts) {
|
|
237
259
|
const ctrl = new AbortController();
|
|
238
260
|
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
@@ -823,7 +845,8 @@ var ToolRegistry = class {
|
|
|
823
845
|
}
|
|
824
846
|
try {
|
|
825
847
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
826
|
-
|
|
848
|
+
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
849
|
+
return opts.maxResultChars ? truncateForModel(str, opts.maxResultChars) : str;
|
|
827
850
|
} catch (err) {
|
|
828
851
|
const e = err;
|
|
829
852
|
if (typeof e.toToolResult === "function") {
|
|
@@ -1393,8 +1416,8 @@ function countLines(path) {
|
|
|
1393
1416
|
|
|
1394
1417
|
// src/telemetry.ts
|
|
1395
1418
|
var DEEPSEEK_PRICING = {
|
|
1396
|
-
"deepseek-chat": { inputCacheHit: 0.
|
|
1397
|
-
"deepseek-reasoner": { inputCacheHit: 0.
|
|
1419
|
+
"deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 },
|
|
1420
|
+
"deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 }
|
|
1398
1421
|
};
|
|
1399
1422
|
var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
|
|
1400
1423
|
var DEEPSEEK_CONTEXT_TOKENS = {
|
|
@@ -1949,6 +1972,24 @@ var CacheFirstLoop = class {
|
|
|
1949
1972
|
return;
|
|
1950
1973
|
}
|
|
1951
1974
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[this.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
1975
|
+
if (usage) {
|
|
1976
|
+
const ratio = usage.promptTokens / ctxMax;
|
|
1977
|
+
if (ratio > 0.6 && ratio <= 0.8) {
|
|
1978
|
+
const before = usage.promptTokens;
|
|
1979
|
+
const soft = this.compact(16e3);
|
|
1980
|
+
if (soft.healedCount > 0) {
|
|
1981
|
+
const approxSaved = Math.round(soft.charsSaved / 4);
|
|
1982
|
+
const after = Math.max(0, before - approxSaved);
|
|
1983
|
+
yield {
|
|
1984
|
+
turn: this._turn,
|
|
1985
|
+
role: "warning",
|
|
1986
|
+
content: `context ${before.toLocaleString()}/${ctxMax.toLocaleString()} (${Math.round(
|
|
1987
|
+
ratio * 100
|
|
1988
|
+
)}%) \u2014 proactively compacted ${soft.healedCount} tool result(s) to 16k, saved ~${approxSaved.toLocaleString()} tokens (now ~${after.toLocaleString()}). Staying ahead of the 80% guard.`
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1952
1993
|
if (usage && usage.promptTokens / ctxMax > 0.8) {
|
|
1953
1994
|
const before = usage.promptTokens;
|
|
1954
1995
|
const compactResult = this.compact(4e3);
|
|
@@ -2011,7 +2052,10 @@ var CacheFirstLoop = class {
|
|
|
2011
2052
|
result = `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
2012
2053
|
${reason}`;
|
|
2013
2054
|
} else {
|
|
2014
|
-
result = await this.tools.dispatch(name, args, {
|
|
2055
|
+
result = await this.tools.dispatch(name, args, {
|
|
2056
|
+
signal,
|
|
2057
|
+
maxResultChars: DEFAULT_MAX_RESULT_CHARS
|
|
2058
|
+
});
|
|
2015
2059
|
const postReport = await runHooks({
|
|
2016
2060
|
hooks: this.hooks,
|
|
2017
2061
|
payload: {
|
|
@@ -4864,8 +4908,8 @@ function writeCache(entry, homeDirOverride) {
|
|
|
4864
4908
|
async function getLatestVersion(opts = {}) {
|
|
4865
4909
|
const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
|
|
4866
4910
|
if (!opts.force) {
|
|
4867
|
-
const
|
|
4868
|
-
if (
|
|
4911
|
+
const cached2 = readCache(opts.homeDir);
|
|
4912
|
+
if (cached2 && Date.now() - cached2.checkedAt < ttl) return cached2.version;
|
|
4869
4913
|
}
|
|
4870
4914
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
4871
4915
|
if (!fetchImpl) return null;
|
|
@@ -5117,7 +5161,7 @@ ${skill.body}${argsBlock}`;
|
|
|
5117
5161
|
}
|
|
5118
5162
|
|
|
5119
5163
|
// src/cli/ui/EventLog.tsx
|
|
5120
|
-
import { Box as Box3, Text as Text3 } from "ink";
|
|
5164
|
+
import { Box as Box3, Text as Text3, useStdout } from "ink";
|
|
5121
5165
|
import React4 from "react";
|
|
5122
5166
|
|
|
5123
5167
|
// src/cli/ui/PlanStateBlock.tsx
|
|
@@ -5277,7 +5321,7 @@ function InlineMd({
|
|
|
5277
5321
|
const status = citations?.get(url);
|
|
5278
5322
|
if (status && !status.ok) {
|
|
5279
5323
|
parts.push(
|
|
5280
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `l${idx++}`, color: "red", strikethrough: true }, `${linkText} \
|
|
5324
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `l${idx++}`, color: "red", strikethrough: true }, `${linkText} \u2717`)
|
|
5281
5325
|
);
|
|
5282
5326
|
} else {
|
|
5283
5327
|
parts.push(
|
|
@@ -5583,7 +5627,7 @@ function Markdown({ text, projectRoot }) {
|
|
|
5583
5627
|
function BrokenCitationsBlock({ items }) {
|
|
5584
5628
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "red", bold: true }, `\u26A0 ${items.length} broken citation${items.length > 1 ? "s" : ""} \u2014 the model referenced paths or lines that don't exist`), items.map((b, i) => (
|
|
5585
5629
|
// biome-ignore lint/suspicious/noArrayIndexKey: list is derived from a Map iteration order, stable per render
|
|
5586
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `bc-${i}`, color: "red" }, ` \
|
|
5630
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `bc-${i}`, color: "red" }, ` \u2717 ${b.url} \u2192 ${b.reason}`)
|
|
5587
5631
|
)));
|
|
5588
5632
|
}
|
|
5589
5633
|
|
|
@@ -5610,32 +5654,49 @@ function useElapsedSeconds() {
|
|
|
5610
5654
|
}
|
|
5611
5655
|
|
|
5612
5656
|
// src/cli/ui/EventLog.tsx
|
|
5657
|
+
var ROLE_GLYPH = {
|
|
5658
|
+
user: "\u25C7",
|
|
5659
|
+
assistant: "\u25C6",
|
|
5660
|
+
assistantPulse: "\u25C7",
|
|
5661
|
+
// pulse alternate for streaming state
|
|
5662
|
+
toolOk: "\u25A3",
|
|
5663
|
+
toolErr: "\u25A5",
|
|
5664
|
+
warning: "\u25B2",
|
|
5665
|
+
error: "\u2726"
|
|
5666
|
+
};
|
|
5667
|
+
function RoleGlyph({
|
|
5668
|
+
glyph,
|
|
5669
|
+
color
|
|
5670
|
+
}) {
|
|
5671
|
+
return /* @__PURE__ */ React4.createElement(Text3, { color, bold: true }, glyph);
|
|
5672
|
+
}
|
|
5613
5673
|
var EventRow = React4.memo(function EventRow2({
|
|
5614
5674
|
event,
|
|
5615
5675
|
projectRoot
|
|
5616
5676
|
}) {
|
|
5617
5677
|
if (event.role === "user") {
|
|
5618
|
-
return /* @__PURE__ */ React4.createElement(Box3,
|
|
5678
|
+
return /* @__PURE__ */ React4.createElement(Box3, { marginTop: event.leadSeparator ? 1 : 0 }, /* @__PURE__ */ React4.createElement(RoleGlyph, { glyph: ROLE_GLYPH.user, color: "cyan" }), /* @__PURE__ */ React4.createElement(Text3, null, " ", event.text));
|
|
5619
5679
|
}
|
|
5620
5680
|
if (event.role === "assistant") {
|
|
5621
5681
|
if (event.streaming) return /* @__PURE__ */ React4.createElement(StreamingAssistant, { event });
|
|
5622
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(
|
|
5682
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(RoleGlyph, { glyph: ROLE_GLYPH.assistant, color: "green" }), event.stats ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, ` ${event.stats.model}`) : null), /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", paddingLeft: 2, marginTop: 1 }, event.branch ? /* @__PURE__ */ React4.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React4.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React4.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React4.createElement(Markdown, { text: event.text, projectRoot }) : /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React4.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React4.createElement(Text3, { color: "magenta" }, event.repair) : null));
|
|
5623
5683
|
}
|
|
5624
5684
|
if (event.role === "tool") {
|
|
5625
5685
|
const isError = event.text.startsWith("ERROR:");
|
|
5626
5686
|
const color = isError ? "red" : "yellow";
|
|
5687
|
+
const glyph = isError ? ROLE_GLYPH.toolErr : ROLE_GLYPH.toolOk;
|
|
5627
5688
|
const marker = isError ? "\u2717" : "\u2192";
|
|
5628
5689
|
const isEditFile = (event.toolName === "edit_file" || event.toolName?.endsWith("_edit_file")) && !isError;
|
|
5629
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text3, { color }, `
|
|
5690
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(RoleGlyph, { glyph, color }), /* @__PURE__ */ React4.createElement(Text3, { color, bold: true }, ` ${event.toolName ?? "?"}`), /* @__PURE__ */ React4.createElement(Text3, { color, dimColor: true }, ` ${marker}`)), /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", paddingLeft: 2, marginTop: 1 }, isEditFile ? /* @__PURE__ */ React4.createElement(EditFileDiff, { text: event.text }) : /* @__PURE__ */ React4.createElement(Text3, { color: isError ? "red" : void 0, dimColor: !isError }, truncate2(event.text, 400))));
|
|
5630
5691
|
}
|
|
5631
5692
|
if (event.role === "error") {
|
|
5632
|
-
return /* @__PURE__ */ React4.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(
|
|
5693
|
+
return /* @__PURE__ */ React4.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(RoleGlyph, { glyph: ROLE_GLYPH.error, color: "red" }), /* @__PURE__ */ React4.createElement(Text3, { color: "red" }, " ", event.text));
|
|
5633
5694
|
}
|
|
5634
5695
|
if (event.role === "info") {
|
|
5635
5696
|
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, event.text));
|
|
5636
5697
|
}
|
|
5637
5698
|
if (event.role === "warning") {
|
|
5638
|
-
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(
|
|
5699
|
+
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(RoleGlyph, { glyph: ROLE_GLYPH.warning, color: "yellow" }), /* @__PURE__ */ React4.createElement(Text3, { color: "yellow" }, " ", event.text));
|
|
5639
5700
|
}
|
|
5640
5701
|
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, null, event.text));
|
|
5641
5702
|
});
|
|
@@ -5659,13 +5720,13 @@ function BranchBlock({ branch }) {
|
|
|
5659
5720
|
const t = (branch.temperatures[i] ?? 0).toFixed(1);
|
|
5660
5721
|
return `${marker} #${i} T=${t} u=${u}`;
|
|
5661
5722
|
}).join(" ");
|
|
5662
|
-
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color: "blue" }, "\
|
|
5723
|
+
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color: "blue" }, "\u2387 branched ", /* @__PURE__ */ React4.createElement(Text3, { bold: true }, branch.budget), ` samples \u2192 picked #${branch.chosenIndex} `, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, per)));
|
|
5663
5724
|
}
|
|
5664
5725
|
function ReasoningBlock({ reasoning }) {
|
|
5665
5726
|
const max = 260;
|
|
5666
5727
|
const flat = reasoning.replace(/\s+/g, " ").trim();
|
|
5667
5728
|
const preview = flat.length <= max ? flat : `\u2026 (+${flat.length - max} earlier chars) ${flat.slice(-max)}`;
|
|
5668
|
-
return /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "
|
|
5729
|
+
return /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u258F "), /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "thinking ", preview));
|
|
5669
5730
|
}
|
|
5670
5731
|
function Elapsed() {
|
|
5671
5732
|
const s = useElapsedSeconds();
|
|
@@ -5673,14 +5734,19 @@ function Elapsed() {
|
|
|
5673
5734
|
const ss = String(s % 60).padStart(2, "0");
|
|
5674
5735
|
return /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, `${mm}:${ss}`);
|
|
5675
5736
|
}
|
|
5737
|
+
function PulsingAssistantGlyph() {
|
|
5738
|
+
const tick = useTick();
|
|
5739
|
+
const on = Math.floor(tick / 4) % 2 === 0;
|
|
5740
|
+
return /* @__PURE__ */ React4.createElement(Text3, { color: "green", bold: true }, on ? ROLE_GLYPH.assistant : ROLE_GLYPH.assistantPulse);
|
|
5741
|
+
}
|
|
5676
5742
|
function StreamingAssistant({ event }) {
|
|
5677
5743
|
if (event.branchProgress) {
|
|
5678
5744
|
const p = event.branchProgress;
|
|
5679
5745
|
if (p.completed === 0) {
|
|
5680
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(
|
|
5746
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React4.createElement(Text3, { color: "blue" }, " \u2387 launching ", p.total, " parallel samples (R1 thinking in parallel)\u2026 "), /* @__PURE__ */ React4.createElement(Elapsed, null)), /* @__PURE__ */ React4.createElement(Text3, { color: "yellow" }, " ", "spread across T=0.0/0.5/1.0 \xB7 reasoner typically takes 30-90s \u2014 this is normal"));
|
|
5681
5747
|
}
|
|
5682
5748
|
const pct2 = Math.round(p.completed / p.total * 100);
|
|
5683
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(
|
|
5749
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React4.createElement(Text3, { color: "blue" }, " \u2387 branching ", p.completed, "/", p.total, " (", pct2, "%) "), /* @__PURE__ */ React4.createElement(Elapsed, null)), /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, " latest #", p.latestIndex, " T=", p.latestTemperature.toFixed(1), " u=", p.latestUncertainties, p.completed < p.total ? " \xB7 waiting for other samples\u2026" : " \xB7 selecting winner\u2026"));
|
|
5684
5750
|
}
|
|
5685
5751
|
const tail = lastLine(event.text, 140);
|
|
5686
5752
|
const reasoningTail = event.reasoning ? lastLine(event.reasoning, 120) : "";
|
|
@@ -5708,7 +5774,11 @@ function StreamingAssistant({ event }) {
|
|
|
5708
5774
|
label = parts.join(" \xB7 ");
|
|
5709
5775
|
labelColor = "green";
|
|
5710
5776
|
}
|
|
5711
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(
|
|
5777
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React4.createElement(Text3, null, " "), /* @__PURE__ */ React4.createElement(Pulse, null), /* @__PURE__ */ React4.createElement(Text3, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React4.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u25B8 ", tail) : preFirstByte ? (
|
|
5778
|
+
// Non-dim yellow: first-time users misread the dim version as
|
|
5779
|
+
// "app frozen". The reassurance has to be VISIBLE to do its job.
|
|
5780
|
+
/* @__PURE__ */ React4.createElement(Text3, { color: "yellow", italic: true }, " waiting for first byte \u2014 this is normal, typically 5-60s depending on model + load")
|
|
5781
|
+
) : reasoningOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", italic: true }, " R1 is thinking before it speaks \u2014 body text arrives when reasoning finishes (typically 20-90s, this is normal)") : toolCallOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "magenta", italic: true }, " tool-call arguments streaming \u2014 the model is about to dispatch a tool") : event.reasoning ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", italic: true }, " R1 still reasoning \u2014 body text or tool call arrives when thinking finishes") : null);
|
|
5712
5782
|
}
|
|
5713
5783
|
function Pulse() {
|
|
5714
5784
|
const tick = useTick();
|
|
@@ -5722,10 +5792,26 @@ function lastLine(s, maxChars) {
|
|
|
5722
5792
|
}
|
|
5723
5793
|
function StatsLine({ stats }) {
|
|
5724
5794
|
const hit = (stats.cacheHitRatio * 100).toFixed(1);
|
|
5725
|
-
return /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "
|
|
5795
|
+
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u258F "), /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "cache ", hit, "% \xB7 tokens ", stats.usage.promptTokens, " \u2192 ", stats.usage.completionTokens, " \xB7 $", stats.cost.toFixed(6)));
|
|
5726
5796
|
}
|
|
5727
5797
|
function truncate2(s, max) {
|
|
5728
|
-
|
|
5798
|
+
if (s.length <= max) return s;
|
|
5799
|
+
if (s.startsWith("ERROR:")) {
|
|
5800
|
+
const firstNl = s.indexOf("\n");
|
|
5801
|
+
const firstLine = firstNl === -1 ? s : s.slice(0, firstNl);
|
|
5802
|
+
if (firstLine.length >= max) {
|
|
5803
|
+
return `${firstLine.slice(0, max)}\u2026 (+${s.length - max} chars \u2014 /tool N for full)`;
|
|
5804
|
+
}
|
|
5805
|
+
const budget = max - firstLine.length - 10;
|
|
5806
|
+
const tail = s.slice(-budget);
|
|
5807
|
+
const skipped2 = s.length - firstLine.length - tail.length;
|
|
5808
|
+
return `${firstLine}
|
|
5809
|
+
\u2026 (+${skipped2} chars) \u2026
|
|
5810
|
+
${tail}`;
|
|
5811
|
+
}
|
|
5812
|
+
const skipped = s.length - max;
|
|
5813
|
+
return `\u2026 (+${skipped} earlier chars \u2014 /tool N for full) \u2026
|
|
5814
|
+
${s.slice(-max)}`;
|
|
5729
5815
|
}
|
|
5730
5816
|
|
|
5731
5817
|
// src/cli/ui/PlanConfirm.tsx
|
|
@@ -5739,7 +5825,8 @@ function SingleSelect({
|
|
|
5739
5825
|
items,
|
|
5740
5826
|
initialValue,
|
|
5741
5827
|
onSubmit,
|
|
5742
|
-
onCancel
|
|
5828
|
+
onCancel,
|
|
5829
|
+
footer
|
|
5743
5830
|
}) {
|
|
5744
5831
|
const initialIndex = Math.max(
|
|
5745
5832
|
0,
|
|
@@ -5766,7 +5853,7 @@ function SingleSelect({
|
|
|
5766
5853
|
active: i === index,
|
|
5767
5854
|
marker: i === index ? "\u25B8" : " "
|
|
5768
5855
|
}
|
|
5769
|
-
)));
|
|
5856
|
+
)), footer ? /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, footer)) : null);
|
|
5770
5857
|
}
|
|
5771
5858
|
function MultiSelect({
|
|
5772
5859
|
items,
|
|
@@ -5842,7 +5929,7 @@ function PlanConfirm({ plan, onChoose, maxRenderedChars, projectRoot }) {
|
|
|
5842
5929
|
|
|
5843
5930
|
\u2026 (${plan.length - cap} chars truncated \u2014 use /tool to view the full proposal)` : plan;
|
|
5844
5931
|
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
|
|
5845
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Markdown, { text: visible, projectRoot })), hasOpenQuestions ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(
|
|
5932
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { 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__ */ React6.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Markdown, { text: visible, projectRoot })), hasOpenQuestions ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(
|
|
5846
5933
|
SingleSelect,
|
|
5847
5934
|
{
|
|
5848
5935
|
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
@@ -5863,7 +5950,9 @@ function PlanConfirm({ plan, onChoose, maxRenderedChars, projectRoot }) {
|
|
|
5863
5950
|
hint: "Exit plan mode. Drop the plan; the model won't implement it."
|
|
5864
5951
|
}
|
|
5865
5952
|
],
|
|
5866
|
-
onSubmit: (v) => onChoose(v)
|
|
5953
|
+
onSubmit: (v) => onChoose(v),
|
|
5954
|
+
onCancel: () => onChoose("cancel"),
|
|
5955
|
+
footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] cancel"
|
|
5867
5956
|
}
|
|
5868
5957
|
)));
|
|
5869
5958
|
}
|
|
@@ -6054,7 +6143,7 @@ function PromptInput({
|
|
|
6054
6143
|
},
|
|
6055
6144
|
{ isActive: !disabled }
|
|
6056
6145
|
);
|
|
6057
|
-
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command
|
|
6146
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026 \xB7 [Esc] to stop" : placeholder ?? "type a message, or /command \xB7 [Shift+Enter] / [Ctrl+J] newline";
|
|
6058
6147
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
6059
6148
|
const borderColor = disabled ? "gray" : "cyan";
|
|
6060
6149
|
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
@@ -6095,7 +6184,7 @@ function LineWithCursor({
|
|
|
6095
6184
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
6096
6185
|
import React9 from "react";
|
|
6097
6186
|
function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
6098
|
-
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "yellow" }, "\u25B8 model wants to run a shell command")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "$ "), /* @__PURE__ */ React9.createElement(Text8, { color: "cyan" }, command))), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
|
|
6187
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "yellow" }, "\u25B8 model wants to run a shell command")), /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", 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__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "$ "), /* @__PURE__ */ React9.createElement(Text8, { color: "cyan" }, command))), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
|
|
6099
6188
|
SingleSelect,
|
|
6100
6189
|
{
|
|
6101
6190
|
initialValue: "run_once",
|
|
@@ -6116,7 +6205,9 @@ function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
|
6116
6205
|
hint: "Tell the model the user refused; it will continue without this command."
|
|
6117
6206
|
}
|
|
6118
6207
|
],
|
|
6119
|
-
onSubmit: (v) => onChoose(v)
|
|
6208
|
+
onSubmit: (v) => onChoose(v),
|
|
6209
|
+
onCancel: () => onChoose("deny"),
|
|
6210
|
+
footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] deny"
|
|
6120
6211
|
}
|
|
6121
6212
|
)));
|
|
6122
6213
|
}
|
|
@@ -6166,7 +6257,7 @@ function SlashSuggestions({
|
|
|
6166
6257
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
6167
6258
|
const hiddenAbove = windowStart;
|
|
6168
6259
|
const hiddenBelow = total - windowStart - shown.length;
|
|
6169
|
-
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React10.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191
|
|
6260
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React10.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
|
|
6170
6261
|
}
|
|
6171
6262
|
function SuggestionRow({ spec, isSelected }) {
|
|
6172
6263
|
const marker = isSelected ? "\u25B8" : " ";
|
|
@@ -6179,8 +6270,37 @@ function SuggestionRow({ spec, isSelected }) {
|
|
|
6179
6270
|
}
|
|
6180
6271
|
|
|
6181
6272
|
// src/cli/ui/StatsPanel.tsx
|
|
6182
|
-
import { Box as Box10, Text as Text10 } from "ink";
|
|
6273
|
+
import { Box as Box10, Text as Text10, useStdout as useStdout2 } from "ink";
|
|
6183
6274
|
import React11 from "react";
|
|
6275
|
+
var WORDMARK_STYLES = [
|
|
6276
|
+
{ ch: "\u25C8", color: "#5eead4", isLogo: true },
|
|
6277
|
+
// teal — brand mark
|
|
6278
|
+
{ ch: " ", color: "#5eead4", isLogo: false },
|
|
6279
|
+
{ ch: "R", color: "#67e8f9", isLogo: false },
|
|
6280
|
+
// cyan
|
|
6281
|
+
{ ch: "E", color: "#7dd3fc", isLogo: false },
|
|
6282
|
+
// sky
|
|
6283
|
+
{ ch: "A", color: "#93c5fd", isLogo: false },
|
|
6284
|
+
// blue
|
|
6285
|
+
{ ch: "S", color: "#a5b4fc", isLogo: false },
|
|
6286
|
+
// indigo
|
|
6287
|
+
{ ch: "O", color: "#c4b5fd", isLogo: false },
|
|
6288
|
+
// violet
|
|
6289
|
+
{ ch: "N", color: "#d8b4fe", isLogo: false },
|
|
6290
|
+
// purple
|
|
6291
|
+
{ ch: "I", color: "#f0abfc", isLogo: false },
|
|
6292
|
+
// fuchsia
|
|
6293
|
+
{ ch: "X", color: "#f0abfc", isLogo: false }
|
|
6294
|
+
// fuchsia
|
|
6295
|
+
];
|
|
6296
|
+
function Wordmark({ busy }) {
|
|
6297
|
+
const tick = useTick();
|
|
6298
|
+
const period = busy ? 5 : 12;
|
|
6299
|
+
const bright = Math.floor(tick / period) % 2 === 0;
|
|
6300
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, WORDMARK_STYLES.map((c) => /* @__PURE__ */ React11.createElement(Text10, { key: `${c.ch}-${c.color}`, color: c.color, bold: c.isLogo ? bright : true }, c.ch)));
|
|
6301
|
+
}
|
|
6302
|
+
var NARROW_BREAKPOINT = 120;
|
|
6303
|
+
var COLD_START_TURNS = 3;
|
|
6184
6304
|
function StatsPanel({
|
|
6185
6305
|
summary,
|
|
6186
6306
|
model,
|
|
@@ -6189,27 +6309,313 @@ function StatsPanel({
|
|
|
6189
6309
|
branchBudget,
|
|
6190
6310
|
planMode,
|
|
6191
6311
|
balance,
|
|
6192
|
-
updateAvailable
|
|
6312
|
+
updateAvailable,
|
|
6313
|
+
busy
|
|
6193
6314
|
}) {
|
|
6194
|
-
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
6195
|
-
const hitColor = summary.cacheHitRatio >= 0.7 ? "green" : summary.cacheHitRatio >= 0.4 ? "yellow" : "red";
|
|
6196
6315
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
6197
6316
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
6198
6317
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
6199
|
-
const
|
|
6200
|
-
|
|
6318
|
+
const { stdout: stdout2 } = useStdout2();
|
|
6319
|
+
const columns = stdout2?.columns ?? 80;
|
|
6320
|
+
const narrow = columns < NARROW_BREAKPOINT;
|
|
6321
|
+
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
6322
|
+
return /* @__PURE__ */ React11.createElement(Box10, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(
|
|
6323
|
+
Header,
|
|
6324
|
+
{
|
|
6325
|
+
model,
|
|
6326
|
+
prefixHash,
|
|
6327
|
+
harvestOn,
|
|
6328
|
+
branchOn,
|
|
6329
|
+
branchBudget: branchBudget ?? 1,
|
|
6330
|
+
planMode,
|
|
6331
|
+
turns: summary.turns,
|
|
6332
|
+
updateAvailable,
|
|
6333
|
+
narrow,
|
|
6334
|
+
busy: busy ?? false
|
|
6335
|
+
}
|
|
6336
|
+
), narrow ? /* @__PURE__ */ React11.createElement(
|
|
6337
|
+
StackedMetrics,
|
|
6338
|
+
{
|
|
6339
|
+
summary,
|
|
6340
|
+
ctxRatio,
|
|
6341
|
+
ctxMax,
|
|
6342
|
+
balance,
|
|
6343
|
+
coldStart
|
|
6344
|
+
}
|
|
6345
|
+
) : /* @__PURE__ */ React11.createElement(
|
|
6346
|
+
InlineMetrics,
|
|
6347
|
+
{
|
|
6348
|
+
summary,
|
|
6349
|
+
ctxRatio,
|
|
6350
|
+
ctxMax,
|
|
6351
|
+
balance,
|
|
6352
|
+
coldStart
|
|
6353
|
+
}
|
|
6354
|
+
));
|
|
6355
|
+
}
|
|
6356
|
+
function Header({
|
|
6357
|
+
model,
|
|
6358
|
+
prefixHash,
|
|
6359
|
+
harvestOn,
|
|
6360
|
+
branchOn,
|
|
6361
|
+
branchBudget,
|
|
6362
|
+
planMode,
|
|
6363
|
+
turns,
|
|
6364
|
+
updateAvailable,
|
|
6365
|
+
narrow,
|
|
6366
|
+
busy
|
|
6367
|
+
}) {
|
|
6368
|
+
return /* @__PURE__ */ React11.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Wordmark, { busy }), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, model), narrow ? null : /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " \xB7 PLAN") : null), /* @__PURE__ */ React11.createElement(Text10, null, updateAvailable ? /* @__PURE__ */ React11.createElement(Text10, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
|
|
6369
|
+
}
|
|
6370
|
+
function InlineMetrics({
|
|
6371
|
+
summary,
|
|
6372
|
+
ctxRatio,
|
|
6373
|
+
ctxMax,
|
|
6374
|
+
balance,
|
|
6375
|
+
coldStart
|
|
6376
|
+
}) {
|
|
6377
|
+
return /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React11.createElement(ContextCell, { ratio: ctxRatio, promptTokens: summary.lastPromptTokens, ctxMax }), /* @__PURE__ */ React11.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React11.createElement(CostCell, { summary, coldStart }), balance ? /* @__PURE__ */ React11.createElement(BalanceCell, { balance }) : null);
|
|
6378
|
+
}
|
|
6379
|
+
function StackedMetrics({
|
|
6380
|
+
summary,
|
|
6381
|
+
ctxRatio,
|
|
6382
|
+
ctxMax,
|
|
6383
|
+
balance,
|
|
6384
|
+
coldStart
|
|
6385
|
+
}) {
|
|
6386
|
+
return /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React11.createElement(
|
|
6387
|
+
ContextCell,
|
|
6388
|
+
{
|
|
6389
|
+
ratio: ctxRatio,
|
|
6390
|
+
promptTokens: summary.lastPromptTokens,
|
|
6391
|
+
ctxMax,
|
|
6392
|
+
showBar: true
|
|
6393
|
+
}
|
|
6394
|
+
), balance ? /* @__PURE__ */ React11.createElement(BalanceCell, { balance }) : null, /* @__PURE__ */ React11.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React11.createElement(CostCell, { summary, coldStart }));
|
|
6395
|
+
}
|
|
6396
|
+
function ContextCell({
|
|
6397
|
+
ratio,
|
|
6398
|
+
promptTokens,
|
|
6399
|
+
ctxMax,
|
|
6400
|
+
showBar
|
|
6401
|
+
}) {
|
|
6402
|
+
if (promptTokens === 0) {
|
|
6403
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "ctx "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\u2014 (no turns yet)"));
|
|
6404
|
+
}
|
|
6405
|
+
const color = ratio >= 0.8 ? "red" : ratio >= 0.6 ? "yellow" : "green";
|
|
6406
|
+
const pct2 = Math.round(ratio * 100);
|
|
6407
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "ctx "), showBar ? /* @__PURE__ */ React11.createElement(Bar, { ratio, color }) : null, showBar ? /* @__PURE__ */ React11.createElement(Text10, null, " ") : null, /* @__PURE__ */ React11.createElement(Text10, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " \xB7 /compact") : null);
|
|
6408
|
+
}
|
|
6409
|
+
function CacheCell({
|
|
6410
|
+
hitRatio,
|
|
6411
|
+
coldStart,
|
|
6412
|
+
turns
|
|
6413
|
+
}) {
|
|
6414
|
+
const pct2 = (hitRatio * 100).toFixed(1);
|
|
6415
|
+
if (turns === 0) {
|
|
6416
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\u2014"));
|
|
6417
|
+
}
|
|
6418
|
+
if (coldStart) {
|
|
6419
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true, italic: true }, "(cold start)"));
|
|
6420
|
+
}
|
|
6421
|
+
const color = hitRatio >= 0.7 ? "green" : hitRatio >= 0.4 ? "yellow" : "red";
|
|
6422
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache "), /* @__PURE__ */ React11.createElement(Text10, { color, bold: true }, pct2, "%"));
|
|
6423
|
+
}
|
|
6424
|
+
function CostCell({
|
|
6425
|
+
summary,
|
|
6426
|
+
coldStart
|
|
6427
|
+
}) {
|
|
6428
|
+
if (summary.turns === 0) {
|
|
6429
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\u2014"));
|
|
6430
|
+
}
|
|
6431
|
+
const primaryColor = coldStart ? void 0 : "green";
|
|
6432
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, { color: primaryColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")"));
|
|
6433
|
+
}
|
|
6434
|
+
function BalanceCell({ balance }) {
|
|
6435
|
+
const color = balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green";
|
|
6436
|
+
return /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "balance "), /* @__PURE__ */ React11.createElement(Text10, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
6437
|
+
}
|
|
6438
|
+
function Bar({ ratio, color }) {
|
|
6439
|
+
const cells = 10;
|
|
6440
|
+
const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
|
|
6441
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(cells - filled);
|
|
6442
|
+
return /* @__PURE__ */ React11.createElement(Text10, { color }, bar);
|
|
6201
6443
|
}
|
|
6202
6444
|
function formatTokens(n) {
|
|
6203
|
-
if (n <
|
|
6204
|
-
const k = n /
|
|
6205
|
-
return k >= 100 ? `${k.toFixed(0)}
|
|
6445
|
+
if (n < 1024) return String(n);
|
|
6446
|
+
const k = n / 1024;
|
|
6447
|
+
return k >= 100 ? `${k.toFixed(0)}K` : `${k.toFixed(1)}K`;
|
|
6206
6448
|
}
|
|
6207
6449
|
|
|
6208
6450
|
// src/cli/ui/slash.ts
|
|
6209
6451
|
import { spawnSync } from "child_process";
|
|
6210
6452
|
|
|
6453
|
+
// src/tokenizer.ts
|
|
6454
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
6455
|
+
import { createRequire } from "module";
|
|
6456
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
6457
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6458
|
+
import { gunzipSync } from "zlib";
|
|
6459
|
+
function buildByteToChar() {
|
|
6460
|
+
const result = new Array(256);
|
|
6461
|
+
const bs = [];
|
|
6462
|
+
for (let b = 33; b <= 126; b++) bs.push(b);
|
|
6463
|
+
for (let b = 161; b <= 172; b++) bs.push(b);
|
|
6464
|
+
for (let b = 174; b <= 255; b++) bs.push(b);
|
|
6465
|
+
const cs = bs.slice();
|
|
6466
|
+
let n = 0;
|
|
6467
|
+
for (let b = 0; b < 256; b++) {
|
|
6468
|
+
if (!bs.includes(b)) {
|
|
6469
|
+
bs.push(b);
|
|
6470
|
+
cs.push(256 + n);
|
|
6471
|
+
n++;
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
for (let i = 0; i < bs.length; i++) {
|
|
6475
|
+
result[bs[i]] = String.fromCodePoint(cs[i]);
|
|
6476
|
+
}
|
|
6477
|
+
return result;
|
|
6478
|
+
}
|
|
6479
|
+
var cached = null;
|
|
6480
|
+
function resolveDataPath() {
|
|
6481
|
+
if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
|
|
6482
|
+
try {
|
|
6483
|
+
const here = dirname7(fileURLToPath2(import.meta.url));
|
|
6484
|
+
return join8(here, "..", "data", "deepseek-tokenizer.json.gz");
|
|
6485
|
+
} catch {
|
|
6486
|
+
const req = createRequire(import.meta.url);
|
|
6487
|
+
return join8(
|
|
6488
|
+
dirname7(req.resolve("reasonix/package.json")),
|
|
6489
|
+
"data",
|
|
6490
|
+
"deepseek-tokenizer.json.gz"
|
|
6491
|
+
);
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
function loadTokenizer() {
|
|
6495
|
+
if (cached) return cached;
|
|
6496
|
+
const buf = readFileSync10(resolveDataPath());
|
|
6497
|
+
const json = gunzipSync(buf).toString("utf8");
|
|
6498
|
+
const data = JSON.parse(json);
|
|
6499
|
+
const mergeRank = /* @__PURE__ */ new Map();
|
|
6500
|
+
for (let i = 0; i < data.model.merges.length; i++) {
|
|
6501
|
+
mergeRank.set(data.model.merges[i], i);
|
|
6502
|
+
}
|
|
6503
|
+
const splitRegexes = [];
|
|
6504
|
+
for (const p of data.pre_tokenizer.pretokenizers) {
|
|
6505
|
+
if (p.type === "Split") {
|
|
6506
|
+
splitRegexes.push(new RegExp(p.pattern.Regex, "gu"));
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
const addedMap = /* @__PURE__ */ new Map();
|
|
6510
|
+
const addedContents = [];
|
|
6511
|
+
for (const t of data.added_tokens) {
|
|
6512
|
+
if (!t.special) {
|
|
6513
|
+
addedMap.set(t.content, t.id);
|
|
6514
|
+
addedContents.push(t.content);
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
addedContents.sort((a, b) => b.length - a.length);
|
|
6518
|
+
const addedPattern = addedContents.length ? new RegExp(addedContents.map(escapeRegex).join("|"), "g") : null;
|
|
6519
|
+
cached = {
|
|
6520
|
+
vocab: data.model.vocab,
|
|
6521
|
+
mergeRank,
|
|
6522
|
+
splitRegexes,
|
|
6523
|
+
byteToChar: buildByteToChar(),
|
|
6524
|
+
addedPattern,
|
|
6525
|
+
addedMap
|
|
6526
|
+
};
|
|
6527
|
+
return cached;
|
|
6528
|
+
}
|
|
6529
|
+
function escapeRegex(s) {
|
|
6530
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6531
|
+
}
|
|
6532
|
+
function applySplit(chunks, re) {
|
|
6533
|
+
const out = [];
|
|
6534
|
+
for (const chunk of chunks) {
|
|
6535
|
+
if (!chunk) continue;
|
|
6536
|
+
re.lastIndex = 0;
|
|
6537
|
+
let last = 0;
|
|
6538
|
+
for (const m of chunk.matchAll(re)) {
|
|
6539
|
+
const idx = m.index ?? 0;
|
|
6540
|
+
if (idx > last) out.push(chunk.slice(last, idx));
|
|
6541
|
+
if (m[0].length > 0) out.push(m[0]);
|
|
6542
|
+
last = idx + m[0].length;
|
|
6543
|
+
}
|
|
6544
|
+
if (last < chunk.length) out.push(chunk.slice(last));
|
|
6545
|
+
}
|
|
6546
|
+
return out;
|
|
6547
|
+
}
|
|
6548
|
+
function byteLevelEncode(s, byteToChar) {
|
|
6549
|
+
const bytes = new TextEncoder().encode(s);
|
|
6550
|
+
let out = "";
|
|
6551
|
+
for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
|
|
6552
|
+
return out;
|
|
6553
|
+
}
|
|
6554
|
+
function bpeEncode(piece, mergeRank) {
|
|
6555
|
+
if (piece.length <= 1) return piece ? [piece] : [];
|
|
6556
|
+
let word = Array.from(piece);
|
|
6557
|
+
while (true) {
|
|
6558
|
+
let bestIdx = -1;
|
|
6559
|
+
let bestRank = Number.POSITIVE_INFINITY;
|
|
6560
|
+
for (let i = 0; i < word.length - 1; i++) {
|
|
6561
|
+
const pair = `${word[i]} ${word[i + 1]}`;
|
|
6562
|
+
const rank = mergeRank.get(pair);
|
|
6563
|
+
if (rank !== void 0 && rank < bestRank) {
|
|
6564
|
+
bestRank = rank;
|
|
6565
|
+
bestIdx = i;
|
|
6566
|
+
if (rank === 0) break;
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
if (bestIdx < 0) break;
|
|
6570
|
+
word = [
|
|
6571
|
+
...word.slice(0, bestIdx),
|
|
6572
|
+
word[bestIdx] + word[bestIdx + 1],
|
|
6573
|
+
...word.slice(bestIdx + 2)
|
|
6574
|
+
];
|
|
6575
|
+
if (word.length === 1) break;
|
|
6576
|
+
}
|
|
6577
|
+
return word;
|
|
6578
|
+
}
|
|
6579
|
+
function encode(text) {
|
|
6580
|
+
if (!text) return [];
|
|
6581
|
+
const t = loadTokenizer();
|
|
6582
|
+
const ids = [];
|
|
6583
|
+
const process2 = (segment) => {
|
|
6584
|
+
if (!segment) return;
|
|
6585
|
+
let chunks = [segment];
|
|
6586
|
+
for (const re of t.splitRegexes) chunks = applySplit(chunks, re);
|
|
6587
|
+
for (const chunk of chunks) {
|
|
6588
|
+
if (!chunk) continue;
|
|
6589
|
+
const byteLevel = byteLevelEncode(chunk, t.byteToChar);
|
|
6590
|
+
const pieces = bpeEncode(byteLevel, t.mergeRank);
|
|
6591
|
+
for (const p of pieces) {
|
|
6592
|
+
const id = t.vocab[p];
|
|
6593
|
+
if (id !== void 0) ids.push(id);
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
};
|
|
6597
|
+
if (t.addedPattern) {
|
|
6598
|
+
t.addedPattern.lastIndex = 0;
|
|
6599
|
+
let last = 0;
|
|
6600
|
+
for (const m of text.matchAll(t.addedPattern)) {
|
|
6601
|
+
const idx = m.index ?? 0;
|
|
6602
|
+
if (idx > last) process2(text.slice(last, idx));
|
|
6603
|
+
const id = t.addedMap.get(m[0]);
|
|
6604
|
+
if (id !== void 0) ids.push(id);
|
|
6605
|
+
last = idx + m[0].length;
|
|
6606
|
+
}
|
|
6607
|
+
if (last < text.length) process2(text.slice(last));
|
|
6608
|
+
} else {
|
|
6609
|
+
process2(text);
|
|
6610
|
+
}
|
|
6611
|
+
return ids;
|
|
6612
|
+
}
|
|
6613
|
+
function countTokens(text) {
|
|
6614
|
+
return encode(text).length;
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6211
6617
|
// src/cli/commands/stats.ts
|
|
6212
|
-
import { existsSync as existsSync7, readFileSync as
|
|
6618
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
|
|
6213
6619
|
function statsCommand(opts) {
|
|
6214
6620
|
if (opts.transcript) {
|
|
6215
6621
|
transcriptSummary(opts.transcript);
|
|
@@ -6222,7 +6628,7 @@ function transcriptSummary(path) {
|
|
|
6222
6628
|
console.error(`no such transcript: ${path}`);
|
|
6223
6629
|
process.exit(1);
|
|
6224
6630
|
}
|
|
6225
|
-
const lines =
|
|
6631
|
+
const lines = readFileSync11(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
6226
6632
|
let assistantTurns = 0;
|
|
6227
6633
|
let toolCalls = 0;
|
|
6228
6634
|
let lastTurn = 0;
|
|
@@ -6324,6 +6730,7 @@ var SLASH_COMMANDS = [
|
|
|
6324
6730
|
summary: "one-tap model + harvest + branch bundle"
|
|
6325
6731
|
},
|
|
6326
6732
|
{ cmd: "model", argsHint: "<id>", summary: "switch DeepSeek model id" },
|
|
6733
|
+
{ cmd: "models", summary: "list available models fetched from DeepSeek /models" },
|
|
6327
6734
|
{ cmd: "harvest", argsHint: "[on|off]", summary: "toggle Pillar-2 plan-state extraction" },
|
|
6328
6735
|
{ cmd: "branch", argsHint: "<N|off>", summary: "run N parallel samples per turn (N>=2)" },
|
|
6329
6736
|
{ cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
|
|
@@ -6352,6 +6759,10 @@ var SLASH_COMMANDS = [
|
|
|
6352
6759
|
summary: "cross-session cost dashboard (today / week / month / all-time \xB7 cache hit \xB7 vs Claude)"
|
|
6353
6760
|
},
|
|
6354
6761
|
{ cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
|
|
6762
|
+
{
|
|
6763
|
+
cmd: "context",
|
|
6764
|
+
summary: "break down where context tokens are going: system / tools / per-turn log"
|
|
6765
|
+
},
|
|
6355
6766
|
{ cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
|
|
6356
6767
|
{ cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
|
|
6357
6768
|
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
@@ -6696,6 +7107,66 @@ ${entry.text}`
|
|
|
6696
7107
|
info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
|
|
6697
7108
|
};
|
|
6698
7109
|
}
|
|
7110
|
+
case "context": {
|
|
7111
|
+
const systemTokens = countTokens(loop.prefix.system);
|
|
7112
|
+
const toolsTokens = countTokens(JSON.stringify(loop.prefix.toolSpecs));
|
|
7113
|
+
const entries = loop.log.toMessages();
|
|
7114
|
+
let userTokens = 0;
|
|
7115
|
+
let assistantTokens = 0;
|
|
7116
|
+
let toolResultTokens = 0;
|
|
7117
|
+
let toolCallTokens = 0;
|
|
7118
|
+
const toolBreakdown = [];
|
|
7119
|
+
let logTurn = 0;
|
|
7120
|
+
for (const e of entries) {
|
|
7121
|
+
const content = typeof e.content === "string" ? e.content : "";
|
|
7122
|
+
if (e.role === "user") {
|
|
7123
|
+
userTokens += countTokens(content);
|
|
7124
|
+
logTurn += 1;
|
|
7125
|
+
} else if (e.role === "assistant") {
|
|
7126
|
+
assistantTokens += countTokens(content);
|
|
7127
|
+
if (Array.isArray(e.tool_calls) && e.tool_calls.length > 0) {
|
|
7128
|
+
toolCallTokens += countTokens(JSON.stringify(e.tool_calls));
|
|
7129
|
+
}
|
|
7130
|
+
} else if (e.role === "tool") {
|
|
7131
|
+
const n = countTokens(content);
|
|
7132
|
+
toolResultTokens += n;
|
|
7133
|
+
toolBreakdown.push({ name: e.name ?? "?", tokens: n, turn: logTurn });
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
const logTokens = userTokens + assistantTokens + toolResultTokens + toolCallTokens;
|
|
7137
|
+
const total = systemTokens + toolsTokens + logTokens;
|
|
7138
|
+
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[loop.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
7139
|
+
const pct2 = (n) => total > 0 ? `${Math.round(n / total * 100)}%`.padStart(4) : " 0%";
|
|
7140
|
+
const row2 = (label, n, note = "") => ` ${label.padEnd(20)}${compactNum(n).padStart(8)} tokens ${pct2(n)}${note ? ` ${note}` : ""}`;
|
|
7141
|
+
const lines = [
|
|
7142
|
+
`Next-request estimate: ~${compactNum(total)} tokens of ${compactNum(ctxMax)} (${Math.round(
|
|
7143
|
+
total / ctxMax * 100
|
|
7144
|
+
)}% of window)`,
|
|
7145
|
+
"",
|
|
7146
|
+
row2("system prompt", systemTokens),
|
|
7147
|
+
row2("tool specs", toolsTokens, `(${loop.prefix.toolSpecs.length} tools)`),
|
|
7148
|
+
row2("log (all turns)", logTokens, `(${entries.length} messages)`),
|
|
7149
|
+
` user ${compactNum(userTokens).padStart(8)} tokens`,
|
|
7150
|
+
` assistant ${compactNum(assistantTokens).padStart(8)} tokens`,
|
|
7151
|
+
` tool-call args ${compactNum(toolCallTokens).padStart(8)} tokens`,
|
|
7152
|
+
` tool results ${compactNum(toolResultTokens).padStart(8)} tokens`
|
|
7153
|
+
];
|
|
7154
|
+
if (toolBreakdown.length > 0) {
|
|
7155
|
+
const top = [...toolBreakdown].sort((a, b) => b.tokens - a.tokens).slice(0, 5);
|
|
7156
|
+
lines.push("");
|
|
7157
|
+
lines.push(`Top tool results by cost (of ${toolBreakdown.length} total):`);
|
|
7158
|
+
for (const t of top) {
|
|
7159
|
+
lines.push(
|
|
7160
|
+
` turn ${String(t.turn).padStart(3)} ${t.name.padEnd(22)} ${compactNum(t.tokens).padStart(8)} tokens`
|
|
7161
|
+
);
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
lines.push("");
|
|
7165
|
+
lines.push(
|
|
7166
|
+
"Count is a local estimate (DeepSeek V3 tokenizer, pure-TS port); server prompt_tokens may add ~3-6% for chat-template role markers."
|
|
7167
|
+
);
|
|
7168
|
+
return { info: lines.join("\n") };
|
|
7169
|
+
}
|
|
6699
7170
|
case "status": {
|
|
6700
7171
|
const branchBudget = loop.branchOptions.budget ?? 1;
|
|
6701
7172
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[loop.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
@@ -6722,10 +7193,44 @@ ${entry.text}`
|
|
|
6722
7193
|
}
|
|
6723
7194
|
case "model": {
|
|
6724
7195
|
const id = args[0];
|
|
6725
|
-
|
|
7196
|
+
const known = ctx.models ?? null;
|
|
7197
|
+
if (!id) {
|
|
7198
|
+
const hint = known && known.length > 0 ? known.join(" | ") : "try deepseek-chat or deepseek-reasoner \u2014 run /models to fetch the live list";
|
|
7199
|
+
return { info: `usage: /model <id> (${hint})` };
|
|
7200
|
+
}
|
|
6726
7201
|
loop.configure({ model: id });
|
|
7202
|
+
if (known && known.length > 0 && !known.includes(id)) {
|
|
7203
|
+
return {
|
|
7204
|
+
info: `model \u2192 ${id} (\u26A0 not in the fetched catalog: ${known.join(", ")}. If this is wrong the next call will 400 \u2014 run /models to refresh.)`
|
|
7205
|
+
};
|
|
7206
|
+
}
|
|
6727
7207
|
return { info: `model \u2192 ${id}` };
|
|
6728
7208
|
}
|
|
7209
|
+
case "models": {
|
|
7210
|
+
const list = ctx.models ?? null;
|
|
7211
|
+
if (list === null) {
|
|
7212
|
+
ctx.refreshModels?.();
|
|
7213
|
+
return {
|
|
7214
|
+
info: "fetching /models from DeepSeek\u2026 run /models again in a moment. If it stays empty, your API key may lack permission or the network is blocked."
|
|
7215
|
+
};
|
|
7216
|
+
}
|
|
7217
|
+
if (list.length === 0) {
|
|
7218
|
+
return {
|
|
7219
|
+
info: "DeepSeek /models returned an empty list. Try /models again, or check your account status at api-docs.deepseek.com."
|
|
7220
|
+
};
|
|
7221
|
+
}
|
|
7222
|
+
const current = loop.model;
|
|
7223
|
+
const lines = list.map((id) => id === current ? `\u25B8 ${id} (current)` : ` ${id}`);
|
|
7224
|
+
return {
|
|
7225
|
+
info: [
|
|
7226
|
+
`Available models (DeepSeek /models \xB7 ${list.length} total):`,
|
|
7227
|
+
"",
|
|
7228
|
+
...lines,
|
|
7229
|
+
"",
|
|
7230
|
+
"Switch with: /model <id>"
|
|
7231
|
+
].join("\n")
|
|
7232
|
+
};
|
|
7233
|
+
}
|
|
6729
7234
|
case "harvest": {
|
|
6730
7235
|
const arg = (args[0] ?? "").toLowerCase();
|
|
6731
7236
|
const on = arg === "" ? !loop.harvestEnabled : arg === "on" || arg === "true" || arg === "1";
|
|
@@ -7137,9 +7642,9 @@ function formatToolList(history) {
|
|
|
7137
7642
|
return lines.join("\n");
|
|
7138
7643
|
}
|
|
7139
7644
|
function compactNum(n) {
|
|
7140
|
-
if (n <
|
|
7141
|
-
const k = n /
|
|
7142
|
-
return k >= 100 ? `${Math.round(k)}
|
|
7645
|
+
if (n < 1024) return String(n);
|
|
7646
|
+
const k = n / 1024;
|
|
7647
|
+
return k >= 100 ? `${Math.round(k)}K` : `${k.toFixed(1)}K`;
|
|
7143
7648
|
}
|
|
7144
7649
|
function stripOuterQuotes(s) {
|
|
7145
7650
|
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
|
|
@@ -7201,6 +7706,7 @@ function App({
|
|
|
7201
7706
|
const [subagentActivity, setSubagentActivity] = useState5(null);
|
|
7202
7707
|
const [statusLine, setStatusLine] = useState5(null);
|
|
7203
7708
|
const [balance, setBalance] = useState5(null);
|
|
7709
|
+
const [models, setModels] = useState5(null);
|
|
7204
7710
|
const [latestVersion, setLatestVersion] = useState5(null);
|
|
7205
7711
|
const updateAvailable = latestVersion && compareVersions(VERSION, latestVersion) < 0 ? latestVersion : null;
|
|
7206
7712
|
const [hookList, setHookList] = useState5(
|
|
@@ -7312,6 +7818,17 @@ function App({
|
|
|
7312
7818
|
cancelled = true;
|
|
7313
7819
|
};
|
|
7314
7820
|
}, [loop]);
|
|
7821
|
+
useEffect2(() => {
|
|
7822
|
+
let cancelled = false;
|
|
7823
|
+
void (async () => {
|
|
7824
|
+
const list = await loop.client.listModels().catch(() => null);
|
|
7825
|
+
if (cancelled || !list) return;
|
|
7826
|
+
setModels(list.data.map((m) => m.id));
|
|
7827
|
+
})();
|
|
7828
|
+
return () => {
|
|
7829
|
+
cancelled = true;
|
|
7830
|
+
};
|
|
7831
|
+
}, [loop]);
|
|
7315
7832
|
useEffect2(() => {
|
|
7316
7833
|
let cancelled = false;
|
|
7317
7834
|
void (async () => {
|
|
@@ -7356,7 +7873,7 @@ function App({
|
|
|
7356
7873
|
}
|
|
7357
7874
|
setSubagentActivity(null);
|
|
7358
7875
|
const seconds = ((ev.elapsedMs ?? 0) / 1e3).toFixed(1);
|
|
7359
|
-
const summary2 = ev.error ? `\
|
|
7876
|
+
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)`;
|
|
7360
7877
|
setHistorical((prev) => [
|
|
7361
7878
|
...prev,
|
|
7362
7879
|
{
|
|
@@ -7540,6 +8057,13 @@ function App({
|
|
|
7540
8057
|
const fresh = await getLatestVersion({ force: true });
|
|
7541
8058
|
if (fresh) setLatestVersion(fresh);
|
|
7542
8059
|
})();
|
|
8060
|
+
},
|
|
8061
|
+
models,
|
|
8062
|
+
refreshModels: () => {
|
|
8063
|
+
void (async () => {
|
|
8064
|
+
const list = await loop.client.listModels().catch(() => null);
|
|
8065
|
+
if (list) setModels(list.data.map((m) => m.id));
|
|
8066
|
+
})();
|
|
7543
8067
|
}
|
|
7544
8068
|
});
|
|
7545
8069
|
if (result.exit) {
|
|
@@ -7596,7 +8120,14 @@ function App({
|
|
|
7596
8120
|
if (promptReport.blocked) return;
|
|
7597
8121
|
}
|
|
7598
8122
|
promptHistory.current.push(text);
|
|
7599
|
-
setHistorical((prev) => [
|
|
8123
|
+
setHistorical((prev) => [
|
|
8124
|
+
...prev,
|
|
8125
|
+
// `leadSeparator`: thin rule above this user turn when history
|
|
8126
|
+
// isn't empty — visual pacing for multi-turn sessions. First
|
|
8127
|
+
// user message leaves it off so the UI doesn't open with a
|
|
8128
|
+
// dangling divider.
|
|
8129
|
+
{ id: `u-${Date.now()}`, role: "user", text, leadSeparator: prev.length > 0 }
|
|
8130
|
+
]);
|
|
7600
8131
|
const assistantId = `a-${Date.now()}`;
|
|
7601
8132
|
const streamRef = { id: assistantId, text: "", reasoning: "" };
|
|
7602
8133
|
const contentBuf = { current: "" };
|
|
@@ -7815,6 +8346,7 @@ function App({
|
|
|
7815
8346
|
latestVersion,
|
|
7816
8347
|
mcpSpecs,
|
|
7817
8348
|
mcpServers,
|
|
8349
|
+
models,
|
|
7818
8350
|
planMode,
|
|
7819
8351
|
session,
|
|
7820
8352
|
slashSelected,
|
|
@@ -7978,6 +8510,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
7978
8510
|
branchBudget: loop.branchOptions.budget,
|
|
7979
8511
|
planMode,
|
|
7980
8512
|
balance,
|
|
8513
|
+
busy,
|
|
7981
8514
|
updateAvailable
|
|
7982
8515
|
}
|
|
7983
8516
|
), /* @__PURE__ */ React12.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React12.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React12.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && subagentActivity ? /* @__PURE__ */ React12.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !ongoingTool && statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React12.createElement(
|
|
@@ -8015,7 +8548,7 @@ function SubagentRow({
|
|
|
8015
8548
|
}) {
|
|
8016
8549
|
const tick = useTick();
|
|
8017
8550
|
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
8018
|
-
return /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, ` \
|
|
8551
|
+
return /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, ` \u232C subagent: ${activity.task}`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
|
|
8019
8552
|
}
|
|
8020
8553
|
function OngoingToolRow({
|
|
8021
8554
|
tool,
|
|
@@ -8396,8 +8929,7 @@ async function codeCommand(opts = {}) {
|
|
|
8396
8929
|
);
|
|
8397
8930
|
await chatCommand({
|
|
8398
8931
|
model: opts.model ?? "deepseek-reasoner",
|
|
8399
|
-
harvest:
|
|
8400
|
-
// smart preset's harvest setting, always on for code
|
|
8932
|
+
harvest: opts.harvest ?? false,
|
|
8401
8933
|
system: codeSystemPrompt2(rootDir),
|
|
8402
8934
|
transcript: opts.transcript,
|
|
8403
8935
|
session,
|
|
@@ -9517,15 +10049,19 @@ program.command("setup").description("Interactive wizard \u2014 API key, preset,
|
|
|
9517
10049
|
await setupCommand({});
|
|
9518
10050
|
});
|
|
9519
10051
|
program.command("code [dir]").description(
|
|
9520
|
-
"Code-editing chat \u2014 filesystem
|
|
9521
|
-
).option("-m, --model <id>", "Override default reasoner model").option("--no-session", "Disable session persistence for this run").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option("--transcript <path>", "Write a JSONL transcript to this path").
|
|
10052
|
+
"Code-editing chat \u2014 filesystem tools rooted at <dir> (default: cwd), coding system prompt, deepseek-reasoner. Model proposes SEARCH/REPLACE blocks; Reasonix applies them to disk."
|
|
10053
|
+
).option("-m, --model <id>", "Override default reasoner model").option("--no-session", "Disable session persistence for this run").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option("--transcript <path>", "Write a JSONL transcript to this path").option(
|
|
10054
|
+
"--harvest",
|
|
10055
|
+
"Extract typed plan state from R1 reasoning (Pillar 2). Adds ~10-15% cost per turn. Off by default in code mode."
|
|
10056
|
+
).action(async (dir, opts) => {
|
|
9522
10057
|
await codeCommand({
|
|
9523
10058
|
dir,
|
|
9524
10059
|
model: opts.model,
|
|
9525
10060
|
noSession: opts.session === false,
|
|
9526
10061
|
transcript: opts.transcript,
|
|
9527
10062
|
forceResume: !!opts.resume,
|
|
9528
|
-
forceNew: !!opts.new
|
|
10063
|
+
forceNew: !!opts.new,
|
|
10064
|
+
harvest: !!opts.harvest
|
|
9529
10065
|
});
|
|
9530
10066
|
});
|
|
9531
10067
|
program.command("chat").description("Interactive Ink TUI with live cache/cost panel.").option("-m, --model <id>", "DeepSeek model id (overrides preset)").option("-s, --system <prompt>", "System prompt (pinned in the immutable prefix)", DEFAULT_SYSTEM).option("--transcript <path>", "Write a JSONL transcript to this path").option(
|