reasonix 0.4.23 → 0.4.26
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 +23 -0
- package/dist/cli/{chunk-K6MR4SWS.js → chunk-2BYEKJHX.js} +119 -10
- package/dist/cli/chunk-2BYEKJHX.js.map +1 -0
- package/dist/cli/index.js +773 -81
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-VDN5U3YE.js → prompt-6DMLWG2H.js} +2 -2
- package/dist/index.d.ts +268 -2
- package/dist/index.js +656 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-K6MR4SWS.js.map +0 -1
- /package/dist/cli/{prompt-VDN5U3YE.js.map → prompt-6DMLWG2H.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
memoryEnabled,
|
|
8
8
|
readProjectMemory,
|
|
9
9
|
sanitizeMemoryName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2BYEKJHX.js";
|
|
11
11
|
|
|
12
12
|
// src/cli/index.ts
|
|
13
13
|
import { Command } from "commander";
|
|
@@ -1893,6 +1893,10 @@ var CacheFirstLoop = class {
|
|
|
1893
1893
|
usage = resp.usage;
|
|
1894
1894
|
}
|
|
1895
1895
|
} catch (err) {
|
|
1896
|
+
if (signal.aborted) {
|
|
1897
|
+
yield { turn: this._turn, role: "done", content: "" };
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1896
1900
|
yield {
|
|
1897
1901
|
turn: this._turn,
|
|
1898
1902
|
role: "error",
|
|
@@ -2212,6 +2216,74 @@ import { promises as fs } from "fs";
|
|
|
2212
2216
|
import * as pathMod from "path";
|
|
2213
2217
|
var DEFAULT_MAX_READ_BYTES = 2 * 1024 * 1024;
|
|
2214
2218
|
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
2219
|
+
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
2220
|
+
"node_modules",
|
|
2221
|
+
".git",
|
|
2222
|
+
".hg",
|
|
2223
|
+
".svn",
|
|
2224
|
+
"dist",
|
|
2225
|
+
"build",
|
|
2226
|
+
"out",
|
|
2227
|
+
".next",
|
|
2228
|
+
".nuxt",
|
|
2229
|
+
"target",
|
|
2230
|
+
// Rust / Java
|
|
2231
|
+
".venv",
|
|
2232
|
+
"venv",
|
|
2233
|
+
"__pycache__",
|
|
2234
|
+
".pytest_cache",
|
|
2235
|
+
".mypy_cache",
|
|
2236
|
+
".cache",
|
|
2237
|
+
"coverage"
|
|
2238
|
+
]);
|
|
2239
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2240
|
+
".png",
|
|
2241
|
+
".jpg",
|
|
2242
|
+
".jpeg",
|
|
2243
|
+
".gif",
|
|
2244
|
+
".bmp",
|
|
2245
|
+
".ico",
|
|
2246
|
+
".webp",
|
|
2247
|
+
".tiff",
|
|
2248
|
+
".pdf",
|
|
2249
|
+
".zip",
|
|
2250
|
+
".tar",
|
|
2251
|
+
".gz",
|
|
2252
|
+
".bz2",
|
|
2253
|
+
".xz",
|
|
2254
|
+
".7z",
|
|
2255
|
+
".rar",
|
|
2256
|
+
".exe",
|
|
2257
|
+
".dll",
|
|
2258
|
+
".so",
|
|
2259
|
+
".dylib",
|
|
2260
|
+
".bin",
|
|
2261
|
+
".class",
|
|
2262
|
+
".jar",
|
|
2263
|
+
".war",
|
|
2264
|
+
".o",
|
|
2265
|
+
".obj",
|
|
2266
|
+
".lib",
|
|
2267
|
+
".a",
|
|
2268
|
+
".woff",
|
|
2269
|
+
".woff2",
|
|
2270
|
+
".ttf",
|
|
2271
|
+
".otf",
|
|
2272
|
+
".eot",
|
|
2273
|
+
".mp3",
|
|
2274
|
+
".mp4",
|
|
2275
|
+
".mov",
|
|
2276
|
+
".avi",
|
|
2277
|
+
".webm",
|
|
2278
|
+
".wasm",
|
|
2279
|
+
".pyc",
|
|
2280
|
+
".pyo"
|
|
2281
|
+
]);
|
|
2282
|
+
function isLikelyBinaryByName(name) {
|
|
2283
|
+
const dot = name.lastIndexOf(".");
|
|
2284
|
+
if (dot < 0) return false;
|
|
2285
|
+
return BINARY_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
2286
|
+
}
|
|
2215
2287
|
function registerFilesystemTools(registry, opts) {
|
|
2216
2288
|
const rootDir = pathMod.resolve(opts.rootDir);
|
|
2217
2289
|
const allowWriting = opts.allowWriting !== false;
|
|
@@ -2221,7 +2293,12 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2221
2293
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
2222
2294
|
throw new Error("path must be a non-empty string");
|
|
2223
2295
|
}
|
|
2224
|
-
|
|
2296
|
+
let normalized = raw;
|
|
2297
|
+
while (normalized.startsWith("/") || normalized.startsWith("\\")) {
|
|
2298
|
+
normalized = normalized.slice(1);
|
|
2299
|
+
}
|
|
2300
|
+
if (normalized.length === 0) normalized = ".";
|
|
2301
|
+
const resolved = pathMod.resolve(rootDir, normalized);
|
|
2225
2302
|
const normRoot = pathMod.resolve(rootDir);
|
|
2226
2303
|
const rel = pathMod.relative(normRoot, resolved);
|
|
2227
2304
|
if (rel.startsWith("..") || pathMod.isAbsolute(rel)) {
|
|
@@ -2387,6 +2464,114 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2387
2464
|
return matches.length === 0 ? "(no matches)" : matches.join("\n");
|
|
2388
2465
|
}
|
|
2389
2466
|
});
|
|
2467
|
+
registry.register({
|
|
2468
|
+
name: "search_content",
|
|
2469
|
+
description: "Recursively grep file CONTENTS for a substring or regex. This is the right tool for 'find all places that call X', 'where is Y referenced', 'what files contain Z'. Different from search_files (which matches FILE NAMES). Returns one match per line in 'path:line: text' format. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) and binary files by default.",
|
|
2470
|
+
readOnly: true,
|
|
2471
|
+
parameters: {
|
|
2472
|
+
type: "object",
|
|
2473
|
+
properties: {
|
|
2474
|
+
pattern: {
|
|
2475
|
+
type: "string",
|
|
2476
|
+
description: "Substring (or regex) to search file contents for."
|
|
2477
|
+
},
|
|
2478
|
+
path: {
|
|
2479
|
+
type: "string",
|
|
2480
|
+
description: "Directory to start the search at (default: sandbox root)."
|
|
2481
|
+
},
|
|
2482
|
+
glob: {
|
|
2483
|
+
type: "string",
|
|
2484
|
+
description: "Optional file-name suffix or substring filter. Examples: '.ts' (only TypeScript), 'test' (any file with 'test' in the name). Reduces noise when you know the file shape."
|
|
2485
|
+
},
|
|
2486
|
+
case_sensitive: {
|
|
2487
|
+
type: "boolean",
|
|
2488
|
+
description: "When true, match case exactly. Default false (case-insensitive)."
|
|
2489
|
+
},
|
|
2490
|
+
include_deps: {
|
|
2491
|
+
type: "boolean",
|
|
2492
|
+
description: "When true, also search inside node_modules / .git / dist / build / etc. Off by default \u2014 most exploration questions are about the user's own code."
|
|
2493
|
+
}
|
|
2494
|
+
},
|
|
2495
|
+
required: ["pattern"]
|
|
2496
|
+
},
|
|
2497
|
+
fn: async (args) => {
|
|
2498
|
+
const startAbs = safePath(args.path ?? ".");
|
|
2499
|
+
const caseSensitive = args.case_sensitive === true;
|
|
2500
|
+
const includeDeps = args.include_deps === true;
|
|
2501
|
+
const nameFilter = typeof args.glob === "string" ? args.glob.toLowerCase() : null;
|
|
2502
|
+
let re = null;
|
|
2503
|
+
try {
|
|
2504
|
+
re = new RegExp(args.pattern, caseSensitive ? "" : "i");
|
|
2505
|
+
} catch {
|
|
2506
|
+
re = null;
|
|
2507
|
+
}
|
|
2508
|
+
const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
|
|
2509
|
+
const matches = [];
|
|
2510
|
+
let totalBytes = 0;
|
|
2511
|
+
let scanned = 0;
|
|
2512
|
+
let truncated = false;
|
|
2513
|
+
const walk2 = async (dir) => {
|
|
2514
|
+
if (truncated) return;
|
|
2515
|
+
let entries;
|
|
2516
|
+
try {
|
|
2517
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2518
|
+
} catch {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
for (const e of entries) {
|
|
2522
|
+
if (truncated) return;
|
|
2523
|
+
if (e.isDirectory()) {
|
|
2524
|
+
if (!includeDeps && SKIP_DIR_NAMES.has(e.name)) continue;
|
|
2525
|
+
await walk2(pathMod.join(dir, e.name));
|
|
2526
|
+
continue;
|
|
2527
|
+
}
|
|
2528
|
+
if (!e.isFile()) continue;
|
|
2529
|
+
if (nameFilter && !e.name.toLowerCase().includes(nameFilter)) continue;
|
|
2530
|
+
if (isLikelyBinaryByName(e.name)) continue;
|
|
2531
|
+
const full = pathMod.join(dir, e.name);
|
|
2532
|
+
let stat;
|
|
2533
|
+
try {
|
|
2534
|
+
stat = await fs.stat(full);
|
|
2535
|
+
} catch {
|
|
2536
|
+
continue;
|
|
2537
|
+
}
|
|
2538
|
+
if (stat.size > 2 * 1024 * 1024) continue;
|
|
2539
|
+
let raw;
|
|
2540
|
+
try {
|
|
2541
|
+
raw = await fs.readFile(full);
|
|
2542
|
+
} catch {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const firstNul = raw.indexOf(0);
|
|
2546
|
+
if (firstNul !== -1 && firstNul < 8 * 1024) continue;
|
|
2547
|
+
const text = raw.toString("utf8");
|
|
2548
|
+
const rel = pathMod.relative(rootDir, full);
|
|
2549
|
+
const lines = text.split(/\r?\n/);
|
|
2550
|
+
for (let li = 0; li < lines.length; li++) {
|
|
2551
|
+
const line = lines[li];
|
|
2552
|
+
const lineForCheck = caseSensitive ? line : line.toLowerCase();
|
|
2553
|
+
const hit = re ? re.test(line) : lineForCheck.includes(needle);
|
|
2554
|
+
if (!hit) continue;
|
|
2555
|
+
const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
|
|
2556
|
+
const out = `${rel}:${li + 1}: ${display}`;
|
|
2557
|
+
if (totalBytes + out.length + 1 > maxListBytes) {
|
|
2558
|
+
matches.push(`[\u2026 truncated at ${maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
|
|
2559
|
+
truncated = true;
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
matches.push(out);
|
|
2563
|
+
totalBytes += out.length + 1;
|
|
2564
|
+
}
|
|
2565
|
+
scanned++;
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
await walk2(startAbs);
|
|
2569
|
+
if (matches.length === 0) {
|
|
2570
|
+
return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
|
|
2571
|
+
}
|
|
2572
|
+
return matches.join("\n");
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2390
2575
|
registry.register({
|
|
2391
2576
|
name: "get_file_info",
|
|
2392
2577
|
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
@@ -2459,10 +2644,10 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2459
2644
|
const after = before.slice(0, firstIdx) + args.replace + before.slice(firstIdx + args.search.length);
|
|
2460
2645
|
await fs.writeFile(abs, after, "utf8");
|
|
2461
2646
|
const rel = pathMod.relative(rootDir, abs);
|
|
2462
|
-
const
|
|
2647
|
+
const header2 = `edited ${rel} (${args.search.length}\u2192${args.replace.length} chars)`;
|
|
2463
2648
|
const startLine = before.slice(0, firstIdx).split(/\r?\n/).length;
|
|
2464
2649
|
const diff = renderEditDiff(args.search, args.replace, startLine);
|
|
2465
|
-
return `${
|
|
2650
|
+
return `${header2}
|
|
2466
2651
|
${diff}`;
|
|
2467
2652
|
}
|
|
2468
2653
|
});
|
|
@@ -2713,6 +2898,127 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
2713
2898
|
return registry;
|
|
2714
2899
|
}
|
|
2715
2900
|
|
|
2901
|
+
// src/tools/subagent.ts
|
|
2902
|
+
var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
|
|
2903
|
+
var DEFAULT_MAX_ITERS = 16;
|
|
2904
|
+
var DEFAULT_SUBAGENT_MODEL = "deepseek-chat";
|
|
2905
|
+
var SUBAGENT_TOOL_NAME = "spawn_subagent";
|
|
2906
|
+
var NEVER_INHERITED_TOOLS = /* @__PURE__ */ new Set([SUBAGENT_TOOL_NAME, "submit_plan"]);
|
|
2907
|
+
async function spawnSubagent(opts) {
|
|
2908
|
+
const model = opts.model ?? DEFAULT_SUBAGENT_MODEL;
|
|
2909
|
+
const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
|
|
2910
|
+
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
2911
|
+
const sink = opts.sink;
|
|
2912
|
+
const startedAt = Date.now();
|
|
2913
|
+
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
2914
|
+
sink?.current?.({
|
|
2915
|
+
kind: "start",
|
|
2916
|
+
task: taskPreview,
|
|
2917
|
+
iter: 0,
|
|
2918
|
+
elapsedMs: 0
|
|
2919
|
+
});
|
|
2920
|
+
const childTools = forkRegistryExcluding(opts.parentRegistry, NEVER_INHERITED_TOOLS);
|
|
2921
|
+
const childPrefix = new ImmutablePrefix({
|
|
2922
|
+
system: opts.system,
|
|
2923
|
+
toolSpecs: childTools.specs()
|
|
2924
|
+
});
|
|
2925
|
+
const childLoop = new CacheFirstLoop({
|
|
2926
|
+
client: opts.client,
|
|
2927
|
+
prefix: childPrefix,
|
|
2928
|
+
tools: childTools,
|
|
2929
|
+
model,
|
|
2930
|
+
maxToolIters,
|
|
2931
|
+
hooks: [],
|
|
2932
|
+
stream: false
|
|
2933
|
+
});
|
|
2934
|
+
const onParentAbort = () => childLoop.abort();
|
|
2935
|
+
opts.parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
2936
|
+
let final = "";
|
|
2937
|
+
let errorMessage;
|
|
2938
|
+
let toolIter = 0;
|
|
2939
|
+
try {
|
|
2940
|
+
for await (const ev of childLoop.step(opts.task)) {
|
|
2941
|
+
if (ev.role === "tool") {
|
|
2942
|
+
toolIter++;
|
|
2943
|
+
sink?.current?.({
|
|
2944
|
+
kind: "progress",
|
|
2945
|
+
task: taskPreview,
|
|
2946
|
+
iter: toolIter,
|
|
2947
|
+
elapsedMs: Date.now() - startedAt
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
if (ev.role === "assistant_final") {
|
|
2951
|
+
final = ev.content ?? "";
|
|
2952
|
+
}
|
|
2953
|
+
if (ev.role === "error") {
|
|
2954
|
+
errorMessage = ev.error ?? "subagent error";
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
} catch (err) {
|
|
2958
|
+
errorMessage = err.message;
|
|
2959
|
+
} finally {
|
|
2960
|
+
opts.parentSignal?.removeEventListener("abort", onParentAbort);
|
|
2961
|
+
}
|
|
2962
|
+
if (!errorMessage && !final) {
|
|
2963
|
+
errorMessage = opts.parentSignal?.aborted ? "subagent aborted before producing an answer" : "subagent ended without producing an answer";
|
|
2964
|
+
}
|
|
2965
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2966
|
+
const turns = childLoop.stats.turns.length;
|
|
2967
|
+
const costUsd2 = childLoop.stats.totalCost;
|
|
2968
|
+
const truncated = final.length > maxResultChars ? `${final.slice(0, maxResultChars)}
|
|
2969
|
+
|
|
2970
|
+
[\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
|
|
2971
|
+
sink?.current?.({
|
|
2972
|
+
kind: "end",
|
|
2973
|
+
task: taskPreview,
|
|
2974
|
+
iter: toolIter,
|
|
2975
|
+
elapsedMs,
|
|
2976
|
+
summary: errorMessage ? void 0 : truncated.slice(0, 120),
|
|
2977
|
+
error: errorMessage,
|
|
2978
|
+
turns
|
|
2979
|
+
});
|
|
2980
|
+
return {
|
|
2981
|
+
success: !errorMessage,
|
|
2982
|
+
output: errorMessage ? "" : truncated,
|
|
2983
|
+
error: errorMessage,
|
|
2984
|
+
turns,
|
|
2985
|
+
toolIters: toolIter,
|
|
2986
|
+
elapsedMs,
|
|
2987
|
+
costUsd: costUsd2
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
function formatSubagentResult(r) {
|
|
2991
|
+
if (!r.success) {
|
|
2992
|
+
return JSON.stringify({
|
|
2993
|
+
success: false,
|
|
2994
|
+
error: r.error ?? "unknown subagent error",
|
|
2995
|
+
turns: r.turns,
|
|
2996
|
+
tool_iters: r.toolIters,
|
|
2997
|
+
elapsed_ms: r.elapsedMs
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
return JSON.stringify({
|
|
3001
|
+
success: true,
|
|
3002
|
+
output: r.output,
|
|
3003
|
+
turns: r.turns,
|
|
3004
|
+
tool_iters: r.toolIters,
|
|
3005
|
+
elapsed_ms: r.elapsedMs,
|
|
3006
|
+
cost_usd: r.costUsd
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
function forkRegistryExcluding(parent, exclude) {
|
|
3010
|
+
const child = new ToolRegistry();
|
|
3011
|
+
for (const spec of parent.specs()) {
|
|
3012
|
+
const name = spec.function.name;
|
|
3013
|
+
if (exclude.has(name)) continue;
|
|
3014
|
+
const def = parent.get(name);
|
|
3015
|
+
if (!def) continue;
|
|
3016
|
+
child.register(def);
|
|
3017
|
+
}
|
|
3018
|
+
if (parent.planMode) child.setPlanMode(true);
|
|
3019
|
+
return child;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
2716
3022
|
// src/tools/shell.ts
|
|
2717
3023
|
import { spawn as spawn2 } from "child_process";
|
|
2718
3024
|
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
@@ -2910,7 +3216,7 @@ function prepareSpawn(argv, opts = {}) {
|
|
|
2910
3216
|
const cmdline = [resolved, ...tail].map(quoteForCmdExe).join(" ");
|
|
2911
3217
|
return {
|
|
2912
3218
|
bin: "cmd.exe",
|
|
2913
|
-
args: ["/d", "/s", "/c", cmdline],
|
|
3219
|
+
args: ["/d", "/s", "/c", withUtf8Codepage(cmdline)],
|
|
2914
3220
|
// windowsVerbatimArguments prevents Node from re-quoting the /c
|
|
2915
3221
|
// payload — we've already composed an exact cmd.exe command
|
|
2916
3222
|
// line. Without this Node wraps our already-quoted string in
|
|
@@ -2922,12 +3228,36 @@ function prepareSpawn(argv, opts = {}) {
|
|
|
2922
3228
|
const cmdline = [head, ...tail].map(quoteForCmdExe).join(" ");
|
|
2923
3229
|
return {
|
|
2924
3230
|
bin: "cmd.exe",
|
|
2925
|
-
args: ["/d", "/s", "/c", cmdline],
|
|
3231
|
+
args: ["/d", "/s", "/c", withUtf8Codepage(cmdline)],
|
|
2926
3232
|
spawnOverrides: { windowsVerbatimArguments: true }
|
|
2927
3233
|
};
|
|
2928
3234
|
}
|
|
3235
|
+
if (isPowerShellExe(resolved)) {
|
|
3236
|
+
const patched = injectPowerShellUtf8(tail);
|
|
3237
|
+
if (patched) {
|
|
3238
|
+
return { bin: resolved, args: patched, spawnOverrides: {} };
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
2929
3241
|
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2930
3242
|
}
|
|
3243
|
+
function isPowerShellExe(resolved) {
|
|
3244
|
+
return /(?:^|[\\/])(?:powershell|pwsh)(?:\.exe)?$/i.test(resolved);
|
|
3245
|
+
}
|
|
3246
|
+
function injectPowerShellUtf8(args) {
|
|
3247
|
+
const prelude = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;$OutputEncoding=[System.Text.Encoding]::UTF8;";
|
|
3248
|
+
for (let i = 0; i < args.length; i++) {
|
|
3249
|
+
const a = args[i] ?? "";
|
|
3250
|
+
if (/^-(?:Command|c)$/i.test(a) && i + 1 < args.length) {
|
|
3251
|
+
const out = [...args];
|
|
3252
|
+
out[i + 1] = `${prelude}${args[i + 1] ?? ""}`;
|
|
3253
|
+
return out;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
return null;
|
|
3257
|
+
}
|
|
3258
|
+
function withUtf8Codepage(cmdline) {
|
|
3259
|
+
return `chcp 65001 >nul & ${cmdline}`;
|
|
3260
|
+
}
|
|
2931
3261
|
function isBareWindowsName(s) {
|
|
2932
3262
|
if (!s) return false;
|
|
2933
3263
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
@@ -3005,11 +3335,11 @@ function registerShellTools(registry, opts) {
|
|
|
3005
3335
|
return registry;
|
|
3006
3336
|
}
|
|
3007
3337
|
function formatCommandResult(cmd, r) {
|
|
3008
|
-
const
|
|
3338
|
+
const header2 = r.timedOut ? `$ ${cmd}
|
|
3009
3339
|
[killed after timeout]` : `$ ${cmd}
|
|
3010
3340
|
[exit ${r.exitCode ?? "?"}]`;
|
|
3011
|
-
return r.output ? `${
|
|
3012
|
-
${r.output}` :
|
|
3341
|
+
return r.output ? `${header2}
|
|
3342
|
+
${r.output}` : header2;
|
|
3013
3343
|
}
|
|
3014
3344
|
|
|
3015
3345
|
// src/tools/web.ts
|
|
@@ -3174,9 +3504,9 @@ function registerWebTools(registry, opts = {}) {
|
|
|
3174
3504
|
throw new Error("web_fetch: url must start with http:// or https://");
|
|
3175
3505
|
}
|
|
3176
3506
|
const page = await webFetch(args.url, { maxChars: maxFetchChars, signal: ctx?.signal });
|
|
3177
|
-
const
|
|
3507
|
+
const header2 = page.title ? `${page.title}
|
|
3178
3508
|
${page.url}` : page.url;
|
|
3179
|
-
return `${
|
|
3509
|
+
return `${header2}
|
|
3180
3510
|
|
|
3181
3511
|
${page.text}`;
|
|
3182
3512
|
}
|
|
@@ -4582,28 +4912,163 @@ function isNpxInstall() {
|
|
|
4582
4912
|
return false;
|
|
4583
4913
|
}
|
|
4584
4914
|
|
|
4915
|
+
// src/usage.ts
|
|
4916
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
4917
|
+
import { homedir as homedir5 } from "os";
|
|
4918
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
4919
|
+
function defaultUsageLogPath(homeDirOverride) {
|
|
4920
|
+
return join6(homeDirOverride ?? homedir5(), ".reasonix", "usage.jsonl");
|
|
4921
|
+
}
|
|
4922
|
+
function appendUsage(input) {
|
|
4923
|
+
const record = {
|
|
4924
|
+
ts: input.now ?? Date.now(),
|
|
4925
|
+
session: input.session,
|
|
4926
|
+
model: input.model,
|
|
4927
|
+
promptTokens: input.usage.promptTokens,
|
|
4928
|
+
completionTokens: input.usage.completionTokens,
|
|
4929
|
+
cacheHitTokens: input.usage.promptCacheHitTokens,
|
|
4930
|
+
cacheMissTokens: input.usage.promptCacheMissTokens,
|
|
4931
|
+
costUsd: costUsd(input.model, input.usage),
|
|
4932
|
+
claudeEquivUsd: claudeEquivalentCost(input.usage)
|
|
4933
|
+
};
|
|
4934
|
+
const path = input.path ?? defaultUsageLogPath();
|
|
4935
|
+
try {
|
|
4936
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
4937
|
+
appendFileSync2(path, `${JSON.stringify(record)}
|
|
4938
|
+
`, "utf8");
|
|
4939
|
+
} catch {
|
|
4940
|
+
}
|
|
4941
|
+
return record;
|
|
4942
|
+
}
|
|
4943
|
+
function readUsageLog(path = defaultUsageLogPath()) {
|
|
4944
|
+
if (!existsSync6(path)) return [];
|
|
4945
|
+
let raw;
|
|
4946
|
+
try {
|
|
4947
|
+
raw = readFileSync8(path, "utf8");
|
|
4948
|
+
} catch {
|
|
4949
|
+
return [];
|
|
4950
|
+
}
|
|
4951
|
+
const out = [];
|
|
4952
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
4953
|
+
if (!line.trim()) continue;
|
|
4954
|
+
try {
|
|
4955
|
+
const rec = JSON.parse(line);
|
|
4956
|
+
if (isValidRecord(rec)) out.push(rec);
|
|
4957
|
+
} catch {
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
return out;
|
|
4961
|
+
}
|
|
4962
|
+
function isValidRecord(rec) {
|
|
4963
|
+
if (!rec || typeof rec !== "object") return false;
|
|
4964
|
+
const r = rec;
|
|
4965
|
+
return typeof r.ts === "number" && typeof r.model === "string" && typeof r.promptTokens === "number" && typeof r.completionTokens === "number" && typeof r.cacheHitTokens === "number" && typeof r.cacheMissTokens === "number" && typeof r.costUsd === "number" && typeof r.claudeEquivUsd === "number";
|
|
4966
|
+
}
|
|
4967
|
+
function bucketCacheHitRatio(b) {
|
|
4968
|
+
const denom = b.cacheHitTokens + b.cacheMissTokens;
|
|
4969
|
+
return denom > 0 ? b.cacheHitTokens / denom : 0;
|
|
4970
|
+
}
|
|
4971
|
+
function bucketSavingsFraction(b) {
|
|
4972
|
+
return b.claudeEquivUsd > 0 ? 1 - b.costUsd / b.claudeEquivUsd : 0;
|
|
4973
|
+
}
|
|
4974
|
+
function emptyBucket(label, since) {
|
|
4975
|
+
return {
|
|
4976
|
+
label,
|
|
4977
|
+
since,
|
|
4978
|
+
turns: 0,
|
|
4979
|
+
promptTokens: 0,
|
|
4980
|
+
completionTokens: 0,
|
|
4981
|
+
cacheHitTokens: 0,
|
|
4982
|
+
cacheMissTokens: 0,
|
|
4983
|
+
costUsd: 0,
|
|
4984
|
+
claudeEquivUsd: 0
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
function addToBucket(b, r) {
|
|
4988
|
+
b.turns += 1;
|
|
4989
|
+
b.promptTokens += r.promptTokens;
|
|
4990
|
+
b.completionTokens += r.completionTokens;
|
|
4991
|
+
b.cacheHitTokens += r.cacheHitTokens;
|
|
4992
|
+
b.cacheMissTokens += r.cacheMissTokens;
|
|
4993
|
+
b.costUsd += r.costUsd;
|
|
4994
|
+
b.claudeEquivUsd += r.claudeEquivUsd;
|
|
4995
|
+
}
|
|
4996
|
+
function aggregateUsage(records, opts = {}) {
|
|
4997
|
+
const now = opts.now ?? Date.now();
|
|
4998
|
+
const day = 24 * 60 * 60 * 1e3;
|
|
4999
|
+
const today = emptyBucket("today", now - day);
|
|
5000
|
+
const week = emptyBucket("week", now - 7 * day);
|
|
5001
|
+
const month = emptyBucket("month", now - 30 * day);
|
|
5002
|
+
const all = emptyBucket("all-time", 0);
|
|
5003
|
+
const modelCounts = /* @__PURE__ */ new Map();
|
|
5004
|
+
const sessionCounts = /* @__PURE__ */ new Map();
|
|
5005
|
+
let firstSeen = null;
|
|
5006
|
+
let lastSeen = null;
|
|
5007
|
+
for (const r of records) {
|
|
5008
|
+
addToBucket(all, r);
|
|
5009
|
+
if (r.ts >= today.since) addToBucket(today, r);
|
|
5010
|
+
if (r.ts >= week.since) addToBucket(week, r);
|
|
5011
|
+
if (r.ts >= month.since) addToBucket(month, r);
|
|
5012
|
+
modelCounts.set(r.model, (modelCounts.get(r.model) ?? 0) + 1);
|
|
5013
|
+
const sessKey = r.session ?? "(ephemeral)";
|
|
5014
|
+
sessionCounts.set(sessKey, (sessionCounts.get(sessKey) ?? 0) + 1);
|
|
5015
|
+
if (firstSeen === null || r.ts < firstSeen) firstSeen = r.ts;
|
|
5016
|
+
if (lastSeen === null || r.ts > lastSeen) lastSeen = r.ts;
|
|
5017
|
+
}
|
|
5018
|
+
const byModel = Array.from(modelCounts.entries()).map(([model, turns]) => ({ model, turns })).sort((a, b) => b.turns - a.turns);
|
|
5019
|
+
const bySession = Array.from(sessionCounts.entries()).map(([session, turns]) => ({ session, turns })).sort((a, b) => b.turns - a.turns);
|
|
5020
|
+
return {
|
|
5021
|
+
buckets: [today, week, month, all],
|
|
5022
|
+
byModel,
|
|
5023
|
+
bySession,
|
|
5024
|
+
firstSeen,
|
|
5025
|
+
lastSeen
|
|
5026
|
+
};
|
|
5027
|
+
}
|
|
5028
|
+
function formatLogSize(path = defaultUsageLogPath()) {
|
|
5029
|
+
if (!existsSync6(path)) return "";
|
|
5030
|
+
try {
|
|
5031
|
+
const s = statSync3(path);
|
|
5032
|
+
const bytes = s.size;
|
|
5033
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
5034
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
5035
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
5036
|
+
} catch {
|
|
5037
|
+
return "";
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
|
|
4585
5041
|
// src/cli/commands/chat.tsx
|
|
4586
|
-
import { existsSync as
|
|
5042
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
4587
5043
|
import { render } from "ink";
|
|
4588
5044
|
import React15, { useState as useState7 } from "react";
|
|
4589
5045
|
|
|
5046
|
+
// src/cli/ui/App.tsx
|
|
5047
|
+
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
5048
|
+
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
5049
|
+
|
|
4590
5050
|
// src/tools/skills.ts
|
|
4591
5051
|
function registerSkillTools(registry, opts = {}) {
|
|
4592
|
-
const store = new SkillStore({
|
|
5052
|
+
const store = new SkillStore({
|
|
5053
|
+
homeDir: opts.homeDir,
|
|
5054
|
+
projectRoot: opts.projectRoot,
|
|
5055
|
+
disableBuiltins: opts.disableBuiltins
|
|
5056
|
+
});
|
|
5057
|
+
const subagentRunner = opts.subagentRunner;
|
|
4593
5058
|
registry.register({
|
|
4594
5059
|
name: "run_skill",
|
|
4595
|
-
description: "
|
|
5060
|
+
description: "Invoke a playbook from the Skills index pinned in the system prompt. Each entry is a self-contained instruction block. Skills marked with \u{1F9EC} in the index spawn an isolated subagent \u2014 only the final distilled answer comes back, the model's tool calls + reasoning during the run never enter your context. Plain skills are inlined: the body becomes a tool result you read and follow. For \u{1F9EC} subagent skills, supply 'arguments' describing the concrete task \u2014 they'll be the only context the subagent has.",
|
|
4596
5061
|
readOnly: true,
|
|
4597
5062
|
parameters: {
|
|
4598
5063
|
type: "object",
|
|
4599
5064
|
properties: {
|
|
4600
5065
|
name: {
|
|
4601
5066
|
type: "string",
|
|
4602
|
-
description: "Skill identifier as it appears in the pinned Skills index (e.g. 'review', 'security-review'). Case-sensitive."
|
|
5067
|
+
description: "Skill identifier as it appears in the pinned Skills index (e.g. 'explore', 'review', 'security-review'). Case-sensitive."
|
|
4603
5068
|
},
|
|
4604
5069
|
arguments: {
|
|
4605
5070
|
type: "string",
|
|
4606
|
-
description: "
|
|
5071
|
+
description: "Free-form arguments the skill should act on. For inline skills: appended to the body as an 'Arguments:' line; the skill's own instructions decide how to consume them. For \u{1F9EC} subagent skills: REQUIRED \u2014 becomes the entire task description the subagent receives, since it has no other context."
|
|
4607
5072
|
}
|
|
4608
5073
|
},
|
|
4609
5074
|
required: ["name"]
|
|
@@ -4622,7 +5087,20 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
4622
5087
|
});
|
|
4623
5088
|
}
|
|
4624
5089
|
const rawArgs = typeof args.arguments === "string" ? args.arguments.trim() : "";
|
|
4625
|
-
|
|
5090
|
+
if (skill.runAs === "subagent") {
|
|
5091
|
+
if (!subagentRunner) {
|
|
5092
|
+
return JSON.stringify({
|
|
5093
|
+
error: `run_skill: skill ${JSON.stringify(name)} is marked runAs=subagent but no subagent runner is configured for this session. Skill authors who need isolation should run inside reasonix code (or a library setup that passes subagentRunner to registerSkillTools).`
|
|
5094
|
+
});
|
|
5095
|
+
}
|
|
5096
|
+
if (!rawArgs) {
|
|
5097
|
+
return JSON.stringify({
|
|
5098
|
+
error: `run_skill: skill ${JSON.stringify(name)} is a subagent and requires 'arguments' \u2014 the subagent has no other context, so describe the concrete task in the arguments field.`
|
|
5099
|
+
});
|
|
5100
|
+
}
|
|
5101
|
+
return subagentRunner(skill, rawArgs);
|
|
5102
|
+
}
|
|
5103
|
+
const header2 = [
|
|
4626
5104
|
`# Skill: ${skill.name}`,
|
|
4627
5105
|
skill.description ? `> ${skill.description}` : "",
|
|
4628
5106
|
`(scope: ${skill.scope} \xB7 ${skill.path})`
|
|
@@ -4630,7 +5108,7 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
4630
5108
|
const argsBlock = rawArgs ? `
|
|
4631
5109
|
|
|
4632
5110
|
Arguments: ${rawArgs}` : "";
|
|
4633
|
-
return `${
|
|
5111
|
+
return `${header2}
|
|
4634
5112
|
|
|
4635
5113
|
${skill.body}${argsBlock}`;
|
|
4636
5114
|
}
|
|
@@ -4638,10 +5116,6 @@ ${skill.body}${argsBlock}`;
|
|
|
4638
5116
|
return registry;
|
|
4639
5117
|
}
|
|
4640
5118
|
|
|
4641
|
-
// src/cli/ui/App.tsx
|
|
4642
|
-
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
4643
|
-
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
4644
|
-
|
|
4645
5119
|
// src/cli/ui/EventLog.tsx
|
|
4646
5120
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
4647
5121
|
import React4 from "react";
|
|
@@ -4714,7 +5188,7 @@ function stripMath(s) {
|
|
|
4714
5188
|
).replace(/\\sqrt\s*\{([^{}]+)\}/g, (_m, g) => `\u221A(${g.trim()})`).replace(/\\boxed\s*\{([^{}]+)\}/g, (_m, g) => `\u3010${g.trim()}\u3011`).replace(/\\text\s*\{([^{}]+)\}/g, (_m, g) => g.trim()).replace(/\\overline\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0304`).replace(/\\hat\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0302`).replace(/\\vec\s*\{([^{}]+)\}/g, (_m, g) => `\u2192${g.trim()}`).replace(/\\cdot/g, "\xB7").replace(/\\times/g, "\xD7").replace(/\\div/g, "\xF7").replace(/\\pm/g, "\xB1").replace(/\\mp/g, "\u2213").replace(/\\leq/g, "\u2264").replace(/\\geq/g, "\u2265").replace(/\\neq/g, "\u2260").replace(/\\approx/g, "\u2248").replace(/\\in\b/g, "\u2208").replace(/\\notin\b/g, "\u2209").replace(/\\infty/g, "\u221E").replace(/\\sum\b/g, "\u03A3").replace(/\\prod\b/g, "\u03A0").replace(/\\int\b/g, "\u222B").replace(/\\alpha/g, "\u03B1").replace(/\\beta/g, "\u03B2").replace(/\\gamma/g, "\u03B3").replace(/\\delta/g, "\u03B4").replace(/\\theta/g, "\u03B8").replace(/\\lambda/g, "\u03BB").replace(/\\mu/g, "\u03BC").replace(/\\pi/g, "\u03C0").replace(/\\sigma/g, "\u03C3").replace(/\\phi/g, "\u03C6").replace(/\\omega/g, "\u03C9").replace(/\\implies\b/g, "\u21D2").replace(/\\iff\b/g, "\u21D4").replace(/\\to\b/g, "\u2192").replace(/\\rightarrow/g, "\u2192").replace(/\\Rightarrow/g, "\u21D2").replace(/\\leftarrow/g, "\u2190").replace(/\\Leftarrow/g, "\u21D0").replace(/\\ldots/g, "\u2026").replace(/\\cdots/g, "\u22EF").replace(/\\quad/g, " ").replace(/\\qquad/g, " ").replace(/\\,/g, " ").replace(/\\;/g, " ").replace(/\\!/g, "").replace(/\\\\/g, "\n").replace(/\^\{([\w+-]+)\}/g, (_m, g) => toSuperscript(g)).replace(/\^([0-9+\-n])/g, (_m, g) => toSuperscript(g)).replace(/_\{([\w+-]+)\}/g, (_m, g) => toSubscript(g)).replace(/_([0-9+\-])/g, (_m, g) => toSubscript(g)).replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, "($1)/($2)").replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}/g, "$1").replace(/\\[a-zA-Z]+/g, "").replace(/[ \t]{2,}/g, " ");
|
|
4715
5189
|
}
|
|
4716
5190
|
var INLINE_RE = /(\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
|
|
4717
|
-
function InlineMd({ text }) {
|
|
5191
|
+
function InlineMd({ text, padTo }) {
|
|
4718
5192
|
const parts = [];
|
|
4719
5193
|
let last = 0;
|
|
4720
5194
|
let idx = 0;
|
|
@@ -4746,8 +5220,20 @@ function InlineMd({ text }) {
|
|
|
4746
5220
|
if (last < text.length) {
|
|
4747
5221
|
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `t${idx++}` }, text.slice(last)));
|
|
4748
5222
|
}
|
|
5223
|
+
if (padTo !== void 0) {
|
|
5224
|
+
const seen = visibleWidth(text);
|
|
5225
|
+
if (seen < padTo) {
|
|
5226
|
+
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `pad${idx++}` }, " ".repeat(padTo - seen)));
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
4749
5229
|
return /* @__PURE__ */ React2.createElement(Text2, null, parts);
|
|
4750
5230
|
}
|
|
5231
|
+
function stripInlineMarkup(s) {
|
|
5232
|
+
return s.replace(/\*\*([^*\n]+?)\*\*/g, "$1").replace(/```([^\n]+?)```/g, (_m, c) => c.replace(/^(\w+)\s+/, "")).replace(/`([^`\n]+?)`/g, "$1").replace(/(?<![*\w])\*([^*\n]+?)\*(?!\w)/g, "$1");
|
|
5233
|
+
}
|
|
5234
|
+
function visibleWidth(s) {
|
|
5235
|
+
return displayWidth(stripInlineMarkup(s));
|
|
5236
|
+
}
|
|
4751
5237
|
function parseBlocks(raw) {
|
|
4752
5238
|
const lines = raw.split(/\r?\n/);
|
|
4753
5239
|
const out = [];
|
|
@@ -4854,21 +5340,51 @@ function parseBlocks(raw) {
|
|
|
4854
5340
|
out.push({ kind: "heading", level: hm[1].length, text: hm[2].trim() });
|
|
4855
5341
|
continue;
|
|
4856
5342
|
}
|
|
4857
|
-
if (line
|
|
5343
|
+
if (/^\s*┌─+┐\s*$/.test(line)) {
|
|
5344
|
+
let j = i + 1;
|
|
5345
|
+
const bodyLines = [];
|
|
5346
|
+
while (j < lines.length && !/^\s*└─+┘\s*$/.test(lines[j])) {
|
|
5347
|
+
const inner = lines[j];
|
|
5348
|
+
const m = inner.match(/^\s*│\s?(.*?)\s?│\s*$/);
|
|
5349
|
+
bodyLines.push(m ? m[1] ?? "" : inner);
|
|
5350
|
+
j++;
|
|
5351
|
+
}
|
|
5352
|
+
if (j < lines.length) {
|
|
5353
|
+
flushPara();
|
|
5354
|
+
flushList();
|
|
5355
|
+
out.push({ kind: "code", lang: "", text: bodyLines.join("\n") });
|
|
5356
|
+
i = j;
|
|
5357
|
+
continue;
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
if (line.includes("|") || line.includes("\u2502")) {
|
|
4858
5361
|
const next = (lines[i + 1] ?? "").trim();
|
|
4859
|
-
|
|
5362
|
+
const isGfmSep = /^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(next);
|
|
5363
|
+
const isBoxSep = /^[│─┼┬┴┌┐└┘├┤\s]+$/.test(next) && /─{2,}/.test(next);
|
|
5364
|
+
if (isGfmSep || isBoxSep) {
|
|
4860
5365
|
flushPara();
|
|
4861
5366
|
flushList();
|
|
4862
|
-
const
|
|
5367
|
+
const header2 = splitTableRow(line);
|
|
5368
|
+
const colCount = header2.length;
|
|
4863
5369
|
const rows = [];
|
|
4864
5370
|
let j = i + 2;
|
|
4865
5371
|
while (j < lines.length) {
|
|
4866
5372
|
const r = lines[j].replace(/\s+$/g, "");
|
|
4867
|
-
if (r.trim() === ""
|
|
5373
|
+
if (r.trim() === "") break;
|
|
5374
|
+
if (!r.includes("|") && !r.includes("\u2502")) {
|
|
5375
|
+
const prev = rows[rows.length - 1];
|
|
5376
|
+
if (prev && prev.length === colCount) {
|
|
5377
|
+
const lastIdx = prev.length - 1;
|
|
5378
|
+
prev[lastIdx] = `${prev[lastIdx] ?? ""} ${r.trim()}`;
|
|
5379
|
+
j++;
|
|
5380
|
+
continue;
|
|
5381
|
+
}
|
|
5382
|
+
break;
|
|
5383
|
+
}
|
|
4868
5384
|
rows.push(splitTableRow(r));
|
|
4869
5385
|
j++;
|
|
4870
5386
|
}
|
|
4871
|
-
out.push({ kind: "table", header, rows });
|
|
5387
|
+
out.push({ kind: "table", header: header2, rows });
|
|
4872
5388
|
i = j - 1;
|
|
4873
5389
|
continue;
|
|
4874
5390
|
}
|
|
@@ -4923,7 +5439,7 @@ function BlockView({ block }) {
|
|
|
4923
5439
|
}
|
|
4924
5440
|
function splitTableRow(line) {
|
|
4925
5441
|
const SENTINEL = "\0";
|
|
4926
|
-
const masked = line.replace(/\\\|/g, SENTINEL);
|
|
5442
|
+
const masked = line.replace(/\\\|/g, SENTINEL).replace(/│/g, "|");
|
|
4927
5443
|
const trimmed = masked.trim().replace(/^\||\|$/g, "");
|
|
4928
5444
|
return trimmed.split("|").map((c) => c.trim().replace(new RegExp(SENTINEL, "g"), "|"));
|
|
4929
5445
|
}
|
|
@@ -4931,24 +5447,19 @@ function TableBlockRow({ block }) {
|
|
|
4931
5447
|
const colCount = Math.max(block.header.length, ...block.rows.map((r) => r.length));
|
|
4932
5448
|
const widths = [];
|
|
4933
5449
|
for (let c = 0; c < colCount; c++) {
|
|
4934
|
-
const cellLengths = [
|
|
4935
|
-
for (const r of block.rows) cellLengths.push(
|
|
5450
|
+
const cellLengths = [visibleWidth(block.header[c] ?? "")];
|
|
5451
|
+
for (const r of block.rows) cellLengths.push(visibleWidth(r[c] ?? ""));
|
|
4936
5452
|
widths.push(Math.min(40, Math.max(3, ...cellLengths)));
|
|
4937
5453
|
}
|
|
4938
|
-
const pad2 = (s, w) => {
|
|
4939
|
-
const dw = displayWidth(s);
|
|
4940
|
-
if (dw >= w) return s;
|
|
4941
|
-
return s + " ".repeat(w - dw);
|
|
4942
|
-
};
|
|
4943
5454
|
const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u253C\u2500");
|
|
4944
5455
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, null, block.header.map((cell, ci) => (
|
|
4945
5456
|
// biome-ignore lint/suspicious/noArrayIndexKey: table columns never reorder — derived from a static header array
|
|
4946
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `h-${ci}`, bold: true, color: "cyan" },
|
|
5457
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `h-${ci}`, bold: true, color: "cyan" }, /* @__PURE__ */ React2.createElement(InlineMd, { text: cell, padTo: widths[ci] ?? 3 }), ci < colCount - 1 ? " \u2502 " : "")
|
|
4947
5458
|
))), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, separator), block.rows.map((row2, ri) => (
|
|
4948
5459
|
// biome-ignore lint/suspicious/noArrayIndexKey: table rows render in source order and don't reorder
|
|
4949
5460
|
/* @__PURE__ */ React2.createElement(Box2, { key: `r-${ri}` }, Array.from({ length: colCount }).map((_, ci) => (
|
|
4950
5461
|
// biome-ignore lint/suspicious/noArrayIndexKey: same — column axis is fixed by the table shape
|
|
4951
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `c-${ri}-${ci}` },
|
|
5462
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c-${ri}-${ci}` }, /* @__PURE__ */ React2.createElement(InlineMd, { text: row2[ci] ?? "", padTo: widths[ci] ?? 3 }), ci < colCount - 1 ? " \u2502 " : "")
|
|
4952
5463
|
)))
|
|
4953
5464
|
)));
|
|
4954
5465
|
}
|
|
@@ -5593,6 +6104,114 @@ function formatTokens(n) {
|
|
|
5593
6104
|
|
|
5594
6105
|
// src/cli/ui/slash.ts
|
|
5595
6106
|
import { spawnSync } from "child_process";
|
|
6107
|
+
|
|
6108
|
+
// src/cli/commands/stats.ts
|
|
6109
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
6110
|
+
function statsCommand(opts) {
|
|
6111
|
+
if (opts.transcript) {
|
|
6112
|
+
transcriptSummary(opts.transcript);
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
dashboard(opts);
|
|
6116
|
+
}
|
|
6117
|
+
function transcriptSummary(path) {
|
|
6118
|
+
if (!existsSync7(path)) {
|
|
6119
|
+
console.error(`no such transcript: ${path}`);
|
|
6120
|
+
process.exit(1);
|
|
6121
|
+
}
|
|
6122
|
+
const lines = readFileSync9(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
6123
|
+
let assistantTurns = 0;
|
|
6124
|
+
let toolCalls = 0;
|
|
6125
|
+
let lastTurn = 0;
|
|
6126
|
+
for (const line of lines) {
|
|
6127
|
+
try {
|
|
6128
|
+
const rec = JSON.parse(line);
|
|
6129
|
+
if (rec.role === "assistant_final") assistantTurns++;
|
|
6130
|
+
if (rec.role === "tool") toolCalls++;
|
|
6131
|
+
if (typeof rec.turn === "number") lastTurn = Math.max(lastTurn, rec.turn);
|
|
6132
|
+
} catch {
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6135
|
+
console.log(`transcript: ${path}`);
|
|
6136
|
+
console.log(`assistant turns: ${assistantTurns}`);
|
|
6137
|
+
console.log(`tool invocations: ${toolCalls}`);
|
|
6138
|
+
console.log(`last turn index: ${lastTurn}`);
|
|
6139
|
+
}
|
|
6140
|
+
function dashboard(opts) {
|
|
6141
|
+
const path = opts.logPath ?? defaultUsageLogPath();
|
|
6142
|
+
const records = readUsageLog(path);
|
|
6143
|
+
if (records.length === 0) {
|
|
6144
|
+
console.log("no usage data yet.");
|
|
6145
|
+
console.log("");
|
|
6146
|
+
console.log(` ${path}`);
|
|
6147
|
+
console.log("");
|
|
6148
|
+
console.log("run `reasonix chat`, `reasonix code`, or `reasonix run <task>` \u2014 every turn");
|
|
6149
|
+
console.log("appends one line to the log and `reasonix stats` will roll it up.");
|
|
6150
|
+
return;
|
|
6151
|
+
}
|
|
6152
|
+
const agg = aggregateUsage(records, { now: opts.now });
|
|
6153
|
+
console.log(renderDashboard(agg, path));
|
|
6154
|
+
}
|
|
6155
|
+
function renderDashboard(agg, logPath) {
|
|
6156
|
+
const lines = [];
|
|
6157
|
+
const size = formatLogSize(logPath);
|
|
6158
|
+
lines.push(`Reasonix usage \u2014 ${logPath}${size ? ` (${size})` : ""}`);
|
|
6159
|
+
lines.push("");
|
|
6160
|
+
lines.push(header());
|
|
6161
|
+
lines.push(divider());
|
|
6162
|
+
for (const b of agg.buckets) {
|
|
6163
|
+
lines.push(bucketRow(b));
|
|
6164
|
+
}
|
|
6165
|
+
lines.push("");
|
|
6166
|
+
if (agg.byModel.length > 0) {
|
|
6167
|
+
const totalTurns = agg.buckets[agg.buckets.length - 1]?.turns ?? 0;
|
|
6168
|
+
const top = agg.byModel[0];
|
|
6169
|
+
if (top && totalTurns > 0) {
|
|
6170
|
+
const pct2 = (top.turns / totalTurns * 100).toFixed(0);
|
|
6171
|
+
lines.push(`most used model: ${top.model} (${pct2}% of turns)`);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
if (agg.bySession.length > 0) {
|
|
6175
|
+
const top = agg.bySession[0];
|
|
6176
|
+
if (top) lines.push(`top session: ${top.session} (${top.turns} turns)`);
|
|
6177
|
+
}
|
|
6178
|
+
if (agg.firstSeen) {
|
|
6179
|
+
lines.push(`tracked since: ${new Date(agg.firstSeen).toISOString().slice(0, 10)}`);
|
|
6180
|
+
}
|
|
6181
|
+
return lines.join("\n");
|
|
6182
|
+
}
|
|
6183
|
+
function header() {
|
|
6184
|
+
return [
|
|
6185
|
+
pad("", 10),
|
|
6186
|
+
pad("turns", 8, "right"),
|
|
6187
|
+
pad("cache hit", 10, "right"),
|
|
6188
|
+
pad("cost (USD)", 14, "right"),
|
|
6189
|
+
pad("vs Claude", 14, "right"),
|
|
6190
|
+
pad("saved", 10, "right")
|
|
6191
|
+
].join(" ");
|
|
6192
|
+
}
|
|
6193
|
+
function divider() {
|
|
6194
|
+
return "-".repeat(70);
|
|
6195
|
+
}
|
|
6196
|
+
function bucketRow(b) {
|
|
6197
|
+
const hit = bucketCacheHitRatio(b);
|
|
6198
|
+
const savings = bucketSavingsFraction(b);
|
|
6199
|
+
return [
|
|
6200
|
+
pad(b.label, 10),
|
|
6201
|
+
pad(b.turns.toString(), 8, "right"),
|
|
6202
|
+
pad(b.turns > 0 ? `${(hit * 100).toFixed(1)}%` : "\u2014", 10, "right"),
|
|
6203
|
+
pad(b.turns > 0 ? `$${b.costUsd.toFixed(6)}` : "\u2014", 14, "right"),
|
|
6204
|
+
pad(b.turns > 0 ? `$${b.claudeEquivUsd.toFixed(4)}` : "\u2014", 14, "right"),
|
|
6205
|
+
pad(b.turns > 0 && savings > 0 ? `${(savings * 100).toFixed(1)}%` : "\u2014", 10, "right")
|
|
6206
|
+
].join(" ");
|
|
6207
|
+
}
|
|
6208
|
+
function pad(s, width, align = "left") {
|
|
6209
|
+
if (s.length >= width) return s;
|
|
6210
|
+
const fill = " ".repeat(width - s.length);
|
|
6211
|
+
return align === "right" ? `${fill}${s}` : `${s}${fill}`;
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6214
|
+
// src/cli/ui/slash.ts
|
|
5596
6215
|
var SLASH_COMMANDS = [
|
|
5597
6216
|
{ cmd: "help", summary: "show the full command reference" },
|
|
5598
6217
|
{ cmd: "status", summary: "current model, flags, context, session" },
|
|
@@ -5625,6 +6244,10 @@ var SLASH_COMMANDS = [
|
|
|
5625
6244
|
cmd: "update",
|
|
5626
6245
|
summary: "show current vs latest version + the shell command to upgrade"
|
|
5627
6246
|
},
|
|
6247
|
+
{
|
|
6248
|
+
cmd: "stats",
|
|
6249
|
+
summary: "cross-session cost dashboard (today / week / month / all-time \xB7 cache hit \xB7 vs Claude)"
|
|
6250
|
+
},
|
|
5628
6251
|
{ cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
|
|
5629
6252
|
{ cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
|
|
5630
6253
|
{ cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
|
|
@@ -5805,6 +6428,9 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
5805
6428
|
case "update": {
|
|
5806
6429
|
return handleUpdateSlash(ctx);
|
|
5807
6430
|
}
|
|
6431
|
+
case "stats": {
|
|
6432
|
+
return handleStatsSlash();
|
|
6433
|
+
}
|
|
5808
6434
|
case "think":
|
|
5809
6435
|
case "reasoning": {
|
|
5810
6436
|
const raw = loop.scratch.reasoning;
|
|
@@ -6041,6 +6667,24 @@ ${entry.text}`
|
|
|
6041
6667
|
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
6042
6668
|
}
|
|
6043
6669
|
}
|
|
6670
|
+
function handleStatsSlash() {
|
|
6671
|
+
const path = defaultUsageLogPath();
|
|
6672
|
+
const records = readUsageLog(path);
|
|
6673
|
+
if (records.length === 0) {
|
|
6674
|
+
return {
|
|
6675
|
+
info: [
|
|
6676
|
+
"no usage data yet.",
|
|
6677
|
+
"",
|
|
6678
|
+
` ${path}`,
|
|
6679
|
+
"",
|
|
6680
|
+
"every turn you run here appends one record \u2014 this session's turns",
|
|
6681
|
+
"will show up in the dashboard once you send a message."
|
|
6682
|
+
].join("\n")
|
|
6683
|
+
};
|
|
6684
|
+
}
|
|
6685
|
+
const agg = aggregateUsage(records);
|
|
6686
|
+
return { info: renderDashboard(agg, path) };
|
|
6687
|
+
}
|
|
6044
6688
|
function handleUpdateSlash(ctx) {
|
|
6045
6689
|
const latest = ctx.latestVersion ?? null;
|
|
6046
6690
|
const lines = [`current: reasonix ${VERSION}`];
|
|
@@ -6185,12 +6829,12 @@ function handleSkillSlash(args, ctx) {
|
|
|
6185
6829
|
};
|
|
6186
6830
|
}
|
|
6187
6831
|
const extra = args.slice(1).join(" ").trim();
|
|
6188
|
-
const
|
|
6832
|
+
const header2 = `# Skill: ${skill.name}${skill.description ? `
|
|
6189
6833
|
> ${skill.description}` : ""}`;
|
|
6190
6834
|
const argsLine = extra ? `
|
|
6191
6835
|
|
|
6192
6836
|
Arguments: ${extra}` : "";
|
|
6193
|
-
const payload = `${
|
|
6837
|
+
const payload = `${header2}
|
|
6194
6838
|
|
|
6195
6839
|
${skill.body}${argsLine}`;
|
|
6196
6840
|
return {
|
|
@@ -6368,9 +7012,9 @@ function appendSection(lines, label, section) {
|
|
|
6368
7012
|
}
|
|
6369
7013
|
function formatToolList(history) {
|
|
6370
7014
|
const total = history.length;
|
|
6371
|
-
const
|
|
7015
|
+
const header2 = `Tool calls in this session (${total}, most recent first):`;
|
|
6372
7016
|
const shown = Math.min(total, 10);
|
|
6373
|
-
const lines = [
|
|
7017
|
+
const lines = [header2];
|
|
6374
7018
|
for (let i = 0; i < shown; i++) {
|
|
6375
7019
|
const entry = history[total - 1 - i];
|
|
6376
7020
|
if (!entry) continue;
|
|
@@ -6451,6 +7095,7 @@ function App({
|
|
|
6451
7095
|
const abortedThisTurn = useRef2(false);
|
|
6452
7096
|
const [ongoingTool, setOngoingTool] = useState5(null);
|
|
6453
7097
|
const [toolProgress, setToolProgress] = useState5(null);
|
|
7098
|
+
const [subagentActivity, setSubagentActivity] = useState5(null);
|
|
6454
7099
|
const [statusLine, setStatusLine] = useState5(null);
|
|
6455
7100
|
const [balance, setBalance] = useState5(null);
|
|
6456
7101
|
const [latestVersion, setLatestVersion] = useState5(null);
|
|
@@ -6507,9 +7152,30 @@ function App({
|
|
|
6507
7152
|
});
|
|
6508
7153
|
}, [slashMatches]);
|
|
6509
7154
|
const loopRef = useRef2(null);
|
|
7155
|
+
const subagentSinkRef = useRef2({ current: null });
|
|
6510
7156
|
const loop = useMemo(() => {
|
|
6511
7157
|
if (loopRef.current) return loopRef.current;
|
|
6512
7158
|
const client = new DeepSeekClient();
|
|
7159
|
+
if (tools && !tools.has("run_skill")) {
|
|
7160
|
+
registerSkillTools(tools, {
|
|
7161
|
+
projectRoot: codeMode?.rootDir,
|
|
7162
|
+
subagentRunner: async (skill, task) => {
|
|
7163
|
+
const result = await spawnSubagent({
|
|
7164
|
+
client,
|
|
7165
|
+
parentRegistry: tools,
|
|
7166
|
+
// Skill body is the subagent's persona/playbook; the user-
|
|
7167
|
+
// supplied task is what to actually do inside it.
|
|
7168
|
+
system: skill.body,
|
|
7169
|
+
task,
|
|
7170
|
+
// Per-skill model override (frontmatter `model: ...`),
|
|
7171
|
+
// else falls through to spawnSubagent's default.
|
|
7172
|
+
model: skill.model,
|
|
7173
|
+
sink: subagentSinkRef.current
|
|
7174
|
+
});
|
|
7175
|
+
return formatSubagentResult(result);
|
|
7176
|
+
}
|
|
7177
|
+
});
|
|
7178
|
+
}
|
|
6513
7179
|
const prefix = new ImmutablePrefix({
|
|
6514
7180
|
system,
|
|
6515
7181
|
toolSpecs: tools?.specs()
|
|
@@ -6527,7 +7193,7 @@ function App({
|
|
|
6527
7193
|
});
|
|
6528
7194
|
loopRef.current = l;
|
|
6529
7195
|
return l;
|
|
6530
|
-
}, [model, system, harvest2, branch, session, tools]);
|
|
7196
|
+
}, [model, system, harvest2, branch, session, tools, codeMode]);
|
|
6531
7197
|
useEffect2(() => {
|
|
6532
7198
|
loop.hooks = hookList;
|
|
6533
7199
|
}, [loop, hookList]);
|
|
@@ -6567,6 +7233,40 @@ function App({
|
|
|
6567
7233
|
if (progressSink.current) progressSink.current = null;
|
|
6568
7234
|
};
|
|
6569
7235
|
}, [progressSink]);
|
|
7236
|
+
useEffect2(() => {
|
|
7237
|
+
subagentSinkRef.current.current = (ev) => {
|
|
7238
|
+
if (ev.kind === "start") {
|
|
7239
|
+
setSubagentActivity({
|
|
7240
|
+
task: ev.task,
|
|
7241
|
+
iter: ev.iter ?? 0,
|
|
7242
|
+
elapsedMs: ev.elapsedMs ?? 0
|
|
7243
|
+
});
|
|
7244
|
+
return;
|
|
7245
|
+
}
|
|
7246
|
+
if (ev.kind === "progress") {
|
|
7247
|
+
setSubagentActivity({
|
|
7248
|
+
task: ev.task,
|
|
7249
|
+
iter: ev.iter ?? 0,
|
|
7250
|
+
elapsedMs: ev.elapsedMs ?? 0
|
|
7251
|
+
});
|
|
7252
|
+
return;
|
|
7253
|
+
}
|
|
7254
|
+
setSubagentActivity(null);
|
|
7255
|
+
const seconds = ((ev.elapsedMs ?? 0) / 1e3).toFixed(1);
|
|
7256
|
+
const summary2 = ev.error ? `\u{1F9EC} subagent "${ev.task}" failed after ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \u2014 ${ev.error}` : `\u{1F9EC} subagent "${ev.task}" done in ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \xB7 ${ev.turns ?? 0} turn(s)`;
|
|
7257
|
+
setHistorical((prev) => [
|
|
7258
|
+
...prev,
|
|
7259
|
+
{
|
|
7260
|
+
id: `subagent-end-${Date.now()}`,
|
|
7261
|
+
role: "info",
|
|
7262
|
+
text: summary2
|
|
7263
|
+
}
|
|
7264
|
+
]);
|
|
7265
|
+
};
|
|
7266
|
+
return () => {
|
|
7267
|
+
subagentSinkRef.current.current = null;
|
|
7268
|
+
};
|
|
7269
|
+
}, []);
|
|
6570
7270
|
const sessionBannerShown = useRef2(false);
|
|
6571
7271
|
useEffect2(() => {
|
|
6572
7272
|
if (sessionBannerShown.current) return;
|
|
@@ -6864,6 +7564,13 @@ function App({
|
|
|
6864
7564
|
const repairNote = ev.repair ? describeRepair(ev.repair) : "";
|
|
6865
7565
|
setStreaming(null);
|
|
6866
7566
|
setSummary(loop.stats.summary());
|
|
7567
|
+
if (ev.stats?.usage) {
|
|
7568
|
+
appendUsage({
|
|
7569
|
+
session: session ?? null,
|
|
7570
|
+
model: ev.stats.model,
|
|
7571
|
+
usage: ev.stats.usage
|
|
7572
|
+
});
|
|
7573
|
+
}
|
|
6867
7574
|
const finalText = ev.content || streamRef.text;
|
|
6868
7575
|
const iterReasoning = streamRef.reasoning || void 0;
|
|
6869
7576
|
const iterId = `${assistantId}-i${assistantIterCounter.current++}`;
|
|
@@ -7006,6 +7713,7 @@ function App({
|
|
|
7006
7713
|
mcpSpecs,
|
|
7007
7714
|
mcpServers,
|
|
7008
7715
|
planMode,
|
|
7716
|
+
session,
|
|
7009
7717
|
slashSelected,
|
|
7010
7718
|
togglePlanMode,
|
|
7011
7719
|
writeTranscript
|
|
@@ -7169,7 +7877,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
7169
7877
|
balance,
|
|
7170
7878
|
updateAvailable
|
|
7171
7879
|
}
|
|
7172
|
-
), /* @__PURE__ */ React12.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React12.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(EventRow, { event: streaming })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React12.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : 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(
|
|
7880
|
+
), /* @__PURE__ */ React12.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React12.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(EventRow, { event: streaming })) : 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(
|
|
7173
7881
|
PlanRefineInput,
|
|
7174
7882
|
{
|
|
7175
7883
|
mode: stagedInput.mode,
|
|
@@ -7199,6 +7907,13 @@ function StatusRow({ text }) {
|
|
|
7199
7907
|
const elapsed = useElapsedSeconds();
|
|
7200
7908
|
return /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` ${elapsed}s`));
|
|
7201
7909
|
}
|
|
7910
|
+
function SubagentRow({
|
|
7911
|
+
activity
|
|
7912
|
+
}) {
|
|
7913
|
+
const tick = useTick();
|
|
7914
|
+
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
7915
|
+
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" }, ` \u{1F9EC} subagent: ${activity.task}`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
|
|
7916
|
+
}
|
|
7202
7917
|
function OngoingToolRow({
|
|
7203
7918
|
tool,
|
|
7204
7919
|
progress
|
|
@@ -7268,8 +7983,8 @@ function formatEditResults(results) {
|
|
|
7268
7983
|
});
|
|
7269
7984
|
const ok = results.filter((r) => r.status === "applied" || r.status === "created").length;
|
|
7270
7985
|
const total = results.length;
|
|
7271
|
-
const
|
|
7272
|
-
return [
|
|
7986
|
+
const header2 = `\u25B8 edit blocks: ${ok}/${total} applied \u2014 /undo to roll back, or \`git diff\` to review`;
|
|
7987
|
+
return [header2, ...lines].join("\n");
|
|
7273
7988
|
}
|
|
7274
7989
|
function formatPendingPreview(blocks) {
|
|
7275
7990
|
const lines = blocks.map((b) => {
|
|
@@ -7278,8 +7993,8 @@ function formatPendingPreview(blocks) {
|
|
|
7278
7993
|
const tag = b.search === "" ? "NEW " : " ";
|
|
7279
7994
|
return ` ${tag}${b.path} (-${removed} +${added} lines)`;
|
|
7280
7995
|
});
|
|
7281
|
-
const
|
|
7282
|
-
return [
|
|
7996
|
+
const header2 = `\u25B8 ${blocks.length} pending edit block(s) \u2014 /apply (or y) to commit \xB7 /discard (or n) to drop`;
|
|
7997
|
+
return [header2, ...lines].join("\n");
|
|
7283
7998
|
}
|
|
7284
7999
|
function countLines2(s) {
|
|
7285
8000
|
if (s.length === 0) return 0;
|
|
@@ -7517,14 +8232,13 @@ async function chatCommand(opts) {
|
|
|
7517
8232
|
if (!opts.seedTools) {
|
|
7518
8233
|
if (!tools) tools = new ToolRegistry();
|
|
7519
8234
|
registerMemoryTools(tools, {});
|
|
7520
|
-
registerSkillTools(tools);
|
|
7521
8235
|
}
|
|
7522
8236
|
let sessionPreview;
|
|
7523
8237
|
if (opts.session && !opts.forceResume && !opts.forceNew) {
|
|
7524
8238
|
const prior = loadSessionMessages(opts.session);
|
|
7525
8239
|
if (prior.length > 0) {
|
|
7526
8240
|
const p = sessionPath(opts.session);
|
|
7527
|
-
const mtime =
|
|
8241
|
+
const mtime = existsSync8(p) ? statSync4(p).mtime : /* @__PURE__ */ new Date();
|
|
7528
8242
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
7529
8243
|
}
|
|
7530
8244
|
} else if (opts.session && opts.forceNew) {
|
|
@@ -7557,7 +8271,7 @@ async function chatCommand(opts) {
|
|
|
7557
8271
|
// src/cli/commands/code.tsx
|
|
7558
8272
|
import { basename, resolve as resolve5 } from "path";
|
|
7559
8273
|
async function codeCommand(opts = {}) {
|
|
7560
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
8274
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-6DMLWG2H.js");
|
|
7561
8275
|
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
7562
8276
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
7563
8277
|
const tools = new ToolRegistry();
|
|
@@ -7573,7 +8287,6 @@ async function codeCommand(opts = {}) {
|
|
|
7573
8287
|
});
|
|
7574
8288
|
registerPlanTool(tools);
|
|
7575
8289
|
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
7576
|
-
registerSkillTools(tools, { projectRoot: rootDir });
|
|
7577
8290
|
process.stderr.write(
|
|
7578
8291
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
7579
8292
|
`
|
|
@@ -7867,7 +8580,7 @@ function mcpListCommand(opts) {
|
|
|
7867
8580
|
console.log("Popular MCP servers you can bridge into Reasonix:");
|
|
7868
8581
|
console.log("");
|
|
7869
8582
|
for (const entry of MCP_CATALOG) {
|
|
7870
|
-
console.log(` ${
|
|
8583
|
+
console.log(` ${pad2(entry.name, 12)} ${entry.summary}`);
|
|
7871
8584
|
console.log(` ${mcpCommandFor(entry)}`);
|
|
7872
8585
|
if (entry.note) console.log(` \xB7 ${entry.note}`);
|
|
7873
8586
|
console.log("");
|
|
@@ -7880,7 +8593,7 @@ function mcpListCommand(opts) {
|
|
|
7880
8593
|
" https://mcp.so \u2014 community-maintained catalog"
|
|
7881
8594
|
);
|
|
7882
8595
|
}
|
|
7883
|
-
function
|
|
8596
|
+
function pad2(s, width) {
|
|
7884
8597
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
7885
8598
|
}
|
|
7886
8599
|
|
|
@@ -8149,6 +8862,9 @@ async function runCommand2(opts) {
|
|
|
8149
8862
|
[error] ${ev.error}
|
|
8150
8863
|
`);
|
|
8151
8864
|
if (ev.role === "done") process.stdout.write("\n");
|
|
8865
|
+
if (ev.role === "assistant_final" && ev.stats?.usage) {
|
|
8866
|
+
appendUsage({ session: null, model: ev.stats.model, usage: ev.stats.usage });
|
|
8867
|
+
}
|
|
8152
8868
|
if (transcriptStream && ev.role !== "assistant_delta") {
|
|
8153
8869
|
writeRecord(transcriptStream, recordFromLoopEvent(ev, { model: opts.model, prefixHash }));
|
|
8154
8870
|
}
|
|
@@ -8545,32 +9261,6 @@ async function setupCommand(_opts = {}) {
|
|
|
8545
9261
|
await waitUntilExit();
|
|
8546
9262
|
}
|
|
8547
9263
|
|
|
8548
|
-
// src/cli/commands/stats.ts
|
|
8549
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
8550
|
-
function statsCommand(opts) {
|
|
8551
|
-
if (!existsSync7(opts.transcript)) {
|
|
8552
|
-
console.error(`no such transcript: ${opts.transcript}`);
|
|
8553
|
-
process.exit(1);
|
|
8554
|
-
}
|
|
8555
|
-
const lines = readFileSync8(opts.transcript, "utf8").split(/\r?\n/).filter(Boolean);
|
|
8556
|
-
let assistantTurns = 0;
|
|
8557
|
-
let toolCalls = 0;
|
|
8558
|
-
let lastTurn = 0;
|
|
8559
|
-
for (const line of lines) {
|
|
8560
|
-
try {
|
|
8561
|
-
const rec = JSON.parse(line);
|
|
8562
|
-
if (rec.role === "assistant_final") assistantTurns++;
|
|
8563
|
-
if (rec.role === "tool") toolCalls++;
|
|
8564
|
-
if (typeof rec.turn === "number") lastTurn = Math.max(lastTurn, rec.turn);
|
|
8565
|
-
} catch {
|
|
8566
|
-
}
|
|
8567
|
-
}
|
|
8568
|
-
console.log(`transcript: ${opts.transcript}`);
|
|
8569
|
-
console.log(`assistant turns: ${assistantTurns}`);
|
|
8570
|
-
console.log(`tool invocations: ${toolCalls}`);
|
|
8571
|
-
console.log(`last turn index: ${lastTurn}`);
|
|
8572
|
-
}
|
|
8573
|
-
|
|
8574
9264
|
// src/cli/commands/update.ts
|
|
8575
9265
|
import { spawn as spawn4 } from "child_process";
|
|
8576
9266
|
function planUpdate(input) {
|
|
@@ -8796,7 +9486,9 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
8796
9486
|
mcpPrefix: opts.mcpPrefix
|
|
8797
9487
|
});
|
|
8798
9488
|
});
|
|
8799
|
-
program.command("stats
|
|
9489
|
+
program.command("stats [transcript]").description(
|
|
9490
|
+
"Show usage dashboard (today / week / month / all-time \xB7 turns \xB7 cache hit \xB7 cost \xB7 savings vs Claude). Pass a transcript path to fall back to the per-file summary (assistant turns + tool calls)."
|
|
9491
|
+
).action((transcript) => {
|
|
8800
9492
|
statsCommand({ transcript });
|
|
8801
9493
|
});
|
|
8802
9494
|
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) => {
|