reasonix 0.4.24 → 0.4.27
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/{chunk-K6MR4SWS.js → chunk-ANMDY236.js} +137 -10
- package/dist/cli/chunk-ANMDY236.js.map +1 -0
- package/dist/cli/index.js +619 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-VDN5U3YE.js → prompt-75XLIUTO.js} +2 -2
- package/dist/index.d.ts +146 -2
- package/dist/index.js +541 -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-75XLIUTO.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-ANMDY236.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.",
|
|
@@ -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;
|
|
@@ -4709,27 +5039,36 @@ function formatLogSize(path = defaultUsageLogPath()) {
|
|
|
4709
5039
|
}
|
|
4710
5040
|
|
|
4711
5041
|
// src/cli/commands/chat.tsx
|
|
4712
|
-
import { existsSync as existsSync8, statSync as
|
|
5042
|
+
import { existsSync as existsSync8, statSync as statSync5 } from "fs";
|
|
4713
5043
|
import { render } from "ink";
|
|
4714
5044
|
import React15, { useState as useState7 } from "react";
|
|
4715
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
|
+
|
|
4716
5050
|
// src/tools/skills.ts
|
|
4717
5051
|
function registerSkillTools(registry, opts = {}) {
|
|
4718
|
-
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;
|
|
4719
5058
|
registry.register({
|
|
4720
5059
|
name: "run_skill",
|
|
4721
|
-
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.",
|
|
4722
5061
|
readOnly: true,
|
|
4723
5062
|
parameters: {
|
|
4724
5063
|
type: "object",
|
|
4725
5064
|
properties: {
|
|
4726
5065
|
name: {
|
|
4727
5066
|
type: "string",
|
|
4728
|
-
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."
|
|
4729
5068
|
},
|
|
4730
5069
|
arguments: {
|
|
4731
5070
|
type: "string",
|
|
4732
|
-
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."
|
|
4733
5072
|
}
|
|
4734
5073
|
},
|
|
4735
5074
|
required: ["name"]
|
|
@@ -4748,6 +5087,19 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
4748
5087
|
});
|
|
4749
5088
|
}
|
|
4750
5089
|
const rawArgs = typeof args.arguments === "string" ? args.arguments.trim() : "";
|
|
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
|
+
}
|
|
4751
5103
|
const header2 = [
|
|
4752
5104
|
`# Skill: ${skill.name}`,
|
|
4753
5105
|
skill.description ? `> ${skill.description}` : "",
|
|
@@ -4764,10 +5116,6 @@ ${skill.body}${argsBlock}`;
|
|
|
4764
5116
|
return registry;
|
|
4765
5117
|
}
|
|
4766
5118
|
|
|
4767
|
-
// src/cli/ui/App.tsx
|
|
4768
|
-
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
4769
|
-
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
4770
|
-
|
|
4771
5119
|
// src/cli/ui/EventLog.tsx
|
|
4772
5120
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
4773
5121
|
import React4 from "react";
|
|
@@ -4789,6 +5137,8 @@ function PlanStateBlock({ planState }) {
|
|
|
4789
5137
|
}
|
|
4790
5138
|
|
|
4791
5139
|
// src/cli/ui/markdown.tsx
|
|
5140
|
+
import { readFileSync as readFileSync9, statSync as statSync4 } from "fs";
|
|
5141
|
+
import { isAbsolute as isAbsolute3, join as join7 } from "path";
|
|
4792
5142
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
4793
5143
|
import React2 from "react";
|
|
4794
5144
|
var SUPERSCRIPT = {
|
|
@@ -4839,8 +5189,75 @@ function stripMath(s) {
|
|
|
4839
5189
|
(_m, n, k) => `C(${n.trim()},${k.trim()})`
|
|
4840
5190
|
).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, " ");
|
|
4841
5191
|
}
|
|
4842
|
-
|
|
4843
|
-
|
|
5192
|
+
function isExternalUrl(url) {
|
|
5193
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith("mailto:") || url.startsWith("//");
|
|
5194
|
+
}
|
|
5195
|
+
function parseCitationUrl(url) {
|
|
5196
|
+
const trimmed = url.trim();
|
|
5197
|
+
if (!trimmed) return null;
|
|
5198
|
+
let m = trimmed.match(/^(.+?)#L(\d+)(?:-L?(\d+))?$/);
|
|
5199
|
+
if (m) {
|
|
5200
|
+
return {
|
|
5201
|
+
path: m[1] ?? "",
|
|
5202
|
+
startLine: Number(m[2]),
|
|
5203
|
+
endLine: m[3] ? Number(m[3]) : void 0
|
|
5204
|
+
};
|
|
5205
|
+
}
|
|
5206
|
+
m = trimmed.match(/^(.+?):(\d+)(?:-(\d+))?$/);
|
|
5207
|
+
if (m) {
|
|
5208
|
+
return {
|
|
5209
|
+
path: m[1] ?? "",
|
|
5210
|
+
startLine: Number(m[2]),
|
|
5211
|
+
endLine: m[3] ? Number(m[3]) : void 0
|
|
5212
|
+
};
|
|
5213
|
+
}
|
|
5214
|
+
return { path: trimmed };
|
|
5215
|
+
}
|
|
5216
|
+
function validateCitation(url, projectRoot) {
|
|
5217
|
+
const parts = parseCitationUrl(url);
|
|
5218
|
+
if (!parts || !parts.path) return { ok: false, reason: "empty path" };
|
|
5219
|
+
const fullPath = isAbsolute3(parts.path) ? parts.path : join7(projectRoot, parts.path);
|
|
5220
|
+
let stat;
|
|
5221
|
+
try {
|
|
5222
|
+
stat = statSync4(fullPath);
|
|
5223
|
+
} catch {
|
|
5224
|
+
return { ok: false, reason: "file not found" };
|
|
5225
|
+
}
|
|
5226
|
+
if (!stat.isFile()) return { ok: false, reason: "not a file" };
|
|
5227
|
+
if (parts.startLine === void 0) return { ok: true };
|
|
5228
|
+
let lineCount;
|
|
5229
|
+
try {
|
|
5230
|
+
lineCount = readFileSync9(fullPath, "utf8").split("\n").length;
|
|
5231
|
+
} catch {
|
|
5232
|
+
return { ok: false, reason: "unreadable" };
|
|
5233
|
+
}
|
|
5234
|
+
if (parts.startLine < 1 || parts.startLine > lineCount) {
|
|
5235
|
+
return { ok: false, reason: `line ${parts.startLine} > ${lineCount}` };
|
|
5236
|
+
}
|
|
5237
|
+
if (parts.endLine !== void 0) {
|
|
5238
|
+
if (parts.endLine < parts.startLine || parts.endLine > lineCount) {
|
|
5239
|
+
return { ok: false, reason: `range end ${parts.endLine} invalid` };
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
return { ok: true };
|
|
5243
|
+
}
|
|
5244
|
+
function collectCitations(text, projectRoot) {
|
|
5245
|
+
const map = /* @__PURE__ */ new Map();
|
|
5246
|
+
const re = /\[([^\]\n]+)\]\(([^)\n]+)\)/g;
|
|
5247
|
+
for (const m of text.matchAll(re)) {
|
|
5248
|
+
const url = m[2] ?? "";
|
|
5249
|
+
if (!url || isExternalUrl(url)) continue;
|
|
5250
|
+
if (map.has(url)) continue;
|
|
5251
|
+
map.set(url, validateCitation(url, projectRoot));
|
|
5252
|
+
}
|
|
5253
|
+
return map;
|
|
5254
|
+
}
|
|
5255
|
+
var INLINE_RE = /(\[([^\]\n]+)\]\(([^)\n]+)\)|\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
|
|
5256
|
+
function InlineMd({
|
|
5257
|
+
text,
|
|
5258
|
+
padTo,
|
|
5259
|
+
citations
|
|
5260
|
+
}) {
|
|
4844
5261
|
const parts = [];
|
|
4845
5262
|
let last = 0;
|
|
4846
5263
|
let idx = 0;
|
|
@@ -4849,22 +5266,41 @@ function InlineMd({ text }) {
|
|
|
4849
5266
|
if (start > last) {
|
|
4850
5267
|
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `t${idx++}` }, text.slice(last, start)));
|
|
4851
5268
|
}
|
|
4852
|
-
if (m[2] !== void 0) {
|
|
5269
|
+
if (m[2] !== void 0 && m[3] !== void 0) {
|
|
5270
|
+
const linkText = m[2];
|
|
5271
|
+
const url = m[3];
|
|
5272
|
+
if (isExternalUrl(url)) {
|
|
5273
|
+
parts.push(
|
|
5274
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `l${idx++}`, color: "blue", underline: true }, linkText)
|
|
5275
|
+
);
|
|
5276
|
+
} else {
|
|
5277
|
+
const status = citations?.get(url);
|
|
5278
|
+
if (status && !status.ok) {
|
|
5279
|
+
parts.push(
|
|
5280
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `l${idx++}`, color: "red", strikethrough: true }, `${linkText} \u274C`)
|
|
5281
|
+
);
|
|
5282
|
+
} else {
|
|
5283
|
+
parts.push(
|
|
5284
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `l${idx++}`, color: "cyan", underline: true }, linkText)
|
|
5285
|
+
);
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
} else if (m[4] !== void 0) {
|
|
4853
5289
|
parts.push(
|
|
4854
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[
|
|
5290
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[4])
|
|
4855
5291
|
);
|
|
4856
|
-
} else if (m[
|
|
4857
|
-
const stripped = m[
|
|
5292
|
+
} else if (m[5] !== void 0) {
|
|
5293
|
+
const stripped = m[5].replace(/^(\w+)\s+/, "");
|
|
4858
5294
|
parts.push(
|
|
4859
5295
|
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, stripped)
|
|
4860
5296
|
);
|
|
4861
|
-
} else if (m[
|
|
5297
|
+
} else if (m[6] !== void 0) {
|
|
4862
5298
|
parts.push(
|
|
4863
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[
|
|
5299
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[6])
|
|
4864
5300
|
);
|
|
4865
|
-
} else if (m[
|
|
5301
|
+
} else if (m[7] !== void 0) {
|
|
4866
5302
|
parts.push(
|
|
4867
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[
|
|
5303
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[7])
|
|
4868
5304
|
);
|
|
4869
5305
|
}
|
|
4870
5306
|
last = start + m[0].length;
|
|
@@ -4872,8 +5308,20 @@ function InlineMd({ text }) {
|
|
|
4872
5308
|
if (last < text.length) {
|
|
4873
5309
|
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `t${idx++}` }, text.slice(last)));
|
|
4874
5310
|
}
|
|
5311
|
+
if (padTo !== void 0) {
|
|
5312
|
+
const seen = visibleWidth(text);
|
|
5313
|
+
if (seen < padTo) {
|
|
5314
|
+
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `pad${idx++}` }, " ".repeat(padTo - seen)));
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
4875
5317
|
return /* @__PURE__ */ React2.createElement(Text2, null, parts);
|
|
4876
5318
|
}
|
|
5319
|
+
function stripInlineMarkup(s) {
|
|
5320
|
+
return s.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, "$1").replace(/\*\*([^*\n]+?)\*\*/g, "$1").replace(/```([^\n]+?)```/g, (_m, c) => c.replace(/^(\w+)\s+/, "")).replace(/`([^`\n]+?)`/g, "$1").replace(/(?<![*\w])\*([^*\n]+?)\*(?!\w)/g, "$1");
|
|
5321
|
+
}
|
|
5322
|
+
function visibleWidth(s) {
|
|
5323
|
+
return displayWidth(stripInlineMarkup(s));
|
|
5324
|
+
}
|
|
4877
5325
|
function parseBlocks(raw) {
|
|
4878
5326
|
const lines = raw.split(/\r?\n/);
|
|
4879
5327
|
const out = [];
|
|
@@ -4980,17 +5428,47 @@ function parseBlocks(raw) {
|
|
|
4980
5428
|
out.push({ kind: "heading", level: hm[1].length, text: hm[2].trim() });
|
|
4981
5429
|
continue;
|
|
4982
5430
|
}
|
|
4983
|
-
if (line
|
|
5431
|
+
if (/^\s*┌─+┐\s*$/.test(line)) {
|
|
5432
|
+
let j = i + 1;
|
|
5433
|
+
const bodyLines = [];
|
|
5434
|
+
while (j < lines.length && !/^\s*└─+┘\s*$/.test(lines[j])) {
|
|
5435
|
+
const inner = lines[j];
|
|
5436
|
+
const m = inner.match(/^\s*│\s?(.*?)\s?│\s*$/);
|
|
5437
|
+
bodyLines.push(m ? m[1] ?? "" : inner);
|
|
5438
|
+
j++;
|
|
5439
|
+
}
|
|
5440
|
+
if (j < lines.length) {
|
|
5441
|
+
flushPara();
|
|
5442
|
+
flushList();
|
|
5443
|
+
out.push({ kind: "code", lang: "", text: bodyLines.join("\n") });
|
|
5444
|
+
i = j;
|
|
5445
|
+
continue;
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
if (line.includes("|") || line.includes("\u2502")) {
|
|
4984
5449
|
const next = (lines[i + 1] ?? "").trim();
|
|
4985
|
-
|
|
5450
|
+
const isGfmSep = /^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(next);
|
|
5451
|
+
const isBoxSep = /^[│─┼┬┴┌┐└┘├┤\s]+$/.test(next) && /─{2,}/.test(next);
|
|
5452
|
+
if (isGfmSep || isBoxSep) {
|
|
4986
5453
|
flushPara();
|
|
4987
5454
|
flushList();
|
|
4988
5455
|
const header2 = splitTableRow(line);
|
|
5456
|
+
const colCount = header2.length;
|
|
4989
5457
|
const rows = [];
|
|
4990
5458
|
let j = i + 2;
|
|
4991
5459
|
while (j < lines.length) {
|
|
4992
5460
|
const r = lines[j].replace(/\s+$/g, "");
|
|
4993
|
-
if (r.trim() === ""
|
|
5461
|
+
if (r.trim() === "") break;
|
|
5462
|
+
if (!r.includes("|") && !r.includes("\u2502")) {
|
|
5463
|
+
const prev = rows[rows.length - 1];
|
|
5464
|
+
if (prev && prev.length === colCount) {
|
|
5465
|
+
const lastIdx = prev.length - 1;
|
|
5466
|
+
prev[lastIdx] = `${prev[lastIdx] ?? ""} ${r.trim()}`;
|
|
5467
|
+
j++;
|
|
5468
|
+
continue;
|
|
5469
|
+
}
|
|
5470
|
+
break;
|
|
5471
|
+
}
|
|
4994
5472
|
rows.push(splitTableRow(r));
|
|
4995
5473
|
j++;
|
|
4996
5474
|
}
|
|
@@ -5029,52 +5507,47 @@ function parseBlocks(raw) {
|
|
|
5029
5507
|
flushList();
|
|
5030
5508
|
return out;
|
|
5031
5509
|
}
|
|
5032
|
-
function BlockView({ block }) {
|
|
5510
|
+
function BlockView({ block, citations }) {
|
|
5033
5511
|
switch (block.kind) {
|
|
5034
5512
|
case "heading":
|
|
5035
|
-
return /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, /* @__PURE__ */ React2.createElement(InlineMd, { text: block.text }));
|
|
5513
|
+
return /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, /* @__PURE__ */ React2.createElement(InlineMd, { text: block.text, citations }));
|
|
5036
5514
|
case "paragraph":
|
|
5037
|
-
return /* @__PURE__ */ React2.createElement(InlineMd, { text: block.text });
|
|
5515
|
+
return /* @__PURE__ */ React2.createElement(InlineMd, { text: block.text, citations });
|
|
5038
5516
|
case "bullet":
|
|
5039
|
-
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React2.createElement(Box2, { key: `${i}-${item.slice(0, 24)}` }, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, block.ordered ? ` ${block.start + i}. ` : " \u2022 "), /* @__PURE__ */ React2.createElement(InlineMd, { text: item }))));
|
|
5517
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React2.createElement(Box2, { key: `${i}-${item.slice(0, 24)}` }, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, block.ordered ? ` ${block.start + i}. ` : " \u2022 "), /* @__PURE__ */ React2.createElement(InlineMd, { text: item, citations }))));
|
|
5040
5518
|
case "code":
|
|
5041
5519
|
return /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, block.text));
|
|
5042
5520
|
case "edit-block":
|
|
5043
5521
|
return /* @__PURE__ */ React2.createElement(EditBlockRow, { block });
|
|
5044
5522
|
case "table":
|
|
5045
|
-
return /* @__PURE__ */ React2.createElement(TableBlockRow, { block });
|
|
5523
|
+
return /* @__PURE__ */ React2.createElement(TableBlockRow, { block, citations });
|
|
5046
5524
|
case "hr":
|
|
5047
5525
|
return /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5048
5526
|
}
|
|
5049
5527
|
}
|
|
5050
5528
|
function splitTableRow(line) {
|
|
5051
5529
|
const SENTINEL = "\0";
|
|
5052
|
-
const masked = line.replace(/\\\|/g, SENTINEL);
|
|
5530
|
+
const masked = line.replace(/\\\|/g, SENTINEL).replace(/│/g, "|");
|
|
5053
5531
|
const trimmed = masked.trim().replace(/^\||\|$/g, "");
|
|
5054
5532
|
return trimmed.split("|").map((c) => c.trim().replace(new RegExp(SENTINEL, "g"), "|"));
|
|
5055
5533
|
}
|
|
5056
|
-
function TableBlockRow({ block }) {
|
|
5534
|
+
function TableBlockRow({ block, citations }) {
|
|
5057
5535
|
const colCount = Math.max(block.header.length, ...block.rows.map((r) => r.length));
|
|
5058
5536
|
const widths = [];
|
|
5059
5537
|
for (let c = 0; c < colCount; c++) {
|
|
5060
|
-
const cellLengths = [
|
|
5061
|
-
for (const r of block.rows) cellLengths.push(
|
|
5538
|
+
const cellLengths = [visibleWidth(block.header[c] ?? "")];
|
|
5539
|
+
for (const r of block.rows) cellLengths.push(visibleWidth(r[c] ?? ""));
|
|
5062
5540
|
widths.push(Math.min(40, Math.max(3, ...cellLengths)));
|
|
5063
5541
|
}
|
|
5064
|
-
const pad3 = (s, w) => {
|
|
5065
|
-
const dw = displayWidth(s);
|
|
5066
|
-
if (dw >= w) return s;
|
|
5067
|
-
return s + " ".repeat(w - dw);
|
|
5068
|
-
};
|
|
5069
5542
|
const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u253C\u2500");
|
|
5070
5543
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, null, block.header.map((cell, ci) => (
|
|
5071
5544
|
// biome-ignore lint/suspicious/noArrayIndexKey: table columns never reorder — derived from a static header array
|
|
5072
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `h-${ci}`, bold: true, color: "cyan" },
|
|
5545
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `h-${ci}`, bold: true, color: "cyan" }, /* @__PURE__ */ React2.createElement(InlineMd, { text: cell, padTo: widths[ci] ?? 3, citations }), ci < colCount - 1 ? " \u2502 " : "")
|
|
5073
5546
|
))), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, separator), block.rows.map((row2, ri) => (
|
|
5074
5547
|
// biome-ignore lint/suspicious/noArrayIndexKey: table rows render in source order and don't reorder
|
|
5075
5548
|
/* @__PURE__ */ React2.createElement(Box2, { key: `r-${ri}` }, Array.from({ length: colCount }).map((_, ci) => (
|
|
5076
5549
|
// biome-ignore lint/suspicious/noArrayIndexKey: same — column axis is fixed by the table shape
|
|
5077
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `c-${ri}-${ci}` },
|
|
5550
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c-${ri}-${ci}` }, /* @__PURE__ */ React2.createElement(InlineMd, { text: row2[ci] ?? "", padTo: widths[ci] ?? 3, citations }), ci < colCount - 1 ? " \u2502 " : "")
|
|
5078
5551
|
)))
|
|
5079
5552
|
)));
|
|
5080
5553
|
}
|
|
@@ -5096,10 +5569,22 @@ function EditBlockRow({ block }) {
|
|
|
5096
5569
|
const replaceLines = block.replace.split("\n");
|
|
5097
5570
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, block.filename), isNewFile ? /* @__PURE__ */ React2.createElement(Text2, { color: "green", bold: true }, " (new file)") : null), isNewFile ? null : /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, searchLines.map((line, i) => /* @__PURE__ */ React2.createElement(Text2, { key: `s-${i}-${line.length}`, color: "red" }, `- ${line}`))), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: isNewFile ? 1 : 0 }, replaceLines.map((line, i) => /* @__PURE__ */ React2.createElement(Text2, { key: `r-${i}-${line.length}`, color: "green" }, `+ ${line}`))));
|
|
5098
5571
|
}
|
|
5099
|
-
function Markdown({ text }) {
|
|
5572
|
+
function Markdown({ text, projectRoot }) {
|
|
5100
5573
|
const cleaned = stripMath(text);
|
|
5574
|
+
const root = projectRoot ?? process.cwd();
|
|
5575
|
+
const citations = React2.useMemo(() => collectCitations(cleaned, root), [cleaned, root]);
|
|
5101
5576
|
const blocks = React2.useMemo(() => parseBlocks(cleaned), [cleaned]);
|
|
5102
|
-
|
|
5577
|
+
const broken = [];
|
|
5578
|
+
for (const [url, status] of citations) {
|
|
5579
|
+
if (!status.ok) broken.push({ url, reason: status.reason });
|
|
5580
|
+
}
|
|
5581
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 1 }, blocks.map((b, i) => /* @__PURE__ */ React2.createElement(BlockView, { key: `${i}-${b.kind}`, block: b, citations })), broken.length > 0 ? /* @__PURE__ */ React2.createElement(BrokenCitationsBlock, { items: broken }) : null);
|
|
5582
|
+
}
|
|
5583
|
+
function BrokenCitationsBlock({ items }) {
|
|
5584
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "red", bold: true }, `\u26A0 ${items.length} broken citation${items.length > 1 ? "s" : ""} \u2014 the model referenced paths or lines that don't exist`), items.map((b, i) => (
|
|
5585
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: list is derived from a Map iteration order, stable per render
|
|
5586
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `bc-${i}`, color: "red" }, ` \u274C ${b.url} \u2192 ${b.reason}`)
|
|
5587
|
+
)));
|
|
5103
5588
|
}
|
|
5104
5589
|
|
|
5105
5590
|
// src/cli/ui/ticker.tsx
|
|
@@ -5125,13 +5610,16 @@ function useElapsedSeconds() {
|
|
|
5125
5610
|
}
|
|
5126
5611
|
|
|
5127
5612
|
// src/cli/ui/EventLog.tsx
|
|
5128
|
-
var EventRow = React4.memo(function EventRow2({
|
|
5613
|
+
var EventRow = React4.memo(function EventRow2({
|
|
5614
|
+
event,
|
|
5615
|
+
projectRoot
|
|
5616
|
+
}) {
|
|
5129
5617
|
if (event.role === "user") {
|
|
5130
5618
|
return /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React4.createElement(Text3, null, event.text));
|
|
5131
5619
|
}
|
|
5132
5620
|
if (event.role === "assistant") {
|
|
5133
5621
|
if (event.streaming) return /* @__PURE__ */ React4.createElement(StreamingAssistant, { event });
|
|
5134
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "green" }, "assistant")), event.branch ? /* @__PURE__ */ React4.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React4.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React4.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React4.createElement(Markdown, { text: event.text }) : /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React4.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React4.createElement(Text3, { color: "magenta" }, event.repair) : null);
|
|
5622
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "green" }, "assistant")), event.branch ? /* @__PURE__ */ React4.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React4.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React4.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React4.createElement(Markdown, { text: event.text, projectRoot }) : /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React4.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React4.createElement(Text3, { color: "magenta" }, event.repair) : null);
|
|
5135
5623
|
}
|
|
5136
5624
|
if (event.role === "tool") {
|
|
5137
5625
|
const isError = event.text.startsWith("ERROR:");
|
|
@@ -5347,14 +5835,14 @@ function findNextEnabled(items, from, step) {
|
|
|
5347
5835
|
|
|
5348
5836
|
// src/cli/ui/PlanConfirm.tsx
|
|
5349
5837
|
var DEFAULT_MAX_RENDERED = 2400;
|
|
5350
|
-
function PlanConfirm({ plan, onChoose, maxRenderedChars }) {
|
|
5838
|
+
function PlanConfirm({ plan, onChoose, maxRenderedChars, projectRoot }) {
|
|
5351
5839
|
const cap = maxRenderedChars ?? DEFAULT_MAX_RENDERED;
|
|
5352
5840
|
const tooLong = plan.length > cap;
|
|
5353
5841
|
const visible = tooLong ? `${plan.slice(0, cap)}
|
|
5354
5842
|
|
|
5355
5843
|
\u2026 (${plan.length - cap} chars truncated \u2014 use /tool to view the full proposal)` : plan;
|
|
5356
5844
|
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
|
|
5357
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Markdown, { text: visible })), hasOpenQuestions ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(
|
|
5845
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Markdown, { text: visible, projectRoot })), hasOpenQuestions ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(
|
|
5358
5846
|
SingleSelect,
|
|
5359
5847
|
{
|
|
5360
5848
|
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
@@ -5721,7 +6209,7 @@ function formatTokens(n) {
|
|
|
5721
6209
|
import { spawnSync } from "child_process";
|
|
5722
6210
|
|
|
5723
6211
|
// src/cli/commands/stats.ts
|
|
5724
|
-
import { existsSync as existsSync7, readFileSync as
|
|
6212
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
5725
6213
|
function statsCommand(opts) {
|
|
5726
6214
|
if (opts.transcript) {
|
|
5727
6215
|
transcriptSummary(opts.transcript);
|
|
@@ -5734,7 +6222,7 @@ function transcriptSummary(path) {
|
|
|
5734
6222
|
console.error(`no such transcript: ${path}`);
|
|
5735
6223
|
process.exit(1);
|
|
5736
6224
|
}
|
|
5737
|
-
const lines =
|
|
6225
|
+
const lines = readFileSync10(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
5738
6226
|
let assistantTurns = 0;
|
|
5739
6227
|
let toolCalls = 0;
|
|
5740
6228
|
let lastTurn = 0;
|
|
@@ -6710,6 +7198,7 @@ function App({
|
|
|
6710
7198
|
const abortedThisTurn = useRef2(false);
|
|
6711
7199
|
const [ongoingTool, setOngoingTool] = useState5(null);
|
|
6712
7200
|
const [toolProgress, setToolProgress] = useState5(null);
|
|
7201
|
+
const [subagentActivity, setSubagentActivity] = useState5(null);
|
|
6713
7202
|
const [statusLine, setStatusLine] = useState5(null);
|
|
6714
7203
|
const [balance, setBalance] = useState5(null);
|
|
6715
7204
|
const [latestVersion, setLatestVersion] = useState5(null);
|
|
@@ -6766,9 +7255,30 @@ function App({
|
|
|
6766
7255
|
});
|
|
6767
7256
|
}, [slashMatches]);
|
|
6768
7257
|
const loopRef = useRef2(null);
|
|
7258
|
+
const subagentSinkRef = useRef2({ current: null });
|
|
6769
7259
|
const loop = useMemo(() => {
|
|
6770
7260
|
if (loopRef.current) return loopRef.current;
|
|
6771
7261
|
const client = new DeepSeekClient();
|
|
7262
|
+
if (tools && !tools.has("run_skill")) {
|
|
7263
|
+
registerSkillTools(tools, {
|
|
7264
|
+
projectRoot: codeMode?.rootDir,
|
|
7265
|
+
subagentRunner: async (skill, task) => {
|
|
7266
|
+
const result = await spawnSubagent({
|
|
7267
|
+
client,
|
|
7268
|
+
parentRegistry: tools,
|
|
7269
|
+
// Skill body is the subagent's persona/playbook; the user-
|
|
7270
|
+
// supplied task is what to actually do inside it.
|
|
7271
|
+
system: skill.body,
|
|
7272
|
+
task,
|
|
7273
|
+
// Per-skill model override (frontmatter `model: ...`),
|
|
7274
|
+
// else falls through to spawnSubagent's default.
|
|
7275
|
+
model: skill.model,
|
|
7276
|
+
sink: subagentSinkRef.current
|
|
7277
|
+
});
|
|
7278
|
+
return formatSubagentResult(result);
|
|
7279
|
+
}
|
|
7280
|
+
});
|
|
7281
|
+
}
|
|
6772
7282
|
const prefix = new ImmutablePrefix({
|
|
6773
7283
|
system,
|
|
6774
7284
|
toolSpecs: tools?.specs()
|
|
@@ -6786,7 +7296,7 @@ function App({
|
|
|
6786
7296
|
});
|
|
6787
7297
|
loopRef.current = l;
|
|
6788
7298
|
return l;
|
|
6789
|
-
}, [model, system, harvest2, branch, session, tools]);
|
|
7299
|
+
}, [model, system, harvest2, branch, session, tools, codeMode]);
|
|
6790
7300
|
useEffect2(() => {
|
|
6791
7301
|
loop.hooks = hookList;
|
|
6792
7302
|
}, [loop, hookList]);
|
|
@@ -6826,6 +7336,40 @@ function App({
|
|
|
6826
7336
|
if (progressSink.current) progressSink.current = null;
|
|
6827
7337
|
};
|
|
6828
7338
|
}, [progressSink]);
|
|
7339
|
+
useEffect2(() => {
|
|
7340
|
+
subagentSinkRef.current.current = (ev) => {
|
|
7341
|
+
if (ev.kind === "start") {
|
|
7342
|
+
setSubagentActivity({
|
|
7343
|
+
task: ev.task,
|
|
7344
|
+
iter: ev.iter ?? 0,
|
|
7345
|
+
elapsedMs: ev.elapsedMs ?? 0
|
|
7346
|
+
});
|
|
7347
|
+
return;
|
|
7348
|
+
}
|
|
7349
|
+
if (ev.kind === "progress") {
|
|
7350
|
+
setSubagentActivity({
|
|
7351
|
+
task: ev.task,
|
|
7352
|
+
iter: ev.iter ?? 0,
|
|
7353
|
+
elapsedMs: ev.elapsedMs ?? 0
|
|
7354
|
+
});
|
|
7355
|
+
return;
|
|
7356
|
+
}
|
|
7357
|
+
setSubagentActivity(null);
|
|
7358
|
+
const seconds = ((ev.elapsedMs ?? 0) / 1e3).toFixed(1);
|
|
7359
|
+
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)`;
|
|
7360
|
+
setHistorical((prev) => [
|
|
7361
|
+
...prev,
|
|
7362
|
+
{
|
|
7363
|
+
id: `subagent-end-${Date.now()}`,
|
|
7364
|
+
role: "info",
|
|
7365
|
+
text: summary2
|
|
7366
|
+
}
|
|
7367
|
+
]);
|
|
7368
|
+
};
|
|
7369
|
+
return () => {
|
|
7370
|
+
subagentSinkRef.current.current = null;
|
|
7371
|
+
};
|
|
7372
|
+
}, []);
|
|
6829
7373
|
const sessionBannerShown = useRef2(false);
|
|
6830
7374
|
useEffect2(() => {
|
|
6831
7375
|
if (sessionBannerShown.current) return;
|
|
@@ -7436,14 +7980,14 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
7436
7980
|
balance,
|
|
7437
7981
|
updateAvailable
|
|
7438
7982
|
}
|
|
7439
|
-
), /* @__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(
|
|
7983
|
+
), /* @__PURE__ */ React12.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React12.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React12.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && subagentActivity ? /* @__PURE__ */ React12.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !ongoingTool && statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React12.createElement(
|
|
7440
7984
|
PlanRefineInput,
|
|
7441
7985
|
{
|
|
7442
7986
|
mode: stagedInput.mode,
|
|
7443
7987
|
onSubmit: handleStagedInputSubmit,
|
|
7444
7988
|
onCancel: handleStagedInputCancel
|
|
7445
7989
|
}
|
|
7446
|
-
) : pendingPlan ? /* @__PURE__ */ React12.createElement(PlanConfirm, { plan: pendingPlan, onChoose: handlePlanConfirm }) : pendingShell ? /* @__PURE__ */ React12.createElement(
|
|
7990
|
+
) : pendingPlan ? /* @__PURE__ */ React12.createElement(PlanConfirm, { plan: pendingPlan, onChoose: handlePlanConfirm, projectRoot: hookCwd }) : pendingShell ? /* @__PURE__ */ React12.createElement(
|
|
7447
7991
|
ShellConfirm,
|
|
7448
7992
|
{
|
|
7449
7993
|
command: pendingShell,
|
|
@@ -7466,6 +8010,13 @@ function StatusRow({ text }) {
|
|
|
7466
8010
|
const elapsed = useElapsedSeconds();
|
|
7467
8011
|
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`));
|
|
7468
8012
|
}
|
|
8013
|
+
function SubagentRow({
|
|
8014
|
+
activity
|
|
8015
|
+
}) {
|
|
8016
|
+
const tick = useTick();
|
|
8017
|
+
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
8018
|
+
return /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, ` \u{1F9EC} subagent: ${activity.task}`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
|
|
8019
|
+
}
|
|
7469
8020
|
function OngoingToolRow({
|
|
7470
8021
|
tool,
|
|
7471
8022
|
progress
|
|
@@ -7784,14 +8335,13 @@ async function chatCommand(opts) {
|
|
|
7784
8335
|
if (!opts.seedTools) {
|
|
7785
8336
|
if (!tools) tools = new ToolRegistry();
|
|
7786
8337
|
registerMemoryTools(tools, {});
|
|
7787
|
-
registerSkillTools(tools);
|
|
7788
8338
|
}
|
|
7789
8339
|
let sessionPreview;
|
|
7790
8340
|
if (opts.session && !opts.forceResume && !opts.forceNew) {
|
|
7791
8341
|
const prior = loadSessionMessages(opts.session);
|
|
7792
8342
|
if (prior.length > 0) {
|
|
7793
8343
|
const p = sessionPath(opts.session);
|
|
7794
|
-
const mtime = existsSync8(p) ?
|
|
8344
|
+
const mtime = existsSync8(p) ? statSync5(p).mtime : /* @__PURE__ */ new Date();
|
|
7795
8345
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
7796
8346
|
}
|
|
7797
8347
|
} else if (opts.session && opts.forceNew) {
|
|
@@ -7824,7 +8374,7 @@ async function chatCommand(opts) {
|
|
|
7824
8374
|
// src/cli/commands/code.tsx
|
|
7825
8375
|
import { basename, resolve as resolve5 } from "path";
|
|
7826
8376
|
async function codeCommand(opts = {}) {
|
|
7827
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
8377
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-75XLIUTO.js");
|
|
7828
8378
|
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
7829
8379
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
7830
8380
|
const tools = new ToolRegistry();
|
|
@@ -7840,7 +8390,6 @@ async function codeCommand(opts = {}) {
|
|
|
7840
8390
|
});
|
|
7841
8391
|
registerPlanTool(tools);
|
|
7842
8392
|
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
7843
|
-
registerSkillTools(tools, { projectRoot: rootDir });
|
|
7844
8393
|
process.stderr.write(
|
|
7845
8394
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
7846
8395
|
`
|
|
@@ -8933,7 +9482,19 @@ function resolveSession(flag, configSession) {
|
|
|
8933
9482
|
}
|
|
8934
9483
|
|
|
8935
9484
|
// src/cli/index.ts
|
|
8936
|
-
var DEFAULT_SYSTEM =
|
|
9485
|
+
var DEFAULT_SYSTEM = `You are Reasonix, a helpful DeepSeek-powered assistant. Be concise and accurate. Use tools when available.
|
|
9486
|
+
|
|
9487
|
+
# Cite or shut up \u2014 non-negotiable
|
|
9488
|
+
|
|
9489
|
+
Every factual claim about a codebase must be backed by evidence. Reasonix VALIDATES your citations \u2014 broken paths render in **red strikethrough with \u274C** in front of the user.
|
|
9490
|
+
|
|
9491
|
+
**Positive claims** \u2014 append a markdown link:
|
|
9492
|
+
- \u2705 \`The MCP client supports listResources [listResources](src/mcp/client.ts:142).\`
|
|
9493
|
+
- \u274C \`The MCP client supports listResources.\` \u2190 unverifiable, do not write.
|
|
9494
|
+
|
|
9495
|
+
**Negative claims** ("X is missing", "Y isn't implemented", "lacks Z") are the #1 hallucination shape. STOP before writing them. If you have a search tool, call it first; if the search returns nothing, cite the search itself as evidence (\`No matches for "foo" in src/\`). If you have no tool, qualify hard: "I haven't verified \u2014 this is a guess."
|
|
9496
|
+
|
|
9497
|
+
Asserting absence without checking is how evaluative answers go wrong. Treat the urge to write "missing" as a red flag in your own reasoning.`;
|
|
8937
9498
|
var program = new Command();
|
|
8938
9499
|
program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION);
|
|
8939
9500
|
program.action(async () => {
|