reasonix 0.12.19 → 0.12.21
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 +535 -100
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +32 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -181,8 +181,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
181
181
|
}
|
|
182
182
|
function sleep(ms, signal) {
|
|
183
183
|
if (ms <= 0) return Promise.resolve();
|
|
184
|
-
return new Promise((
|
|
185
|
-
const timer = setTimeout(
|
|
184
|
+
return new Promise((resolve14, reject) => {
|
|
185
|
+
const timer = setTimeout(resolve14, ms);
|
|
186
186
|
if (signal) {
|
|
187
187
|
const onAbort = () => {
|
|
188
188
|
clearTimeout(timer);
|
|
@@ -548,8 +548,8 @@ var defaultSelector = (samples) => {
|
|
|
548
548
|
})[0];
|
|
549
549
|
};
|
|
550
550
|
async function runBranches(client, request, opts = {}) {
|
|
551
|
-
const
|
|
552
|
-
const temperatures = resolveTemperatures(
|
|
551
|
+
const budget2 = Math.max(1, opts.budget ?? 1);
|
|
552
|
+
const temperatures = resolveTemperatures(budget2, opts.temperatures);
|
|
553
553
|
const selector = opts.selector ?? defaultSelector;
|
|
554
554
|
const samples = await Promise.all(
|
|
555
555
|
temperatures.map(async (temperature, index) => {
|
|
@@ -586,12 +586,12 @@ function aggregateBranchUsage(samples) {
|
|
|
586
586
|
promptCacheMissTokens
|
|
587
587
|
};
|
|
588
588
|
}
|
|
589
|
-
function resolveTemperatures(
|
|
590
|
-
if (custom && custom.length >=
|
|
591
|
-
if (
|
|
589
|
+
function resolveTemperatures(budget2, custom) {
|
|
590
|
+
if (custom && custom.length >= budget2) return [...custom.slice(0, budget2)];
|
|
591
|
+
if (budget2 === 1) return [0];
|
|
592
592
|
const out = [];
|
|
593
|
-
for (let i = 0; i <
|
|
594
|
-
out.push(Number((i / (
|
|
593
|
+
for (let i = 0; i < budget2; i++) {
|
|
594
|
+
out.push(Number((i / (budget2 - 1)).toFixed(2)));
|
|
595
595
|
}
|
|
596
596
|
return out;
|
|
597
597
|
}
|
|
@@ -668,7 +668,7 @@ function matchesTool(hook, toolName) {
|
|
|
668
668
|
}
|
|
669
669
|
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
670
670
|
function defaultSpawner(input) {
|
|
671
|
-
return new Promise((
|
|
671
|
+
return new Promise((resolve14) => {
|
|
672
672
|
const child = spawn(input.command, {
|
|
673
673
|
cwd: input.cwd,
|
|
674
674
|
shell: true,
|
|
@@ -713,7 +713,7 @@ function defaultSpawner(input) {
|
|
|
713
713
|
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
714
714
|
child.once("error", (err) => {
|
|
715
715
|
clearTimeout(timer);
|
|
716
|
-
|
|
716
|
+
resolve14({
|
|
717
717
|
exitCode: null,
|
|
718
718
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
719
719
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
@@ -724,7 +724,7 @@ function defaultSpawner(input) {
|
|
|
724
724
|
});
|
|
725
725
|
child.once("close", (code) => {
|
|
726
726
|
clearTimeout(timer);
|
|
727
|
-
|
|
727
|
+
resolve14({
|
|
728
728
|
exitCode: code,
|
|
729
729
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
730
730
|
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
@@ -1276,29 +1276,29 @@ function truncateForModelByTokens(s, maxTokens) {
|
|
|
1276
1276
|
|
|
1277
1277
|
${tail}`;
|
|
1278
1278
|
}
|
|
1279
|
-
function sizePrefixToTokens(s,
|
|
1280
|
-
if (
|
|
1281
|
-
let size = Math.min(s.length,
|
|
1279
|
+
function sizePrefixToTokens(s, budget2) {
|
|
1280
|
+
if (budget2 <= 0 || s.length === 0) return "";
|
|
1281
|
+
let size = Math.min(s.length, budget2 * 4);
|
|
1282
1282
|
for (let iter = 0; iter < 6; iter++) {
|
|
1283
1283
|
if (size <= 0) return "";
|
|
1284
1284
|
const slice = s.slice(0, size);
|
|
1285
1285
|
const count = countTokens(slice);
|
|
1286
|
-
if (count <=
|
|
1287
|
-
const next = Math.floor(size * (
|
|
1286
|
+
if (count <= budget2) return slice;
|
|
1287
|
+
const next = Math.floor(size * (budget2 / count) * 0.95);
|
|
1288
1288
|
if (next >= size) return s.slice(0, Math.max(0, size - 1));
|
|
1289
1289
|
size = next;
|
|
1290
1290
|
}
|
|
1291
1291
|
return s.slice(0, Math.max(0, size));
|
|
1292
1292
|
}
|
|
1293
|
-
function sizeSuffixToTokens(s,
|
|
1294
|
-
if (
|
|
1295
|
-
let size = Math.min(s.length,
|
|
1293
|
+
function sizeSuffixToTokens(s, budget2) {
|
|
1294
|
+
if (budget2 <= 0 || s.length === 0) return "";
|
|
1295
|
+
let size = Math.min(s.length, budget2 * 4);
|
|
1296
1296
|
for (let iter = 0; iter < 6; iter++) {
|
|
1297
1297
|
if (size <= 0) return "";
|
|
1298
1298
|
const slice = s.slice(-size);
|
|
1299
1299
|
const count = countTokens(slice);
|
|
1300
|
-
if (count <=
|
|
1301
|
-
const next = Math.floor(size * (
|
|
1300
|
+
if (count <= budget2) return slice;
|
|
1301
|
+
const next = Math.floor(size * (budget2 / count) * 0.95);
|
|
1302
1302
|
if (next >= size) return s.slice(-Math.max(0, size - 1));
|
|
1303
1303
|
size = next;
|
|
1304
1304
|
}
|
|
@@ -1999,6 +1999,19 @@ var CacheFirstLoop = class {
|
|
|
1999
1999
|
* flip it live alongside `model`.
|
|
2000
2000
|
*/
|
|
2001
2001
|
autoEscalate = true;
|
|
2002
|
+
/**
|
|
2003
|
+
* Soft USD budget — see {@link CacheFirstLoopOptions.budgetUsd}.
|
|
2004
|
+
* Mutable so `/budget` slash can set / change / clear it mid-session.
|
|
2005
|
+
* `null` (the default) disables all budget checks.
|
|
2006
|
+
*/
|
|
2007
|
+
budgetUsd;
|
|
2008
|
+
/**
|
|
2009
|
+
* Set the first time a turn crosses 80% of the budget so the warning
|
|
2010
|
+
* doesn't repeat every turn afterwards. Cleared by `setBudget` (any
|
|
2011
|
+
* change re-arms the warning, including raising the cap above the
|
|
2012
|
+
* current spend).
|
|
2013
|
+
*/
|
|
2014
|
+
_budgetWarned = false;
|
|
2002
2015
|
sessionName;
|
|
2003
2016
|
/**
|
|
2004
2017
|
* Hook list, mutable so `/hooks reload` can swap it without
|
|
@@ -2064,6 +2077,7 @@ var CacheFirstLoop = class {
|
|
|
2064
2077
|
this.model = opts.model ?? "deepseek-v4-flash";
|
|
2065
2078
|
this.reasoningEffort = opts.reasoningEffort ?? "max";
|
|
2066
2079
|
if (opts.autoEscalate !== void 0) this.autoEscalate = opts.autoEscalate;
|
|
2080
|
+
this.budgetUsd = typeof opts.budgetUsd === "number" && opts.budgetUsd > 0 ? opts.budgetUsd : null;
|
|
2067
2081
|
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
2068
2082
|
this.hooks = opts.hooks ?? [];
|
|
2069
2083
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
@@ -2303,6 +2317,16 @@ var CacheFirstLoop = class {
|
|
|
2303
2317
|
}
|
|
2304
2318
|
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
2305
2319
|
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Set / change / clear the soft USD budget. `null` (or any non-
|
|
2322
|
+
* positive number) disables the cap entirely. Re-arms the 80%
|
|
2323
|
+
* warning so a user who bumps the cap mid-session sees a fresh
|
|
2324
|
+
* threshold message at the new boundary.
|
|
2325
|
+
*/
|
|
2326
|
+
setBudget(usd) {
|
|
2327
|
+
this.budgetUsd = typeof usd === "number" && usd > 0 ? usd : null;
|
|
2328
|
+
this._budgetWarned = false;
|
|
2329
|
+
}
|
|
2306
2330
|
/**
|
|
2307
2331
|
* Arm pro for the next turn (consumed at turn start). Called by
|
|
2308
2332
|
* `/pro`. Idempotent — repeated calls stay armed, `disarmPro()`
|
|
@@ -2464,6 +2488,26 @@ var CacheFirstLoop = class {
|
|
|
2464
2488
|
return userText;
|
|
2465
2489
|
}
|
|
2466
2490
|
async *step(userInput) {
|
|
2491
|
+
if (this.budgetUsd !== null) {
|
|
2492
|
+
const spent = this.stats.totalCost;
|
|
2493
|
+
if (spent >= this.budgetUsd) {
|
|
2494
|
+
yield {
|
|
2495
|
+
turn: this._turn,
|
|
2496
|
+
role: "error",
|
|
2497
|
+
content: "",
|
|
2498
|
+
error: `session budget exhausted \u2014 spent $${spent.toFixed(4)} \u2265 cap $${this.budgetUsd.toFixed(2)}. Bump the cap with /budget <usd>, clear it with /budget off, or end the session.`
|
|
2499
|
+
};
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
if (!this._budgetWarned && spent >= this.budgetUsd * 0.8) {
|
|
2503
|
+
this._budgetWarned = true;
|
|
2504
|
+
yield {
|
|
2505
|
+
turn: this._turn,
|
|
2506
|
+
role: "warning",
|
|
2507
|
+
content: `\u25B2 budget 80% used \u2014 $${spent.toFixed(4)} of $${this.budgetUsd.toFixed(2)}. Next turn or two likely trips the cap.`
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2467
2511
|
this._turn++;
|
|
2468
2512
|
this.scratch.reset();
|
|
2469
2513
|
this.repair.resetStorm();
|
|
@@ -2560,14 +2604,14 @@ var CacheFirstLoop = class {
|
|
|
2560
2604
|
let preHarvestedPlanState;
|
|
2561
2605
|
try {
|
|
2562
2606
|
if (this.branchEnabled) {
|
|
2563
|
-
const
|
|
2607
|
+
const budget2 = this.branchOptions.budget ?? 1;
|
|
2564
2608
|
yield {
|
|
2565
2609
|
turn: this._turn,
|
|
2566
2610
|
role: "branch_start",
|
|
2567
2611
|
content: "",
|
|
2568
2612
|
branchProgress: {
|
|
2569
2613
|
completed: 0,
|
|
2570
|
-
total:
|
|
2614
|
+
total: budget2,
|
|
2571
2615
|
latestIndex: -1,
|
|
2572
2616
|
latestTemperature: -1,
|
|
2573
2617
|
latestUncertainties: -1
|
|
@@ -2601,9 +2645,9 @@ var CacheFirstLoop = class {
|
|
|
2601
2645
|
onSampleDone
|
|
2602
2646
|
}
|
|
2603
2647
|
);
|
|
2604
|
-
for (let k = 0; k <
|
|
2605
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2606
|
-
waiter =
|
|
2648
|
+
for (let k = 0; k < budget2; k++) {
|
|
2649
|
+
const sample = queue.shift() ?? await new Promise((resolve14) => {
|
|
2650
|
+
waiter = resolve14;
|
|
2607
2651
|
});
|
|
2608
2652
|
yield {
|
|
2609
2653
|
turn: this._turn,
|
|
@@ -2611,7 +2655,7 @@ var CacheFirstLoop = class {
|
|
|
2611
2655
|
content: "",
|
|
2612
2656
|
branchProgress: {
|
|
2613
2657
|
completed: k + 1,
|
|
2614
|
-
total:
|
|
2658
|
+
total: budget2,
|
|
2615
2659
|
latestIndex: sample.index,
|
|
2616
2660
|
latestTemperature: sample.temperature,
|
|
2617
2661
|
latestUncertainties: sample.planState.uncertainties.length
|
|
@@ -5400,7 +5444,7 @@ async function runCommand(cmd, opts) {
|
|
|
5400
5444
|
};
|
|
5401
5445
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
5402
5446
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
5403
|
-
return await new Promise((
|
|
5447
|
+
return await new Promise((resolve14, reject) => {
|
|
5404
5448
|
let child;
|
|
5405
5449
|
try {
|
|
5406
5450
|
child = spawn3(bin, args, effectiveSpawnOpts);
|
|
@@ -5445,7 +5489,7 @@ async function runCommand(cmd, opts) {
|
|
|
5445
5489
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
5446
5490
|
|
|
5447
5491
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
5448
|
-
|
|
5492
|
+
resolve14({ exitCode: code, output, timedOut });
|
|
5449
5493
|
});
|
|
5450
5494
|
});
|
|
5451
5495
|
}
|
|
@@ -6788,7 +6832,7 @@ var McpClient = class {
|
|
|
6788
6832
|
const id = this.nextId++;
|
|
6789
6833
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
6790
6834
|
let abortHandler = null;
|
|
6791
|
-
const promise = new Promise((
|
|
6835
|
+
const promise = new Promise((resolve14, reject) => {
|
|
6792
6836
|
const timeout = setTimeout(() => {
|
|
6793
6837
|
this.pending.delete(id);
|
|
6794
6838
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -6797,7 +6841,7 @@ var McpClient = class {
|
|
|
6797
6841
|
);
|
|
6798
6842
|
}, this.requestTimeoutMs);
|
|
6799
6843
|
this.pending.set(id, {
|
|
6800
|
-
resolve:
|
|
6844
|
+
resolve: resolve14,
|
|
6801
6845
|
reject,
|
|
6802
6846
|
timeout
|
|
6803
6847
|
});
|
|
@@ -6920,12 +6964,12 @@ var StdioTransport = class {
|
|
|
6920
6964
|
}
|
|
6921
6965
|
async send(message) {
|
|
6922
6966
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
6923
|
-
return new Promise((
|
|
6967
|
+
return new Promise((resolve14, reject) => {
|
|
6924
6968
|
const line = `${JSON.stringify(message)}
|
|
6925
6969
|
`;
|
|
6926
6970
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
6927
6971
|
if (err) reject(err);
|
|
6928
|
-
else
|
|
6972
|
+
else resolve14();
|
|
6929
6973
|
});
|
|
6930
6974
|
});
|
|
6931
6975
|
}
|
|
@@ -6936,8 +6980,8 @@ var StdioTransport = class {
|
|
|
6936
6980
|
continue;
|
|
6937
6981
|
}
|
|
6938
6982
|
if (this.closed) return;
|
|
6939
|
-
const next = await new Promise((
|
|
6940
|
-
this.waiters.push(
|
|
6983
|
+
const next = await new Promise((resolve14) => {
|
|
6984
|
+
this.waiters.push(resolve14);
|
|
6941
6985
|
});
|
|
6942
6986
|
if (next === null) return;
|
|
6943
6987
|
yield next;
|
|
@@ -7003,8 +7047,8 @@ var SseTransport = class {
|
|
|
7003
7047
|
constructor(opts) {
|
|
7004
7048
|
this.url = opts.url;
|
|
7005
7049
|
this.headers = opts.headers ?? {};
|
|
7006
|
-
this.endpointReady = new Promise((
|
|
7007
|
-
this.resolveEndpoint =
|
|
7050
|
+
this.endpointReady = new Promise((resolve14, reject) => {
|
|
7051
|
+
this.resolveEndpoint = resolve14;
|
|
7008
7052
|
this.rejectEndpoint = reject;
|
|
7009
7053
|
});
|
|
7010
7054
|
this.endpointReady.catch(() => void 0);
|
|
@@ -7031,8 +7075,8 @@ var SseTransport = class {
|
|
|
7031
7075
|
continue;
|
|
7032
7076
|
}
|
|
7033
7077
|
if (this.closed) return;
|
|
7034
|
-
const next = await new Promise((
|
|
7035
|
-
this.waiters.push(
|
|
7078
|
+
const next = await new Promise((resolve14) => {
|
|
7079
|
+
this.waiters.push(resolve14);
|
|
7036
7080
|
});
|
|
7037
7081
|
if (next === null) return;
|
|
7038
7082
|
yield next;
|
|
@@ -7219,8 +7263,8 @@ var StreamableHttpTransport = class {
|
|
|
7219
7263
|
continue;
|
|
7220
7264
|
}
|
|
7221
7265
|
if (this.closed) return;
|
|
7222
|
-
const next = await new Promise((
|
|
7223
|
-
this.waiters.push(
|
|
7266
|
+
const next = await new Promise((resolve14) => {
|
|
7267
|
+
this.waiters.push(resolve14);
|
|
7224
7268
|
});
|
|
7225
7269
|
if (next === null) return;
|
|
7226
7270
|
yield next;
|
|
@@ -9688,7 +9732,7 @@ async function startOllamaDaemon(opts = {}) {
|
|
|
9688
9732
|
return { ready: false, pid };
|
|
9689
9733
|
}
|
|
9690
9734
|
async function pullOllamaModel(modelName, opts = {}) {
|
|
9691
|
-
return new Promise((
|
|
9735
|
+
return new Promise((resolve14) => {
|
|
9692
9736
|
const child = spawn5("ollama", ["pull", modelName], {
|
|
9693
9737
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9694
9738
|
windowsHide: true
|
|
@@ -9700,8 +9744,8 @@ async function pullOllamaModel(modelName, opts = {}) {
|
|
|
9700
9744
|
}
|
|
9701
9745
|
streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
|
|
9702
9746
|
streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
|
|
9703
|
-
child.once("exit", (code) =>
|
|
9704
|
-
child.once("error", () =>
|
|
9747
|
+
child.once("exit", (code) => resolve14(code ?? -1));
|
|
9748
|
+
child.once("error", () => resolve14(-1));
|
|
9705
9749
|
});
|
|
9706
9750
|
}
|
|
9707
9751
|
function streamLines(stream, cb) {
|
|
@@ -10546,7 +10590,7 @@ var MAX_BODY_BYTES = 256 * 1024;
|
|
|
10546
10590
|
async function readBody(req) {
|
|
10547
10591
|
let total = 0;
|
|
10548
10592
|
const chunks = [];
|
|
10549
|
-
return new Promise((
|
|
10593
|
+
return new Promise((resolve14, reject) => {
|
|
10550
10594
|
req.on("data", (chunk) => {
|
|
10551
10595
|
total += chunk.length;
|
|
10552
10596
|
if (total > MAX_BODY_BYTES) {
|
|
@@ -10556,7 +10600,7 @@ async function readBody(req) {
|
|
|
10556
10600
|
}
|
|
10557
10601
|
chunks.push(chunk);
|
|
10558
10602
|
});
|
|
10559
|
-
req.on("end", () =>
|
|
10603
|
+
req.on("end", () => resolve14(Buffer.concat(chunks).toString("utf8")));
|
|
10560
10604
|
req.on("error", reject);
|
|
10561
10605
|
});
|
|
10562
10606
|
}
|
|
@@ -10633,7 +10677,7 @@ function startDashboardServer(ctx, opts = {}) {
|
|
|
10633
10677
|
const token = opts.token ?? mintToken();
|
|
10634
10678
|
const host = opts.host ?? "127.0.0.1";
|
|
10635
10679
|
const port = opts.port ?? 0;
|
|
10636
|
-
return new Promise((
|
|
10680
|
+
return new Promise((resolve14, reject) => {
|
|
10637
10681
|
const server = createServer((req, res) => {
|
|
10638
10682
|
dispatch(req, res, ctx, token).catch((err) => {
|
|
10639
10683
|
if (!res.headersSent) {
|
|
@@ -10654,7 +10698,7 @@ function startDashboardServer(ctx, opts = {}) {
|
|
|
10654
10698
|
server.close(() => doneResolve());
|
|
10655
10699
|
setTimeout(() => server.closeAllConnections?.(), 1e3).unref();
|
|
10656
10700
|
});
|
|
10657
|
-
|
|
10701
|
+
resolve14({ url, token, port: finalPort, close });
|
|
10658
10702
|
});
|
|
10659
10703
|
});
|
|
10660
10704
|
}
|
|
@@ -11270,8 +11314,8 @@ function SelectRow({
|
|
|
11270
11314
|
active,
|
|
11271
11315
|
marker
|
|
11272
11316
|
}) {
|
|
11273
|
-
const
|
|
11274
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React4.createElement(Box3, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, item.hint)) : null);
|
|
11317
|
+
const color2 = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
11318
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color: color2 }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React4.createElement(Box3, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, item.hint)) : null);
|
|
11275
11319
|
}
|
|
11276
11320
|
function findNextEnabled(items, from, step) {
|
|
11277
11321
|
if (items.length === 0) return 0;
|
|
@@ -11401,13 +11445,13 @@ var MIN_DIFF_ROWS = 8;
|
|
|
11401
11445
|
function EditConfirm({ block, onChoose }) {
|
|
11402
11446
|
const { stdout: stdout3 } = useStdout2();
|
|
11403
11447
|
const rows = stdout3?.rows ?? 40;
|
|
11404
|
-
const
|
|
11448
|
+
const budget2 = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
|
|
11405
11449
|
const allLines = useMemo(
|
|
11406
11450
|
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
11407
11451
|
[block]
|
|
11408
11452
|
);
|
|
11409
11453
|
const [scroll, setScroll] = useState2(0);
|
|
11410
|
-
const maxScroll = Math.max(0, allLines.length -
|
|
11454
|
+
const maxScroll = Math.max(0, allLines.length - budget2);
|
|
11411
11455
|
const effectiveScroll = Math.min(scroll, maxScroll);
|
|
11412
11456
|
useKeystroke((ev) => {
|
|
11413
11457
|
if (ev.paste) return;
|
|
@@ -11438,11 +11482,11 @@ function EditConfirm({ block, onChoose }) {
|
|
|
11438
11482
|
return;
|
|
11439
11483
|
}
|
|
11440
11484
|
if (key.pageDown || input === " " || input === "f") {
|
|
11441
|
-
setScroll((s) => Math.min(maxScroll, s + Math.max(1,
|
|
11485
|
+
setScroll((s) => Math.min(maxScroll, s + Math.max(1, budget2 - 2)));
|
|
11442
11486
|
return;
|
|
11443
11487
|
}
|
|
11444
11488
|
if (key.pageUp || input === "b") {
|
|
11445
|
-
setScroll((s) => Math.max(0, s - Math.max(1,
|
|
11489
|
+
setScroll((s) => Math.max(0, s - Math.max(1, budget2 - 2)));
|
|
11446
11490
|
return;
|
|
11447
11491
|
}
|
|
11448
11492
|
if (input === "g") {
|
|
@@ -11458,9 +11502,9 @@ function EditConfirm({ block, onChoose }) {
|
|
|
11458
11502
|
const removed = isNew ? 0 : (block.search.match(/\n/g)?.length ?? 0) + 1;
|
|
11459
11503
|
const added = block.replace === "" ? 0 : (block.replace.match(/\n/g)?.length ?? 0) + 1;
|
|
11460
11504
|
const tag = isNew ? "NEW" : "EDIT";
|
|
11461
|
-
const visibleLines = allLines.slice(effectiveScroll, effectiveScroll +
|
|
11505
|
+
const visibleLines = allLines.slice(effectiveScroll, effectiveScroll + budget2);
|
|
11462
11506
|
const hiddenAbove = effectiveScroll;
|
|
11463
|
-
const hiddenBelow = Math.max(0, allLines.length - effectiveScroll -
|
|
11507
|
+
const hiddenBelow = Math.max(0, allLines.length - effectiveScroll - budget2);
|
|
11464
11508
|
const totalLines = allLines.length;
|
|
11465
11509
|
const showScrollHud = hiddenAbove + hiddenBelow > 0;
|
|
11466
11510
|
const subtitleParts = [`-${removed} +${added} lines`];
|
|
@@ -11486,13 +11530,13 @@ function EditConfirm({ block, onChoose }) {
|
|
|
11486
11530
|
) : null,
|
|
11487
11531
|
/* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, visibleLines.map((line, i) => {
|
|
11488
11532
|
const trimmed = line.trimStart();
|
|
11489
|
-
const
|
|
11490
|
-
const dim = !
|
|
11533
|
+
const color2 = trimmed.startsWith("+") ? "#4ade80" : trimmed.startsWith("-") ? "#f87171" : void 0;
|
|
11534
|
+
const dim = !color2;
|
|
11491
11535
|
return /* @__PURE__ */ React6.createElement(
|
|
11492
11536
|
Text4,
|
|
11493
11537
|
{
|
|
11494
11538
|
key: `diff-${effectiveScroll}-${i}`,
|
|
11495
|
-
color,
|
|
11539
|
+
color: color2,
|
|
11496
11540
|
dimColor: dim
|
|
11497
11541
|
},
|
|
11498
11542
|
line
|
|
@@ -11526,7 +11570,7 @@ function PlanStateBlock({ planState }) {
|
|
|
11526
11570
|
if (planState.rejectedPaths.length)
|
|
11527
11571
|
fields.push(["rejected", planState.rejectedPaths, "#94a3b8", true]);
|
|
11528
11572
|
if (fields.length === 0) return null;
|
|
11529
|
-
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items,
|
|
11573
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color2, dim]) => /* @__PURE__ */ React7.createElement(Box6, { key: label }, /* @__PURE__ */ React7.createElement(Text5, { backgroundColor: color2, color: "black", bold: true, dimColor: dim }, ` ${label} ${items.length} `), /* @__PURE__ */ React7.createElement(Text5, null, " "), /* @__PURE__ */ React7.createElement(Text5, { dimColor: dim }, items.join(" \xB7 ")))));
|
|
11530
11574
|
}
|
|
11531
11575
|
|
|
11532
11576
|
// src/cli/ui/PlanStepList.tsx
|
|
@@ -12437,8 +12481,8 @@ function gradientCells(width, glyph = GLYPH.block) {
|
|
|
12437
12481
|
const t2 = width === 1 ? 0 : i * last / (width - 1);
|
|
12438
12482
|
const lo = Math.floor(t2);
|
|
12439
12483
|
const hi = Math.min(last, lo + 1);
|
|
12440
|
-
const
|
|
12441
|
-
cells.push({ ch: glyph, color });
|
|
12484
|
+
const color2 = t2 - lo < 0.5 ? GRADIENT[lo] : GRADIENT[hi];
|
|
12485
|
+
cells.push({ ch: glyph, color: color2 });
|
|
12442
12486
|
}
|
|
12443
12487
|
return cells;
|
|
12444
12488
|
}
|
|
@@ -12615,9 +12659,9 @@ var ROLE_GLYPH = {
|
|
|
12615
12659
|
};
|
|
12616
12660
|
function RoleGlyph({
|
|
12617
12661
|
glyph,
|
|
12618
|
-
color
|
|
12662
|
+
color: color2
|
|
12619
12663
|
}) {
|
|
12620
|
-
return /* @__PURE__ */ React11.createElement(Text8, { color, bold: true }, glyph);
|
|
12664
|
+
return /* @__PURE__ */ React11.createElement(Text8, { color: color2, bold: true }, glyph);
|
|
12621
12665
|
}
|
|
12622
12666
|
function ToolPill({ label, status: status2 }) {
|
|
12623
12667
|
const bg = status2 === "err" ? "red" : "yellow";
|
|
@@ -13547,13 +13591,13 @@ function buildViewport(line, cursorCol, visibleCells, pastes) {
|
|
|
13547
13591
|
return clipAroundCursor(line, cursorCol, visibleCells, pastes);
|
|
13548
13592
|
}
|
|
13549
13593
|
function clipFromLeft(line, visibleCells, pastes) {
|
|
13550
|
-
const
|
|
13594
|
+
const budget2 = Math.max(1, visibleCells - 1);
|
|
13551
13595
|
let used = 0;
|
|
13552
13596
|
let end = 0;
|
|
13553
13597
|
while (end < line.length) {
|
|
13554
13598
|
const ch = line[end];
|
|
13555
13599
|
const cw = charCellsAt(line, end, pastes);
|
|
13556
|
-
if (used + cw >
|
|
13600
|
+
if (used + cw > budget2) break;
|
|
13557
13601
|
used += cw;
|
|
13558
13602
|
end++;
|
|
13559
13603
|
}
|
|
@@ -13561,10 +13605,10 @@ function clipFromLeft(line, visibleCells, pastes) {
|
|
|
13561
13605
|
return { segments, cursorCell: null, hiddenLeft: false, hiddenRight: end < line.length };
|
|
13562
13606
|
}
|
|
13563
13607
|
function clipAroundCursor(line, cursorCol, visibleCells, pastes) {
|
|
13564
|
-
let
|
|
13608
|
+
let budget2 = visibleCells;
|
|
13565
13609
|
const reservedForMarkers = 2;
|
|
13566
|
-
|
|
13567
|
-
const halfBudget = Math.floor(
|
|
13610
|
+
budget2 = Math.max(1, budget2 - reservedForMarkers);
|
|
13611
|
+
const halfBudget = Math.floor(budget2 / 2);
|
|
13568
13612
|
let start = cursorCol;
|
|
13569
13613
|
let leftCells = 0;
|
|
13570
13614
|
while (start > 0 && leftCells < halfBudget) {
|
|
@@ -13573,7 +13617,7 @@ function clipAroundCursor(line, cursorCol, visibleCells, pastes) {
|
|
|
13573
13617
|
start--;
|
|
13574
13618
|
leftCells += cw;
|
|
13575
13619
|
}
|
|
13576
|
-
const rightBudget =
|
|
13620
|
+
const rightBudget = budget2 - leftCells;
|
|
13577
13621
|
let end = cursorCol;
|
|
13578
13622
|
let rightCells = 0;
|
|
13579
13623
|
const cursorChar = cursorCol < line.length ? charCellsAt(line, cursorCol, pastes) : 1;
|
|
@@ -14080,7 +14124,8 @@ function StatsPanel({
|
|
|
14080
14124
|
busy,
|
|
14081
14125
|
proArmed,
|
|
14082
14126
|
escalated,
|
|
14083
|
-
dashboardUrl
|
|
14127
|
+
dashboardUrl,
|
|
14128
|
+
budgetUsd
|
|
14084
14129
|
}) {
|
|
14085
14130
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
14086
14131
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model2] ?? DEFAULT_CONTEXT_TOKENS;
|
|
@@ -14126,7 +14171,12 @@ function StatsPanel({
|
|
|
14126
14171
|
balance,
|
|
14127
14172
|
coldStart
|
|
14128
14173
|
}
|
|
14129
|
-
));
|
|
14174
|
+
), budgetUsd !== null && budgetUsd !== void 0 ? /* @__PURE__ */ React21.createElement(BudgetRow, { spent: summary.totalCostUsd, cap: budgetUsd }) : null);
|
|
14175
|
+
}
|
|
14176
|
+
function BudgetRow({ spent, cap }) {
|
|
14177
|
+
const pct2 = Math.max(0, spent / cap * 100);
|
|
14178
|
+
const color2 = pct2 >= 100 ? "#f87171" : pct2 >= 80 ? "#fbbf24" : "#94a3b8";
|
|
14179
|
+
return /* @__PURE__ */ React21.createElement(Box19, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " budget "), /* @__PURE__ */ React21.createElement(Text17, { color: color2 }, `$${spent.toFixed(4)} / $${cap.toFixed(2)}`, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, ` (${pct2.toFixed(0)}%)`)));
|
|
14130
14180
|
}
|
|
14131
14181
|
function Header({
|
|
14132
14182
|
model: model2,
|
|
@@ -14196,9 +14246,9 @@ function ContextCell({
|
|
|
14196
14246
|
if (promptTokens === 0) {
|
|
14197
14247
|
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info, dimColor: true }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u2014 (no turns yet)"));
|
|
14198
14248
|
}
|
|
14199
|
-
const
|
|
14249
|
+
const color2 = ratio >= 0.8 ? COLOR.err : ratio >= 0.6 ? COLOR.warn : COLOR.ok;
|
|
14200
14250
|
const pct2 = Math.round(ratio * 100);
|
|
14201
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Bar, { ratio, color, cells: showBar ? 14 : 10 }), /* @__PURE__ */ React21.createElement(Text17, null, " "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.err, bold: true }, " \xB7 /compact") : null);
|
|
14251
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Bar, { ratio, color: color2, cells: showBar ? 14 : 10 }), /* @__PURE__ */ React21.createElement(Text17, null, " "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.err, bold: true }, " \xB7 /compact") : null);
|
|
14202
14252
|
}
|
|
14203
14253
|
function CacheCell({
|
|
14204
14254
|
hitRatio,
|
|
@@ -14212,8 +14262,8 @@ function CacheCell({
|
|
|
14212
14262
|
if (coldStart) {
|
|
14213
14263
|
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info, dimColor: true }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true, italic: true }, "(cold start)"));
|
|
14214
14264
|
}
|
|
14215
|
-
const
|
|
14216
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, pct2, "%"));
|
|
14265
|
+
const color2 = hitRatio >= 0.7 ? COLOR.ok : hitRatio >= 0.4 ? COLOR.warn : COLOR.err;
|
|
14266
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, pct2, "%"));
|
|
14217
14267
|
}
|
|
14218
14268
|
function turnCostColor(cost) {
|
|
14219
14269
|
if (cost <= 0) return void 0;
|
|
@@ -14239,16 +14289,16 @@ function CostCell({
|
|
|
14239
14289
|
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25F4 turn "), /* @__PURE__ */ React21.createElement(Text17, { color: turnColor, bold: !coldStart, dimColor: coldStart }, "$", summary.lastTurnCostUsd.toFixed(4)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " \xB7 session "), /* @__PURE__ */ React21.createElement(Text17, { color: sessionColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(4)));
|
|
14240
14290
|
}
|
|
14241
14291
|
function BalanceCell({ balance }) {
|
|
14242
|
-
const
|
|
14243
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25D0 balance "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
14292
|
+
const color2 = balance.total < 1 ? COLOR.err : balance.total < 5 ? COLOR.warn : COLOR.ok;
|
|
14293
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25D0 balance "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
14244
14294
|
}
|
|
14245
14295
|
function Bar({
|
|
14246
14296
|
ratio,
|
|
14247
|
-
color,
|
|
14297
|
+
color: color2,
|
|
14248
14298
|
cells = 14
|
|
14249
14299
|
}) {
|
|
14250
14300
|
const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
|
|
14251
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color }, "\u25B0".repeat(filled)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u25B1".repeat(cells - filled)));
|
|
14301
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: color2 }, "\u25B0".repeat(filled)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u25B1".repeat(cells - filled)));
|
|
14252
14302
|
}
|
|
14253
14303
|
function formatTokens(n) {
|
|
14254
14304
|
if (n < 1024) return String(n);
|
|
@@ -14828,6 +14878,12 @@ var SLASH_COMMANDS = [
|
|
|
14828
14878
|
summary: "arm v4-pro for the NEXT turn only (one-shot \xB7 auto-disarms after turn)",
|
|
14829
14879
|
argCompleter: ["off"]
|
|
14830
14880
|
},
|
|
14881
|
+
{
|
|
14882
|
+
cmd: "budget",
|
|
14883
|
+
argsHint: "[usd|off]",
|
|
14884
|
+
summary: "session USD cap \u2014 warns at 80%, refuses next turn at 100%. Off by default. /budget alone shows status",
|
|
14885
|
+
argCompleter: ["off", "1", "5", "10", "20", "50"]
|
|
14886
|
+
},
|
|
14831
14887
|
{ cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
|
|
14832
14888
|
{
|
|
14833
14889
|
cmd: "resource",
|
|
@@ -16317,6 +16373,42 @@ var pro = (args, loop2, ctx) => {
|
|
|
16317
16373
|
};
|
|
16318
16374
|
};
|
|
16319
16375
|
var ESCALATION_MODEL_ID = "deepseek-v4-pro";
|
|
16376
|
+
var budget = (args, loop2) => {
|
|
16377
|
+
const arg = args[0]?.trim() ?? "";
|
|
16378
|
+
if (arg === "") {
|
|
16379
|
+
if (loop2.budgetUsd === null) {
|
|
16380
|
+
return {
|
|
16381
|
+
info: "no session budget set \u2014 Reasonix will keep going until you stop it. Set one with: /budget <usd> (e.g. /budget 5)"
|
|
16382
|
+
};
|
|
16383
|
+
}
|
|
16384
|
+
const spent2 = loop2.stats.totalCost;
|
|
16385
|
+
const pct2 = spent2 / loop2.budgetUsd * 100;
|
|
16386
|
+
return {
|
|
16387
|
+
info: `budget: $${spent2.toFixed(4)} of $${loop2.budgetUsd.toFixed(2)} (${pct2.toFixed(1)}%) \xB7 /budget off to clear, /budget <usd> to change`
|
|
16388
|
+
};
|
|
16389
|
+
}
|
|
16390
|
+
if (arg === "off" || arg === "none" || arg === "0") {
|
|
16391
|
+
loop2.setBudget(null);
|
|
16392
|
+
return { info: "budget \u2192 off (no cap)" };
|
|
16393
|
+
}
|
|
16394
|
+
const cleaned = arg.replace(/^\$/, "");
|
|
16395
|
+
const usd = Number(cleaned);
|
|
16396
|
+
if (!Number.isFinite(usd) || usd <= 0) {
|
|
16397
|
+
return {
|
|
16398
|
+
info: `usage: /budget <usd> (got "${arg}" \u2014 must be a positive number, e.g. /budget 5 or /budget 12.50)`
|
|
16399
|
+
};
|
|
16400
|
+
}
|
|
16401
|
+
loop2.setBudget(usd);
|
|
16402
|
+
const spent = loop2.stats.totalCost;
|
|
16403
|
+
if (spent >= usd) {
|
|
16404
|
+
return {
|
|
16405
|
+
info: `\u25B2 budget \u2192 $${usd.toFixed(2)} but already spent $${spent.toFixed(4)}. Next turn will be refused \u2014 bump the cap higher to keep going, or end the session.`
|
|
16406
|
+
};
|
|
16407
|
+
}
|
|
16408
|
+
return {
|
|
16409
|
+
info: `budget \u2192 $${usd.toFixed(2)} (so far: $${spent.toFixed(4)} \xB7 warns at 80%, refuses next turn at 100% \xB7 /budget off to clear)`
|
|
16410
|
+
};
|
|
16411
|
+
};
|
|
16320
16412
|
var handlers9 = {
|
|
16321
16413
|
model,
|
|
16322
16414
|
models,
|
|
@@ -16324,7 +16416,8 @@ var handlers9 = {
|
|
|
16324
16416
|
preset,
|
|
16325
16417
|
branch,
|
|
16326
16418
|
effort,
|
|
16327
|
-
pro
|
|
16419
|
+
pro,
|
|
16420
|
+
budget
|
|
16328
16421
|
};
|
|
16329
16422
|
|
|
16330
16423
|
// src/cli/ui/slash/handlers/observability.ts
|
|
@@ -17577,6 +17670,7 @@ function App({
|
|
|
17577
17670
|
transcript,
|
|
17578
17671
|
harvest: harvest3,
|
|
17579
17672
|
branch: branch2,
|
|
17673
|
+
budgetUsd,
|
|
17580
17674
|
session,
|
|
17581
17675
|
tools,
|
|
17582
17676
|
mcpSpecs,
|
|
@@ -17790,6 +17884,7 @@ function App({
|
|
|
17790
17884
|
model: model2,
|
|
17791
17885
|
harvest: harvest3,
|
|
17792
17886
|
branch: branch2,
|
|
17887
|
+
budgetUsd,
|
|
17793
17888
|
session,
|
|
17794
17889
|
hooks: hookList,
|
|
17795
17890
|
hookCwd: currentRootDir,
|
|
@@ -17800,7 +17895,7 @@ function App({
|
|
|
17800
17895
|
});
|
|
17801
17896
|
loopRef.current = l;
|
|
17802
17897
|
return l;
|
|
17803
|
-
}, [model2, system, harvest3, branch2, session, tools, codeMode]);
|
|
17898
|
+
}, [model2, system, harvest3, branch2, budgetUsd, session, tools, codeMode]);
|
|
17804
17899
|
useEffect6(() => {
|
|
17805
17900
|
loop2.hooks = hookList;
|
|
17806
17901
|
}, [loop2, hookList]);
|
|
@@ -18129,11 +18224,11 @@ function App({
|
|
|
18129
18224
|
if (key.escape && busy) {
|
|
18130
18225
|
if (abortedThisTurn.current) return;
|
|
18131
18226
|
abortedThisTurn.current = true;
|
|
18132
|
-
const
|
|
18133
|
-
if (
|
|
18227
|
+
const resolve14 = editReviewResolveRef.current;
|
|
18228
|
+
if (resolve14) {
|
|
18134
18229
|
editReviewResolveRef.current = null;
|
|
18135
18230
|
setPendingEditReview(null);
|
|
18136
|
-
|
|
18231
|
+
resolve14("reject");
|
|
18137
18232
|
}
|
|
18138
18233
|
if (activeLoopRef.current) stopLoop();
|
|
18139
18234
|
loop2.abort();
|
|
@@ -18599,11 +18694,11 @@ function App({
|
|
|
18599
18694
|
handleStagedInputSubmitRef.current(text ?? "", { plan: plan2, mode: choice }).catch(() => void 0);
|
|
18600
18695
|
},
|
|
18601
18696
|
resolveEditReview: (choice) => {
|
|
18602
|
-
const
|
|
18603
|
-
if (
|
|
18697
|
+
const resolve14 = editReviewResolveRef.current;
|
|
18698
|
+
if (resolve14) {
|
|
18604
18699
|
editReviewResolveRef.current = null;
|
|
18605
18700
|
setPendingEditReview(null);
|
|
18606
|
-
|
|
18701
|
+
resolve14(choice);
|
|
18607
18702
|
}
|
|
18608
18703
|
},
|
|
18609
18704
|
resolveWorkspaceConfirm: (choice) => {
|
|
@@ -20045,7 +20140,8 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
20045
20140
|
updateAvailable,
|
|
20046
20141
|
proArmed,
|
|
20047
20142
|
escalated: turnOnPro,
|
|
20048
|
-
dashboardUrl
|
|
20143
|
+
dashboardUrl,
|
|
20144
|
+
budgetUsd: loop2.budgetUsd
|
|
20049
20145
|
}
|
|
20050
20146
|
), /* @__PURE__ */ React24.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React24.createElement(EventRow, { key: item.id, event: item, projectRoot: currentRootDir })), !historical.some((e) => e.role === "user" || e.role === "assistant") && !busy && !streaming ? /* @__PURE__ */ React24.createElement(WelcomeBanner, { inCodeMode: !!codeMode }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && streaming ? /* @__PURE__ */ React24.createElement(Box22, { marginY: 1 }, /* @__PURE__ */ React24.createElement(EventRow, { event: streaming, projectRoot: currentRootDir })) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && ongoingTool ? /* @__PURE__ */ React24.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && subagentActivity ? /* @__PURE__ */ React24.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !ongoingTool && statusLine ? /* @__PURE__ */ React24.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision ? /* @__PURE__ */ React24.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React24.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React24.createElement(
|
|
20051
20147
|
PlanRefineInput,
|
|
@@ -20128,10 +20224,10 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
20128
20224
|
{
|
|
20129
20225
|
block: pendingEditReview,
|
|
20130
20226
|
onChoose: (choice) => {
|
|
20131
|
-
const
|
|
20132
|
-
if (
|
|
20227
|
+
const resolve14 = editReviewResolveRef.current;
|
|
20228
|
+
if (resolve14) {
|
|
20133
20229
|
editReviewResolveRef.current = null;
|
|
20134
|
-
|
|
20230
|
+
resolve14(choice);
|
|
20135
20231
|
}
|
|
20136
20232
|
}
|
|
20137
20233
|
}
|
|
@@ -20316,6 +20412,7 @@ function Root({
|
|
|
20316
20412
|
transcript: appProps.transcript,
|
|
20317
20413
|
harvest: appProps.harvest,
|
|
20318
20414
|
branch: appProps.branch,
|
|
20415
|
+
budgetUsd: appProps.budgetUsd,
|
|
20319
20416
|
session: appProps.session,
|
|
20320
20417
|
tools,
|
|
20321
20418
|
mcpSpecs,
|
|
@@ -20477,6 +20574,7 @@ async function codeCommand(opts = {}) {
|
|
|
20477
20574
|
await chatCommand({
|
|
20478
20575
|
model: opts.model ?? "deepseek-v4-flash",
|
|
20479
20576
|
harvest: opts.harvest ?? false,
|
|
20577
|
+
budgetUsd: opts.budgetUsd,
|
|
20480
20578
|
system: codeSystemPrompt2(rootDir, { hasSemanticSearch: semantic2.enabled }),
|
|
20481
20579
|
transcript: opts.transcript,
|
|
20482
20580
|
session,
|
|
@@ -20528,8 +20626,8 @@ function CacheBadge({ usage }) {
|
|
|
20528
20626
|
const total = hit + miss;
|
|
20529
20627
|
if (total === 0) return null;
|
|
20530
20628
|
const pct2 = hit / total * 100;
|
|
20531
|
-
const
|
|
20532
|
-
return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color }, pct2.toFixed(1), "%"));
|
|
20629
|
+
const color2 = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
20630
|
+
return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color: color2 }, pct2.toFixed(1), "%"));
|
|
20533
20631
|
}
|
|
20534
20632
|
function truncate2(s, max) {
|
|
20535
20633
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -20652,8 +20750,313 @@ markdown report written to ${opts.mdPath}`);
|
|
|
20652
20750
|
console.log(renderSummaryTable(report));
|
|
20653
20751
|
}
|
|
20654
20752
|
|
|
20753
|
+
// src/cli/commands/doctor.ts
|
|
20754
|
+
import { existsSync as existsSync23, statSync as statSync14 } from "fs";
|
|
20755
|
+
import { homedir as homedir10 } from "os";
|
|
20756
|
+
import { dirname as dirname16, join as join21, resolve as resolve12 } from "path";
|
|
20757
|
+
var TTY = process.stdout.isTTY && process.env.TERM !== "dumb";
|
|
20758
|
+
function color(text, code) {
|
|
20759
|
+
if (!TTY) return text;
|
|
20760
|
+
return `\x1B[${code}m${text}\x1B[0m`;
|
|
20761
|
+
}
|
|
20762
|
+
function badge(level) {
|
|
20763
|
+
if (level === "ok") return color("\u2713", "32");
|
|
20764
|
+
if (level === "warn") return color("\u26A0", "33");
|
|
20765
|
+
return color("\u2717", "31");
|
|
20766
|
+
}
|
|
20767
|
+
function tail4(s) {
|
|
20768
|
+
return s.length <= 4 ? s : `\u2026${s.slice(-4)}`;
|
|
20769
|
+
}
|
|
20770
|
+
function fmtBytes(n) {
|
|
20771
|
+
if (n < 1024) return `${n} B`;
|
|
20772
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
20773
|
+
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
20774
|
+
}
|
|
20775
|
+
async function checkApiKey() {
|
|
20776
|
+
const fromEnv = process.env.DEEPSEEK_API_KEY;
|
|
20777
|
+
if (fromEnv) {
|
|
20778
|
+
return {
|
|
20779
|
+
label: "api key ",
|
|
20780
|
+
level: "ok",
|
|
20781
|
+
detail: `set via env DEEPSEEK_API_KEY (${tail4(fromEnv)})`
|
|
20782
|
+
};
|
|
20783
|
+
}
|
|
20784
|
+
try {
|
|
20785
|
+
const cfg = readConfig();
|
|
20786
|
+
if (cfg.apiKey) {
|
|
20787
|
+
return {
|
|
20788
|
+
label: "api key ",
|
|
20789
|
+
level: "ok",
|
|
20790
|
+
detail: `from ${defaultConfigPath()} (${tail4(cfg.apiKey)})`
|
|
20791
|
+
};
|
|
20792
|
+
}
|
|
20793
|
+
} catch {
|
|
20794
|
+
}
|
|
20795
|
+
return {
|
|
20796
|
+
label: "api key ",
|
|
20797
|
+
level: "fail",
|
|
20798
|
+
detail: "not set \u2014 `reasonix setup` to save one, or export DEEPSEEK_API_KEY. Get a key at https://platform.deepseek.com/api_keys"
|
|
20799
|
+
};
|
|
20800
|
+
}
|
|
20801
|
+
async function checkConfig() {
|
|
20802
|
+
const path5 = defaultConfigPath();
|
|
20803
|
+
if (!existsSync23(path5)) {
|
|
20804
|
+
return {
|
|
20805
|
+
label: "config ",
|
|
20806
|
+
level: "warn",
|
|
20807
|
+
detail: "missing \u2014 running with library defaults. `reasonix setup` writes one."
|
|
20808
|
+
};
|
|
20809
|
+
}
|
|
20810
|
+
try {
|
|
20811
|
+
const cfg = readConfig(path5);
|
|
20812
|
+
const parts = [];
|
|
20813
|
+
if (cfg.preset) parts.push(`preset=${cfg.preset}`);
|
|
20814
|
+
if (cfg.editMode) parts.push(`editMode=${cfg.editMode}`);
|
|
20815
|
+
if (cfg.mcp && cfg.mcp.length > 0) parts.push(`mcp=${cfg.mcp.length}`);
|
|
20816
|
+
return {
|
|
20817
|
+
label: "config ",
|
|
20818
|
+
level: "ok",
|
|
20819
|
+
detail: `${path5}${parts.length ? ` (${parts.join(", ")})` : ""}`
|
|
20820
|
+
};
|
|
20821
|
+
} catch (err) {
|
|
20822
|
+
return {
|
|
20823
|
+
label: "config ",
|
|
20824
|
+
level: "fail",
|
|
20825
|
+
detail: `${path5} unreadable \u2014 ${err.message}`
|
|
20826
|
+
};
|
|
20827
|
+
}
|
|
20828
|
+
}
|
|
20829
|
+
async function checkApiReach() {
|
|
20830
|
+
const key = process.env.DEEPSEEK_API_KEY ?? readConfig().apiKey;
|
|
20831
|
+
if (!key) {
|
|
20832
|
+
return {
|
|
20833
|
+
label: "api reach ",
|
|
20834
|
+
level: "warn",
|
|
20835
|
+
detail: "skipped \u2014 no api key to test with"
|
|
20836
|
+
};
|
|
20837
|
+
}
|
|
20838
|
+
try {
|
|
20839
|
+
const client = new DeepSeekClient({ apiKey: key });
|
|
20840
|
+
const ctl = new AbortController();
|
|
20841
|
+
const timer = setTimeout(() => ctl.abort(), 8e3);
|
|
20842
|
+
let balance;
|
|
20843
|
+
try {
|
|
20844
|
+
balance = await client.getBalance({ signal: ctl.signal });
|
|
20845
|
+
} finally {
|
|
20846
|
+
clearTimeout(timer);
|
|
20847
|
+
}
|
|
20848
|
+
if (!balance) {
|
|
20849
|
+
return {
|
|
20850
|
+
label: "api reach ",
|
|
20851
|
+
level: "fail",
|
|
20852
|
+
detail: "/user/balance returned null \u2014 auth failed or network blocked"
|
|
20853
|
+
};
|
|
20854
|
+
}
|
|
20855
|
+
if (!balance.is_available) {
|
|
20856
|
+
const info2 = balance.balance_infos[0];
|
|
20857
|
+
return {
|
|
20858
|
+
label: "api reach ",
|
|
20859
|
+
level: "warn",
|
|
20860
|
+
detail: `account flagged not-available${info2 ? ` (${info2.total_balance} ${info2.currency})` : ""} \u2014 top up or check your dashboard`
|
|
20861
|
+
};
|
|
20862
|
+
}
|
|
20863
|
+
const info = balance.balance_infos[0];
|
|
20864
|
+
return {
|
|
20865
|
+
label: "api reach ",
|
|
20866
|
+
level: "ok",
|
|
20867
|
+
detail: info ? `/user/balance ok \u2014 ${info.total_balance} ${info.currency}` : "/user/balance ok"
|
|
20868
|
+
};
|
|
20869
|
+
} catch (err) {
|
|
20870
|
+
return {
|
|
20871
|
+
label: "api reach ",
|
|
20872
|
+
level: "fail",
|
|
20873
|
+
detail: `${err.message}`
|
|
20874
|
+
};
|
|
20875
|
+
}
|
|
20876
|
+
}
|
|
20877
|
+
async function checkTokenizer() {
|
|
20878
|
+
const candidates = [
|
|
20879
|
+
join21(
|
|
20880
|
+
dirname16(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")),
|
|
20881
|
+
"..",
|
|
20882
|
+
"..",
|
|
20883
|
+
"..",
|
|
20884
|
+
"data",
|
|
20885
|
+
"deepseek-tokenizer.json.gz"
|
|
20886
|
+
),
|
|
20887
|
+
join21(process.cwd(), "data", "deepseek-tokenizer.json.gz")
|
|
20888
|
+
];
|
|
20889
|
+
for (const p of candidates) {
|
|
20890
|
+
if (existsSync23(p)) {
|
|
20891
|
+
try {
|
|
20892
|
+
const stat2 = statSync14(p);
|
|
20893
|
+
return {
|
|
20894
|
+
label: "tokenizer ",
|
|
20895
|
+
level: "ok",
|
|
20896
|
+
detail: `${p} (${fmtBytes(stat2.size)})`
|
|
20897
|
+
};
|
|
20898
|
+
} catch {
|
|
20899
|
+
}
|
|
20900
|
+
}
|
|
20901
|
+
}
|
|
20902
|
+
return {
|
|
20903
|
+
label: "tokenizer ",
|
|
20904
|
+
level: "warn",
|
|
20905
|
+
detail: "data/deepseek-tokenizer.json.gz not found \u2014 token counts will fall back to char heuristics"
|
|
20906
|
+
};
|
|
20907
|
+
}
|
|
20908
|
+
async function checkSessions() {
|
|
20909
|
+
try {
|
|
20910
|
+
const list = listSessions();
|
|
20911
|
+
if (list.length === 0) {
|
|
20912
|
+
return {
|
|
20913
|
+
label: "sessions ",
|
|
20914
|
+
level: "ok",
|
|
20915
|
+
detail: "0 saved"
|
|
20916
|
+
};
|
|
20917
|
+
}
|
|
20918
|
+
const totalBytes = list.reduce((s, e) => s + e.size, 0);
|
|
20919
|
+
const oldest = list[list.length - 1];
|
|
20920
|
+
const ageDays = Math.floor((Date.now() - oldest.mtime.getTime()) / (24 * 60 * 60 * 1e3));
|
|
20921
|
+
const stale = list.filter(
|
|
20922
|
+
(e) => Date.now() - e.mtime.getTime() >= 90 * 24 * 60 * 60 * 1e3
|
|
20923
|
+
).length;
|
|
20924
|
+
const detail = `${list.length} saved \xB7 ${fmtBytes(totalBytes)} \xB7 oldest ${ageDays}d`;
|
|
20925
|
+
if (stale > 0) {
|
|
20926
|
+
return {
|
|
20927
|
+
label: "sessions ",
|
|
20928
|
+
level: "warn",
|
|
20929
|
+
detail: `${detail} \xB7 ${stale} idle \u226590d (run /prune-sessions)`
|
|
20930
|
+
};
|
|
20931
|
+
}
|
|
20932
|
+
return { label: "sessions ", level: "ok", detail };
|
|
20933
|
+
} catch (err) {
|
|
20934
|
+
return {
|
|
20935
|
+
label: "sessions ",
|
|
20936
|
+
level: "warn",
|
|
20937
|
+
detail: `cannot list \u2014 ${err.message}`
|
|
20938
|
+
};
|
|
20939
|
+
}
|
|
20940
|
+
}
|
|
20941
|
+
async function checkHooks(projectRoot) {
|
|
20942
|
+
try {
|
|
20943
|
+
const all = loadHooks({ projectRoot });
|
|
20944
|
+
const global = all.filter((h) => h.scope === "global").length;
|
|
20945
|
+
const project = all.filter((h) => h.scope === "project").length;
|
|
20946
|
+
return {
|
|
20947
|
+
label: "hooks ",
|
|
20948
|
+
level: "ok",
|
|
20949
|
+
detail: `${global} global, ${project} project`
|
|
20950
|
+
};
|
|
20951
|
+
} catch (err) {
|
|
20952
|
+
return {
|
|
20953
|
+
label: "hooks ",
|
|
20954
|
+
level: "warn",
|
|
20955
|
+
detail: `couldn't parse settings.json \u2014 ${err.message}`
|
|
20956
|
+
};
|
|
20957
|
+
}
|
|
20958
|
+
}
|
|
20959
|
+
async function checkOllama(projectRoot) {
|
|
20960
|
+
let exists = false;
|
|
20961
|
+
try {
|
|
20962
|
+
exists = await indexExists(projectRoot);
|
|
20963
|
+
} catch {
|
|
20964
|
+
}
|
|
20965
|
+
if (!exists) {
|
|
20966
|
+
return {
|
|
20967
|
+
label: "ollama ",
|
|
20968
|
+
level: "ok",
|
|
20969
|
+
detail: "not in use (no semantic index built; `reasonix index` to enable)"
|
|
20970
|
+
};
|
|
20971
|
+
}
|
|
20972
|
+
try {
|
|
20973
|
+
const status2 = await checkOllamaStatus(process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text");
|
|
20974
|
+
if (!status2.binaryFound) {
|
|
20975
|
+
return {
|
|
20976
|
+
label: "ollama ",
|
|
20977
|
+
level: "warn",
|
|
20978
|
+
detail: "binary not on PATH \u2014 semantic_search will fail; install from https://ollama.com"
|
|
20979
|
+
};
|
|
20980
|
+
}
|
|
20981
|
+
if (!status2.daemonRunning) {
|
|
20982
|
+
return {
|
|
20983
|
+
label: "ollama ",
|
|
20984
|
+
level: "warn",
|
|
20985
|
+
detail: "daemon not running \u2014 `ollama serve` (or just call /semantic in TUI to auto-start)"
|
|
20986
|
+
};
|
|
20987
|
+
}
|
|
20988
|
+
if (!status2.modelPulled) {
|
|
20989
|
+
return {
|
|
20990
|
+
label: "ollama ",
|
|
20991
|
+
level: "warn",
|
|
20992
|
+
detail: `model ${status2.modelName} not pulled \u2014 \`ollama pull ${status2.modelName}\``
|
|
20993
|
+
};
|
|
20994
|
+
}
|
|
20995
|
+
return {
|
|
20996
|
+
label: "ollama ",
|
|
20997
|
+
level: "ok",
|
|
20998
|
+
detail: `daemon up \xB7 model ${status2.modelName} ready`
|
|
20999
|
+
};
|
|
21000
|
+
} catch (err) {
|
|
21001
|
+
return {
|
|
21002
|
+
label: "ollama ",
|
|
21003
|
+
level: "warn",
|
|
21004
|
+
detail: `probe failed \u2014 ${err.message}`
|
|
21005
|
+
};
|
|
21006
|
+
}
|
|
21007
|
+
}
|
|
21008
|
+
async function checkProject(projectRoot) {
|
|
21009
|
+
const markers = [".git", "REASONIX.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
|
|
21010
|
+
const found = markers.filter((m) => existsSync23(join21(projectRoot, m)));
|
|
21011
|
+
if (found.length === 0) {
|
|
21012
|
+
return {
|
|
21013
|
+
label: "project ",
|
|
21014
|
+
level: "warn",
|
|
21015
|
+
detail: `${projectRoot} has none of: ${markers.slice(0, 3).join(", ")} \u2026 \u2014 \`reasonix code\` will still run, but @-mentions and project memory have nothing to anchor`
|
|
21016
|
+
};
|
|
21017
|
+
}
|
|
21018
|
+
return {
|
|
21019
|
+
label: "project ",
|
|
21020
|
+
level: "ok",
|
|
21021
|
+
detail: `${projectRoot} (${found.join(", ")})`
|
|
21022
|
+
};
|
|
21023
|
+
}
|
|
21024
|
+
async function doctorCommand() {
|
|
21025
|
+
loadDotenv();
|
|
21026
|
+
const projectRoot = resolve12(process.cwd());
|
|
21027
|
+
console.log(`${color(`reasonix ${VERSION} \xB7 doctor`, "1")} (cwd: ${projectRoot})`);
|
|
21028
|
+
console.log(` home: ${homedir10()}`);
|
|
21029
|
+
console.log("");
|
|
21030
|
+
const checks = await Promise.all([
|
|
21031
|
+
checkApiKey(),
|
|
21032
|
+
checkConfig(),
|
|
21033
|
+
checkApiReach(),
|
|
21034
|
+
checkTokenizer(),
|
|
21035
|
+
checkSessions(),
|
|
21036
|
+
checkHooks(projectRoot),
|
|
21037
|
+
checkOllama(projectRoot),
|
|
21038
|
+
checkProject(projectRoot)
|
|
21039
|
+
]);
|
|
21040
|
+
for (const c of checks) {
|
|
21041
|
+
console.log(` ${badge(c.level)} ${c.label} ${c.detail}`);
|
|
21042
|
+
}
|
|
21043
|
+
const ok = checks.filter((c) => c.level === "ok").length;
|
|
21044
|
+
const warn = checks.filter((c) => c.level === "warn").length;
|
|
21045
|
+
const fail = checks.filter((c) => c.level === "fail").length;
|
|
21046
|
+
console.log("");
|
|
21047
|
+
const summary = `${ok} ok \xB7 ${warn} warn \xB7 ${fail} fail`;
|
|
21048
|
+
if (fail > 0) {
|
|
21049
|
+
console.log(color(summary, "31"));
|
|
21050
|
+
process.exit(1);
|
|
21051
|
+
} else if (warn > 0) {
|
|
21052
|
+
console.log(color(summary, "33"));
|
|
21053
|
+
} else {
|
|
21054
|
+
console.log(color(summary, "32"));
|
|
21055
|
+
}
|
|
21056
|
+
}
|
|
21057
|
+
|
|
20655
21058
|
// src/cli/commands/index.ts
|
|
20656
|
-
import { resolve as
|
|
21059
|
+
import { resolve as resolve13 } from "path";
|
|
20657
21060
|
|
|
20658
21061
|
// src/index/semantic/preflight.ts
|
|
20659
21062
|
import { stdin as stdin2, stdout } from "process";
|
|
@@ -20727,7 +21130,7 @@ async function confirm(question, defaultYes) {
|
|
|
20727
21130
|
|
|
20728
21131
|
// src/cli/commands/index.ts
|
|
20729
21132
|
async function indexCommand(opts = {}) {
|
|
20730
|
-
const root =
|
|
21133
|
+
const root = resolve13(opts.dir ?? process.cwd());
|
|
20731
21134
|
const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
|
|
20732
21135
|
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
20733
21136
|
const preflightOk = await ollamaPreflight({
|
|
@@ -21213,7 +21616,8 @@ async function runCommand2(opts) {
|
|
|
21213
21616
|
tools,
|
|
21214
21617
|
model: opts.model,
|
|
21215
21618
|
harvest: opts.harvest,
|
|
21216
|
-
branch: opts.branch
|
|
21619
|
+
branch: opts.branch,
|
|
21620
|
+
budgetUsd: opts.budgetUsd
|
|
21217
21621
|
});
|
|
21218
21622
|
const prefixHash = prefix.fingerprint;
|
|
21219
21623
|
let transcriptStream = null;
|
|
@@ -21648,13 +22052,13 @@ function planUpdate(input) {
|
|
|
21648
22052
|
};
|
|
21649
22053
|
}
|
|
21650
22054
|
function defaultSpawn(argv) {
|
|
21651
|
-
return new Promise((
|
|
22055
|
+
return new Promise((resolve14, reject) => {
|
|
21652
22056
|
const child = spawn6(argv[0], argv.slice(1), {
|
|
21653
22057
|
stdio: "inherit",
|
|
21654
22058
|
shell: process.platform === "win32"
|
|
21655
22059
|
});
|
|
21656
22060
|
child.once("error", reject);
|
|
21657
|
-
child.once("exit", (code) =>
|
|
22061
|
+
child.once("exit", (code) => resolve14(code ?? 1));
|
|
21658
22062
|
});
|
|
21659
22063
|
}
|
|
21660
22064
|
async function updateCommand(opts = {}) {
|
|
@@ -21769,6 +22173,17 @@ Your training data has a cutoff. When an answer's correctness depends on somethi
|
|
|
21769
22173
|
The signal isn't a topic list \u2014 it's: "if I'm wrong about this, is it because reality moved on?". If yes, ground the answer in fresh evidence; if no (definitions, mechanisms, well-established APIs), answer from memory.
|
|
21770
22174
|
|
|
21771
22175
|
${ESCALATION_CONTRACT}`;
|
|
22176
|
+
function parseBudgetFlag(raw) {
|
|
22177
|
+
if (raw === void 0) return void 0;
|
|
22178
|
+
if (!Number.isFinite(raw) || raw <= 0) {
|
|
22179
|
+
process.stderr.write(
|
|
22180
|
+
`\u25B2 ignoring --budget=${raw} (must be a positive number) \u2014 running with no cap
|
|
22181
|
+
`
|
|
22182
|
+
);
|
|
22183
|
+
return void 0;
|
|
22184
|
+
}
|
|
22185
|
+
return raw;
|
|
22186
|
+
}
|
|
21772
22187
|
var program = new Command();
|
|
21773
22188
|
program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION).option(
|
|
21774
22189
|
"-c, --continue",
|
|
@@ -21806,6 +22221,10 @@ program.command("code [dir]").description(
|
|
|
21806
22221
|
).option("-m, --model <id>", "Override default model (v4-flash)").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(
|
|
21807
22222
|
"--harvest",
|
|
21808
22223
|
"Opt-in Pillar-2 plan-state extraction. Adds one flash call per turn; off by default (no preset enables it)."
|
|
22224
|
+
).option(
|
|
22225
|
+
"--budget <usd>",
|
|
22226
|
+
"Soft USD cap on session spend. Off by default. Warns at 80%, refuses next turn at 100%. Mid-session: /budget <usd> or /budget off.",
|
|
22227
|
+
(v) => Number.parseFloat(v)
|
|
21809
22228
|
).option(
|
|
21810
22229
|
"--no-dashboard",
|
|
21811
22230
|
"Suppress the auto-launched embedded web dashboard. Default behavior boots it on TUI mount and shows the URL in the status bar (clickable in OSC-8-aware terminals)."
|
|
@@ -21818,6 +22237,7 @@ program.command("code [dir]").description(
|
|
|
21818
22237
|
forceResume: !!opts.resume,
|
|
21819
22238
|
forceNew: !!opts.new,
|
|
21820
22239
|
harvest: !!opts.harvest,
|
|
22240
|
+
budgetUsd: parseBudgetFlag(opts.budget),
|
|
21821
22241
|
noDashboard: opts.dashboard === false
|
|
21822
22242
|
});
|
|
21823
22243
|
});
|
|
@@ -21831,6 +22251,10 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
21831
22251
|
"--branch <n>",
|
|
21832
22252
|
"Self-consistency: run N parallel samples per turn (N\xD7 cost). Manual only \u2014 never auto-enabled.",
|
|
21833
22253
|
(v) => Number.parseInt(v, 10)
|
|
22254
|
+
).option(
|
|
22255
|
+
"--budget <usd>",
|
|
22256
|
+
"Soft USD cap on session spend. Off by default. Warns at 80%, refuses next turn at 100%. Mid-session: /budget <usd> or /budget off.",
|
|
22257
|
+
(v) => Number.parseFloat(v)
|
|
21834
22258
|
).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option(
|
|
21835
22259
|
"-c, --continue",
|
|
21836
22260
|
"Resume the most-recently-used session (any name) without showing the picker."
|
|
@@ -21868,6 +22292,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
21868
22292
|
transcript: opts.transcript,
|
|
21869
22293
|
harvest: defaults.harvest,
|
|
21870
22294
|
branch: defaults.branch,
|
|
22295
|
+
budgetUsd: parseBudgetFlag(opts.budget),
|
|
21871
22296
|
session: continueOpts.session,
|
|
21872
22297
|
mcp: defaults.mcp,
|
|
21873
22298
|
mcpPrefix: opts.mcpPrefix,
|
|
@@ -21880,6 +22305,10 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
21880
22305
|
"--branch <n>",
|
|
21881
22306
|
"Self-consistency: run N parallel samples per turn and pick the most confident",
|
|
21882
22307
|
(v) => Number.parseInt(v, 10)
|
|
22308
|
+
).option(
|
|
22309
|
+
"--budget <usd>",
|
|
22310
|
+
"Soft USD cap on session spend. Off by default. Refuses to start a new turn once cumulative cost \u2265 cap.",
|
|
22311
|
+
(v) => Number.parseFloat(v)
|
|
21883
22312
|
).option("--transcript <path>", "Write a JSONL transcript to this path for replay/diff").option(
|
|
21884
22313
|
"--mcp <spec>",
|
|
21885
22314
|
'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE).',
|
|
@@ -21903,6 +22332,7 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
21903
22332
|
system: applyMemoryStack(opts.system, process.cwd()),
|
|
21904
22333
|
harvest: defaults.harvest,
|
|
21905
22334
|
branch: defaults.branch,
|
|
22335
|
+
budgetUsd: parseBudgetFlag(opts.budget),
|
|
21906
22336
|
transcript: opts.transcript,
|
|
21907
22337
|
mcp: defaults.mcp,
|
|
21908
22338
|
mcpPrefix: opts.mcpPrefix
|
|
@@ -21913,6 +22343,11 @@ program.command("stats [transcript]").description(
|
|
|
21913
22343
|
).action((transcript) => {
|
|
21914
22344
|
statsCommand({ transcript });
|
|
21915
22345
|
});
|
|
22346
|
+
program.command("doctor").description(
|
|
22347
|
+
"One-command health check \u2014 API key, config, /user/balance reachability, tokenizer, sessions, hooks, Ollama (if used), project markers. Exit 1 on any fail; 0 on warn / clean."
|
|
22348
|
+
).action(async () => {
|
|
22349
|
+
await doctorCommand();
|
|
22350
|
+
});
|
|
21916
22351
|
program.command("sessions [name]").description("List saved chat sessions, or inspect one by name.").option("-v, --verbose", "Include system prompts + tool-call metadata when inspecting").action((name, opts) => {
|
|
21917
22352
|
sessionsCommand({ name, verbose: !!opts.verbose });
|
|
21918
22353
|
});
|