reasonix 0.4.17 → 0.4.20
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 +89 -2
- package/dist/cli/chunk-DDIKQZVD.js +445 -0
- package/dist/cli/chunk-DDIKQZVD.js.map +1 -0
- package/dist/cli/index.js +947 -282
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-HK5XLH55.js → prompt-YEJEJ3IZ.js} +2 -2
- package/dist/index.d.ts +296 -4
- package/dist/index.js +680 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-3YQRWFES.js +0 -131
- package/dist/cli/chunk-3YQRWFES.js.map +0 -1
- /package/dist/cli/{prompt-HK5XLH55.js.map → prompt-YEJEJ3IZ.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
MemoryStore,
|
|
3
4
|
PROJECT_MEMORY_FILE,
|
|
4
|
-
|
|
5
|
+
applyMemoryStack,
|
|
5
6
|
memoryEnabled,
|
|
6
|
-
readProjectMemory
|
|
7
|
-
|
|
7
|
+
readProjectMemory,
|
|
8
|
+
sanitizeMemoryName
|
|
9
|
+
} from "./chunk-DDIKQZVD.js";
|
|
8
10
|
|
|
9
11
|
// src/cli/index.ts
|
|
10
12
|
import { Command } from "commander";
|
|
@@ -575,9 +577,25 @@ function setByPath(target, path, value) {
|
|
|
575
577
|
var ToolRegistry = class {
|
|
576
578
|
_tools = /* @__PURE__ */ new Map();
|
|
577
579
|
_autoFlatten;
|
|
580
|
+
/**
|
|
581
|
+
* When true, `dispatch` refuses any tool whose `readOnly` flag isn't
|
|
582
|
+
* set (and whose `readOnlyCheck` doesn't pass on the specific args).
|
|
583
|
+
* Drives `reasonix code`'s Plan Mode — the model can still explore
|
|
584
|
+
* via read tools but its writes and non-allowlisted shell calls are
|
|
585
|
+
* bounced until the user approves a submitted plan.
|
|
586
|
+
*/
|
|
587
|
+
_planMode = false;
|
|
578
588
|
constructor(opts = {}) {
|
|
579
589
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
580
590
|
}
|
|
591
|
+
/** Enable / disable plan-mode enforcement at dispatch. */
|
|
592
|
+
setPlanMode(on) {
|
|
593
|
+
this._planMode = Boolean(on);
|
|
594
|
+
}
|
|
595
|
+
/** True when the registry is currently refusing non-readonly calls. */
|
|
596
|
+
get planMode() {
|
|
597
|
+
return this._planMode;
|
|
598
|
+
}
|
|
581
599
|
register(def) {
|
|
582
600
|
if (!def.name) throw new Error("tool requires a name");
|
|
583
601
|
const internal = { ...def };
|
|
@@ -629,16 +647,38 @@ var ToolRegistry = class {
|
|
|
629
647
|
if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
|
|
630
648
|
args = nestArguments(args);
|
|
631
649
|
}
|
|
650
|
+
if (this._planMode && !isReadOnlyCall(tool, args)) {
|
|
651
|
+
return JSON.stringify({
|
|
652
|
+
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.`
|
|
653
|
+
});
|
|
654
|
+
}
|
|
632
655
|
try {
|
|
633
656
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
634
657
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
635
658
|
} catch (err) {
|
|
659
|
+
const e = err;
|
|
660
|
+
if (typeof e.toToolResult === "function") {
|
|
661
|
+
try {
|
|
662
|
+
return JSON.stringify(e.toToolResult());
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
}
|
|
636
666
|
return JSON.stringify({
|
|
637
|
-
error: `${
|
|
667
|
+
error: `${e.name}: ${e.message}`
|
|
638
668
|
});
|
|
639
669
|
}
|
|
640
670
|
}
|
|
641
671
|
};
|
|
672
|
+
function isReadOnlyCall(tool, args) {
|
|
673
|
+
if (tool.readOnlyCheck) {
|
|
674
|
+
try {
|
|
675
|
+
return Boolean(tool.readOnlyCheck(args));
|
|
676
|
+
} catch {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return tool.readOnly === true;
|
|
681
|
+
}
|
|
642
682
|
function hasDotKey(obj) {
|
|
643
683
|
for (const k of Object.keys(obj)) {
|
|
644
684
|
if (k.includes(".")) return true;
|
|
@@ -1025,6 +1065,16 @@ var ToolCallRepair = class {
|
|
|
1025
1065
|
this.opts = opts;
|
|
1026
1066
|
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3);
|
|
1027
1067
|
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Drop the StormBreaker's sliding window of recent (name, args)
|
|
1070
|
+
* signatures. Called at the start of every user turn — a fresh user
|
|
1071
|
+
* message is a new intent, so carrying old repetition state into it
|
|
1072
|
+
* would turn a valid "try again with different input" flow into a
|
|
1073
|
+
* false-positive block.
|
|
1074
|
+
*/
|
|
1075
|
+
resetStorm() {
|
|
1076
|
+
this.storm.reset();
|
|
1077
|
+
}
|
|
1028
1078
|
process(declaredCalls, reasoningContent, content = null) {
|
|
1029
1079
|
const report = {
|
|
1030
1080
|
scavenged: 0,
|
|
@@ -1477,6 +1527,7 @@ var CacheFirstLoop = class {
|
|
|
1477
1527
|
async *step(userInput) {
|
|
1478
1528
|
this._turn++;
|
|
1479
1529
|
this.scratch.reset();
|
|
1530
|
+
this.repair.resetStorm();
|
|
1480
1531
|
this._turnAbort = new AbortController();
|
|
1481
1532
|
const signal = this._turnAbort.signal;
|
|
1482
1533
|
let pendingUser = userInput;
|
|
@@ -1700,6 +1751,16 @@ var CacheFirstLoop = class {
|
|
|
1700
1751
|
repair: report,
|
|
1701
1752
|
branch: branchSummary
|
|
1702
1753
|
};
|
|
1754
|
+
if (report.stormsBroken > 0) {
|
|
1755
|
+
const noteTail = report.notes.length ? ` \u2014 ${report.notes[report.notes.length - 1]}` : "";
|
|
1756
|
+
const allSuppressed = repairedCalls.length === 0 && toolCalls.length > 0;
|
|
1757
|
+
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`;
|
|
1758
|
+
yield {
|
|
1759
|
+
turn: this._turn,
|
|
1760
|
+
role: "warning",
|
|
1761
|
+
content: `${phrase}${noteTail}`
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1703
1764
|
if (repairedCalls.length === 0) {
|
|
1704
1765
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
1705
1766
|
return;
|
|
@@ -1949,6 +2010,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
1949
2010
|
registry.register({
|
|
1950
2011
|
name: "read_file",
|
|
1951
2012
|
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.",
|
|
2013
|
+
readOnly: true,
|
|
1952
2014
|
parameters: {
|
|
1953
2015
|
type: "object",
|
|
1954
2016
|
properties: {
|
|
@@ -1986,6 +2048,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
1986
2048
|
registry.register({
|
|
1987
2049
|
name: "list_directory",
|
|
1988
2050
|
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.",
|
|
2051
|
+
readOnly: true,
|
|
1989
2052
|
parameters: {
|
|
1990
2053
|
type: "object",
|
|
1991
2054
|
properties: {
|
|
@@ -2005,6 +2068,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2005
2068
|
registry.register({
|
|
2006
2069
|
name: "directory_tree",
|
|
2007
2070
|
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.",
|
|
2071
|
+
readOnly: true,
|
|
2008
2072
|
parameters: {
|
|
2009
2073
|
type: "object",
|
|
2010
2074
|
properties: {
|
|
@@ -2051,6 +2115,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2051
2115
|
registry.register({
|
|
2052
2116
|
name: "search_files",
|
|
2053
2117
|
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.",
|
|
2118
|
+
readOnly: true,
|
|
2054
2119
|
parameters: {
|
|
2055
2120
|
type: "object",
|
|
2056
2121
|
properties: {
|
|
@@ -2103,6 +2168,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2103
2168
|
registry.register({
|
|
2104
2169
|
name: "get_file_info",
|
|
2105
2170
|
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
2171
|
+
readOnly: true,
|
|
2106
2172
|
parameters: {
|
|
2107
2173
|
type: "object",
|
|
2108
2174
|
properties: {
|
|
@@ -2142,7 +2208,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2142
2208
|
});
|
|
2143
2209
|
registry.register({
|
|
2144
2210
|
name: "edit_file",
|
|
2145
|
-
description: "Apply a SEARCH/REPLACE edit to an existing file. `search` must match exactly (whitespace sensitive) \u2014 no regex. The match must be unique in the file; otherwise the edit is refused to avoid surprise rewrites.
|
|
2211
|
+
description: "Apply a SEARCH/REPLACE edit to an existing file. `search` must match exactly (whitespace sensitive) \u2014 no regex. The match must be unique in the file; otherwise the edit is refused to avoid surprise rewrites.",
|
|
2146
2212
|
parameters: {
|
|
2147
2213
|
type: "object",
|
|
2148
2214
|
properties: {
|
|
@@ -2259,8 +2325,175 @@ function lineDiff(a, b) {
|
|
|
2259
2325
|
return out;
|
|
2260
2326
|
}
|
|
2261
2327
|
|
|
2328
|
+
// src/tools/memory.ts
|
|
2329
|
+
function registerMemoryTools(registry, opts = {}) {
|
|
2330
|
+
const store = new MemoryStore({ homeDir: opts.homeDir, projectRoot: opts.projectRoot });
|
|
2331
|
+
const hasProject = store.hasProjectScope();
|
|
2332
|
+
registry.register({
|
|
2333
|
+
name: "remember",
|
|
2334
|
+
description: "Save a memory for future sessions. Use when the user states a preference, corrects your approach, shares a non-obvious fact about this project, or explicitly asks you to remember something. Don't remember transient task state \u2014 only things worth recalling next session. The memory is written now but won't re-load into the system prompt until the next `/new` or launch.",
|
|
2335
|
+
parameters: {
|
|
2336
|
+
type: "object",
|
|
2337
|
+
properties: {
|
|
2338
|
+
type: {
|
|
2339
|
+
type: "string",
|
|
2340
|
+
enum: ["user", "feedback", "project", "reference"],
|
|
2341
|
+
description: "'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
2342
|
+
},
|
|
2343
|
+
scope: {
|
|
2344
|
+
type: "string",
|
|
2345
|
+
enum: ["global", "project"],
|
|
2346
|
+
description: "'global' = applies across every project (preferences, tooling); 'project' = scoped to the current sandbox (decisions, local facts). Only available in `reasonix code`."
|
|
2347
|
+
},
|
|
2348
|
+
name: {
|
|
2349
|
+
type: "string",
|
|
2350
|
+
description: "filename-safe identifier, 3-40 chars, alnum + _ - . (no path separators, no leading dot)."
|
|
2351
|
+
},
|
|
2352
|
+
description: {
|
|
2353
|
+
type: "string",
|
|
2354
|
+
description: "One-line summary shown in MEMORY.md (under ~150 chars)."
|
|
2355
|
+
},
|
|
2356
|
+
content: {
|
|
2357
|
+
type: "string",
|
|
2358
|
+
description: "Full memory body in markdown. For feedback/project types, structure as: rule/fact, then **Why:** line, then **How to apply:** line."
|
|
2359
|
+
}
|
|
2360
|
+
},
|
|
2361
|
+
required: ["type", "scope", "name", "description", "content"]
|
|
2362
|
+
},
|
|
2363
|
+
fn: async (args) => {
|
|
2364
|
+
if (args.scope === "project" && !hasProject) {
|
|
2365
|
+
return JSON.stringify({
|
|
2366
|
+
error: "scope='project' is unavailable in this session (no sandbox root). Retry with scope='global', or ask the user to switch to `reasonix code` for project-scoped memory."
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
try {
|
|
2370
|
+
const path = store.write({
|
|
2371
|
+
name: args.name,
|
|
2372
|
+
type: args.type,
|
|
2373
|
+
scope: args.scope,
|
|
2374
|
+
description: args.description,
|
|
2375
|
+
body: args.content
|
|
2376
|
+
});
|
|
2377
|
+
const key = sanitizeMemoryName(args.name);
|
|
2378
|
+
return [
|
|
2379
|
+
`\u2713 REMEMBERED (${args.scope}/${key}): ${args.description}`,
|
|
2380
|
+
"",
|
|
2381
|
+
"TREAT THIS AS ESTABLISHED FACT for the rest of this session.",
|
|
2382
|
+
"The user just told you \u2014 don't re-explore the filesystem to re-derive it.",
|
|
2383
|
+
`(Saved to ${path}; pins into the system prompt on next /new or launch.)`
|
|
2384
|
+
].join("\n");
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
return JSON.stringify({ error: `remember failed: ${err.message}` });
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2390
|
+
registry.register({
|
|
2391
|
+
name: "forget",
|
|
2392
|
+
description: "Delete a memory file and remove it from MEMORY.md. Use when the user explicitly asks to forget something, or when a previously-remembered fact has become wrong. Irreversible \u2014 no tombstone.",
|
|
2393
|
+
parameters: {
|
|
2394
|
+
type: "object",
|
|
2395
|
+
properties: {
|
|
2396
|
+
name: { type: "string", description: "Memory name (the identifier used in `remember`)." },
|
|
2397
|
+
scope: { type: "string", enum: ["global", "project"] }
|
|
2398
|
+
},
|
|
2399
|
+
required: ["name", "scope"]
|
|
2400
|
+
},
|
|
2401
|
+
fn: async (args) => {
|
|
2402
|
+
if (args.scope === "project" && !hasProject) {
|
|
2403
|
+
return JSON.stringify({
|
|
2404
|
+
error: "scope='project' is unavailable in this session (no sandbox root)."
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
try {
|
|
2408
|
+
const existed = store.delete(args.scope, args.name);
|
|
2409
|
+
return existed ? `forgot (${args.scope}/${sanitizeMemoryName(args.name)}). Re-load on next /new or launch.` : `no such memory: ${args.scope}/${args.name} (nothing to forget).`;
|
|
2410
|
+
} catch (err) {
|
|
2411
|
+
return JSON.stringify({ error: `forget failed: ${err.message}` });
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
registry.register({
|
|
2416
|
+
name: "recall_memory",
|
|
2417
|
+
description: "Read the full body of a memory file when its MEMORY.md one-liner (already in the system prompt) isn't enough detail. Most of the time the index suffices \u2014 only call this when the user's question genuinely requires the full context.",
|
|
2418
|
+
readOnly: true,
|
|
2419
|
+
parameters: {
|
|
2420
|
+
type: "object",
|
|
2421
|
+
properties: {
|
|
2422
|
+
name: { type: "string" },
|
|
2423
|
+
scope: { type: "string", enum: ["global", "project"] }
|
|
2424
|
+
},
|
|
2425
|
+
required: ["name", "scope"]
|
|
2426
|
+
},
|
|
2427
|
+
fn: async (args) => {
|
|
2428
|
+
if (args.scope === "project" && !hasProject) {
|
|
2429
|
+
return JSON.stringify({
|
|
2430
|
+
error: "scope='project' is unavailable in this session (no sandbox root)."
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
try {
|
|
2434
|
+
const entry = store.read(args.scope, args.name);
|
|
2435
|
+
return [
|
|
2436
|
+
`# ${entry.name} (${entry.scope}/${entry.type}, created ${entry.createdAt || "?"})`,
|
|
2437
|
+
entry.description ? `> ${entry.description}` : "",
|
|
2438
|
+
"",
|
|
2439
|
+
entry.body
|
|
2440
|
+
].filter(Boolean).join("\n");
|
|
2441
|
+
} catch (err) {
|
|
2442
|
+
return JSON.stringify({ error: `recall failed: ${err.message}` });
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
return registry;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// src/tools/plan.ts
|
|
2450
|
+
var PlanProposedError = class extends Error {
|
|
2451
|
+
plan;
|
|
2452
|
+
constructor(plan) {
|
|
2453
|
+
super(
|
|
2454
|
+
"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."
|
|
2455
|
+
);
|
|
2456
|
+
this.name = "PlanProposedError";
|
|
2457
|
+
this.plan = plan;
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Structured tool-result shape. Consumed by the TUI to extract the
|
|
2461
|
+
* plan without regex-scraping the error message.
|
|
2462
|
+
*/
|
|
2463
|
+
toToolResult() {
|
|
2464
|
+
return { error: `${this.name}: ${this.message}`, plan: this.plan };
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
function registerPlanTool(registry, opts = {}) {
|
|
2468
|
+
registry.register({
|
|
2469
|
+
name: "submit_plan",
|
|
2470
|
+
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.",
|
|
2471
|
+
readOnly: true,
|
|
2472
|
+
parameters: {
|
|
2473
|
+
type: "object",
|
|
2474
|
+
properties: {
|
|
2475
|
+
plan: {
|
|
2476
|
+
type: "string",
|
|
2477
|
+
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."
|
|
2478
|
+
}
|
|
2479
|
+
},
|
|
2480
|
+
required: ["plan"]
|
|
2481
|
+
},
|
|
2482
|
+
fn: async (args) => {
|
|
2483
|
+
const plan = (args?.plan ?? "").trim();
|
|
2484
|
+
if (!plan) {
|
|
2485
|
+
throw new Error("submit_plan: empty plan \u2014 write a markdown plan and try again.");
|
|
2486
|
+
}
|
|
2487
|
+
opts.onPlanSubmitted?.(plan);
|
|
2488
|
+
throw new PlanProposedError(plan);
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
return registry;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2262
2494
|
// src/tools/shell.ts
|
|
2263
2495
|
import { spawn } from "child_process";
|
|
2496
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
2264
2497
|
import * as pathMod2 from "path";
|
|
2265
2498
|
var DEFAULT_TIMEOUT_SEC = 60;
|
|
2266
2499
|
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
@@ -2378,10 +2611,12 @@ async function runCommand(cmd, opts) {
|
|
|
2378
2611
|
windowsHide: true,
|
|
2379
2612
|
env: process.env
|
|
2380
2613
|
};
|
|
2614
|
+
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
2615
|
+
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
2381
2616
|
return await new Promise((resolve6, reject) => {
|
|
2382
2617
|
let child;
|
|
2383
2618
|
try {
|
|
2384
|
-
child = spawn(
|
|
2619
|
+
child = spawn(bin, args, effectiveSpawnOpts);
|
|
2385
2620
|
} catch (err) {
|
|
2386
2621
|
reject(err);
|
|
2387
2622
|
return;
|
|
@@ -2415,6 +2650,59 @@ async function runCommand(cmd, opts) {
|
|
|
2415
2650
|
});
|
|
2416
2651
|
});
|
|
2417
2652
|
}
|
|
2653
|
+
function resolveExecutable(cmd, opts = {}) {
|
|
2654
|
+
const platform = opts.platform ?? process.platform;
|
|
2655
|
+
if (platform !== "win32") return cmd;
|
|
2656
|
+
if (!cmd) return cmd;
|
|
2657
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod2.isAbsolute(cmd)) return cmd;
|
|
2658
|
+
if (pathMod2.extname(cmd)) return cmd;
|
|
2659
|
+
const env = opts.env ?? process.env;
|
|
2660
|
+
const pathExt = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
2661
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod2.delimiter);
|
|
2662
|
+
const pathDirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
2663
|
+
const isFile = opts.isFile ?? defaultIsFile;
|
|
2664
|
+
for (const dir of pathDirs) {
|
|
2665
|
+
for (const ext of pathExt) {
|
|
2666
|
+
const full = pathMod2.win32.join(dir, cmd + ext);
|
|
2667
|
+
if (isFile(full)) return full;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
return cmd;
|
|
2671
|
+
}
|
|
2672
|
+
function defaultIsFile(full) {
|
|
2673
|
+
try {
|
|
2674
|
+
return existsSync2(full) && statSync2(full).isFile();
|
|
2675
|
+
} catch {
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
function prepareSpawn(argv, opts = {}) {
|
|
2680
|
+
const head = argv[0] ?? "";
|
|
2681
|
+
const tail = argv.slice(1);
|
|
2682
|
+
const platform = opts.platform ?? process.platform;
|
|
2683
|
+
const resolved = resolveExecutable(head, opts);
|
|
2684
|
+
if (platform !== "win32") {
|
|
2685
|
+
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2686
|
+
}
|
|
2687
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
2688
|
+
const cmdline = [resolved, ...tail].map(quoteForCmdExe).join(" ");
|
|
2689
|
+
return {
|
|
2690
|
+
bin: "cmd.exe",
|
|
2691
|
+
args: ["/d", "/s", "/c", cmdline],
|
|
2692
|
+
// windowsVerbatimArguments prevents Node from re-quoting the /c
|
|
2693
|
+
// payload — we've already composed an exact cmd.exe command
|
|
2694
|
+
// line. Without this Node wraps our already-quoted string in
|
|
2695
|
+
// another round of quotes and cmd.exe can't parse it.
|
|
2696
|
+
spawnOverrides: { windowsVerbatimArguments: true }
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2700
|
+
}
|
|
2701
|
+
function quoteForCmdExe(arg) {
|
|
2702
|
+
if (arg === "") return '""';
|
|
2703
|
+
if (!/[\s"&|<>^%(),;!]/.test(arg)) return arg;
|
|
2704
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
2705
|
+
}
|
|
2418
2706
|
var NeedsConfirmationError = class extends Error {
|
|
2419
2707
|
command;
|
|
2420
2708
|
constructor(command) {
|
|
@@ -2433,13 +2721,23 @@ function registerShellTools(registry, opts) {
|
|
|
2433
2721
|
const allowAll = opts.allowAll ?? false;
|
|
2434
2722
|
registry.register({
|
|
2435
2723
|
name: "run_command",
|
|
2436
|
-
description: "Run a shell command in the project root and return its combined stdout+stderr.
|
|
2724
|
+
description: "Run a shell command in the project root and return its combined stdout+stderr. Common read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
2725
|
+
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
2726
|
+
// cargo check, ls, grep …) so the model can actually investigate
|
|
2727
|
+
// during planning. Anything that would otherwise trigger a
|
|
2728
|
+
// confirmation prompt is treated as "not read-only" and bounced.
|
|
2729
|
+
readOnlyCheck: (args) => {
|
|
2730
|
+
if (allowAll) return true;
|
|
2731
|
+
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
2732
|
+
if (!cmd) return false;
|
|
2733
|
+
return isAllowed(cmd, extraAllowed);
|
|
2734
|
+
},
|
|
2437
2735
|
parameters: {
|
|
2438
2736
|
type: "object",
|
|
2439
2737
|
properties: {
|
|
2440
2738
|
command: {
|
|
2441
2739
|
type: "string",
|
|
2442
|
-
description: "Full command line
|
|
2740
|
+
description: "Full command line. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
|
|
2443
2741
|
},
|
|
2444
2742
|
timeoutSec: {
|
|
2445
2743
|
type: "integer",
|
|
@@ -2600,6 +2898,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
2600
2898
|
registry.register({
|
|
2601
2899
|
name: "web_search",
|
|
2602
2900
|
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.",
|
|
2901
|
+
readOnly: true,
|
|
2603
2902
|
parameters: {
|
|
2604
2903
|
type: "object",
|
|
2605
2904
|
properties: {
|
|
@@ -2622,6 +2921,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
2622
2921
|
registry.register({
|
|
2623
2922
|
name: "web_fetch",
|
|
2624
2923
|
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.",
|
|
2924
|
+
readOnly: true,
|
|
2625
2925
|
parameters: {
|
|
2626
2926
|
type: "object",
|
|
2627
2927
|
properties: {
|
|
@@ -3817,7 +4117,7 @@ async function trySection(load) {
|
|
|
3817
4117
|
}
|
|
3818
4118
|
|
|
3819
4119
|
// src/code/edit-blocks.ts
|
|
3820
|
-
import { existsSync as
|
|
4120
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3821
4121
|
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
3822
4122
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3823
4123
|
function parseEditBlocks(text) {
|
|
@@ -3846,7 +4146,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3846
4146
|
};
|
|
3847
4147
|
}
|
|
3848
4148
|
const searchEmpty = block.search.length === 0;
|
|
3849
|
-
const exists =
|
|
4149
|
+
const exists = existsSync3(absTarget);
|
|
3850
4150
|
try {
|
|
3851
4151
|
if (!exists) {
|
|
3852
4152
|
if (!searchEmpty) {
|
|
@@ -3894,7 +4194,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
3894
4194
|
if (seen.has(b.path)) continue;
|
|
3895
4195
|
seen.add(b.path);
|
|
3896
4196
|
const abs = resolve4(absRoot, b.path);
|
|
3897
|
-
if (!
|
|
4197
|
+
if (!existsSync3(abs)) {
|
|
3898
4198
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3899
4199
|
continue;
|
|
3900
4200
|
}
|
|
@@ -3919,7 +4219,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
3919
4219
|
}
|
|
3920
4220
|
try {
|
|
3921
4221
|
if (snap.prevContent === null) {
|
|
3922
|
-
if (
|
|
4222
|
+
if (existsSync3(abs)) unlinkSync2(abs);
|
|
3923
4223
|
return {
|
|
3924
4224
|
path: snap.path,
|
|
3925
4225
|
status: "applied",
|
|
@@ -3942,16 +4242,16 @@ function sep() {
|
|
|
3942
4242
|
}
|
|
3943
4243
|
|
|
3944
4244
|
// src/index.ts
|
|
3945
|
-
var VERSION = "0.4.
|
|
4245
|
+
var VERSION = "0.4.20";
|
|
3946
4246
|
|
|
3947
4247
|
// src/cli/commands/chat.tsx
|
|
3948
|
-
import { existsSync as
|
|
4248
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
3949
4249
|
import { render } from "ink";
|
|
3950
|
-
import
|
|
4250
|
+
import React15, { useState as useState7 } from "react";
|
|
3951
4251
|
|
|
3952
4252
|
// src/cli/ui/App.tsx
|
|
3953
|
-
import { Box as
|
|
3954
|
-
import
|
|
4253
|
+
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
4254
|
+
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
3955
4255
|
|
|
3956
4256
|
// src/cli/ui/EventLog.tsx
|
|
3957
4257
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -4425,9 +4725,177 @@ function truncate2(s, max) {
|
|
|
4425
4725
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
4426
4726
|
}
|
|
4427
4727
|
|
|
4428
|
-
// src/cli/ui/
|
|
4728
|
+
// src/cli/ui/PlanConfirm.tsx
|
|
4729
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
4730
|
+
import React6 from "react";
|
|
4731
|
+
|
|
4732
|
+
// src/cli/ui/Select.tsx
|
|
4429
4733
|
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
4430
|
-
import React5, {
|
|
4734
|
+
import React5, { useState as useState2 } from "react";
|
|
4735
|
+
function SingleSelect({
|
|
4736
|
+
items,
|
|
4737
|
+
initialValue,
|
|
4738
|
+
onSubmit,
|
|
4739
|
+
onCancel
|
|
4740
|
+
}) {
|
|
4741
|
+
const initialIndex = Math.max(
|
|
4742
|
+
0,
|
|
4743
|
+
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4744
|
+
);
|
|
4745
|
+
const [index, setIndex] = useState2(initialIndex === -1 ? 0 : initialIndex);
|
|
4746
|
+
useInput((_input, key) => {
|
|
4747
|
+
if (key.upArrow) {
|
|
4748
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4749
|
+
} else if (key.downArrow) {
|
|
4750
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4751
|
+
} else if (key.return) {
|
|
4752
|
+
const chosen = items[index];
|
|
4753
|
+
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4754
|
+
} else if (key.escape && onCancel) {
|
|
4755
|
+
onCancel();
|
|
4756
|
+
}
|
|
4757
|
+
});
|
|
4758
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React5.createElement(
|
|
4759
|
+
SelectRow,
|
|
4760
|
+
{
|
|
4761
|
+
key: item.value,
|
|
4762
|
+
item,
|
|
4763
|
+
active: i === index,
|
|
4764
|
+
marker: i === index ? "\u25B8" : " "
|
|
4765
|
+
}
|
|
4766
|
+
)));
|
|
4767
|
+
}
|
|
4768
|
+
function MultiSelect({
|
|
4769
|
+
items,
|
|
4770
|
+
initialSelected = [],
|
|
4771
|
+
onSubmit,
|
|
4772
|
+
onCancel,
|
|
4773
|
+
footer
|
|
4774
|
+
}) {
|
|
4775
|
+
const [index, setIndex] = useState2(() => {
|
|
4776
|
+
const first = items.findIndex((i) => !i.disabled);
|
|
4777
|
+
return first === -1 ? 0 : first;
|
|
4778
|
+
});
|
|
4779
|
+
const [selected, setSelected] = useState2(new Set(initialSelected));
|
|
4780
|
+
useInput((input, key) => {
|
|
4781
|
+
if (key.upArrow) {
|
|
4782
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4783
|
+
} else if (key.downArrow) {
|
|
4784
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4785
|
+
} else if (input === " ") {
|
|
4786
|
+
const item = items[index];
|
|
4787
|
+
if (!item || item.disabled) return;
|
|
4788
|
+
setSelected((prev) => {
|
|
4789
|
+
const next = new Set(prev);
|
|
4790
|
+
if (next.has(item.value)) next.delete(item.value);
|
|
4791
|
+
else next.add(item.value);
|
|
4792
|
+
return next;
|
|
4793
|
+
});
|
|
4794
|
+
} else if (key.return) {
|
|
4795
|
+
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4796
|
+
onSubmit(ordered);
|
|
4797
|
+
} else if (key.escape && onCancel) {
|
|
4798
|
+
onCancel();
|
|
4799
|
+
}
|
|
4800
|
+
});
|
|
4801
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, items.map((item, i) => {
|
|
4802
|
+
const checked = selected.has(item.value);
|
|
4803
|
+
const marker = checked ? "[x]" : "[ ]";
|
|
4804
|
+
return /* @__PURE__ */ React5.createElement(
|
|
4805
|
+
SelectRow,
|
|
4806
|
+
{
|
|
4807
|
+
key: item.value,
|
|
4808
|
+
item,
|
|
4809
|
+
active: i === index,
|
|
4810
|
+
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4811
|
+
}
|
|
4812
|
+
);
|
|
4813
|
+
}), footer ? /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, footer)) : null);
|
|
4814
|
+
}
|
|
4815
|
+
function SelectRow({
|
|
4816
|
+
item,
|
|
4817
|
+
active,
|
|
4818
|
+
marker
|
|
4819
|
+
}) {
|
|
4820
|
+
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4821
|
+
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);
|
|
4822
|
+
}
|
|
4823
|
+
function findNextEnabled(items, from, step) {
|
|
4824
|
+
if (items.length === 0) return 0;
|
|
4825
|
+
let i = from;
|
|
4826
|
+
for (let tries = 0; tries < items.length; tries++) {
|
|
4827
|
+
i = (i + step + items.length) % items.length;
|
|
4828
|
+
if (!items[i]?.disabled) return i;
|
|
4829
|
+
}
|
|
4830
|
+
return from;
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4833
|
+
// src/cli/ui/PlanConfirm.tsx
|
|
4834
|
+
var DEFAULT_MAX_RENDERED = 2400;
|
|
4835
|
+
function PlanConfirm({ plan, onChoose, maxRenderedChars }) {
|
|
4836
|
+
const cap = maxRenderedChars ?? DEFAULT_MAX_RENDERED;
|
|
4837
|
+
const tooLong = plan.length > cap;
|
|
4838
|
+
const visible = tooLong ? `${plan.slice(0, cap)}
|
|
4839
|
+
|
|
4840
|
+
\u2026 (${plan.length - cap} chars truncated \u2014 use /tool to view the full proposal)` : plan;
|
|
4841
|
+
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
|
|
4842
|
+
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(
|
|
4843
|
+
SingleSelect,
|
|
4844
|
+
{
|
|
4845
|
+
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
4846
|
+
items: [
|
|
4847
|
+
{
|
|
4848
|
+
value: "approve",
|
|
4849
|
+
label: "Approve and implement",
|
|
4850
|
+
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)."
|
|
4851
|
+
},
|
|
4852
|
+
{
|
|
4853
|
+
value: "refine",
|
|
4854
|
+
label: "Refine / answer questions",
|
|
4855
|
+
hint: "Stay in plan mode. Write answers, modifications, or critiques; the model revises and re-submits."
|
|
4856
|
+
},
|
|
4857
|
+
{
|
|
4858
|
+
value: "cancel",
|
|
4859
|
+
label: "Cancel",
|
|
4860
|
+
hint: "Exit plan mode. Drop the plan; the model won't implement it."
|
|
4861
|
+
}
|
|
4862
|
+
],
|
|
4863
|
+
onSubmit: (v) => onChoose(v)
|
|
4864
|
+
}
|
|
4865
|
+
)));
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
// src/cli/ui/PlanRefineInput.tsx
|
|
4869
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
4870
|
+
import React7, { useState as useState3 } from "react";
|
|
4871
|
+
function PlanRefineInput({ mode, onSubmit, onCancel }) {
|
|
4872
|
+
const [value, setValue] = useState3("");
|
|
4873
|
+
useInput2((input, key) => {
|
|
4874
|
+
if (key.escape) {
|
|
4875
|
+
onCancel();
|
|
4876
|
+
return;
|
|
4877
|
+
}
|
|
4878
|
+
if (key.return) {
|
|
4879
|
+
onSubmit(value.trim());
|
|
4880
|
+
return;
|
|
4881
|
+
}
|
|
4882
|
+
if (key.backspace || key.delete) {
|
|
4883
|
+
setValue((v) => v.slice(0, -1));
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
if (input && !key.ctrl && !key.meta) {
|
|
4887
|
+
setValue((v) => v + input);
|
|
4888
|
+
}
|
|
4889
|
+
});
|
|
4890
|
+
const title = mode === "approve" ? "\u25B8 approving \u2014 any last instructions or answers to open questions?" : "\u25B8 refining \u2014 what should the model change?";
|
|
4891
|
+
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.";
|
|
4892
|
+
const blankHint = mode === "approve" ? " (Enter with blank = approve without extra instructions.)" : " (Enter with blank = ask the model to list concrete questions.)";
|
|
4893
|
+
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"))));
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4896
|
+
// src/cli/ui/PromptInput.tsx
|
|
4897
|
+
import { Box as Box7, Text as Text7, useInput as useInput3 } from "ink";
|
|
4898
|
+
import React8, { useRef, useState as useState4 } from "react";
|
|
4431
4899
|
|
|
4432
4900
|
// src/cli/ui/multiline-keys.ts
|
|
4433
4901
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
@@ -4542,7 +5010,7 @@ function PromptInput({
|
|
|
4542
5010
|
disabled,
|
|
4543
5011
|
placeholder
|
|
4544
5012
|
}) {
|
|
4545
|
-
const [cursor, setCursor] =
|
|
5013
|
+
const [cursor, setCursor] = useState4(value.length);
|
|
4546
5014
|
const lastLocalValueRef = useRef(value);
|
|
4547
5015
|
if (value !== lastLocalValueRef.current) {
|
|
4548
5016
|
lastLocalValueRef.current = value;
|
|
@@ -4552,7 +5020,7 @@ function PromptInput({
|
|
|
4552
5020
|
}
|
|
4553
5021
|
const tick = useTick();
|
|
4554
5022
|
const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
|
|
4555
|
-
|
|
5023
|
+
useInput3(
|
|
4556
5024
|
(input, key) => {
|
|
4557
5025
|
const ke = {
|
|
4558
5026
|
input,
|
|
@@ -4587,13 +5055,13 @@ function PromptInput({
|
|
|
4587
5055
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
4588
5056
|
const borderColor = disabled ? "gray" : "cyan";
|
|
4589
5057
|
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
4590
|
-
return /* @__PURE__ */
|
|
5058
|
+
return /* @__PURE__ */ React8.createElement(Box7, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
4591
5059
|
const isFirst = i === 0;
|
|
4592
5060
|
const showPlaceholder = isFirst && value.length === 0;
|
|
4593
5061
|
const isCursorLine = i === cursorLine;
|
|
4594
5062
|
return (
|
|
4595
5063
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
4596
|
-
/* @__PURE__ */
|
|
5064
|
+
/* @__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(
|
|
4597
5065
|
LineWithCursor,
|
|
4598
5066
|
{
|
|
4599
5067
|
line,
|
|
@@ -4601,7 +5069,7 @@ function PromptInput({
|
|
|
4601
5069
|
showCursor,
|
|
4602
5070
|
borderColor
|
|
4603
5071
|
}
|
|
4604
|
-
) : /* @__PURE__ */
|
|
5072
|
+
) : /* @__PURE__ */ React8.createElement(Text7, null, line))
|
|
4605
5073
|
);
|
|
4606
5074
|
}));
|
|
4607
5075
|
}
|
|
@@ -4615,119 +5083,16 @@ function LineWithCursor({
|
|
|
4615
5083
|
const atCursor = line.slice(col, col + 1);
|
|
4616
5084
|
const after = line.slice(col + 1);
|
|
4617
5085
|
if (atCursor.length === 0) {
|
|
4618
|
-
return /* @__PURE__ */
|
|
4619
|
-
}
|
|
4620
|
-
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text4, null, before), /* @__PURE__ */ React5.createElement(Text4, { inverse: showCursor }, atCursor), /* @__PURE__ */ React5.createElement(Text4, null, after));
|
|
4621
|
-
}
|
|
4622
|
-
|
|
4623
|
-
// src/cli/ui/ShellConfirm.tsx
|
|
4624
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
4625
|
-
import React7 from "react";
|
|
4626
|
-
|
|
4627
|
-
// src/cli/ui/Select.tsx
|
|
4628
|
-
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
4629
|
-
import React6, { useState as useState3 } from "react";
|
|
4630
|
-
function SingleSelect({
|
|
4631
|
-
items,
|
|
4632
|
-
initialValue,
|
|
4633
|
-
onSubmit,
|
|
4634
|
-
onCancel
|
|
4635
|
-
}) {
|
|
4636
|
-
const initialIndex = Math.max(
|
|
4637
|
-
0,
|
|
4638
|
-
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4639
|
-
);
|
|
4640
|
-
const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
|
|
4641
|
-
useInput2((_input, key) => {
|
|
4642
|
-
if (key.upArrow) {
|
|
4643
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4644
|
-
} else if (key.downArrow) {
|
|
4645
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4646
|
-
} else if (key.return) {
|
|
4647
|
-
const chosen = items[index];
|
|
4648
|
-
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4649
|
-
} else if (key.escape && onCancel) {
|
|
4650
|
-
onCancel();
|
|
4651
|
-
}
|
|
4652
|
-
});
|
|
4653
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React6.createElement(
|
|
4654
|
-
SelectRow,
|
|
4655
|
-
{
|
|
4656
|
-
key: item.value,
|
|
4657
|
-
item,
|
|
4658
|
-
active: i === index,
|
|
4659
|
-
marker: i === index ? "\u25B8" : " "
|
|
4660
|
-
}
|
|
4661
|
-
)));
|
|
4662
|
-
}
|
|
4663
|
-
function MultiSelect({
|
|
4664
|
-
items,
|
|
4665
|
-
initialSelected = [],
|
|
4666
|
-
onSubmit,
|
|
4667
|
-
onCancel,
|
|
4668
|
-
footer
|
|
4669
|
-
}) {
|
|
4670
|
-
const [index, setIndex] = useState3(() => {
|
|
4671
|
-
const first = items.findIndex((i) => !i.disabled);
|
|
4672
|
-
return first === -1 ? 0 : first;
|
|
4673
|
-
});
|
|
4674
|
-
const [selected, setSelected] = useState3(new Set(initialSelected));
|
|
4675
|
-
useInput2((input, key) => {
|
|
4676
|
-
if (key.upArrow) {
|
|
4677
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4678
|
-
} else if (key.downArrow) {
|
|
4679
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4680
|
-
} else if (input === " ") {
|
|
4681
|
-
const item = items[index];
|
|
4682
|
-
if (!item || item.disabled) return;
|
|
4683
|
-
setSelected((prev) => {
|
|
4684
|
-
const next = new Set(prev);
|
|
4685
|
-
if (next.has(item.value)) next.delete(item.value);
|
|
4686
|
-
else next.add(item.value);
|
|
4687
|
-
return next;
|
|
4688
|
-
});
|
|
4689
|
-
} else if (key.return) {
|
|
4690
|
-
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4691
|
-
onSubmit(ordered);
|
|
4692
|
-
} else if (key.escape && onCancel) {
|
|
4693
|
-
onCancel();
|
|
4694
|
-
}
|
|
4695
|
-
});
|
|
4696
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => {
|
|
4697
|
-
const checked = selected.has(item.value);
|
|
4698
|
-
const marker = checked ? "[x]" : "[ ]";
|
|
4699
|
-
return /* @__PURE__ */ React6.createElement(
|
|
4700
|
-
SelectRow,
|
|
4701
|
-
{
|
|
4702
|
-
key: item.value,
|
|
4703
|
-
item,
|
|
4704
|
-
active: i === index,
|
|
4705
|
-
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4706
|
-
}
|
|
4707
|
-
);
|
|
4708
|
-
}), footer ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, footer)) : null);
|
|
4709
|
-
}
|
|
4710
|
-
function SelectRow({
|
|
4711
|
-
item,
|
|
4712
|
-
active,
|
|
4713
|
-
marker
|
|
4714
|
-
}) {
|
|
4715
|
-
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4716
|
-
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);
|
|
4717
|
-
}
|
|
4718
|
-
function findNextEnabled(items, from, step) {
|
|
4719
|
-
if (items.length === 0) return 0;
|
|
4720
|
-
let i = from;
|
|
4721
|
-
for (let tries = 0; tries < items.length; tries++) {
|
|
4722
|
-
i = (i + step + items.length) % items.length;
|
|
4723
|
-
if (!items[i]?.disabled) return i;
|
|
5086
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, null, before), /* @__PURE__ */ React8.createElement(Text7, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
4724
5087
|
}
|
|
4725
|
-
return
|
|
5088
|
+
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));
|
|
4726
5089
|
}
|
|
4727
5090
|
|
|
4728
5091
|
// src/cli/ui/ShellConfirm.tsx
|
|
5092
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
5093
|
+
import React9 from "react";
|
|
4729
5094
|
function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
4730
|
-
return /* @__PURE__ */
|
|
5095
|
+
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(
|
|
4731
5096
|
SingleSelect,
|
|
4732
5097
|
{
|
|
4733
5098
|
initialValue: "run_once",
|
|
@@ -4782,15 +5147,15 @@ function derivePrefix(command) {
|
|
|
4782
5147
|
}
|
|
4783
5148
|
|
|
4784
5149
|
// src/cli/ui/SlashSuggestions.tsx
|
|
4785
|
-
import { Box as
|
|
4786
|
-
import
|
|
5150
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
5151
|
+
import React10 from "react";
|
|
4787
5152
|
function SlashSuggestions({
|
|
4788
5153
|
matches,
|
|
4789
5154
|
selectedIndex
|
|
4790
5155
|
}) {
|
|
4791
5156
|
if (matches === null) return null;
|
|
4792
5157
|
if (matches.length === 0) {
|
|
4793
|
-
return /* @__PURE__ */
|
|
5158
|
+
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"));
|
|
4794
5159
|
}
|
|
4795
5160
|
const MAX = 8;
|
|
4796
5161
|
const total = matches.length;
|
|
@@ -4798,27 +5163,28 @@ function SlashSuggestions({
|
|
|
4798
5163
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
4799
5164
|
const hiddenAbove = windowStart;
|
|
4800
5165
|
const hiddenBelow = total - windowStart - shown.length;
|
|
4801
|
-
return /* @__PURE__ */
|
|
5166
|
+
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"));
|
|
4802
5167
|
}
|
|
4803
5168
|
function SuggestionRow({ spec, isSelected }) {
|
|
4804
5169
|
const marker = isSelected ? "\u25B8" : " ";
|
|
4805
5170
|
const name = `/${spec.cmd}`;
|
|
4806
5171
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
4807
5172
|
if (isSelected) {
|
|
4808
|
-
return /* @__PURE__ */
|
|
5173
|
+
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));
|
|
4809
5174
|
}
|
|
4810
|
-
return /* @__PURE__ */
|
|
5175
|
+
return /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
4811
5176
|
}
|
|
4812
5177
|
|
|
4813
5178
|
// src/cli/ui/StatsPanel.tsx
|
|
4814
|
-
import { Box as
|
|
4815
|
-
import
|
|
5179
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
5180
|
+
import React11 from "react";
|
|
4816
5181
|
function StatsPanel({
|
|
4817
5182
|
summary,
|
|
4818
5183
|
model,
|
|
4819
5184
|
prefixHash,
|
|
4820
5185
|
harvestOn,
|
|
4821
5186
|
branchBudget,
|
|
5187
|
+
planMode,
|
|
4822
5188
|
balance
|
|
4823
5189
|
}) {
|
|
4824
5190
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
@@ -4827,7 +5193,7 @@ function StatsPanel({
|
|
|
4827
5193
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4828
5194
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
4829
5195
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
4830
|
-
return /* @__PURE__ */
|
|
5196
|
+
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));
|
|
4831
5197
|
}
|
|
4832
5198
|
function formatTokens(n) {
|
|
4833
5199
|
if (n < 1e3) return String(n);
|
|
@@ -4850,7 +5216,11 @@ var SLASH_COMMANDS = [
|
|
|
4850
5216
|
{ cmd: "branch", argsHint: "<N|off>", summary: "run N parallel samples per turn (N>=2)" },
|
|
4851
5217
|
{ cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
|
|
4852
5218
|
{ cmd: "tool", argsHint: "[N]", summary: "dump full output of the Nth tool call (1=latest)" },
|
|
4853
|
-
{
|
|
5219
|
+
{
|
|
5220
|
+
cmd: "memory",
|
|
5221
|
+
argsHint: "[list|show <name>|forget <name>|clear <scope> confirm]",
|
|
5222
|
+
summary: "show / manage pinned memory (REASONIX.md + ~/.reasonix/memory)"
|
|
5223
|
+
},
|
|
4854
5224
|
{ cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
|
|
4855
5225
|
{ cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
|
|
4856
5226
|
{ cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
|
|
@@ -4869,6 +5239,17 @@ var SLASH_COMMANDS = [
|
|
|
4869
5239
|
argsHint: '"msg"',
|
|
4870
5240
|
summary: "git add -A && git commit -m ...",
|
|
4871
5241
|
contextual: "code"
|
|
5242
|
+
},
|
|
5243
|
+
{
|
|
5244
|
+
cmd: "plan",
|
|
5245
|
+
argsHint: "[on|off]",
|
|
5246
|
+
summary: "toggle read-only plan mode (writes bounced until submit_plan + approval)",
|
|
5247
|
+
contextual: "code"
|
|
5248
|
+
},
|
|
5249
|
+
{
|
|
5250
|
+
cmd: "apply-plan",
|
|
5251
|
+
summary: "force-approve a pending / in-text plan (fallback if picker was missed)",
|
|
5252
|
+
contextual: "code"
|
|
4872
5253
|
}
|
|
4873
5254
|
];
|
|
4874
5255
|
function suggestSlashCommands(prefix, codeMode = false) {
|
|
@@ -4919,12 +5300,15 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
4919
5300
|
" /compact [cap] shrink large tool results in history (default 4k/result)",
|
|
4920
5301
|
" /think dump the most recent turn's full R1 reasoning (reasoner only)",
|
|
4921
5302
|
" /tool [N] list tool calls (or dump full output of #N, 1=most recent)",
|
|
4922
|
-
" /memory
|
|
5303
|
+
" /memory [sub] show pinned memory (REASONIX.md + ~/.reasonix/memory).",
|
|
5304
|
+
" subs: list | show <name> | forget <name> | clear <scope> confirm",
|
|
4923
5305
|
" /retry truncate & resend your last message (fresh sample from the model)",
|
|
4924
5306
|
" /apply (code mode) commit the pending edit blocks to disk",
|
|
4925
5307
|
" /discard (code mode) drop pending edits without writing",
|
|
4926
5308
|
" /undo (code mode) roll back the last applied edit batch",
|
|
4927
5309
|
' /commit "msg" (code mode) git add -A && git commit -m "msg"',
|
|
5310
|
+
" /plan [on|off] (code mode) toggle read-only plan mode; writes gated behind submit_plan + your approval",
|
|
5311
|
+
" /apply-plan (code mode) force-approve pending/in-text plan (fallback)",
|
|
4928
5312
|
" /sessions list saved sessions (current is marked with \u25B8)",
|
|
4929
5313
|
" /forget delete the current session from disk",
|
|
4930
5314
|
" /new start fresh: drop all context + clear scrollback",
|
|
@@ -5002,43 +5386,7 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
5002
5386
|
};
|
|
5003
5387
|
}
|
|
5004
5388
|
case "memory": {
|
|
5005
|
-
|
|
5006
|
-
return {
|
|
5007
|
-
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."
|
|
5008
|
-
};
|
|
5009
|
-
}
|
|
5010
|
-
if (!ctx.memoryRoot) {
|
|
5011
|
-
return {
|
|
5012
|
-
info: "no project root on this session \u2014 `/memory` needs a working directory to resolve REASONIX.md from."
|
|
5013
|
-
};
|
|
5014
|
-
}
|
|
5015
|
-
const mem = readProjectMemory(ctx.memoryRoot);
|
|
5016
|
-
if (!mem) {
|
|
5017
|
-
return {
|
|
5018
|
-
info: [
|
|
5019
|
-
`no ${PROJECT_MEMORY_FILE} in ${ctx.memoryRoot}.`,
|
|
5020
|
-
"",
|
|
5021
|
-
"Project memory is an optional file you pin notes into \u2014 project conventions,",
|
|
5022
|
-
"things the model keeps forgetting, domain glossary, setup gotchas. When present,",
|
|
5023
|
-
"its contents are appended to the system prompt (the immutable-prefix region)",
|
|
5024
|
-
"so every turn sees it without eating per-turn context, and the prefix cache stays",
|
|
5025
|
-
"warm as long as the file is stable.",
|
|
5026
|
-
"",
|
|
5027
|
-
`Create it with: echo "# Project notes for Reasonix" > ${PROJECT_MEMORY_FILE}`,
|
|
5028
|
-
"Re-launch (or `/new`) to pick up changes \u2014 the prefix is hashed at session start."
|
|
5029
|
-
].join("\n")
|
|
5030
|
-
};
|
|
5031
|
-
}
|
|
5032
|
-
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)`;
|
|
5033
|
-
return {
|
|
5034
|
-
info: [
|
|
5035
|
-
header,
|
|
5036
|
-
"",
|
|
5037
|
-
mem.content,
|
|
5038
|
-
"",
|
|
5039
|
-
"Changes take effect on the next launch or `/new` \u2014 the system prompt is hashed once per session to keep the prefix cache warm."
|
|
5040
|
-
].join("\n")
|
|
5041
|
-
};
|
|
5389
|
+
return handleMemorySlash(args, ctx);
|
|
5042
5390
|
}
|
|
5043
5391
|
case "think":
|
|
5044
5392
|
case "reasoning": {
|
|
@@ -5108,6 +5456,42 @@ ${entry.text}`
|
|
|
5108
5456
|
}
|
|
5109
5457
|
return { info: ctx.codeDiscard() };
|
|
5110
5458
|
}
|
|
5459
|
+
case "plan": {
|
|
5460
|
+
if (!ctx.setPlanMode) {
|
|
5461
|
+
return {
|
|
5462
|
+
info: "/plan is only available inside `reasonix code` \u2014 chat mode doesn't gate tool writes."
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
const currentOn = Boolean(ctx.planMode);
|
|
5466
|
+
const raw = (args[0] ?? "").toLowerCase();
|
|
5467
|
+
let target;
|
|
5468
|
+
if (raw === "on" || raw === "true" || raw === "1") target = true;
|
|
5469
|
+
else if (raw === "off" || raw === "false" || raw === "0") target = false;
|
|
5470
|
+
else target = !currentOn;
|
|
5471
|
+
ctx.setPlanMode(target);
|
|
5472
|
+
if (target) {
|
|
5473
|
+
return {
|
|
5474
|
+
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."
|
|
5475
|
+
};
|
|
5476
|
+
}
|
|
5477
|
+
return {
|
|
5478
|
+
info: "\u25B8 plan mode OFF \u2014 write tools are live again. Model can still propose plans autonomously for large tasks."
|
|
5479
|
+
};
|
|
5480
|
+
}
|
|
5481
|
+
case "apply-plan":
|
|
5482
|
+
case "applyplan": {
|
|
5483
|
+
if (!ctx.setPlanMode) {
|
|
5484
|
+
return {
|
|
5485
|
+
info: "/apply-plan is only available inside `reasonix code`."
|
|
5486
|
+
};
|
|
5487
|
+
}
|
|
5488
|
+
ctx.setPlanMode(false);
|
|
5489
|
+
ctx.clearPendingPlan?.();
|
|
5490
|
+
return {
|
|
5491
|
+
info: "\u25B8 plan approved \u2014 implementing",
|
|
5492
|
+
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."
|
|
5493
|
+
};
|
|
5494
|
+
}
|
|
5111
5495
|
case "commit": {
|
|
5112
5496
|
if (!ctx.codeRoot) {
|
|
5113
5497
|
return {
|
|
@@ -5178,6 +5562,7 @@ ${entry.text}`
|
|
|
5178
5562
|
const toolCount = loop.prefix.toolSpecs.length;
|
|
5179
5563
|
const mcpLine = ` mcp ${mcpCount} server(s), ${toolCount} tool(s) in registry`;
|
|
5180
5564
|
const pendingLine = pending > 0 ? ` edits ${pending} pending (/apply to commit, /discard to drop)` : "";
|
|
5565
|
+
const planLine = ctx.planMode ? " plan ON \u2014 writes gated (submit_plan + approval)" : "";
|
|
5181
5566
|
const lines = [
|
|
5182
5567
|
` model ${loop.model}`,
|
|
5183
5568
|
` flags harvest=${loop.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop.stream ? "on" : "off"}`,
|
|
@@ -5186,6 +5571,7 @@ ${entry.text}`
|
|
|
5186
5571
|
sessionLine
|
|
5187
5572
|
];
|
|
5188
5573
|
if (pendingLine) lines.push(pendingLine);
|
|
5574
|
+
if (planLine) lines.push(planLine);
|
|
5189
5575
|
return { info: lines.join("\n") };
|
|
5190
5576
|
}
|
|
5191
5577
|
case "model": {
|
|
@@ -5238,6 +5624,158 @@ ${entry.text}`
|
|
|
5238
5624
|
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
5239
5625
|
}
|
|
5240
5626
|
}
|
|
5627
|
+
function handleMemorySlash(args, ctx) {
|
|
5628
|
+
if (!memoryEnabled()) {
|
|
5629
|
+
return {
|
|
5630
|
+
info: "memory is disabled (REASONIX_MEMORY=off in env). Unset the var to re-enable \u2014 no REASONIX.md or ~/.reasonix/memory content will be pinned in the meantime."
|
|
5631
|
+
};
|
|
5632
|
+
}
|
|
5633
|
+
if (!ctx.memoryRoot) {
|
|
5634
|
+
return {
|
|
5635
|
+
info: "no working directory on this session \u2014 `/memory` needs a root to resolve REASONIX.md from. (Running in a test harness?)"
|
|
5636
|
+
};
|
|
5637
|
+
}
|
|
5638
|
+
const store = new MemoryStore({ projectRoot: ctx.codeRoot });
|
|
5639
|
+
const sub = (args[0] ?? "").toLowerCase();
|
|
5640
|
+
if (sub === "list" || sub === "ls") {
|
|
5641
|
+
const entries = store.list();
|
|
5642
|
+
if (entries.length === 0) {
|
|
5643
|
+
return {
|
|
5644
|
+
info: "no user memories yet. The model can call `remember` to save one, or you can create files by hand in ~/.reasonix/memory/global/ or the per-project subdir."
|
|
5645
|
+
};
|
|
5646
|
+
}
|
|
5647
|
+
const lines = [`User memories (${entries.length}):`];
|
|
5648
|
+
for (const e of entries) {
|
|
5649
|
+
const tag = `${e.scope}/${e.type}`.padEnd(18);
|
|
5650
|
+
const name = e.name.padEnd(28);
|
|
5651
|
+
const desc = e.description.length > 70 ? `${e.description.slice(0, 69)}\u2026` : e.description;
|
|
5652
|
+
lines.push(` ${tag} ${name} ${desc}`);
|
|
5653
|
+
}
|
|
5654
|
+
lines.push("");
|
|
5655
|
+
lines.push("View body: /memory show <name> Delete: /memory forget <name>");
|
|
5656
|
+
return { info: lines.join("\n") };
|
|
5657
|
+
}
|
|
5658
|
+
if (sub === "show" || sub === "cat") {
|
|
5659
|
+
const target = args[1];
|
|
5660
|
+
if (!target) return { info: "usage: /memory show <name> or /memory show <scope>/<name>" };
|
|
5661
|
+
const resolved = resolveMemoryTarget(store, target);
|
|
5662
|
+
if (!resolved) return { info: `no memory found: ${target}` };
|
|
5663
|
+
try {
|
|
5664
|
+
const entry = store.read(resolved.scope, resolved.name);
|
|
5665
|
+
return {
|
|
5666
|
+
info: [
|
|
5667
|
+
`\u25B8 ${entry.scope}/${entry.name} (${entry.type}, created ${entry.createdAt || "?"})`,
|
|
5668
|
+
entry.description ? ` ${entry.description}` : "",
|
|
5669
|
+
"",
|
|
5670
|
+
entry.body
|
|
5671
|
+
].filter((l) => l !== "").concat("").join("\n")
|
|
5672
|
+
};
|
|
5673
|
+
} catch (err) {
|
|
5674
|
+
return { info: `show failed: ${err.message}` };
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
if (sub === "forget" || sub === "rm" || sub === "delete") {
|
|
5678
|
+
const target = args[1];
|
|
5679
|
+
if (!target) return { info: "usage: /memory forget <name> or /memory forget <scope>/<name>" };
|
|
5680
|
+
const resolved = resolveMemoryTarget(store, target);
|
|
5681
|
+
if (!resolved) return { info: `no memory found: ${target}` };
|
|
5682
|
+
try {
|
|
5683
|
+
const ok = store.delete(resolved.scope, resolved.name);
|
|
5684
|
+
return {
|
|
5685
|
+
info: ok ? `\u25B8 forgot ${resolved.scope}/${resolved.name}. Next /new or launch won't see it.` : `could not forget ${resolved.scope}/${resolved.name} (already gone?)`
|
|
5686
|
+
};
|
|
5687
|
+
} catch (err) {
|
|
5688
|
+
return { info: `forget failed: ${err.message}` };
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
if (sub === "clear") {
|
|
5692
|
+
const rawScope = (args[1] ?? "").toLowerCase();
|
|
5693
|
+
if (rawScope !== "global" && rawScope !== "project") {
|
|
5694
|
+
return { info: "usage: /memory clear <global|project> confirm" };
|
|
5695
|
+
}
|
|
5696
|
+
if ((args[2] ?? "").toLowerCase() !== "confirm") {
|
|
5697
|
+
return {
|
|
5698
|
+
info: `about to delete every memory in scope=${rawScope}. Re-run with the word 'confirm' to proceed: /memory clear ${rawScope} confirm`
|
|
5699
|
+
};
|
|
5700
|
+
}
|
|
5701
|
+
const scope = rawScope;
|
|
5702
|
+
const entries = store.list().filter((e) => e.scope === scope);
|
|
5703
|
+
let deleted = 0;
|
|
5704
|
+
for (const e of entries) {
|
|
5705
|
+
try {
|
|
5706
|
+
if (store.delete(scope, e.name)) deleted++;
|
|
5707
|
+
} catch {
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
return { info: `\u25B8 cleared scope=${scope} \u2014 deleted ${deleted} memory file(s).` };
|
|
5711
|
+
}
|
|
5712
|
+
const parts = [];
|
|
5713
|
+
const projMem = readProjectMemory(ctx.memoryRoot);
|
|
5714
|
+
if (projMem) {
|
|
5715
|
+
const hdr = projMem.truncated ? `\u25B8 ${PROJECT_MEMORY_FILE}: ${projMem.path} (${projMem.originalChars.toLocaleString()} chars, truncated)` : `\u25B8 ${PROJECT_MEMORY_FILE}: ${projMem.path} (${projMem.originalChars.toLocaleString()} chars)`;
|
|
5716
|
+
parts.push(hdr, "", projMem.content);
|
|
5717
|
+
}
|
|
5718
|
+
const globalIdx = store.loadIndex("global");
|
|
5719
|
+
if (globalIdx) {
|
|
5720
|
+
parts.push(
|
|
5721
|
+
"",
|
|
5722
|
+
`\u25B8 global memory (${globalIdx.originalChars.toLocaleString()} chars${globalIdx.truncated ? ", truncated" : ""})`,
|
|
5723
|
+
"",
|
|
5724
|
+
globalIdx.content
|
|
5725
|
+
);
|
|
5726
|
+
}
|
|
5727
|
+
const projectIdx = store.loadIndex("project");
|
|
5728
|
+
if (projectIdx) {
|
|
5729
|
+
parts.push(
|
|
5730
|
+
"",
|
|
5731
|
+
`\u25B8 project memory (${projectIdx.originalChars.toLocaleString()} chars${projectIdx.truncated ? ", truncated" : ""})`,
|
|
5732
|
+
"",
|
|
5733
|
+
projectIdx.content
|
|
5734
|
+
);
|
|
5735
|
+
}
|
|
5736
|
+
if (parts.length === 0) {
|
|
5737
|
+
return {
|
|
5738
|
+
info: [
|
|
5739
|
+
`no memory pinned in ${ctx.memoryRoot}.`,
|
|
5740
|
+
"",
|
|
5741
|
+
"Three layers are available:",
|
|
5742
|
+
` 1. ${PROJECT_MEMORY_FILE} \u2014 committable team memory (in the repo).`,
|
|
5743
|
+
" 2. ~/.reasonix/memory/global/ \u2014 your cross-project private memory.",
|
|
5744
|
+
` 3. ~/.reasonix/memory/<project-hash>/ \u2014 this project's private memory.`,
|
|
5745
|
+
"",
|
|
5746
|
+
"Ask the model to `remember` something, or hand-edit files directly.",
|
|
5747
|
+
"Changes take effect on next /new or launch \u2014 the system prompt is hashed once per session to keep the prefix cache warm.",
|
|
5748
|
+
"",
|
|
5749
|
+
"Subcommands: /memory list | /memory show <name> | /memory forget <name> | /memory clear <scope> confirm"
|
|
5750
|
+
].join("\n")
|
|
5751
|
+
};
|
|
5752
|
+
}
|
|
5753
|
+
parts.push(
|
|
5754
|
+
"",
|
|
5755
|
+
"Changes take effect on next /new or launch. Subcommands: /memory list | show | forget | clear"
|
|
5756
|
+
);
|
|
5757
|
+
return { info: parts.join("\n") };
|
|
5758
|
+
}
|
|
5759
|
+
function resolveMemoryTarget(store, raw) {
|
|
5760
|
+
const slash = raw.indexOf("/");
|
|
5761
|
+
if (slash > 0) {
|
|
5762
|
+
const scopeRaw = raw.slice(0, slash).toLowerCase();
|
|
5763
|
+
const name = raw.slice(slash + 1);
|
|
5764
|
+
if (scopeRaw !== "global" && scopeRaw !== "project") return null;
|
|
5765
|
+
const scope = scopeRaw;
|
|
5766
|
+
if (scope === "project" && !store.hasProjectScope()) return null;
|
|
5767
|
+
return { scope, name };
|
|
5768
|
+
}
|
|
5769
|
+
for (const scope of ["project", "global"]) {
|
|
5770
|
+
if (scope === "project" && !store.hasProjectScope()) continue;
|
|
5771
|
+
try {
|
|
5772
|
+
store.read(scope, raw);
|
|
5773
|
+
return { scope, name: raw };
|
|
5774
|
+
} catch {
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
return null;
|
|
5778
|
+
}
|
|
5241
5779
|
function appendSection(lines, label, section) {
|
|
5242
5780
|
if (!section || !section.supported) {
|
|
5243
5781
|
lines.push(
|
|
@@ -5332,25 +5870,28 @@ function App({
|
|
|
5332
5870
|
codeMode
|
|
5333
5871
|
}) {
|
|
5334
5872
|
const { exit } = useApp();
|
|
5335
|
-
const [historical, setHistorical] =
|
|
5336
|
-
const [streaming, setStreaming] =
|
|
5337
|
-
const [input, setInput] =
|
|
5338
|
-
const [busy, setBusy] =
|
|
5873
|
+
const [historical, setHistorical] = useState5([]);
|
|
5874
|
+
const [streaming, setStreaming] = useState5(null);
|
|
5875
|
+
const [input, setInput] = useState5("");
|
|
5876
|
+
const [busy, setBusy] = useState5(false);
|
|
5339
5877
|
const abortedThisTurn = useRef2(false);
|
|
5340
|
-
const [ongoingTool, setOngoingTool] =
|
|
5341
|
-
const [toolProgress, setToolProgress] =
|
|
5342
|
-
const [statusLine, setStatusLine] =
|
|
5343
|
-
const [balance, setBalance] =
|
|
5878
|
+
const [ongoingTool, setOngoingTool] = useState5(null);
|
|
5879
|
+
const [toolProgress, setToolProgress] = useState5(null);
|
|
5880
|
+
const [statusLine, setStatusLine] = useState5(null);
|
|
5881
|
+
const [balance, setBalance] = useState5(null);
|
|
5344
5882
|
const lastEditSnapshots = useRef2(null);
|
|
5345
5883
|
const pendingEdits = useRef2([]);
|
|
5346
|
-
const [pendingShell, setPendingShell] =
|
|
5347
|
-
const [
|
|
5884
|
+
const [pendingShell, setPendingShell] = useState5(null);
|
|
5885
|
+
const [pendingPlan, setPendingPlan] = useState5(null);
|
|
5886
|
+
const [stagedInput, setStagedInput] = useState5(null);
|
|
5887
|
+
const [planMode, setPlanMode] = useState5(false);
|
|
5888
|
+
const [queuedSubmit, setQueuedSubmit] = useState5(null);
|
|
5348
5889
|
const promptHistory = useRef2([]);
|
|
5349
5890
|
const historyCursor = useRef2(-1);
|
|
5350
5891
|
const assistantIterCounter = useRef2(0);
|
|
5351
5892
|
const toolHistoryRef = useRef2([]);
|
|
5352
|
-
const [slashSelected, setSlashSelected] =
|
|
5353
|
-
const [summary, setSummary] =
|
|
5893
|
+
const [slashSelected, setSlashSelected] = useState5(0);
|
|
5894
|
+
const [summary, setSummary] = useState5({
|
|
5354
5895
|
turns: 0,
|
|
5355
5896
|
totalCostUsd: 0,
|
|
5356
5897
|
totalInputCostUsd: 0,
|
|
@@ -5455,7 +5996,7 @@ function App({
|
|
|
5455
5996
|
]);
|
|
5456
5997
|
}
|
|
5457
5998
|
}, [session, loop]);
|
|
5458
|
-
|
|
5999
|
+
useInput4((_input, key) => {
|
|
5459
6000
|
if (key.escape && busy) {
|
|
5460
6001
|
if (abortedThisTurn.current) return;
|
|
5461
6002
|
abortedThisTurn.current = true;
|
|
@@ -5535,6 +6076,16 @@ function App({
|
|
|
5535
6076
|
},
|
|
5536
6077
|
[model, prefixHash]
|
|
5537
6078
|
);
|
|
6079
|
+
const togglePlanMode = useCallback(
|
|
6080
|
+
(on) => {
|
|
6081
|
+
setPlanMode(on);
|
|
6082
|
+
tools?.setPlanMode(on);
|
|
6083
|
+
},
|
|
6084
|
+
[tools]
|
|
6085
|
+
);
|
|
6086
|
+
const clearPendingPlan = useCallback(() => {
|
|
6087
|
+
setPendingPlan(null);
|
|
6088
|
+
}, []);
|
|
5538
6089
|
const handleSubmit = useCallback(
|
|
5539
6090
|
async (raw) => {
|
|
5540
6091
|
let text = raw.trim();
|
|
@@ -5567,7 +6118,10 @@ function App({
|
|
|
5567
6118
|
codeRoot: codeMode?.rootDir,
|
|
5568
6119
|
pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
|
|
5569
6120
|
toolHistory: () => toolHistoryRef.current,
|
|
5570
|
-
memoryRoot: codeMode?.rootDir ?? process.cwd()
|
|
6121
|
+
memoryRoot: codeMode?.rootDir ?? process.cwd(),
|
|
6122
|
+
planMode,
|
|
6123
|
+
setPlanMode: codeMode ? togglePlanMode : void 0,
|
|
6124
|
+
clearPendingPlan: codeMode ? clearPendingPlan : void 0
|
|
5571
6125
|
});
|
|
5572
6126
|
if (result.exit) {
|
|
5573
6127
|
transcriptRef.current?.end();
|
|
@@ -5743,6 +6297,15 @@ function App({
|
|
|
5743
6297
|
} catch {
|
|
5744
6298
|
}
|
|
5745
6299
|
}
|
|
6300
|
+
if (codeMode && ev.toolName === "submit_plan" && ev.content.includes('"PlanProposedError:')) {
|
|
6301
|
+
try {
|
|
6302
|
+
const parsed = JSON.parse(ev.content);
|
|
6303
|
+
if (typeof parsed.plan === "string" && parsed.plan.trim()) {
|
|
6304
|
+
setPendingPlan(parsed.plan.trim());
|
|
6305
|
+
}
|
|
6306
|
+
} catch {
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
5746
6309
|
} else if (ev.role === "error") {
|
|
5747
6310
|
setHistorical((prev) => [
|
|
5748
6311
|
...prev,
|
|
@@ -5775,6 +6338,7 @@ function App({
|
|
|
5775
6338
|
},
|
|
5776
6339
|
[
|
|
5777
6340
|
busy,
|
|
6341
|
+
clearPendingPlan,
|
|
5778
6342
|
codeApply,
|
|
5779
6343
|
codeDiscard,
|
|
5780
6344
|
codeMode,
|
|
@@ -5783,7 +6347,9 @@ function App({
|
|
|
5783
6347
|
loop,
|
|
5784
6348
|
mcpSpecs,
|
|
5785
6349
|
mcpServers,
|
|
6350
|
+
planMode,
|
|
5786
6351
|
slashSelected,
|
|
6352
|
+
togglePlanMode,
|
|
5787
6353
|
writeTranscript
|
|
5788
6354
|
]
|
|
5789
6355
|
);
|
|
@@ -5848,7 +6414,92 @@ ${body}`;
|
|
|
5848
6414
|
void handleSubmit(text);
|
|
5849
6415
|
}
|
|
5850
6416
|
}, [busy, queuedSubmit, handleSubmit]);
|
|
5851
|
-
|
|
6417
|
+
const handlePlanConfirm = useCallback(
|
|
6418
|
+
async (choice) => {
|
|
6419
|
+
const hadPendingPlan = pendingPlan !== null;
|
|
6420
|
+
if (!hadPendingPlan && choice !== "approve") {
|
|
6421
|
+
return;
|
|
6422
|
+
}
|
|
6423
|
+
if (choice === "refine" || choice === "approve") {
|
|
6424
|
+
if (pendingPlan) {
|
|
6425
|
+
setStagedInput({ plan: pendingPlan, mode: choice });
|
|
6426
|
+
setPendingPlan(null);
|
|
6427
|
+
} else if (choice === "approve") {
|
|
6428
|
+
setStagedInput({ plan: "", mode: "approve" });
|
|
6429
|
+
}
|
|
6430
|
+
return;
|
|
6431
|
+
}
|
|
6432
|
+
setPendingPlan(null);
|
|
6433
|
+
togglePlanMode(false);
|
|
6434
|
+
const marker = "\u25B8 plan cancelled";
|
|
6435
|
+
const synthetic = "The plan was cancelled. Drop it entirely. Ask me what I actually want before proposing another plan or making any changes.";
|
|
6436
|
+
setHistorical((prev) => [
|
|
6437
|
+
...prev,
|
|
6438
|
+
{ id: `plan-${choice}-${Date.now()}`, role: "info", text: marker }
|
|
6439
|
+
]);
|
|
6440
|
+
if (busy) {
|
|
6441
|
+
loop.abort();
|
|
6442
|
+
setQueuedSubmit(synthetic);
|
|
6443
|
+
} else {
|
|
6444
|
+
await handleSubmit(synthetic);
|
|
6445
|
+
}
|
|
6446
|
+
},
|
|
6447
|
+
[pendingPlan, togglePlanMode, busy, loop, handleSubmit]
|
|
6448
|
+
);
|
|
6449
|
+
const handleStagedInputSubmit = useCallback(
|
|
6450
|
+
async (feedback) => {
|
|
6451
|
+
const staged = stagedInput;
|
|
6452
|
+
setStagedInput(null);
|
|
6453
|
+
if (!staged) return;
|
|
6454
|
+
const trimmed = feedback.trim();
|
|
6455
|
+
let synthetic;
|
|
6456
|
+
let marker;
|
|
6457
|
+
if (staged.mode === "approve") {
|
|
6458
|
+
togglePlanMode(false);
|
|
6459
|
+
if (trimmed) {
|
|
6460
|
+
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.
|
|
6461
|
+
|
|
6462
|
+
User's additional instructions / answers to your open questions:
|
|
6463
|
+
|
|
6464
|
+
${trimmed}
|
|
6465
|
+
|
|
6466
|
+
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.`;
|
|
6467
|
+
marker = `\u25B8 plan approved + instructions \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}`;
|
|
6468
|
+
} else {
|
|
6469
|
+
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.";
|
|
6470
|
+
marker = "\u25B8 plan approved \u2014 implementing";
|
|
6471
|
+
}
|
|
6472
|
+
} else {
|
|
6473
|
+
if (trimmed) {
|
|
6474
|
+
synthetic = `The plan needs refinement. User feedback / answers:
|
|
6475
|
+
|
|
6476
|
+
${trimmed}
|
|
6477
|
+
|
|
6478
|
+
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.`;
|
|
6479
|
+
marker = `\u25B8 refining \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}`;
|
|
6480
|
+
} else {
|
|
6481
|
+
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.";
|
|
6482
|
+
marker = "\u25B8 refining \u2014 asking the model to clarify";
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
setHistorical((prev) => [
|
|
6486
|
+
...prev,
|
|
6487
|
+
{ id: `plan-${staged.mode}-${Date.now()}`, role: "info", text: marker }
|
|
6488
|
+
]);
|
|
6489
|
+
if (busy) {
|
|
6490
|
+
loop.abort();
|
|
6491
|
+
setQueuedSubmit(synthetic);
|
|
6492
|
+
} else {
|
|
6493
|
+
await handleSubmit(synthetic);
|
|
6494
|
+
}
|
|
6495
|
+
},
|
|
6496
|
+
[stagedInput, togglePlanMode, busy, loop, handleSubmit]
|
|
6497
|
+
);
|
|
6498
|
+
const handleStagedInputCancel = useCallback(() => {
|
|
6499
|
+
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
6500
|
+
setStagedInput(null);
|
|
6501
|
+
}, [stagedInput]);
|
|
6502
|
+
return /* @__PURE__ */ React12.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(
|
|
5852
6503
|
StatsPanel,
|
|
5853
6504
|
{
|
|
5854
6505
|
summary,
|
|
@@ -5856,16 +6507,24 @@ ${body}`;
|
|
|
5856
6507
|
prefixHash,
|
|
5857
6508
|
harvestOn: loop.harvestEnabled,
|
|
5858
6509
|
branchBudget: loop.branchOptions.budget,
|
|
6510
|
+
planMode,
|
|
5859
6511
|
balance
|
|
5860
6512
|
}
|
|
5861
|
-
), /* @__PURE__ */
|
|
6513
|
+
), /* @__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(
|
|
6514
|
+
PlanRefineInput,
|
|
6515
|
+
{
|
|
6516
|
+
mode: stagedInput.mode,
|
|
6517
|
+
onSubmit: handleStagedInputSubmit,
|
|
6518
|
+
onCancel: handleStagedInputCancel
|
|
6519
|
+
}
|
|
6520
|
+
) : pendingPlan ? /* @__PURE__ */ React12.createElement(PlanConfirm, { plan: pendingPlan, onChoose: handlePlanConfirm }) : pendingShell ? /* @__PURE__ */ React12.createElement(
|
|
5862
6521
|
ShellConfirm,
|
|
5863
6522
|
{
|
|
5864
6523
|
command: pendingShell,
|
|
5865
6524
|
allowPrefix: derivePrefix(pendingShell),
|
|
5866
6525
|
onChoose: handleShellConfirm
|
|
5867
6526
|
}
|
|
5868
|
-
) : /* @__PURE__ */
|
|
6527
|
+
) : /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
|
|
5869
6528
|
PromptInput,
|
|
5870
6529
|
{
|
|
5871
6530
|
value: input,
|
|
@@ -5873,13 +6532,13 @@ ${body}`;
|
|
|
5873
6532
|
onSubmit: handleSubmit,
|
|
5874
6533
|
disabled: busy
|
|
5875
6534
|
}
|
|
5876
|
-
), /* @__PURE__ */
|
|
6535
|
+
), /* @__PURE__ */ React12.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }))));
|
|
5877
6536
|
}
|
|
5878
6537
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5879
6538
|
function StatusRow({ text }) {
|
|
5880
6539
|
const tick = useTick();
|
|
5881
6540
|
const elapsed = useElapsedSeconds();
|
|
5882
|
-
return /* @__PURE__ */
|
|
6541
|
+
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`));
|
|
5883
6542
|
}
|
|
5884
6543
|
function OngoingToolRow({
|
|
5885
6544
|
tool,
|
|
@@ -5888,7 +6547,7 @@ function OngoingToolRow({
|
|
|
5888
6547
|
const tick = useTick();
|
|
5889
6548
|
const elapsed = useElapsedSeconds();
|
|
5890
6549
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
5891
|
-
return /* @__PURE__ */
|
|
6550
|
+
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);
|
|
5892
6551
|
}
|
|
5893
6552
|
function renderProgressLine(p) {
|
|
5894
6553
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -5984,15 +6643,15 @@ function describeRepair(repair) {
|
|
|
5984
6643
|
}
|
|
5985
6644
|
|
|
5986
6645
|
// src/cli/ui/SessionPicker.tsx
|
|
5987
|
-
import { Box as
|
|
5988
|
-
import
|
|
6646
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
6647
|
+
import React13 from "react";
|
|
5989
6648
|
function SessionPicker({
|
|
5990
6649
|
sessionName,
|
|
5991
6650
|
messageCount,
|
|
5992
6651
|
lastActive,
|
|
5993
6652
|
onChoose
|
|
5994
6653
|
}) {
|
|
5995
|
-
return /* @__PURE__ */
|
|
6654
|
+
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(
|
|
5996
6655
|
SingleSelect,
|
|
5997
6656
|
{
|
|
5998
6657
|
initialValue: "new",
|
|
@@ -6015,7 +6674,7 @@ function SessionPicker({
|
|
|
6015
6674
|
],
|
|
6016
6675
|
onSubmit: (v) => onChoose(v)
|
|
6017
6676
|
}
|
|
6018
|
-
), /* @__PURE__ */
|
|
6677
|
+
), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
|
|
6019
6678
|
}
|
|
6020
6679
|
function relativeTime(date) {
|
|
6021
6680
|
const ms = Date.now() - date.getTime();
|
|
@@ -6031,12 +6690,12 @@ function relativeTime(date) {
|
|
|
6031
6690
|
}
|
|
6032
6691
|
|
|
6033
6692
|
// src/cli/ui/Setup.tsx
|
|
6034
|
-
import { Box as
|
|
6693
|
+
import { Box as Box13, Text as Text13, useApp as useApp2 } from "ink";
|
|
6035
6694
|
import TextInput from "ink-text-input";
|
|
6036
|
-
import
|
|
6695
|
+
import React14, { useState as useState6 } from "react";
|
|
6037
6696
|
function Setup({ onReady }) {
|
|
6038
|
-
const [value, setValue] =
|
|
6039
|
-
const [error, setError] =
|
|
6697
|
+
const [value, setValue] = useState6("");
|
|
6698
|
+
const [error, setError] = useState6(null);
|
|
6040
6699
|
const { exit } = useApp2();
|
|
6041
6700
|
const handleSubmit = (raw) => {
|
|
6042
6701
|
const trimmed = raw.trim();
|
|
@@ -6057,7 +6716,7 @@ function Setup({ onReady }) {
|
|
|
6057
6716
|
}
|
|
6058
6717
|
onReady(trimmed);
|
|
6059
6718
|
};
|
|
6060
|
-
return /* @__PURE__ */
|
|
6719
|
+
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(
|
|
6061
6720
|
TextInput,
|
|
6062
6721
|
{
|
|
6063
6722
|
value,
|
|
@@ -6066,7 +6725,7 @@ function Setup({ onReady }) {
|
|
|
6066
6725
|
mask: "\u2022",
|
|
6067
6726
|
placeholder: "sk-..."
|
|
6068
6727
|
}
|
|
6069
|
-
)), error ? /* @__PURE__ */
|
|
6728
|
+
)), 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.)")));
|
|
6070
6729
|
}
|
|
6071
6730
|
|
|
6072
6731
|
// src/cli/commands/chat.tsx
|
|
@@ -6079,10 +6738,10 @@ function Root({
|
|
|
6079
6738
|
sessionPreview,
|
|
6080
6739
|
...appProps
|
|
6081
6740
|
}) {
|
|
6082
|
-
const [key, setKey] =
|
|
6083
|
-
const [pending, setPending] =
|
|
6741
|
+
const [key, setKey] = useState7(initialKey);
|
|
6742
|
+
const [pending, setPending] = useState7(sessionPreview);
|
|
6084
6743
|
if (!key) {
|
|
6085
|
-
return /* @__PURE__ */
|
|
6744
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6086
6745
|
Setup,
|
|
6087
6746
|
{
|
|
6088
6747
|
onReady: (k) => {
|
|
@@ -6094,7 +6753,7 @@ function Root({
|
|
|
6094
6753
|
}
|
|
6095
6754
|
process.env.DEEPSEEK_API_KEY = key;
|
|
6096
6755
|
if (pending && appProps.session) {
|
|
6097
|
-
return /* @__PURE__ */
|
|
6756
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6098
6757
|
SessionPicker,
|
|
6099
6758
|
{
|
|
6100
6759
|
sessionName: appProps.session,
|
|
@@ -6109,7 +6768,7 @@ function Root({
|
|
|
6109
6768
|
}
|
|
6110
6769
|
);
|
|
6111
6770
|
}
|
|
6112
|
-
return /* @__PURE__ */
|
|
6771
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6113
6772
|
App,
|
|
6114
6773
|
{
|
|
6115
6774
|
model: appProps.model,
|
|
@@ -6196,19 +6855,23 @@ async function chatCommand(opts) {
|
|
|
6196
6855
|
if (!tools) tools = new ToolRegistry();
|
|
6197
6856
|
registerWebTools(tools);
|
|
6198
6857
|
}
|
|
6858
|
+
if (!opts.seedTools) {
|
|
6859
|
+
if (!tools) tools = new ToolRegistry();
|
|
6860
|
+
registerMemoryTools(tools, {});
|
|
6861
|
+
}
|
|
6199
6862
|
let sessionPreview;
|
|
6200
6863
|
if (opts.session && !opts.forceResume && !opts.forceNew) {
|
|
6201
6864
|
const prior = loadSessionMessages(opts.session);
|
|
6202
6865
|
if (prior.length > 0) {
|
|
6203
6866
|
const p = sessionPath(opts.session);
|
|
6204
|
-
const mtime =
|
|
6867
|
+
const mtime = existsSync4(p) ? statSync3(p).mtime : /* @__PURE__ */ new Date();
|
|
6205
6868
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
6206
6869
|
}
|
|
6207
6870
|
} else if (opts.session && opts.forceNew) {
|
|
6208
6871
|
rewriteSession(opts.session, []);
|
|
6209
6872
|
}
|
|
6210
6873
|
const { waitUntilExit } = render(
|
|
6211
|
-
/* @__PURE__ */
|
|
6874
|
+
/* @__PURE__ */ React15.createElement(
|
|
6212
6875
|
Root,
|
|
6213
6876
|
{
|
|
6214
6877
|
initialKey,
|
|
@@ -6234,7 +6897,7 @@ async function chatCommand(opts) {
|
|
|
6234
6897
|
// src/cli/commands/code.tsx
|
|
6235
6898
|
import { basename, resolve as resolve5 } from "path";
|
|
6236
6899
|
async function codeCommand(opts = {}) {
|
|
6237
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
6900
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-YEJEJ3IZ.js");
|
|
6238
6901
|
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
6239
6902
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
6240
6903
|
const tools = new ToolRegistry();
|
|
@@ -6245,6 +6908,8 @@ async function codeCommand(opts = {}) {
|
|
|
6245
6908
|
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
6246
6909
|
extraAllowed: loadProjectShellAllowed(rootDir)
|
|
6247
6910
|
});
|
|
6911
|
+
registerPlanTool(tools);
|
|
6912
|
+
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
6248
6913
|
process.stderr.write(
|
|
6249
6914
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
6250
6915
|
`
|
|
@@ -6267,34 +6932,34 @@ async function codeCommand(opts = {}) {
|
|
|
6267
6932
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
6268
6933
|
import { basename as basename2 } from "path";
|
|
6269
6934
|
import { render as render2 } from "ink";
|
|
6270
|
-
import
|
|
6935
|
+
import React18 from "react";
|
|
6271
6936
|
|
|
6272
6937
|
// src/cli/ui/DiffApp.tsx
|
|
6273
|
-
import { Box as
|
|
6274
|
-
import
|
|
6938
|
+
import { Box as Box15, Static as Static2, Text as Text15, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
6939
|
+
import React17, { useState as useState8 } from "react";
|
|
6275
6940
|
|
|
6276
6941
|
// src/cli/ui/RecordView.tsx
|
|
6277
|
-
import { Box as
|
|
6278
|
-
import
|
|
6942
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
6943
|
+
import React16 from "react";
|
|
6279
6944
|
function RecordView({ rec, compact = false }) {
|
|
6280
6945
|
const toolArgsMax = compact ? 120 : 200;
|
|
6281
6946
|
const toolContentMax = compact ? 200 : 400;
|
|
6282
6947
|
if (rec.role === "user") {
|
|
6283
|
-
return /* @__PURE__ */
|
|
6948
|
+
return /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React16.createElement(Text14, null, rec.content));
|
|
6284
6949
|
}
|
|
6285
6950
|
if (rec.role === "assistant_final") {
|
|
6286
|
-
return /* @__PURE__ */
|
|
6951
|
+
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)"));
|
|
6287
6952
|
}
|
|
6288
6953
|
if (rec.role === "tool") {
|
|
6289
|
-
return /* @__PURE__ */
|
|
6954
|
+
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)));
|
|
6290
6955
|
}
|
|
6291
6956
|
if (rec.role === "error") {
|
|
6292
|
-
return /* @__PURE__ */
|
|
6957
|
+
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));
|
|
6293
6958
|
}
|
|
6294
6959
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
6295
6960
|
return null;
|
|
6296
6961
|
}
|
|
6297
|
-
return /* @__PURE__ */
|
|
6962
|
+
return /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
6298
6963
|
}
|
|
6299
6964
|
function CacheBadge({ usage }) {
|
|
6300
6965
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -6303,7 +6968,7 @@ function CacheBadge({ usage }) {
|
|
|
6303
6968
|
if (total === 0) return null;
|
|
6304
6969
|
const pct2 = hit / total * 100;
|
|
6305
6970
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
6306
|
-
return /* @__PURE__ */
|
|
6971
|
+
return /* @__PURE__ */ React16.createElement(Text14, null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React16.createElement(Text14, { color }, pct2.toFixed(1), "%"));
|
|
6307
6972
|
}
|
|
6308
6973
|
function truncate3(s, max) {
|
|
6309
6974
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -6314,8 +6979,8 @@ function DiffApp({ report }) {
|
|
|
6314
6979
|
const { exit } = useApp3();
|
|
6315
6980
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
6316
6981
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
6317
|
-
const [idx, setIdx] =
|
|
6318
|
-
|
|
6982
|
+
const [idx, setIdx] = useState8(Math.max(0, initialIdx));
|
|
6983
|
+
useInput5((input, key) => {
|
|
6319
6984
|
if (input === "q" || key.ctrl && input === "c") {
|
|
6320
6985
|
exit();
|
|
6321
6986
|
return;
|
|
@@ -6337,7 +7002,7 @@ function DiffApp({ report }) {
|
|
|
6337
7002
|
}
|
|
6338
7003
|
});
|
|
6339
7004
|
const pair = report.pairs[idx];
|
|
6340
|
-
return /* @__PURE__ */
|
|
7005
|
+
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")));
|
|
6341
7006
|
}
|
|
6342
7007
|
function DiffHeader({ report }) {
|
|
6343
7008
|
const a = report.a;
|
|
@@ -6355,15 +7020,15 @@ function DiffHeader({ report }) {
|
|
|
6355
7020
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
6356
7021
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
6357
7022
|
}
|
|
6358
|
-
return /* @__PURE__ */
|
|
7023
|
+
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);
|
|
6359
7024
|
}
|
|
6360
7025
|
function Pane({
|
|
6361
7026
|
label,
|
|
6362
7027
|
headerColor,
|
|
6363
7028
|
records
|
|
6364
7029
|
}) {
|
|
6365
|
-
return /* @__PURE__ */
|
|
6366
|
-
|
|
7030
|
+
return /* @__PURE__ */ React17.createElement(
|
|
7031
|
+
Box15,
|
|
6367
7032
|
{
|
|
6368
7033
|
flexDirection: "column",
|
|
6369
7034
|
flexGrow: 1,
|
|
@@ -6371,21 +7036,21 @@ function Pane({
|
|
|
6371
7036
|
borderStyle: "single",
|
|
6372
7037
|
borderColor: headerColor
|
|
6373
7038
|
},
|
|
6374
|
-
/* @__PURE__ */
|
|
6375
|
-
records.length === 0 ? /* @__PURE__ */
|
|
7039
|
+
/* @__PURE__ */ React17.createElement(Text15, { color: headerColor, bold: true }, label),
|
|
7040
|
+
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 }))
|
|
6376
7041
|
);
|
|
6377
7042
|
}
|
|
6378
7043
|
function KindBadge({ kind }) {
|
|
6379
7044
|
if (kind === "match") {
|
|
6380
|
-
return /* @__PURE__ */
|
|
7045
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "green" }, "\u2713 match");
|
|
6381
7046
|
}
|
|
6382
7047
|
if (kind === "diverge") {
|
|
6383
|
-
return /* @__PURE__ */
|
|
7048
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "yellow" }, "\u2605 diverge");
|
|
6384
7049
|
}
|
|
6385
7050
|
if (kind === "only_in_a") {
|
|
6386
|
-
return /* @__PURE__ */
|
|
7051
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "blue" }, "\u2190 only in A");
|
|
6387
7052
|
}
|
|
6388
|
-
return /* @__PURE__ */
|
|
7053
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "magenta" }, "\u2192 only in B");
|
|
6389
7054
|
}
|
|
6390
7055
|
function paneRecords(pair, side) {
|
|
6391
7056
|
if (!pair) return [];
|
|
@@ -6416,7 +7081,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
6416
7081
|
return;
|
|
6417
7082
|
}
|
|
6418
7083
|
if (wantTui) {
|
|
6419
|
-
const { waitUntilExit } = render2(
|
|
7084
|
+
const { waitUntilExit } = render2(React18.createElement(DiffApp, { report }), {
|
|
6420
7085
|
exitOnCtrlC: true,
|
|
6421
7086
|
patchConsole: false
|
|
6422
7087
|
});
|
|
@@ -6557,16 +7222,16 @@ function pad(s, width) {
|
|
|
6557
7222
|
|
|
6558
7223
|
// src/cli/commands/replay.ts
|
|
6559
7224
|
import { render as render3 } from "ink";
|
|
6560
|
-
import
|
|
7225
|
+
import React20 from "react";
|
|
6561
7226
|
|
|
6562
7227
|
// src/cli/ui/ReplayApp.tsx
|
|
6563
|
-
import { Box as
|
|
6564
|
-
import
|
|
7228
|
+
import { Box as Box16, Static as Static3, Text as Text16, useApp as useApp4, useInput as useInput6 } from "ink";
|
|
7229
|
+
import React19, { useMemo as useMemo2, useState as useState9 } from "react";
|
|
6565
7230
|
function ReplayApp({ meta, pages }) {
|
|
6566
7231
|
const { exit } = useApp4();
|
|
6567
7232
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
6568
|
-
const [idx, setIdx] =
|
|
6569
|
-
|
|
7233
|
+
const [idx, setIdx] = useState9(maxIdx);
|
|
7234
|
+
useInput6((input, key) => {
|
|
6570
7235
|
if (input === "q" || key.ctrl && input === "c") {
|
|
6571
7236
|
exit();
|
|
6572
7237
|
return;
|
|
@@ -6600,14 +7265,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
6600
7265
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
6601
7266
|
const currentPage = pages[idx];
|
|
6602
7267
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
6603
|
-
return /* @__PURE__ */
|
|
7268
|
+
return /* @__PURE__ */ React19.createElement(Box16, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(
|
|
6604
7269
|
StatsPanel,
|
|
6605
7270
|
{
|
|
6606
7271
|
summary,
|
|
6607
7272
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
6608
7273
|
prefixHash
|
|
6609
7274
|
}
|
|
6610
|
-
), /* @__PURE__ */
|
|
7275
|
+
), /* @__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")));
|
|
6611
7276
|
}
|
|
6612
7277
|
|
|
6613
7278
|
// src/cli/commands/replay.ts
|
|
@@ -6619,7 +7284,7 @@ async function replayCommand(opts) {
|
|
|
6619
7284
|
}
|
|
6620
7285
|
const { parsed } = replayFromFile(opts.path);
|
|
6621
7286
|
const pages = groupRecordsByTurn(parsed.records);
|
|
6622
|
-
const { waitUntilExit } = render3(
|
|
7287
|
+
const { waitUntilExit } = render3(React20.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
6623
7288
|
exitOnCtrlC: true,
|
|
6624
7289
|
patchConsole: false
|
|
6625
7290
|
});
|
|
@@ -6921,12 +7586,12 @@ function truncate4(s, max) {
|
|
|
6921
7586
|
|
|
6922
7587
|
// src/cli/commands/setup.tsx
|
|
6923
7588
|
import { render as render4 } from "ink";
|
|
6924
|
-
import
|
|
7589
|
+
import React22 from "react";
|
|
6925
7590
|
|
|
6926
7591
|
// src/cli/ui/Wizard.tsx
|
|
6927
|
-
import { Box as
|
|
7592
|
+
import { Box as Box17, Text as Text17, useApp as useApp5, useInput as useInput7 } from "ink";
|
|
6928
7593
|
import TextInput2 from "ink-text-input";
|
|
6929
|
-
import
|
|
7594
|
+
import React21, { useState as useState10 } from "react";
|
|
6930
7595
|
|
|
6931
7596
|
// src/cli/ui/presets.ts
|
|
6932
7597
|
var PRESETS = {
|
|
@@ -6953,19 +7618,19 @@ var PRESET_DESCRIPTIONS = {
|
|
|
6953
7618
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
6954
7619
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
6955
7620
|
const { exit } = useApp5();
|
|
6956
|
-
const [step, setStep] =
|
|
6957
|
-
const [data, setData] =
|
|
7621
|
+
const [step, setStep] = useState10(existingApiKey ? "preset" : "apiKey");
|
|
7622
|
+
const [data, setData] = useState10({
|
|
6958
7623
|
apiKey: existingApiKey ?? "",
|
|
6959
7624
|
preset: initial?.preset ?? "fast",
|
|
6960
7625
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
6961
7626
|
catalogArgs: {}
|
|
6962
7627
|
});
|
|
6963
|
-
const [error, setError] =
|
|
6964
|
-
|
|
7628
|
+
const [error, setError] = useState10(null);
|
|
7629
|
+
useInput7((_input, key) => {
|
|
6965
7630
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
6966
7631
|
});
|
|
6967
7632
|
if (step === "apiKey") {
|
|
6968
|
-
return /* @__PURE__ */
|
|
7633
|
+
return /* @__PURE__ */ React21.createElement(
|
|
6969
7634
|
ApiKeyStep,
|
|
6970
7635
|
{
|
|
6971
7636
|
onSubmit: (key) => {
|
|
@@ -6979,7 +7644,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6979
7644
|
);
|
|
6980
7645
|
}
|
|
6981
7646
|
if (step === "preset") {
|
|
6982
|
-
return /* @__PURE__ */
|
|
7647
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React21.createElement(
|
|
6983
7648
|
SingleSelect,
|
|
6984
7649
|
{
|
|
6985
7650
|
items: presetItems(),
|
|
@@ -6989,10 +7654,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6989
7654
|
setStep("mcp");
|
|
6990
7655
|
}
|
|
6991
7656
|
}
|
|
6992
|
-
), /* @__PURE__ */
|
|
7657
|
+
), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
6993
7658
|
}
|
|
6994
7659
|
if (step === "mcp") {
|
|
6995
|
-
return /* @__PURE__ */
|
|
7660
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React21.createElement(
|
|
6996
7661
|
MultiSelect,
|
|
6997
7662
|
{
|
|
6998
7663
|
items: mcpItems(),
|
|
@@ -7017,7 +7682,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7017
7682
|
}
|
|
7018
7683
|
const currentName = pending[0];
|
|
7019
7684
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
7020
|
-
return /* @__PURE__ */
|
|
7685
|
+
return /* @__PURE__ */ React21.createElement(
|
|
7021
7686
|
McpArgsStep,
|
|
7022
7687
|
{
|
|
7023
7688
|
entry,
|
|
@@ -7035,7 +7700,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7035
7700
|
}
|
|
7036
7701
|
if (step === "review") {
|
|
7037
7702
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
7038
|
-
return /* @__PURE__ */
|
|
7703
|
+
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(
|
|
7039
7704
|
SummaryLine,
|
|
7040
7705
|
{
|
|
7041
7706
|
label: "MCP",
|
|
@@ -7043,8 +7708,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7043
7708
|
}
|
|
7044
7709
|
), specs.map((spec, i) => (
|
|
7045
7710
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
7046
|
-
/* @__PURE__ */
|
|
7047
|
-
)), /* @__PURE__ */
|
|
7711
|
+
/* @__PURE__ */ React21.createElement(Box17, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\xB7 ", spec))
|
|
7712
|
+
)), /* @__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(
|
|
7048
7713
|
ReviewConfirm,
|
|
7049
7714
|
{
|
|
7050
7715
|
onConfirm: () => {
|
|
@@ -7070,15 +7735,15 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7070
7735
|
}
|
|
7071
7736
|
));
|
|
7072
7737
|
}
|
|
7073
|
-
return /* @__PURE__ */
|
|
7738
|
+
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 }));
|
|
7074
7739
|
}
|
|
7075
7740
|
function ApiKeyStep({
|
|
7076
7741
|
onSubmit,
|
|
7077
7742
|
error,
|
|
7078
7743
|
onError
|
|
7079
7744
|
}) {
|
|
7080
|
-
const [value, setValue] =
|
|
7081
|
-
return /* @__PURE__ */
|
|
7745
|
+
const [value, setValue] = useState10("");
|
|
7746
|
+
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(
|
|
7082
7747
|
TextInput2,
|
|
7083
7748
|
{
|
|
7084
7749
|
value,
|
|
@@ -7095,7 +7760,7 @@ function ApiKeyStep({
|
|
|
7095
7760
|
mask: "\u2022",
|
|
7096
7761
|
placeholder: "sk-..."
|
|
7097
7762
|
}
|
|
7098
|
-
)), error ? /* @__PURE__ */
|
|
7763
|
+
)), 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);
|
|
7099
7764
|
}
|
|
7100
7765
|
function McpArgsStep({
|
|
7101
7766
|
entry,
|
|
@@ -7103,8 +7768,8 @@ function McpArgsStep({
|
|
|
7103
7768
|
onSubmit,
|
|
7104
7769
|
onError
|
|
7105
7770
|
}) {
|
|
7106
|
-
const [value, setValue] =
|
|
7107
|
-
return /* @__PURE__ */
|
|
7771
|
+
const [value, setValue] = useState10("");
|
|
7772
|
+
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(
|
|
7108
7773
|
TextInput2,
|
|
7109
7774
|
{
|
|
7110
7775
|
value,
|
|
@@ -7120,16 +7785,16 @@ function McpArgsStep({
|
|
|
7120
7785
|
},
|
|
7121
7786
|
placeholder: placeholderFor(entry)
|
|
7122
7787
|
}
|
|
7123
|
-
)), error ? /* @__PURE__ */
|
|
7788
|
+
)), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : null));
|
|
7124
7789
|
}
|
|
7125
7790
|
function ReviewConfirm({ onConfirm }) {
|
|
7126
|
-
|
|
7791
|
+
useInput7((_i, key) => {
|
|
7127
7792
|
if (key.return) onConfirm();
|
|
7128
7793
|
});
|
|
7129
7794
|
return null;
|
|
7130
7795
|
}
|
|
7131
7796
|
function ExitOnEnter({ onExit }) {
|
|
7132
|
-
|
|
7797
|
+
useInput7((_i, key) => {
|
|
7133
7798
|
if (key.return) onExit();
|
|
7134
7799
|
});
|
|
7135
7800
|
return null;
|
|
@@ -7140,10 +7805,10 @@ function StepFrame({
|
|
|
7140
7805
|
total,
|
|
7141
7806
|
children
|
|
7142
7807
|
}) {
|
|
7143
|
-
return /* @__PURE__ */
|
|
7808
|
+
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));
|
|
7144
7809
|
}
|
|
7145
7810
|
function SummaryLine({ label, value }) {
|
|
7146
|
-
return /* @__PURE__ */
|
|
7811
|
+
return /* @__PURE__ */ React21.createElement(Box17, null, /* @__PURE__ */ React21.createElement(Text17, null, label.padEnd(12)), /* @__PURE__ */ React21.createElement(Text17, { bold: true }, value));
|
|
7147
7812
|
}
|
|
7148
7813
|
function presetItems() {
|
|
7149
7814
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -7199,7 +7864,7 @@ async function setupCommand(_opts = {}) {
|
|
|
7199
7864
|
const existingKey = loadApiKey();
|
|
7200
7865
|
const existing = readConfig();
|
|
7201
7866
|
const { waitUntilExit, unmount } = render4(
|
|
7202
|
-
/* @__PURE__ */
|
|
7867
|
+
/* @__PURE__ */ React22.createElement(
|
|
7203
7868
|
Wizard,
|
|
7204
7869
|
{
|
|
7205
7870
|
existingApiKey: existingKey,
|
|
@@ -7217,9 +7882,9 @@ async function setupCommand(_opts = {}) {
|
|
|
7217
7882
|
}
|
|
7218
7883
|
|
|
7219
7884
|
// src/cli/commands/stats.ts
|
|
7220
|
-
import { existsSync as
|
|
7885
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
7221
7886
|
function statsCommand(opts) {
|
|
7222
|
-
if (!
|
|
7887
|
+
if (!existsSync5(opts.transcript)) {
|
|
7223
7888
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
7224
7889
|
process.exit(1);
|
|
7225
7890
|
}
|
|
@@ -7294,7 +7959,7 @@ program.action(async () => {
|
|
|
7294
7959
|
const defaults = resolveDefaults({});
|
|
7295
7960
|
await chatCommand({
|
|
7296
7961
|
model: defaults.model,
|
|
7297
|
-
system:
|
|
7962
|
+
system: applyMemoryStack(DEFAULT_SYSTEM, process.cwd()),
|
|
7298
7963
|
harvest: defaults.harvest,
|
|
7299
7964
|
branch: defaults.branch,
|
|
7300
7965
|
session: defaults.session,
|
|
@@ -7346,7 +8011,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
7346
8011
|
});
|
|
7347
8012
|
await chatCommand({
|
|
7348
8013
|
model: defaults.model,
|
|
7349
|
-
system:
|
|
8014
|
+
system: applyMemoryStack(opts.system, process.cwd()),
|
|
7350
8015
|
transcript: opts.transcript,
|
|
7351
8016
|
harvest: defaults.harvest,
|
|
7352
8017
|
branch: defaults.branch,
|
|
@@ -7381,7 +8046,7 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
7381
8046
|
await runCommand2({
|
|
7382
8047
|
task,
|
|
7383
8048
|
model: defaults.model,
|
|
7384
|
-
system:
|
|
8049
|
+
system: applyMemoryStack(opts.system, process.cwd()),
|
|
7385
8050
|
harvest: defaults.harvest,
|
|
7386
8051
|
branch: defaults.branch,
|
|
7387
8052
|
transcript: opts.transcript,
|