reasonix 0.4.16 → 0.4.19
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 +64 -0
- package/dist/cli/chunk-HNEWBEWZ.js +152 -0
- package/dist/cli/chunk-HNEWBEWZ.js.map +1 -0
- package/dist/cli/index.js +701 -238
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-MMANQ36Z.js → prompt-JNNNJLYF.js} +2 -2
- package/dist/index.d.ts +217 -4
- package/dist/index.js +276 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-2P2MZLCE.js +0 -81
- package/dist/cli/chunk-2P2MZLCE.js.map +0 -1
- /package/dist/cli/{prompt-MMANQ36Z.js.map → prompt-JNNNJLYF.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
PROJECT_MEMORY_FILE,
|
|
4
|
+
applyProjectMemory,
|
|
5
|
+
memoryEnabled,
|
|
6
|
+
readProjectMemory
|
|
7
|
+
} from "./chunk-HNEWBEWZ.js";
|
|
3
8
|
|
|
4
9
|
// src/cli/index.ts
|
|
5
10
|
import { Command } from "commander";
|
|
@@ -570,9 +575,25 @@ function setByPath(target, path, value) {
|
|
|
570
575
|
var ToolRegistry = class {
|
|
571
576
|
_tools = /* @__PURE__ */ new Map();
|
|
572
577
|
_autoFlatten;
|
|
578
|
+
/**
|
|
579
|
+
* When true, `dispatch` refuses any tool whose `readOnly` flag isn't
|
|
580
|
+
* set (and whose `readOnlyCheck` doesn't pass on the specific args).
|
|
581
|
+
* Drives `reasonix code`'s Plan Mode — the model can still explore
|
|
582
|
+
* via read tools but its writes and non-allowlisted shell calls are
|
|
583
|
+
* bounced until the user approves a submitted plan.
|
|
584
|
+
*/
|
|
585
|
+
_planMode = false;
|
|
573
586
|
constructor(opts = {}) {
|
|
574
587
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
575
588
|
}
|
|
589
|
+
/** Enable / disable plan-mode enforcement at dispatch. */
|
|
590
|
+
setPlanMode(on) {
|
|
591
|
+
this._planMode = Boolean(on);
|
|
592
|
+
}
|
|
593
|
+
/** True when the registry is currently refusing non-readonly calls. */
|
|
594
|
+
get planMode() {
|
|
595
|
+
return this._planMode;
|
|
596
|
+
}
|
|
576
597
|
register(def) {
|
|
577
598
|
if (!def.name) throw new Error("tool requires a name");
|
|
578
599
|
const internal = { ...def };
|
|
@@ -624,16 +645,38 @@ var ToolRegistry = class {
|
|
|
624
645
|
if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
|
|
625
646
|
args = nestArguments(args);
|
|
626
647
|
}
|
|
648
|
+
if (this._planMode && !isReadOnlyCall(tool, args)) {
|
|
649
|
+
return JSON.stringify({
|
|
650
|
+
error: `${name}: unavailable in plan mode \u2014 this is a read-only exploration phase. Use read_file / list_directory / search_files / directory_tree / web_search / allowlisted shell commands to investigate. Call submit_plan with your proposed plan when you're ready for the user's review.`
|
|
651
|
+
});
|
|
652
|
+
}
|
|
627
653
|
try {
|
|
628
654
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
629
655
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
630
656
|
} catch (err) {
|
|
657
|
+
const e = err;
|
|
658
|
+
if (typeof e.toToolResult === "function") {
|
|
659
|
+
try {
|
|
660
|
+
return JSON.stringify(e.toToolResult());
|
|
661
|
+
} catch {
|
|
662
|
+
}
|
|
663
|
+
}
|
|
631
664
|
return JSON.stringify({
|
|
632
|
-
error: `${
|
|
665
|
+
error: `${e.name}: ${e.message}`
|
|
633
666
|
});
|
|
634
667
|
}
|
|
635
668
|
}
|
|
636
669
|
};
|
|
670
|
+
function isReadOnlyCall(tool, args) {
|
|
671
|
+
if (tool.readOnlyCheck) {
|
|
672
|
+
try {
|
|
673
|
+
return Boolean(tool.readOnlyCheck(args));
|
|
674
|
+
} catch {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return tool.readOnly === true;
|
|
679
|
+
}
|
|
637
680
|
function hasDotKey(obj) {
|
|
638
681
|
for (const k of Object.keys(obj)) {
|
|
639
682
|
if (k.includes(".")) return true;
|
|
@@ -1020,6 +1063,16 @@ var ToolCallRepair = class {
|
|
|
1020
1063
|
this.opts = opts;
|
|
1021
1064
|
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3);
|
|
1022
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Drop the StormBreaker's sliding window of recent (name, args)
|
|
1068
|
+
* signatures. Called at the start of every user turn — a fresh user
|
|
1069
|
+
* message is a new intent, so carrying old repetition state into it
|
|
1070
|
+
* would turn a valid "try again with different input" flow into a
|
|
1071
|
+
* false-positive block.
|
|
1072
|
+
*/
|
|
1073
|
+
resetStorm() {
|
|
1074
|
+
this.storm.reset();
|
|
1075
|
+
}
|
|
1023
1076
|
process(declaredCalls, reasoningContent, content = null) {
|
|
1024
1077
|
const report = {
|
|
1025
1078
|
scavenged: 0,
|
|
@@ -1472,6 +1525,7 @@ var CacheFirstLoop = class {
|
|
|
1472
1525
|
async *step(userInput) {
|
|
1473
1526
|
this._turn++;
|
|
1474
1527
|
this.scratch.reset();
|
|
1528
|
+
this.repair.resetStorm();
|
|
1475
1529
|
this._turnAbort = new AbortController();
|
|
1476
1530
|
const signal = this._turnAbort.signal;
|
|
1477
1531
|
let pendingUser = userInput;
|
|
@@ -1695,6 +1749,16 @@ var CacheFirstLoop = class {
|
|
|
1695
1749
|
repair: report,
|
|
1696
1750
|
branch: branchSummary
|
|
1697
1751
|
};
|
|
1752
|
+
if (report.stormsBroken > 0) {
|
|
1753
|
+
const noteTail = report.notes.length ? ` \u2014 ${report.notes[report.notes.length - 1]}` : "";
|
|
1754
|
+
const allSuppressed = repairedCalls.length === 0 && toolCalls.length > 0;
|
|
1755
|
+
const phrase = allSuppressed ? `stopped the model from calling the same tool with identical args repeatedly (all ${toolCalls.length} call(s) this turn were already in the recent-repeat window). Likely a stuck retry \u2014 reword your instruction, rule out the underlying blocker, or try /retry after fixing it` : `suppressed ${report.stormsBroken} repeat tool call(s) that had fired 3+ times with identical args in a sliding window`;
|
|
1756
|
+
yield {
|
|
1757
|
+
turn: this._turn,
|
|
1758
|
+
role: "warning",
|
|
1759
|
+
content: `${phrase}${noteTail}`
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1698
1762
|
if (repairedCalls.length === 0) {
|
|
1699
1763
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
1700
1764
|
return;
|
|
@@ -1944,6 +2008,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
1944
2008
|
registry.register({
|
|
1945
2009
|
name: "read_file",
|
|
1946
2010
|
description: "Read a file under the sandbox root. Returns the full contents (truncated with a notice if larger than the per-call cap). Paths may be relative to the root or absolute-under-root.",
|
|
2011
|
+
readOnly: true,
|
|
1947
2012
|
parameters: {
|
|
1948
2013
|
type: "object",
|
|
1949
2014
|
properties: {
|
|
@@ -1981,6 +2046,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
1981
2046
|
registry.register({
|
|
1982
2047
|
name: "list_directory",
|
|
1983
2048
|
description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
|
|
2049
|
+
readOnly: true,
|
|
1984
2050
|
parameters: {
|
|
1985
2051
|
type: "object",
|
|
1986
2052
|
properties: {
|
|
@@ -2000,6 +2066,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2000
2066
|
registry.register({
|
|
2001
2067
|
name: "directory_tree",
|
|
2002
2068
|
description: "Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Caps output so a huge tree doesn't drown the context.",
|
|
2069
|
+
readOnly: true,
|
|
2003
2070
|
parameters: {
|
|
2004
2071
|
type: "object",
|
|
2005
2072
|
properties: {
|
|
@@ -2046,6 +2113,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2046
2113
|
registry.register({
|
|
2047
2114
|
name: "search_files",
|
|
2048
2115
|
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line.",
|
|
2116
|
+
readOnly: true,
|
|
2049
2117
|
parameters: {
|
|
2050
2118
|
type: "object",
|
|
2051
2119
|
properties: {
|
|
@@ -2098,6 +2166,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2098
2166
|
registry.register({
|
|
2099
2167
|
name: "get_file_info",
|
|
2100
2168
|
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
2169
|
+
readOnly: true,
|
|
2101
2170
|
parameters: {
|
|
2102
2171
|
type: "object",
|
|
2103
2172
|
properties: {
|
|
@@ -2254,8 +2323,54 @@ function lineDiff(a, b) {
|
|
|
2254
2323
|
return out;
|
|
2255
2324
|
}
|
|
2256
2325
|
|
|
2326
|
+
// src/tools/plan.ts
|
|
2327
|
+
var PlanProposedError = class extends Error {
|
|
2328
|
+
plan;
|
|
2329
|
+
constructor(plan) {
|
|
2330
|
+
super(
|
|
2331
|
+
"PlanProposedError: plan submitted. STOP calling tools now \u2014 the TUI has shown the plan to the user. Wait for their next message; it will either approve (you'll then implement the plan), request a refinement (you should explore more and submit an updated plan), or cancel (drop the plan and ask what they want instead). Don't call any tools in the meantime."
|
|
2332
|
+
);
|
|
2333
|
+
this.name = "PlanProposedError";
|
|
2334
|
+
this.plan = plan;
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Structured tool-result shape. Consumed by the TUI to extract the
|
|
2338
|
+
* plan without regex-scraping the error message.
|
|
2339
|
+
*/
|
|
2340
|
+
toToolResult() {
|
|
2341
|
+
return { error: `${this.name}: ${this.message}`, plan: this.plan };
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
function registerPlanTool(registry, opts = {}) {
|
|
2345
|
+
registry.register({
|
|
2346
|
+
name: "submit_plan",
|
|
2347
|
+
description: "Submit a concrete plan to the user for review before executing. Use this for tasks that warrant a review gate \u2014 multi-file refactors, architecture changes, anything that would be expensive or confusing to undo. Skip it for small fixes (one-line typo, obvious bug with a clear fix) \u2014 just make the change. The user will either approve (you then implement it), ask for refinement, or cancel. If the user has already enabled /plan mode, writes are blocked at dispatch and you MUST use this. Write the plan as markdown with a one-line summary, a bulleted list of files to touch and what will change, and any risks or open questions.",
|
|
2348
|
+
readOnly: true,
|
|
2349
|
+
parameters: {
|
|
2350
|
+
type: "object",
|
|
2351
|
+
properties: {
|
|
2352
|
+
plan: {
|
|
2353
|
+
type: "string",
|
|
2354
|
+
description: "Markdown-formatted plan. Lead with a one-sentence summary. Then a file-by-file breakdown of what you'll change and why. Flag any risks or open questions at the end so the user can weigh in before you start."
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
required: ["plan"]
|
|
2358
|
+
},
|
|
2359
|
+
fn: async (args) => {
|
|
2360
|
+
const plan = (args?.plan ?? "").trim();
|
|
2361
|
+
if (!plan) {
|
|
2362
|
+
throw new Error("submit_plan: empty plan \u2014 write a markdown plan and try again.");
|
|
2363
|
+
}
|
|
2364
|
+
opts.onPlanSubmitted?.(plan);
|
|
2365
|
+
throw new PlanProposedError(plan);
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
return registry;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2257
2371
|
// src/tools/shell.ts
|
|
2258
2372
|
import { spawn } from "child_process";
|
|
2373
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
2259
2374
|
import * as pathMod2 from "path";
|
|
2260
2375
|
var DEFAULT_TIMEOUT_SEC = 60;
|
|
2261
2376
|
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
@@ -2373,10 +2488,12 @@ async function runCommand(cmd, opts) {
|
|
|
2373
2488
|
windowsHide: true,
|
|
2374
2489
|
env: process.env
|
|
2375
2490
|
};
|
|
2491
|
+
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
2492
|
+
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
2376
2493
|
return await new Promise((resolve6, reject) => {
|
|
2377
2494
|
let child;
|
|
2378
2495
|
try {
|
|
2379
|
-
child = spawn(
|
|
2496
|
+
child = spawn(bin, args, effectiveSpawnOpts);
|
|
2380
2497
|
} catch (err) {
|
|
2381
2498
|
reject(err);
|
|
2382
2499
|
return;
|
|
@@ -2410,6 +2527,59 @@ async function runCommand(cmd, opts) {
|
|
|
2410
2527
|
});
|
|
2411
2528
|
});
|
|
2412
2529
|
}
|
|
2530
|
+
function resolveExecutable(cmd, opts = {}) {
|
|
2531
|
+
const platform = opts.platform ?? process.platform;
|
|
2532
|
+
if (platform !== "win32") return cmd;
|
|
2533
|
+
if (!cmd) return cmd;
|
|
2534
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod2.isAbsolute(cmd)) return cmd;
|
|
2535
|
+
if (pathMod2.extname(cmd)) return cmd;
|
|
2536
|
+
const env = opts.env ?? process.env;
|
|
2537
|
+
const pathExt = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
2538
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod2.delimiter);
|
|
2539
|
+
const pathDirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
2540
|
+
const isFile = opts.isFile ?? defaultIsFile;
|
|
2541
|
+
for (const dir of pathDirs) {
|
|
2542
|
+
for (const ext of pathExt) {
|
|
2543
|
+
const full = pathMod2.join(dir, cmd + ext);
|
|
2544
|
+
if (isFile(full)) return full;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return cmd;
|
|
2548
|
+
}
|
|
2549
|
+
function defaultIsFile(full) {
|
|
2550
|
+
try {
|
|
2551
|
+
return existsSync2(full) && statSync2(full).isFile();
|
|
2552
|
+
} catch {
|
|
2553
|
+
return false;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
function prepareSpawn(argv, opts = {}) {
|
|
2557
|
+
const head = argv[0] ?? "";
|
|
2558
|
+
const tail = argv.slice(1);
|
|
2559
|
+
const platform = opts.platform ?? process.platform;
|
|
2560
|
+
const resolved = resolveExecutable(head, opts);
|
|
2561
|
+
if (platform !== "win32") {
|
|
2562
|
+
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2563
|
+
}
|
|
2564
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
2565
|
+
const cmdline = [resolved, ...tail].map(quoteForCmdExe).join(" ");
|
|
2566
|
+
return {
|
|
2567
|
+
bin: "cmd.exe",
|
|
2568
|
+
args: ["/d", "/s", "/c", cmdline],
|
|
2569
|
+
// windowsVerbatimArguments prevents Node from re-quoting the /c
|
|
2570
|
+
// payload — we've already composed an exact cmd.exe command
|
|
2571
|
+
// line. Without this Node wraps our already-quoted string in
|
|
2572
|
+
// another round of quotes and cmd.exe can't parse it.
|
|
2573
|
+
spawnOverrides: { windowsVerbatimArguments: true }
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2577
|
+
}
|
|
2578
|
+
function quoteForCmdExe(arg) {
|
|
2579
|
+
if (arg === "") return '""';
|
|
2580
|
+
if (!/[\s"&|<>^%(),;!]/.test(arg)) return arg;
|
|
2581
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
2582
|
+
}
|
|
2413
2583
|
var NeedsConfirmationError = class extends Error {
|
|
2414
2584
|
command;
|
|
2415
2585
|
constructor(command) {
|
|
@@ -2429,6 +2599,16 @@ function registerShellTools(registry, opts) {
|
|
|
2429
2599
|
registry.register({
|
|
2430
2600
|
name: "run_command",
|
|
2431
2601
|
description: "Run a shell command in the project root and return its combined stdout+stderr. Read-only and test commands (git status, ls, npm test, pytest, cargo test, grep, etc.) run immediately. Anything that could mutate state (npm install, git commit, rm, chmod) is refused and the user has to confirm in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
2602
|
+
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
2603
|
+
// cargo check, ls, grep …) so the model can actually investigate
|
|
2604
|
+
// during planning. Anything that would otherwise trigger a
|
|
2605
|
+
// confirmation prompt is treated as "not read-only" and bounced.
|
|
2606
|
+
readOnlyCheck: (args) => {
|
|
2607
|
+
if (allowAll) return true;
|
|
2608
|
+
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
2609
|
+
if (!cmd) return false;
|
|
2610
|
+
return isAllowed(cmd, extraAllowed);
|
|
2611
|
+
},
|
|
2432
2612
|
parameters: {
|
|
2433
2613
|
type: "object",
|
|
2434
2614
|
properties: {
|
|
@@ -2595,6 +2775,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
2595
2775
|
registry.register({
|
|
2596
2776
|
name: "web_search",
|
|
2597
2777
|
description: "Search the public web. Returns ranked results with title, url, and snippet. Use this when the question needs information more current than your training data, when you're unsure of a factual detail, or when the user asks about a specific webpage/library/release you haven't seen.",
|
|
2778
|
+
readOnly: true,
|
|
2598
2779
|
parameters: {
|
|
2599
2780
|
type: "object",
|
|
2600
2781
|
properties: {
|
|
@@ -2617,6 +2798,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
2617
2798
|
registry.register({
|
|
2618
2799
|
name: "web_fetch",
|
|
2619
2800
|
description: "Download a URL and return its visible text content (HTML pages get scripts/styles/nav stripped). Truncated at the tool-result cap. Use after web_search when a snippet isn't enough.",
|
|
2801
|
+
readOnly: true,
|
|
2620
2802
|
parameters: {
|
|
2621
2803
|
type: "object",
|
|
2622
2804
|
properties: {
|
|
@@ -3812,7 +3994,7 @@ async function trySection(load) {
|
|
|
3812
3994
|
}
|
|
3813
3995
|
|
|
3814
3996
|
// src/code/edit-blocks.ts
|
|
3815
|
-
import { existsSync as
|
|
3997
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3816
3998
|
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
3817
3999
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3818
4000
|
function parseEditBlocks(text) {
|
|
@@ -3841,7 +4023,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3841
4023
|
};
|
|
3842
4024
|
}
|
|
3843
4025
|
const searchEmpty = block.search.length === 0;
|
|
3844
|
-
const exists =
|
|
4026
|
+
const exists = existsSync3(absTarget);
|
|
3845
4027
|
try {
|
|
3846
4028
|
if (!exists) {
|
|
3847
4029
|
if (!searchEmpty) {
|
|
@@ -3889,7 +4071,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
3889
4071
|
if (seen.has(b.path)) continue;
|
|
3890
4072
|
seen.add(b.path);
|
|
3891
4073
|
const abs = resolve4(absRoot, b.path);
|
|
3892
|
-
if (!
|
|
4074
|
+
if (!existsSync3(abs)) {
|
|
3893
4075
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3894
4076
|
continue;
|
|
3895
4077
|
}
|
|
@@ -3914,7 +4096,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
3914
4096
|
}
|
|
3915
4097
|
try {
|
|
3916
4098
|
if (snap.prevContent === null) {
|
|
3917
|
-
if (
|
|
4099
|
+
if (existsSync3(abs)) unlinkSync2(abs);
|
|
3918
4100
|
return {
|
|
3919
4101
|
path: snap.path,
|
|
3920
4102
|
status: "applied",
|
|
@@ -3937,16 +4119,16 @@ function sep() {
|
|
|
3937
4119
|
}
|
|
3938
4120
|
|
|
3939
4121
|
// src/index.ts
|
|
3940
|
-
var VERSION = "0.4.
|
|
4122
|
+
var VERSION = "0.4.19";
|
|
3941
4123
|
|
|
3942
4124
|
// src/cli/commands/chat.tsx
|
|
3943
|
-
import { existsSync as
|
|
4125
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
3944
4126
|
import { render } from "ink";
|
|
3945
|
-
import
|
|
4127
|
+
import React15, { useState as useState7 } from "react";
|
|
3946
4128
|
|
|
3947
4129
|
// src/cli/ui/App.tsx
|
|
3948
|
-
import { Box as
|
|
3949
|
-
import
|
|
4130
|
+
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
4131
|
+
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
3950
4132
|
|
|
3951
4133
|
// src/cli/ui/EventLog.tsx
|
|
3952
4134
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -4420,9 +4602,177 @@ function truncate2(s, max) {
|
|
|
4420
4602
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
4421
4603
|
}
|
|
4422
4604
|
|
|
4423
|
-
// src/cli/ui/
|
|
4605
|
+
// src/cli/ui/PlanConfirm.tsx
|
|
4606
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
4607
|
+
import React6 from "react";
|
|
4608
|
+
|
|
4609
|
+
// src/cli/ui/Select.tsx
|
|
4424
4610
|
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
4425
|
-
import React5, {
|
|
4611
|
+
import React5, { useState as useState2 } from "react";
|
|
4612
|
+
function SingleSelect({
|
|
4613
|
+
items,
|
|
4614
|
+
initialValue,
|
|
4615
|
+
onSubmit,
|
|
4616
|
+
onCancel
|
|
4617
|
+
}) {
|
|
4618
|
+
const initialIndex = Math.max(
|
|
4619
|
+
0,
|
|
4620
|
+
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4621
|
+
);
|
|
4622
|
+
const [index, setIndex] = useState2(initialIndex === -1 ? 0 : initialIndex);
|
|
4623
|
+
useInput((_input, key) => {
|
|
4624
|
+
if (key.upArrow) {
|
|
4625
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4626
|
+
} else if (key.downArrow) {
|
|
4627
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4628
|
+
} else if (key.return) {
|
|
4629
|
+
const chosen = items[index];
|
|
4630
|
+
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4631
|
+
} else if (key.escape && onCancel) {
|
|
4632
|
+
onCancel();
|
|
4633
|
+
}
|
|
4634
|
+
});
|
|
4635
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React5.createElement(
|
|
4636
|
+
SelectRow,
|
|
4637
|
+
{
|
|
4638
|
+
key: item.value,
|
|
4639
|
+
item,
|
|
4640
|
+
active: i === index,
|
|
4641
|
+
marker: i === index ? "\u25B8" : " "
|
|
4642
|
+
}
|
|
4643
|
+
)));
|
|
4644
|
+
}
|
|
4645
|
+
function MultiSelect({
|
|
4646
|
+
items,
|
|
4647
|
+
initialSelected = [],
|
|
4648
|
+
onSubmit,
|
|
4649
|
+
onCancel,
|
|
4650
|
+
footer
|
|
4651
|
+
}) {
|
|
4652
|
+
const [index, setIndex] = useState2(() => {
|
|
4653
|
+
const first = items.findIndex((i) => !i.disabled);
|
|
4654
|
+
return first === -1 ? 0 : first;
|
|
4655
|
+
});
|
|
4656
|
+
const [selected, setSelected] = useState2(new Set(initialSelected));
|
|
4657
|
+
useInput((input, key) => {
|
|
4658
|
+
if (key.upArrow) {
|
|
4659
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4660
|
+
} else if (key.downArrow) {
|
|
4661
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4662
|
+
} else if (input === " ") {
|
|
4663
|
+
const item = items[index];
|
|
4664
|
+
if (!item || item.disabled) return;
|
|
4665
|
+
setSelected((prev) => {
|
|
4666
|
+
const next = new Set(prev);
|
|
4667
|
+
if (next.has(item.value)) next.delete(item.value);
|
|
4668
|
+
else next.add(item.value);
|
|
4669
|
+
return next;
|
|
4670
|
+
});
|
|
4671
|
+
} else if (key.return) {
|
|
4672
|
+
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4673
|
+
onSubmit(ordered);
|
|
4674
|
+
} else if (key.escape && onCancel) {
|
|
4675
|
+
onCancel();
|
|
4676
|
+
}
|
|
4677
|
+
});
|
|
4678
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, items.map((item, i) => {
|
|
4679
|
+
const checked = selected.has(item.value);
|
|
4680
|
+
const marker = checked ? "[x]" : "[ ]";
|
|
4681
|
+
return /* @__PURE__ */ React5.createElement(
|
|
4682
|
+
SelectRow,
|
|
4683
|
+
{
|
|
4684
|
+
key: item.value,
|
|
4685
|
+
item,
|
|
4686
|
+
active: i === index,
|
|
4687
|
+
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4688
|
+
}
|
|
4689
|
+
);
|
|
4690
|
+
}), footer ? /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, footer)) : null);
|
|
4691
|
+
}
|
|
4692
|
+
function SelectRow({
|
|
4693
|
+
item,
|
|
4694
|
+
active,
|
|
4695
|
+
marker
|
|
4696
|
+
}) {
|
|
4697
|
+
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4698
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text4, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React5.createElement(Box4, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, item.hint)) : null);
|
|
4699
|
+
}
|
|
4700
|
+
function findNextEnabled(items, from, step) {
|
|
4701
|
+
if (items.length === 0) return 0;
|
|
4702
|
+
let i = from;
|
|
4703
|
+
for (let tries = 0; tries < items.length; tries++) {
|
|
4704
|
+
i = (i + step + items.length) % items.length;
|
|
4705
|
+
if (!items[i]?.disabled) return i;
|
|
4706
|
+
}
|
|
4707
|
+
return from;
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// src/cli/ui/PlanConfirm.tsx
|
|
4711
|
+
var DEFAULT_MAX_RENDERED = 2400;
|
|
4712
|
+
function PlanConfirm({ plan, onChoose, maxRenderedChars }) {
|
|
4713
|
+
const cap = maxRenderedChars ?? DEFAULT_MAX_RENDERED;
|
|
4714
|
+
const tooLong = plan.length > cap;
|
|
4715
|
+
const visible = tooLong ? `${plan.slice(0, cap)}
|
|
4716
|
+
|
|
4717
|
+
\u2026 (${plan.length - cap} chars truncated \u2014 use /tool to view the full proposal)` : plan;
|
|
4718
|
+
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
|
|
4719
|
+
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(
|
|
4720
|
+
SingleSelect,
|
|
4721
|
+
{
|
|
4722
|
+
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
4723
|
+
items: [
|
|
4724
|
+
{
|
|
4725
|
+
value: "approve",
|
|
4726
|
+
label: "Approve and implement",
|
|
4727
|
+
hint: "Exit plan mode. The model starts executing. You'll get a text input to add any last instructions (or just press Enter to skip)."
|
|
4728
|
+
},
|
|
4729
|
+
{
|
|
4730
|
+
value: "refine",
|
|
4731
|
+
label: "Refine / answer questions",
|
|
4732
|
+
hint: "Stay in plan mode. Write answers, modifications, or critiques; the model revises and re-submits."
|
|
4733
|
+
},
|
|
4734
|
+
{
|
|
4735
|
+
value: "cancel",
|
|
4736
|
+
label: "Cancel",
|
|
4737
|
+
hint: "Exit plan mode. Drop the plan; the model won't implement it."
|
|
4738
|
+
}
|
|
4739
|
+
],
|
|
4740
|
+
onSubmit: (v) => onChoose(v)
|
|
4741
|
+
}
|
|
4742
|
+
)));
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
// src/cli/ui/PlanRefineInput.tsx
|
|
4746
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
4747
|
+
import React7, { useState as useState3 } from "react";
|
|
4748
|
+
function PlanRefineInput({ mode, onSubmit, onCancel }) {
|
|
4749
|
+
const [value, setValue] = useState3("");
|
|
4750
|
+
useInput2((input, key) => {
|
|
4751
|
+
if (key.escape) {
|
|
4752
|
+
onCancel();
|
|
4753
|
+
return;
|
|
4754
|
+
}
|
|
4755
|
+
if (key.return) {
|
|
4756
|
+
onSubmit(value.trim());
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
if (key.backspace || key.delete) {
|
|
4760
|
+
setValue((v) => v.slice(0, -1));
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
if (input && !key.ctrl && !key.meta) {
|
|
4764
|
+
setValue((v) => v + input);
|
|
4765
|
+
}
|
|
4766
|
+
});
|
|
4767
|
+
const title = mode === "approve" ? "\u25B8 approving \u2014 any last instructions or answers to open questions?" : "\u25B8 refining \u2014 what should the model change?";
|
|
4768
|
+
const hint = mode === "approve" ? "Answer questions the plan raised, add constraints, or just press Enter to approve as-is." : "Describe what's wrong or missing, or answer questions the plan raised.";
|
|
4769
|
+
const blankHint = mode === "approve" ? " (Enter with blank = approve without extra instructions.)" : " (Enter with blank = ask the model to list concrete questions.)";
|
|
4770
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "yellow" }, title)), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, hint, " Enter to send \xB7 Esc to return to the picker.", value === "" ? blankHint : "")), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, "\u203A "), /* @__PURE__ */ React7.createElement(Text6, null, value || " "), /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, "\u258D"))));
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
// src/cli/ui/PromptInput.tsx
|
|
4774
|
+
import { Box as Box7, Text as Text7, useInput as useInput3 } from "ink";
|
|
4775
|
+
import React8, { useRef, useState as useState4 } from "react";
|
|
4426
4776
|
|
|
4427
4777
|
// src/cli/ui/multiline-keys.ts
|
|
4428
4778
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
@@ -4537,7 +4887,7 @@ function PromptInput({
|
|
|
4537
4887
|
disabled,
|
|
4538
4888
|
placeholder
|
|
4539
4889
|
}) {
|
|
4540
|
-
const [cursor, setCursor] =
|
|
4890
|
+
const [cursor, setCursor] = useState4(value.length);
|
|
4541
4891
|
const lastLocalValueRef = useRef(value);
|
|
4542
4892
|
if (value !== lastLocalValueRef.current) {
|
|
4543
4893
|
lastLocalValueRef.current = value;
|
|
@@ -4547,7 +4897,7 @@ function PromptInput({
|
|
|
4547
4897
|
}
|
|
4548
4898
|
const tick = useTick();
|
|
4549
4899
|
const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
|
|
4550
|
-
|
|
4900
|
+
useInput3(
|
|
4551
4901
|
(input, key) => {
|
|
4552
4902
|
const ke = {
|
|
4553
4903
|
input,
|
|
@@ -4582,13 +4932,13 @@ function PromptInput({
|
|
|
4582
4932
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
4583
4933
|
const borderColor = disabled ? "gray" : "cyan";
|
|
4584
4934
|
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
4585
|
-
return /* @__PURE__ */
|
|
4935
|
+
return /* @__PURE__ */ React8.createElement(Box7, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
4586
4936
|
const isFirst = i === 0;
|
|
4587
4937
|
const showPlaceholder = isFirst && value.length === 0;
|
|
4588
4938
|
const isCursorLine = i === cursorLine;
|
|
4589
4939
|
return (
|
|
4590
4940
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
4591
|
-
/* @__PURE__ */
|
|
4941
|
+
/* @__PURE__ */ React8.createElement(Box7, { key: i }, isFirst ? /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React8.createElement(Text7, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React8.createElement(
|
|
4592
4942
|
LineWithCursor,
|
|
4593
4943
|
{
|
|
4594
4944
|
line,
|
|
@@ -4596,7 +4946,7 @@ function PromptInput({
|
|
|
4596
4946
|
showCursor,
|
|
4597
4947
|
borderColor
|
|
4598
4948
|
}
|
|
4599
|
-
) : /* @__PURE__ */
|
|
4949
|
+
) : /* @__PURE__ */ React8.createElement(Text7, null, line))
|
|
4600
4950
|
);
|
|
4601
4951
|
}));
|
|
4602
4952
|
}
|
|
@@ -4610,119 +4960,16 @@ function LineWithCursor({
|
|
|
4610
4960
|
const atCursor = line.slice(col, col + 1);
|
|
4611
4961
|
const after = line.slice(col + 1);
|
|
4612
4962
|
if (atCursor.length === 0) {
|
|
4613
|
-
return /* @__PURE__ */
|
|
4963
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, null, before), /* @__PURE__ */ React8.createElement(Text7, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
4614
4964
|
}
|
|
4615
|
-
return /* @__PURE__ */
|
|
4616
|
-
}
|
|
4617
|
-
|
|
4618
|
-
// src/cli/ui/ShellConfirm.tsx
|
|
4619
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
4620
|
-
import React7 from "react";
|
|
4621
|
-
|
|
4622
|
-
// src/cli/ui/Select.tsx
|
|
4623
|
-
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
4624
|
-
import React6, { useState as useState3 } from "react";
|
|
4625
|
-
function SingleSelect({
|
|
4626
|
-
items,
|
|
4627
|
-
initialValue,
|
|
4628
|
-
onSubmit,
|
|
4629
|
-
onCancel
|
|
4630
|
-
}) {
|
|
4631
|
-
const initialIndex = Math.max(
|
|
4632
|
-
0,
|
|
4633
|
-
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4634
|
-
);
|
|
4635
|
-
const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
|
|
4636
|
-
useInput2((_input, key) => {
|
|
4637
|
-
if (key.upArrow) {
|
|
4638
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4639
|
-
} else if (key.downArrow) {
|
|
4640
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4641
|
-
} else if (key.return) {
|
|
4642
|
-
const chosen = items[index];
|
|
4643
|
-
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4644
|
-
} else if (key.escape && onCancel) {
|
|
4645
|
-
onCancel();
|
|
4646
|
-
}
|
|
4647
|
-
});
|
|
4648
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React6.createElement(
|
|
4649
|
-
SelectRow,
|
|
4650
|
-
{
|
|
4651
|
-
key: item.value,
|
|
4652
|
-
item,
|
|
4653
|
-
active: i === index,
|
|
4654
|
-
marker: i === index ? "\u25B8" : " "
|
|
4655
|
-
}
|
|
4656
|
-
)));
|
|
4657
|
-
}
|
|
4658
|
-
function MultiSelect({
|
|
4659
|
-
items,
|
|
4660
|
-
initialSelected = [],
|
|
4661
|
-
onSubmit,
|
|
4662
|
-
onCancel,
|
|
4663
|
-
footer
|
|
4664
|
-
}) {
|
|
4665
|
-
const [index, setIndex] = useState3(() => {
|
|
4666
|
-
const first = items.findIndex((i) => !i.disabled);
|
|
4667
|
-
return first === -1 ? 0 : first;
|
|
4668
|
-
});
|
|
4669
|
-
const [selected, setSelected] = useState3(new Set(initialSelected));
|
|
4670
|
-
useInput2((input, key) => {
|
|
4671
|
-
if (key.upArrow) {
|
|
4672
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4673
|
-
} else if (key.downArrow) {
|
|
4674
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4675
|
-
} else if (input === " ") {
|
|
4676
|
-
const item = items[index];
|
|
4677
|
-
if (!item || item.disabled) return;
|
|
4678
|
-
setSelected((prev) => {
|
|
4679
|
-
const next = new Set(prev);
|
|
4680
|
-
if (next.has(item.value)) next.delete(item.value);
|
|
4681
|
-
else next.add(item.value);
|
|
4682
|
-
return next;
|
|
4683
|
-
});
|
|
4684
|
-
} else if (key.return) {
|
|
4685
|
-
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4686
|
-
onSubmit(ordered);
|
|
4687
|
-
} else if (key.escape && onCancel) {
|
|
4688
|
-
onCancel();
|
|
4689
|
-
}
|
|
4690
|
-
});
|
|
4691
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => {
|
|
4692
|
-
const checked = selected.has(item.value);
|
|
4693
|
-
const marker = checked ? "[x]" : "[ ]";
|
|
4694
|
-
return /* @__PURE__ */ React6.createElement(
|
|
4695
|
-
SelectRow,
|
|
4696
|
-
{
|
|
4697
|
-
key: item.value,
|
|
4698
|
-
item,
|
|
4699
|
-
active: i === index,
|
|
4700
|
-
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4701
|
-
}
|
|
4702
|
-
);
|
|
4703
|
-
}), footer ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, footer)) : null);
|
|
4704
|
-
}
|
|
4705
|
-
function SelectRow({
|
|
4706
|
-
item,
|
|
4707
|
-
active,
|
|
4708
|
-
marker
|
|
4709
|
-
}) {
|
|
4710
|
-
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4711
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, item.hint)) : null);
|
|
4712
|
-
}
|
|
4713
|
-
function findNextEnabled(items, from, step) {
|
|
4714
|
-
if (items.length === 0) return 0;
|
|
4715
|
-
let i = from;
|
|
4716
|
-
for (let tries = 0; tries < items.length; tries++) {
|
|
4717
|
-
i = (i + step + items.length) % items.length;
|
|
4718
|
-
if (!items[i]?.disabled) return i;
|
|
4719
|
-
}
|
|
4720
|
-
return from;
|
|
4965
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, null, before), /* @__PURE__ */ React8.createElement(Text7, { inverse: showCursor }, atCursor), /* @__PURE__ */ React8.createElement(Text7, null, after));
|
|
4721
4966
|
}
|
|
4722
4967
|
|
|
4723
4968
|
// src/cli/ui/ShellConfirm.tsx
|
|
4969
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
4970
|
+
import React9 from "react";
|
|
4724
4971
|
function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
4725
|
-
return /* @__PURE__ */
|
|
4972
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "yellow" }, "\u25B8 model wants to run a shell command")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "$ "), /* @__PURE__ */ React9.createElement(Text8, { color: "cyan" }, command))), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
|
|
4726
4973
|
SingleSelect,
|
|
4727
4974
|
{
|
|
4728
4975
|
initialValue: "run_once",
|
|
@@ -4777,15 +5024,15 @@ function derivePrefix(command) {
|
|
|
4777
5024
|
}
|
|
4778
5025
|
|
|
4779
5026
|
// src/cli/ui/SlashSuggestions.tsx
|
|
4780
|
-
import { Box as
|
|
4781
|
-
import
|
|
5027
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
5028
|
+
import React10 from "react";
|
|
4782
5029
|
function SlashSuggestions({
|
|
4783
5030
|
matches,
|
|
4784
5031
|
selectedIndex
|
|
4785
5032
|
}) {
|
|
4786
5033
|
if (matches === null) return null;
|
|
4787
5034
|
if (matches.length === 0) {
|
|
4788
|
-
return /* @__PURE__ */
|
|
5035
|
+
return /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
|
|
4789
5036
|
}
|
|
4790
5037
|
const MAX = 8;
|
|
4791
5038
|
const total = matches.length;
|
|
@@ -4793,27 +5040,28 @@ function SlashSuggestions({
|
|
|
4793
5040
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
4794
5041
|
const hiddenAbove = windowStart;
|
|
4795
5042
|
const hiddenBelow = total - windowStart - shown.length;
|
|
4796
|
-
return /* @__PURE__ */
|
|
5043
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React10.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191/\u2193 navigate \xB7 Tab or Enter to pick"));
|
|
4797
5044
|
}
|
|
4798
5045
|
function SuggestionRow({ spec, isSelected }) {
|
|
4799
5046
|
const marker = isSelected ? "\u25B8" : " ";
|
|
4800
5047
|
const name = `/${spec.cmd}`;
|
|
4801
5048
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
4802
5049
|
if (isSelected) {
|
|
4803
|
-
return /* @__PURE__ */
|
|
5050
|
+
return /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, " ", spec.summary));
|
|
4804
5051
|
}
|
|
4805
|
-
return /* @__PURE__ */
|
|
5052
|
+
return /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
4806
5053
|
}
|
|
4807
5054
|
|
|
4808
5055
|
// src/cli/ui/StatsPanel.tsx
|
|
4809
|
-
import { Box as
|
|
4810
|
-
import
|
|
5056
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
5057
|
+
import React11 from "react";
|
|
4811
5058
|
function StatsPanel({
|
|
4812
5059
|
summary,
|
|
4813
5060
|
model,
|
|
4814
5061
|
prefixHash,
|
|
4815
5062
|
harvestOn,
|
|
4816
5063
|
branchBudget,
|
|
5064
|
+
planMode,
|
|
4817
5065
|
balance
|
|
4818
5066
|
}) {
|
|
4819
5067
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
@@ -4822,7 +5070,7 @@ function StatsPanel({
|
|
|
4822
5070
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4823
5071
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
4824
5072
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
4825
|
-
return /* @__PURE__ */
|
|
5073
|
+
return /* @__PURE__ */ React11.createElement(Box10, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, model), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 PLAN") : null), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache hit "), /* @__PURE__ */ React11.createElement(Text10, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "ctx "), /* @__PURE__ */ React11.createElement(Text10, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "balance "), /* @__PURE__ */ React11.createElement(Text10, { color: balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green", bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : "")) : null));
|
|
4826
5074
|
}
|
|
4827
5075
|
function formatTokens(n) {
|
|
4828
5076
|
if (n < 1e3) return String(n);
|
|
@@ -4845,6 +5093,7 @@ var SLASH_COMMANDS = [
|
|
|
4845
5093
|
{ cmd: "branch", argsHint: "<N|off>", summary: "run N parallel samples per turn (N>=2)" },
|
|
4846
5094
|
{ cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
|
|
4847
5095
|
{ cmd: "tool", argsHint: "[N]", summary: "dump full output of the Nth tool call (1=latest)" },
|
|
5096
|
+
{ cmd: "memory", summary: "show the project's REASONIX.md (pinned into the system prompt)" },
|
|
4848
5097
|
{ cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
|
|
4849
5098
|
{ cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
|
|
4850
5099
|
{ cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
|
|
@@ -4863,6 +5112,17 @@ var SLASH_COMMANDS = [
|
|
|
4863
5112
|
argsHint: '"msg"',
|
|
4864
5113
|
summary: "git add -A && git commit -m ...",
|
|
4865
5114
|
contextual: "code"
|
|
5115
|
+
},
|
|
5116
|
+
{
|
|
5117
|
+
cmd: "plan",
|
|
5118
|
+
argsHint: "[on|off]",
|
|
5119
|
+
summary: "toggle read-only plan mode (writes bounced until submit_plan + approval)",
|
|
5120
|
+
contextual: "code"
|
|
5121
|
+
},
|
|
5122
|
+
{
|
|
5123
|
+
cmd: "apply-plan",
|
|
5124
|
+
summary: "force-approve a pending / in-text plan (fallback if picker was missed)",
|
|
5125
|
+
contextual: "code"
|
|
4866
5126
|
}
|
|
4867
5127
|
];
|
|
4868
5128
|
function suggestSlashCommands(prefix, codeMode = false) {
|
|
@@ -4913,11 +5173,14 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
4913
5173
|
" /compact [cap] shrink large tool results in history (default 4k/result)",
|
|
4914
5174
|
" /think dump the most recent turn's full R1 reasoning (reasoner only)",
|
|
4915
5175
|
" /tool [N] list tool calls (or dump full output of #N, 1=most recent)",
|
|
5176
|
+
" /memory show the project's REASONIX.md (pinned into the system prompt)",
|
|
4916
5177
|
" /retry truncate & resend your last message (fresh sample from the model)",
|
|
4917
5178
|
" /apply (code mode) commit the pending edit blocks to disk",
|
|
4918
5179
|
" /discard (code mode) drop pending edits without writing",
|
|
4919
5180
|
" /undo (code mode) roll back the last applied edit batch",
|
|
4920
5181
|
' /commit "msg" (code mode) git add -A && git commit -m "msg"',
|
|
5182
|
+
" /plan [on|off] (code mode) toggle read-only plan mode; writes gated behind submit_plan + your approval",
|
|
5183
|
+
" /apply-plan (code mode) force-approve pending/in-text plan (fallback)",
|
|
4921
5184
|
" /sessions list saved sessions (current is marked with \u25B8)",
|
|
4922
5185
|
" /forget delete the current session from disk",
|
|
4923
5186
|
" /new start fresh: drop all context + clear scrollback",
|
|
@@ -4994,6 +5257,45 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
4994
5257
|
resubmit: prev
|
|
4995
5258
|
};
|
|
4996
5259
|
}
|
|
5260
|
+
case "memory": {
|
|
5261
|
+
if (!memoryEnabled()) {
|
|
5262
|
+
return {
|
|
5263
|
+
info: "project memory is disabled (REASONIX_MEMORY=off in env). Unset the var to re-enable; no REASONIX.md will be pinned in the meantime."
|
|
5264
|
+
};
|
|
5265
|
+
}
|
|
5266
|
+
if (!ctx.memoryRoot) {
|
|
5267
|
+
return {
|
|
5268
|
+
info: "no project root on this session \u2014 `/memory` needs a working directory to resolve REASONIX.md from."
|
|
5269
|
+
};
|
|
5270
|
+
}
|
|
5271
|
+
const mem = readProjectMemory(ctx.memoryRoot);
|
|
5272
|
+
if (!mem) {
|
|
5273
|
+
return {
|
|
5274
|
+
info: [
|
|
5275
|
+
`no ${PROJECT_MEMORY_FILE} in ${ctx.memoryRoot}.`,
|
|
5276
|
+
"",
|
|
5277
|
+
"Project memory is an optional file you pin notes into \u2014 project conventions,",
|
|
5278
|
+
"things the model keeps forgetting, domain glossary, setup gotchas. When present,",
|
|
5279
|
+
"its contents are appended to the system prompt (the immutable-prefix region)",
|
|
5280
|
+
"so every turn sees it without eating per-turn context, and the prefix cache stays",
|
|
5281
|
+
"warm as long as the file is stable.",
|
|
5282
|
+
"",
|
|
5283
|
+
`Create it with: echo "# Project notes for Reasonix" > ${PROJECT_MEMORY_FILE}`,
|
|
5284
|
+
"Re-launch (or `/new`) to pick up changes \u2014 the prefix is hashed at session start."
|
|
5285
|
+
].join("\n")
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
const header = mem.truncated ? `\u25B8 project memory: ${mem.path} (${mem.originalChars.toLocaleString()} chars, truncated for the prefix)` : `\u25B8 project memory: ${mem.path} (${mem.originalChars.toLocaleString()} chars)`;
|
|
5289
|
+
return {
|
|
5290
|
+
info: [
|
|
5291
|
+
header,
|
|
5292
|
+
"",
|
|
5293
|
+
mem.content,
|
|
5294
|
+
"",
|
|
5295
|
+
"Changes take effect on the next launch or `/new` \u2014 the system prompt is hashed once per session to keep the prefix cache warm."
|
|
5296
|
+
].join("\n")
|
|
5297
|
+
};
|
|
5298
|
+
}
|
|
4997
5299
|
case "think":
|
|
4998
5300
|
case "reasoning": {
|
|
4999
5301
|
const raw = loop.scratch.reasoning;
|
|
@@ -5062,6 +5364,42 @@ ${entry.text}`
|
|
|
5062
5364
|
}
|
|
5063
5365
|
return { info: ctx.codeDiscard() };
|
|
5064
5366
|
}
|
|
5367
|
+
case "plan": {
|
|
5368
|
+
if (!ctx.setPlanMode) {
|
|
5369
|
+
return {
|
|
5370
|
+
info: "/plan is only available inside `reasonix code` \u2014 chat mode doesn't gate tool writes."
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5373
|
+
const currentOn = Boolean(ctx.planMode);
|
|
5374
|
+
const raw = (args[0] ?? "").toLowerCase();
|
|
5375
|
+
let target;
|
|
5376
|
+
if (raw === "on" || raw === "true" || raw === "1") target = true;
|
|
5377
|
+
else if (raw === "off" || raw === "false" || raw === "0") target = false;
|
|
5378
|
+
else target = !currentOn;
|
|
5379
|
+
ctx.setPlanMode(target);
|
|
5380
|
+
if (target) {
|
|
5381
|
+
return {
|
|
5382
|
+
info: "\u25B8 plan mode ON \u2014 write tools are gated; the model MUST call `submit_plan` before anything executes. (The model can also call submit_plan on its own for big tasks even when plan mode is off \u2014 this toggle is the stronger, explicit constraint.) Type /plan off to leave."
|
|
5383
|
+
};
|
|
5384
|
+
}
|
|
5385
|
+
return {
|
|
5386
|
+
info: "\u25B8 plan mode OFF \u2014 write tools are live again. Model can still propose plans autonomously for large tasks."
|
|
5387
|
+
};
|
|
5388
|
+
}
|
|
5389
|
+
case "apply-plan":
|
|
5390
|
+
case "applyplan": {
|
|
5391
|
+
if (!ctx.setPlanMode) {
|
|
5392
|
+
return {
|
|
5393
|
+
info: "/apply-plan is only available inside `reasonix code`."
|
|
5394
|
+
};
|
|
5395
|
+
}
|
|
5396
|
+
ctx.setPlanMode(false);
|
|
5397
|
+
ctx.clearPendingPlan?.();
|
|
5398
|
+
return {
|
|
5399
|
+
info: "\u25B8 plan approved \u2014 implementing",
|
|
5400
|
+
resubmit: "The plan above has been approved. Implement it now. You are out of plan mode \u2014 use edit_file / write_file / run_command as needed. Stick to the plan unless you discover a concrete reason to deviate; if you do, tell me and wait for a response before making that deviation."
|
|
5401
|
+
};
|
|
5402
|
+
}
|
|
5065
5403
|
case "commit": {
|
|
5066
5404
|
if (!ctx.codeRoot) {
|
|
5067
5405
|
return {
|
|
@@ -5132,6 +5470,7 @@ ${entry.text}`
|
|
|
5132
5470
|
const toolCount = loop.prefix.toolSpecs.length;
|
|
5133
5471
|
const mcpLine = ` mcp ${mcpCount} server(s), ${toolCount} tool(s) in registry`;
|
|
5134
5472
|
const pendingLine = pending > 0 ? ` edits ${pending} pending (/apply to commit, /discard to drop)` : "";
|
|
5473
|
+
const planLine = ctx.planMode ? " plan ON \u2014 writes gated (submit_plan + approval)" : "";
|
|
5135
5474
|
const lines = [
|
|
5136
5475
|
` model ${loop.model}`,
|
|
5137
5476
|
` flags harvest=${loop.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop.stream ? "on" : "off"}`,
|
|
@@ -5140,6 +5479,7 @@ ${entry.text}`
|
|
|
5140
5479
|
sessionLine
|
|
5141
5480
|
];
|
|
5142
5481
|
if (pendingLine) lines.push(pendingLine);
|
|
5482
|
+
if (planLine) lines.push(planLine);
|
|
5143
5483
|
return { info: lines.join("\n") };
|
|
5144
5484
|
}
|
|
5145
5485
|
case "model": {
|
|
@@ -5286,25 +5626,28 @@ function App({
|
|
|
5286
5626
|
codeMode
|
|
5287
5627
|
}) {
|
|
5288
5628
|
const { exit } = useApp();
|
|
5289
|
-
const [historical, setHistorical] =
|
|
5290
|
-
const [streaming, setStreaming] =
|
|
5291
|
-
const [input, setInput] =
|
|
5292
|
-
const [busy, setBusy] =
|
|
5629
|
+
const [historical, setHistorical] = useState5([]);
|
|
5630
|
+
const [streaming, setStreaming] = useState5(null);
|
|
5631
|
+
const [input, setInput] = useState5("");
|
|
5632
|
+
const [busy, setBusy] = useState5(false);
|
|
5293
5633
|
const abortedThisTurn = useRef2(false);
|
|
5294
|
-
const [ongoingTool, setOngoingTool] =
|
|
5295
|
-
const [toolProgress, setToolProgress] =
|
|
5296
|
-
const [statusLine, setStatusLine] =
|
|
5297
|
-
const [balance, setBalance] =
|
|
5634
|
+
const [ongoingTool, setOngoingTool] = useState5(null);
|
|
5635
|
+
const [toolProgress, setToolProgress] = useState5(null);
|
|
5636
|
+
const [statusLine, setStatusLine] = useState5(null);
|
|
5637
|
+
const [balance, setBalance] = useState5(null);
|
|
5298
5638
|
const lastEditSnapshots = useRef2(null);
|
|
5299
5639
|
const pendingEdits = useRef2([]);
|
|
5300
|
-
const [pendingShell, setPendingShell] =
|
|
5301
|
-
const [
|
|
5640
|
+
const [pendingShell, setPendingShell] = useState5(null);
|
|
5641
|
+
const [pendingPlan, setPendingPlan] = useState5(null);
|
|
5642
|
+
const [stagedInput, setStagedInput] = useState5(null);
|
|
5643
|
+
const [planMode, setPlanMode] = useState5(false);
|
|
5644
|
+
const [queuedSubmit, setQueuedSubmit] = useState5(null);
|
|
5302
5645
|
const promptHistory = useRef2([]);
|
|
5303
5646
|
const historyCursor = useRef2(-1);
|
|
5304
5647
|
const assistantIterCounter = useRef2(0);
|
|
5305
5648
|
const toolHistoryRef = useRef2([]);
|
|
5306
|
-
const [slashSelected, setSlashSelected] =
|
|
5307
|
-
const [summary, setSummary] =
|
|
5649
|
+
const [slashSelected, setSlashSelected] = useState5(0);
|
|
5650
|
+
const [summary, setSummary] = useState5({
|
|
5308
5651
|
turns: 0,
|
|
5309
5652
|
totalCostUsd: 0,
|
|
5310
5653
|
totalInputCostUsd: 0,
|
|
@@ -5409,7 +5752,7 @@ function App({
|
|
|
5409
5752
|
]);
|
|
5410
5753
|
}
|
|
5411
5754
|
}, [session, loop]);
|
|
5412
|
-
|
|
5755
|
+
useInput4((_input, key) => {
|
|
5413
5756
|
if (key.escape && busy) {
|
|
5414
5757
|
if (abortedThisTurn.current) return;
|
|
5415
5758
|
abortedThisTurn.current = true;
|
|
@@ -5489,6 +5832,16 @@ function App({
|
|
|
5489
5832
|
},
|
|
5490
5833
|
[model, prefixHash]
|
|
5491
5834
|
);
|
|
5835
|
+
const togglePlanMode = useCallback(
|
|
5836
|
+
(on) => {
|
|
5837
|
+
setPlanMode(on);
|
|
5838
|
+
tools?.setPlanMode(on);
|
|
5839
|
+
},
|
|
5840
|
+
[tools]
|
|
5841
|
+
);
|
|
5842
|
+
const clearPendingPlan = useCallback(() => {
|
|
5843
|
+
setPendingPlan(null);
|
|
5844
|
+
}, []);
|
|
5492
5845
|
const handleSubmit = useCallback(
|
|
5493
5846
|
async (raw) => {
|
|
5494
5847
|
let text = raw.trim();
|
|
@@ -5520,7 +5873,11 @@ function App({
|
|
|
5520
5873
|
codeDiscard: codeMode ? codeDiscard : void 0,
|
|
5521
5874
|
codeRoot: codeMode?.rootDir,
|
|
5522
5875
|
pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
|
|
5523
|
-
toolHistory: () => toolHistoryRef.current
|
|
5876
|
+
toolHistory: () => toolHistoryRef.current,
|
|
5877
|
+
memoryRoot: codeMode?.rootDir ?? process.cwd(),
|
|
5878
|
+
planMode,
|
|
5879
|
+
setPlanMode: codeMode ? togglePlanMode : void 0,
|
|
5880
|
+
clearPendingPlan: codeMode ? clearPendingPlan : void 0
|
|
5524
5881
|
});
|
|
5525
5882
|
if (result.exit) {
|
|
5526
5883
|
transcriptRef.current?.end();
|
|
@@ -5696,6 +6053,15 @@ function App({
|
|
|
5696
6053
|
} catch {
|
|
5697
6054
|
}
|
|
5698
6055
|
}
|
|
6056
|
+
if (codeMode && ev.toolName === "submit_plan" && ev.content.includes('"PlanProposedError:')) {
|
|
6057
|
+
try {
|
|
6058
|
+
const parsed = JSON.parse(ev.content);
|
|
6059
|
+
if (typeof parsed.plan === "string" && parsed.plan.trim()) {
|
|
6060
|
+
setPendingPlan(parsed.plan.trim());
|
|
6061
|
+
}
|
|
6062
|
+
} catch {
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
5699
6065
|
} else if (ev.role === "error") {
|
|
5700
6066
|
setHistorical((prev) => [
|
|
5701
6067
|
...prev,
|
|
@@ -5728,6 +6094,7 @@ function App({
|
|
|
5728
6094
|
},
|
|
5729
6095
|
[
|
|
5730
6096
|
busy,
|
|
6097
|
+
clearPendingPlan,
|
|
5731
6098
|
codeApply,
|
|
5732
6099
|
codeDiscard,
|
|
5733
6100
|
codeMode,
|
|
@@ -5736,7 +6103,9 @@ function App({
|
|
|
5736
6103
|
loop,
|
|
5737
6104
|
mcpSpecs,
|
|
5738
6105
|
mcpServers,
|
|
6106
|
+
planMode,
|
|
5739
6107
|
slashSelected,
|
|
6108
|
+
togglePlanMode,
|
|
5740
6109
|
writeTranscript
|
|
5741
6110
|
]
|
|
5742
6111
|
);
|
|
@@ -5801,7 +6170,92 @@ ${body}`;
|
|
|
5801
6170
|
void handleSubmit(text);
|
|
5802
6171
|
}
|
|
5803
6172
|
}, [busy, queuedSubmit, handleSubmit]);
|
|
5804
|
-
|
|
6173
|
+
const handlePlanConfirm = useCallback(
|
|
6174
|
+
async (choice) => {
|
|
6175
|
+
const hadPendingPlan = pendingPlan !== null;
|
|
6176
|
+
if (!hadPendingPlan && choice !== "approve") {
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6179
|
+
if (choice === "refine" || choice === "approve") {
|
|
6180
|
+
if (pendingPlan) {
|
|
6181
|
+
setStagedInput({ plan: pendingPlan, mode: choice });
|
|
6182
|
+
setPendingPlan(null);
|
|
6183
|
+
} else if (choice === "approve") {
|
|
6184
|
+
setStagedInput({ plan: "", mode: "approve" });
|
|
6185
|
+
}
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
setPendingPlan(null);
|
|
6189
|
+
togglePlanMode(false);
|
|
6190
|
+
const marker = "\u25B8 plan cancelled";
|
|
6191
|
+
const synthetic = "The plan was cancelled. Drop it entirely. Ask me what I actually want before proposing another plan or making any changes.";
|
|
6192
|
+
setHistorical((prev) => [
|
|
6193
|
+
...prev,
|
|
6194
|
+
{ id: `plan-${choice}-${Date.now()}`, role: "info", text: marker }
|
|
6195
|
+
]);
|
|
6196
|
+
if (busy) {
|
|
6197
|
+
loop.abort();
|
|
6198
|
+
setQueuedSubmit(synthetic);
|
|
6199
|
+
} else {
|
|
6200
|
+
await handleSubmit(synthetic);
|
|
6201
|
+
}
|
|
6202
|
+
},
|
|
6203
|
+
[pendingPlan, togglePlanMode, busy, loop, handleSubmit]
|
|
6204
|
+
);
|
|
6205
|
+
const handleStagedInputSubmit = useCallback(
|
|
6206
|
+
async (feedback) => {
|
|
6207
|
+
const staged = stagedInput;
|
|
6208
|
+
setStagedInput(null);
|
|
6209
|
+
if (!staged) return;
|
|
6210
|
+
const trimmed = feedback.trim();
|
|
6211
|
+
let synthetic;
|
|
6212
|
+
let marker;
|
|
6213
|
+
if (staged.mode === "approve") {
|
|
6214
|
+
togglePlanMode(false);
|
|
6215
|
+
if (trimmed) {
|
|
6216
|
+
synthetic = `The plan above has been approved. Implement it now. You are out of plan mode \u2014 use edit_file / write_file / run_command as needed.
|
|
6217
|
+
|
|
6218
|
+
User's additional instructions / answers to your open questions:
|
|
6219
|
+
|
|
6220
|
+
${trimmed}
|
|
6221
|
+
|
|
6222
|
+
Factor these in before the first edit. Stick to the plan unless you discover a concrete reason to deviate; if you do, tell me and wait for a response.`;
|
|
6223
|
+
marker = `\u25B8 plan approved + instructions \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}`;
|
|
6224
|
+
} else {
|
|
6225
|
+
synthetic = "The plan above has been approved. Implement it now. You are out of plan mode \u2014 use edit_file / write_file / run_command as needed. If the plan listed open questions and I didn't answer them, default to the safest interpretation and call them out in your first reply. Don't fabricate preferences \u2014 if a question is truly unanswerable without me, stop and ask.";
|
|
6226
|
+
marker = "\u25B8 plan approved \u2014 implementing";
|
|
6227
|
+
}
|
|
6228
|
+
} else {
|
|
6229
|
+
if (trimmed) {
|
|
6230
|
+
synthetic = `The plan needs refinement. User feedback / answers:
|
|
6231
|
+
|
|
6232
|
+
${trimmed}
|
|
6233
|
+
|
|
6234
|
+
Stay in plan mode \u2014 address the feedback (explore more if needed), then submit an improved submit_plan call. Don't propose a near-identical plan unless you explain why the feedback doesn't apply.`;
|
|
6235
|
+
marker = `\u25B8 refining \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}`;
|
|
6236
|
+
} else {
|
|
6237
|
+
synthetic = "The plan needs refinement, but the user didn't give specifics. Ask them one or two concrete questions \u2014 scope, approach, file boundaries, or the risks you flagged \u2014 then wait for their answer before submitting an updated plan.";
|
|
6238
|
+
marker = "\u25B8 refining \u2014 asking the model to clarify";
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
setHistorical((prev) => [
|
|
6242
|
+
...prev,
|
|
6243
|
+
{ id: `plan-${staged.mode}-${Date.now()}`, role: "info", text: marker }
|
|
6244
|
+
]);
|
|
6245
|
+
if (busy) {
|
|
6246
|
+
loop.abort();
|
|
6247
|
+
setQueuedSubmit(synthetic);
|
|
6248
|
+
} else {
|
|
6249
|
+
await handleSubmit(synthetic);
|
|
6250
|
+
}
|
|
6251
|
+
},
|
|
6252
|
+
[stagedInput, togglePlanMode, busy, loop, handleSubmit]
|
|
6253
|
+
);
|
|
6254
|
+
const handleStagedInputCancel = useCallback(() => {
|
|
6255
|
+
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
6256
|
+
setStagedInput(null);
|
|
6257
|
+
}, [stagedInput]);
|
|
6258
|
+
return /* @__PURE__ */ React12.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(
|
|
5805
6259
|
StatsPanel,
|
|
5806
6260
|
{
|
|
5807
6261
|
summary,
|
|
@@ -5809,16 +6263,24 @@ ${body}`;
|
|
|
5809
6263
|
prefixHash,
|
|
5810
6264
|
harvestOn: loop.harvestEnabled,
|
|
5811
6265
|
branchBudget: loop.branchOptions.budget,
|
|
6266
|
+
planMode,
|
|
5812
6267
|
balance
|
|
5813
6268
|
}
|
|
5814
|
-
), /* @__PURE__ */
|
|
6269
|
+
), /* @__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(
|
|
6270
|
+
PlanRefineInput,
|
|
6271
|
+
{
|
|
6272
|
+
mode: stagedInput.mode,
|
|
6273
|
+
onSubmit: handleStagedInputSubmit,
|
|
6274
|
+
onCancel: handleStagedInputCancel
|
|
6275
|
+
}
|
|
6276
|
+
) : pendingPlan ? /* @__PURE__ */ React12.createElement(PlanConfirm, { plan: pendingPlan, onChoose: handlePlanConfirm }) : pendingShell ? /* @__PURE__ */ React12.createElement(
|
|
5815
6277
|
ShellConfirm,
|
|
5816
6278
|
{
|
|
5817
6279
|
command: pendingShell,
|
|
5818
6280
|
allowPrefix: derivePrefix(pendingShell),
|
|
5819
6281
|
onChoose: handleShellConfirm
|
|
5820
6282
|
}
|
|
5821
|
-
) : /* @__PURE__ */
|
|
6283
|
+
) : /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
|
|
5822
6284
|
PromptInput,
|
|
5823
6285
|
{
|
|
5824
6286
|
value: input,
|
|
@@ -5826,13 +6288,13 @@ ${body}`;
|
|
|
5826
6288
|
onSubmit: handleSubmit,
|
|
5827
6289
|
disabled: busy
|
|
5828
6290
|
}
|
|
5829
|
-
), /* @__PURE__ */
|
|
6291
|
+
), /* @__PURE__ */ React12.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }))));
|
|
5830
6292
|
}
|
|
5831
6293
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5832
6294
|
function StatusRow({ text }) {
|
|
5833
6295
|
const tick = useTick();
|
|
5834
6296
|
const elapsed = useElapsedSeconds();
|
|
5835
|
-
return /* @__PURE__ */
|
|
6297
|
+
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`));
|
|
5836
6298
|
}
|
|
5837
6299
|
function OngoingToolRow({
|
|
5838
6300
|
tool,
|
|
@@ -5841,7 +6303,7 @@ function OngoingToolRow({
|
|
|
5841
6303
|
const tick = useTick();
|
|
5842
6304
|
const elapsed = useElapsedSeconds();
|
|
5843
6305
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
5844
|
-
return /* @__PURE__ */
|
|
6306
|
+
return /* @__PURE__ */ React12.createElement(Box11, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, summary)) : null);
|
|
5845
6307
|
}
|
|
5846
6308
|
function renderProgressLine(p) {
|
|
5847
6309
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -5937,15 +6399,15 @@ function describeRepair(repair) {
|
|
|
5937
6399
|
}
|
|
5938
6400
|
|
|
5939
6401
|
// src/cli/ui/SessionPicker.tsx
|
|
5940
|
-
import { Box as
|
|
5941
|
-
import
|
|
6402
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
6403
|
+
import React13 from "react";
|
|
5942
6404
|
function SessionPicker({
|
|
5943
6405
|
sessionName,
|
|
5944
6406
|
messageCount,
|
|
5945
6407
|
lastActive,
|
|
5946
6408
|
onChoose
|
|
5947
6409
|
}) {
|
|
5948
|
-
return /* @__PURE__ */
|
|
6410
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React13.createElement(
|
|
5949
6411
|
SingleSelect,
|
|
5950
6412
|
{
|
|
5951
6413
|
initialValue: "new",
|
|
@@ -5968,7 +6430,7 @@ function SessionPicker({
|
|
|
5968
6430
|
],
|
|
5969
6431
|
onSubmit: (v) => onChoose(v)
|
|
5970
6432
|
}
|
|
5971
|
-
), /* @__PURE__ */
|
|
6433
|
+
), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
|
|
5972
6434
|
}
|
|
5973
6435
|
function relativeTime(date) {
|
|
5974
6436
|
const ms = Date.now() - date.getTime();
|
|
@@ -5984,12 +6446,12 @@ function relativeTime(date) {
|
|
|
5984
6446
|
}
|
|
5985
6447
|
|
|
5986
6448
|
// src/cli/ui/Setup.tsx
|
|
5987
|
-
import { Box as
|
|
6449
|
+
import { Box as Box13, Text as Text13, useApp as useApp2 } from "ink";
|
|
5988
6450
|
import TextInput from "ink-text-input";
|
|
5989
|
-
import
|
|
6451
|
+
import React14, { useState as useState6 } from "react";
|
|
5990
6452
|
function Setup({ onReady }) {
|
|
5991
|
-
const [value, setValue] =
|
|
5992
|
-
const [error, setError] =
|
|
6453
|
+
const [value, setValue] = useState6("");
|
|
6454
|
+
const [error, setError] = useState6(null);
|
|
5993
6455
|
const { exit } = useApp2();
|
|
5994
6456
|
const handleSubmit = (raw) => {
|
|
5995
6457
|
const trimmed = raw.trim();
|
|
@@ -6010,7 +6472,7 @@ function Setup({ onReady }) {
|
|
|
6010
6472
|
}
|
|
6011
6473
|
onReady(trimmed);
|
|
6012
6474
|
};
|
|
6013
|
-
return /* @__PURE__ */
|
|
6475
|
+
return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React14.createElement(
|
|
6014
6476
|
TextInput,
|
|
6015
6477
|
{
|
|
6016
6478
|
value,
|
|
@@ -6019,7 +6481,7 @@ function Setup({ onReady }) {
|
|
|
6019
6481
|
mask: "\u2022",
|
|
6020
6482
|
placeholder: "sk-..."
|
|
6021
6483
|
}
|
|
6022
|
-
)), error ? /* @__PURE__ */
|
|
6484
|
+
)), error ? /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "red" }, error)) : value ? /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "(Type /exit to abort.)")));
|
|
6023
6485
|
}
|
|
6024
6486
|
|
|
6025
6487
|
// src/cli/commands/chat.tsx
|
|
@@ -6032,10 +6494,10 @@ function Root({
|
|
|
6032
6494
|
sessionPreview,
|
|
6033
6495
|
...appProps
|
|
6034
6496
|
}) {
|
|
6035
|
-
const [key, setKey] =
|
|
6036
|
-
const [pending, setPending] =
|
|
6497
|
+
const [key, setKey] = useState7(initialKey);
|
|
6498
|
+
const [pending, setPending] = useState7(sessionPreview);
|
|
6037
6499
|
if (!key) {
|
|
6038
|
-
return /* @__PURE__ */
|
|
6500
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6039
6501
|
Setup,
|
|
6040
6502
|
{
|
|
6041
6503
|
onReady: (k) => {
|
|
@@ -6047,7 +6509,7 @@ function Root({
|
|
|
6047
6509
|
}
|
|
6048
6510
|
process.env.DEEPSEEK_API_KEY = key;
|
|
6049
6511
|
if (pending && appProps.session) {
|
|
6050
|
-
return /* @__PURE__ */
|
|
6512
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6051
6513
|
SessionPicker,
|
|
6052
6514
|
{
|
|
6053
6515
|
sessionName: appProps.session,
|
|
@@ -6062,7 +6524,7 @@ function Root({
|
|
|
6062
6524
|
}
|
|
6063
6525
|
);
|
|
6064
6526
|
}
|
|
6065
|
-
return /* @__PURE__ */
|
|
6527
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6066
6528
|
App,
|
|
6067
6529
|
{
|
|
6068
6530
|
model: appProps.model,
|
|
@@ -6154,14 +6616,14 @@ async function chatCommand(opts) {
|
|
|
6154
6616
|
const prior = loadSessionMessages(opts.session);
|
|
6155
6617
|
if (prior.length > 0) {
|
|
6156
6618
|
const p = sessionPath(opts.session);
|
|
6157
|
-
const mtime =
|
|
6619
|
+
const mtime = existsSync4(p) ? statSync3(p).mtime : /* @__PURE__ */ new Date();
|
|
6158
6620
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
6159
6621
|
}
|
|
6160
6622
|
} else if (opts.session && opts.forceNew) {
|
|
6161
6623
|
rewriteSession(opts.session, []);
|
|
6162
6624
|
}
|
|
6163
6625
|
const { waitUntilExit } = render(
|
|
6164
|
-
/* @__PURE__ */
|
|
6626
|
+
/* @__PURE__ */ React15.createElement(
|
|
6165
6627
|
Root,
|
|
6166
6628
|
{
|
|
6167
6629
|
initialKey,
|
|
@@ -6187,7 +6649,7 @@ async function chatCommand(opts) {
|
|
|
6187
6649
|
// src/cli/commands/code.tsx
|
|
6188
6650
|
import { basename, resolve as resolve5 } from "path";
|
|
6189
6651
|
async function codeCommand(opts = {}) {
|
|
6190
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
6652
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-JNNNJLYF.js");
|
|
6191
6653
|
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
6192
6654
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
6193
6655
|
const tools = new ToolRegistry();
|
|
@@ -6198,6 +6660,7 @@ async function codeCommand(opts = {}) {
|
|
|
6198
6660
|
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
6199
6661
|
extraAllowed: loadProjectShellAllowed(rootDir)
|
|
6200
6662
|
});
|
|
6663
|
+
registerPlanTool(tools);
|
|
6201
6664
|
process.stderr.write(
|
|
6202
6665
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
6203
6666
|
`
|
|
@@ -6220,34 +6683,34 @@ async function codeCommand(opts = {}) {
|
|
|
6220
6683
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
6221
6684
|
import { basename as basename2 } from "path";
|
|
6222
6685
|
import { render as render2 } from "ink";
|
|
6223
|
-
import
|
|
6686
|
+
import React18 from "react";
|
|
6224
6687
|
|
|
6225
6688
|
// src/cli/ui/DiffApp.tsx
|
|
6226
|
-
import { Box as
|
|
6227
|
-
import
|
|
6689
|
+
import { Box as Box15, Static as Static2, Text as Text15, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
6690
|
+
import React17, { useState as useState8 } from "react";
|
|
6228
6691
|
|
|
6229
6692
|
// src/cli/ui/RecordView.tsx
|
|
6230
|
-
import { Box as
|
|
6231
|
-
import
|
|
6693
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
6694
|
+
import React16 from "react";
|
|
6232
6695
|
function RecordView({ rec, compact = false }) {
|
|
6233
6696
|
const toolArgsMax = compact ? 120 : 200;
|
|
6234
6697
|
const toolContentMax = compact ? 200 : 400;
|
|
6235
6698
|
if (rec.role === "user") {
|
|
6236
|
-
return /* @__PURE__ */
|
|
6699
|
+
return /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React16.createElement(Text14, null, rec.content));
|
|
6237
6700
|
}
|
|
6238
6701
|
if (rec.role === "assistant_final") {
|
|
6239
|
-
return /* @__PURE__ */
|
|
6702
|
+
return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Text14, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React16.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React16.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React16.createElement(Text14, null, rec.content) : /* @__PURE__ */ React16.createElement(Text14, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
6240
6703
|
}
|
|
6241
6704
|
if (rec.role === "tool") {
|
|
6242
|
-
return /* @__PURE__ */
|
|
6705
|
+
return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
|
|
6243
6706
|
}
|
|
6244
6707
|
if (rec.role === "error") {
|
|
6245
|
-
return /* @__PURE__ */
|
|
6708
|
+
return /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React16.createElement(Text14, { color: "red" }, rec.error ?? rec.content));
|
|
6246
6709
|
}
|
|
6247
6710
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
6248
6711
|
return null;
|
|
6249
6712
|
}
|
|
6250
|
-
return /* @__PURE__ */
|
|
6713
|
+
return /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
6251
6714
|
}
|
|
6252
6715
|
function CacheBadge({ usage }) {
|
|
6253
6716
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -6256,7 +6719,7 @@ function CacheBadge({ usage }) {
|
|
|
6256
6719
|
if (total === 0) return null;
|
|
6257
6720
|
const pct2 = hit / total * 100;
|
|
6258
6721
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
6259
|
-
return /* @__PURE__ */
|
|
6722
|
+
return /* @__PURE__ */ React16.createElement(Text14, null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React16.createElement(Text14, { color }, pct2.toFixed(1), "%"));
|
|
6260
6723
|
}
|
|
6261
6724
|
function truncate3(s, max) {
|
|
6262
6725
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -6267,8 +6730,8 @@ function DiffApp({ report }) {
|
|
|
6267
6730
|
const { exit } = useApp3();
|
|
6268
6731
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
6269
6732
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
6270
|
-
const [idx, setIdx] =
|
|
6271
|
-
|
|
6733
|
+
const [idx, setIdx] = useState8(Math.max(0, initialIdx));
|
|
6734
|
+
useInput5((input, key) => {
|
|
6272
6735
|
if (input === "q" || key.ctrl && input === "c") {
|
|
6273
6736
|
exit();
|
|
6274
6737
|
return;
|
|
@@ -6290,7 +6753,7 @@ function DiffApp({ report }) {
|
|
|
6290
6753
|
}
|
|
6291
6754
|
});
|
|
6292
6755
|
const pair = report.pairs[idx];
|
|
6293
|
-
return /* @__PURE__ */
|
|
6756
|
+
return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(DiffHeader, { report }), /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React17.createElement(Text15, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React17.createElement(Text15, null, pair ? /* @__PURE__ */ React17.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React17.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React17.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text15, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React17.createElement(Text15, null, pair.divergenceNote)) : null, /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "j"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "k"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "N"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "g"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "q"), " ", "quit")));
|
|
6294
6757
|
}
|
|
6295
6758
|
function DiffHeader({ report }) {
|
|
6296
6759
|
const a = report.a;
|
|
@@ -6308,15 +6771,15 @@ function DiffHeader({ report }) {
|
|
|
6308
6771
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
6309
6772
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
6310
6773
|
}
|
|
6311
|
-
return /* @__PURE__ */
|
|
6774
|
+
return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Box15, { justifyContent: "space-between" }, /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React17.createElement(Text15, { color: "blue" }, a.label), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " vs B="), /* @__PURE__ */ React17.createElement(Text15, { color: "magenta" }, b.label)), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "cache "), /* @__PURE__ */ React17.createElement(Text15, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React17.createElement(Text15, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React17.createElement(Text15, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "cost "), /* @__PURE__ */ React17.createElement(Text15, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React17.createElement(Text15, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React17.createElement(Text15, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "model calls "), /* @__PURE__ */ React17.createElement(Text15, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
6312
6775
|
}
|
|
6313
6776
|
function Pane({
|
|
6314
6777
|
label,
|
|
6315
6778
|
headerColor,
|
|
6316
6779
|
records
|
|
6317
6780
|
}) {
|
|
6318
|
-
return /* @__PURE__ */
|
|
6319
|
-
|
|
6781
|
+
return /* @__PURE__ */ React17.createElement(
|
|
6782
|
+
Box15,
|
|
6320
6783
|
{
|
|
6321
6784
|
flexDirection: "column",
|
|
6322
6785
|
flexGrow: 1,
|
|
@@ -6324,21 +6787,21 @@ function Pane({
|
|
|
6324
6787
|
borderStyle: "single",
|
|
6325
6788
|
borderColor: headerColor
|
|
6326
6789
|
},
|
|
6327
|
-
/* @__PURE__ */
|
|
6328
|
-
records.length === 0 ? /* @__PURE__ */
|
|
6790
|
+
/* @__PURE__ */ React17.createElement(Text15, { color: headerColor, bold: true }, label),
|
|
6791
|
+
records.length === 0 ? /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React17.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React17.createElement(RecordView, { key, rec, compact: true }))
|
|
6329
6792
|
);
|
|
6330
6793
|
}
|
|
6331
6794
|
function KindBadge({ kind }) {
|
|
6332
6795
|
if (kind === "match") {
|
|
6333
|
-
return /* @__PURE__ */
|
|
6796
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "green" }, "\u2713 match");
|
|
6334
6797
|
}
|
|
6335
6798
|
if (kind === "diverge") {
|
|
6336
|
-
return /* @__PURE__ */
|
|
6799
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "yellow" }, "\u2605 diverge");
|
|
6337
6800
|
}
|
|
6338
6801
|
if (kind === "only_in_a") {
|
|
6339
|
-
return /* @__PURE__ */
|
|
6802
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "blue" }, "\u2190 only in A");
|
|
6340
6803
|
}
|
|
6341
|
-
return /* @__PURE__ */
|
|
6804
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "magenta" }, "\u2192 only in B");
|
|
6342
6805
|
}
|
|
6343
6806
|
function paneRecords(pair, side) {
|
|
6344
6807
|
if (!pair) return [];
|
|
@@ -6369,7 +6832,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
6369
6832
|
return;
|
|
6370
6833
|
}
|
|
6371
6834
|
if (wantTui) {
|
|
6372
|
-
const { waitUntilExit } = render2(
|
|
6835
|
+
const { waitUntilExit } = render2(React18.createElement(DiffApp, { report }), {
|
|
6373
6836
|
exitOnCtrlC: true,
|
|
6374
6837
|
patchConsole: false
|
|
6375
6838
|
});
|
|
@@ -6510,16 +6973,16 @@ function pad(s, width) {
|
|
|
6510
6973
|
|
|
6511
6974
|
// src/cli/commands/replay.ts
|
|
6512
6975
|
import { render as render3 } from "ink";
|
|
6513
|
-
import
|
|
6976
|
+
import React20 from "react";
|
|
6514
6977
|
|
|
6515
6978
|
// src/cli/ui/ReplayApp.tsx
|
|
6516
|
-
import { Box as
|
|
6517
|
-
import
|
|
6979
|
+
import { Box as Box16, Static as Static3, Text as Text16, useApp as useApp4, useInput as useInput6 } from "ink";
|
|
6980
|
+
import React19, { useMemo as useMemo2, useState as useState9 } from "react";
|
|
6518
6981
|
function ReplayApp({ meta, pages }) {
|
|
6519
6982
|
const { exit } = useApp4();
|
|
6520
6983
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
6521
|
-
const [idx, setIdx] =
|
|
6522
|
-
|
|
6984
|
+
const [idx, setIdx] = useState9(maxIdx);
|
|
6985
|
+
useInput6((input, key) => {
|
|
6523
6986
|
if (input === "q" || key.ctrl && input === "c") {
|
|
6524
6987
|
exit();
|
|
6525
6988
|
return;
|
|
@@ -6553,14 +7016,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
6553
7016
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
6554
7017
|
const currentPage = pages[idx];
|
|
6555
7018
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
6556
|
-
return /* @__PURE__ */
|
|
7019
|
+
return /* @__PURE__ */ React19.createElement(Box16, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(
|
|
6557
7020
|
StatsPanel,
|
|
6558
7021
|
{
|
|
6559
7022
|
summary,
|
|
6560
7023
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
6561
7024
|
prefixHash
|
|
6562
7025
|
}
|
|
6563
|
-
), /* @__PURE__ */
|
|
7026
|
+
), /* @__PURE__ */ React19.createElement(Box16, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React19.createElement(Box16, { justifyContent: "space-between" }, /* @__PURE__ */ React19.createElement(Text16, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React19.createElement(Text16, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React19.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React19.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React19.createElement(Text16, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React19.createElement(Box16, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React19.createElement(Text16, { dimColor: true }, /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "j"), "/", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "k"), "/", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "q"), " quit")));
|
|
6564
7027
|
}
|
|
6565
7028
|
|
|
6566
7029
|
// src/cli/commands/replay.ts
|
|
@@ -6572,7 +7035,7 @@ async function replayCommand(opts) {
|
|
|
6572
7035
|
}
|
|
6573
7036
|
const { parsed } = replayFromFile(opts.path);
|
|
6574
7037
|
const pages = groupRecordsByTurn(parsed.records);
|
|
6575
|
-
const { waitUntilExit } = render3(
|
|
7038
|
+
const { waitUntilExit } = render3(React20.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
6576
7039
|
exitOnCtrlC: true,
|
|
6577
7040
|
patchConsole: false
|
|
6578
7041
|
});
|
|
@@ -6874,12 +7337,12 @@ function truncate4(s, max) {
|
|
|
6874
7337
|
|
|
6875
7338
|
// src/cli/commands/setup.tsx
|
|
6876
7339
|
import { render as render4 } from "ink";
|
|
6877
|
-
import
|
|
7340
|
+
import React22 from "react";
|
|
6878
7341
|
|
|
6879
7342
|
// src/cli/ui/Wizard.tsx
|
|
6880
|
-
import { Box as
|
|
7343
|
+
import { Box as Box17, Text as Text17, useApp as useApp5, useInput as useInput7 } from "ink";
|
|
6881
7344
|
import TextInput2 from "ink-text-input";
|
|
6882
|
-
import
|
|
7345
|
+
import React21, { useState as useState10 } from "react";
|
|
6883
7346
|
|
|
6884
7347
|
// src/cli/ui/presets.ts
|
|
6885
7348
|
var PRESETS = {
|
|
@@ -6906,19 +7369,19 @@ var PRESET_DESCRIPTIONS = {
|
|
|
6906
7369
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
6907
7370
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
6908
7371
|
const { exit } = useApp5();
|
|
6909
|
-
const [step, setStep] =
|
|
6910
|
-
const [data, setData] =
|
|
7372
|
+
const [step, setStep] = useState10(existingApiKey ? "preset" : "apiKey");
|
|
7373
|
+
const [data, setData] = useState10({
|
|
6911
7374
|
apiKey: existingApiKey ?? "",
|
|
6912
7375
|
preset: initial?.preset ?? "fast",
|
|
6913
7376
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
6914
7377
|
catalogArgs: {}
|
|
6915
7378
|
});
|
|
6916
|
-
const [error, setError] =
|
|
6917
|
-
|
|
7379
|
+
const [error, setError] = useState10(null);
|
|
7380
|
+
useInput7((_input, key) => {
|
|
6918
7381
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
6919
7382
|
});
|
|
6920
7383
|
if (step === "apiKey") {
|
|
6921
|
-
return /* @__PURE__ */
|
|
7384
|
+
return /* @__PURE__ */ React21.createElement(
|
|
6922
7385
|
ApiKeyStep,
|
|
6923
7386
|
{
|
|
6924
7387
|
onSubmit: (key) => {
|
|
@@ -6932,7 +7395,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6932
7395
|
);
|
|
6933
7396
|
}
|
|
6934
7397
|
if (step === "preset") {
|
|
6935
|
-
return /* @__PURE__ */
|
|
7398
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React21.createElement(
|
|
6936
7399
|
SingleSelect,
|
|
6937
7400
|
{
|
|
6938
7401
|
items: presetItems(),
|
|
@@ -6942,10 +7405,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6942
7405
|
setStep("mcp");
|
|
6943
7406
|
}
|
|
6944
7407
|
}
|
|
6945
|
-
), /* @__PURE__ */
|
|
7408
|
+
), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
6946
7409
|
}
|
|
6947
7410
|
if (step === "mcp") {
|
|
6948
|
-
return /* @__PURE__ */
|
|
7411
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React21.createElement(
|
|
6949
7412
|
MultiSelect,
|
|
6950
7413
|
{
|
|
6951
7414
|
items: mcpItems(),
|
|
@@ -6970,7 +7433,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6970
7433
|
}
|
|
6971
7434
|
const currentName = pending[0];
|
|
6972
7435
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
6973
|
-
return /* @__PURE__ */
|
|
7436
|
+
return /* @__PURE__ */ React21.createElement(
|
|
6974
7437
|
McpArgsStep,
|
|
6975
7438
|
{
|
|
6976
7439
|
entry,
|
|
@@ -6988,7 +7451,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6988
7451
|
}
|
|
6989
7452
|
if (step === "review") {
|
|
6990
7453
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
6991
|
-
return /* @__PURE__ */
|
|
7454
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React21.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React21.createElement(
|
|
6992
7455
|
SummaryLine,
|
|
6993
7456
|
{
|
|
6994
7457
|
label: "MCP",
|
|
@@ -6996,8 +7459,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6996
7459
|
}
|
|
6997
7460
|
), specs.map((spec, i) => (
|
|
6998
7461
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
6999
|
-
/* @__PURE__ */
|
|
7000
|
-
)), /* @__PURE__ */
|
|
7462
|
+
/* @__PURE__ */ React21.createElement(Box17, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\xB7 ", spec))
|
|
7463
|
+
)), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : null, /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React21.createElement(
|
|
7001
7464
|
ReviewConfirm,
|
|
7002
7465
|
{
|
|
7003
7466
|
onConfirm: () => {
|
|
@@ -7023,15 +7486,15 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7023
7486
|
}
|
|
7024
7487
|
));
|
|
7025
7488
|
}
|
|
7026
|
-
return /* @__PURE__ */
|
|
7489
|
+
return /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React21.createElement(ExitOnEnter, { onExit: exit }));
|
|
7027
7490
|
}
|
|
7028
7491
|
function ApiKeyStep({
|
|
7029
7492
|
onSubmit,
|
|
7030
7493
|
error,
|
|
7031
7494
|
onError
|
|
7032
7495
|
}) {
|
|
7033
|
-
const [value, setValue] =
|
|
7034
|
-
return /* @__PURE__ */
|
|
7496
|
+
const [value, setValue] = useState10("");
|
|
7497
|
+
return /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React21.createElement(
|
|
7035
7498
|
TextInput2,
|
|
7036
7499
|
{
|
|
7037
7500
|
value,
|
|
@@ -7048,7 +7511,7 @@ function ApiKeyStep({
|
|
|
7048
7511
|
mask: "\u2022",
|
|
7049
7512
|
placeholder: "sk-..."
|
|
7050
7513
|
}
|
|
7051
|
-
)), error ? /* @__PURE__ */
|
|
7514
|
+
)), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : value ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
7052
7515
|
}
|
|
7053
7516
|
function McpArgsStep({
|
|
7054
7517
|
entry,
|
|
@@ -7056,8 +7519,8 @@ function McpArgsStep({
|
|
|
7056
7519
|
onSubmit,
|
|
7057
7520
|
onError
|
|
7058
7521
|
}) {
|
|
7059
|
-
const [value, setValue] =
|
|
7060
|
-
return /* @__PURE__ */
|
|
7522
|
+
const [value, setValue] = useState10("");
|
|
7523
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(Text17, null, entry.summary), entry.note ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Required parameter: "), /* @__PURE__ */ React21.createElement(Text17, { bold: true }, entry.userArgs)), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React21.createElement(
|
|
7061
7524
|
TextInput2,
|
|
7062
7525
|
{
|
|
7063
7526
|
value,
|
|
@@ -7073,16 +7536,16 @@ function McpArgsStep({
|
|
|
7073
7536
|
},
|
|
7074
7537
|
placeholder: placeholderFor(entry)
|
|
7075
7538
|
}
|
|
7076
|
-
)), error ? /* @__PURE__ */
|
|
7539
|
+
)), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : null));
|
|
7077
7540
|
}
|
|
7078
7541
|
function ReviewConfirm({ onConfirm }) {
|
|
7079
|
-
|
|
7542
|
+
useInput7((_i, key) => {
|
|
7080
7543
|
if (key.return) onConfirm();
|
|
7081
7544
|
});
|
|
7082
7545
|
return null;
|
|
7083
7546
|
}
|
|
7084
7547
|
function ExitOnEnter({ onExit }) {
|
|
7085
|
-
|
|
7548
|
+
useInput7((_i, key) => {
|
|
7086
7549
|
if (key.return) onExit();
|
|
7087
7550
|
});
|
|
7088
7551
|
return null;
|
|
@@ -7093,10 +7556,10 @@ function StepFrame({
|
|
|
7093
7556
|
total,
|
|
7094
7557
|
children
|
|
7095
7558
|
}) {
|
|
7096
|
-
return /* @__PURE__ */
|
|
7559
|
+
return /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React21.createElement(Box17, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1, flexDirection: "column" }, children));
|
|
7097
7560
|
}
|
|
7098
7561
|
function SummaryLine({ label, value }) {
|
|
7099
|
-
return /* @__PURE__ */
|
|
7562
|
+
return /* @__PURE__ */ React21.createElement(Box17, null, /* @__PURE__ */ React21.createElement(Text17, null, label.padEnd(12)), /* @__PURE__ */ React21.createElement(Text17, { bold: true }, value));
|
|
7100
7563
|
}
|
|
7101
7564
|
function presetItems() {
|
|
7102
7565
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -7152,7 +7615,7 @@ async function setupCommand(_opts = {}) {
|
|
|
7152
7615
|
const existingKey = loadApiKey();
|
|
7153
7616
|
const existing = readConfig();
|
|
7154
7617
|
const { waitUntilExit, unmount } = render4(
|
|
7155
|
-
/* @__PURE__ */
|
|
7618
|
+
/* @__PURE__ */ React22.createElement(
|
|
7156
7619
|
Wizard,
|
|
7157
7620
|
{
|
|
7158
7621
|
existingApiKey: existingKey,
|
|
@@ -7170,9 +7633,9 @@ async function setupCommand(_opts = {}) {
|
|
|
7170
7633
|
}
|
|
7171
7634
|
|
|
7172
7635
|
// src/cli/commands/stats.ts
|
|
7173
|
-
import { existsSync as
|
|
7636
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
7174
7637
|
function statsCommand(opts) {
|
|
7175
|
-
if (!
|
|
7638
|
+
if (!existsSync5(opts.transcript)) {
|
|
7176
7639
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
7177
7640
|
process.exit(1);
|
|
7178
7641
|
}
|
|
@@ -7247,7 +7710,7 @@ program.action(async () => {
|
|
|
7247
7710
|
const defaults = resolveDefaults({});
|
|
7248
7711
|
await chatCommand({
|
|
7249
7712
|
model: defaults.model,
|
|
7250
|
-
system: DEFAULT_SYSTEM,
|
|
7713
|
+
system: applyProjectMemory(DEFAULT_SYSTEM, process.cwd()),
|
|
7251
7714
|
harvest: defaults.harvest,
|
|
7252
7715
|
branch: defaults.branch,
|
|
7253
7716
|
session: defaults.session,
|
|
@@ -7299,7 +7762,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
7299
7762
|
});
|
|
7300
7763
|
await chatCommand({
|
|
7301
7764
|
model: defaults.model,
|
|
7302
|
-
system: opts.system,
|
|
7765
|
+
system: applyProjectMemory(opts.system, process.cwd()),
|
|
7303
7766
|
transcript: opts.transcript,
|
|
7304
7767
|
harvest: defaults.harvest,
|
|
7305
7768
|
branch: defaults.branch,
|
|
@@ -7334,7 +7797,7 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
7334
7797
|
await runCommand2({
|
|
7335
7798
|
task,
|
|
7336
7799
|
model: defaults.model,
|
|
7337
|
-
system: opts.system,
|
|
7800
|
+
system: applyProjectMemory(opts.system, process.cwd()),
|
|
7338
7801
|
harvest: defaults.harvest,
|
|
7339
7802
|
branch: defaults.branch,
|
|
7340
7803
|
transcript: opts.transcript,
|