reasonix 0.5.23 → 0.5.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{chunk-ANMDY236.js → chunk-C266QOQU.js} +44 -1
- package/dist/cli/chunk-C266QOQU.js.map +1 -0
- package/dist/cli/index.js +1751 -374
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-75XLIUTO.js → prompt-OVVMCH5F.js} +2 -2
- package/dist/index.d.ts +210 -1
- package/dist/index.js +723 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-ANMDY236.js.map +0 -1
- /package/dist/cli/{prompt-75XLIUTO.js.map → prompt-OVVMCH5F.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
memoryEnabled,
|
|
8
8
|
readProjectMemory,
|
|
9
9
|
sanitizeMemoryName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-C266QOQU.js";
|
|
11
11
|
|
|
12
12
|
// src/cli/index.ts
|
|
13
13
|
import { Command } from "commander";
|
|
@@ -67,6 +67,33 @@ function addProjectShellAllowed(rootDir, prefix, path = defaultConfigPath()) {
|
|
|
67
67
|
cfg.projects[rootDir].shellAllowed = [...existing, trimmed];
|
|
68
68
|
writeConfig(cfg, path);
|
|
69
69
|
}
|
|
70
|
+
function loadEditMode(path = defaultConfigPath()) {
|
|
71
|
+
const v = readConfig(path).editMode;
|
|
72
|
+
return v === "auto" ? "auto" : "review";
|
|
73
|
+
}
|
|
74
|
+
function saveEditMode(mode, path = defaultConfigPath()) {
|
|
75
|
+
const cfg = readConfig(path);
|
|
76
|
+
cfg.editMode = mode;
|
|
77
|
+
writeConfig(cfg, path);
|
|
78
|
+
}
|
|
79
|
+
function editModeHintShown(path = defaultConfigPath()) {
|
|
80
|
+
return readConfig(path).editModeHintShown === true;
|
|
81
|
+
}
|
|
82
|
+
function loadReasoningEffort(path = defaultConfigPath()) {
|
|
83
|
+
const v = readConfig(path).reasoningEffort;
|
|
84
|
+
return v === "high" ? "high" : "max";
|
|
85
|
+
}
|
|
86
|
+
function saveReasoningEffort(effort, path = defaultConfigPath()) {
|
|
87
|
+
const cfg = readConfig(path);
|
|
88
|
+
cfg.reasoningEffort = effort;
|
|
89
|
+
writeConfig(cfg, path);
|
|
90
|
+
}
|
|
91
|
+
function markEditModeHintShown(path = defaultConfigPath()) {
|
|
92
|
+
const cfg = readConfig(path);
|
|
93
|
+
if (cfg.editModeHintShown === true) return;
|
|
94
|
+
cfg.editModeHintShown = true;
|
|
95
|
+
writeConfig(cfg, path);
|
|
96
|
+
}
|
|
70
97
|
function isPlausibleKey(key) {
|
|
71
98
|
const trimmed = key.trim();
|
|
72
99
|
return /^sk-[A-Za-z0-9_-]{16,}$/.test(trimmed);
|
|
@@ -126,8 +153,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
126
153
|
}
|
|
127
154
|
function sleep(ms, signal) {
|
|
128
155
|
if (ms <= 0) return Promise.resolve();
|
|
129
|
-
return new Promise((
|
|
130
|
-
const timer = setTimeout(
|
|
156
|
+
return new Promise((resolve8, reject) => {
|
|
157
|
+
const timer = setTimeout(resolve8, ms);
|
|
131
158
|
if (signal) {
|
|
132
159
|
const onAbort = () => {
|
|
133
160
|
clearTimeout(timer);
|
|
@@ -612,7 +639,7 @@ function matchesTool(hook, toolName) {
|
|
|
612
639
|
}
|
|
613
640
|
}
|
|
614
641
|
function defaultSpawner(input) {
|
|
615
|
-
return new Promise((
|
|
642
|
+
return new Promise((resolve8) => {
|
|
616
643
|
const child = spawn(input.command, {
|
|
617
644
|
cwd: input.cwd,
|
|
618
645
|
shell: true,
|
|
@@ -639,7 +666,7 @@ function defaultSpawner(input) {
|
|
|
639
666
|
});
|
|
640
667
|
child.once("error", (err) => {
|
|
641
668
|
clearTimeout(timer);
|
|
642
|
-
|
|
669
|
+
resolve8({
|
|
643
670
|
exitCode: null,
|
|
644
671
|
stdout: stdout2,
|
|
645
672
|
stderr,
|
|
@@ -649,7 +676,7 @@ function defaultSpawner(input) {
|
|
|
649
676
|
});
|
|
650
677
|
child.once("close", (code) => {
|
|
651
678
|
clearTimeout(timer);
|
|
652
|
-
|
|
679
|
+
resolve8({
|
|
653
680
|
exitCode: code,
|
|
654
681
|
stdout: stdout2.trim(),
|
|
655
682
|
stderr: stderr.trim(),
|
|
@@ -979,6 +1006,12 @@ var ToolRegistry = class {
|
|
|
979
1006
|
* bounced until the user approves a submitted plan.
|
|
980
1007
|
*/
|
|
981
1008
|
_planMode = false;
|
|
1009
|
+
/**
|
|
1010
|
+
* Optional hook run after arg parsing but before tool.fn. Lets the TUI
|
|
1011
|
+
* reroute specific tool calls (e.g. edit_file in review mode) without
|
|
1012
|
+
* modifying the tool definitions themselves.
|
|
1013
|
+
*/
|
|
1014
|
+
_interceptor = null;
|
|
982
1015
|
constructor(opts = {}) {
|
|
983
1016
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
984
1017
|
}
|
|
@@ -990,6 +1023,14 @@ var ToolRegistry = class {
|
|
|
990
1023
|
get planMode() {
|
|
991
1024
|
return this._planMode;
|
|
992
1025
|
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Install or clear the dispatch interceptor. At most one interceptor
|
|
1028
|
+
* is active at a time — calling twice replaces the previous. Pass
|
|
1029
|
+
* `null` to remove.
|
|
1030
|
+
*/
|
|
1031
|
+
setToolInterceptor(fn) {
|
|
1032
|
+
this._interceptor = fn;
|
|
1033
|
+
}
|
|
993
1034
|
register(def) {
|
|
994
1035
|
if (!def.name) throw new Error("tool requires a name");
|
|
995
1036
|
const internal = { ...def };
|
|
@@ -1046,6 +1087,16 @@ var ToolRegistry = class {
|
|
|
1046
1087
|
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.`
|
|
1047
1088
|
});
|
|
1048
1089
|
}
|
|
1090
|
+
if (this._interceptor) {
|
|
1091
|
+
try {
|
|
1092
|
+
const short = await this._interceptor(name, args);
|
|
1093
|
+
if (typeof short === "string") return short;
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
return JSON.stringify({
|
|
1096
|
+
error: `${name}: interceptor failed \u2014 ${err.message}`
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1049
1100
|
try {
|
|
1050
1101
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
1051
1102
|
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
@@ -1779,6 +1830,7 @@ function round(n, digits) {
|
|
|
1779
1830
|
}
|
|
1780
1831
|
|
|
1781
1832
|
// src/loop.ts
|
|
1833
|
+
var ARGS_COMPACT_THRESHOLD_TOKENS = 800;
|
|
1782
1834
|
var CacheFirstLoop = class {
|
|
1783
1835
|
client;
|
|
1784
1836
|
prefix;
|
|
@@ -1882,12 +1934,62 @@ var CacheFirstLoop = class {
|
|
|
1882
1934
|
* authored intent we can't mechanically shrink without losing
|
|
1883
1935
|
* meaning.
|
|
1884
1936
|
*/
|
|
1885
|
-
|
|
1937
|
+
/**
|
|
1938
|
+
* Conservative args-only shrink fired after every tool response —
|
|
1939
|
+
* strictly about ONE thing: stop oversized `edit_file` / `write_file`
|
|
1940
|
+
* arguments from riding every future turn's prompt.
|
|
1941
|
+
*
|
|
1942
|
+
* Why this is worth doing AUTOMATICALLY (not just on /compact):
|
|
1943
|
+
* Each tool-call arguments string sticks in the log verbatim. On a
|
|
1944
|
+
* coding session with ~10 edits, that's 20-40K tokens of stale
|
|
1945
|
+
* SEARCH/REPLACE text riding along on every turn. Even at a 98.9%
|
|
1946
|
+
* cache hit rate the input cost still adds up linearly (cache-hit
|
|
1947
|
+
* price × tokens × turns). Compacting IMMEDIATELY after the tool
|
|
1948
|
+
* responds means the next turn's prompt is already smaller — the
|
|
1949
|
+
* shrink is a one-time write that saves every future prompt.
|
|
1950
|
+
*
|
|
1951
|
+
* Threshold rationale: 800 tokens ≈ 3 KB. A typical 20-line edit's
|
|
1952
|
+
* args land well under that; massive rewrites (whole-file content,
|
|
1953
|
+
* 100+ line refactors) land above and get the compaction. Small
|
|
1954
|
+
* edits stay byte-verbatim so nothing common-case changes.
|
|
1955
|
+
*
|
|
1956
|
+
* Safety: we ONLY shrink args whose tool has ALREADY responded.
|
|
1957
|
+
* Structurally that's every call in `log.toMessages()` at this
|
|
1958
|
+
* point — the current turn's assistant/tool pairing is by
|
|
1959
|
+
* construction closed by the time we get here (append happens
|
|
1960
|
+
* AFTER dispatch). The in-flight assistant message being built
|
|
1961
|
+
* lives in scratch, not the log, so this pass can't touch it.
|
|
1962
|
+
*
|
|
1963
|
+
* Model impact: the model may occasionally want to reference the
|
|
1964
|
+
* exact SEARCH text of a prior edit — it then reads the file
|
|
1965
|
+
* directly (which shows current state) or looks at the preceding
|
|
1966
|
+
* assistant text (which has its plan). Losing the stale args is a
|
|
1967
|
+
* net win: one extra read_file vs. dragging N KB of stale text
|
|
1968
|
+
* through every subsequent turn.
|
|
1969
|
+
*/
|
|
1970
|
+
compactToolCallArgsAfterResponse() {
|
|
1886
1971
|
const before = this.log.toMessages();
|
|
1887
|
-
const { messages, healedCount
|
|
1972
|
+
const { messages, healedCount } = shrinkOversizedToolCallArgsByTokens(
|
|
1888
1973
|
before,
|
|
1889
|
-
|
|
1974
|
+
ARGS_COMPACT_THRESHOLD_TOKENS
|
|
1890
1975
|
);
|
|
1976
|
+
if (healedCount === 0) return;
|
|
1977
|
+
this.log.compactInPlace(messages);
|
|
1978
|
+
if (this.sessionName) {
|
|
1979
|
+
try {
|
|
1980
|
+
rewriteSession(this.sessionName, messages);
|
|
1981
|
+
} catch {
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
compact(maxTokens = 4e3) {
|
|
1986
|
+
const before = this.log.toMessages();
|
|
1987
|
+
const resultsPass = shrinkOversizedToolResultsByTokens(before, maxTokens);
|
|
1988
|
+
const argsPass = shrinkOversizedToolCallArgsByTokens(resultsPass.messages, maxTokens);
|
|
1989
|
+
const messages = argsPass.messages;
|
|
1990
|
+
const healedCount = resultsPass.healedCount + argsPass.healedCount;
|
|
1991
|
+
const tokensSaved = resultsPass.tokensSaved + argsPass.tokensSaved;
|
|
1992
|
+
const charsSaved = resultsPass.charsSaved + argsPass.charsSaved;
|
|
1891
1993
|
if (healedCount > 0) {
|
|
1892
1994
|
this.log.compactInPlace(messages);
|
|
1893
1995
|
if (this.sessionName) {
|
|
@@ -2130,8 +2232,8 @@ var CacheFirstLoop = class {
|
|
|
2130
2232
|
}
|
|
2131
2233
|
);
|
|
2132
2234
|
for (let k = 0; k < budget; k++) {
|
|
2133
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2134
|
-
waiter =
|
|
2235
|
+
const sample = queue.shift() ?? await new Promise((resolve8) => {
|
|
2236
|
+
waiter = resolve8;
|
|
2135
2237
|
});
|
|
2136
2238
|
yield {
|
|
2137
2239
|
turn: this._turn,
|
|
@@ -2397,6 +2499,7 @@ ${reason}`;
|
|
|
2397
2499
|
name,
|
|
2398
2500
|
content: result
|
|
2399
2501
|
});
|
|
2502
|
+
this.compactToolCallArgsAfterResponse();
|
|
2400
2503
|
yield {
|
|
2401
2504
|
turn: this._turn,
|
|
2402
2505
|
role: "tool",
|
|
@@ -2582,6 +2685,56 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
|
2582
2685
|
});
|
|
2583
2686
|
return { messages: out, healedCount, tokensSaved, charsSaved };
|
|
2584
2687
|
}
|
|
2688
|
+
function shrinkOversizedToolCallArgsByTokens(messages, maxTokens) {
|
|
2689
|
+
let healedCount = 0;
|
|
2690
|
+
let tokensSaved = 0;
|
|
2691
|
+
let charsSaved = 0;
|
|
2692
|
+
const out = messages.map((msg) => {
|
|
2693
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) return msg;
|
|
2694
|
+
let changed = false;
|
|
2695
|
+
const newCalls = msg.tool_calls.map((call) => {
|
|
2696
|
+
const args = call.function?.arguments;
|
|
2697
|
+
if (typeof args !== "string" || args.length <= maxTokens) return call;
|
|
2698
|
+
const beforeTokens = countTokens(args);
|
|
2699
|
+
if (beforeTokens <= maxTokens) return call;
|
|
2700
|
+
const shrunk = shrinkJsonLongStrings(args);
|
|
2701
|
+
const afterTokens = countTokens(shrunk);
|
|
2702
|
+
if (afterTokens >= beforeTokens) return call;
|
|
2703
|
+
changed = true;
|
|
2704
|
+
healedCount += 1;
|
|
2705
|
+
tokensSaved += beforeTokens - afterTokens;
|
|
2706
|
+
charsSaved += args.length - shrunk.length;
|
|
2707
|
+
return { ...call, function: { ...call.function, arguments: shrunk } };
|
|
2708
|
+
});
|
|
2709
|
+
if (!changed) return msg;
|
|
2710
|
+
return { ...msg, tool_calls: newCalls };
|
|
2711
|
+
});
|
|
2712
|
+
return { messages: out, healedCount, tokensSaved, charsSaved };
|
|
2713
|
+
}
|
|
2714
|
+
function shrinkJsonLongStrings(jsonStr) {
|
|
2715
|
+
let parsed;
|
|
2716
|
+
try {
|
|
2717
|
+
parsed = JSON.parse(jsonStr);
|
|
2718
|
+
} catch {
|
|
2719
|
+
const head = jsonStr.slice(0, 200);
|
|
2720
|
+
return `${head}\u2026[shrunk: ${jsonStr.length} chars, unparsed]`;
|
|
2721
|
+
}
|
|
2722
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2723
|
+
return jsonStr;
|
|
2724
|
+
}
|
|
2725
|
+
const LONG_THRESHOLD = 300;
|
|
2726
|
+
const input = parsed;
|
|
2727
|
+
const output = {};
|
|
2728
|
+
for (const [k, v] of Object.entries(input)) {
|
|
2729
|
+
if (typeof v === "string" && v.length > LONG_THRESHOLD) {
|
|
2730
|
+
const newlines = v.match(/\n/g)?.length ?? 0;
|
|
2731
|
+
output[k] = `[\u2026shrunk: ${v.length} chars, ${newlines} lines \u2014 tool already responded, see result]`;
|
|
2732
|
+
} else {
|
|
2733
|
+
output[k] = v;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return JSON.stringify(output);
|
|
2737
|
+
}
|
|
2585
2738
|
function fixToolCallPairing(messages) {
|
|
2586
2739
|
const out = [];
|
|
2587
2740
|
let droppedAssistantCalls = 0;
|
|
@@ -2876,6 +3029,9 @@ import { promises as fs } from "fs";
|
|
|
2876
3029
|
import * as pathMod from "path";
|
|
2877
3030
|
var DEFAULT_MAX_READ_BYTES = 2 * 1024 * 1024;
|
|
2878
3031
|
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
3032
|
+
var DEFAULT_AUTO_PREVIEW_LINES = 200;
|
|
3033
|
+
var AUTO_PREVIEW_HEAD_LINES = 80;
|
|
3034
|
+
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
2879
3035
|
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
2880
3036
|
"node_modules",
|
|
2881
3037
|
".git",
|
|
@@ -2968,14 +3124,22 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2968
3124
|
};
|
|
2969
3125
|
registry.register({
|
|
2970
3126
|
name: "read_file",
|
|
2971
|
-
description:
|
|
3127
|
+
description: `Read a file under the sandbox root. To save context, PREFER to scope the read instead of pulling the whole file:
|
|
3128
|
+
- head: N \u2192 first N lines (imports, public API, small configs)
|
|
3129
|
+
- tail: N \u2192 last N lines (recently-added code, log tails)
|
|
3130
|
+
- range: "A-B" \u2192 inclusive line range A..B, 1-indexed (e.g. "120-180" around an edit site)
|
|
3131
|
+
When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker rather than dumping everything. If you need the middle, re-call with a range. Prefer search_content to locate a symbol first, then read_file with a range around the hit \u2014 one scoped read beats three full-file reads.`,
|
|
2972
3132
|
readOnly: true,
|
|
2973
3133
|
parameters: {
|
|
2974
3134
|
type: "object",
|
|
2975
3135
|
properties: {
|
|
2976
3136
|
path: { type: "string", description: "Path to read (relative to rootDir or absolute)." },
|
|
2977
3137
|
head: { type: "integer", description: "If set, return only the first N lines." },
|
|
2978
|
-
tail: { type: "integer", description: "If set, return only the last N lines." }
|
|
3138
|
+
tail: { type: "integer", description: "If set, return only the last N lines." },
|
|
3139
|
+
range: {
|
|
3140
|
+
type: "string",
|
|
3141
|
+
description: 'Inclusive line range like "50-100" or "50-50". 1-indexed. Takes precedence over head/tail when all three are set. Out-of-range requests clamp to file bounds.'
|
|
3142
|
+
}
|
|
2979
3143
|
},
|
|
2980
3144
|
required: ["path"]
|
|
2981
3145
|
},
|
|
@@ -2987,21 +3151,52 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2987
3151
|
}
|
|
2988
3152
|
const raw = await fs.readFile(abs);
|
|
2989
3153
|
if (raw.length > maxReadBytes) {
|
|
2990
|
-
const
|
|
2991
|
-
return `${
|
|
3154
|
+
const headBytes = raw.slice(0, maxReadBytes).toString("utf8");
|
|
3155
|
+
return `${headBytes}
|
|
2992
3156
|
|
|
2993
|
-
[\u2026truncated ${raw.length - maxReadBytes} bytes \u2014 file is ${raw.length} B, cap ${maxReadBytes} B. Retry with head/tail for targeted view.]`;
|
|
3157
|
+
[\u2026truncated ${raw.length - maxReadBytes} bytes \u2014 file is ${raw.length} B, cap ${maxReadBytes} B. Retry with head/tail/range for targeted view.]`;
|
|
2994
3158
|
}
|
|
2995
3159
|
const text = raw.toString("utf8");
|
|
3160
|
+
let lines = text.split(/\r?\n/);
|
|
3161
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
3162
|
+
const totalLines = lines.length;
|
|
3163
|
+
if (typeof args.range === "string" && /^\d+\s*-\s*\d+$/.test(args.range)) {
|
|
3164
|
+
const [rawStart, rawEnd] = args.range.split("-").map((s) => Number.parseInt(s, 10));
|
|
3165
|
+
const start = Math.max(1, rawStart ?? 1);
|
|
3166
|
+
const end = Math.min(totalLines, Math.max(start, rawEnd ?? totalLines));
|
|
3167
|
+
const slice = lines.slice(start - 1, end);
|
|
3168
|
+
const label = `[range ${start}-${end} of ${totalLines} lines]`;
|
|
3169
|
+
return `${label}
|
|
3170
|
+
${slice.join("\n")}`;
|
|
3171
|
+
}
|
|
2996
3172
|
if (typeof args.head === "number" && args.head > 0) {
|
|
2997
|
-
|
|
3173
|
+
const count = Math.min(args.head, totalLines);
|
|
3174
|
+
const slice = lines.slice(0, count);
|
|
3175
|
+
const marker = count < totalLines ? `
|
|
3176
|
+
|
|
3177
|
+
[\u2026head ${count} of ${totalLines} lines \u2014 call again with range / tail for more]` : "";
|
|
3178
|
+
return slice.join("\n") + marker;
|
|
2998
3179
|
}
|
|
2999
3180
|
if (typeof args.tail === "number" && args.tail > 0) {
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3181
|
+
const count = Math.min(args.tail, totalLines);
|
|
3182
|
+
const slice = lines.slice(totalLines - count);
|
|
3183
|
+
const marker = count < totalLines ? `[\u2026tail ${count} of ${totalLines} lines \u2014 call again with range / head for more]
|
|
3184
|
+
|
|
3185
|
+
` : "";
|
|
3186
|
+
return marker + slice.join("\n");
|
|
3187
|
+
}
|
|
3188
|
+
if (totalLines <= DEFAULT_AUTO_PREVIEW_LINES) return lines.join("\n");
|
|
3189
|
+
const head = lines.slice(0, AUTO_PREVIEW_HEAD_LINES).join("\n");
|
|
3190
|
+
const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
|
|
3191
|
+
const omitted = totalLines - AUTO_PREVIEW_HEAD_LINES - AUTO_PREVIEW_TAIL_LINES;
|
|
3192
|
+
return [
|
|
3193
|
+
`[auto-preview: head ${AUTO_PREVIEW_HEAD_LINES} + tail ${AUTO_PREVIEW_TAIL_LINES} of ${totalLines} lines]`,
|
|
3194
|
+
head,
|
|
3195
|
+
`
|
|
3196
|
+
[\u2026 ${omitted} lines omitted \u2014 call read_file again with range:"A-B" (1-indexed) or head / tail to get the middle]
|
|
3197
|
+
`,
|
|
3198
|
+
tail
|
|
3199
|
+
].join("\n");
|
|
3005
3200
|
}
|
|
3006
3201
|
});
|
|
3007
3202
|
registry.register({
|
|
@@ -3026,21 +3221,34 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3026
3221
|
});
|
|
3027
3222
|
registry.register({
|
|
3028
3223
|
name: "directory_tree",
|
|
3029
|
-
description:
|
|
3224
|
+
description: `Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Budget-aware by default:
|
|
3225
|
+
- maxDepth defaults to 2 (root + one level). A depth-4 tree on a real repo blew ~5K tokens in one call. If you truly need deeper, pass maxDepth:N explicitly.
|
|
3226
|
+
- Skips ${[...SKIP_DIR_NAMES].sort().join(", ")} unless include_deps:true. Traversing into node_modules / .git / dist is almost always token-waste.
|
|
3227
|
+
- Large subtrees (>50 children) auto-collapse to "[N files, M dirs hidden \u2014 list_directory <path> to inspect]" so one huge folder can't dominate the output.
|
|
3228
|
+
Prefer \`list_directory\` for a single-level view, \`search_files\` to find specific paths, and \`search_content\` to find code.`,
|
|
3030
3229
|
readOnly: true,
|
|
3031
3230
|
parameters: {
|
|
3032
3231
|
type: "object",
|
|
3033
3232
|
properties: {
|
|
3034
3233
|
path: { type: "string", description: "Root of the tree (default: sandbox root)." },
|
|
3035
|
-
maxDepth: {
|
|
3234
|
+
maxDepth: {
|
|
3235
|
+
type: "integer",
|
|
3236
|
+
description: "Max recursion depth (default 2). Depth 0 shows only the top-level entries; depth 2 is usually enough to see module structure."
|
|
3237
|
+
},
|
|
3238
|
+
include_deps: {
|
|
3239
|
+
type: "boolean",
|
|
3240
|
+
description: "When true, also traverse node_modules / .git / dist / build / etc. Off by default \u2014 most exploration questions are about the user's own code."
|
|
3241
|
+
}
|
|
3036
3242
|
}
|
|
3037
3243
|
},
|
|
3038
3244
|
fn: async (args) => {
|
|
3039
3245
|
const startAbs = safePath(args.path ?? ".");
|
|
3040
|
-
const maxDepth = typeof args.maxDepth === "number" ? args.maxDepth :
|
|
3246
|
+
const maxDepth = typeof args.maxDepth === "number" ? args.maxDepth : 2;
|
|
3247
|
+
const includeDeps = args.include_deps === true;
|
|
3041
3248
|
const lines = [];
|
|
3042
3249
|
let totalBytes = 0;
|
|
3043
3250
|
let truncated = false;
|
|
3251
|
+
const PER_DIR_CHILD_CAP = 50;
|
|
3044
3252
|
const walk2 = async (dir, depth) => {
|
|
3045
3253
|
if (truncated) return;
|
|
3046
3254
|
if (depth > maxDepth) return;
|
|
@@ -3051,10 +3259,27 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3051
3259
|
return;
|
|
3052
3260
|
}
|
|
3053
3261
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3262
|
+
let emitted = 0;
|
|
3054
3263
|
for (const e of entries) {
|
|
3055
3264
|
if (truncated) return;
|
|
3265
|
+
const skip = e.isDirectory() && !includeDeps && SKIP_DIR_NAMES.has(e.name);
|
|
3266
|
+
if (emitted >= PER_DIR_CHILD_CAP) {
|
|
3267
|
+
const remaining = entries.length - emitted;
|
|
3268
|
+
let restFiles = 0;
|
|
3269
|
+
let restDirs = 0;
|
|
3270
|
+
for (const r of entries.slice(emitted)) {
|
|
3271
|
+
if (r.isDirectory()) restDirs++;
|
|
3272
|
+
else restFiles++;
|
|
3273
|
+
}
|
|
3274
|
+
const indent2 = " ".repeat(depth);
|
|
3275
|
+
lines.push(
|
|
3276
|
+
`${indent2}[\u2026 ${remaining} entries hidden (${restDirs} dirs, ${restFiles} files) \u2014 list_directory on this path to see all]`
|
|
3277
|
+
);
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3056
3280
|
const indent = " ".repeat(depth);
|
|
3057
|
-
const
|
|
3281
|
+
const suffix = skip ? " (skipped \u2014 pass include_deps:true to traverse)" : "";
|
|
3282
|
+
const line = e.isDirectory() ? `${indent}${e.name}/${suffix}` : `${indent}${e.name}`;
|
|
3058
3283
|
totalBytes += line.length + 1;
|
|
3059
3284
|
if (totalBytes > maxListBytes) {
|
|
3060
3285
|
lines.push(` [\u2026 tree truncated at ${maxListBytes} bytes \u2026]`);
|
|
@@ -3062,7 +3287,8 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3062
3287
|
return;
|
|
3063
3288
|
}
|
|
3064
3289
|
lines.push(line);
|
|
3065
|
-
|
|
3290
|
+
emitted++;
|
|
3291
|
+
if (e.isDirectory() && !skip) {
|
|
3066
3292
|
await walk2(pathMod.join(dir, e.name), depth + 1);
|
|
3067
3293
|
}
|
|
3068
3294
|
}
|
|
@@ -3704,9 +3930,311 @@ function forkRegistryExcluding(parent, exclude) {
|
|
|
3704
3930
|
}
|
|
3705
3931
|
|
|
3706
3932
|
// src/tools/shell.ts
|
|
3707
|
-
import { spawn as
|
|
3933
|
+
import { spawn as spawn3 } from "child_process";
|
|
3708
3934
|
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
3935
|
+
import * as pathMod3 from "path";
|
|
3936
|
+
|
|
3937
|
+
// src/tools/jobs.ts
|
|
3938
|
+
import { spawn as spawn2 } from "child_process";
|
|
3709
3939
|
import * as pathMod2 from "path";
|
|
3940
|
+
function killProcessTree(pid, signal) {
|
|
3941
|
+
if (process.platform === "win32") {
|
|
3942
|
+
const args = ["/pid", String(pid), "/T"];
|
|
3943
|
+
if (signal === "SIGKILL") args.push("/F");
|
|
3944
|
+
try {
|
|
3945
|
+
const killer = spawn2("taskkill", args, {
|
|
3946
|
+
stdio: "ignore",
|
|
3947
|
+
windowsHide: true
|
|
3948
|
+
});
|
|
3949
|
+
killer.on("error", () => {
|
|
3950
|
+
});
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
try {
|
|
3956
|
+
process.kill(-pid, signal);
|
|
3957
|
+
return;
|
|
3958
|
+
} catch {
|
|
3959
|
+
}
|
|
3960
|
+
try {
|
|
3961
|
+
process.kill(pid, signal);
|
|
3962
|
+
} catch {
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
var DEFAULT_OUTPUT_CAP_BYTES = 64 * 1024;
|
|
3966
|
+
var READY_SIGNALS = [
|
|
3967
|
+
// HTTP server banners
|
|
3968
|
+
/\blistening on\b/i,
|
|
3969
|
+
/\blocal:\s+https?:\/\//i,
|
|
3970
|
+
/\bhttps?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?\b/i,
|
|
3971
|
+
/\b(?:ready|server started|started server|app listening)\b/i,
|
|
3972
|
+
// Bundlers / compilers
|
|
3973
|
+
/\bcompiled successfully\b/i,
|
|
3974
|
+
/\bbuild complete(?:d)?\b/i,
|
|
3975
|
+
/\bwatching for (?:file )?changes\b/i,
|
|
3976
|
+
/\bready in \d+/i,
|
|
3977
|
+
// Generic
|
|
3978
|
+
/\bstartup (?:complete|finished)\b/i
|
|
3979
|
+
];
|
|
3980
|
+
var JobRegistry = class {
|
|
3981
|
+
jobs = /* @__PURE__ */ new Map();
|
|
3982
|
+
nextId = 1;
|
|
3983
|
+
/**
|
|
3984
|
+
* Spawn a background child. Resolves after `waitSec` OR on ready
|
|
3985
|
+
* signal OR on early exit, whichever comes first. The child continues
|
|
3986
|
+
* to run (and buffer output) regardless of which path fires.
|
|
3987
|
+
*/
|
|
3988
|
+
async start(command, opts) {
|
|
3989
|
+
const trimmed = command.trim();
|
|
3990
|
+
if (!trimmed) throw new Error("run_background: empty command");
|
|
3991
|
+
const op = detectShellOperator(trimmed);
|
|
3992
|
+
if (op !== null) {
|
|
3993
|
+
throw new Error(
|
|
3994
|
+
`run_background: shell operator "${op}" is not supported \u2014 spawn one process per background job. Compose via your orchestration, not the shell.`
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
const argv = tokenizeCommand(trimmed);
|
|
3998
|
+
if (argv.length === 0) throw new Error("run_background: empty command");
|
|
3999
|
+
const waitMs = Math.max(0, Math.min(30, opts.waitSec ?? 3)) * 1e3;
|
|
4000
|
+
const maxBytes = opts.maxBufferBytes ?? DEFAULT_OUTPUT_CAP_BYTES;
|
|
4001
|
+
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
4002
|
+
const spawnOpts = {
|
|
4003
|
+
cwd: pathMod2.resolve(opts.cwd),
|
|
4004
|
+
shell: false,
|
|
4005
|
+
windowsHide: true,
|
|
4006
|
+
env: process.env,
|
|
4007
|
+
// POSIX: detach so the child becomes its own process-group leader.
|
|
4008
|
+
// Required for `process.kill(-pid, …)` later — without it a group
|
|
4009
|
+
// kill fails and we end up only signaling the wrapper, leaving
|
|
4010
|
+
// grandchildren (node → vite → esbuild …) orphaned.
|
|
4011
|
+
// Windows: detached would spawn a new console window; leave the
|
|
4012
|
+
// default and use taskkill /T for tree termination.
|
|
4013
|
+
detached: process.platform !== "win32",
|
|
4014
|
+
...spawnOverrides
|
|
4015
|
+
};
|
|
4016
|
+
let child;
|
|
4017
|
+
try {
|
|
4018
|
+
child = spawn2(bin, args, spawnOpts);
|
|
4019
|
+
} catch (err) {
|
|
4020
|
+
const id2 = this.nextId++;
|
|
4021
|
+
const job2 = {
|
|
4022
|
+
id: id2,
|
|
4023
|
+
command: trimmed,
|
|
4024
|
+
pid: null,
|
|
4025
|
+
startedAt: Date.now(),
|
|
4026
|
+
exitCode: null,
|
|
4027
|
+
output: `[spawn failed] ${err.message}`,
|
|
4028
|
+
totalBytesWritten: 0,
|
|
4029
|
+
running: false,
|
|
4030
|
+
spawnError: err.message,
|
|
4031
|
+
child: null,
|
|
4032
|
+
readyPromise: Promise.resolve(),
|
|
4033
|
+
signalReady: () => {
|
|
4034
|
+
}
|
|
4035
|
+
};
|
|
4036
|
+
this.jobs.set(id2, job2);
|
|
4037
|
+
return {
|
|
4038
|
+
jobId: id2,
|
|
4039
|
+
pid: null,
|
|
4040
|
+
stillRunning: false,
|
|
4041
|
+
readyMatched: false,
|
|
4042
|
+
preview: job2.output,
|
|
4043
|
+
exitCode: null
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
const id = this.nextId++;
|
|
4047
|
+
let readyResolve = () => {
|
|
4048
|
+
};
|
|
4049
|
+
const readyPromise = new Promise((res) => {
|
|
4050
|
+
readyResolve = res;
|
|
4051
|
+
});
|
|
4052
|
+
const job = {
|
|
4053
|
+
id,
|
|
4054
|
+
command: trimmed,
|
|
4055
|
+
pid: child.pid ?? null,
|
|
4056
|
+
startedAt: Date.now(),
|
|
4057
|
+
exitCode: null,
|
|
4058
|
+
output: "",
|
|
4059
|
+
totalBytesWritten: 0,
|
|
4060
|
+
running: true,
|
|
4061
|
+
child,
|
|
4062
|
+
readyPromise,
|
|
4063
|
+
signalReady: readyResolve
|
|
4064
|
+
};
|
|
4065
|
+
this.jobs.set(id, job);
|
|
4066
|
+
let readyMatched = false;
|
|
4067
|
+
const onData = (chunk) => {
|
|
4068
|
+
const s = chunk.toString();
|
|
4069
|
+
job.totalBytesWritten += s.length;
|
|
4070
|
+
job.output += s;
|
|
4071
|
+
if (job.output.length > maxBytes) {
|
|
4072
|
+
const overflow = job.output.length - maxBytes;
|
|
4073
|
+
const cut = job.output.indexOf("\n", overflow);
|
|
4074
|
+
const start = cut >= 0 ? cut + 1 : overflow;
|
|
4075
|
+
job.output = `[\u2026 older output dropped \u2026]
|
|
4076
|
+
${job.output.slice(start)}`;
|
|
4077
|
+
}
|
|
4078
|
+
if (!readyMatched) {
|
|
4079
|
+
for (const re of READY_SIGNALS) {
|
|
4080
|
+
if (re.test(s) || re.test(job.output)) {
|
|
4081
|
+
readyMatched = true;
|
|
4082
|
+
job.signalReady();
|
|
4083
|
+
break;
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
4088
|
+
child.stdout?.on("data", onData);
|
|
4089
|
+
child.stderr?.on("data", onData);
|
|
4090
|
+
child.on("error", (err) => {
|
|
4091
|
+
job.running = false;
|
|
4092
|
+
job.spawnError = err.message;
|
|
4093
|
+
job.signalReady();
|
|
4094
|
+
});
|
|
4095
|
+
child.on("close", (code) => {
|
|
4096
|
+
job.running = false;
|
|
4097
|
+
job.exitCode = code;
|
|
4098
|
+
job.signalReady();
|
|
4099
|
+
});
|
|
4100
|
+
const onAbort = () => this.stop(id, { graceMs: 100 });
|
|
4101
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
4102
|
+
let timer = null;
|
|
4103
|
+
await Promise.race([
|
|
4104
|
+
readyPromise,
|
|
4105
|
+
new Promise((res) => {
|
|
4106
|
+
timer = setTimeout(res, waitMs);
|
|
4107
|
+
})
|
|
4108
|
+
]);
|
|
4109
|
+
if (timer) clearTimeout(timer);
|
|
4110
|
+
return {
|
|
4111
|
+
jobId: id,
|
|
4112
|
+
pid: job.pid,
|
|
4113
|
+
stillRunning: job.running,
|
|
4114
|
+
readyMatched,
|
|
4115
|
+
preview: job.output,
|
|
4116
|
+
exitCode: job.exitCode
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
/**
|
|
4120
|
+
* Read a job's accumulated output. `since` lets a caller poll
|
|
4121
|
+
* incrementally: pass the byte count returned from the last call to
|
|
4122
|
+
* get only newly-written content. Returns both full output and a
|
|
4123
|
+
* running snapshot so the caller can use whichever.
|
|
4124
|
+
*/
|
|
4125
|
+
read(id, opts = {}) {
|
|
4126
|
+
const job = this.jobs.get(id);
|
|
4127
|
+
if (!job) return null;
|
|
4128
|
+
const full = job.output;
|
|
4129
|
+
let slice = full;
|
|
4130
|
+
if (typeof opts.since === "number" && opts.since >= 0 && opts.since < full.length) {
|
|
4131
|
+
slice = full.slice(opts.since);
|
|
4132
|
+
}
|
|
4133
|
+
if (typeof opts.tailLines === "number" && opts.tailLines > 0) {
|
|
4134
|
+
const lines = slice.split("\n");
|
|
4135
|
+
const keep = lines.slice(Math.max(0, lines.length - opts.tailLines));
|
|
4136
|
+
slice = keep.join("\n");
|
|
4137
|
+
}
|
|
4138
|
+
return {
|
|
4139
|
+
output: slice,
|
|
4140
|
+
byteLength: full.length,
|
|
4141
|
+
running: job.running,
|
|
4142
|
+
exitCode: job.exitCode,
|
|
4143
|
+
command: job.command,
|
|
4144
|
+
pid: job.pid,
|
|
4145
|
+
spawnError: job.spawnError
|
|
4146
|
+
};
|
|
4147
|
+
}
|
|
4148
|
+
/**
|
|
4149
|
+
* Send SIGTERM, wait `graceMs`, then SIGKILL if still alive. Returns
|
|
4150
|
+
* the final job record (or null when the job id is unknown). Safe to
|
|
4151
|
+
* call on an already-exited job — returns the record unchanged.
|
|
4152
|
+
*/
|
|
4153
|
+
async stop(id, opts = {}) {
|
|
4154
|
+
const job = this.jobs.get(id);
|
|
4155
|
+
if (!job) return null;
|
|
4156
|
+
if (!job.running || !job.child) return snapshot(job);
|
|
4157
|
+
const graceMs = Math.max(0, opts.graceMs ?? 2e3);
|
|
4158
|
+
if (job.pid !== null) {
|
|
4159
|
+
killProcessTree(job.pid, "SIGTERM");
|
|
4160
|
+
} else {
|
|
4161
|
+
try {
|
|
4162
|
+
job.child.kill("SIGTERM");
|
|
4163
|
+
} catch {
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
await Promise.race([job.readyPromise, new Promise((res) => setTimeout(res, graceMs))]);
|
|
4167
|
+
if (job.running) {
|
|
4168
|
+
if (job.pid !== null) {
|
|
4169
|
+
killProcessTree(job.pid, "SIGKILL");
|
|
4170
|
+
} else {
|
|
4171
|
+
try {
|
|
4172
|
+
job.child.kill("SIGKILL");
|
|
4173
|
+
} catch {
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
await new Promise((res) => setTimeout(res, 800));
|
|
4177
|
+
}
|
|
4178
|
+
return snapshot(job);
|
|
4179
|
+
}
|
|
4180
|
+
list() {
|
|
4181
|
+
return [...this.jobs.values()].map(snapshot);
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Best-effort kill of every still-running job. Called on TUI shutdown
|
|
4185
|
+
* so dev servers don't outlive the Reasonix process. Resolves after
|
|
4186
|
+
* every child has closed or a hard deadline passes (3s total).
|
|
4187
|
+
*/
|
|
4188
|
+
async shutdown(deadlineMs = 5e3) {
|
|
4189
|
+
const start = Date.now();
|
|
4190
|
+
const runningJobs = [...this.jobs.values()].filter((j) => j.running && j.child);
|
|
4191
|
+
if (runningJobs.length === 0) return;
|
|
4192
|
+
for (const job of runningJobs) {
|
|
4193
|
+
if (job.pid !== null) killProcessTree(job.pid, "SIGTERM");
|
|
4194
|
+
else
|
|
4195
|
+
try {
|
|
4196
|
+
job.child?.kill("SIGTERM");
|
|
4197
|
+
} catch {
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
const allClose = Promise.all(runningJobs.map((j) => j.readyPromise));
|
|
4201
|
+
const elapsed = () => Date.now() - start;
|
|
4202
|
+
const graceMs = Math.min(1500, Math.max(0, deadlineMs / 2));
|
|
4203
|
+
await Promise.race([allClose, new Promise((res) => setTimeout(res, graceMs))]);
|
|
4204
|
+
for (const job of runningJobs) {
|
|
4205
|
+
if (!job.running) continue;
|
|
4206
|
+
if (job.pid !== null) killProcessTree(job.pid, "SIGKILL");
|
|
4207
|
+
else
|
|
4208
|
+
try {
|
|
4209
|
+
job.child?.kill("SIGKILL");
|
|
4210
|
+
} catch {
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
const remaining = Math.max(800, deadlineMs - elapsed());
|
|
4214
|
+
await Promise.race([allClose, new Promise((res) => setTimeout(res, remaining))]);
|
|
4215
|
+
}
|
|
4216
|
+
/** Count of still-running jobs — drives the TUI status-bar indicator. */
|
|
4217
|
+
runningCount() {
|
|
4218
|
+
let n = 0;
|
|
4219
|
+
for (const job of this.jobs.values()) if (job.running) n++;
|
|
4220
|
+
return n;
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
4223
|
+
function snapshot(job) {
|
|
4224
|
+
return {
|
|
4225
|
+
id: job.id,
|
|
4226
|
+
command: job.command,
|
|
4227
|
+
pid: job.pid,
|
|
4228
|
+
startedAt: job.startedAt,
|
|
4229
|
+
exitCode: job.exitCode,
|
|
4230
|
+
output: job.output,
|
|
4231
|
+
totalBytesWritten: job.totalBytesWritten,
|
|
4232
|
+
running: job.running,
|
|
4233
|
+
spawnError: job.spawnError
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
// src/tools/shell.ts
|
|
3710
4238
|
var DEFAULT_TIMEOUT_SEC = 60;
|
|
3711
4239
|
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
3712
4240
|
var BUILTIN_ALLOWLIST = [
|
|
@@ -3875,10 +4403,10 @@ async function runCommand(cmd, opts) {
|
|
|
3875
4403
|
};
|
|
3876
4404
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
3877
4405
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
3878
|
-
return await new Promise((
|
|
4406
|
+
return await new Promise((resolve8, reject) => {
|
|
3879
4407
|
let child;
|
|
3880
4408
|
try {
|
|
3881
|
-
child =
|
|
4409
|
+
child = spawn3(bin, args, effectiveSpawnOpts);
|
|
3882
4410
|
} catch (err) {
|
|
3883
4411
|
reject(err);
|
|
3884
4412
|
return;
|
|
@@ -3908,7 +4436,7 @@ async function runCommand(cmd, opts) {
|
|
|
3908
4436
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
3909
4437
|
|
|
3910
4438
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
3911
|
-
|
|
4439
|
+
resolve8({ exitCode: code, output, timedOut });
|
|
3912
4440
|
});
|
|
3913
4441
|
});
|
|
3914
4442
|
}
|
|
@@ -3916,16 +4444,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
3916
4444
|
const platform = opts.platform ?? process.platform;
|
|
3917
4445
|
if (platform !== "win32") return cmd;
|
|
3918
4446
|
if (!cmd) return cmd;
|
|
3919
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
3920
|
-
if (
|
|
4447
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod3.isAbsolute(cmd)) return cmd;
|
|
4448
|
+
if (pathMod3.extname(cmd)) return cmd;
|
|
3921
4449
|
const env = opts.env ?? process.env;
|
|
3922
4450
|
const pathExt = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
3923
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
4451
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod3.delimiter);
|
|
3924
4452
|
const pathDirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
3925
4453
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
3926
4454
|
for (const dir of pathDirs) {
|
|
3927
4455
|
for (const ext of pathExt) {
|
|
3928
|
-
const full =
|
|
4456
|
+
const full = pathMod3.win32.join(dir, cmd + ext);
|
|
3929
4457
|
if (isFile(full)) return full;
|
|
3930
4458
|
}
|
|
3931
4459
|
}
|
|
@@ -3995,8 +4523,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
3995
4523
|
function isBareWindowsName(s) {
|
|
3996
4524
|
if (!s) return false;
|
|
3997
4525
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
3998
|
-
if (
|
|
3999
|
-
if (
|
|
4526
|
+
if (pathMod3.isAbsolute(s)) return false;
|
|
4527
|
+
if (pathMod3.extname(s)) return false;
|
|
4000
4528
|
return true;
|
|
4001
4529
|
}
|
|
4002
4530
|
function quoteForCmdExe(arg) {
|
|
@@ -4015,12 +4543,13 @@ var NeedsConfirmationError = class extends Error {
|
|
|
4015
4543
|
}
|
|
4016
4544
|
};
|
|
4017
4545
|
function registerShellTools(registry, opts) {
|
|
4018
|
-
const rootDir =
|
|
4546
|
+
const rootDir = pathMod3.resolve(opts.rootDir);
|
|
4019
4547
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
4020
4548
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
4549
|
+
const jobs = opts.jobs ?? new JobRegistry();
|
|
4021
4550
|
const getExtraAllowed = typeof opts.extraAllowed === "function" ? opts.extraAllowed : (() => {
|
|
4022
|
-
const
|
|
4023
|
-
return () =>
|
|
4551
|
+
const snapshot2 = opts.extraAllowed ?? [];
|
|
4552
|
+
return () => snapshot2;
|
|
4024
4553
|
})();
|
|
4025
4554
|
const allowAll = opts.allowAll ?? false;
|
|
4026
4555
|
registry.register({
|
|
@@ -4066,8 +4595,126 @@ function registerShellTools(registry, opts) {
|
|
|
4066
4595
|
return formatCommandResult(cmd, result);
|
|
4067
4596
|
}
|
|
4068
4597
|
});
|
|
4598
|
+
registry.register({
|
|
4599
|
+
name: "run_background",
|
|
4600
|
+
description: "Spawn a long-running process (dev server, watcher, any command that doesn't naturally exit) and detach. Waits up to `waitSec` seconds for startup (or until the output matches a readiness signal like 'Local:', 'listening on', 'compiled successfully'), then returns the job id + startup preview. The process keeps running; call `job_output` to tail its logs, `stop_job` to kill it, `list_jobs` to see all running jobs. USE THIS \u2014 not `run_command` \u2014 for: npm/yarn/pnpm run dev, uvicorn / flask run, go run, cargo watch, tsc --watch, webpack serve, anything with 'dev' / 'serve' / 'watch' in the name.",
|
|
4601
|
+
parameters: {
|
|
4602
|
+
type: "object",
|
|
4603
|
+
properties: {
|
|
4604
|
+
command: {
|
|
4605
|
+
type: "string",
|
|
4606
|
+
description: "Full command line. Same quoting rules as run_command (no pipes / redirects / chaining)."
|
|
4607
|
+
},
|
|
4608
|
+
waitSec: {
|
|
4609
|
+
type: "integer",
|
|
4610
|
+
description: "Max seconds to wait for startup before returning. 0..30, default 3. A ready-signal match short-circuits this."
|
|
4611
|
+
}
|
|
4612
|
+
},
|
|
4613
|
+
required: ["command"]
|
|
4614
|
+
},
|
|
4615
|
+
fn: async (args, ctx) => {
|
|
4616
|
+
const cmd = args.command.trim();
|
|
4617
|
+
if (!cmd) throw new Error("run_background: empty command");
|
|
4618
|
+
if (!allowAll && !isAllowed(cmd, getExtraAllowed())) {
|
|
4619
|
+
throw new NeedsConfirmationError(cmd);
|
|
4620
|
+
}
|
|
4621
|
+
const result = await jobs.start(cmd, {
|
|
4622
|
+
cwd: rootDir,
|
|
4623
|
+
waitSec: args.waitSec,
|
|
4624
|
+
signal: ctx?.signal
|
|
4625
|
+
});
|
|
4626
|
+
return formatJobStart(result);
|
|
4627
|
+
}
|
|
4628
|
+
});
|
|
4629
|
+
registry.register({
|
|
4630
|
+
name: "job_output",
|
|
4631
|
+
description: "Read the latest output of a background job started with `run_background`. By default returns the tail of the buffer (last 80 lines). Pass `since` (the `byteLength` from a previous call) to stream only new content incrementally. Tells you whether the job is still running, so you can stop polling when it's done.",
|
|
4632
|
+
readOnly: true,
|
|
4633
|
+
parameters: {
|
|
4634
|
+
type: "object",
|
|
4635
|
+
properties: {
|
|
4636
|
+
jobId: { type: "integer", description: "Job id returned by run_background." },
|
|
4637
|
+
since: {
|
|
4638
|
+
type: "integer",
|
|
4639
|
+
description: "Return only output written past this byte offset (for incremental polling)."
|
|
4640
|
+
},
|
|
4641
|
+
tailLines: {
|
|
4642
|
+
type: "integer",
|
|
4643
|
+
description: "Cap the returned slice to the last N lines. Default 80, 0 = unlimited."
|
|
4644
|
+
}
|
|
4645
|
+
},
|
|
4646
|
+
required: ["jobId"]
|
|
4647
|
+
},
|
|
4648
|
+
fn: async (args) => {
|
|
4649
|
+
const out = jobs.read(args.jobId, {
|
|
4650
|
+
since: args.since,
|
|
4651
|
+
tailLines: args.tailLines ?? 80
|
|
4652
|
+
});
|
|
4653
|
+
if (!out) return `job ${args.jobId}: not found (use list_jobs)`;
|
|
4654
|
+
return formatJobRead(args.jobId, out);
|
|
4655
|
+
}
|
|
4656
|
+
});
|
|
4657
|
+
registry.register({
|
|
4658
|
+
name: "stop_job",
|
|
4659
|
+
description: "Stop a background job started with `run_background`. SIGTERM first; SIGKILL after a short grace period if it doesn't exit cleanly. Returns the final output + exit code. Safe to call on an already-exited job.",
|
|
4660
|
+
parameters: {
|
|
4661
|
+
type: "object",
|
|
4662
|
+
properties: {
|
|
4663
|
+
jobId: { type: "integer" }
|
|
4664
|
+
},
|
|
4665
|
+
required: ["jobId"]
|
|
4666
|
+
},
|
|
4667
|
+
fn: async (args) => {
|
|
4668
|
+
const rec = await jobs.stop(args.jobId);
|
|
4669
|
+
if (!rec) return `job ${args.jobId}: not found`;
|
|
4670
|
+
return formatJobStop(rec);
|
|
4671
|
+
}
|
|
4672
|
+
});
|
|
4673
|
+
registry.register({
|
|
4674
|
+
name: "list_jobs",
|
|
4675
|
+
description: "List every background job started this session \u2014 running and exited \u2014 with id, command, pid, status. Use when you've lost track of which job_id corresponds to which process, or to see what's still alive.",
|
|
4676
|
+
readOnly: true,
|
|
4677
|
+
parameters: { type: "object", properties: {} },
|
|
4678
|
+
fn: async () => {
|
|
4679
|
+
const all = jobs.list();
|
|
4680
|
+
if (all.length === 0) return "(no background jobs started this session)";
|
|
4681
|
+
return all.map(formatJobRow).join("\n");
|
|
4682
|
+
}
|
|
4683
|
+
});
|
|
4069
4684
|
return registry;
|
|
4070
4685
|
}
|
|
4686
|
+
function formatJobStart(r) {
|
|
4687
|
+
const header2 = r.stillRunning ? `[job ${r.jobId} started \xB7 pid ${r.pid ?? "?"} \xB7 ${r.readyMatched ? "READY signal matched" : "running (no ready signal yet)"}]` : r.exitCode !== null ? `[job ${r.jobId} exited during startup \xB7 exit ${r.exitCode}]` : `[job ${r.jobId} failed to start]`;
|
|
4688
|
+
return r.preview ? `${header2}
|
|
4689
|
+
${r.preview}` : header2;
|
|
4690
|
+
}
|
|
4691
|
+
function formatJobRead(jobId, r) {
|
|
4692
|
+
const status = r.running ? `running \xB7 pid ${r.pid ?? "?"}` : r.exitCode !== null ? `exited ${r.exitCode}` : r.spawnError ? `failed (${r.spawnError})` : "stopped";
|
|
4693
|
+
const header2 = `[job ${jobId} \xB7 ${status} \xB7 byteLength=${r.byteLength}]
|
|
4694
|
+
$ ${r.command}`;
|
|
4695
|
+
return r.output ? `${header2}
|
|
4696
|
+
${r.output}` : header2;
|
|
4697
|
+
}
|
|
4698
|
+
function formatJobStop(r) {
|
|
4699
|
+
const running = r.running ? "still running (SIGKILL may be pending)" : `exit ${r.exitCode ?? "?"}`;
|
|
4700
|
+
const tail = tailLines(r.output, 40);
|
|
4701
|
+
const header2 = `[job ${r.id} stopped \xB7 ${running}]
|
|
4702
|
+
$ ${r.command}`;
|
|
4703
|
+
return tail ? `${header2}
|
|
4704
|
+
${tail}` : header2;
|
|
4705
|
+
}
|
|
4706
|
+
function formatJobRow(r) {
|
|
4707
|
+
const age = ((Date.now() - r.startedAt) / 1e3).toFixed(1);
|
|
4708
|
+
const state = r.running ? `running \xB7 pid ${r.pid ?? "?"}` : r.exitCode !== null ? `exit ${r.exitCode}` : r.spawnError ? "failed" : "stopped";
|
|
4709
|
+
return ` ${String(r.id).padStart(3)} ${state.padEnd(24)} ${age}s ago $ ${r.command}`;
|
|
4710
|
+
}
|
|
4711
|
+
function tailLines(s, n) {
|
|
4712
|
+
if (!s) return "";
|
|
4713
|
+
const lines = s.split("\n");
|
|
4714
|
+
if (lines.length <= n) return s;
|
|
4715
|
+
const dropped = lines.length - n;
|
|
4716
|
+
return [`[\u2026 ${dropped} earlier lines \u2026]`, ...lines.slice(-n)].join("\n");
|
|
4717
|
+
}
|
|
4071
4718
|
function formatCommandResult(cmd, r) {
|
|
4072
4719
|
const header2 = r.timedOut ? `$ ${cmd}
|
|
4073
4720
|
[killed after timeout]` : `$ ${cmd}
|
|
@@ -4261,11 +4908,11 @@ ${i + 1}. ${r.title}`);
|
|
|
4261
4908
|
|
|
4262
4909
|
// src/env.ts
|
|
4263
4910
|
import { readFileSync as readFileSync6 } from "fs";
|
|
4264
|
-
import { resolve as
|
|
4911
|
+
import { resolve as resolve5 } from "path";
|
|
4265
4912
|
function loadDotenv(path = ".env") {
|
|
4266
4913
|
let raw;
|
|
4267
4914
|
try {
|
|
4268
|
-
raw = readFileSync6(
|
|
4915
|
+
raw = readFileSync6(resolve5(process.cwd(), path), "utf8");
|
|
4269
4916
|
} catch {
|
|
4270
4917
|
return;
|
|
4271
4918
|
}
|
|
@@ -4978,7 +5625,7 @@ var McpClient = class {
|
|
|
4978
5625
|
const id = this.nextId++;
|
|
4979
5626
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
4980
5627
|
let abortHandler = null;
|
|
4981
|
-
const promise = new Promise((
|
|
5628
|
+
const promise = new Promise((resolve8, reject) => {
|
|
4982
5629
|
const timeout = setTimeout(() => {
|
|
4983
5630
|
this.pending.delete(id);
|
|
4984
5631
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -4987,7 +5634,7 @@ var McpClient = class {
|
|
|
4987
5634
|
);
|
|
4988
5635
|
}, this.requestTimeoutMs);
|
|
4989
5636
|
this.pending.set(id, {
|
|
4990
|
-
resolve:
|
|
5637
|
+
resolve: resolve8,
|
|
4991
5638
|
reject,
|
|
4992
5639
|
timeout
|
|
4993
5640
|
});
|
|
@@ -5069,7 +5716,7 @@ var McpClient = class {
|
|
|
5069
5716
|
};
|
|
5070
5717
|
|
|
5071
5718
|
// src/mcp/stdio.ts
|
|
5072
|
-
import { spawn as
|
|
5719
|
+
import { spawn as spawn4 } from "child_process";
|
|
5073
5720
|
var StdioTransport = class {
|
|
5074
5721
|
child;
|
|
5075
5722
|
queue = [];
|
|
@@ -5084,14 +5731,14 @@ var StdioTransport = class {
|
|
|
5084
5731
|
opts.command,
|
|
5085
5732
|
...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
|
|
5086
5733
|
].join(" ");
|
|
5087
|
-
this.child =
|
|
5734
|
+
this.child = spawn4(line, [], {
|
|
5088
5735
|
env,
|
|
5089
5736
|
cwd: opts.cwd,
|
|
5090
5737
|
stdio: ["pipe", "pipe", "inherit"],
|
|
5091
5738
|
shell: true
|
|
5092
5739
|
});
|
|
5093
5740
|
} else {
|
|
5094
|
-
this.child =
|
|
5741
|
+
this.child = spawn4(opts.command, opts.args ?? [], {
|
|
5095
5742
|
env,
|
|
5096
5743
|
cwd: opts.cwd,
|
|
5097
5744
|
stdio: ["pipe", "pipe", "inherit"]
|
|
@@ -5110,12 +5757,12 @@ var StdioTransport = class {
|
|
|
5110
5757
|
}
|
|
5111
5758
|
async send(message) {
|
|
5112
5759
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
5113
|
-
return new Promise((
|
|
5760
|
+
return new Promise((resolve8, reject) => {
|
|
5114
5761
|
const line = `${JSON.stringify(message)}
|
|
5115
5762
|
`;
|
|
5116
5763
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
5117
5764
|
if (err) reject(err);
|
|
5118
|
-
else
|
|
5765
|
+
else resolve8();
|
|
5119
5766
|
});
|
|
5120
5767
|
});
|
|
5121
5768
|
}
|
|
@@ -5126,8 +5773,8 @@ var StdioTransport = class {
|
|
|
5126
5773
|
continue;
|
|
5127
5774
|
}
|
|
5128
5775
|
if (this.closed) return;
|
|
5129
|
-
const next = await new Promise((
|
|
5130
|
-
this.waiters.push(
|
|
5776
|
+
const next = await new Promise((resolve8) => {
|
|
5777
|
+
this.waiters.push(resolve8);
|
|
5131
5778
|
});
|
|
5132
5779
|
if (next === null) return;
|
|
5133
5780
|
yield next;
|
|
@@ -5193,8 +5840,8 @@ var SseTransport = class {
|
|
|
5193
5840
|
constructor(opts) {
|
|
5194
5841
|
this.url = opts.url;
|
|
5195
5842
|
this.headers = opts.headers ?? {};
|
|
5196
|
-
this.endpointReady = new Promise((
|
|
5197
|
-
this.resolveEndpoint =
|
|
5843
|
+
this.endpointReady = new Promise((resolve8, reject) => {
|
|
5844
|
+
this.resolveEndpoint = resolve8;
|
|
5198
5845
|
this.rejectEndpoint = reject;
|
|
5199
5846
|
});
|
|
5200
5847
|
this.endpointReady.catch(() => void 0);
|
|
@@ -5221,8 +5868,8 @@ var SseTransport = class {
|
|
|
5221
5868
|
continue;
|
|
5222
5869
|
}
|
|
5223
5870
|
if (this.closed) return;
|
|
5224
|
-
const next = await new Promise((
|
|
5225
|
-
this.waiters.push(
|
|
5871
|
+
const next = await new Promise((resolve8) => {
|
|
5872
|
+
this.waiters.push(resolve8);
|
|
5226
5873
|
});
|
|
5227
5874
|
if (next === null) return;
|
|
5228
5875
|
yield next;
|
|
@@ -5422,7 +6069,7 @@ async function trySection(load) {
|
|
|
5422
6069
|
|
|
5423
6070
|
// src/code/edit-blocks.ts
|
|
5424
6071
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5425
|
-
import { dirname as dirname5, resolve as
|
|
6072
|
+
import { dirname as dirname5, resolve as resolve6 } from "path";
|
|
5426
6073
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
5427
6074
|
function parseEditBlocks(text) {
|
|
5428
6075
|
const out = [];
|
|
@@ -5440,8 +6087,8 @@ function parseEditBlocks(text) {
|
|
|
5440
6087
|
return out;
|
|
5441
6088
|
}
|
|
5442
6089
|
function applyEditBlock(block, rootDir) {
|
|
5443
|
-
const absRoot =
|
|
5444
|
-
const absTarget =
|
|
6090
|
+
const absRoot = resolve6(rootDir);
|
|
6091
|
+
const absTarget = resolve6(absRoot, block.path);
|
|
5445
6092
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
5446
6093
|
return {
|
|
5447
6094
|
path: block.path,
|
|
@@ -5490,14 +6137,26 @@ function applyEditBlock(block, rootDir) {
|
|
|
5490
6137
|
function applyEditBlocks(blocks, rootDir) {
|
|
5491
6138
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
5492
6139
|
}
|
|
6140
|
+
function toWholeFileEditBlock(path, content, rootDir) {
|
|
6141
|
+
const abs = resolve6(rootDir, path);
|
|
6142
|
+
let search = "";
|
|
6143
|
+
if (existsSync6(abs)) {
|
|
6144
|
+
try {
|
|
6145
|
+
search = readFileSync8(abs, "utf8");
|
|
6146
|
+
} catch {
|
|
6147
|
+
search = "";
|
|
6148
|
+
}
|
|
6149
|
+
}
|
|
6150
|
+
return { path, search, replace: content, offset: 0 };
|
|
6151
|
+
}
|
|
5493
6152
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
5494
|
-
const absRoot =
|
|
6153
|
+
const absRoot = resolve6(rootDir);
|
|
5495
6154
|
const seen = /* @__PURE__ */ new Set();
|
|
5496
6155
|
const snapshots = [];
|
|
5497
6156
|
for (const b of blocks) {
|
|
5498
6157
|
if (seen.has(b.path)) continue;
|
|
5499
6158
|
seen.add(b.path);
|
|
5500
|
-
const abs =
|
|
6159
|
+
const abs = resolve6(absRoot, b.path);
|
|
5501
6160
|
if (!existsSync6(abs)) {
|
|
5502
6161
|
snapshots.push({ path: b.path, prevContent: null });
|
|
5503
6162
|
continue;
|
|
@@ -5511,9 +6170,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
5511
6170
|
return snapshots;
|
|
5512
6171
|
}
|
|
5513
6172
|
function restoreSnapshots(snapshots, rootDir) {
|
|
5514
|
-
const absRoot =
|
|
6173
|
+
const absRoot = resolve6(rootDir);
|
|
5515
6174
|
return snapshots.map((snap) => {
|
|
5516
|
-
const abs =
|
|
6175
|
+
const abs = resolve6(absRoot, snap.path);
|
|
5517
6176
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
5518
6177
|
return {
|
|
5519
6178
|
path: snap.path,
|
|
@@ -5800,11 +6459,11 @@ function formatLogSize(path = defaultUsageLogPath()) {
|
|
|
5800
6459
|
// src/cli/commands/chat.tsx
|
|
5801
6460
|
import { existsSync as existsSync11, statSync as statSync6 } from "fs";
|
|
5802
6461
|
import { render } from "ink";
|
|
5803
|
-
import
|
|
6462
|
+
import React18, { useState as useState8 } from "react";
|
|
5804
6463
|
|
|
5805
6464
|
// src/cli/ui/App.tsx
|
|
5806
|
-
import { Box as
|
|
5807
|
-
import
|
|
6465
|
+
import { Box as Box14, Static, Text as Text14, useApp, useInput as useInput5 } from "ink";
|
|
6466
|
+
import React15, { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState6 } from "react";
|
|
5808
6467
|
|
|
5809
6468
|
// src/code/diff-preview.ts
|
|
5810
6469
|
function formatEditBlockDiff(block, opts = {}) {
|
|
@@ -6027,13 +6686,108 @@ function FileRow({ path, isSelected }) {
|
|
|
6027
6686
|
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, marker, " ", base, dir ? ` ${dir}` : ""));
|
|
6028
6687
|
}
|
|
6029
6688
|
|
|
6689
|
+
// src/cli/ui/EditConfirm.tsx
|
|
6690
|
+
import { Box as Box2, Text as Text2, useInput, useStdout } from "ink";
|
|
6691
|
+
import React2, { useMemo, useState } from "react";
|
|
6692
|
+
var MODAL_OVERHEAD_ROWS = 18;
|
|
6693
|
+
var MIN_DIFF_ROWS = 8;
|
|
6694
|
+
function EditConfirm({ block, onChoose }) {
|
|
6695
|
+
const { stdout: stdout2 } = useStdout();
|
|
6696
|
+
const rows = stdout2?.rows ?? 40;
|
|
6697
|
+
const budget = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
|
|
6698
|
+
const allLines = useMemo(
|
|
6699
|
+
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
6700
|
+
[block]
|
|
6701
|
+
);
|
|
6702
|
+
const [scroll, setScroll] = useState(0);
|
|
6703
|
+
const maxScroll = Math.max(0, allLines.length - budget);
|
|
6704
|
+
const effectiveScroll = Math.min(scroll, maxScroll);
|
|
6705
|
+
useInput((input, key) => {
|
|
6706
|
+
if (key.return || input === "y") {
|
|
6707
|
+
onChoose("apply");
|
|
6708
|
+
return;
|
|
6709
|
+
}
|
|
6710
|
+
if (input === "n") {
|
|
6711
|
+
onChoose("reject");
|
|
6712
|
+
return;
|
|
6713
|
+
}
|
|
6714
|
+
if (input === "a") {
|
|
6715
|
+
onChoose("apply-rest-of-turn");
|
|
6716
|
+
return;
|
|
6717
|
+
}
|
|
6718
|
+
if (input === "A") {
|
|
6719
|
+
onChoose("flip-to-auto");
|
|
6720
|
+
return;
|
|
6721
|
+
}
|
|
6722
|
+
if (key.downArrow || input === "j") {
|
|
6723
|
+
setScroll((s) => Math.min(maxScroll, s + 1));
|
|
6724
|
+
return;
|
|
6725
|
+
}
|
|
6726
|
+
if (key.upArrow || input === "k") {
|
|
6727
|
+
setScroll((s) => Math.max(0, s - 1));
|
|
6728
|
+
return;
|
|
6729
|
+
}
|
|
6730
|
+
if (key.pageDown || input === " " || input === "f") {
|
|
6731
|
+
setScroll((s) => Math.min(maxScroll, s + Math.max(1, budget - 2)));
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6734
|
+
if (key.pageUp || input === "b") {
|
|
6735
|
+
setScroll((s) => Math.max(0, s - Math.max(1, budget - 2)));
|
|
6736
|
+
return;
|
|
6737
|
+
}
|
|
6738
|
+
if (input === "g") {
|
|
6739
|
+
setScroll(0);
|
|
6740
|
+
return;
|
|
6741
|
+
}
|
|
6742
|
+
if (input === "G") {
|
|
6743
|
+
setScroll(maxScroll);
|
|
6744
|
+
return;
|
|
6745
|
+
}
|
|
6746
|
+
});
|
|
6747
|
+
const isNew = block.search === "";
|
|
6748
|
+
const removed = isNew ? 0 : (block.search.match(/\n/g)?.length ?? 0) + 1;
|
|
6749
|
+
const added = block.replace === "" ? 0 : (block.replace.match(/\n/g)?.length ?? 0) + 1;
|
|
6750
|
+
const tag = isNew ? "NEW" : "EDIT";
|
|
6751
|
+
const visibleLines = allLines.slice(effectiveScroll, effectiveScroll + budget);
|
|
6752
|
+
const hiddenAbove = effectiveScroll;
|
|
6753
|
+
const hiddenBelow = Math.max(0, allLines.length - effectiveScroll - budget);
|
|
6754
|
+
const totalLines = allLines.length;
|
|
6755
|
+
const showScrollHud = hiddenAbove + hiddenBelow > 0;
|
|
6756
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u25B8 model wants to edit a file")), /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan", dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: isNew ? "green" : "yellow", bold: true }, `[${tag}] `), /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, block.path), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, ` (-${removed} +${added} lines)`), showScrollHud ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, ` \xB7 viewing ${effectiveScroll + 1}-${effectiveScroll + visibleLines.length}/${totalLines}`) : null)), hiddenAbove > 0 ? /* @__PURE__ */ React2.createElement(
|
|
6757
|
+
Text2,
|
|
6758
|
+
{
|
|
6759
|
+
dimColor: true
|
|
6760
|
+
},
|
|
6761
|
+
` \u2191 ${hiddenAbove} line${hiddenAbove === 1 ? "" : "s"} above (\u2191/k or PgUp)`
|
|
6762
|
+
) : null, /* @__PURE__ */ React2.createElement(Box2, { marginTop: hiddenAbove > 0 ? 0 : 1, flexDirection: "column" }, visibleLines.map((line, i) => {
|
|
6763
|
+
const trimmed = line.trimStart();
|
|
6764
|
+
const color = trimmed.startsWith("+") ? "green" : trimmed.startsWith("-") ? "red" : void 0;
|
|
6765
|
+
const dim = !color;
|
|
6766
|
+
return /* @__PURE__ */ React2.createElement(
|
|
6767
|
+
Text2,
|
|
6768
|
+
{
|
|
6769
|
+
key: `diff-${effectiveScroll}-${i}`,
|
|
6770
|
+
color,
|
|
6771
|
+
dimColor: dim
|
|
6772
|
+
},
|
|
6773
|
+
line
|
|
6774
|
+
);
|
|
6775
|
+
})), hiddenBelow > 0 ? /* @__PURE__ */ React2.createElement(
|
|
6776
|
+
Text2,
|
|
6777
|
+
{
|
|
6778
|
+
dimColor: true
|
|
6779
|
+
},
|
|
6780
|
+
` \u2193 ${hiddenBelow} line${hiddenBelow === 1 ? "" : "s"} below (\u2193/j or Space/PgDn)`
|
|
6781
|
+
) : null, /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "[", /* @__PURE__ */ React2.createElement(Text2, { color: "cyan", bold: true }, "y"), "/Enter] apply \xB7 [", /* @__PURE__ */ React2.createElement(Text2, { color: "cyan", bold: true }, "n"), "] reject \xB7 [", /* @__PURE__ */ React2.createElement(Text2, { color: "cyan", bold: true }, "a"), "] apply rest \xB7 [", /* @__PURE__ */ React2.createElement(Text2, { color: "cyan", bold: true }, "A"), "] flip AUTO \xB7 [", /* @__PURE__ */ React2.createElement(Text2, { color: "cyan", bold: true }, "\u2191\u2193/Space"), "] scroll \xB7 [Esc] abort")));
|
|
6782
|
+
}
|
|
6783
|
+
|
|
6030
6784
|
// src/cli/ui/EventLog.tsx
|
|
6031
|
-
import { Box as
|
|
6032
|
-
import
|
|
6785
|
+
import { Box as Box5, Text as Text5, useStdout as useStdout2 } from "ink";
|
|
6786
|
+
import React6 from "react";
|
|
6033
6787
|
|
|
6034
6788
|
// src/cli/ui/PlanStateBlock.tsx
|
|
6035
|
-
import { Box as
|
|
6036
|
-
import
|
|
6789
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
6790
|
+
import React3 from "react";
|
|
6037
6791
|
function PlanStateBlock({ planState }) {
|
|
6038
6792
|
const fields = [];
|
|
6039
6793
|
if (planState.subgoals.length) fields.push(["subgoals", planState.subgoals, "cyan", false]);
|
|
@@ -6044,14 +6798,14 @@ function PlanStateBlock({ planState }) {
|
|
|
6044
6798
|
if (planState.rejectedPaths.length)
|
|
6045
6799
|
fields.push(["rejected", planState.rejectedPaths, "red", true]);
|
|
6046
6800
|
if (fields.length === 0) return null;
|
|
6047
|
-
return /* @__PURE__ */
|
|
6801
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color, dim]) => /* @__PURE__ */ React3.createElement(Text3, { key: label }, /* @__PURE__ */ React3.createElement(Text3, { color, bold: true, dimColor: dim }, "\u2039 ", label), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, ` (${items.length})`), /* @__PURE__ */ React3.createElement(Text3, null, `: ${items.join(" \xB7 ")}`))));
|
|
6048
6802
|
}
|
|
6049
6803
|
|
|
6050
6804
|
// src/cli/ui/markdown.tsx
|
|
6051
6805
|
import { readFileSync as readFileSync12, statSync as statSync5 } from "fs";
|
|
6052
6806
|
import { isAbsolute as isAbsolute4, join as join10 } from "path";
|
|
6053
|
-
import { Box as
|
|
6054
|
-
import
|
|
6807
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
6808
|
+
import React4 from "react";
|
|
6055
6809
|
var SUPERSCRIPT = {
|
|
6056
6810
|
"0": "\u2070",
|
|
6057
6811
|
"1": "\xB9",
|
|
@@ -6176,57 +6930,57 @@ function InlineMd({
|
|
|
6176
6930
|
for (const m of text.matchAll(INLINE_RE)) {
|
|
6177
6931
|
const start = m.index ?? 0;
|
|
6178
6932
|
if (start > last) {
|
|
6179
|
-
parts.push(/* @__PURE__ */
|
|
6933
|
+
parts.push(/* @__PURE__ */ React4.createElement(Text4, { key: `t${idx++}` }, text.slice(last, start)));
|
|
6180
6934
|
}
|
|
6181
6935
|
if (m[2] !== void 0 && m[3] !== void 0) {
|
|
6182
6936
|
const linkText = m[2];
|
|
6183
6937
|
const url = m[3];
|
|
6184
6938
|
if (isExternalUrl(url)) {
|
|
6185
6939
|
parts.push(
|
|
6186
|
-
/* @__PURE__ */
|
|
6940
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `l${idx++}`, color: "blue", underline: true }, linkText)
|
|
6187
6941
|
);
|
|
6188
6942
|
} else {
|
|
6189
6943
|
const status = citations?.get(url);
|
|
6190
6944
|
if (status && !status.ok) {
|
|
6191
6945
|
parts.push(
|
|
6192
|
-
/* @__PURE__ */
|
|
6946
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `l${idx++}`, color: "red", strikethrough: true }, `${linkText} \u2717`)
|
|
6193
6947
|
);
|
|
6194
6948
|
} else {
|
|
6195
6949
|
parts.push(
|
|
6196
|
-
/* @__PURE__ */
|
|
6950
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `l${idx++}`, color: "cyan", underline: true }, linkText)
|
|
6197
6951
|
);
|
|
6198
6952
|
}
|
|
6199
6953
|
}
|
|
6200
6954
|
} else if (m[4] !== void 0) {
|
|
6201
6955
|
parts.push(
|
|
6202
|
-
/* @__PURE__ */
|
|
6956
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `b${idx++}`, bold: true }, m[4])
|
|
6203
6957
|
);
|
|
6204
6958
|
} else if (m[5] !== void 0) {
|
|
6205
6959
|
const stripped = m[5].replace(/^(\w+)\s+/, "");
|
|
6206
6960
|
parts.push(
|
|
6207
|
-
/* @__PURE__ */
|
|
6961
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `c${idx++}`, color: "yellow" }, stripped)
|
|
6208
6962
|
);
|
|
6209
6963
|
} else if (m[6] !== void 0) {
|
|
6210
6964
|
parts.push(
|
|
6211
|
-
/* @__PURE__ */
|
|
6965
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `c${idx++}`, color: "yellow" }, m[6])
|
|
6212
6966
|
);
|
|
6213
6967
|
} else if (m[7] !== void 0) {
|
|
6214
6968
|
parts.push(
|
|
6215
|
-
/* @__PURE__ */
|
|
6969
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `i${idx++}`, italic: true }, m[7])
|
|
6216
6970
|
);
|
|
6217
6971
|
}
|
|
6218
6972
|
last = start + m[0].length;
|
|
6219
6973
|
}
|
|
6220
6974
|
if (last < text.length) {
|
|
6221
|
-
parts.push(/* @__PURE__ */
|
|
6975
|
+
parts.push(/* @__PURE__ */ React4.createElement(Text4, { key: `t${idx++}` }, text.slice(last)));
|
|
6222
6976
|
}
|
|
6223
6977
|
if (padTo !== void 0) {
|
|
6224
6978
|
const seen = visibleWidth(text);
|
|
6225
6979
|
if (seen < padTo) {
|
|
6226
|
-
parts.push(/* @__PURE__ */
|
|
6980
|
+
parts.push(/* @__PURE__ */ React4.createElement(Text4, { key: `pad${idx++}` }, " ".repeat(padTo - seen)));
|
|
6227
6981
|
}
|
|
6228
6982
|
}
|
|
6229
|
-
return /* @__PURE__ */
|
|
6983
|
+
return /* @__PURE__ */ React4.createElement(Text4, null, parts);
|
|
6230
6984
|
}
|
|
6231
6985
|
function stripInlineMarkup(s) {
|
|
6232
6986
|
return s.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, "$1").replace(/\*\*([^*\n]+?)\*\*/g, "$1").replace(/```([^\n]+?)```/g, (_m, c) => c.replace(/^(\w+)\s+/, "")).replace(/`([^`\n]+?)`/g, "$1").replace(/(?<![*\w])\*([^*\n]+?)\*(?!\w)/g, "$1");
|
|
@@ -6422,19 +7176,19 @@ function parseBlocks(raw) {
|
|
|
6422
7176
|
function BlockView({ block, citations }) {
|
|
6423
7177
|
switch (block.kind) {
|
|
6424
7178
|
case "heading":
|
|
6425
|
-
return /* @__PURE__ */
|
|
7179
|
+
return /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, /* @__PURE__ */ React4.createElement(InlineMd, { text: block.text, citations }));
|
|
6426
7180
|
case "paragraph":
|
|
6427
|
-
return /* @__PURE__ */
|
|
7181
|
+
return /* @__PURE__ */ React4.createElement(InlineMd, { text: block.text, citations });
|
|
6428
7182
|
case "bullet":
|
|
6429
|
-
return /* @__PURE__ */
|
|
7183
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React4.createElement(Box4, { key: `${i}-${item.slice(0, 24)}` }, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, block.ordered ? ` ${block.start + i}. ` : " \u2022 "), /* @__PURE__ */ React4.createElement(InlineMd, { text: item, citations }))));
|
|
6430
7184
|
case "code":
|
|
6431
|
-
return /* @__PURE__ */
|
|
7185
|
+
return /* @__PURE__ */ React4.createElement(Box4, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: "yellow" }, block.text));
|
|
6432
7186
|
case "edit-block":
|
|
6433
|
-
return /* @__PURE__ */
|
|
7187
|
+
return /* @__PURE__ */ React4.createElement(EditBlockRow, { block });
|
|
6434
7188
|
case "table":
|
|
6435
|
-
return /* @__PURE__ */
|
|
7189
|
+
return /* @__PURE__ */ React4.createElement(TableBlockRow, { block, citations });
|
|
6436
7190
|
case "hr":
|
|
6437
|
-
return /* @__PURE__ */
|
|
7191
|
+
return /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
6438
7192
|
}
|
|
6439
7193
|
}
|
|
6440
7194
|
function splitTableRow(line) {
|
|
@@ -6452,14 +7206,14 @@ function TableBlockRow({ block, citations }) {
|
|
|
6452
7206
|
widths.push(Math.min(40, Math.max(3, ...cellLengths)));
|
|
6453
7207
|
}
|
|
6454
7208
|
const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u253C\u2500");
|
|
6455
|
-
return /* @__PURE__ */
|
|
7209
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box4, null, block.header.map((cell, ci) => (
|
|
6456
7210
|
// biome-ignore lint/suspicious/noArrayIndexKey: table columns never reorder — derived from a static header array
|
|
6457
|
-
/* @__PURE__ */
|
|
6458
|
-
))), /* @__PURE__ */
|
|
7211
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `h-${ci}`, bold: true, color: "cyan" }, /* @__PURE__ */ React4.createElement(InlineMd, { text: cell, padTo: widths[ci] ?? 3, citations }), ci < colCount - 1 ? " \u2502 " : "")
|
|
7212
|
+
))), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, separator), block.rows.map((row2, ri) => (
|
|
6459
7213
|
// biome-ignore lint/suspicious/noArrayIndexKey: table rows render in source order and don't reorder
|
|
6460
|
-
/* @__PURE__ */
|
|
7214
|
+
/* @__PURE__ */ React4.createElement(Box4, { key: `r-${ri}` }, Array.from({ length: colCount }).map((_, ci) => (
|
|
6461
7215
|
// biome-ignore lint/suspicious/noArrayIndexKey: same — column axis is fixed by the table shape
|
|
6462
|
-
/* @__PURE__ */
|
|
7216
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `c-${ri}-${ci}` }, /* @__PURE__ */ React4.createElement(InlineMd, { text: row2[ci] ?? "", padTo: widths[ci] ?? 3, citations }), ci < colCount - 1 ? " \u2502 " : "")
|
|
6463
7217
|
)))
|
|
6464
7218
|
)));
|
|
6465
7219
|
}
|
|
@@ -6479,44 +7233,44 @@ function EditBlockRow({ block }) {
|
|
|
6479
7233
|
const isNewFile = block.search.length === 0;
|
|
6480
7234
|
const searchLines = block.search.split("\n");
|
|
6481
7235
|
const replaceLines = block.replace.split("\n");
|
|
6482
|
-
return /* @__PURE__ */
|
|
7236
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, block.filename), isNewFile ? /* @__PURE__ */ React4.createElement(Text4, { color: "green", bold: true }, " (new file)") : null), isNewFile ? null : /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginTop: 1 }, searchLines.map((line, i) => /* @__PURE__ */ React4.createElement(Text4, { key: `s-${i}-${line.length}`, color: "red" }, `- ${line}`))), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginTop: isNewFile ? 1 : 0 }, replaceLines.map((line, i) => /* @__PURE__ */ React4.createElement(Text4, { key: `r-${i}-${line.length}`, color: "green" }, `+ ${line}`))));
|
|
6483
7237
|
}
|
|
6484
7238
|
function Markdown({ text, projectRoot }) {
|
|
6485
7239
|
const cleaned = stripMath(text);
|
|
6486
7240
|
const root = projectRoot ?? process.cwd();
|
|
6487
|
-
const citations =
|
|
6488
|
-
const blocks =
|
|
7241
|
+
const citations = React4.useMemo(() => collectCitations(cleaned, root), [cleaned, root]);
|
|
7242
|
+
const blocks = React4.useMemo(() => parseBlocks(cleaned), [cleaned]);
|
|
6489
7243
|
const broken = [];
|
|
6490
7244
|
for (const [url, status] of citations) {
|
|
6491
7245
|
if (!status.ok) broken.push({ url, reason: status.reason });
|
|
6492
7246
|
}
|
|
6493
|
-
return /* @__PURE__ */
|
|
7247
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 1 }, blocks.map((b, i) => /* @__PURE__ */ React4.createElement(BlockView, { key: `${i}-${b.kind}`, block: b, citations })), broken.length > 0 ? /* @__PURE__ */ React4.createElement(BrokenCitationsBlock, { items: broken }) : null);
|
|
6494
7248
|
}
|
|
6495
7249
|
function BrokenCitationsBlock({ items }) {
|
|
6496
|
-
return /* @__PURE__ */
|
|
7250
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: "red", bold: true }, `\u26A0 ${items.length} broken citation${items.length > 1 ? "s" : ""} \u2014 the model referenced paths or lines that don't exist`), items.map((b, i) => (
|
|
6497
7251
|
// biome-ignore lint/suspicious/noArrayIndexKey: list is derived from a Map iteration order, stable per render
|
|
6498
|
-
/* @__PURE__ */
|
|
7252
|
+
/* @__PURE__ */ React4.createElement(Text4, { key: `bc-${i}`, color: "red" }, ` \u2717 ${b.url} \u2192 ${b.reason}`)
|
|
6499
7253
|
)));
|
|
6500
7254
|
}
|
|
6501
7255
|
|
|
6502
7256
|
// src/cli/ui/ticker.tsx
|
|
6503
|
-
import
|
|
7257
|
+
import React5, { createContext, useContext, useEffect, useState as useState2 } from "react";
|
|
6504
7258
|
var TICK_MS = 120;
|
|
6505
7259
|
var TickContext = createContext(0);
|
|
6506
7260
|
function TickerProvider({ children, disabled }) {
|
|
6507
|
-
const [tick, setTick] =
|
|
7261
|
+
const [tick, setTick] = useState2(0);
|
|
6508
7262
|
useEffect(() => {
|
|
6509
7263
|
if (disabled) return;
|
|
6510
7264
|
const id = setInterval(() => setTick((t) => t + 1), TICK_MS);
|
|
6511
7265
|
return () => clearInterval(id);
|
|
6512
7266
|
}, [disabled]);
|
|
6513
|
-
return /* @__PURE__ */
|
|
7267
|
+
return /* @__PURE__ */ React5.createElement(TickContext.Provider, { value: tick }, children);
|
|
6514
7268
|
}
|
|
6515
7269
|
function useTick() {
|
|
6516
7270
|
return useContext(TickContext);
|
|
6517
7271
|
}
|
|
6518
7272
|
function useElapsedSeconds() {
|
|
6519
|
-
const [start] =
|
|
7273
|
+
const [start] = useState2(() => Date.now());
|
|
6520
7274
|
useTick();
|
|
6521
7275
|
return Math.floor((Date.now() - start) / 1e3);
|
|
6522
7276
|
}
|
|
@@ -6536,18 +7290,18 @@ function RoleGlyph({
|
|
|
6536
7290
|
glyph,
|
|
6537
7291
|
color
|
|
6538
7292
|
}) {
|
|
6539
|
-
return /* @__PURE__ */
|
|
7293
|
+
return /* @__PURE__ */ React6.createElement(Text5, { color, bold: true }, glyph);
|
|
6540
7294
|
}
|
|
6541
|
-
var EventRow =
|
|
7295
|
+
var EventRow = React6.memo(function EventRow2({
|
|
6542
7296
|
event,
|
|
6543
7297
|
projectRoot
|
|
6544
7298
|
}) {
|
|
6545
7299
|
if (event.role === "user") {
|
|
6546
|
-
return /* @__PURE__ */
|
|
7300
|
+
return /* @__PURE__ */ React6.createElement(Box5, { marginTop: event.leadSeparator ? 1 : 0 }, /* @__PURE__ */ React6.createElement(RoleGlyph, { glyph: ROLE_GLYPH.user, color: "cyan" }), /* @__PURE__ */ React6.createElement(Text5, null, " ", event.text));
|
|
6547
7301
|
}
|
|
6548
7302
|
if (event.role === "assistant") {
|
|
6549
|
-
if (event.streaming) return /* @__PURE__ */
|
|
6550
|
-
return /* @__PURE__ */
|
|
7303
|
+
if (event.streaming) return /* @__PURE__ */ React6.createElement(StreamingAssistant, { event });
|
|
7304
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(RoleGlyph, { glyph: ROLE_GLYPH.assistant, color: "green" }), event.stats ? /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, ` ${event.stats.model}`) : null), /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", paddingLeft: 2, marginTop: 1 }, event.branch ? /* @__PURE__ */ React6.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React6.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React6.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React6.createElement(Markdown, { text: event.text, projectRoot }) : /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React6.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React6.createElement(Text5, { color: "magenta" }, event.repair) : null));
|
|
6551
7305
|
}
|
|
6552
7306
|
if (event.role === "tool") {
|
|
6553
7307
|
const isError = event.text.startsWith("ERROR:");
|
|
@@ -6555,31 +7309,31 @@ var EventRow = React5.memo(function EventRow2({
|
|
|
6555
7309
|
const glyph = isError ? ROLE_GLYPH.toolErr : ROLE_GLYPH.toolOk;
|
|
6556
7310
|
const marker = isError ? "\u2717" : "\u2192";
|
|
6557
7311
|
const isEditFile = (event.toolName === "edit_file" || event.toolName?.endsWith("_edit_file")) && !isError;
|
|
6558
|
-
return /* @__PURE__ */
|
|
7312
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(RoleGlyph, { glyph, color }), /* @__PURE__ */ React6.createElement(Text5, { color, bold: true }, ` ${event.toolName ?? "?"}`), /* @__PURE__ */ React6.createElement(Text5, { color, dimColor: true }, ` ${marker}`)), /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", paddingLeft: 2, marginTop: 1 }, isEditFile ? /* @__PURE__ */ React6.createElement(EditFileDiff, { text: event.text }) : /* @__PURE__ */ React6.createElement(Text5, { color: isError ? "red" : void 0, dimColor: !isError }, truncate2(event.text, 400))));
|
|
6559
7313
|
}
|
|
6560
7314
|
if (event.role === "error") {
|
|
6561
|
-
return /* @__PURE__ */
|
|
7315
|
+
return /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(RoleGlyph, { glyph: ROLE_GLYPH.error, color: "red" }), /* @__PURE__ */ React6.createElement(Text5, { color: "red" }, " ", event.text));
|
|
6562
7316
|
}
|
|
6563
7317
|
if (event.role === "info") {
|
|
6564
|
-
return /* @__PURE__ */
|
|
7318
|
+
return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, event.text));
|
|
6565
7319
|
}
|
|
6566
7320
|
if (event.role === "warning") {
|
|
6567
|
-
return /* @__PURE__ */
|
|
7321
|
+
return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(RoleGlyph, { glyph: ROLE_GLYPH.warning, color: "yellow" }), /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, " ", event.text));
|
|
6568
7322
|
}
|
|
6569
|
-
return /* @__PURE__ */
|
|
7323
|
+
return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, null, event.text));
|
|
6570
7324
|
});
|
|
6571
7325
|
function EditFileDiff({ text }) {
|
|
6572
7326
|
const lines = text.split(/\r?\n/);
|
|
6573
7327
|
const [statusHeader, hunkHeader, ...body] = lines;
|
|
6574
|
-
return /* @__PURE__ */
|
|
7328
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, ` ${statusHeader ?? ""}`), hunkHeader !== void 0 ? /* @__PURE__ */ React6.createElement(Text5, { color: "cyan", bold: true }, hunkHeader) : null, body.map((line, i) => {
|
|
6575
7329
|
const key = `${i}-${line.slice(0, 32)}`;
|
|
6576
7330
|
if (line.startsWith("- ")) {
|
|
6577
|
-
return /* @__PURE__ */
|
|
7331
|
+
return /* @__PURE__ */ React6.createElement(Text5, { key, color: "red" }, line);
|
|
6578
7332
|
}
|
|
6579
7333
|
if (line.startsWith("+ ")) {
|
|
6580
|
-
return /* @__PURE__ */
|
|
7334
|
+
return /* @__PURE__ */ React6.createElement(Text5, { key, color: "green" }, line);
|
|
6581
7335
|
}
|
|
6582
|
-
return /* @__PURE__ */
|
|
7336
|
+
return /* @__PURE__ */ React6.createElement(Text5, { key, dimColor: true }, line);
|
|
6583
7337
|
}));
|
|
6584
7338
|
}
|
|
6585
7339
|
function BranchBlock({ branch }) {
|
|
@@ -6588,33 +7342,33 @@ function BranchBlock({ branch }) {
|
|
|
6588
7342
|
const t = (branch.temperatures[i] ?? 0).toFixed(1);
|
|
6589
7343
|
return `${marker} #${i} T=${t} u=${u}`;
|
|
6590
7344
|
}).join(" ");
|
|
6591
|
-
return /* @__PURE__ */
|
|
7345
|
+
return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { color: "blue" }, "\u2387 branched ", /* @__PURE__ */ React6.createElement(Text5, { bold: true }, branch.budget), ` samples \u2192 picked #${branch.chosenIndex} `, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, per)));
|
|
6592
7346
|
}
|
|
6593
7347
|
function ReasoningBlock({ reasoning }) {
|
|
6594
7348
|
const max = 260;
|
|
6595
7349
|
const flat = reasoning.replace(/\s+/g, " ").trim();
|
|
6596
7350
|
const preview = flat.length <= max ? flat : `\u2026 (+${flat.length - max} earlier chars) ${flat.slice(-max)}`;
|
|
6597
|
-
return /* @__PURE__ */
|
|
7351
|
+
return /* @__PURE__ */ React6.createElement(Box5, { marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "\u258F "), /* @__PURE__ */ React6.createElement(Text5, { dimColor: true, italic: true }, "thinking ", preview));
|
|
6598
7352
|
}
|
|
6599
7353
|
function Elapsed() {
|
|
6600
7354
|
const s = useElapsedSeconds();
|
|
6601
7355
|
const mm = String(Math.floor(s / 60)).padStart(2, "0");
|
|
6602
7356
|
const ss = String(s % 60).padStart(2, "0");
|
|
6603
|
-
return /* @__PURE__ */
|
|
7357
|
+
return /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, `${mm}:${ss}`);
|
|
6604
7358
|
}
|
|
6605
7359
|
function PulsingAssistantGlyph() {
|
|
6606
7360
|
const tick = useTick();
|
|
6607
7361
|
const on = Math.floor(tick / 4) % 2 === 0;
|
|
6608
|
-
return /* @__PURE__ */
|
|
7362
|
+
return /* @__PURE__ */ React6.createElement(Text5, { color: "green", bold: true }, on ? ROLE_GLYPH.assistant : ROLE_GLYPH.assistantPulse);
|
|
6609
7363
|
}
|
|
6610
7364
|
function StreamingAssistant({ event }) {
|
|
6611
7365
|
if (event.branchProgress) {
|
|
6612
7366
|
const p = event.branchProgress;
|
|
6613
7367
|
if (p.completed === 0) {
|
|
6614
|
-
return /* @__PURE__ */
|
|
7368
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React6.createElement(Text5, { color: "blue" }, " \u2387 launching ", p.total, " parallel samples (R1 thinking in parallel)\u2026 "), /* @__PURE__ */ React6.createElement(Elapsed, null)), /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, " ", "spread across T=0.0/0.5/1.0 \xB7 reasoner typically takes 30-90s \u2014 this is normal"));
|
|
6615
7369
|
}
|
|
6616
7370
|
const pct2 = Math.round(p.completed / p.total * 100);
|
|
6617
|
-
return /* @__PURE__ */
|
|
7371
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React6.createElement(Text5, { color: "blue" }, " \u2387 branching ", p.completed, "/", p.total, " (", pct2, "%) "), /* @__PURE__ */ React6.createElement(Elapsed, null)), /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, " latest #", p.latestIndex, " T=", p.latestTemperature.toFixed(1), " u=", p.latestUncertainties, p.completed < p.total ? " \xB7 waiting for other samples\u2026" : " \xB7 selecting winner\u2026"));
|
|
6618
7372
|
}
|
|
6619
7373
|
const tail = lastLine(event.text, 140);
|
|
6620
7374
|
const reasoningTail = event.reasoning ? lastLine(event.reasoning, 120) : "";
|
|
@@ -6644,16 +7398,16 @@ function StreamingAssistant({ event }) {
|
|
|
6644
7398
|
label = parts.join(" \xB7 ");
|
|
6645
7399
|
labelColor = "green";
|
|
6646
7400
|
}
|
|
6647
|
-
return /* @__PURE__ */
|
|
7401
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React6.createElement(Text5, null, " "), /* @__PURE__ */ React6.createElement(Pulse, null), /* @__PURE__ */ React6.createElement(Text5, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React6.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React6.createElement(Text5, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "\u25B8 ", tail) : preFirstByte ? (
|
|
6648
7402
|
// Non-dim yellow: first-time users misread the dim version as
|
|
6649
7403
|
// "app frozen". The reassurance has to be VISIBLE to do its job.
|
|
6650
|
-
/* @__PURE__ */
|
|
6651
|
-
) : reasoningOnly ? /* @__PURE__ */
|
|
7404
|
+
/* @__PURE__ */ React6.createElement(Text5, { color: "yellow", italic: true }, " waiting for first byte \u2014 this is normal, typically 5-60s depending on model + load")
|
|
7405
|
+
) : reasoningOnly ? /* @__PURE__ */ React6.createElement(Text5, { color: "yellow", italic: true }, " R1 is thinking before it speaks \u2014 body text arrives when reasoning finishes (typically 20-90s, this is normal)") : toolCallOnly ? /* @__PURE__ */ React6.createElement(Text5, { color: "magenta", italic: true }, " tool-call arguments streaming \u2014 the model is about to dispatch a tool") : event.reasoning ? /* @__PURE__ */ React6.createElement(Text5, { color: "yellow", italic: true }, " R1 still reasoning \u2014 body text or tool call arrives when thinking finishes") : null);
|
|
6652
7406
|
}
|
|
6653
7407
|
function Pulse() {
|
|
6654
7408
|
const tick = useTick();
|
|
6655
7409
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
6656
|
-
return /* @__PURE__ */
|
|
7410
|
+
return /* @__PURE__ */ React6.createElement(Text5, { color: "cyan" }, frames[Math.floor(tick / 4) % frames.length]);
|
|
6657
7411
|
}
|
|
6658
7412
|
function formatToolCallIndex(tb) {
|
|
6659
7413
|
if (!tb || tb.index === void 0) return "";
|
|
@@ -6672,7 +7426,7 @@ function lastLine(s, maxChars) {
|
|
|
6672
7426
|
}
|
|
6673
7427
|
function StatsLine({ stats }) {
|
|
6674
7428
|
const hit = (stats.cacheHitRatio * 100).toFixed(1);
|
|
6675
|
-
return /* @__PURE__ */
|
|
7429
|
+
return /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "\u258F "), /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "cache ", hit, "% \xB7 tokens ", stats.usage.promptTokens, " \u2192 ", stats.usage.completionTokens, " \xB7 $", stats.cost.toFixed(6)));
|
|
6676
7430
|
}
|
|
6677
7431
|
function truncate2(s, max) {
|
|
6678
7432
|
if (s.length <= max) return s;
|
|
@@ -6695,12 +7449,12 @@ ${s.slice(-max)}`;
|
|
|
6695
7449
|
}
|
|
6696
7450
|
|
|
6697
7451
|
// src/cli/ui/PlanConfirm.tsx
|
|
6698
|
-
import { Box as
|
|
6699
|
-
import
|
|
7452
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
7453
|
+
import React8 from "react";
|
|
6700
7454
|
|
|
6701
7455
|
// src/cli/ui/Select.tsx
|
|
6702
|
-
import { Box as
|
|
6703
|
-
import
|
|
7456
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
7457
|
+
import React7, { useState as useState3 } from "react";
|
|
6704
7458
|
function SingleSelect({
|
|
6705
7459
|
items,
|
|
6706
7460
|
initialValue,
|
|
@@ -6712,8 +7466,8 @@ function SingleSelect({
|
|
|
6712
7466
|
0,
|
|
6713
7467
|
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
6714
7468
|
);
|
|
6715
|
-
const [index, setIndex] =
|
|
6716
|
-
|
|
7469
|
+
const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
|
|
7470
|
+
useInput2((_input, key) => {
|
|
6717
7471
|
if (key.upArrow) {
|
|
6718
7472
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
6719
7473
|
} else if (key.downArrow) {
|
|
@@ -6725,7 +7479,7 @@ function SingleSelect({
|
|
|
6725
7479
|
onCancel();
|
|
6726
7480
|
}
|
|
6727
7481
|
});
|
|
6728
|
-
return /* @__PURE__ */
|
|
7482
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React7.createElement(
|
|
6729
7483
|
SelectRow,
|
|
6730
7484
|
{
|
|
6731
7485
|
key: item.value,
|
|
@@ -6733,7 +7487,7 @@ function SingleSelect({
|
|
|
6733
7487
|
active: i === index,
|
|
6734
7488
|
marker: i === index ? "\u25B8" : " "
|
|
6735
7489
|
}
|
|
6736
|
-
)), footer ? /* @__PURE__ */
|
|
7490
|
+
)), footer ? /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, footer)) : null);
|
|
6737
7491
|
}
|
|
6738
7492
|
function MultiSelect({
|
|
6739
7493
|
items,
|
|
@@ -6742,12 +7496,12 @@ function MultiSelect({
|
|
|
6742
7496
|
onCancel,
|
|
6743
7497
|
footer
|
|
6744
7498
|
}) {
|
|
6745
|
-
const [index, setIndex] =
|
|
7499
|
+
const [index, setIndex] = useState3(() => {
|
|
6746
7500
|
const first = items.findIndex((i) => !i.disabled);
|
|
6747
7501
|
return first === -1 ? 0 : first;
|
|
6748
7502
|
});
|
|
6749
|
-
const [selected, setSelected] =
|
|
6750
|
-
|
|
7503
|
+
const [selected, setSelected] = useState3(new Set(initialSelected));
|
|
7504
|
+
useInput2((input, key) => {
|
|
6751
7505
|
if (key.upArrow) {
|
|
6752
7506
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
6753
7507
|
} else if (key.downArrow) {
|
|
@@ -6768,10 +7522,10 @@ function MultiSelect({
|
|
|
6768
7522
|
onCancel();
|
|
6769
7523
|
}
|
|
6770
7524
|
});
|
|
6771
|
-
return /* @__PURE__ */
|
|
7525
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column" }, items.map((item, i) => {
|
|
6772
7526
|
const checked = selected.has(item.value);
|
|
6773
7527
|
const marker = checked ? "[x]" : "[ ]";
|
|
6774
|
-
return /* @__PURE__ */
|
|
7528
|
+
return /* @__PURE__ */ React7.createElement(
|
|
6775
7529
|
SelectRow,
|
|
6776
7530
|
{
|
|
6777
7531
|
key: item.value,
|
|
@@ -6780,7 +7534,7 @@ function MultiSelect({
|
|
|
6780
7534
|
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
6781
7535
|
}
|
|
6782
7536
|
);
|
|
6783
|
-
}), footer ? /* @__PURE__ */
|
|
7537
|
+
}), footer ? /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, footer)) : null);
|
|
6784
7538
|
}
|
|
6785
7539
|
function SelectRow({
|
|
6786
7540
|
item,
|
|
@@ -6788,7 +7542,7 @@ function SelectRow({
|
|
|
6788
7542
|
marker
|
|
6789
7543
|
}) {
|
|
6790
7544
|
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
6791
|
-
return /* @__PURE__ */
|
|
7545
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Text6, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React7.createElement(Box6, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, item.hint)) : null);
|
|
6792
7546
|
}
|
|
6793
7547
|
function findNextEnabled(items, from, step) {
|
|
6794
7548
|
if (items.length === 0) return 0;
|
|
@@ -6831,7 +7585,7 @@ function PlanConfirmInner({
|
|
|
6831
7585
|
const sourceLineBudget = Math.max(MIN_BODY_ROWS, Math.floor(renderedBudget / MARKDOWN_EXPANSION));
|
|
6832
7586
|
const visible = clampBodyByLines(charCapped, sourceLineBudget);
|
|
6833
7587
|
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
|
|
6834
|
-
return /* @__PURE__ */
|
|
7588
|
+
return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { color: "cyan", dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Markdown, { text: visible, projectRoot })), hasOpenQuestions ? /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(
|
|
6835
7589
|
SingleSelect,
|
|
6836
7590
|
{
|
|
6837
7591
|
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
@@ -6858,14 +7612,14 @@ function PlanConfirmInner({
|
|
|
6858
7612
|
}
|
|
6859
7613
|
)));
|
|
6860
7614
|
}
|
|
6861
|
-
var PlanConfirm =
|
|
7615
|
+
var PlanConfirm = React8.memo(PlanConfirmInner);
|
|
6862
7616
|
|
|
6863
7617
|
// src/cli/ui/PlanRefineInput.tsx
|
|
6864
|
-
import { Box as
|
|
6865
|
-
import
|
|
7618
|
+
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
7619
|
+
import React9, { useState as useState4 } from "react";
|
|
6866
7620
|
function PlanRefineInput({ mode, onSubmit, onCancel }) {
|
|
6867
|
-
const [value, setValue] =
|
|
6868
|
-
|
|
7621
|
+
const [value, setValue] = useState4("");
|
|
7622
|
+
useInput3((input, key) => {
|
|
6869
7623
|
if (key.escape) {
|
|
6870
7624
|
onCancel();
|
|
6871
7625
|
return;
|
|
@@ -6885,12 +7639,12 @@ function PlanRefineInput({ mode, onSubmit, onCancel }) {
|
|
|
6885
7639
|
const title = mode === "approve" ? "\u25B8 approving \u2014 any last instructions or answers to open questions?" : "\u25B8 refining \u2014 what should the model change?";
|
|
6886
7640
|
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.";
|
|
6887
7641
|
const blankHint = mode === "approve" ? " (Enter with blank = approve without extra instructions.)" : " (Enter with blank = ask the model to list concrete questions.)";
|
|
6888
|
-
return /* @__PURE__ */
|
|
7642
|
+
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" }, title)), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, hint, " Enter to send \xB7 Esc to return to the picker.", value === "" ? blankHint : "")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u203A "), /* @__PURE__ */ React9.createElement(Text8, null, value || " "), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u258D"))));
|
|
6889
7643
|
}
|
|
6890
7644
|
|
|
6891
7645
|
// src/cli/ui/PromptInput.tsx
|
|
6892
|
-
import { Box as
|
|
6893
|
-
import
|
|
7646
|
+
import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
|
|
7647
|
+
import React10, { useRef, useState as useState5 } from "react";
|
|
6894
7648
|
|
|
6895
7649
|
// src/cli/ui/multiline-keys.ts
|
|
6896
7650
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
@@ -7005,7 +7759,7 @@ function PromptInput({
|
|
|
7005
7759
|
disabled,
|
|
7006
7760
|
placeholder
|
|
7007
7761
|
}) {
|
|
7008
|
-
const [cursor, setCursor] =
|
|
7762
|
+
const [cursor, setCursor] = useState5(value.length);
|
|
7009
7763
|
const lastLocalValueRef = useRef(value);
|
|
7010
7764
|
if (value !== lastLocalValueRef.current) {
|
|
7011
7765
|
lastLocalValueRef.current = value;
|
|
@@ -7015,7 +7769,7 @@ function PromptInput({
|
|
|
7015
7769
|
}
|
|
7016
7770
|
const tick = useTick();
|
|
7017
7771
|
const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
|
|
7018
|
-
|
|
7772
|
+
useInput4(
|
|
7019
7773
|
(input, key) => {
|
|
7020
7774
|
const ke = {
|
|
7021
7775
|
input,
|
|
@@ -7050,13 +7804,13 @@ function PromptInput({
|
|
|
7050
7804
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
7051
7805
|
const borderColor = disabled ? "gray" : "cyan";
|
|
7052
7806
|
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
7053
|
-
return /* @__PURE__ */
|
|
7807
|
+
return /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
7054
7808
|
const isFirst = i === 0;
|
|
7055
7809
|
const showPlaceholder = isFirst && value.length === 0;
|
|
7056
7810
|
const isCursorLine = i === cursorLine;
|
|
7057
7811
|
return (
|
|
7058
7812
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
7059
|
-
/* @__PURE__ */
|
|
7813
|
+
/* @__PURE__ */ React10.createElement(Box9, { key: i }, isFirst ? /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React10.createElement(Text9, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React10.createElement(
|
|
7060
7814
|
LineWithCursor,
|
|
7061
7815
|
{
|
|
7062
7816
|
line,
|
|
@@ -7064,7 +7818,7 @@ function PromptInput({
|
|
|
7064
7818
|
showCursor,
|
|
7065
7819
|
borderColor
|
|
7066
7820
|
}
|
|
7067
|
-
) : /* @__PURE__ */
|
|
7821
|
+
) : /* @__PURE__ */ React10.createElement(Text9, null, line))
|
|
7068
7822
|
);
|
|
7069
7823
|
}));
|
|
7070
7824
|
}
|
|
@@ -7078,16 +7832,17 @@ function LineWithCursor({
|
|
|
7078
7832
|
const atCursor = line.slice(col, col + 1);
|
|
7079
7833
|
const after = line.slice(col + 1);
|
|
7080
7834
|
if (atCursor.length === 0) {
|
|
7081
|
-
return /* @__PURE__ */
|
|
7835
|
+
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Text9, null, before), /* @__PURE__ */ React10.createElement(Text9, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
7082
7836
|
}
|
|
7083
|
-
return /* @__PURE__ */
|
|
7837
|
+
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Text9, null, before), /* @__PURE__ */ React10.createElement(Text9, { inverse: showCursor }, atCursor), /* @__PURE__ */ React10.createElement(Text9, null, after));
|
|
7084
7838
|
}
|
|
7085
7839
|
|
|
7086
7840
|
// src/cli/ui/ShellConfirm.tsx
|
|
7087
|
-
import { Box as
|
|
7088
|
-
import
|
|
7089
|
-
function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
7090
|
-
|
|
7841
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
7842
|
+
import React11 from "react";
|
|
7843
|
+
function ShellConfirm({ command, allowPrefix, kind, onChoose }) {
|
|
7844
|
+
const isBackground = kind === "run_background";
|
|
7845
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: "yellow" }, isBackground ? "\u25B8 model wants to start a BACKGROUND process" : "\u25B8 model wants to run a shell command")), isBackground ? /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (long-running: dev server / watcher; keeps running after approval, /kill to stop)")) : null, /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow", dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "$ "), /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, command))), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(
|
|
7091
7846
|
SingleSelect,
|
|
7092
7847
|
{
|
|
7093
7848
|
initialValue: "run_once",
|
|
@@ -7144,8 +7899,8 @@ function derivePrefix(command) {
|
|
|
7144
7899
|
}
|
|
7145
7900
|
|
|
7146
7901
|
// src/cli/ui/SlashArgPicker.tsx
|
|
7147
|
-
import { Box as
|
|
7148
|
-
import
|
|
7902
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
7903
|
+
import React12 from "react";
|
|
7149
7904
|
function SlashArgPicker({
|
|
7150
7905
|
matches,
|
|
7151
7906
|
selectedIndex,
|
|
@@ -7154,11 +7909,11 @@ function SlashArgPicker({
|
|
|
7154
7909
|
partial
|
|
7155
7910
|
}) {
|
|
7156
7911
|
if (kind === "hint") {
|
|
7157
|
-
return /* @__PURE__ */
|
|
7912
|
+
return /* @__PURE__ */ React12.createElement(Box11, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " ", /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary));
|
|
7158
7913
|
}
|
|
7159
7914
|
if (matches === null) return null;
|
|
7160
7915
|
if (matches.length === 0) {
|
|
7161
|
-
return /* @__PURE__ */
|
|
7916
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " ", /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary), /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, ' no match for "', partial, '" \u2014 keep typing, or Backspace to edit'));
|
|
7162
7917
|
}
|
|
7163
7918
|
const MAX = 8;
|
|
7164
7919
|
const total = matches.length;
|
|
@@ -7166,26 +7921,26 @@ function SlashArgPicker({
|
|
|
7166
7921
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
7167
7922
|
const hiddenAbove = windowStart;
|
|
7168
7923
|
const hiddenBelow = total - windowStart - shown.length;
|
|
7169
|
-
return /* @__PURE__ */
|
|
7924
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " ", /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary), hiddenAbove > 0 ? /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((value, i) => /* @__PURE__ */ React12.createElement(ArgRow, { key: value, value, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
|
|
7170
7925
|
}
|
|
7171
7926
|
function ArgRow({ value, isSelected }) {
|
|
7172
7927
|
const marker = isSelected ? "\u25B8" : " ";
|
|
7173
7928
|
if (isSelected) {
|
|
7174
|
-
return /* @__PURE__ */
|
|
7929
|
+
return /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, marker, " ", value));
|
|
7175
7930
|
}
|
|
7176
|
-
return /* @__PURE__ */
|
|
7931
|
+
return /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, marker, " ", value));
|
|
7177
7932
|
}
|
|
7178
7933
|
|
|
7179
7934
|
// src/cli/ui/SlashSuggestions.tsx
|
|
7180
|
-
import { Box as
|
|
7181
|
-
import
|
|
7935
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
7936
|
+
import React13 from "react";
|
|
7182
7937
|
function SlashSuggestions({
|
|
7183
7938
|
matches,
|
|
7184
7939
|
selectedIndex
|
|
7185
7940
|
}) {
|
|
7186
7941
|
if (matches === null) return null;
|
|
7187
7942
|
if (matches.length === 0) {
|
|
7188
|
-
return /* @__PURE__ */
|
|
7943
|
+
return /* @__PURE__ */ React13.createElement(Box12, { paddingX: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
|
|
7189
7944
|
}
|
|
7190
7945
|
const MAX = 8;
|
|
7191
7946
|
const total = matches.length;
|
|
@@ -7193,21 +7948,21 @@ function SlashSuggestions({
|
|
|
7193
7948
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
7194
7949
|
const hiddenAbove = windowStart;
|
|
7195
7950
|
const hiddenBelow = total - windowStart - shown.length;
|
|
7196
|
-
return /* @__PURE__ */
|
|
7951
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React13.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
|
|
7197
7952
|
}
|
|
7198
7953
|
function SuggestionRow({ spec, isSelected }) {
|
|
7199
7954
|
const marker = isSelected ? "\u25B8" : " ";
|
|
7200
7955
|
const name = `/${spec.cmd}`;
|
|
7201
7956
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
7202
7957
|
if (isSelected) {
|
|
7203
|
-
return /* @__PURE__ */
|
|
7958
|
+
return /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, " ", spec.summary));
|
|
7204
7959
|
}
|
|
7205
|
-
return /* @__PURE__ */
|
|
7960
|
+
return /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
7206
7961
|
}
|
|
7207
7962
|
|
|
7208
7963
|
// src/cli/ui/StatsPanel.tsx
|
|
7209
|
-
import { Box as
|
|
7210
|
-
import
|
|
7964
|
+
import { Box as Box13, Text as Text13, useStdout as useStdout3 } from "ink";
|
|
7965
|
+
import React14 from "react";
|
|
7211
7966
|
var WORDMARK_STYLES = [
|
|
7212
7967
|
{ ch: "\u25C8", color: "#5eead4", isLogo: true },
|
|
7213
7968
|
// teal — brand mark
|
|
@@ -7233,7 +7988,7 @@ function Wordmark({ busy }) {
|
|
|
7233
7988
|
const tick = useTick();
|
|
7234
7989
|
const period = busy ? 5 : 12;
|
|
7235
7990
|
const bright = Math.floor(tick / period) % 2 === 0;
|
|
7236
|
-
return /* @__PURE__ */
|
|
7991
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, WORDMARK_STYLES.map((c) => /* @__PURE__ */ React14.createElement(Text13, { key: `${c.ch}-${c.color}`, color: c.color, bold: c.isLogo ? bright : true }, c.ch)));
|
|
7237
7992
|
}
|
|
7238
7993
|
var NARROW_BREAKPOINT = 120;
|
|
7239
7994
|
var COLD_START_TURNS = 3;
|
|
@@ -7245,6 +8000,7 @@ function StatsPanel({
|
|
|
7245
8000
|
branchBudget,
|
|
7246
8001
|
reasoningEffort,
|
|
7247
8002
|
planMode,
|
|
8003
|
+
editMode,
|
|
7248
8004
|
balance,
|
|
7249
8005
|
updateAvailable,
|
|
7250
8006
|
busy
|
|
@@ -7252,11 +8008,11 @@ function StatsPanel({
|
|
|
7252
8008
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
7253
8009
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
7254
8010
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
7255
|
-
const { stdout: stdout2 } =
|
|
8011
|
+
const { stdout: stdout2 } = useStdout3();
|
|
7256
8012
|
const columns = stdout2?.columns ?? 80;
|
|
7257
8013
|
const narrow = columns < NARROW_BREAKPOINT;
|
|
7258
8014
|
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
7259
|
-
return /* @__PURE__ */
|
|
8015
|
+
return /* @__PURE__ */ React14.createElement(Box13, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React14.createElement(
|
|
7260
8016
|
Header,
|
|
7261
8017
|
{
|
|
7262
8018
|
model,
|
|
@@ -7266,12 +8022,13 @@ function StatsPanel({
|
|
|
7266
8022
|
branchBudget: branchBudget ?? 1,
|
|
7267
8023
|
reasoningEffort,
|
|
7268
8024
|
planMode,
|
|
8025
|
+
editMode,
|
|
7269
8026
|
turns: summary.turns,
|
|
7270
8027
|
updateAvailable,
|
|
7271
8028
|
narrow,
|
|
7272
8029
|
busy: busy ?? false
|
|
7273
8030
|
}
|
|
7274
|
-
), narrow ? /* @__PURE__ */
|
|
8031
|
+
), narrow ? /* @__PURE__ */ React14.createElement(
|
|
7275
8032
|
StackedMetrics,
|
|
7276
8033
|
{
|
|
7277
8034
|
summary,
|
|
@@ -7280,7 +8037,7 @@ function StatsPanel({
|
|
|
7280
8037
|
balance,
|
|
7281
8038
|
coldStart
|
|
7282
8039
|
}
|
|
7283
|
-
) : /* @__PURE__ */
|
|
8040
|
+
) : /* @__PURE__ */ React14.createElement(
|
|
7284
8041
|
InlineMetrics,
|
|
7285
8042
|
{
|
|
7286
8043
|
summary,
|
|
@@ -7299,12 +8056,13 @@ function Header({
|
|
|
7299
8056
|
branchBudget,
|
|
7300
8057
|
reasoningEffort,
|
|
7301
8058
|
planMode,
|
|
8059
|
+
editMode,
|
|
7302
8060
|
turns,
|
|
7303
8061
|
updateAvailable,
|
|
7304
8062
|
narrow,
|
|
7305
8063
|
busy
|
|
7306
8064
|
}) {
|
|
7307
|
-
return /* @__PURE__ */
|
|
8065
|
+
return /* @__PURE__ */ React14.createElement(Box13, { justifyContent: "space-between" }, /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Wordmark, { busy }), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, model), narrow ? null : /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React14.createElement(Text13, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React14.createElement(Text13, { color: "blue" }, " \xB7 branch", branchBudget) : null, reasoningEffort === "max" ? /* @__PURE__ */ React14.createElement(Text13, { color: "green" }, " \xB7 max") : null, reasoningEffort === "high" ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, " \xB7 high") : null, planMode ? /* @__PURE__ */ React14.createElement(Text13, { color: "red", bold: true }, " \xB7 PLAN") : null, editMode ? /* @__PURE__ */ React14.createElement(Text13, { color: editMode === "auto" ? "magenta" : "cyan", bold: true }, editMode === "auto" ? " \xB7 AUTO" : " \xB7 review") : null), /* @__PURE__ */ React14.createElement(Text13, null, updateAvailable ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
|
|
7308
8066
|
}
|
|
7309
8067
|
function InlineMetrics({
|
|
7310
8068
|
summary,
|
|
@@ -7313,7 +8071,7 @@ function InlineMetrics({
|
|
|
7313
8071
|
balance,
|
|
7314
8072
|
coldStart
|
|
7315
8073
|
}) {
|
|
7316
|
-
return /* @__PURE__ */
|
|
8074
|
+
return /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React14.createElement(ContextCell, { ratio: ctxRatio, promptTokens: summary.lastPromptTokens, ctxMax }), /* @__PURE__ */ React14.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React14.createElement(CostCell, { summary, coldStart }), balance ? /* @__PURE__ */ React14.createElement(BalanceCell, { balance }) : null);
|
|
7317
8075
|
}
|
|
7318
8076
|
function StackedMetrics({
|
|
7319
8077
|
summary,
|
|
@@ -7322,7 +8080,7 @@ function StackedMetrics({
|
|
|
7322
8080
|
balance,
|
|
7323
8081
|
coldStart
|
|
7324
8082
|
}) {
|
|
7325
|
-
return /* @__PURE__ */
|
|
8083
|
+
return /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(
|
|
7326
8084
|
ContextCell,
|
|
7327
8085
|
{
|
|
7328
8086
|
ratio: ctxRatio,
|
|
@@ -7330,7 +8088,7 @@ function StackedMetrics({
|
|
|
7330
8088
|
ctxMax,
|
|
7331
8089
|
showBar: true
|
|
7332
8090
|
}
|
|
7333
|
-
), balance ? /* @__PURE__ */
|
|
8091
|
+
), balance ? /* @__PURE__ */ React14.createElement(BalanceCell, { balance }) : null, /* @__PURE__ */ React14.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React14.createElement(CostCell, { summary, coldStart }));
|
|
7334
8092
|
}
|
|
7335
8093
|
function ContextCell({
|
|
7336
8094
|
ratio,
|
|
@@ -7339,11 +8097,11 @@ function ContextCell({
|
|
|
7339
8097
|
showBar
|
|
7340
8098
|
}) {
|
|
7341
8099
|
if (promptTokens === 0) {
|
|
7342
|
-
return /* @__PURE__ */
|
|
8100
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "ctx "), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "\u2014 (no turns yet)"));
|
|
7343
8101
|
}
|
|
7344
8102
|
const color = ratio >= 0.8 ? "red" : ratio >= 0.6 ? "yellow" : "green";
|
|
7345
8103
|
const pct2 = Math.round(ratio * 100);
|
|
7346
|
-
return /* @__PURE__ */
|
|
8104
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "ctx "), showBar ? /* @__PURE__ */ React14.createElement(Bar, { ratio, color }) : null, showBar ? /* @__PURE__ */ React14.createElement(Text13, null, " ") : null, /* @__PURE__ */ React14.createElement(Text13, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React14.createElement(Text13, { color: "red", bold: true }, " \xB7 /compact") : null);
|
|
7347
8105
|
}
|
|
7348
8106
|
function CacheCell({
|
|
7349
8107
|
hitRatio,
|
|
@@ -7352,33 +8110,33 @@ function CacheCell({
|
|
|
7352
8110
|
}) {
|
|
7353
8111
|
const pct2 = (hitRatio * 100).toFixed(1);
|
|
7354
8112
|
if (turns === 0) {
|
|
7355
|
-
return /* @__PURE__ */
|
|
8113
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "cache "), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "\u2014"));
|
|
7356
8114
|
}
|
|
7357
8115
|
if (coldStart) {
|
|
7358
|
-
return /* @__PURE__ */
|
|
8116
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "cache "), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true, italic: true }, "(cold start)"));
|
|
7359
8117
|
}
|
|
7360
8118
|
const color = hitRatio >= 0.7 ? "green" : hitRatio >= 0.4 ? "yellow" : "red";
|
|
7361
|
-
return /* @__PURE__ */
|
|
8119
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "cache "), /* @__PURE__ */ React14.createElement(Text13, { color, bold: true }, pct2, "%"));
|
|
7362
8120
|
}
|
|
7363
8121
|
function CostCell({
|
|
7364
8122
|
summary,
|
|
7365
8123
|
coldStart
|
|
7366
8124
|
}) {
|
|
7367
8125
|
if (summary.turns === 0) {
|
|
7368
|
-
return /* @__PURE__ */
|
|
8126
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "cost "), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "\u2014"));
|
|
7369
8127
|
}
|
|
7370
8128
|
const primaryColor = coldStart ? void 0 : "green";
|
|
7371
|
-
return /* @__PURE__ */
|
|
8129
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "cost "), /* @__PURE__ */ React14.createElement(Text13, { color: primaryColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")"));
|
|
7372
8130
|
}
|
|
7373
8131
|
function BalanceCell({ balance }) {
|
|
7374
8132
|
const color = balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green";
|
|
7375
|
-
return /* @__PURE__ */
|
|
8133
|
+
return /* @__PURE__ */ React14.createElement(Text13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "balance "), /* @__PURE__ */ React14.createElement(Text13, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
7376
8134
|
}
|
|
7377
8135
|
function Bar({ ratio, color }) {
|
|
7378
8136
|
const cells = 10;
|
|
7379
8137
|
const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
|
|
7380
8138
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(cells - filled);
|
|
7381
|
-
return /* @__PURE__ */
|
|
8139
|
+
return /* @__PURE__ */ React14.createElement(Text13, { color }, bar);
|
|
7382
8140
|
}
|
|
7383
8141
|
function formatTokens(n) {
|
|
7384
8142
|
if (n < 1024) return String(n);
|
|
@@ -7814,7 +8572,7 @@ var SLASH_COMMANDS = [
|
|
|
7814
8572
|
{
|
|
7815
8573
|
cmd: "compact",
|
|
7816
8574
|
argsHint: "[tokens]",
|
|
7817
|
-
summary: "shrink oversized tool results in the log
|
|
8575
|
+
summary: "shrink oversized tool results AND tool-call args (edit_file search/replace) in the log; cap in tokens, default 4000"
|
|
7818
8576
|
},
|
|
7819
8577
|
{ cmd: "keys", summary: "show all keyboard shortcuts and prompt prefixes" },
|
|
7820
8578
|
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
@@ -7827,6 +8585,17 @@ var SLASH_COMMANDS = [
|
|
|
7827
8585
|
{ cmd: "apply", summary: "commit pending edit blocks to disk", contextual: "code" },
|
|
7828
8586
|
{ cmd: "discard", summary: "drop pending edit blocks without writing", contextual: "code" },
|
|
7829
8587
|
{ cmd: "undo", summary: "roll back the last applied edit batch", contextual: "code" },
|
|
8588
|
+
{
|
|
8589
|
+
cmd: "history",
|
|
8590
|
+
summary: "list every edit batch this session (ids for /show, undone markers)",
|
|
8591
|
+
contextual: "code"
|
|
8592
|
+
},
|
|
8593
|
+
{
|
|
8594
|
+
cmd: "show",
|
|
8595
|
+
argsHint: "[id]",
|
|
8596
|
+
summary: "dump a stored edit diff (omit id for newest non-undone)",
|
|
8597
|
+
contextual: "code"
|
|
8598
|
+
},
|
|
7830
8599
|
{
|
|
7831
8600
|
cmd: "commit",
|
|
7832
8601
|
argsHint: '"msg"',
|
|
@@ -7844,6 +8613,26 @@ var SLASH_COMMANDS = [
|
|
|
7844
8613
|
cmd: "apply-plan",
|
|
7845
8614
|
summary: "force-approve a pending / in-text plan (fallback if picker was missed)",
|
|
7846
8615
|
contextual: "code"
|
|
8616
|
+
},
|
|
8617
|
+
{
|
|
8618
|
+
cmd: "mode",
|
|
8619
|
+
argsHint: "[review|auto]",
|
|
8620
|
+
summary: "edit-gate: review (queue for /apply) or auto (apply+undo banner). Shift+Tab cycles.",
|
|
8621
|
+
contextual: "code",
|
|
8622
|
+
argCompleter: ["review", "auto"]
|
|
8623
|
+
},
|
|
8624
|
+
{ cmd: "jobs", summary: "list background jobs started by run_background", contextual: "code" },
|
|
8625
|
+
{
|
|
8626
|
+
cmd: "kill",
|
|
8627
|
+
argsHint: "<id>",
|
|
8628
|
+
summary: "stop a background job by id (SIGTERM \u2192 SIGKILL after grace)",
|
|
8629
|
+
contextual: "code"
|
|
8630
|
+
},
|
|
8631
|
+
{
|
|
8632
|
+
cmd: "logs",
|
|
8633
|
+
argsHint: "<id> [lines]",
|
|
8634
|
+
summary: "tail a background job's output (default last 80 lines)",
|
|
8635
|
+
contextual: "code"
|
|
7847
8636
|
}
|
|
7848
8637
|
];
|
|
7849
8638
|
function suggestSlashCommands(prefix, codeMode = false) {
|
|
@@ -7912,6 +8701,8 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
7912
8701
|
" Backspace delete left; Delete delete under cursor",
|
|
7913
8702
|
" Esc abort the in-flight turn",
|
|
7914
8703
|
" y / n accept / reject pending edits (code mode)",
|
|
8704
|
+
" Shift+Tab cycle edit gate: review \u2194 AUTO (code mode, persists to config)",
|
|
8705
|
+
" u undo the latest non-undone edit batch (session-wide, not just banner)",
|
|
7915
8706
|
"",
|
|
7916
8707
|
"Prompt prefixes:",
|
|
7917
8708
|
" /<name> slash command; Tab/Enter picks from the suggestion list",
|
|
@@ -7960,10 +8751,16 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
7960
8751
|
" /retry truncate & resend your last message (fresh sample from the model)",
|
|
7961
8752
|
" /apply (code mode) commit the pending edit blocks to disk",
|
|
7962
8753
|
" /discard (code mode) drop pending edits without writing",
|
|
7963
|
-
" /undo (code mode) roll back the
|
|
8754
|
+
" /undo (code mode) roll back the latest non-undone edit batch",
|
|
8755
|
+
" /history (code mode) list every edit batch this session",
|
|
8756
|
+
" /show [id] (code mode) dump a stored edit diff (newest when id omitted)",
|
|
7964
8757
|
' /commit "msg" (code mode) git add -A && git commit -m "msg"',
|
|
7965
8758
|
" /plan [on|off] (code mode) toggle read-only plan mode; writes gated behind submit_plan + your approval",
|
|
7966
8759
|
" /apply-plan (code mode) force-approve pending/in-text plan (fallback)",
|
|
8760
|
+
" /mode [review|auto] (code mode) edit-gate: queue edits for /apply or apply instantly (Shift+Tab cycles, u undoes within 5s)",
|
|
8761
|
+
" /jobs (code mode) list background processes (run_background) \u2014 running and exited",
|
|
8762
|
+
" /kill <id> (code mode) stop a background job by id (SIGTERM \u2192 SIGKILL)",
|
|
8763
|
+
" /logs <id> [lines] (code mode) tail a background job's output (default 80 lines)",
|
|
7967
8764
|
" /sessions list saved sessions (current is marked with \u25B8)",
|
|
7968
8765
|
" /forget delete the current session from disk",
|
|
7969
8766
|
" /new start fresh: drop all context + clear scrollback",
|
|
@@ -8128,7 +8925,19 @@ ${entry.text}`
|
|
|
8128
8925
|
info: "/undo is only available inside `reasonix code` \u2014 chat mode doesn't apply edits."
|
|
8129
8926
|
};
|
|
8130
8927
|
}
|
|
8131
|
-
return { info: ctx.codeUndo() };
|
|
8928
|
+
return { info: ctx.codeUndo(args) };
|
|
8929
|
+
}
|
|
8930
|
+
case "history": {
|
|
8931
|
+
if (!ctx.codeHistory) {
|
|
8932
|
+
return { info: "/history is only available inside `reasonix code`." };
|
|
8933
|
+
}
|
|
8934
|
+
return { info: ctx.codeHistory() };
|
|
8935
|
+
}
|
|
8936
|
+
case "show": {
|
|
8937
|
+
if (!ctx.codeShowEdit) {
|
|
8938
|
+
return { info: "/show is only available inside `reasonix code`." };
|
|
8939
|
+
}
|
|
8940
|
+
return { info: ctx.codeShowEdit(args) };
|
|
8132
8941
|
}
|
|
8133
8942
|
case "apply": {
|
|
8134
8943
|
if (!ctx.codeApply) {
|
|
@@ -8168,6 +8977,81 @@ ${entry.text}`
|
|
|
8168
8977
|
info: "\u25B8 plan mode OFF \u2014 write tools are live again. Model can still propose plans autonomously for large tasks."
|
|
8169
8978
|
};
|
|
8170
8979
|
}
|
|
8980
|
+
case "jobs": {
|
|
8981
|
+
if (!ctx.jobs) {
|
|
8982
|
+
return { info: "/jobs is only available inside `reasonix code`." };
|
|
8983
|
+
}
|
|
8984
|
+
const rows = ctx.jobs.list();
|
|
8985
|
+
if (rows.length === 0) {
|
|
8986
|
+
return { info: "no background jobs yet \u2014 use run_background to start one" };
|
|
8987
|
+
}
|
|
8988
|
+
const lines = ["Background jobs:"];
|
|
8989
|
+
for (const r of rows) {
|
|
8990
|
+
const age = ((Date.now() - r.startedAt) / 1e3).toFixed(1);
|
|
8991
|
+
const state = r.running ? `running \xB7 pid ${r.pid ?? "?"}` : r.exitCode !== null ? `exit ${r.exitCode}` : r.spawnError ? "failed" : "stopped";
|
|
8992
|
+
lines.push(
|
|
8993
|
+
` ${String(r.id).padStart(3)} ${state.padEnd(20)} ${age.padStart(6)}s ago $ ${r.command}`
|
|
8994
|
+
);
|
|
8995
|
+
}
|
|
8996
|
+
lines.push("");
|
|
8997
|
+
lines.push("/kill <id> to stop one \xB7 /logs <id> [lines] to tail output");
|
|
8998
|
+
return { info: lines.join("\n") };
|
|
8999
|
+
}
|
|
9000
|
+
case "kill": {
|
|
9001
|
+
if (!ctx.jobs) return { info: "/kill is only available inside `reasonix code`." };
|
|
9002
|
+
const id = Number.parseInt(args[0] ?? "", 10);
|
|
9003
|
+
if (!Number.isFinite(id)) return { info: "usage: /kill <id> (see /jobs for ids)" };
|
|
9004
|
+
const rec = ctx.jobs.list().find((r) => r.id === id);
|
|
9005
|
+
if (!rec) return { info: `job ${id}: not found` };
|
|
9006
|
+
if (!rec.running) return { info: `job ${id} already exited (${rec.exitCode ?? "?"})` };
|
|
9007
|
+
const jobsRef = ctx.jobs;
|
|
9008
|
+
void (async () => {
|
|
9009
|
+
const final = await jobsRef.stop(id);
|
|
9010
|
+
if (!final) return;
|
|
9011
|
+
const status = final.running ? "still alive after SIGKILL (!) \u2014 report this as a bug" : final.exitCode !== null ? `exit ${final.exitCode}` : "stopped";
|
|
9012
|
+
ctx.postInfo?.(`\u25B8 job ${id} ${status}`);
|
|
9013
|
+
})();
|
|
9014
|
+
return {
|
|
9015
|
+
info: `\u25B8 stopping job ${id} (tree kill: SIGTERM \u2192 SIGKILL after 2s grace; Windows: taskkill /T /F)`
|
|
9016
|
+
};
|
|
9017
|
+
}
|
|
9018
|
+
case "logs": {
|
|
9019
|
+
if (!ctx.jobs) return { info: "/logs is only available inside `reasonix code`." };
|
|
9020
|
+
const id = Number.parseInt(args[0] ?? "", 10);
|
|
9021
|
+
if (!Number.isFinite(id)) {
|
|
9022
|
+
return { info: "usage: /logs <id> [lines] (default last 80 lines)" };
|
|
9023
|
+
}
|
|
9024
|
+
const requested = Number.parseInt(args[1] ?? "", 10);
|
|
9025
|
+
const tail = Number.isFinite(requested) && requested > 0 ? requested : 80;
|
|
9026
|
+
const out = ctx.jobs.read(id, { tailLines: tail });
|
|
9027
|
+
if (!out) return { info: `job ${id}: not found` };
|
|
9028
|
+
const status = out.running ? `running \xB7 pid ${out.pid ?? "?"}` : out.exitCode !== null ? `exited ${out.exitCode}` : out.spawnError ? `failed (${out.spawnError})` : "stopped";
|
|
9029
|
+
const header2 = `[job ${id} \xB7 ${status}]
|
|
9030
|
+
$ ${out.command}`;
|
|
9031
|
+
return { info: out.output ? `${header2}
|
|
9032
|
+
${out.output}` : header2 };
|
|
9033
|
+
}
|
|
9034
|
+
case "mode": {
|
|
9035
|
+
if (!ctx.setEditMode) {
|
|
9036
|
+
return {
|
|
9037
|
+
info: "/mode is only available inside `reasonix code`."
|
|
9038
|
+
};
|
|
9039
|
+
}
|
|
9040
|
+
const raw = (args[0] ?? "").toLowerCase();
|
|
9041
|
+
const current = ctx.editMode ?? "review";
|
|
9042
|
+
let target;
|
|
9043
|
+
if (raw === "review") target = "review";
|
|
9044
|
+
else if (raw === "auto") target = "auto";
|
|
9045
|
+
else if (raw === "") {
|
|
9046
|
+
target = current === "auto" ? "review" : "auto";
|
|
9047
|
+
} else {
|
|
9048
|
+
return { info: "usage: /mode <review|auto> (Shift+Tab also cycles)" };
|
|
9049
|
+
}
|
|
9050
|
+
ctx.setEditMode(target);
|
|
9051
|
+
return {
|
|
9052
|
+
info: target === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo, or /undo later" : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)"
|
|
9053
|
+
};
|
|
9054
|
+
}
|
|
8171
9055
|
case "apply-plan":
|
|
8172
9056
|
case "applyplan": {
|
|
8173
9057
|
if (!ctx.setPlanMode) {
|
|
@@ -8203,11 +9087,11 @@ ${entry.text}`
|
|
|
8203
9087
|
const { healedCount, tokensSaved, charsSaved } = loop.compact(cap);
|
|
8204
9088
|
if (healedCount === 0) {
|
|
8205
9089
|
return {
|
|
8206
|
-
info: `\u25B8 nothing to compact \u2014 no tool result in history
|
|
9090
|
+
info: `\u25B8 nothing to compact \u2014 no tool result or tool-call args in history exceed ${cap.toLocaleString()} tokens.`
|
|
8207
9091
|
};
|
|
8208
9092
|
}
|
|
8209
9093
|
return {
|
|
8210
|
-
info: `\u25B8 compacted ${healedCount}
|
|
9094
|
+
info: `\u25B8 compacted ${healedCount} payload(s) to ${cap.toLocaleString()} tokens each (tool results + tool-call args), saved ${tokensSaved.toLocaleString()} tokens (${charsSaved.toLocaleString()} chars). Session file rewritten.`
|
|
8211
9095
|
};
|
|
8212
9096
|
}
|
|
8213
9097
|
case "sessions": {
|
|
@@ -8313,6 +9197,7 @@ ${entry.text}`
|
|
|
8313
9197
|
const mcpLine = ` mcp ${mcpCount} server(s), ${toolCount} tool(s) in registry`;
|
|
8314
9198
|
const pendingLine = pending > 0 ? ` edits ${pending} pending (/apply to commit, /discard to drop)` : "";
|
|
8315
9199
|
const planLine = ctx.planMode ? " plan ON \u2014 writes gated (submit_plan + approval)" : "";
|
|
9200
|
+
const modeLine = ctx.editMode === "auto" ? " mode AUTO \u2014 edits apply immediately (u to undo within 5s \xB7 Shift+Tab to flip)" : ctx.editMode === "review" ? " mode review \u2014 edits queue for /apply or y (Shift+Tab to flip)" : "";
|
|
8316
9201
|
const lines = [
|
|
8317
9202
|
` model ${loop.model}`,
|
|
8318
9203
|
` flags harvest=${loop.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop.stream ? "on" : "off"} \xB7 effort=${loop.reasoningEffort}`,
|
|
@@ -8322,6 +9207,7 @@ ${entry.text}`
|
|
|
8322
9207
|
];
|
|
8323
9208
|
if (pendingLine) lines.push(pendingLine);
|
|
8324
9209
|
if (planLine) lines.push(planLine);
|
|
9210
|
+
if (modeLine) lines.push(modeLine);
|
|
8325
9211
|
return { info: lines.join("\n") };
|
|
8326
9212
|
}
|
|
8327
9213
|
case "model": {
|
|
@@ -8408,14 +9294,18 @@ ${entry.text}`
|
|
|
8408
9294
|
const raw = (args[0] ?? "").toLowerCase();
|
|
8409
9295
|
if (raw === "") {
|
|
8410
9296
|
return {
|
|
8411
|
-
info: `reasoning_effort \u2192 ${loop.reasoningEffort} (use /effort high for cheaper/faster, /effort max for the agent-class default)`
|
|
9297
|
+
info: `reasoning_effort \u2192 ${loop.reasoningEffort} (use /effort high for cheaper/faster, /effort max for the agent-class default \xB7 persisted across relaunches)`
|
|
8412
9298
|
};
|
|
8413
9299
|
}
|
|
8414
9300
|
if (raw !== "high" && raw !== "max") {
|
|
8415
9301
|
return { info: "usage: /effort <high|max>" };
|
|
8416
9302
|
}
|
|
8417
9303
|
loop.configure({ reasoningEffort: raw });
|
|
8418
|
-
|
|
9304
|
+
try {
|
|
9305
|
+
saveReasoningEffort(raw);
|
|
9306
|
+
} catch {
|
|
9307
|
+
}
|
|
9308
|
+
return { info: `reasoning_effort \u2192 ${raw} (persisted)` };
|
|
8419
9309
|
}
|
|
8420
9310
|
default:
|
|
8421
9311
|
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
@@ -8827,6 +9717,14 @@ function gitTail(res) {
|
|
|
8827
9717
|
|
|
8828
9718
|
// src/cli/ui/App.tsx
|
|
8829
9719
|
var FLUSH_INTERVAL_MS = 100;
|
|
9720
|
+
function isEntryFullyUndone(e) {
|
|
9721
|
+
return e.snapshots.length > 0 && e.snapshots.every((s) => e.undoneFiles.has(s.path));
|
|
9722
|
+
}
|
|
9723
|
+
function entryStatus(e) {
|
|
9724
|
+
if (e.undoneFiles.size === 0) return "applied";
|
|
9725
|
+
if (isEntryFullyUndone(e)) return "UNDONE";
|
|
9726
|
+
return "PARTIAL";
|
|
9727
|
+
}
|
|
8830
9728
|
var PLAIN_UI = process.env.REASONIX_UI === "plain";
|
|
8831
9729
|
function App({
|
|
8832
9730
|
model,
|
|
@@ -8842,36 +9740,66 @@ function App({
|
|
|
8842
9740
|
codeMode
|
|
8843
9741
|
}) {
|
|
8844
9742
|
const { exit } = useApp();
|
|
8845
|
-
const [historical, setHistorical] =
|
|
8846
|
-
const [streaming, setStreaming] =
|
|
8847
|
-
const [input, setInput] =
|
|
8848
|
-
const [busy, setBusy] =
|
|
9743
|
+
const [historical, setHistorical] = useState6([]);
|
|
9744
|
+
const [streaming, setStreaming] = useState6(null);
|
|
9745
|
+
const [input, setInput] = useState6("");
|
|
9746
|
+
const [busy, setBusy] = useState6(false);
|
|
8849
9747
|
const abortedThisTurn = useRef2(false);
|
|
8850
|
-
const [ongoingTool, setOngoingTool] =
|
|
8851
|
-
const [toolProgress, setToolProgress] =
|
|
8852
|
-
const [subagentActivity, setSubagentActivity] =
|
|
8853
|
-
const [statusLine, setStatusLine] =
|
|
8854
|
-
const [balance, setBalance] =
|
|
8855
|
-
const [models, setModels] =
|
|
8856
|
-
const [latestVersion, setLatestVersion] =
|
|
9748
|
+
const [ongoingTool, setOngoingTool] = useState6(null);
|
|
9749
|
+
const [toolProgress, setToolProgress] = useState6(null);
|
|
9750
|
+
const [subagentActivity, setSubagentActivity] = useState6(null);
|
|
9751
|
+
const [statusLine, setStatusLine] = useState6(null);
|
|
9752
|
+
const [balance, setBalance] = useState6(null);
|
|
9753
|
+
const [models, setModels] = useState6(null);
|
|
9754
|
+
const [latestVersion, setLatestVersion] = useState6(null);
|
|
8857
9755
|
const updateAvailable = latestVersion && compareVersions(VERSION, latestVersion) < 0 ? latestVersion : null;
|
|
8858
|
-
const [hookList, setHookList] =
|
|
9756
|
+
const [hookList, setHookList] = useState6(
|
|
8859
9757
|
() => loadHooks({ projectRoot: codeMode?.rootDir })
|
|
8860
9758
|
);
|
|
8861
9759
|
const hookCwd = codeMode?.rootDir ?? process.cwd();
|
|
8862
|
-
const
|
|
9760
|
+
const editHistory = useRef2([]);
|
|
9761
|
+
const nextHistoryId = useRef2(1);
|
|
9762
|
+
const currentTurnEntry = useRef2(null);
|
|
8863
9763
|
const pendingEdits = useRef2([]);
|
|
8864
|
-
const [
|
|
8865
|
-
const
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
const [
|
|
9764
|
+
const [pendingCount, setPendingCount] = useState6(0);
|
|
9765
|
+
const syncPendingCount = useCallback(() => {
|
|
9766
|
+
setPendingCount(pendingEdits.current.length);
|
|
9767
|
+
}, []);
|
|
9768
|
+
const [editMode, setEditMode] = useState6(() => codeMode ? loadEditMode() : "review");
|
|
9769
|
+
const editModeRef = useRef2(editMode);
|
|
9770
|
+
useEffect2(() => {
|
|
9771
|
+
editModeRef.current = editMode;
|
|
9772
|
+
if (codeMode) saveEditMode(editMode);
|
|
9773
|
+
}, [editMode, codeMode]);
|
|
9774
|
+
const [undoBanner, setUndoBanner] = useState6(null);
|
|
9775
|
+
const undoTimeoutRef = useRef2(null);
|
|
9776
|
+
const [pendingEditReview, setPendingEditReview] = useState6(null);
|
|
9777
|
+
const editReviewResolveRef = useRef2(null);
|
|
9778
|
+
const turnEditPolicyRef = useRef2("ask");
|
|
9779
|
+
const [modeFlash, setModeFlash] = useState6(false);
|
|
9780
|
+
const modeFlashTimeoutRef = useRef2(null);
|
|
9781
|
+
const prevEditModeRef = useRef2(editMode);
|
|
9782
|
+
useEffect2(() => {
|
|
9783
|
+
if (prevEditModeRef.current === editMode) return;
|
|
9784
|
+
prevEditModeRef.current = editMode;
|
|
9785
|
+
setModeFlash(true);
|
|
9786
|
+
if (modeFlashTimeoutRef.current) clearTimeout(modeFlashTimeoutRef.current);
|
|
9787
|
+
modeFlashTimeoutRef.current = setTimeout(() => {
|
|
9788
|
+
setModeFlash(false);
|
|
9789
|
+
modeFlashTimeoutRef.current = null;
|
|
9790
|
+
}, 1200);
|
|
9791
|
+
}, [editMode]);
|
|
9792
|
+
const [pendingShell, setPendingShell] = useState6(null);
|
|
9793
|
+
const [pendingPlan, setPendingPlan] = useState6(null);
|
|
9794
|
+
const [stagedInput, setStagedInput] = useState6(null);
|
|
9795
|
+
const [planMode, setPlanMode] = useState6(false);
|
|
9796
|
+
const [queuedSubmit, setQueuedSubmit] = useState6(null);
|
|
8869
9797
|
const promptHistory = useRef2([]);
|
|
8870
9798
|
const historyCursor = useRef2(-1);
|
|
8871
9799
|
const assistantIterCounter = useRef2(0);
|
|
8872
9800
|
const toolHistoryRef = useRef2([]);
|
|
8873
|
-
const [slashSelected, setSlashSelected] =
|
|
8874
|
-
const [summary, setSummary] =
|
|
9801
|
+
const [slashSelected, setSlashSelected] = useState6(0);
|
|
9802
|
+
const [summary, setSummary] = useState6({
|
|
8875
9803
|
turns: 0,
|
|
8876
9804
|
totalCostUsd: 0,
|
|
8877
9805
|
totalInputCostUsd: 0,
|
|
@@ -8895,7 +9823,7 @@ function App({
|
|
|
8895
9823
|
transcriptRef.current?.end();
|
|
8896
9824
|
};
|
|
8897
9825
|
}, []);
|
|
8898
|
-
const slashMatches =
|
|
9826
|
+
const slashMatches = useMemo2(() => {
|
|
8899
9827
|
if (!input.startsWith("/") || input.includes(" ")) return null;
|
|
8900
9828
|
return suggestSlashCommands(input.slice(1), !!codeMode);
|
|
8901
9829
|
}, [input, codeMode]);
|
|
@@ -8906,8 +9834,8 @@ function App({
|
|
|
8906
9834
|
return prev;
|
|
8907
9835
|
});
|
|
8908
9836
|
}, [slashMatches]);
|
|
8909
|
-
const [atSelected, setAtSelected] =
|
|
8910
|
-
const atFiles =
|
|
9837
|
+
const [atSelected, setAtSelected] = useState6(0);
|
|
9838
|
+
const atFiles = useMemo2(() => {
|
|
8911
9839
|
if (!codeMode?.rootDir) return [];
|
|
8912
9840
|
try {
|
|
8913
9841
|
return listFilesWithStatsSync(codeMode.rootDir, { maxResults: 500 });
|
|
@@ -8923,12 +9851,12 @@ function App({
|
|
|
8923
9851
|
list.unshift(p);
|
|
8924
9852
|
if (list.length > 20) list.length = 20;
|
|
8925
9853
|
}, []);
|
|
8926
|
-
const atPicker =
|
|
9854
|
+
const atPicker = useMemo2(() => {
|
|
8927
9855
|
if (!codeMode?.rootDir) return null;
|
|
8928
9856
|
if (slashMatches !== null) return null;
|
|
8929
9857
|
return detectAtPicker(input);
|
|
8930
9858
|
}, [codeMode?.rootDir, input, slashMatches]);
|
|
8931
|
-
const atMatches =
|
|
9859
|
+
const atMatches = useMemo2(() => {
|
|
8932
9860
|
if (!atPicker) return null;
|
|
8933
9861
|
return rankPickerCandidates(atFiles, atPicker.query, {
|
|
8934
9862
|
limit: 40,
|
|
@@ -8950,13 +9878,13 @@ function App({
|
|
|
8950
9878
|
},
|
|
8951
9879
|
[atPicker, input]
|
|
8952
9880
|
);
|
|
8953
|
-
const [slashArgSelected, setSlashArgSelected] =
|
|
8954
|
-
const slashArgContext =
|
|
9881
|
+
const [slashArgSelected, setSlashArgSelected] = useState6(0);
|
|
9882
|
+
const slashArgContext = useMemo2(() => {
|
|
8955
9883
|
if (!input.startsWith("/")) return null;
|
|
8956
9884
|
if (slashMatches !== null) return null;
|
|
8957
9885
|
return detectSlashArgContext(input, !!codeMode);
|
|
8958
9886
|
}, [input, slashMatches, codeMode]);
|
|
8959
|
-
const slashArgMatches =
|
|
9887
|
+
const slashArgMatches = useMemo2(() => {
|
|
8960
9888
|
if (!slashArgContext || slashArgContext.kind !== "picker") return null;
|
|
8961
9889
|
const completer = slashArgContext.spec.argCompleter;
|
|
8962
9890
|
const partial = slashArgContext.partial;
|
|
@@ -9013,7 +9941,7 @@ function App({
|
|
|
9013
9941
|
);
|
|
9014
9942
|
const loopRef = useRef2(null);
|
|
9015
9943
|
const subagentSinkRef = useRef2({ current: null });
|
|
9016
|
-
const loop =
|
|
9944
|
+
const loop = useMemo2(() => {
|
|
9017
9945
|
if (loopRef.current) return loopRef.current;
|
|
9018
9946
|
const client = new DeepSeekClient();
|
|
9019
9947
|
if (tools && !tools.has("run_skill")) {
|
|
@@ -9052,7 +9980,11 @@ function App({
|
|
|
9052
9980
|
branch,
|
|
9053
9981
|
session,
|
|
9054
9982
|
hooks: hookList,
|
|
9055
|
-
hookCwd
|
|
9983
|
+
hookCwd,
|
|
9984
|
+
// Restore the user's last-chosen effort cap. Without this a
|
|
9985
|
+
// `/effort high` silently reverted to `max` on relaunch — the
|
|
9986
|
+
// loop's constructor default wins over persisted state.
|
|
9987
|
+
reasoningEffort: loadReasoningEffort()
|
|
9056
9988
|
});
|
|
9057
9989
|
loopRef.current = l;
|
|
9058
9990
|
return l;
|
|
@@ -9192,6 +10124,7 @@ function App({
|
|
|
9192
10124
|
const restored = loadPendingEdits(session);
|
|
9193
10125
|
if (restored && restored.length > 0) {
|
|
9194
10126
|
pendingEdits.current = restored;
|
|
10127
|
+
syncPendingCount();
|
|
9195
10128
|
setHistorical((prev) => [
|
|
9196
10129
|
...prev,
|
|
9197
10130
|
{
|
|
@@ -9202,14 +10135,54 @@ function App({
|
|
|
9202
10135
|
]);
|
|
9203
10136
|
}
|
|
9204
10137
|
}
|
|
9205
|
-
|
|
9206
|
-
|
|
10138
|
+
if (codeMode && !editModeHintShown()) {
|
|
10139
|
+
setHistorical((prev) => [
|
|
10140
|
+
...prev,
|
|
10141
|
+
{
|
|
10142
|
+
id: `sys-edittip-${Date.now()}`,
|
|
10143
|
+
role: "info",
|
|
10144
|
+
text: "\u25B8 TIP: edit-gate keybindings\n y / n accept or drop pending edits\n Shift+Tab switch review \u2194 AUTO (persisted; AUTO applies instantly)\n u undo the last auto-applied batch (within the 5s banner)\n Current mode is shown in the bottom status bar. Run /keys anytime for the full list.\n (This tip shows once \u2014 suppressed after.)"
|
|
10145
|
+
}
|
|
10146
|
+
]);
|
|
10147
|
+
markEditModeHintShown();
|
|
10148
|
+
}
|
|
10149
|
+
}, [session, loop, codeMode, syncPendingCount]);
|
|
10150
|
+
useInput5((chKey, key) => {
|
|
9207
10151
|
if (key.escape && busy) {
|
|
9208
10152
|
if (abortedThisTurn.current) return;
|
|
9209
10153
|
abortedThisTurn.current = true;
|
|
10154
|
+
const resolve8 = editReviewResolveRef.current;
|
|
10155
|
+
if (resolve8) {
|
|
10156
|
+
editReviewResolveRef.current = null;
|
|
10157
|
+
setPendingEditReview(null);
|
|
10158
|
+
resolve8("reject");
|
|
10159
|
+
}
|
|
9210
10160
|
loop.abort();
|
|
9211
10161
|
return;
|
|
9212
10162
|
}
|
|
10163
|
+
if (codeMode && key.shift && key.tab && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview) {
|
|
10164
|
+
setEditMode((m) => {
|
|
10165
|
+
const next = m === "auto" ? "review" : "auto";
|
|
10166
|
+
setHistorical((prev) => [
|
|
10167
|
+
...prev,
|
|
10168
|
+
{
|
|
10169
|
+
id: `mode-${Date.now()}`,
|
|
10170
|
+
role: "info",
|
|
10171
|
+
text: next === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo" : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)"
|
|
10172
|
+
}
|
|
10173
|
+
]);
|
|
10174
|
+
return next;
|
|
10175
|
+
});
|
|
10176
|
+
return;
|
|
10177
|
+
}
|
|
10178
|
+
if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && // Fire when EITHER the banner is up OR there's any non-undone
|
|
10179
|
+
// history entry — the keybind is useful long after the 5-second
|
|
10180
|
+
// banner expires, which users rightly want.
|
|
10181
|
+
(undoBanner || editHistory.current.some((e) => !isEntryFullyUndone(e)))) {
|
|
10182
|
+
const out = codeUndo([]);
|
|
10183
|
+
setHistorical((prev) => [...prev, { id: `undo-${Date.now()}`, role: "info", text: out }]);
|
|
10184
|
+
return;
|
|
10185
|
+
}
|
|
9213
10186
|
if (busy) return;
|
|
9214
10187
|
if (pendingShell) return;
|
|
9215
10188
|
if (atMatches && atMatches.length > 0) {
|
|
@@ -9275,16 +10248,269 @@ function App({
|
|
|
9275
10248
|
}
|
|
9276
10249
|
}
|
|
9277
10250
|
});
|
|
9278
|
-
const
|
|
10251
|
+
const recordEdit = useCallback(
|
|
10252
|
+
(source, blocks, results, snaps) => {
|
|
10253
|
+
if (snaps.length === 0) return;
|
|
10254
|
+
let entry = currentTurnEntry.current;
|
|
10255
|
+
if (!entry) {
|
|
10256
|
+
entry = {
|
|
10257
|
+
id: nextHistoryId.current++,
|
|
10258
|
+
at: Date.now(),
|
|
10259
|
+
source,
|
|
10260
|
+
blocks: [],
|
|
10261
|
+
results: [],
|
|
10262
|
+
snapshots: [],
|
|
10263
|
+
undoneFiles: /* @__PURE__ */ new Set()
|
|
10264
|
+
};
|
|
10265
|
+
currentTurnEntry.current = entry;
|
|
10266
|
+
editHistory.current.push(entry);
|
|
10267
|
+
}
|
|
10268
|
+
entry.blocks.push(...blocks);
|
|
10269
|
+
entry.results.push(...results);
|
|
10270
|
+
const seen = new Set(entry.snapshots.map((s) => s.path));
|
|
10271
|
+
for (const s of snaps) {
|
|
10272
|
+
if (!seen.has(s.path)) entry.snapshots.push(s);
|
|
10273
|
+
}
|
|
10274
|
+
},
|
|
10275
|
+
[]
|
|
10276
|
+
);
|
|
10277
|
+
const armUndoBanner = useCallback((results) => {
|
|
10278
|
+
setUndoBanner({ results, expiresAt: Date.now() + 5e3 });
|
|
10279
|
+
if (undoTimeoutRef.current) clearTimeout(undoTimeoutRef.current);
|
|
10280
|
+
undoTimeoutRef.current = setTimeout(() => {
|
|
10281
|
+
setUndoBanner(null);
|
|
10282
|
+
undoTimeoutRef.current = null;
|
|
10283
|
+
}, 5e3);
|
|
10284
|
+
}, []);
|
|
10285
|
+
const codeUndo = useCallback(
|
|
10286
|
+
(args = []) => {
|
|
10287
|
+
if (!codeMode) return "not in code mode";
|
|
10288
|
+
const root = codeMode.rootDir;
|
|
10289
|
+
const revert = (entry2, paths) => {
|
|
10290
|
+
const subset = entry2.snapshots.filter((s) => paths.includes(s.path));
|
|
10291
|
+
if (subset.length === 0) {
|
|
10292
|
+
return `batch #${entry2.id}: nothing to undo (already restored or path not in batch)`;
|
|
10293
|
+
}
|
|
10294
|
+
const results = restoreSnapshots(subset, root);
|
|
10295
|
+
for (const s of subset) entry2.undoneFiles.add(s.path);
|
|
10296
|
+
if (currentTurnEntry.current === entry2 && isEntryFullyUndone(entry2)) {
|
|
10297
|
+
currentTurnEntry.current = null;
|
|
10298
|
+
}
|
|
10299
|
+
if (undoTimeoutRef.current) {
|
|
10300
|
+
clearTimeout(undoTimeoutRef.current);
|
|
10301
|
+
undoTimeoutRef.current = null;
|
|
10302
|
+
}
|
|
10303
|
+
setUndoBanner(null);
|
|
10304
|
+
const when = new Date(entry2.at).toISOString().replace("T", " ").slice(11, 19);
|
|
10305
|
+
const scope = subset.length === 1 ? subset[0].path : `${subset.length} file(s)`;
|
|
10306
|
+
const header2 = `\u25B8 undo: reverted ${scope} from batch #${entry2.id} (${when})`;
|
|
10307
|
+
return [header2, ...formatUndoRows(results)].join("\n");
|
|
10308
|
+
};
|
|
10309
|
+
const idArg = args[0];
|
|
10310
|
+
const pathArg = args[1];
|
|
10311
|
+
if (!idArg) {
|
|
10312
|
+
for (let i = editHistory.current.length - 1; i >= 0; i--) {
|
|
10313
|
+
const e = editHistory.current[i];
|
|
10314
|
+
if (isEntryFullyUndone(e)) continue;
|
|
10315
|
+
const remaining = e.snapshots.map((s) => s.path).filter((p) => !e.undoneFiles.has(p));
|
|
10316
|
+
return revert(e, remaining);
|
|
10317
|
+
}
|
|
10318
|
+
return "nothing to undo \u2014 every batch in the session history is already undone";
|
|
10319
|
+
}
|
|
10320
|
+
const id = Number.parseInt(idArg, 10);
|
|
10321
|
+
if (!Number.isFinite(id)) {
|
|
10322
|
+
return "usage: /undo [id] [path] (omit id for newest; id from /history; path from /show <id>)";
|
|
10323
|
+
}
|
|
10324
|
+
const entry = editHistory.current.find((e) => e.id === id);
|
|
10325
|
+
if (!entry) return `no edit #${id} \u2014 run /history to see valid ids`;
|
|
10326
|
+
if (!pathArg) {
|
|
10327
|
+
const remaining = entry.snapshots.map((s) => s.path).filter((p) => !entry.undoneFiles.has(p));
|
|
10328
|
+
if (remaining.length === 0) return `batch #${id} is already fully undone`;
|
|
10329
|
+
return revert(entry, remaining);
|
|
10330
|
+
}
|
|
10331
|
+
const snap = entry.snapshots.find((s) => s.path === pathArg);
|
|
10332
|
+
if (!snap) {
|
|
10333
|
+
const files = [...new Set(entry.blocks.map((b) => b.path))];
|
|
10334
|
+
return `batch #${id} doesn't include "${pathArg}" \u2014 files in this batch: ${files.join(", ")}`;
|
|
10335
|
+
}
|
|
10336
|
+
if (entry.undoneFiles.has(pathArg)) {
|
|
10337
|
+
return `${pathArg} in batch #${id} is already undone`;
|
|
10338
|
+
}
|
|
10339
|
+
return revert(entry, [pathArg]);
|
|
10340
|
+
},
|
|
10341
|
+
[codeMode]
|
|
10342
|
+
);
|
|
10343
|
+
const codeHistory = useCallback(() => {
|
|
9279
10344
|
if (!codeMode) return "not in code mode";
|
|
9280
|
-
const
|
|
9281
|
-
if (
|
|
9282
|
-
|
|
10345
|
+
const entries = editHistory.current;
|
|
10346
|
+
if (entries.length === 0) return "no edits recorded this session yet";
|
|
10347
|
+
const lines = ["Edit history (oldest first):"];
|
|
10348
|
+
for (const e of entries) {
|
|
10349
|
+
const when = new Date(e.at).toISOString().replace("T", " ").slice(11, 19);
|
|
10350
|
+
const files = new Set(e.blocks.map((b) => b.path));
|
|
10351
|
+
const fileList = [...files].join(", ");
|
|
10352
|
+
const fileSummary = fileList.length > 60 ? `${fileList.slice(0, 60)}\u2026` : fileList;
|
|
10353
|
+
const status = entryStatus(e);
|
|
10354
|
+
const statusText = status === "applied" ? "applied" : status === "PARTIAL" ? "PARTIAL" : "UNDONE ";
|
|
10355
|
+
lines.push(
|
|
10356
|
+
` #${String(e.id).padStart(3)} ${when} ${statusText} ${e.source.padEnd(12)} ${files.size} file \xB7 ${e.blocks.length} block ${fileSummary}`
|
|
10357
|
+
);
|
|
9283
10358
|
}
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
10359
|
+
lines.push("");
|
|
10360
|
+
lines.push(
|
|
10361
|
+
"/show <id> \u2192 per-file summary \xB7 /show <id> <path> \u2192 full diff of one file"
|
|
10362
|
+
);
|
|
10363
|
+
lines.push(
|
|
10364
|
+
"/undo \u2192 newest non-undone \xB7 /undo <id> [path] \u2192 target a specific batch or file"
|
|
10365
|
+
);
|
|
10366
|
+
return lines.join("\n");
|
|
9287
10367
|
}, [codeMode]);
|
|
10368
|
+
const codeShowEdit = useCallback(
|
|
10369
|
+
(args = []) => {
|
|
10370
|
+
if (!codeMode) return "not in code mode";
|
|
10371
|
+
const entries = editHistory.current;
|
|
10372
|
+
if (entries.length === 0) return "no edits recorded this session \u2014 /history is empty";
|
|
10373
|
+
const idArg = args[0];
|
|
10374
|
+
const pathArg = args[1];
|
|
10375
|
+
let entry;
|
|
10376
|
+
if (!idArg) {
|
|
10377
|
+
entry = [...entries].reverse().find((e) => !isEntryFullyUndone(e)) ?? entries[entries.length - 1];
|
|
10378
|
+
} else {
|
|
10379
|
+
const id = Number.parseInt(idArg, 10);
|
|
10380
|
+
if (!Number.isFinite(id)) {
|
|
10381
|
+
return "usage: /show [id] [path] (omit id for newest; path from the per-file summary)";
|
|
10382
|
+
}
|
|
10383
|
+
entry = entries.find((e) => e.id === id);
|
|
10384
|
+
if (!entry) return `no edit #${id} \u2014 run /history to see valid ids`;
|
|
10385
|
+
}
|
|
10386
|
+
if (!entry) return "unexpected: history lookup failed";
|
|
10387
|
+
if (pathArg) {
|
|
10388
|
+
const fileBlocks = entry.blocks.filter((b) => b.path === pathArg);
|
|
10389
|
+
if (fileBlocks.length === 0) {
|
|
10390
|
+
const files2 = [...new Set(entry.blocks.map((b) => b.path))];
|
|
10391
|
+
return `batch #${entry.id} doesn't include "${pathArg}" \u2014 files in this batch: ${files2.join(", ")}`;
|
|
10392
|
+
}
|
|
10393
|
+
const when2 = new Date(entry.at).toISOString().replace("T", " ").slice(11, 19);
|
|
10394
|
+
const state = entry.undoneFiles.has(pathArg) ? "UNDONE" : "applied";
|
|
10395
|
+
const header3 = `\u25B8 edit #${entry.id} \xB7 ${when2} \xB7 ${pathArg} \xB7 ${state} \xB7 ${fileBlocks.length} block(s)`;
|
|
10396
|
+
const diff = formatAllBlockDiffs(fileBlocks, { maxLines: 60, contextLines: 2 });
|
|
10397
|
+
const footer = entry.undoneFiles.has(pathArg) ? "(already reverted \u2014 /history shows the batch-level status)" : `/undo ${entry.id} ${pathArg} \u2192 revert just this file`;
|
|
10398
|
+
return [header3, ...diff, "", footer].join("\n");
|
|
10399
|
+
}
|
|
10400
|
+
const when = new Date(entry.at).toISOString().replace("T", " ").slice(11, 19);
|
|
10401
|
+
const files = [...new Set(entry.blocks.map((b) => b.path))];
|
|
10402
|
+
const status = entryStatus(entry);
|
|
10403
|
+
const header2 = `\u25B8 edit #${entry.id} \xB7 ${when} \xB7 ${entry.source} \xB7 ${status} \xB7 ${files.length} file(s)`;
|
|
10404
|
+
const countLines3 = (s) => s.length === 0 ? 0 : (s.match(/\n/g)?.length ?? 0) + 1;
|
|
10405
|
+
const fileLines = files.map((path) => {
|
|
10406
|
+
const fileBlocks = entry.blocks.filter((b) => b.path === path);
|
|
10407
|
+
let removed = 0;
|
|
10408
|
+
let added = 0;
|
|
10409
|
+
for (const b of fileBlocks) {
|
|
10410
|
+
removed += countLines3(b.search);
|
|
10411
|
+
added += countLines3(b.replace);
|
|
10412
|
+
}
|
|
10413
|
+
const state = entry.undoneFiles.has(path) ? "UNDONE" : "applied";
|
|
10414
|
+
return ` ${state.padEnd(7)} -${String(removed).padStart(3)}/+${String(added).padStart(3)} ${path} (${fileBlocks.length} block${fileBlocks.length === 1 ? "" : "s"})`;
|
|
10415
|
+
});
|
|
10416
|
+
return [
|
|
10417
|
+
header2,
|
|
10418
|
+
...fileLines,
|
|
10419
|
+
"",
|
|
10420
|
+
`/show ${entry.id} <path> \u2192 full diff of one file`,
|
|
10421
|
+
`/undo ${entry.id} <path> \u2192 revert just that file \xB7 /undo ${entry.id} \u2192 revert whole batch`
|
|
10422
|
+
].join("\n");
|
|
10423
|
+
},
|
|
10424
|
+
[codeMode]
|
|
10425
|
+
);
|
|
10426
|
+
useEffect2(() => {
|
|
10427
|
+
if (!tools || !codeMode) return;
|
|
10428
|
+
tools.setToolInterceptor(async (name, args) => {
|
|
10429
|
+
if (name !== "edit_file" && name !== "write_file") return null;
|
|
10430
|
+
const rawPath = typeof args.path === "string" ? args.path : "";
|
|
10431
|
+
if (!rawPath) return null;
|
|
10432
|
+
let relPath = rawPath;
|
|
10433
|
+
while (relPath.startsWith("/") || relPath.startsWith("\\")) {
|
|
10434
|
+
relPath = relPath.slice(1);
|
|
10435
|
+
}
|
|
10436
|
+
if (!relPath) return null;
|
|
10437
|
+
let block;
|
|
10438
|
+
if (name === "edit_file") {
|
|
10439
|
+
const search = typeof args.search === "string" ? args.search : "";
|
|
10440
|
+
const replace = typeof args.replace === "string" ? args.replace : "";
|
|
10441
|
+
if (!search) return null;
|
|
10442
|
+
block = { path: relPath, search, replace, offset: 0 };
|
|
10443
|
+
} else {
|
|
10444
|
+
const content = typeof args.content === "string" ? args.content : "";
|
|
10445
|
+
block = toWholeFileEditBlock(relPath, content, codeMode.rootDir);
|
|
10446
|
+
}
|
|
10447
|
+
const applyNow = () => {
|
|
10448
|
+
const snaps = snapshotBeforeEdits([block], codeMode.rootDir);
|
|
10449
|
+
const results = applyEditBlocks([block], codeMode.rootDir);
|
|
10450
|
+
const good = results.some((r) => r.status === "applied" || r.status === "created");
|
|
10451
|
+
if (good) {
|
|
10452
|
+
recordEdit("auto", [block], results, snaps);
|
|
10453
|
+
armUndoBanner(results);
|
|
10454
|
+
}
|
|
10455
|
+
setHistorical((prev) => [
|
|
10456
|
+
...prev,
|
|
10457
|
+
{
|
|
10458
|
+
id: `ae-${Date.now()}-${Math.random()}`,
|
|
10459
|
+
role: "info",
|
|
10460
|
+
text: formatEditResults(results)
|
|
10461
|
+
}
|
|
10462
|
+
]);
|
|
10463
|
+
return formatEditResults(results);
|
|
10464
|
+
};
|
|
10465
|
+
if (editModeRef.current === "auto") return applyNow();
|
|
10466
|
+
if (turnEditPolicyRef.current === "apply-all") return applyNow();
|
|
10467
|
+
const choice = await new Promise((resolveChoice) => {
|
|
10468
|
+
editReviewResolveRef.current = resolveChoice;
|
|
10469
|
+
setPendingEditReview(block);
|
|
10470
|
+
});
|
|
10471
|
+
editReviewResolveRef.current = null;
|
|
10472
|
+
setPendingEditReview(null);
|
|
10473
|
+
if (choice === "reject") {
|
|
10474
|
+
setHistorical((prev) => [
|
|
10475
|
+
...prev,
|
|
10476
|
+
{
|
|
10477
|
+
id: `er-${Date.now()}-${Math.random()}`,
|
|
10478
|
+
role: "info",
|
|
10479
|
+
text: `\u25B8 rejected edit to ${block.path}`
|
|
10480
|
+
}
|
|
10481
|
+
]);
|
|
10482
|
+
return `User rejected this edit to ${block.path}. Don't retry the same SEARCH/REPLACE \u2014 either try a different approach or ask the user what they want instead.`;
|
|
10483
|
+
}
|
|
10484
|
+
if (choice === "apply-rest-of-turn") {
|
|
10485
|
+
turnEditPolicyRef.current = "apply-all";
|
|
10486
|
+
setHistorical((prev) => [
|
|
10487
|
+
...prev,
|
|
10488
|
+
{
|
|
10489
|
+
id: `er-${Date.now()}-${Math.random()}`,
|
|
10490
|
+
role: "info",
|
|
10491
|
+
text: "\u25B8 auto-approving remaining edits for this turn"
|
|
10492
|
+
}
|
|
10493
|
+
]);
|
|
10494
|
+
return applyNow();
|
|
10495
|
+
}
|
|
10496
|
+
if (choice === "flip-to-auto") {
|
|
10497
|
+
setEditMode("auto");
|
|
10498
|
+
setHistorical((prev) => [
|
|
10499
|
+
...prev,
|
|
10500
|
+
{
|
|
10501
|
+
id: `er-${Date.now()}-${Math.random()}`,
|
|
10502
|
+
role: "info",
|
|
10503
|
+
text: "\u25B8 flipped to AUTO mode for the rest of the session (persisted)"
|
|
10504
|
+
}
|
|
10505
|
+
]);
|
|
10506
|
+
return applyNow();
|
|
10507
|
+
}
|
|
10508
|
+
return applyNow();
|
|
10509
|
+
});
|
|
10510
|
+
return () => {
|
|
10511
|
+
tools.setToolInterceptor(null);
|
|
10512
|
+
};
|
|
10513
|
+
}, [tools, codeMode, session, recordEdit, armUndoBanner, syncPendingCount, setEditMode]);
|
|
9288
10514
|
const codeApply = useCallback(() => {
|
|
9289
10515
|
if (!codeMode) return "not in code mode";
|
|
9290
10516
|
const blocks = pendingEdits.current;
|
|
@@ -9294,18 +10520,20 @@ function App({
|
|
|
9294
10520
|
const snaps = snapshotBeforeEdits(blocks, codeMode.rootDir);
|
|
9295
10521
|
const results = applyEditBlocks(blocks, codeMode.rootDir);
|
|
9296
10522
|
const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
|
|
9297
|
-
if (anyApplied)
|
|
10523
|
+
if (anyApplied) recordEdit("review-apply", blocks, results, snaps);
|
|
9298
10524
|
pendingEdits.current = [];
|
|
9299
10525
|
clearPendingEdits(session ?? null);
|
|
10526
|
+
syncPendingCount();
|
|
9300
10527
|
return formatEditResults(results);
|
|
9301
|
-
}, [codeMode, session]);
|
|
10528
|
+
}, [codeMode, session, syncPendingCount, recordEdit]);
|
|
9302
10529
|
const codeDiscard = useCallback(() => {
|
|
9303
10530
|
const count = pendingEdits.current.length;
|
|
9304
10531
|
if (count === 0) return "nothing pending to discard.";
|
|
9305
10532
|
pendingEdits.current = [];
|
|
9306
10533
|
clearPendingEdits(session ?? null);
|
|
10534
|
+
syncPendingCount();
|
|
9307
10535
|
return `\u25B8 discarded ${count} pending edit block(s). Nothing was written to disk.`;
|
|
9308
|
-
}, [session]);
|
|
10536
|
+
}, [session, syncPendingCount]);
|
|
9309
10537
|
const prefixHash = loop.prefix.fingerprint;
|
|
9310
10538
|
const writeTranscript = useCallback(
|
|
9311
10539
|
(ev) => {
|
|
@@ -9423,6 +10651,8 @@ function App({
|
|
|
9423
10651
|
codeUndo: codeMode ? codeUndo : void 0,
|
|
9424
10652
|
codeApply: codeMode ? codeApply : void 0,
|
|
9425
10653
|
codeDiscard: codeMode ? codeDiscard : void 0,
|
|
10654
|
+
codeHistory: codeMode ? codeHistory : void 0,
|
|
10655
|
+
codeShowEdit: codeMode ? codeShowEdit : void 0,
|
|
9426
10656
|
codeRoot: codeMode?.rootDir,
|
|
9427
10657
|
pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
|
|
9428
10658
|
toolHistory: () => toolHistoryRef.current,
|
|
@@ -9430,6 +10660,13 @@ function App({
|
|
|
9430
10660
|
planMode,
|
|
9431
10661
|
setPlanMode: codeMode ? togglePlanMode : void 0,
|
|
9432
10662
|
clearPendingPlan: codeMode ? clearPendingPlan : void 0,
|
|
10663
|
+
editMode: codeMode ? editMode : void 0,
|
|
10664
|
+
setEditMode: codeMode ? setEditMode : void 0,
|
|
10665
|
+
jobs: codeMode?.jobs,
|
|
10666
|
+
postInfo: (text2) => setHistorical((prev) => [
|
|
10667
|
+
...prev,
|
|
10668
|
+
{ id: `sys-late-${Date.now()}-${Math.random()}`, role: "info", text: text2 }
|
|
10669
|
+
]),
|
|
9433
10670
|
reloadHooks: () => {
|
|
9434
10671
|
const fresh = loadHooks({ projectRoot: codeMode?.rootDir });
|
|
9435
10672
|
setHookList(fresh);
|
|
@@ -9466,6 +10703,7 @@ function App({
|
|
|
9466
10703
|
if (codeMode) {
|
|
9467
10704
|
pendingEdits.current = [];
|
|
9468
10705
|
clearPendingEdits(session ?? null);
|
|
10706
|
+
syncPendingCount();
|
|
9469
10707
|
}
|
|
9470
10708
|
return;
|
|
9471
10709
|
}
|
|
@@ -9474,6 +10712,7 @@ function App({
|
|
|
9474
10712
|
if (codeMode) {
|
|
9475
10713
|
pendingEdits.current = [];
|
|
9476
10714
|
clearPendingEdits(session ?? null);
|
|
10715
|
+
syncPendingCount();
|
|
9477
10716
|
}
|
|
9478
10717
|
return;
|
|
9479
10718
|
}
|
|
@@ -9536,6 +10775,10 @@ function App({
|
|
|
9536
10775
|
setStreaming({ id: assistantId, role: "assistant", text: "", streaming: true });
|
|
9537
10776
|
setBusy(true);
|
|
9538
10777
|
abortedThisTurn.current = false;
|
|
10778
|
+
if (codeMode) {
|
|
10779
|
+
currentTurnEntry.current = null;
|
|
10780
|
+
}
|
|
10781
|
+
turnEditPolicyRef.current = "ask";
|
|
9539
10782
|
const flush = () => {
|
|
9540
10783
|
if (!contentBuf.current && !reasoningBuf.current && !toolCallBuildBuf.current) return;
|
|
9541
10784
|
streamRef.text += contentBuf.current;
|
|
@@ -9649,16 +10892,37 @@ function App({
|
|
|
9649
10892
|
if (codeMode && finalText && !ev.forcedSummary) {
|
|
9650
10893
|
const blocks = parseEditBlocks(finalText);
|
|
9651
10894
|
if (blocks.length > 0) {
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
text
|
|
10895
|
+
if (editModeRef.current === "auto") {
|
|
10896
|
+
const snaps = snapshotBeforeEdits(blocks, codeMode.rootDir);
|
|
10897
|
+
const results = applyEditBlocks(blocks, codeMode.rootDir);
|
|
10898
|
+
const good = results.some(
|
|
10899
|
+
(r) => r.status === "applied" || r.status === "created"
|
|
10900
|
+
);
|
|
10901
|
+
if (good) {
|
|
10902
|
+
recordEdit("auto-text", blocks, results, snaps);
|
|
10903
|
+
armUndoBanner(results);
|
|
9660
10904
|
}
|
|
9661
|
-
|
|
10905
|
+
setHistorical((prev) => [
|
|
10906
|
+
...prev,
|
|
10907
|
+
{
|
|
10908
|
+
id: `applied-${Date.now()}`,
|
|
10909
|
+
role: "info",
|
|
10910
|
+
text: formatEditResults(results)
|
|
10911
|
+
}
|
|
10912
|
+
]);
|
|
10913
|
+
} else {
|
|
10914
|
+
pendingEdits.current = [...pendingEdits.current, ...blocks];
|
|
10915
|
+
savePendingEdits(session ?? null, pendingEdits.current);
|
|
10916
|
+
syncPendingCount();
|
|
10917
|
+
setHistorical((prev) => [
|
|
10918
|
+
...prev,
|
|
10919
|
+
{
|
|
10920
|
+
id: `pending-${Date.now()}`,
|
|
10921
|
+
role: "info",
|
|
10922
|
+
text: formatPendingPreview(pendingEdits.current)
|
|
10923
|
+
}
|
|
10924
|
+
]);
|
|
10925
|
+
}
|
|
9662
10926
|
}
|
|
9663
10927
|
}
|
|
9664
10928
|
} else if (ev.role === "tool_start") {
|
|
@@ -9694,11 +10958,14 @@ function App({
|
|
|
9694
10958
|
toolName: ev.toolName
|
|
9695
10959
|
}
|
|
9696
10960
|
]);
|
|
9697
|
-
if (codeMode && ev.toolName === "run_command" && ev.content.includes('"NeedsConfirmationError:') && ev.toolArgs) {
|
|
10961
|
+
if (codeMode && (ev.toolName === "run_command" || ev.toolName === "run_background") && ev.content.includes('"NeedsConfirmationError:') && ev.toolArgs) {
|
|
9698
10962
|
try {
|
|
9699
10963
|
const parsed = JSON.parse(ev.toolArgs);
|
|
9700
10964
|
if (typeof parsed.command === "string" && parsed.command.trim()) {
|
|
9701
|
-
setPendingShell(
|
|
10965
|
+
setPendingShell({
|
|
10966
|
+
command: parsed.command.trim(),
|
|
10967
|
+
kind: ev.toolName
|
|
10968
|
+
});
|
|
9702
10969
|
}
|
|
9703
10970
|
} catch {
|
|
9704
10971
|
}
|
|
@@ -9769,7 +11036,9 @@ function App({
|
|
|
9769
11036
|
clearPendingPlan,
|
|
9770
11037
|
codeApply,
|
|
9771
11038
|
codeDiscard,
|
|
11039
|
+
codeHistory,
|
|
9772
11040
|
codeMode,
|
|
11041
|
+
codeShowEdit,
|
|
9773
11042
|
codeUndo,
|
|
9774
11043
|
exit,
|
|
9775
11044
|
hookCwd,
|
|
@@ -9792,13 +11061,18 @@ function App({
|
|
|
9792
11061
|
slashArgSelected,
|
|
9793
11062
|
pickSlashArg,
|
|
9794
11063
|
togglePlanMode,
|
|
9795
|
-
writeTranscript
|
|
11064
|
+
writeTranscript,
|
|
11065
|
+
recordEdit,
|
|
11066
|
+
armUndoBanner,
|
|
11067
|
+
editMode,
|
|
11068
|
+
syncPendingCount
|
|
9796
11069
|
]
|
|
9797
11070
|
);
|
|
9798
11071
|
const handleShellConfirm = useCallback(
|
|
9799
11072
|
async (choice) => {
|
|
9800
|
-
const
|
|
9801
|
-
if (!
|
|
11073
|
+
const pending = pendingShell;
|
|
11074
|
+
if (!pending || !codeMode) return;
|
|
11075
|
+
const { command: cmd, kind } = pending;
|
|
9802
11076
|
setPendingShell(null);
|
|
9803
11077
|
let synthetic;
|
|
9804
11078
|
if (choice === "deny") {
|
|
@@ -9822,23 +11096,65 @@ function App({
|
|
|
9822
11096
|
}
|
|
9823
11097
|
setHistorical((prev) => [
|
|
9824
11098
|
...prev,
|
|
9825
|
-
{
|
|
11099
|
+
{
|
|
11100
|
+
id: `sh-run-${Date.now()}`,
|
|
11101
|
+
role: "info",
|
|
11102
|
+
text: kind === "run_background" ? `\u25B8 starting (background): ${cmd}` : `\u25B8 running: ${cmd}`
|
|
11103
|
+
}
|
|
9826
11104
|
]);
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
11105
|
+
if (kind === "run_background" && codeMode.jobs) {
|
|
11106
|
+
let startedOk = false;
|
|
11107
|
+
let jobId = null;
|
|
11108
|
+
let preview = "";
|
|
11109
|
+
try {
|
|
11110
|
+
const res = await codeMode.jobs.start(cmd, { cwd: codeMode.rootDir });
|
|
11111
|
+
startedOk = true;
|
|
11112
|
+
jobId = res.jobId;
|
|
11113
|
+
preview = res.preview;
|
|
11114
|
+
const header2 = res.stillRunning ? `[job ${res.jobId} started \xB7 pid ${res.pid ?? "?"} \xB7 ${res.readyMatched ? "READY signal matched" : "running"}]` : res.exitCode !== null ? `[job ${res.jobId} exited during startup \xB7 exit ${res.exitCode}]` : `[job ${res.jobId} failed to start]`;
|
|
11115
|
+
const body = preview ? `${header2}
|
|
11116
|
+
${preview}` : header2;
|
|
11117
|
+
setHistorical((prev) => [
|
|
11118
|
+
...prev,
|
|
11119
|
+
{ id: `sh-out-${Date.now()}`, role: "info", text: body }
|
|
11120
|
+
]);
|
|
11121
|
+
synthetic = `I approved the background spawn. ${header2}
|
|
11122
|
+
|
|
11123
|
+
Startup preview:
|
|
11124
|
+
|
|
11125
|
+
${preview || "(no output yet)"}
|
|
11126
|
+
|
|
11127
|
+
The process is still running \u2014 use job_output to read newer logs, stop_job to halt it.`;
|
|
11128
|
+
} catch (err) {
|
|
11129
|
+
const msg = `$ ${cmd}
|
|
11130
|
+
[failed to start] ${err.message}`;
|
|
11131
|
+
setHistorical((prev) => [
|
|
11132
|
+
...prev,
|
|
11133
|
+
{ id: `sh-out-${Date.now()}`, role: "info", text: msg }
|
|
11134
|
+
]);
|
|
11135
|
+
synthetic = `I approved the background spawn but it failed to start:
|
|
11136
|
+
|
|
11137
|
+
${msg}`;
|
|
11138
|
+
}
|
|
11139
|
+
void startedOk;
|
|
11140
|
+
void jobId;
|
|
11141
|
+
} else {
|
|
11142
|
+
let body;
|
|
11143
|
+
try {
|
|
11144
|
+
const res = await runCommand(cmd, { cwd: codeMode.rootDir });
|
|
11145
|
+
body = formatCommandResult(cmd, res);
|
|
11146
|
+
} catch (err) {
|
|
11147
|
+
body = `$ ${cmd}
|
|
9833
11148
|
[failed to spawn] ${err.message}`;
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
11149
|
+
}
|
|
11150
|
+
setHistorical((prev) => [
|
|
11151
|
+
...prev,
|
|
11152
|
+
{ id: `sh-out-${Date.now()}`, role: "info", text: body }
|
|
11153
|
+
]);
|
|
11154
|
+
synthetic = `I ran the command you requested. Output:
|
|
9840
11155
|
|
|
9841
11156
|
${body}`;
|
|
11157
|
+
}
|
|
9842
11158
|
}
|
|
9843
11159
|
if (busy) {
|
|
9844
11160
|
loop.abort();
|
|
@@ -9949,7 +11265,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
9949
11265
|
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
9950
11266
|
setStagedInput(null);
|
|
9951
11267
|
}, [stagedInput]);
|
|
9952
|
-
return /* @__PURE__ */
|
|
11268
|
+
return /* @__PURE__ */ React15.createElement(TickerProvider, { disabled: PLAIN_UI || !!pendingPlan || !!pendingShell || !!pendingEditReview }, /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React15.createElement(
|
|
9953
11269
|
StatsPanel,
|
|
9954
11270
|
{
|
|
9955
11271
|
summary,
|
|
@@ -9959,32 +11275,56 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
9959
11275
|
branchBudget: loop.branchOptions.budget,
|
|
9960
11276
|
reasoningEffort: loop.reasoningEffort,
|
|
9961
11277
|
planMode,
|
|
11278
|
+
editMode: codeMode ? editMode : void 0,
|
|
9962
11279
|
balance,
|
|
9963
11280
|
busy,
|
|
9964
11281
|
updateAvailable
|
|
9965
11282
|
}
|
|
9966
|
-
), /* @__PURE__ */
|
|
11283
|
+
), /* @__PURE__ */ React15.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React15.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && streaming ? /* @__PURE__ */ React15.createElement(Box14, { marginY: 1 }, /* @__PURE__ */ React15.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && ongoingTool ? /* @__PURE__ */ React15.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && subagentActivity ? /* @__PURE__ */ React15.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !ongoingTool && statusLine ? /* @__PURE__ */ React15.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview ? /* @__PURE__ */ React15.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React15.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React15.createElement(
|
|
9967
11284
|
PlanRefineInput,
|
|
9968
11285
|
{
|
|
9969
11286
|
mode: stagedInput.mode,
|
|
9970
11287
|
onSubmit: handleStagedInputSubmit,
|
|
9971
11288
|
onCancel: handleStagedInputCancel
|
|
9972
11289
|
}
|
|
9973
|
-
) : pendingPlan ? /* @__PURE__ */
|
|
11290
|
+
) : pendingPlan ? /* @__PURE__ */ React15.createElement(
|
|
9974
11291
|
PlanConfirm,
|
|
9975
11292
|
{
|
|
9976
11293
|
plan: pendingPlan,
|
|
9977
11294
|
onChoose: stableHandlePlanConfirm,
|
|
9978
11295
|
projectRoot: hookCwd
|
|
9979
11296
|
}
|
|
9980
|
-
) : pendingShell ? /* @__PURE__ */
|
|
11297
|
+
) : pendingShell ? /* @__PURE__ */ React15.createElement(
|
|
9981
11298
|
ShellConfirm,
|
|
9982
11299
|
{
|
|
9983
|
-
command: pendingShell,
|
|
9984
|
-
allowPrefix: derivePrefix(pendingShell),
|
|
11300
|
+
command: pendingShell.command,
|
|
11301
|
+
allowPrefix: derivePrefix(pendingShell.command),
|
|
11302
|
+
kind: pendingShell.kind,
|
|
9985
11303
|
onChoose: handleShellConfirm
|
|
9986
11304
|
}
|
|
9987
|
-
) :
|
|
11305
|
+
) : pendingEditReview ? /* @__PURE__ */ React15.createElement(
|
|
11306
|
+
EditConfirm,
|
|
11307
|
+
{
|
|
11308
|
+
block: pendingEditReview,
|
|
11309
|
+
onChoose: (choice) => {
|
|
11310
|
+
const resolve8 = editReviewResolveRef.current;
|
|
11311
|
+
if (resolve8) {
|
|
11312
|
+
editReviewResolveRef.current = null;
|
|
11313
|
+
resolve8(choice);
|
|
11314
|
+
}
|
|
11315
|
+
}
|
|
11316
|
+
}
|
|
11317
|
+
) : /* @__PURE__ */ React15.createElement(React15.Fragment, null, codeMode ? /* @__PURE__ */ React15.createElement(
|
|
11318
|
+
ModeStatusBar,
|
|
11319
|
+
{
|
|
11320
|
+
editMode,
|
|
11321
|
+
pendingCount,
|
|
11322
|
+
flash: modeFlash,
|
|
11323
|
+
planMode,
|
|
11324
|
+
undoArmed: !!undoBanner || editHistory.current.some((e) => !isEntryFullyUndone(e)),
|
|
11325
|
+
jobs: codeMode.jobs
|
|
11326
|
+
}
|
|
11327
|
+
) : null, /* @__PURE__ */ React15.createElement(
|
|
9988
11328
|
PromptInput,
|
|
9989
11329
|
{
|
|
9990
11330
|
value: input,
|
|
@@ -9992,14 +11332,14 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
9992
11332
|
onSubmit: handleSubmit,
|
|
9993
11333
|
disabled: busy
|
|
9994
11334
|
}
|
|
9995
|
-
), /* @__PURE__ */
|
|
11335
|
+
), /* @__PURE__ */ React15.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React15.createElement(
|
|
9996
11336
|
AtMentionSuggestions,
|
|
9997
11337
|
{
|
|
9998
11338
|
matches: atMatches,
|
|
9999
11339
|
selectedIndex: atSelected,
|
|
10000
11340
|
query: atPicker?.query ?? ""
|
|
10001
11341
|
}
|
|
10002
|
-
), slashArgContext ? /* @__PURE__ */
|
|
11342
|
+
), slashArgContext ? /* @__PURE__ */ React15.createElement(
|
|
10003
11343
|
SlashArgPicker,
|
|
10004
11344
|
{
|
|
10005
11345
|
matches: slashArgMatches,
|
|
@@ -10014,14 +11354,44 @@ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834"
|
|
|
10014
11354
|
function StatusRow({ text }) {
|
|
10015
11355
|
const tick = useTick();
|
|
10016
11356
|
const elapsed = useElapsedSeconds();
|
|
10017
|
-
return /* @__PURE__ */
|
|
11357
|
+
return /* @__PURE__ */ React15.createElement(Box14, { marginY: 1 }, /* @__PURE__ */ React15.createElement(Text14, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React15.createElement(Text14, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, ` ${elapsed}s`));
|
|
11358
|
+
}
|
|
11359
|
+
function ModeStatusBar({
|
|
11360
|
+
editMode,
|
|
11361
|
+
pendingCount,
|
|
11362
|
+
flash,
|
|
11363
|
+
planMode,
|
|
11364
|
+
undoArmed,
|
|
11365
|
+
jobs
|
|
11366
|
+
}) {
|
|
11367
|
+
useTick();
|
|
11368
|
+
const running = jobs?.runningCount() ?? 0;
|
|
11369
|
+
const jobsTag = running > 0 ? /* @__PURE__ */ React15.createElement(Text14, { color: "yellow", bold: true }, ` \xB7 \u23F5 ${running} job${running === 1 ? "" : "s"}`) : null;
|
|
11370
|
+
if (planMode) {
|
|
11371
|
+
return /* @__PURE__ */ React15.createElement(Box14, { paddingX: 1 }, /* @__PURE__ */ React15.createElement(Text14, { color: "red", bold: true, inverse: flash }, "\u25B8 PLAN"), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " writes gated \u2014 submit_plan + approval required \xB7 /plan off to leave"), jobsTag);
|
|
11372
|
+
}
|
|
11373
|
+
const label = editMode === "auto" ? "AUTO" : "review";
|
|
11374
|
+
const color = editMode === "auto" ? "magenta" : "cyan";
|
|
11375
|
+
const mid = editMode === "auto" ? undoArmed ? "edits apply immediately \xB7 u = undo last batch" : "edits apply immediately \xB7 5s undo window after each batch" : pendingCount > 0 ? `${pendingCount} queued \xB7 y = /apply \xB7 n = /discard` : "edits queue for review \xB7 y = /apply \xB7 n = /discard";
|
|
11376
|
+
const flip = editMode === "auto" ? "Shift+Tab \u2192 review" : "Shift+Tab \u2192 AUTO";
|
|
11377
|
+
return /* @__PURE__ */ React15.createElement(Box14, { paddingX: 1 }, /* @__PURE__ */ React15.createElement(Text14, { color, bold: true, inverse: flash }, `\u25B8 ${label}`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, ` ${mid} \xB7 ${flip}`), jobsTag);
|
|
11378
|
+
}
|
|
11379
|
+
function UndoBanner({
|
|
11380
|
+
banner
|
|
11381
|
+
}) {
|
|
11382
|
+
useTick();
|
|
11383
|
+
const remainingMs = Math.max(0, banner.expiresAt - Date.now());
|
|
11384
|
+
const remainingSec = Math.ceil(remainingMs / 1e3);
|
|
11385
|
+
const ok = banner.results.filter((r) => r.status === "applied" || r.status === "created").length;
|
|
11386
|
+
const total = banner.results.length;
|
|
11387
|
+
return /* @__PURE__ */ React15.createElement(Box14, { marginY: 1, borderStyle: "round", borderColor: "magenta", paddingX: 1 }, /* @__PURE__ */ React15.createElement(Text14, { color: "magenta", bold: true }, "\u2713 auto-applied "), /* @__PURE__ */ React15.createElement(Text14, { color: "magenta" }, `${ok}/${total} edit${total === 1 ? "" : "s"}`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " \xB7 press "), /* @__PURE__ */ React15.createElement(Text14, { color: "magenta", bold: true }, "u"), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " to undo ("), /* @__PURE__ */ React15.createElement(Text14, { color: remainingSec <= 1 ? "red" : "magenta" }, `${remainingSec}s`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, ")"));
|
|
10018
11388
|
}
|
|
10019
11389
|
function SubagentRow({
|
|
10020
11390
|
activity
|
|
10021
11391
|
}) {
|
|
10022
11392
|
const tick = useTick();
|
|
10023
11393
|
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
10024
|
-
return /* @__PURE__ */
|
|
11394
|
+
return /* @__PURE__ */ React15.createElement(Box14, { paddingLeft: 2 }, /* @__PURE__ */ React15.createElement(Text14, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React15.createElement(Text14, { color: "magenta" }, ` \u232C subagent: ${activity.task}`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
|
|
10025
11395
|
}
|
|
10026
11396
|
function OngoingToolRow({
|
|
10027
11397
|
tool,
|
|
@@ -10030,7 +11400,7 @@ function OngoingToolRow({
|
|
|
10030
11400
|
const tick = useTick();
|
|
10031
11401
|
const elapsed = useElapsedSeconds();
|
|
10032
11402
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
10033
|
-
return /* @__PURE__ */
|
|
11403
|
+
return /* @__PURE__ */ React15.createElement(Box14, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React15.createElement(Box14, null, /* @__PURE__ */ React15.createElement(Text14, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React15.createElement(Text14, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React15.createElement(Box14, { paddingLeft: 2 }, /* @__PURE__ */ React15.createElement(Text14, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React15.createElement(Box14, { paddingLeft: 2 }, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, summary)) : null);
|
|
10034
11404
|
}
|
|
10035
11405
|
function renderProgressLine(p) {
|
|
10036
11406
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -10100,13 +11470,12 @@ function formatPendingPreview(blocks) {
|
|
|
10100
11470
|
const diffLines = formatAllBlockDiffs(blocks);
|
|
10101
11471
|
return [header2, ...diffLines].join("\n");
|
|
10102
11472
|
}
|
|
10103
|
-
function
|
|
10104
|
-
|
|
11473
|
+
function formatUndoRows(results) {
|
|
11474
|
+
return results.map((r) => {
|
|
10105
11475
|
const mark = r.status === "applied" ? "\u2713" : "\u2717";
|
|
10106
11476
|
const detail = r.message ? ` (${r.message})` : "";
|
|
10107
11477
|
return ` ${mark} ${r.path}${detail}`;
|
|
10108
11478
|
});
|
|
10109
|
-
return [`\u25B8 undo: restored ${results.length} file(s) to pre-edit state`, ...lines].join("\n");
|
|
10110
11479
|
}
|
|
10111
11480
|
function describeRepair(repair) {
|
|
10112
11481
|
const parts = [];
|
|
@@ -10117,15 +11486,15 @@ function describeRepair(repair) {
|
|
|
10117
11486
|
}
|
|
10118
11487
|
|
|
10119
11488
|
// src/cli/ui/SessionPicker.tsx
|
|
10120
|
-
import { Box as
|
|
10121
|
-
import
|
|
11489
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
11490
|
+
import React16 from "react";
|
|
10122
11491
|
function SessionPicker({
|
|
10123
11492
|
sessionName,
|
|
10124
11493
|
messageCount,
|
|
10125
11494
|
lastActive,
|
|
10126
11495
|
onChoose
|
|
10127
11496
|
}) {
|
|
10128
|
-
return /* @__PURE__ */
|
|
11497
|
+
return /* @__PURE__ */ React16.createElement(Box15, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React16.createElement(Box15, { marginBottom: 1 }, /* @__PURE__ */ React16.createElement(Text15, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React16.createElement(
|
|
10129
11498
|
SingleSelect,
|
|
10130
11499
|
{
|
|
10131
11500
|
initialValue: "new",
|
|
@@ -10148,7 +11517,7 @@ function SessionPicker({
|
|
|
10148
11517
|
],
|
|
10149
11518
|
onSubmit: (v) => onChoose(v)
|
|
10150
11519
|
}
|
|
10151
|
-
), /* @__PURE__ */
|
|
11520
|
+
), /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
|
|
10152
11521
|
}
|
|
10153
11522
|
function relativeTime(date) {
|
|
10154
11523
|
const ms = Date.now() - date.getTime();
|
|
@@ -10164,12 +11533,12 @@ function relativeTime(date) {
|
|
|
10164
11533
|
}
|
|
10165
11534
|
|
|
10166
11535
|
// src/cli/ui/Setup.tsx
|
|
10167
|
-
import { Box as
|
|
11536
|
+
import { Box as Box16, Text as Text16, useApp as useApp2 } from "ink";
|
|
10168
11537
|
import TextInput from "ink-text-input";
|
|
10169
|
-
import
|
|
11538
|
+
import React17, { useState as useState7 } from "react";
|
|
10170
11539
|
function Setup({ onReady }) {
|
|
10171
|
-
const [value, setValue] =
|
|
10172
|
-
const [error, setError] =
|
|
11540
|
+
const [value, setValue] = useState7("");
|
|
11541
|
+
const [error, setError] = useState7(null);
|
|
10173
11542
|
const { exit } = useApp2();
|
|
10174
11543
|
const handleSubmit = (raw) => {
|
|
10175
11544
|
const trimmed = raw.trim();
|
|
@@ -10190,7 +11559,7 @@ function Setup({ onReady }) {
|
|
|
10190
11559
|
}
|
|
10191
11560
|
onReady(trimmed);
|
|
10192
11561
|
};
|
|
10193
|
-
return /* @__PURE__ */
|
|
11562
|
+
return /* @__PURE__ */ React17.createElement(Box16, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text16, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React17.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React17.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React17.createElement(
|
|
10194
11563
|
TextInput,
|
|
10195
11564
|
{
|
|
10196
11565
|
value,
|
|
@@ -10199,7 +11568,7 @@ function Setup({ onReady }) {
|
|
|
10199
11568
|
mask: "\u2022",
|
|
10200
11569
|
placeholder: "sk-..."
|
|
10201
11570
|
}
|
|
10202
|
-
)), error ? /* @__PURE__ */
|
|
11571
|
+
)), error ? /* @__PURE__ */ React17.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, { color: "red" }, error)) : value ? /* @__PURE__ */ React17.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React17.createElement(Box16, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, "(Type /exit to abort.)")));
|
|
10203
11572
|
}
|
|
10204
11573
|
|
|
10205
11574
|
// src/cli/commands/chat.tsx
|
|
@@ -10212,10 +11581,10 @@ function Root({
|
|
|
10212
11581
|
sessionPreview,
|
|
10213
11582
|
...appProps
|
|
10214
11583
|
}) {
|
|
10215
|
-
const [key, setKey] =
|
|
10216
|
-
const [pending, setPending] =
|
|
11584
|
+
const [key, setKey] = useState8(initialKey);
|
|
11585
|
+
const [pending, setPending] = useState8(sessionPreview);
|
|
10217
11586
|
if (!key) {
|
|
10218
|
-
return /* @__PURE__ */
|
|
11587
|
+
return /* @__PURE__ */ React18.createElement(
|
|
10219
11588
|
Setup,
|
|
10220
11589
|
{
|
|
10221
11590
|
onReady: (k) => {
|
|
@@ -10227,7 +11596,7 @@ function Root({
|
|
|
10227
11596
|
}
|
|
10228
11597
|
process.env.DEEPSEEK_API_KEY = key;
|
|
10229
11598
|
if (pending && appProps.session) {
|
|
10230
|
-
return /* @__PURE__ */
|
|
11599
|
+
return /* @__PURE__ */ React18.createElement(
|
|
10231
11600
|
SessionPicker,
|
|
10232
11601
|
{
|
|
10233
11602
|
sessionName: appProps.session,
|
|
@@ -10242,7 +11611,7 @@ function Root({
|
|
|
10242
11611
|
}
|
|
10243
11612
|
);
|
|
10244
11613
|
}
|
|
10245
|
-
return /* @__PURE__ */
|
|
11614
|
+
return /* @__PURE__ */ React18.createElement(
|
|
10246
11615
|
App,
|
|
10247
11616
|
{
|
|
10248
11617
|
model: appProps.model,
|
|
@@ -10346,7 +11715,7 @@ async function chatCommand(opts) {
|
|
|
10346
11715
|
rewriteSession(opts.session, []);
|
|
10347
11716
|
}
|
|
10348
11717
|
const { waitUntilExit } = render(
|
|
10349
|
-
/* @__PURE__ */
|
|
11718
|
+
/* @__PURE__ */ React18.createElement(
|
|
10350
11719
|
Root,
|
|
10351
11720
|
{
|
|
10352
11721
|
initialKey,
|
|
@@ -10370,13 +11739,14 @@ async function chatCommand(opts) {
|
|
|
10370
11739
|
}
|
|
10371
11740
|
|
|
10372
11741
|
// src/cli/commands/code.tsx
|
|
10373
|
-
import { basename, resolve as
|
|
11742
|
+
import { basename, resolve as resolve7 } from "path";
|
|
10374
11743
|
async function codeCommand(opts = {}) {
|
|
10375
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
10376
|
-
const rootDir =
|
|
11744
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-OVVMCH5F.js");
|
|
11745
|
+
const rootDir = resolve7(opts.dir ?? process.cwd());
|
|
10377
11746
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
10378
11747
|
const tools = new ToolRegistry();
|
|
10379
11748
|
registerFilesystemTools(tools, { rootDir });
|
|
11749
|
+
const jobs = new JobRegistry();
|
|
10380
11750
|
registerShellTools(tools, {
|
|
10381
11751
|
rootDir,
|
|
10382
11752
|
// Per-project "always allow" list persisted from prior ShellConfirm
|
|
@@ -10384,7 +11754,8 @@ async function codeCommand(opts = {}) {
|
|
|
10384
11754
|
// GETTER form — re-read every dispatch so a prefix the user adds
|
|
10385
11755
|
// via ShellConfirm mid-session takes effect on the next shell call
|
|
10386
11756
|
// instead of waiting for `/new` or a relaunch.
|
|
10387
|
-
extraAllowed: () => loadProjectShellAllowed(rootDir)
|
|
11757
|
+
extraAllowed: () => loadProjectShellAllowed(rootDir),
|
|
11758
|
+
jobs
|
|
10388
11759
|
});
|
|
10389
11760
|
registerPlanTool(tools);
|
|
10390
11761
|
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
@@ -10392,6 +11763,12 @@ async function codeCommand(opts = {}) {
|
|
|
10392
11763
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
10393
11764
|
`
|
|
10394
11765
|
);
|
|
11766
|
+
const sigShutdown = () => {
|
|
11767
|
+
void jobs.shutdown();
|
|
11768
|
+
};
|
|
11769
|
+
process.once("SIGINT", sigShutdown);
|
|
11770
|
+
process.once("SIGTERM", sigShutdown);
|
|
11771
|
+
process.once("exit", sigShutdown);
|
|
10395
11772
|
await chatCommand({
|
|
10396
11773
|
model: opts.model ?? "deepseek-v4-pro",
|
|
10397
11774
|
harvest: opts.harvest ?? false,
|
|
@@ -10399,7 +11776,7 @@ async function codeCommand(opts = {}) {
|
|
|
10399
11776
|
transcript: opts.transcript,
|
|
10400
11777
|
session,
|
|
10401
11778
|
seedTools: tools,
|
|
10402
|
-
codeMode: { rootDir },
|
|
11779
|
+
codeMode: { rootDir, jobs },
|
|
10403
11780
|
forceResume: opts.forceResume,
|
|
10404
11781
|
forceNew: opts.forceNew
|
|
10405
11782
|
});
|
|
@@ -10409,34 +11786,34 @@ async function codeCommand(opts = {}) {
|
|
|
10409
11786
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
10410
11787
|
import { basename as basename2 } from "path";
|
|
10411
11788
|
import { render as render2 } from "ink";
|
|
10412
|
-
import
|
|
11789
|
+
import React21 from "react";
|
|
10413
11790
|
|
|
10414
11791
|
// src/cli/ui/DiffApp.tsx
|
|
10415
|
-
import { Box as
|
|
10416
|
-
import
|
|
11792
|
+
import { Box as Box18, Static as Static2, Text as Text18, useApp as useApp3, useInput as useInput6 } from "ink";
|
|
11793
|
+
import React20, { useState as useState9 } from "react";
|
|
10417
11794
|
|
|
10418
11795
|
// src/cli/ui/RecordView.tsx
|
|
10419
|
-
import { Box as
|
|
10420
|
-
import
|
|
11796
|
+
import { Box as Box17, Text as Text17 } from "ink";
|
|
11797
|
+
import React19 from "react";
|
|
10421
11798
|
function RecordView({ rec, compact = false }) {
|
|
10422
11799
|
const toolArgsMax = compact ? 120 : 200;
|
|
10423
11800
|
const toolContentMax = compact ? 200 : 400;
|
|
10424
11801
|
if (rec.role === "user") {
|
|
10425
|
-
return /* @__PURE__ */
|
|
11802
|
+
return /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text17, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React19.createElement(Text17, null, rec.content));
|
|
10426
11803
|
}
|
|
10427
11804
|
if (rec.role === "assistant_final") {
|
|
10428
|
-
return /* @__PURE__ */
|
|
11805
|
+
return /* @__PURE__ */ React19.createElement(Box17, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React19.createElement(Box17, null, /* @__PURE__ */ React19.createElement(Text17, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React19.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React19.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React19.createElement(Text17, null, rec.content) : /* @__PURE__ */ React19.createElement(Text17, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
10429
11806
|
}
|
|
10430
11807
|
if (rec.role === "tool") {
|
|
10431
|
-
return /* @__PURE__ */
|
|
11808
|
+
return /* @__PURE__ */ React19.createElement(Box17, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text17, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
|
|
10432
11809
|
}
|
|
10433
11810
|
if (rec.role === "error") {
|
|
10434
|
-
return /* @__PURE__ */
|
|
11811
|
+
return /* @__PURE__ */ React19.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text17, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React19.createElement(Text17, { color: "red" }, rec.error ?? rec.content));
|
|
10435
11812
|
}
|
|
10436
11813
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
10437
11814
|
return null;
|
|
10438
11815
|
}
|
|
10439
|
-
return /* @__PURE__ */
|
|
11816
|
+
return /* @__PURE__ */ React19.createElement(Box17, null, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
10440
11817
|
}
|
|
10441
11818
|
function CacheBadge({ usage }) {
|
|
10442
11819
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -10445,7 +11822,7 @@ function CacheBadge({ usage }) {
|
|
|
10445
11822
|
if (total === 0) return null;
|
|
10446
11823
|
const pct2 = hit / total * 100;
|
|
10447
11824
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
10448
|
-
return /* @__PURE__ */
|
|
11825
|
+
return /* @__PURE__ */ React19.createElement(Text17, null, /* @__PURE__ */ React19.createElement(Text17, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React19.createElement(Text17, { color }, pct2.toFixed(1), "%"));
|
|
10449
11826
|
}
|
|
10450
11827
|
function truncate3(s, max) {
|
|
10451
11828
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -10456,8 +11833,8 @@ function DiffApp({ report }) {
|
|
|
10456
11833
|
const { exit } = useApp3();
|
|
10457
11834
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
10458
11835
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
10459
|
-
const [idx, setIdx] =
|
|
10460
|
-
|
|
11836
|
+
const [idx, setIdx] = useState9(Math.max(0, initialIdx));
|
|
11837
|
+
useInput6((input, key) => {
|
|
10461
11838
|
if (input === "q" || key.ctrl && input === "c") {
|
|
10462
11839
|
exit();
|
|
10463
11840
|
return;
|
|
@@ -10479,7 +11856,7 @@ function DiffApp({ report }) {
|
|
|
10479
11856
|
}
|
|
10480
11857
|
});
|
|
10481
11858
|
const pair = report.pairs[idx];
|
|
10482
|
-
return /* @__PURE__ */
|
|
11859
|
+
return /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(DiffHeader, { report }), /* @__PURE__ */ React20.createElement(Box18, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React20.createElement(Text18, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React20.createElement(Text18, null, pair ? /* @__PURE__ */ React20.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React20.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React20.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React20.createElement(Box18, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React20.createElement(Text18, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React20.createElement(Text18, null, pair.divergenceNote)) : null, /* @__PURE__ */ React20.createElement(Box18, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "j"), "/", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "k"), "/", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "N"), "/", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "g"), "/", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "q"), " ", "quit")));
|
|
10483
11860
|
}
|
|
10484
11861
|
function DiffHeader({ report }) {
|
|
10485
11862
|
const a = report.a;
|
|
@@ -10497,15 +11874,15 @@ function DiffHeader({ report }) {
|
|
|
10497
11874
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
10498
11875
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
10499
11876
|
}
|
|
10500
|
-
return /* @__PURE__ */
|
|
11877
|
+
return /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React20.createElement(Box18, { justifyContent: "space-between" }, /* @__PURE__ */ React20.createElement(Text18, null, /* @__PURE__ */ React20.createElement(Text18, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React20.createElement(Text18, { color: "blue" }, a.label), /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, " vs B="), /* @__PURE__ */ React20.createElement(Text18, { color: "magenta" }, b.label)), /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React20.createElement(Box18, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React20.createElement(Text18, null, /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, "cache "), /* @__PURE__ */ React20.createElement(Text18, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React20.createElement(Text18, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React20.createElement(Text18, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React20.createElement(Text18, null, /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, "cost "), /* @__PURE__ */ React20.createElement(Text18, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React20.createElement(Text18, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React20.createElement(Text18, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React20.createElement(Text18, null, /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, "model calls "), /* @__PURE__ */ React20.createElement(Text18, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React20.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React20.createElement(Text18, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
10501
11878
|
}
|
|
10502
11879
|
function Pane({
|
|
10503
11880
|
label,
|
|
10504
11881
|
headerColor,
|
|
10505
11882
|
records
|
|
10506
11883
|
}) {
|
|
10507
|
-
return /* @__PURE__ */
|
|
10508
|
-
|
|
11884
|
+
return /* @__PURE__ */ React20.createElement(
|
|
11885
|
+
Box18,
|
|
10509
11886
|
{
|
|
10510
11887
|
flexDirection: "column",
|
|
10511
11888
|
flexGrow: 1,
|
|
@@ -10513,21 +11890,21 @@ function Pane({
|
|
|
10513
11890
|
borderStyle: "single",
|
|
10514
11891
|
borderColor: headerColor
|
|
10515
11892
|
},
|
|
10516
|
-
/* @__PURE__ */
|
|
10517
|
-
records.length === 0 ? /* @__PURE__ */
|
|
11893
|
+
/* @__PURE__ */ React20.createElement(Text18, { color: headerColor, bold: true }, label),
|
|
11894
|
+
records.length === 0 ? /* @__PURE__ */ React20.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React20.createElement(Text18, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React20.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React20.createElement(RecordView, { key, rec, compact: true }))
|
|
10518
11895
|
);
|
|
10519
11896
|
}
|
|
10520
11897
|
function KindBadge({ kind }) {
|
|
10521
11898
|
if (kind === "match") {
|
|
10522
|
-
return /* @__PURE__ */
|
|
11899
|
+
return /* @__PURE__ */ React20.createElement(Text18, { color: "green" }, "\u2713 match");
|
|
10523
11900
|
}
|
|
10524
11901
|
if (kind === "diverge") {
|
|
10525
|
-
return /* @__PURE__ */
|
|
11902
|
+
return /* @__PURE__ */ React20.createElement(Text18, { color: "yellow" }, "\u2605 diverge");
|
|
10526
11903
|
}
|
|
10527
11904
|
if (kind === "only_in_a") {
|
|
10528
|
-
return /* @__PURE__ */
|
|
11905
|
+
return /* @__PURE__ */ React20.createElement(Text18, { color: "blue" }, "\u2190 only in A");
|
|
10529
11906
|
}
|
|
10530
|
-
return /* @__PURE__ */
|
|
11907
|
+
return /* @__PURE__ */ React20.createElement(Text18, { color: "magenta" }, "\u2192 only in B");
|
|
10531
11908
|
}
|
|
10532
11909
|
function paneRecords(pair, side) {
|
|
10533
11910
|
if (!pair) return [];
|
|
@@ -10558,7 +11935,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
10558
11935
|
return;
|
|
10559
11936
|
}
|
|
10560
11937
|
if (wantTui) {
|
|
10561
|
-
const { waitUntilExit } = render2(
|
|
11938
|
+
const { waitUntilExit } = render2(React21.createElement(DiffApp, { report }), {
|
|
10562
11939
|
exitOnCtrlC: true,
|
|
10563
11940
|
patchConsole: false
|
|
10564
11941
|
});
|
|
@@ -10699,16 +12076,16 @@ function pad2(s, width) {
|
|
|
10699
12076
|
|
|
10700
12077
|
// src/cli/commands/replay.ts
|
|
10701
12078
|
import { render as render3 } from "ink";
|
|
10702
|
-
import
|
|
12079
|
+
import React23 from "react";
|
|
10703
12080
|
|
|
10704
12081
|
// src/cli/ui/ReplayApp.tsx
|
|
10705
|
-
import { Box as
|
|
10706
|
-
import
|
|
12082
|
+
import { Box as Box19, Static as Static3, Text as Text19, useApp as useApp4, useInput as useInput7 } from "ink";
|
|
12083
|
+
import React22, { useMemo as useMemo3, useState as useState10 } from "react";
|
|
10707
12084
|
function ReplayApp({ meta, pages }) {
|
|
10708
12085
|
const { exit } = useApp4();
|
|
10709
12086
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
10710
|
-
const [idx, setIdx] =
|
|
10711
|
-
|
|
12087
|
+
const [idx, setIdx] = useState10(maxIdx);
|
|
12088
|
+
useInput7((input, key) => {
|
|
10712
12089
|
if (input === "q" || key.ctrl && input === "c") {
|
|
10713
12090
|
exit();
|
|
10714
12091
|
return;
|
|
@@ -10727,7 +12104,7 @@ function ReplayApp({ meta, pages }) {
|
|
|
10727
12104
|
setIdx(maxIdx);
|
|
10728
12105
|
}
|
|
10729
12106
|
});
|
|
10730
|
-
const cumStats =
|
|
12107
|
+
const cumStats = useMemo3(() => computeCumulativeStats(pages, idx), [pages, idx]);
|
|
10731
12108
|
const summary = {
|
|
10732
12109
|
turns: cumStats.turns,
|
|
10733
12110
|
totalCostUsd: cumStats.totalCostUsd,
|
|
@@ -10742,14 +12119,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
10742
12119
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
10743
12120
|
const currentPage = pages[idx];
|
|
10744
12121
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
10745
|
-
return /* @__PURE__ */
|
|
12122
|
+
return /* @__PURE__ */ React22.createElement(Box19, { flexDirection: "column" }, /* @__PURE__ */ React22.createElement(
|
|
10746
12123
|
StatsPanel,
|
|
10747
12124
|
{
|
|
10748
12125
|
summary,
|
|
10749
12126
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
10750
12127
|
prefixHash
|
|
10751
12128
|
}
|
|
10752
|
-
), /* @__PURE__ */
|
|
12129
|
+
), /* @__PURE__ */ React22.createElement(Box19, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React22.createElement(Box19, { justifyContent: "space-between" }, /* @__PURE__ */ React22.createElement(Text19, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React22.createElement(Text19, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React22.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React22.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React22.createElement(Text19, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React22.createElement(Text19, { dimColor: true }, /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "j"), "/", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "k"), "/", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React22.createElement(Text19, { bold: true }, "q"), " quit")));
|
|
10753
12130
|
}
|
|
10754
12131
|
|
|
10755
12132
|
// src/cli/commands/replay.ts
|
|
@@ -10761,7 +12138,7 @@ async function replayCommand(opts) {
|
|
|
10761
12138
|
}
|
|
10762
12139
|
const { parsed } = replayFromFile(opts.path);
|
|
10763
12140
|
const pages = groupRecordsByTurn(parsed.records);
|
|
10764
|
-
const { waitUntilExit } = render3(
|
|
12141
|
+
const { waitUntilExit } = render3(React23.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
10765
12142
|
exitOnCtrlC: true,
|
|
10766
12143
|
patchConsole: false
|
|
10767
12144
|
});
|
|
@@ -11066,12 +12443,12 @@ function truncate4(s, max) {
|
|
|
11066
12443
|
|
|
11067
12444
|
// src/cli/commands/setup.tsx
|
|
11068
12445
|
import { render as render4 } from "ink";
|
|
11069
|
-
import
|
|
12446
|
+
import React25 from "react";
|
|
11070
12447
|
|
|
11071
12448
|
// src/cli/ui/Wizard.tsx
|
|
11072
|
-
import { Box as
|
|
12449
|
+
import { Box as Box20, Text as Text20, useApp as useApp5, useInput as useInput8 } from "ink";
|
|
11073
12450
|
import TextInput2 from "ink-text-input";
|
|
11074
|
-
import
|
|
12451
|
+
import React24, { useState as useState11 } from "react";
|
|
11075
12452
|
|
|
11076
12453
|
// src/cli/ui/presets.ts
|
|
11077
12454
|
var PRESETS = {
|
|
@@ -11103,19 +12480,19 @@ var PRESET_DESCRIPTIONS = {
|
|
|
11103
12480
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
11104
12481
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
11105
12482
|
const { exit } = useApp5();
|
|
11106
|
-
const [step, setStep] =
|
|
11107
|
-
const [data, setData] =
|
|
12483
|
+
const [step, setStep] = useState11(existingApiKey ? "preset" : "apiKey");
|
|
12484
|
+
const [data, setData] = useState11({
|
|
11108
12485
|
apiKey: existingApiKey ?? "",
|
|
11109
12486
|
preset: initial?.preset ?? "fast",
|
|
11110
12487
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
11111
12488
|
catalogArgs: {}
|
|
11112
12489
|
});
|
|
11113
|
-
const [error, setError] =
|
|
11114
|
-
|
|
12490
|
+
const [error, setError] = useState11(null);
|
|
12491
|
+
useInput8((_input, key) => {
|
|
11115
12492
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
11116
12493
|
});
|
|
11117
12494
|
if (step === "apiKey") {
|
|
11118
|
-
return /* @__PURE__ */
|
|
12495
|
+
return /* @__PURE__ */ React24.createElement(
|
|
11119
12496
|
ApiKeyStep,
|
|
11120
12497
|
{
|
|
11121
12498
|
onSubmit: (key) => {
|
|
@@ -11129,7 +12506,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
11129
12506
|
);
|
|
11130
12507
|
}
|
|
11131
12508
|
if (step === "preset") {
|
|
11132
|
-
return /* @__PURE__ */
|
|
12509
|
+
return /* @__PURE__ */ React24.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React24.createElement(
|
|
11133
12510
|
SingleSelect,
|
|
11134
12511
|
{
|
|
11135
12512
|
items: presetItems(),
|
|
@@ -11139,10 +12516,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
11139
12516
|
setStep("mcp");
|
|
11140
12517
|
}
|
|
11141
12518
|
}
|
|
11142
|
-
), /* @__PURE__ */
|
|
12519
|
+
), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
11143
12520
|
}
|
|
11144
12521
|
if (step === "mcp") {
|
|
11145
|
-
return /* @__PURE__ */
|
|
12522
|
+
return /* @__PURE__ */ React24.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React24.createElement(
|
|
11146
12523
|
MultiSelect,
|
|
11147
12524
|
{
|
|
11148
12525
|
items: mcpItems(),
|
|
@@ -11167,7 +12544,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
11167
12544
|
}
|
|
11168
12545
|
const currentName = pending[0];
|
|
11169
12546
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
11170
|
-
return /* @__PURE__ */
|
|
12547
|
+
return /* @__PURE__ */ React24.createElement(
|
|
11171
12548
|
McpArgsStep,
|
|
11172
12549
|
{
|
|
11173
12550
|
entry,
|
|
@@ -11185,7 +12562,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
11185
12562
|
}
|
|
11186
12563
|
if (step === "review") {
|
|
11187
12564
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
11188
|
-
return /* @__PURE__ */
|
|
12565
|
+
return /* @__PURE__ */ React24.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React24.createElement(Box20, { flexDirection: "column" }, /* @__PURE__ */ React24.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React24.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React24.createElement(
|
|
11189
12566
|
SummaryLine,
|
|
11190
12567
|
{
|
|
11191
12568
|
label: "MCP",
|
|
@@ -11193,8 +12570,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
11193
12570
|
}
|
|
11194
12571
|
), specs.map((spec, i) => (
|
|
11195
12572
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
11196
|
-
/* @__PURE__ */
|
|
11197
|
-
)), /* @__PURE__ */
|
|
12573
|
+
/* @__PURE__ */ React24.createElement(Box20, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "\xB7 ", spec))
|
|
12574
|
+
)), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { color: "red" }, error)) : null, /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React24.createElement(
|
|
11198
12575
|
ReviewConfirm,
|
|
11199
12576
|
{
|
|
11200
12577
|
onConfirm: () => {
|
|
@@ -11220,15 +12597,15 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
11220
12597
|
}
|
|
11221
12598
|
));
|
|
11222
12599
|
}
|
|
11223
|
-
return /* @__PURE__ */
|
|
12600
|
+
return /* @__PURE__ */ React24.createElement(Box20, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Text20, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React24.createElement(ExitOnEnter, { onExit: exit }));
|
|
11224
12601
|
}
|
|
11225
12602
|
function ApiKeyStep({
|
|
11226
12603
|
onSubmit,
|
|
11227
12604
|
error,
|
|
11228
12605
|
onError
|
|
11229
12606
|
}) {
|
|
11230
|
-
const [value, setValue] =
|
|
11231
|
-
return /* @__PURE__ */
|
|
12607
|
+
const [value, setValue] = useState11("");
|
|
12608
|
+
return /* @__PURE__ */ React24.createElement(Box20, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Text20, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React24.createElement(
|
|
11232
12609
|
TextInput2,
|
|
11233
12610
|
{
|
|
11234
12611
|
value,
|
|
@@ -11245,7 +12622,7 @@ function ApiKeyStep({
|
|
|
11245
12622
|
mask: "\u2022",
|
|
11246
12623
|
placeholder: "sk-..."
|
|
11247
12624
|
}
|
|
11248
|
-
)), error ? /* @__PURE__ */
|
|
12625
|
+
)), error ? /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { color: "red" }, error)) : value ? /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
11249
12626
|
}
|
|
11250
12627
|
function McpArgsStep({
|
|
11251
12628
|
entry,
|
|
@@ -11253,8 +12630,8 @@ function McpArgsStep({
|
|
|
11253
12630
|
onSubmit,
|
|
11254
12631
|
onError
|
|
11255
12632
|
}) {
|
|
11256
|
-
const [value, setValue] =
|
|
11257
|
-
return /* @__PURE__ */
|
|
12633
|
+
const [value, setValue] = useState11("");
|
|
12634
|
+
return /* @__PURE__ */ React24.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React24.createElement(Box20, { flexDirection: "column" }, /* @__PURE__ */ React24.createElement(Text20, null, entry.summary), entry.note ? /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, null, "Required parameter: "), /* @__PURE__ */ React24.createElement(Text20, { bold: true }, entry.userArgs)), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React24.createElement(
|
|
11258
12635
|
TextInput2,
|
|
11259
12636
|
{
|
|
11260
12637
|
value,
|
|
@@ -11270,16 +12647,16 @@ function McpArgsStep({
|
|
|
11270
12647
|
},
|
|
11271
12648
|
placeholder: placeholderFor(entry)
|
|
11272
12649
|
}
|
|
11273
|
-
)), error ? /* @__PURE__ */
|
|
12650
|
+
)), error ? /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text20, { color: "red" }, error)) : null));
|
|
11274
12651
|
}
|
|
11275
12652
|
function ReviewConfirm({ onConfirm }) {
|
|
11276
|
-
|
|
12653
|
+
useInput8((_i, key) => {
|
|
11277
12654
|
if (key.return) onConfirm();
|
|
11278
12655
|
});
|
|
11279
12656
|
return null;
|
|
11280
12657
|
}
|
|
11281
12658
|
function ExitOnEnter({ onExit }) {
|
|
11282
|
-
|
|
12659
|
+
useInput8((_i, key) => {
|
|
11283
12660
|
if (key.return) onExit();
|
|
11284
12661
|
});
|
|
11285
12662
|
return null;
|
|
@@ -11290,10 +12667,10 @@ function StepFrame({
|
|
|
11290
12667
|
total,
|
|
11291
12668
|
children
|
|
11292
12669
|
}) {
|
|
11293
|
-
return /* @__PURE__ */
|
|
12670
|
+
return /* @__PURE__ */ React24.createElement(Box20, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Box20, null, /* @__PURE__ */ React24.createElement(Text20, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React24.createElement(Text20, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React24.createElement(Box20, { marginTop: 1, flexDirection: "column" }, children));
|
|
11294
12671
|
}
|
|
11295
12672
|
function SummaryLine({ label, value }) {
|
|
11296
|
-
return /* @__PURE__ */
|
|
12673
|
+
return /* @__PURE__ */ React24.createElement(Box20, null, /* @__PURE__ */ React24.createElement(Text20, null, label.padEnd(12)), /* @__PURE__ */ React24.createElement(Text20, { bold: true }, value));
|
|
11297
12674
|
}
|
|
11298
12675
|
function presetItems() {
|
|
11299
12676
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -11349,7 +12726,7 @@ async function setupCommand(_opts = {}) {
|
|
|
11349
12726
|
const existingKey = loadApiKey();
|
|
11350
12727
|
const existing = readConfig();
|
|
11351
12728
|
const { waitUntilExit, unmount } = render4(
|
|
11352
|
-
/* @__PURE__ */
|
|
12729
|
+
/* @__PURE__ */ React25.createElement(
|
|
11353
12730
|
Wizard,
|
|
11354
12731
|
{
|
|
11355
12732
|
existingApiKey: existingKey,
|
|
@@ -11367,7 +12744,7 @@ async function setupCommand(_opts = {}) {
|
|
|
11367
12744
|
}
|
|
11368
12745
|
|
|
11369
12746
|
// src/cli/commands/update.ts
|
|
11370
|
-
import { spawn as
|
|
12747
|
+
import { spawn as spawn5 } from "child_process";
|
|
11371
12748
|
function planUpdate(input) {
|
|
11372
12749
|
const diff = compareVersions(input.current, input.latest);
|
|
11373
12750
|
if (diff > 0) {
|
|
@@ -11397,13 +12774,13 @@ function planUpdate(input) {
|
|
|
11397
12774
|
};
|
|
11398
12775
|
}
|
|
11399
12776
|
function defaultSpawn(argv) {
|
|
11400
|
-
return new Promise((
|
|
11401
|
-
const child =
|
|
12777
|
+
return new Promise((resolve8, reject) => {
|
|
12778
|
+
const child = spawn5(argv[0], argv.slice(1), {
|
|
11402
12779
|
stdio: "inherit",
|
|
11403
12780
|
shell: process.platform === "win32"
|
|
11404
12781
|
});
|
|
11405
12782
|
child.once("error", reject);
|
|
11406
|
-
child.once("exit", (code) =>
|
|
12783
|
+
child.once("exit", (code) => resolve8(code ?? 1));
|
|
11407
12784
|
});
|
|
11408
12785
|
}
|
|
11409
12786
|
async function updateCommand(opts = {}) {
|