reasonix 0.6.0 → 0.7.0
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-NXYPGKA3.js → chunk-5DZMZCCW.js} +22 -3
- package/dist/cli/chunk-5DZMZCCW.js.map +1 -0
- package/dist/cli/index.js +1989 -544
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-KX6A4DVX.js → prompt-2OABSPAW.js} +2 -2
- package/dist/index.d.ts +256 -45
- package/dist/index.js +470 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-NXYPGKA3.js.map +0 -1
- /package/dist/cli/{prompt-KX6A4DVX.js.map → prompt-2OABSPAW.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
memoryEnabled,
|
|
11
11
|
readProjectMemory,
|
|
12
12
|
sanitizeMemoryName
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5DZMZCCW.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -443,7 +443,7 @@ async function harvest(reasoningContent, client, options = {}, signal) {
|
|
|
443
443
|
const minLen = options.minReasoningLen ?? 40;
|
|
444
444
|
const trimmed = reasoningContent.trim();
|
|
445
445
|
if (trimmed.length < minLen) return emptyPlanState();
|
|
446
|
-
const model2 = options.model ?? "deepseek-
|
|
446
|
+
const model2 = options.model ?? "deepseek-v4-flash";
|
|
447
447
|
const maxItems = options.maxItems ?? 5;
|
|
448
448
|
const maxItemLen = options.maxItemLen ?? 80;
|
|
449
449
|
const system = SYSTEM_PROMPT.replace("{maxItems}", String(maxItems)).replace(
|
|
@@ -1838,8 +1838,9 @@ var ARGS_COMPACT_THRESHOLD_TOKENS = 800;
|
|
|
1838
1838
|
var TURN_END_RESULT_CAP_TOKENS = 3e3;
|
|
1839
1839
|
var FAILURE_ESCALATION_THRESHOLD = 3;
|
|
1840
1840
|
var ESCALATION_MODEL = "deepseek-v4-pro";
|
|
1841
|
-
var
|
|
1842
|
-
var
|
|
1841
|
+
var NEEDS_PRO_MARKER_PREFIX = "<<<NEEDS_PRO";
|
|
1842
|
+
var NEEDS_PRO_MARKER_RE = /^<<<NEEDS_PRO(?::\s*([^>]*))?>>>/;
|
|
1843
|
+
var NEEDS_PRO_BUFFER_CHARS = 256;
|
|
1843
1844
|
var CacheFirstLoop = class {
|
|
1844
1845
|
client;
|
|
1845
1846
|
prefix;
|
|
@@ -1905,6 +1906,14 @@ var CacheFirstLoop = class {
|
|
|
1905
1906
|
* the user doesn't watch flash retry the same edit 5 times.
|
|
1906
1907
|
*/
|
|
1907
1908
|
_turnFailureCount = 0;
|
|
1909
|
+
/**
|
|
1910
|
+
* Per-type breakdown of failure signals counted toward the turn's
|
|
1911
|
+
* auto-escalation threshold. Surfaced in the warning when the
|
|
1912
|
+
* threshold trips so the user sees what kind of trouble flash
|
|
1913
|
+
* actually hit ("3× search-mismatch, 2× truncated") rather than
|
|
1914
|
+
* just a bare count. Reset alongside _turnFailureCount.
|
|
1915
|
+
*/
|
|
1916
|
+
_turnFailureTypes = {};
|
|
1908
1917
|
constructor(opts) {
|
|
1909
1918
|
this.client = opts.client;
|
|
1910
1919
|
this.prefix = opts.prefix;
|
|
@@ -1932,10 +1941,11 @@ var CacheFirstLoop = class {
|
|
|
1932
1941
|
this.sessionName = opts.session ?? null;
|
|
1933
1942
|
if (this.sessionName) {
|
|
1934
1943
|
const prior = loadSessionMessages(this.sessionName);
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1944
|
+
const shrunk = healLoadedMessagesByTokens(prior, DEFAULT_MAX_RESULT_TOKENS);
|
|
1945
|
+
const stamped = stampMissingReasoningForThinkingMode(shrunk.messages, this.model);
|
|
1946
|
+
const messages = stamped.messages;
|
|
1947
|
+
const healedCount = shrunk.healedCount + stamped.stampedCount;
|
|
1948
|
+
const tokensSaved = shrunk.tokensSaved;
|
|
1939
1949
|
for (const msg of messages) this.log.append(msg);
|
|
1940
1950
|
this.resumedMessageCount = messages.length;
|
|
1941
1951
|
if (healedCount > 0) {
|
|
@@ -2162,15 +2172,41 @@ var CacheFirstLoop = class {
|
|
|
2162
2172
|
return this._escalateThisTurn ? ESCALATION_MODEL : this.model;
|
|
2163
2173
|
}
|
|
2164
2174
|
/**
|
|
2165
|
-
*
|
|
2166
|
-
*
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2175
|
+
* Parse the escalation marker out of the model's leading content.
|
|
2176
|
+
* Returns `{ matched: true, reason? }` for both bare and reason-
|
|
2177
|
+
* carrying forms. Only the FIRST line matters — the model is
|
|
2178
|
+
* instructed to emit the marker as the first output token if at
|
|
2179
|
+
* all. Matches anywhere else in the text are normal content
|
|
2180
|
+
* references (e.g. the user asked about the marker itself).
|
|
2171
2181
|
*/
|
|
2182
|
+
parseEscalationMarker(content) {
|
|
2183
|
+
const m = NEEDS_PRO_MARKER_RE.exec(content.trimStart());
|
|
2184
|
+
if (!m) return { matched: false };
|
|
2185
|
+
const reason = m[1]?.trim();
|
|
2186
|
+
return { matched: true, reason: reason || void 0 };
|
|
2187
|
+
}
|
|
2188
|
+
/** Convenience boolean — same gate the streaming path used to call. */
|
|
2172
2189
|
isEscalationRequest(content) {
|
|
2173
|
-
return
|
|
2190
|
+
return this.parseEscalationMarker(content).matched;
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Could `buf` STILL plausibly become the full marker as more chunks
|
|
2194
|
+
* arrive? Drives the streaming buffer's flush decision: while this
|
|
2195
|
+
* is true we keep accumulating; once it's false (or the buffer
|
|
2196
|
+
* exceeds the byte limit) we flush so the user isn't staring at a
|
|
2197
|
+
* delayed display for arbitrary content that just happens to start
|
|
2198
|
+
* with `<`.
|
|
2199
|
+
*/
|
|
2200
|
+
looksLikePartialEscalationMarker(buf) {
|
|
2201
|
+
const t = buf.trimStart();
|
|
2202
|
+
if (t.length === 0) return true;
|
|
2203
|
+
if (t.length <= NEEDS_PRO_MARKER_PREFIX.length) {
|
|
2204
|
+
return NEEDS_PRO_MARKER_PREFIX.startsWith(t);
|
|
2205
|
+
}
|
|
2206
|
+
if (!t.startsWith(NEEDS_PRO_MARKER_PREFIX)) return false;
|
|
2207
|
+
const rest = t.slice(NEEDS_PRO_MARKER_PREFIX.length);
|
|
2208
|
+
if (rest[0] !== ">" && rest[0] !== ":") return false;
|
|
2209
|
+
return true;
|
|
2174
2210
|
}
|
|
2175
2211
|
/**
|
|
2176
2212
|
* Check whether a tool result string looks like a "flash struggled"
|
|
@@ -2184,16 +2220,18 @@ var CacheFirstLoop = class {
|
|
|
2184
2220
|
*/
|
|
2185
2221
|
noteToolFailureSignal(resultJson, repair) {
|
|
2186
2222
|
let bumped = false;
|
|
2187
|
-
|
|
2188
|
-
this._turnFailureCount +=
|
|
2223
|
+
const bump = (kind, by = 1) => {
|
|
2224
|
+
this._turnFailureCount += by;
|
|
2225
|
+
this._turnFailureTypes[kind] = (this._turnFailureTypes[kind] ?? 0) + by;
|
|
2189
2226
|
bumped = true;
|
|
2227
|
+
};
|
|
2228
|
+
if (resultJson.includes('"error"') && resultJson.includes("search text not found")) {
|
|
2229
|
+
bump("search-mismatch");
|
|
2190
2230
|
}
|
|
2191
2231
|
if (repair) {
|
|
2192
|
-
|
|
2193
|
-
if (
|
|
2194
|
-
|
|
2195
|
-
bumped = true;
|
|
2196
|
-
}
|
|
2232
|
+
if (repair.scavenged > 0) bump("scavenged", repair.scavenged);
|
|
2233
|
+
if (repair.truncationsFixed > 0) bump("truncated", repair.truncationsFixed);
|
|
2234
|
+
if (repair.stormsBroken > 0) bump("storm-broken", repair.stormsBroken);
|
|
2197
2235
|
}
|
|
2198
2236
|
if (bumped && !this._escalateThisTurn && this._turnFailureCount >= FAILURE_ESCALATION_THRESHOLD) {
|
|
2199
2237
|
this._escalateThisTurn = true;
|
|
@@ -2201,6 +2239,16 @@ var CacheFirstLoop = class {
|
|
|
2201
2239
|
}
|
|
2202
2240
|
return false;
|
|
2203
2241
|
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Render `_turnFailureTypes` as a comma-separated breakdown like
|
|
2244
|
+
* "2× search-mismatch, 1× truncated" for the auto-escalation
|
|
2245
|
+
* warning. Empty if no types have been recorded yet (defensive —
|
|
2246
|
+
* the warning sites only call this after a bump).
|
|
2247
|
+
*/
|
|
2248
|
+
formatFailureBreakdown() {
|
|
2249
|
+
const parts = Object.entries(this._turnFailureTypes).filter(([, n]) => n > 0).map(([kind, n]) => `${n}\xD7 ${kind}`);
|
|
2250
|
+
return parts.length > 0 ? parts.join(", ") : `${this._turnFailureCount} repair/error signal(s)`;
|
|
2251
|
+
}
|
|
2204
2252
|
buildMessages(pendingUser) {
|
|
2205
2253
|
const healed = healLoadedMessages(this.log.toMessages(), DEFAULT_MAX_RESULT_CHARS);
|
|
2206
2254
|
const msgs = [...this.prefix.toMessages(), ...healed.messages];
|
|
@@ -2256,6 +2304,7 @@ var CacheFirstLoop = class {
|
|
|
2256
2304
|
this.scratch.reset();
|
|
2257
2305
|
this.repair.resetStorm();
|
|
2258
2306
|
this._turnFailureCount = 0;
|
|
2307
|
+
this._turnFailureTypes = {};
|
|
2259
2308
|
this._escalateThisTurn = false;
|
|
2260
2309
|
let armedConsumed = false;
|
|
2261
2310
|
if (this._proArmedForNextTurn) {
|
|
@@ -2444,7 +2493,7 @@ var CacheFirstLoop = class {
|
|
|
2444
2493
|
if (this.isEscalationRequest(escalationBuf)) {
|
|
2445
2494
|
break;
|
|
2446
2495
|
}
|
|
2447
|
-
if (escalationBuf.length >= NEEDS_PRO_BUFFER_CHARS ||
|
|
2496
|
+
if (escalationBuf.length >= NEEDS_PRO_BUFFER_CHARS || !this.looksLikePartialEscalationMarker(escalationBuf)) {
|
|
2448
2497
|
escalationBufFlushed = true;
|
|
2449
2498
|
yield {
|
|
2450
2499
|
turn: this._turn,
|
|
@@ -2539,11 +2588,13 @@ var CacheFirstLoop = class {
|
|
|
2539
2588
|
return;
|
|
2540
2589
|
}
|
|
2541
2590
|
if (this.modelForCurrentCall() !== ESCALATION_MODEL && this.isEscalationRequest(assistantContent)) {
|
|
2591
|
+
const { reason } = this.parseEscalationMarker(assistantContent);
|
|
2542
2592
|
this._escalateThisTurn = true;
|
|
2593
|
+
const reasonSuffix = reason ? ` \u2014 ${reason}` : "";
|
|
2543
2594
|
yield {
|
|
2544
2595
|
turn: this._turn,
|
|
2545
2596
|
role: "warning",
|
|
2546
|
-
content: `\u21E7 flash requested escalation \u2014 retrying this turn on ${ESCALATION_MODEL}`
|
|
2597
|
+
content: `\u21E7 flash requested escalation \u2014 retrying this turn on ${ESCALATION_MODEL}${reasonSuffix}`
|
|
2547
2598
|
};
|
|
2548
2599
|
assistantContent = "";
|
|
2549
2600
|
reasoningContent = "";
|
|
@@ -2578,7 +2629,12 @@ var CacheFirstLoop = class {
|
|
|
2578
2629
|
assistantContent || null
|
|
2579
2630
|
);
|
|
2580
2631
|
this.appendAndPersist(
|
|
2581
|
-
this.assistantMessage(
|
|
2632
|
+
this.assistantMessage(
|
|
2633
|
+
assistantContent,
|
|
2634
|
+
repairedCalls,
|
|
2635
|
+
this.modelForCurrentCall(),
|
|
2636
|
+
reasoningContent
|
|
2637
|
+
)
|
|
2582
2638
|
);
|
|
2583
2639
|
yield {
|
|
2584
2640
|
turn: this._turn,
|
|
@@ -2593,7 +2649,7 @@ var CacheFirstLoop = class {
|
|
|
2593
2649
|
yield {
|
|
2594
2650
|
turn: this._turn,
|
|
2595
2651
|
role: "warning",
|
|
2596
|
-
content: `\u21E7 auto-escalating to ${ESCALATION_MODEL} for the rest of this turn \u2014 flash hit ${this.
|
|
2652
|
+
content: `\u21E7 auto-escalating to ${ESCALATION_MODEL} for the rest of this turn \u2014 flash hit ${this.formatFailureBreakdown()}. Next turn falls back to ${this.model} unless /pro is armed.`
|
|
2597
2653
|
};
|
|
2598
2654
|
}
|
|
2599
2655
|
if (report.stormsBroken > 0) {
|
|
@@ -2717,7 +2773,7 @@ ${reason}`;
|
|
|
2717
2773
|
yield {
|
|
2718
2774
|
turn: this._turn,
|
|
2719
2775
|
role: "warning",
|
|
2720
|
-
content: `\u21E7 auto-escalating to ${ESCALATION_MODEL} for the rest of this turn \u2014 flash hit ${this.
|
|
2776
|
+
content: `\u21E7 auto-escalating to ${ESCALATION_MODEL} for the rest of this turn \u2014 flash hit ${this.formatFailureBreakdown()}. Next turn falls back to ${this.model} unless /pro is armed.`
|
|
2721
2777
|
};
|
|
2722
2778
|
}
|
|
2723
2779
|
yield {
|
|
@@ -2761,7 +2817,9 @@ ${reason}`;
|
|
|
2761
2817
|
|
|
2762
2818
|
${summary}`;
|
|
2763
2819
|
const summaryStats = this.stats.record(this._turn, summaryModel, resp.usage ?? new Usage());
|
|
2764
|
-
this.appendAndPersist(
|
|
2820
|
+
this.appendAndPersist(
|
|
2821
|
+
this.assistantMessage(summary, [], summaryModel, resp.reasoningContent)
|
|
2822
|
+
);
|
|
2765
2823
|
yield {
|
|
2766
2824
|
turn: this._turn,
|
|
2767
2825
|
role: "assistant_final",
|
|
@@ -2792,28 +2850,39 @@ ${summary}`;
|
|
|
2792
2850
|
}
|
|
2793
2851
|
return final;
|
|
2794
2852
|
}
|
|
2795
|
-
|
|
2853
|
+
/**
|
|
2854
|
+
* Build an assistant message for the log. The `producingModel` arg is
|
|
2855
|
+
* the model that actually generated this turn (flash, pro, the
|
|
2856
|
+
* forced-summary flash call, `this.model` for synthetics, etc.) —
|
|
2857
|
+
* NOT `this.model`, because escalation + forced-summary can both
|
|
2858
|
+
* route a single turn to a different model.
|
|
2859
|
+
*
|
|
2860
|
+
* The single invariant this encodes: if the producing model is
|
|
2861
|
+
* thinking-mode, `reasoning_content` MUST be present on the
|
|
2862
|
+
* persisted message — even as an empty string. DeepSeek's validator
|
|
2863
|
+
* 400s the NEXT request if any historical thinking-mode assistant
|
|
2864
|
+
* turn is missing it. We used to gate on `reasoning.length > 0`,
|
|
2865
|
+
* which silently dropped the field whenever the stream emitted zero
|
|
2866
|
+
* reasoning deltas or the API returned `reasoning_content: null` —
|
|
2867
|
+
* both legitimate edge cases the 0.5.15/0.5.18 fixes missed.
|
|
2868
|
+
*/
|
|
2869
|
+
assistantMessage(content, toolCalls, producingModel, reasoningContent) {
|
|
2796
2870
|
const msg = { role: "assistant", content };
|
|
2797
2871
|
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
2798
|
-
if (
|
|
2799
|
-
msg.reasoning_content = reasoningContent;
|
|
2872
|
+
if (isThinkingModeModel(producingModel)) {
|
|
2873
|
+
msg.reasoning_content = reasoningContent ?? "";
|
|
2800
2874
|
}
|
|
2801
2875
|
return msg;
|
|
2802
2876
|
}
|
|
2803
2877
|
/**
|
|
2804
|
-
*
|
|
2805
|
-
*
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
* doesn't care — field stays absent there.
|
|
2878
|
+
* Synthetic assistant message (abort notices, future system injections)
|
|
2879
|
+
* — no real API round trip. Delegates to {@link assistantMessage} with
|
|
2880
|
+
* `this.model` as the stand-in producer, so the same thinking-mode
|
|
2881
|
+
* invariant applies: reasoner sessions get an empty-string
|
|
2882
|
+
* `reasoning_content`; V3 sessions get nothing.
|
|
2810
2883
|
*/
|
|
2811
2884
|
syntheticAssistantMessage(content) {
|
|
2812
|
-
|
|
2813
|
-
if (isThinkingModeModel(this.model)) {
|
|
2814
|
-
msg.reasoning_content = "";
|
|
2815
|
-
}
|
|
2816
|
-
return msg;
|
|
2885
|
+
return this.assistantMessage(content, [], this.model, "");
|
|
2817
2886
|
}
|
|
2818
2887
|
};
|
|
2819
2888
|
function isThinkingModeModel(model2) {
|
|
@@ -3006,6 +3075,19 @@ function healLoadedMessages(messages, maxChars) {
|
|
|
3006
3075
|
const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
|
|
3007
3076
|
return { messages: paired.messages, healedCount, healedFrom: shrunk.healedFrom };
|
|
3008
3077
|
}
|
|
3078
|
+
function stampMissingReasoningForThinkingMode(messages, model2) {
|
|
3079
|
+
if (!isThinkingModeModel(model2)) {
|
|
3080
|
+
return { messages, stampedCount: 0 };
|
|
3081
|
+
}
|
|
3082
|
+
let stampedCount = 0;
|
|
3083
|
+
const out = messages.map((msg) => {
|
|
3084
|
+
if (msg.role !== "assistant") return msg;
|
|
3085
|
+
if (Object.hasOwn(msg, "reasoning_content")) return msg;
|
|
3086
|
+
stampedCount += 1;
|
|
3087
|
+
return { ...msg, reasoning_content: "" };
|
|
3088
|
+
});
|
|
3089
|
+
return { messages: out, stampedCount };
|
|
3090
|
+
}
|
|
3009
3091
|
function healLoadedMessagesByTokens(messages, maxTokens) {
|
|
3010
3092
|
const shrunk = shrinkOversizedToolResultsByTokens(messages, maxTokens);
|
|
3011
3093
|
const paired = fixToolCallPairing(shrunk.messages);
|
|
@@ -3963,28 +4045,233 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
3963
4045
|
return registry;
|
|
3964
4046
|
}
|
|
3965
4047
|
|
|
3966
|
-
// src/tools/
|
|
4048
|
+
// src/tools/choice.ts
|
|
4049
|
+
var ChoiceRequestedError = class extends Error {
|
|
4050
|
+
question;
|
|
4051
|
+
options;
|
|
4052
|
+
allowCustom;
|
|
4053
|
+
constructor(question, options, allowCustom) {
|
|
4054
|
+
super(
|
|
4055
|
+
"ChoiceRequestedError: choice submitted. STOP calling tools now \u2014 the TUI has shown the options to the user. Wait for their next message; it will either be 'user picked <id>' (carry on with that branch), 'user answered: <text>' (custom free-form reply; read and proceed), or 'user cancelled the choice' (drop the question and ask what they want instead). Don't call any tools in the meantime."
|
|
4056
|
+
);
|
|
4057
|
+
this.name = "ChoiceRequestedError";
|
|
4058
|
+
this.question = question;
|
|
4059
|
+
this.options = options;
|
|
4060
|
+
this.allowCustom = allowCustom;
|
|
4061
|
+
}
|
|
4062
|
+
toToolResult() {
|
|
4063
|
+
return {
|
|
4064
|
+
error: `${this.name}: ${this.message}`,
|
|
4065
|
+
question: this.question,
|
|
4066
|
+
options: this.options,
|
|
4067
|
+
allowCustom: this.allowCustom
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
};
|
|
4071
|
+
function sanitizeOptions(raw) {
|
|
4072
|
+
if (!Array.isArray(raw)) return [];
|
|
4073
|
+
const out = [];
|
|
4074
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4075
|
+
for (const entry of raw) {
|
|
4076
|
+
if (!entry || typeof entry !== "object") continue;
|
|
4077
|
+
const e = entry;
|
|
4078
|
+
const id = typeof e.id === "string" ? e.id.trim() : "";
|
|
4079
|
+
const title = typeof e.title === "string" ? e.title.trim() : "";
|
|
4080
|
+
if (!id || !title) continue;
|
|
4081
|
+
if (seen.has(id)) continue;
|
|
4082
|
+
seen.add(id);
|
|
4083
|
+
const summary = typeof e.summary === "string" ? e.summary.trim() || void 0 : void 0;
|
|
4084
|
+
const opt = { id, title };
|
|
4085
|
+
if (summary) opt.summary = summary;
|
|
4086
|
+
out.push(opt);
|
|
4087
|
+
}
|
|
4088
|
+
return out;
|
|
4089
|
+
}
|
|
4090
|
+
function registerChoiceTool(registry, opts = {}) {
|
|
4091
|
+
registry.register({
|
|
4092
|
+
name: "ask_choice",
|
|
4093
|
+
description: "Present 2\u20136 alternatives to the user. The principle: if the user is supposed to pick, the tool picks \u2014 you don't enumerate the choices as prose. Prose menus have no picker in this TUI, so the user gets a wall of text to scroll through and a letter to type, strictly worse than the magenta picker this tool renders. Call it whenever (a) the user has asked for options, (b) you've analyzed multiple approaches and the final call is theirs, or (c) it's a preference fork you can't resolve without them. Skip it when one option is clearly best (just do it, or submit_plan) or a free-form text answer fits (ask in prose). Keep option ids short and stable (A/B/C). Each option: title + optional summary. allowCustom=true when their real answer might not fit. Max 6 options \u2014 narrow first if more. A one-sentence lead-in before the call is fine; don't repeat the options in it.",
|
|
4094
|
+
readOnly: true,
|
|
4095
|
+
parameters: {
|
|
4096
|
+
type: "object",
|
|
4097
|
+
properties: {
|
|
4098
|
+
question: {
|
|
4099
|
+
type: "string",
|
|
4100
|
+
description: "The question to put in front of the user. One sentence. Don't repeat the options in the question text \u2014 the picker renders them separately."
|
|
4101
|
+
},
|
|
4102
|
+
options: {
|
|
4103
|
+
type: "array",
|
|
4104
|
+
description: "2\u20134 alternatives. Each needs a stable id and a short title; summary is optional.",
|
|
4105
|
+
items: {
|
|
4106
|
+
type: "object",
|
|
4107
|
+
properties: {
|
|
4108
|
+
id: { type: "string", description: "Short stable id (A, B, C, or option-1)." },
|
|
4109
|
+
title: { type: "string", description: "One-line title shown as the option label." },
|
|
4110
|
+
summary: {
|
|
4111
|
+
type: "string",
|
|
4112
|
+
description: "Optional. A second dimmed line with more detail. Keep under ~80 chars."
|
|
4113
|
+
}
|
|
4114
|
+
},
|
|
4115
|
+
required: ["id", "title"]
|
|
4116
|
+
}
|
|
4117
|
+
},
|
|
4118
|
+
allowCustom: {
|
|
4119
|
+
type: "boolean",
|
|
4120
|
+
description: "If true, the picker shows a 'Let me type my own answer' escape hatch. Default false. Turn on when the user's real answer might not fit any of your pre-defined options."
|
|
4121
|
+
}
|
|
4122
|
+
},
|
|
4123
|
+
required: ["question", "options"]
|
|
4124
|
+
},
|
|
4125
|
+
fn: async (args) => {
|
|
4126
|
+
const question = (args?.question ?? "").trim();
|
|
4127
|
+
if (!question) {
|
|
4128
|
+
throw new Error(
|
|
4129
|
+
"ask_choice: question is required \u2014 write one sentence explaining the decision."
|
|
4130
|
+
);
|
|
4131
|
+
}
|
|
4132
|
+
const options = sanitizeOptions(args?.options);
|
|
4133
|
+
if (options.length < 2) {
|
|
4134
|
+
throw new Error(
|
|
4135
|
+
"ask_choice: need at least 2 well-formed options (each with a non-empty id and title). If you just need a text answer, ask the user in plain assistant text instead."
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
4138
|
+
if (options.length > 6) {
|
|
4139
|
+
throw new Error(
|
|
4140
|
+
"ask_choice: too many options (max 6). If you really have this many branches, split into two sequential ask_choice calls or narrow down first."
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
const allowCustom = args?.allowCustom === true;
|
|
4144
|
+
opts.onChoiceRequested?.(question, options);
|
|
4145
|
+
throw new ChoiceRequestedError(question, options, allowCustom);
|
|
4146
|
+
}
|
|
4147
|
+
});
|
|
4148
|
+
return registry;
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
// src/tools/plan-errors.ts
|
|
3967
4152
|
var PlanProposedError = class extends Error {
|
|
3968
4153
|
plan;
|
|
3969
|
-
|
|
4154
|
+
steps;
|
|
4155
|
+
summary;
|
|
4156
|
+
constructor(plan2, steps, summary) {
|
|
3970
4157
|
super(
|
|
3971
4158
|
"PlanProposedError: plan submitted. STOP calling tools now \u2014 the TUI has shown the plan to the user. Wait for their next message; it will either approve (you'll then implement the plan), request a refinement (you should explore more and submit an updated plan), or cancel (drop the plan and ask what they want instead). Don't call any tools in the meantime."
|
|
3972
4159
|
);
|
|
3973
4160
|
this.name = "PlanProposedError";
|
|
3974
4161
|
this.plan = plan2;
|
|
4162
|
+
this.steps = steps;
|
|
4163
|
+
this.summary = summary;
|
|
3975
4164
|
}
|
|
3976
4165
|
/**
|
|
3977
4166
|
* Structured tool-result shape. Consumed by the TUI to extract the
|
|
3978
|
-
* plan without regex-scraping the error message.
|
|
4167
|
+
* plan without regex-scraping the error message. Optional fields
|
|
4168
|
+
* are omitted from the payload when absent so consumers don't see
|
|
4169
|
+
* `undefined` keys in the JSON.
|
|
3979
4170
|
*/
|
|
3980
4171
|
toToolResult() {
|
|
3981
|
-
|
|
4172
|
+
const payload = {
|
|
4173
|
+
error: `${this.name}: ${this.message}`,
|
|
4174
|
+
plan: this.plan
|
|
4175
|
+
};
|
|
4176
|
+
if (this.steps && this.steps.length > 0) payload.steps = this.steps;
|
|
4177
|
+
if (this.summary) payload.summary = this.summary;
|
|
4178
|
+
return payload;
|
|
3982
4179
|
}
|
|
3983
4180
|
};
|
|
3984
|
-
|
|
4181
|
+
var PlanCheckpointError = class extends Error {
|
|
4182
|
+
stepId;
|
|
4183
|
+
title;
|
|
4184
|
+
result;
|
|
4185
|
+
notes;
|
|
4186
|
+
constructor(update2) {
|
|
4187
|
+
super(
|
|
4188
|
+
"PlanCheckpointError: step complete \u2014 STOP calling tools. The TUI has paused the plan for user review. Wait for the next user message; it will either say continue (proceed to the next step), request a revision (adjust the remaining plan), or stop (summarize and end)."
|
|
4189
|
+
);
|
|
4190
|
+
this.name = "PlanCheckpointError";
|
|
4191
|
+
this.stepId = update2.stepId;
|
|
4192
|
+
this.title = update2.title;
|
|
4193
|
+
this.result = update2.result;
|
|
4194
|
+
this.notes = update2.notes;
|
|
4195
|
+
}
|
|
4196
|
+
toToolResult() {
|
|
4197
|
+
const payload = {
|
|
4198
|
+
error: `${this.name}: ${this.message}`,
|
|
4199
|
+
kind: "step_completed",
|
|
4200
|
+
stepId: this.stepId,
|
|
4201
|
+
result: this.result
|
|
4202
|
+
};
|
|
4203
|
+
if (this.title) payload.title = this.title;
|
|
4204
|
+
if (this.notes) payload.notes = this.notes;
|
|
4205
|
+
return payload;
|
|
4206
|
+
}
|
|
4207
|
+
};
|
|
4208
|
+
var PlanRevisionProposedError = class extends Error {
|
|
4209
|
+
reason;
|
|
4210
|
+
remainingSteps;
|
|
4211
|
+
summary;
|
|
4212
|
+
constructor(reason, remainingSteps, summary) {
|
|
4213
|
+
super(
|
|
4214
|
+
"PlanRevisionProposedError: revision submitted. STOP calling tools now \u2014 the TUI has paused for the user to review your proposed change. Wait for their next message; it will say 'revision accepted' (proceed with the new step list), 'revision rejected' (keep the original plan and continue), or 'revision cancelled' (drop the proposal entirely). Don't call any tools in the meantime."
|
|
4215
|
+
);
|
|
4216
|
+
this.name = "PlanRevisionProposedError";
|
|
4217
|
+
this.reason = reason;
|
|
4218
|
+
this.remainingSteps = remainingSteps;
|
|
4219
|
+
this.summary = summary;
|
|
4220
|
+
}
|
|
4221
|
+
toToolResult() {
|
|
4222
|
+
const payload = {
|
|
4223
|
+
error: `${this.name}: ${this.message}`,
|
|
4224
|
+
reason: this.reason,
|
|
4225
|
+
remainingSteps: this.remainingSteps
|
|
4226
|
+
};
|
|
4227
|
+
if (this.summary) payload.summary = this.summary;
|
|
4228
|
+
return payload;
|
|
4229
|
+
}
|
|
4230
|
+
};
|
|
4231
|
+
|
|
4232
|
+
// src/tools/plan-core.ts
|
|
4233
|
+
var SUBMIT_PLAN_DESCRIPTION = "Submit ONE concrete plan you've already decided on. Use this for tasks that warrant a review gate \u2014 multi-file refactors, architecture changes, anything that would be expensive or confusing to undo. Skip it for small fixes (one-line typo, obvious bug with a clear fix) \u2014 just make the change. The user will either approve (you then implement it), ask for refinement, or cancel. If the user has already enabled /plan mode, writes are blocked at dispatch and you MUST use this. CRITICAL: do NOT use submit_plan to present alternative routes (A/B/C, option 1/2/3) for the user to pick from \u2014 the picker only exposes approve/refine/cancel, so a menu plan strands the user with no way to choose. For branching decisions, call `ask_choice` instead; only call submit_plan once the user has picked a direction and you have a single actionable plan. Write the plan as markdown with a one-line summary, a bulleted list of files to touch and what will change, and any risks or open questions. STRONGLY PREFERRED: pass `steps` \u2014 an array of {id, title, action, risk?} \u2014 so the UI renders a structured step list above the approval picker and tracks per-step progress. Use risk='high' for steps that touch prod data / break public APIs / are hard to undo; 'med' for non-trivial but reversible (multi-file edits, schema tweaks); 'low' for safe local work. After each step, call `mark_step_complete` so the user sees progress ticks.";
|
|
4234
|
+
var MARK_STEP_COMPLETE_DESCRIPTION = "Mark one step of the approved plan as done AND pause for the user to review. Call this after finishing each step. The TUI shows a \u2713 progress row and mounts a Continue / Revise / Stop picker \u2014 you MUST stop calling tools after this fires and wait for the next user message. Pass the `stepId` from the plan's steps array, a short `result` (what you did), and optional `notes` for anything surprising (errors, scope changes, follow-ups). This tool doesn't change any files. Don't call it if the plan didn't include structured steps, and don't invent ids that weren't in the original plan.";
|
|
4235
|
+
var REVISE_PLAN_DESCRIPTION = "Surgically replace the REMAINING steps of an in-flight plan. Call this when the user has given feedback at a checkpoint that warrants a structured plan change \u2014 skip a step, swap two steps, add a new step, change risk, etc. Pass: `reason` (one sentence why), `remainingSteps` (the new tail of the plan, replacing whatever steps haven't been done yet), and optional `summary` (updated one-line plan summary). Done steps are NEVER touched \u2014 keep them out of `remainingSteps`. The TUI shows a diff (removed in red, kept in gray, added in green) and the user accepts or rejects. Don't call this for trivial mid-step adjustments \u2014 just keep executing. Don't call submit_plan for revisions either \u2014 that resets the whole plan including completed steps. Use submit_plan only when the entire approach has changed; use revise_plan when the tail needs editing.";
|
|
4236
|
+
var STEP_ITEM_SCHEMA = {
|
|
4237
|
+
type: "object",
|
|
4238
|
+
properties: {
|
|
4239
|
+
id: { type: "string", description: "Stable id, e.g. step-1." },
|
|
4240
|
+
title: { type: "string", description: "Short imperative title." },
|
|
4241
|
+
action: { type: "string", description: "One-sentence description of the concrete action." },
|
|
4242
|
+
risk: {
|
|
4243
|
+
type: "string",
|
|
4244
|
+
enum: ["low", "med", "high"],
|
|
4245
|
+
description: "Self-assessed risk. 'high' = hard-to-undo / touches prod / breaks API; 'med' = non-trivial but reversible; 'low' = safe local work. The UI shows a colored dot per step so the user knows where to focus review. Omit if you're unsure."
|
|
4246
|
+
}
|
|
4247
|
+
},
|
|
4248
|
+
required: ["id", "title", "action"]
|
|
4249
|
+
};
|
|
4250
|
+
function sanitizeRisk(raw) {
|
|
4251
|
+
if (raw === "low" || raw === "med" || raw === "high") return raw;
|
|
4252
|
+
return void 0;
|
|
4253
|
+
}
|
|
4254
|
+
function sanitizeSteps(raw) {
|
|
4255
|
+
if (!Array.isArray(raw)) return void 0;
|
|
4256
|
+
const steps = [];
|
|
4257
|
+
for (const entry of raw) {
|
|
4258
|
+
if (!entry || typeof entry !== "object") continue;
|
|
4259
|
+
const e = entry;
|
|
4260
|
+
const id = typeof e.id === "string" ? e.id.trim() : "";
|
|
4261
|
+
const title = typeof e.title === "string" ? e.title.trim() : "";
|
|
4262
|
+
const action = typeof e.action === "string" ? e.action.trim() : "";
|
|
4263
|
+
if (!id || !title || !action) continue;
|
|
4264
|
+
const step = { id, title, action };
|
|
4265
|
+
const risk = sanitizeRisk(e.risk);
|
|
4266
|
+
if (risk) step.risk = risk;
|
|
4267
|
+
steps.push(step);
|
|
4268
|
+
}
|
|
4269
|
+
return steps.length > 0 ? steps : void 0;
|
|
4270
|
+
}
|
|
4271
|
+
function registerSubmitPlan(registry, opts) {
|
|
3985
4272
|
registry.register({
|
|
3986
4273
|
name: "submit_plan",
|
|
3987
|
-
description:
|
|
4274
|
+
description: SUBMIT_PLAN_DESCRIPTION,
|
|
3988
4275
|
readOnly: true,
|
|
3989
4276
|
parameters: {
|
|
3990
4277
|
type: "object",
|
|
@@ -3992,6 +4279,15 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
3992
4279
|
plan: {
|
|
3993
4280
|
type: "string",
|
|
3994
4281
|
description: "Markdown-formatted plan. Lead with a one-sentence summary. Then a file-by-file breakdown of what you'll change and why. Flag any risks or open questions at the end so the user can weigh in before you start."
|
|
4282
|
+
},
|
|
4283
|
+
steps: {
|
|
4284
|
+
type: "array",
|
|
4285
|
+
description: "Structured step list (strongly recommended). When provided, the UI renders a compact step list above the approval picker AND tracks per-step progress via `mark_step_complete`. Use stable ids (step-1, step-2, ...). Skip only for tiny one-step plans where the markdown body is enough.",
|
|
4286
|
+
items: STEP_ITEM_SCHEMA
|
|
4287
|
+
},
|
|
4288
|
+
summary: {
|
|
4289
|
+
type: "string",
|
|
4290
|
+
description: "Optional. One-sentence human-friendly title for the plan, ~80 chars max. Surfaces in the PlanConfirm picker header and in /plans listings ('\u25B8 refactor auth into signed tokens \xB7 2/5 done'). Skip for trivial plans where the first line of the markdown body is already short and clear."
|
|
3995
4291
|
}
|
|
3996
4292
|
},
|
|
3997
4293
|
required: ["plan"]
|
|
@@ -4001,10 +4297,108 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
4001
4297
|
if (!plan2) {
|
|
4002
4298
|
throw new Error("submit_plan: empty plan \u2014 write a markdown plan and try again.");
|
|
4003
4299
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4300
|
+
const steps = sanitizeSteps(args?.steps);
|
|
4301
|
+
const summary = typeof args?.summary === "string" ? args.summary.trim() || void 0 : void 0;
|
|
4302
|
+
opts.onPlanSubmitted?.(plan2, steps);
|
|
4303
|
+
throw new PlanProposedError(plan2, steps, summary);
|
|
4304
|
+
}
|
|
4305
|
+
});
|
|
4306
|
+
}
|
|
4307
|
+
function registerMarkStepComplete(registry, opts) {
|
|
4308
|
+
registry.register({
|
|
4309
|
+
name: "mark_step_complete",
|
|
4310
|
+
description: MARK_STEP_COMPLETE_DESCRIPTION,
|
|
4311
|
+
readOnly: true,
|
|
4312
|
+
parameters: {
|
|
4313
|
+
type: "object",
|
|
4314
|
+
properties: {
|
|
4315
|
+
stepId: {
|
|
4316
|
+
type: "string",
|
|
4317
|
+
description: "The id of the step being marked complete. Must match one from submit_plan's steps array."
|
|
4318
|
+
},
|
|
4319
|
+
title: {
|
|
4320
|
+
type: "string",
|
|
4321
|
+
description: "Optional. The step's title, echoed back for the UI. If omitted, the UI falls back to the id."
|
|
4322
|
+
},
|
|
4323
|
+
result: {
|
|
4324
|
+
type: "string",
|
|
4325
|
+
description: "One-sentence summary of what was done for this step."
|
|
4326
|
+
},
|
|
4327
|
+
notes: {
|
|
4328
|
+
type: "string",
|
|
4329
|
+
description: "Optional. Anything surprising \u2014 blockers hit, assumptions revised, follow-ups for later steps."
|
|
4330
|
+
}
|
|
4331
|
+
},
|
|
4332
|
+
required: ["stepId", "result"]
|
|
4333
|
+
},
|
|
4334
|
+
fn: async (args) => {
|
|
4335
|
+
const stepId = (args?.stepId ?? "").trim();
|
|
4336
|
+
const result = (args?.result ?? "").trim();
|
|
4337
|
+
if (!stepId) {
|
|
4338
|
+
throw new Error("mark_step_complete: stepId is required.");
|
|
4339
|
+
}
|
|
4340
|
+
if (!result) {
|
|
4341
|
+
throw new Error(
|
|
4342
|
+
"mark_step_complete: result is required \u2014 say in one sentence what you did."
|
|
4343
|
+
);
|
|
4344
|
+
}
|
|
4345
|
+
const title = typeof args?.title === "string" ? args.title.trim() || void 0 : void 0;
|
|
4346
|
+
const notes = typeof args?.notes === "string" ? args.notes.trim() || void 0 : void 0;
|
|
4347
|
+
const update2 = { kind: "step_completed", stepId, result };
|
|
4348
|
+
if (title) update2.title = title;
|
|
4349
|
+
if (notes) update2.notes = notes;
|
|
4350
|
+
opts.onStepCompleted?.(update2);
|
|
4351
|
+
throw new PlanCheckpointError({ stepId, title, result, notes });
|
|
4352
|
+
}
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
function registerRevisePlan(registry, opts) {
|
|
4356
|
+
registry.register({
|
|
4357
|
+
name: "revise_plan",
|
|
4358
|
+
description: REVISE_PLAN_DESCRIPTION,
|
|
4359
|
+
readOnly: true,
|
|
4360
|
+
parameters: {
|
|
4361
|
+
type: "object",
|
|
4362
|
+
properties: {
|
|
4363
|
+
reason: {
|
|
4364
|
+
type: "string",
|
|
4365
|
+
description: "One sentence explaining why you're revising \u2014 what the user asked for, what changed your assessment."
|
|
4366
|
+
},
|
|
4367
|
+
remainingSteps: {
|
|
4368
|
+
type: "array",
|
|
4369
|
+
description: "The new tail of the plan \u2014 what should run from here on. Each entry: {id, title, action, risk?}. Use stable ids; reuse old ids when a step is just being adjusted, generate new ones for genuinely new steps.",
|
|
4370
|
+
items: STEP_ITEM_SCHEMA
|
|
4371
|
+
},
|
|
4372
|
+
summary: {
|
|
4373
|
+
type: "string",
|
|
4374
|
+
description: "Optional. Updated one-line plan summary if the overall framing has shifted."
|
|
4375
|
+
}
|
|
4376
|
+
},
|
|
4377
|
+
required: ["reason", "remainingSteps"]
|
|
4378
|
+
},
|
|
4379
|
+
fn: async (args) => {
|
|
4380
|
+
const reason = (args?.reason ?? "").trim();
|
|
4381
|
+
if (!reason) {
|
|
4382
|
+
throw new Error(
|
|
4383
|
+
"revise_plan: reason is required \u2014 write one sentence explaining the change."
|
|
4384
|
+
);
|
|
4385
|
+
}
|
|
4386
|
+
const remainingSteps = sanitizeSteps(args?.remainingSteps);
|
|
4387
|
+
if (!remainingSteps || remainingSteps.length === 0) {
|
|
4388
|
+
throw new Error(
|
|
4389
|
+
"revise_plan: remainingSteps must be a non-empty array of well-formed steps. If the user wants to STOP rather than continue, don't revise \u2014 the picker has its own Stop option."
|
|
4390
|
+
);
|
|
4391
|
+
}
|
|
4392
|
+
const summary = typeof args?.summary === "string" ? args.summary.trim() || void 0 : void 0;
|
|
4393
|
+
opts.onPlanRevisionProposed?.(reason, remainingSteps, summary);
|
|
4394
|
+
throw new PlanRevisionProposedError(reason, remainingSteps, summary);
|
|
4006
4395
|
}
|
|
4007
4396
|
});
|
|
4397
|
+
}
|
|
4398
|
+
function registerPlanTool(registry, opts = {}) {
|
|
4399
|
+
registerSubmitPlan(registry, opts);
|
|
4400
|
+
registerMarkStepComplete(registry, opts);
|
|
4401
|
+
registerRevisePlan(registry, opts);
|
|
4008
4402
|
return registry;
|
|
4009
4403
|
}
|
|
4010
4404
|
|
|
@@ -6700,13 +7094,13 @@ function formatLogSize(path = defaultUsageLogPath()) {
|
|
|
6700
7094
|
}
|
|
6701
7095
|
|
|
6702
7096
|
// src/cli/commands/chat.tsx
|
|
6703
|
-
import { existsSync as
|
|
7097
|
+
import { existsSync as existsSync12, statSync as statSync7 } from "fs";
|
|
6704
7098
|
import { render } from "ink";
|
|
6705
|
-
import
|
|
7099
|
+
import React23, { useState as useState12 } from "react";
|
|
6706
7100
|
|
|
6707
7101
|
// src/cli/ui/App.tsx
|
|
6708
|
-
import { Box as
|
|
6709
|
-
import
|
|
7102
|
+
import { Box as Box19, Static, useApp, useInput as useInput5 } from "ink";
|
|
7103
|
+
import React20, { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef5, useState as useState10 } from "react";
|
|
6710
7104
|
|
|
6711
7105
|
// src/code/pending-edits.ts
|
|
6712
7106
|
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
@@ -6760,6 +7154,166 @@ function clearPendingEdits(sessionName) {
|
|
|
6760
7154
|
}
|
|
6761
7155
|
}
|
|
6762
7156
|
|
|
7157
|
+
// src/code/plan-store.ts
|
|
7158
|
+
import {
|
|
7159
|
+
existsSync as existsSync10,
|
|
7160
|
+
mkdirSync as mkdirSync7,
|
|
7161
|
+
readFileSync as readFileSync12,
|
|
7162
|
+
readdirSync as readdirSync3,
|
|
7163
|
+
renameSync,
|
|
7164
|
+
statSync as statSync5,
|
|
7165
|
+
unlinkSync as unlinkSync4,
|
|
7166
|
+
writeFileSync as writeFileSync6
|
|
7167
|
+
} from "fs";
|
|
7168
|
+
import { dirname as dirname9, join as join10 } from "path";
|
|
7169
|
+
function planStatePath(sessionName) {
|
|
7170
|
+
return join10(sessionsDir(), `${sanitizeName(sessionName)}.plan.json`);
|
|
7171
|
+
}
|
|
7172
|
+
function loadPlanState(sessionName) {
|
|
7173
|
+
const path = planStatePath(sessionName);
|
|
7174
|
+
if (!existsSync10(path)) return null;
|
|
7175
|
+
try {
|
|
7176
|
+
const raw = readFileSync12(path, "utf8");
|
|
7177
|
+
const parsed = JSON.parse(raw);
|
|
7178
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
7179
|
+
if (parsed.version !== 1) return null;
|
|
7180
|
+
if (!Array.isArray(parsed.steps)) return null;
|
|
7181
|
+
if (!Array.isArray(parsed.completedStepIds)) return null;
|
|
7182
|
+
if (typeof parsed.updatedAt !== "string") return null;
|
|
7183
|
+
const steps = [];
|
|
7184
|
+
for (const s of parsed.steps) {
|
|
7185
|
+
if (!s || typeof s !== "object") continue;
|
|
7186
|
+
const e = s;
|
|
7187
|
+
if (typeof e.id !== "string" || !e.id) continue;
|
|
7188
|
+
if (typeof e.title !== "string" || !e.title) continue;
|
|
7189
|
+
if (typeof e.action !== "string" || !e.action) continue;
|
|
7190
|
+
const step = { id: e.id, title: e.title, action: e.action };
|
|
7191
|
+
if (e.risk === "low" || e.risk === "med" || e.risk === "high") step.risk = e.risk;
|
|
7192
|
+
steps.push(step);
|
|
7193
|
+
}
|
|
7194
|
+
if (steps.length === 0) return null;
|
|
7195
|
+
const completedStepIds = parsed.completedStepIds.filter(
|
|
7196
|
+
(id) => typeof id === "string" && id.length > 0
|
|
7197
|
+
);
|
|
7198
|
+
const out = {
|
|
7199
|
+
version: 1,
|
|
7200
|
+
steps,
|
|
7201
|
+
completedStepIds,
|
|
7202
|
+
updatedAt: parsed.updatedAt
|
|
7203
|
+
};
|
|
7204
|
+
if (typeof parsed.body === "string" && parsed.body) out.body = parsed.body;
|
|
7205
|
+
if (typeof parsed.summary === "string" && parsed.summary) out.summary = parsed.summary;
|
|
7206
|
+
return out;
|
|
7207
|
+
} catch {
|
|
7208
|
+
return null;
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
7212
|
+
const path = planStatePath(sessionName);
|
|
7213
|
+
try {
|
|
7214
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
7215
|
+
const state = {
|
|
7216
|
+
version: 1,
|
|
7217
|
+
steps,
|
|
7218
|
+
completedStepIds: [...completedStepIds],
|
|
7219
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7220
|
+
};
|
|
7221
|
+
if (extras?.body) state.body = extras.body;
|
|
7222
|
+
if (extras?.summary) state.summary = extras.summary;
|
|
7223
|
+
writeFileSync6(path, `${JSON.stringify(state, null, 2)}
|
|
7224
|
+
`, "utf8");
|
|
7225
|
+
} catch (err) {
|
|
7226
|
+
process.stderr.write(
|
|
7227
|
+
`\u25B8 plan-store: failed to save plan for "${sessionName}": ${err.message}
|
|
7228
|
+
`
|
|
7229
|
+
);
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
function clearPlanState(sessionName) {
|
|
7233
|
+
const path = planStatePath(sessionName);
|
|
7234
|
+
try {
|
|
7235
|
+
if (existsSync10(path)) unlinkSync4(path);
|
|
7236
|
+
} catch {
|
|
7237
|
+
}
|
|
7238
|
+
}
|
|
7239
|
+
function archivePlanState(sessionName) {
|
|
7240
|
+
const active = planStatePath(sessionName);
|
|
7241
|
+
if (!existsSync10(active)) return null;
|
|
7242
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7243
|
+
const suffix = Math.random().toString(36).slice(2, 6);
|
|
7244
|
+
const archive = join10(
|
|
7245
|
+
sessionsDir(),
|
|
7246
|
+
`${sanitizeName(sessionName)}.plan.${stamp}-${suffix}.done.json`
|
|
7247
|
+
);
|
|
7248
|
+
try {
|
|
7249
|
+
renameSync(active, archive);
|
|
7250
|
+
return archive;
|
|
7251
|
+
} catch (err) {
|
|
7252
|
+
process.stderr.write(
|
|
7253
|
+
`\u25B8 plan-store: failed to archive plan for "${sessionName}": ${err.message}
|
|
7254
|
+
`
|
|
7255
|
+
);
|
|
7256
|
+
return null;
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
function listPlanArchives(sessionName) {
|
|
7260
|
+
const dir = sessionsDir();
|
|
7261
|
+
if (!existsSync10(dir)) return [];
|
|
7262
|
+
const prefix = `${sanitizeName(sessionName)}.plan.`;
|
|
7263
|
+
const suffix = ".done.json";
|
|
7264
|
+
let entries;
|
|
7265
|
+
try {
|
|
7266
|
+
entries = readdirSync3(dir);
|
|
7267
|
+
} catch {
|
|
7268
|
+
return [];
|
|
7269
|
+
}
|
|
7270
|
+
const summaries = [];
|
|
7271
|
+
for (const name of entries) {
|
|
7272
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix)) continue;
|
|
7273
|
+
const full = join10(dir, name);
|
|
7274
|
+
try {
|
|
7275
|
+
const raw = readFileSync12(full, "utf8");
|
|
7276
|
+
const parsed = JSON.parse(raw);
|
|
7277
|
+
if (parsed.version !== 1) continue;
|
|
7278
|
+
if (!Array.isArray(parsed.steps) || parsed.steps.length === 0) continue;
|
|
7279
|
+
const steps = parsed.steps.filter(
|
|
7280
|
+
(s) => !!s && typeof s === "object" && typeof s.id === "string" && typeof s.title === "string" && typeof s.action === "string"
|
|
7281
|
+
);
|
|
7282
|
+
if (steps.length === 0) continue;
|
|
7283
|
+
const completedStepIds = Array.isArray(parsed.completedStepIds) ? parsed.completedStepIds.filter((id) => typeof id === "string" && !!id) : [];
|
|
7284
|
+
let completedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : "";
|
|
7285
|
+
if (!completedAt || Number.isNaN(Date.parse(completedAt))) {
|
|
7286
|
+
try {
|
|
7287
|
+
completedAt = statSync5(full).mtime.toISOString();
|
|
7288
|
+
} catch {
|
|
7289
|
+
completedAt = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
const entry = { path: full, completedAt, steps, completedStepIds };
|
|
7293
|
+
if (typeof parsed.body === "string" && parsed.body) entry.body = parsed.body;
|
|
7294
|
+
if (typeof parsed.summary === "string" && parsed.summary) entry.summary = parsed.summary;
|
|
7295
|
+
summaries.push(entry);
|
|
7296
|
+
} catch {
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7299
|
+
summaries.sort((a, b) => b.completedAt.localeCompare(a.completedAt));
|
|
7300
|
+
return summaries;
|
|
7301
|
+
}
|
|
7302
|
+
function relativeTime(updatedAt, now = Date.now()) {
|
|
7303
|
+
const t = Date.parse(updatedAt);
|
|
7304
|
+
if (Number.isNaN(t)) return updatedAt;
|
|
7305
|
+
const diffMs = Math.max(0, now - t);
|
|
7306
|
+
const sec = Math.floor(diffMs / 1e3);
|
|
7307
|
+
if (sec < 60) return `${sec}s ago`;
|
|
7308
|
+
const min = Math.floor(sec / 60);
|
|
7309
|
+
if (min < 60) return `${min}m ago`;
|
|
7310
|
+
const hr = Math.floor(min / 60);
|
|
7311
|
+
if (hr < 24) return `${hr}h ago`;
|
|
7312
|
+
const day = Math.floor(hr / 24);
|
|
7313
|
+
if (day < 7) return `${day}d ago`;
|
|
7314
|
+
return updatedAt.slice(0, 10);
|
|
7315
|
+
}
|
|
7316
|
+
|
|
6763
7317
|
// src/tools/skills.ts
|
|
6764
7318
|
function registerSkillTools(registry, opts = {}) {
|
|
6765
7319
|
const store = new SkillStore({
|
|
@@ -6869,51 +7423,195 @@ function FileRow({ path, isSelected }) {
|
|
|
6869
7423
|
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, marker, " ", base, dir ? ` ${dir}` : ""));
|
|
6870
7424
|
}
|
|
6871
7425
|
|
|
6872
|
-
// src/cli/ui/
|
|
6873
|
-
import { Box as
|
|
6874
|
-
import
|
|
7426
|
+
// src/cli/ui/ChoiceConfirm.tsx
|
|
7427
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
7428
|
+
import React3 from "react";
|
|
6875
7429
|
|
|
6876
|
-
// src/
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
}
|
|
6890
|
-
let trailing = 0;
|
|
6891
|
-
while (trailing < search.length - leading && trailing < replace.length - leading && search[search.length - 1 - trailing] === replace[replace.length - 1 - trailing]) {
|
|
6892
|
-
trailing++;
|
|
6893
|
-
}
|
|
6894
|
-
const searchMiddle = search.slice(leading, search.length - trailing);
|
|
6895
|
-
const replaceMiddle = replace.slice(leading, replace.length - trailing);
|
|
6896
|
-
const leadShown = search.slice(Math.max(0, leading - contextLines), leading);
|
|
6897
|
-
const leadHidden = leading - leadShown.length;
|
|
6898
|
-
const trailShown = search.slice(
|
|
6899
|
-
search.length - trailing,
|
|
6900
|
-
search.length - trailing + contextLines
|
|
7430
|
+
// src/cli/ui/Select.tsx
|
|
7431
|
+
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7432
|
+
import React2, { useState } from "react";
|
|
7433
|
+
function SingleSelect({
|
|
7434
|
+
items,
|
|
7435
|
+
initialValue,
|
|
7436
|
+
onSubmit,
|
|
7437
|
+
onCancel,
|
|
7438
|
+
footer
|
|
7439
|
+
}) {
|
|
7440
|
+
const initialIndex = Math.max(
|
|
7441
|
+
0,
|
|
7442
|
+
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
6901
7443
|
);
|
|
6902
|
-
const
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
}
|
|
6916
|
-
|
|
7444
|
+
const [index, setIndex] = useState(initialIndex === -1 ? 0 : initialIndex);
|
|
7445
|
+
useInput((_input, key) => {
|
|
7446
|
+
if (key.upArrow) {
|
|
7447
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
7448
|
+
} else if (key.downArrow) {
|
|
7449
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
7450
|
+
} else if (key.return) {
|
|
7451
|
+
const chosen = items[index];
|
|
7452
|
+
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
7453
|
+
} else if (key.escape && onCancel) {
|
|
7454
|
+
onCancel();
|
|
7455
|
+
}
|
|
7456
|
+
});
|
|
7457
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React2.createElement(
|
|
7458
|
+
SelectRow,
|
|
7459
|
+
{
|
|
7460
|
+
key: item.value,
|
|
7461
|
+
item,
|
|
7462
|
+
active: i === index,
|
|
7463
|
+
marker: i === index ? "\u25B8" : " "
|
|
7464
|
+
}
|
|
7465
|
+
)), footer ? /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, footer)) : null);
|
|
7466
|
+
}
|
|
7467
|
+
function MultiSelect({
|
|
7468
|
+
items,
|
|
7469
|
+
initialSelected = [],
|
|
7470
|
+
onSubmit,
|
|
7471
|
+
onCancel,
|
|
7472
|
+
footer
|
|
7473
|
+
}) {
|
|
7474
|
+
const [index, setIndex] = useState(() => {
|
|
7475
|
+
const first = items.findIndex((i) => !i.disabled);
|
|
7476
|
+
return first === -1 ? 0 : first;
|
|
7477
|
+
});
|
|
7478
|
+
const [selected, setSelected] = useState(new Set(initialSelected));
|
|
7479
|
+
useInput((input, key) => {
|
|
7480
|
+
if (key.upArrow) {
|
|
7481
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
7482
|
+
} else if (key.downArrow) {
|
|
7483
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
7484
|
+
} else if (input === " ") {
|
|
7485
|
+
const item = items[index];
|
|
7486
|
+
if (!item || item.disabled) return;
|
|
7487
|
+
setSelected((prev) => {
|
|
7488
|
+
const next = new Set(prev);
|
|
7489
|
+
if (next.has(item.value)) next.delete(item.value);
|
|
7490
|
+
else next.add(item.value);
|
|
7491
|
+
return next;
|
|
7492
|
+
});
|
|
7493
|
+
} else if (key.return) {
|
|
7494
|
+
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
7495
|
+
onSubmit(ordered);
|
|
7496
|
+
} else if (key.escape && onCancel) {
|
|
7497
|
+
onCancel();
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, items.map((item, i) => {
|
|
7501
|
+
const checked = selected.has(item.value);
|
|
7502
|
+
const marker = checked ? "[x]" : "[ ]";
|
|
7503
|
+
return /* @__PURE__ */ React2.createElement(
|
|
7504
|
+
SelectRow,
|
|
7505
|
+
{
|
|
7506
|
+
key: item.value,
|
|
7507
|
+
item,
|
|
7508
|
+
active: i === index,
|
|
7509
|
+
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
7510
|
+
}
|
|
7511
|
+
);
|
|
7512
|
+
}), footer ? /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, footer)) : null);
|
|
7513
|
+
}
|
|
7514
|
+
function SelectRow({
|
|
7515
|
+
item,
|
|
7516
|
+
active,
|
|
7517
|
+
marker
|
|
7518
|
+
}) {
|
|
7519
|
+
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
7520
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React2.createElement(Box2, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, item.hint)) : null);
|
|
7521
|
+
}
|
|
7522
|
+
function findNextEnabled(items, from, step) {
|
|
7523
|
+
if (items.length === 0) return 0;
|
|
7524
|
+
let i = from;
|
|
7525
|
+
for (let tries = 0; tries < items.length; tries++) {
|
|
7526
|
+
i = (i + step + items.length) % items.length;
|
|
7527
|
+
if (!items[i]?.disabled) return i;
|
|
7528
|
+
}
|
|
7529
|
+
return from;
|
|
7530
|
+
}
|
|
7531
|
+
|
|
7532
|
+
// src/cli/ui/ChoiceConfirm.tsx
|
|
7533
|
+
var CUSTOM_VALUE = "__custom__";
|
|
7534
|
+
var CANCEL_VALUE = "__cancel__";
|
|
7535
|
+
function ChoiceConfirmInner({ question, options, allowCustom, onChoose }) {
|
|
7536
|
+
const items = options.map((opt) => ({
|
|
7537
|
+
value: opt.id,
|
|
7538
|
+
label: `${opt.id} \xB7 ${opt.title}`,
|
|
7539
|
+
hint: opt.summary
|
|
7540
|
+
}));
|
|
7541
|
+
if (allowCustom) {
|
|
7542
|
+
items.push({
|
|
7543
|
+
value: CUSTOM_VALUE,
|
|
7544
|
+
label: "Let me type my own answer",
|
|
7545
|
+
hint: "None of the above fits \u2014 type a free-form reply. The model reads it verbatim."
|
|
7546
|
+
});
|
|
7547
|
+
}
|
|
7548
|
+
items.push({
|
|
7549
|
+
value: CANCEL_VALUE,
|
|
7550
|
+
label: "Cancel \u2014 drop the question",
|
|
7551
|
+
hint: "Model stops and asks what you want instead."
|
|
7552
|
+
});
|
|
7553
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "magenta" }, "\u{1F500} the model is asking you to pick")), /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, question)), /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(
|
|
7554
|
+
SingleSelect,
|
|
7555
|
+
{
|
|
7556
|
+
initialValue: options[0]?.id,
|
|
7557
|
+
items,
|
|
7558
|
+
onSubmit: (v) => {
|
|
7559
|
+
if (v === CUSTOM_VALUE) onChoose({ kind: "custom" });
|
|
7560
|
+
else if (v === CANCEL_VALUE) onChoose({ kind: "cancel" });
|
|
7561
|
+
else onChoose({ kind: "pick", optionId: v });
|
|
7562
|
+
},
|
|
7563
|
+
onCancel: () => onChoose({ kind: "cancel" }),
|
|
7564
|
+
footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] cancel"
|
|
7565
|
+
}
|
|
7566
|
+
)));
|
|
7567
|
+
}
|
|
7568
|
+
var ChoiceConfirm = React3.memo(ChoiceConfirmInner);
|
|
7569
|
+
|
|
7570
|
+
// src/cli/ui/EditConfirm.tsx
|
|
7571
|
+
import { Box as Box4, Text as Text4, useInput as useInput2, useStdout } from "ink";
|
|
7572
|
+
import React4, { useMemo, useState as useState2 } from "react";
|
|
7573
|
+
|
|
7574
|
+
// src/code/diff-preview.ts
|
|
7575
|
+
function formatEditBlockDiff(block, opts = {}) {
|
|
7576
|
+
const contextLines = Math.max(0, opts.contextLines ?? 2);
|
|
7577
|
+
const maxLines = Math.max(4, opts.maxLines ?? 20);
|
|
7578
|
+
const indent = opts.indent ?? " ";
|
|
7579
|
+
const search = block.search === "" ? [] : block.search.split("\n");
|
|
7580
|
+
const replace = block.replace.split("\n");
|
|
7581
|
+
if (search.length === 0) {
|
|
7582
|
+
return renderAllPlus(replace, indent, maxLines);
|
|
7583
|
+
}
|
|
7584
|
+
let leading = 0;
|
|
7585
|
+
while (leading < search.length && leading < replace.length && search[leading] === replace[leading]) {
|
|
7586
|
+
leading++;
|
|
7587
|
+
}
|
|
7588
|
+
let trailing = 0;
|
|
7589
|
+
while (trailing < search.length - leading && trailing < replace.length - leading && search[search.length - 1 - trailing] === replace[replace.length - 1 - trailing]) {
|
|
7590
|
+
trailing++;
|
|
7591
|
+
}
|
|
7592
|
+
const searchMiddle = search.slice(leading, search.length - trailing);
|
|
7593
|
+
const replaceMiddle = replace.slice(leading, replace.length - trailing);
|
|
7594
|
+
const leadShown = search.slice(Math.max(0, leading - contextLines), leading);
|
|
7595
|
+
const leadHidden = leading - leadShown.length;
|
|
7596
|
+
const trailShown = search.slice(
|
|
7597
|
+
search.length - trailing,
|
|
7598
|
+
search.length - trailing + contextLines
|
|
7599
|
+
);
|
|
7600
|
+
const trailHidden = trailing - trailShown.length;
|
|
7601
|
+
const out = [];
|
|
7602
|
+
if (leadHidden > 0) {
|
|
7603
|
+
out.push(`${indent} \u2026 ${leadHidden} unchanged line${leadHidden === 1 ? "" : "s"} above`);
|
|
7604
|
+
}
|
|
7605
|
+
for (const l of leadShown) out.push(`${indent} ${l}`);
|
|
7606
|
+
for (const l of searchMiddle) out.push(`${indent}- ${l}`);
|
|
7607
|
+
for (const l of replaceMiddle) out.push(`${indent}+ ${l}`);
|
|
7608
|
+
for (const l of trailShown) out.push(`${indent} ${l}`);
|
|
7609
|
+
if (trailHidden > 0) {
|
|
7610
|
+
out.push(`${indent} \u2026 ${trailHidden} unchanged line${trailHidden === 1 ? "" : "s"} below`);
|
|
7611
|
+
}
|
|
7612
|
+
return capLines(out, maxLines, indent);
|
|
7613
|
+
}
|
|
7614
|
+
function formatAllBlockDiffs(blocks, opts = {}) {
|
|
6917
7615
|
const out = [];
|
|
6918
7616
|
for (let i = 0; i < blocks.length; i++) {
|
|
6919
7617
|
const b = blocks[i];
|
|
@@ -6953,10 +7651,10 @@ function EditConfirm({ block, onChoose }) {
|
|
|
6953
7651
|
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
6954
7652
|
[block]
|
|
6955
7653
|
);
|
|
6956
|
-
const [scroll, setScroll] =
|
|
7654
|
+
const [scroll, setScroll] = useState2(0);
|
|
6957
7655
|
const maxScroll = Math.max(0, allLines.length - budget);
|
|
6958
7656
|
const effectiveScroll = Math.min(scroll, maxScroll);
|
|
6959
|
-
|
|
7657
|
+
useInput2((input, key) => {
|
|
6960
7658
|
if (key.return || input === "y") {
|
|
6961
7659
|
onChoose("apply");
|
|
6962
7660
|
return;
|
|
@@ -7007,18 +7705,18 @@ function EditConfirm({ block, onChoose }) {
|
|
|
7007
7705
|
const hiddenBelow = Math.max(0, allLines.length - effectiveScroll - budget);
|
|
7008
7706
|
const totalLines = allLines.length;
|
|
7009
7707
|
const showScrollHud = hiddenAbove + hiddenBelow > 0;
|
|
7010
|
-
return /* @__PURE__ */
|
|
7011
|
-
|
|
7708
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, "\u25B8 model wants to edit a file")), /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { 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__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { color: isNew ? "green" : "yellow", bold: true }, `[${tag}] `), /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, block.path), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, ` (-${removed} +${added} lines)`), showScrollHud ? /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, ` \xB7 viewing ${effectiveScroll + 1}-${effectiveScroll + visibleLines.length}/${totalLines}`) : null)), hiddenAbove > 0 ? /* @__PURE__ */ React4.createElement(
|
|
7709
|
+
Text4,
|
|
7012
7710
|
{
|
|
7013
7711
|
dimColor: true
|
|
7014
7712
|
},
|
|
7015
7713
|
` \u2191 ${hiddenAbove} line${hiddenAbove === 1 ? "" : "s"} above (\u2191/k or PgUp)`
|
|
7016
|
-
) : null, /* @__PURE__ */
|
|
7714
|
+
) : null, /* @__PURE__ */ React4.createElement(Box4, { marginTop: hiddenAbove > 0 ? 0 : 1, flexDirection: "column" }, visibleLines.map((line, i) => {
|
|
7017
7715
|
const trimmed = line.trimStart();
|
|
7018
7716
|
const color = trimmed.startsWith("+") ? "green" : trimmed.startsWith("-") ? "red" : void 0;
|
|
7019
7717
|
const dim = !color;
|
|
7020
|
-
return /* @__PURE__ */
|
|
7021
|
-
|
|
7718
|
+
return /* @__PURE__ */ React4.createElement(
|
|
7719
|
+
Text4,
|
|
7022
7720
|
{
|
|
7023
7721
|
key: `diff-${effectiveScroll}-${i}`,
|
|
7024
7722
|
color,
|
|
@@ -7026,22 +7724,22 @@ function EditConfirm({ block, onChoose }) {
|
|
|
7026
7724
|
},
|
|
7027
7725
|
line
|
|
7028
7726
|
);
|
|
7029
|
-
})), hiddenBelow > 0 ? /* @__PURE__ */
|
|
7030
|
-
|
|
7727
|
+
})), hiddenBelow > 0 ? /* @__PURE__ */ React4.createElement(
|
|
7728
|
+
Text4,
|
|
7031
7729
|
{
|
|
7032
7730
|
dimColor: true
|
|
7033
7731
|
},
|
|
7034
7732
|
` \u2193 ${hiddenBelow} line${hiddenBelow === 1 ? "" : "s"} below (\u2193/j or Space/PgDn)`
|
|
7035
|
-
) : null, /* @__PURE__ */
|
|
7733
|
+
) : null, /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "[", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan", bold: true }, "y"), "/Enter] apply \xB7 [", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan", bold: true }, "n"), "] reject \xB7 [", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan", bold: true }, "a"), "] apply rest \xB7 [", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan", bold: true }, "A"), "] flip AUTO \xB7 [", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan", bold: true }, "\u2191\u2193/Space"), "] scroll \xB7 [Esc] abort")));
|
|
7036
7734
|
}
|
|
7037
7735
|
|
|
7038
7736
|
// src/cli/ui/EventLog.tsx
|
|
7039
|
-
import { Box as
|
|
7040
|
-
import
|
|
7737
|
+
import { Box as Box8, Text as Text8, useStdout as useStdout2 } from "ink";
|
|
7738
|
+
import React9 from "react";
|
|
7041
7739
|
|
|
7042
7740
|
// src/cli/ui/PlanStateBlock.tsx
|
|
7043
|
-
import { Box as
|
|
7044
|
-
import
|
|
7741
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
7742
|
+
import React5 from "react";
|
|
7045
7743
|
function PlanStateBlock({ planState }) {
|
|
7046
7744
|
const fields = [];
|
|
7047
7745
|
if (planState.subgoals.length) fields.push(["subgoals", planState.subgoals, "cyan", false]);
|
|
@@ -7052,14 +7750,69 @@ function PlanStateBlock({ planState }) {
|
|
|
7052
7750
|
if (planState.rejectedPaths.length)
|
|
7053
7751
|
fields.push(["rejected", planState.rejectedPaths, "red", true]);
|
|
7054
7752
|
if (fields.length === 0) return null;
|
|
7055
|
-
return /* @__PURE__ */
|
|
7753
|
+
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color, dim]) => /* @__PURE__ */ React5.createElement(Text5, { key: label }, /* @__PURE__ */ React5.createElement(Text5, { color, bold: true, dimColor: dim }, "\u2039 ", label), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, ` (${items.length})`), /* @__PURE__ */ React5.createElement(Text5, null, `: ${items.join(" \xB7 ")}`))));
|
|
7056
7754
|
}
|
|
7057
7755
|
|
|
7756
|
+
// src/cli/ui/PlanStepList.tsx
|
|
7757
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
7758
|
+
import React6 from "react";
|
|
7759
|
+
function riskDots(risk) {
|
|
7760
|
+
switch (risk) {
|
|
7761
|
+
case "high":
|
|
7762
|
+
return { dots: "\u25CF\u25CF\u25CF", color: "red" };
|
|
7763
|
+
case "med":
|
|
7764
|
+
return { dots: "\u25CF\u25CF ", color: "yellow" };
|
|
7765
|
+
case "low":
|
|
7766
|
+
return { dots: "\u25CF ", color: "green" };
|
|
7767
|
+
default:
|
|
7768
|
+
return { dots: " ", color: "gray" };
|
|
7769
|
+
}
|
|
7770
|
+
}
|
|
7771
|
+
function statusGlyph(status2) {
|
|
7772
|
+
switch (status2) {
|
|
7773
|
+
case "done":
|
|
7774
|
+
return { glyph: "\u2713", color: "green" };
|
|
7775
|
+
case "running":
|
|
7776
|
+
return { glyph: "\u25B6", color: "cyan" };
|
|
7777
|
+
case "skipped":
|
|
7778
|
+
return { glyph: "\u2014", color: "gray" };
|
|
7779
|
+
default:
|
|
7780
|
+
return { glyph: " ", color: "yellow" };
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
function getStatus(stepId, statuses) {
|
|
7784
|
+
if (!statuses) return "pending";
|
|
7785
|
+
if (statuses instanceof Map) {
|
|
7786
|
+
return statuses.get(stepId) ?? "pending";
|
|
7787
|
+
}
|
|
7788
|
+
return statuses[stepId] ?? "pending";
|
|
7789
|
+
}
|
|
7790
|
+
function PlanStepListInner({ steps, statuses, focusStepId }) {
|
|
7791
|
+
if (steps.length === 0) return null;
|
|
7792
|
+
const hasAnyRisk = steps.some((s) => s.risk !== void 0);
|
|
7793
|
+
const doneCount = Array.from(
|
|
7794
|
+
{ length: steps.length },
|
|
7795
|
+
(_, i) => getStatus(steps[i].id, statuses)
|
|
7796
|
+
).filter((s) => s === "done").length;
|
|
7797
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, `${steps.length} step${steps.length === 1 ? "" : "s"}`, doneCount > 0 ? ` \xB7 ${doneCount} done` : "", hasAnyRisk ? " \xB7 risk: " : ""), hasAnyRisk ? /* @__PURE__ */ React6.createElement(RiskLegend, null) : null), /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", marginTop: 1 }, steps.map((step) => {
|
|
7798
|
+
const status2 = getStatus(step.id, statuses);
|
|
7799
|
+
const focus = focusStepId === step.id;
|
|
7800
|
+
const risk = riskDots(step.risk);
|
|
7801
|
+
const glyph = statusGlyph(status2);
|
|
7802
|
+
const titleDim = status2 === "done" || status2 === "skipped";
|
|
7803
|
+
return /* @__PURE__ */ React6.createElement(Box6, { key: step.id }, /* @__PURE__ */ React6.createElement(Text6, { color: focus ? "cyan" : "gray", bold: focus }, focus ? "\u203A " : " "), /* @__PURE__ */ React6.createElement(Text6, { color: risk.color, bold: true }, risk.dots), /* @__PURE__ */ React6.createElement(Text6, { color: glyph.color, bold: true }, ` ${glyph.glyph} `), /* @__PURE__ */ React6.createElement(Text6, { dimColor: titleDim }, `${step.id} \xB7 ${step.title}`));
|
|
7804
|
+
})));
|
|
7805
|
+
}
|
|
7806
|
+
function RiskLegend() {
|
|
7807
|
+
return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "\u25CF"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " low "), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "\u25CF\u25CF"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " med "), /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, "\u25CF\u25CF\u25CF"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " high"));
|
|
7808
|
+
}
|
|
7809
|
+
var PlanStepList = React6.memo(PlanStepListInner);
|
|
7810
|
+
|
|
7058
7811
|
// src/cli/ui/markdown.tsx
|
|
7059
|
-
import { readFileSync as
|
|
7060
|
-
import { isAbsolute as isAbsolute4, join as
|
|
7061
|
-
import { Box as
|
|
7062
|
-
import
|
|
7812
|
+
import { readFileSync as readFileSync13, statSync as statSync6 } from "fs";
|
|
7813
|
+
import { isAbsolute as isAbsolute4, join as join11 } from "path";
|
|
7814
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
7815
|
+
import React7 from "react";
|
|
7063
7816
|
var SUPERSCRIPT = {
|
|
7064
7817
|
"0": "\u2070",
|
|
7065
7818
|
"1": "\xB9",
|
|
@@ -7136,10 +7889,10 @@ function validateCitation(url, projectRoot) {
|
|
|
7136
7889
|
const parts = parseCitationUrl(url);
|
|
7137
7890
|
if (!parts || !parts.path) return { ok: false, reason: "empty path" };
|
|
7138
7891
|
const normalized = parts.path.replace(/^[/\\]+/, "");
|
|
7139
|
-
const fullPath = isAbsolute4(normalized) ? normalized :
|
|
7892
|
+
const fullPath = isAbsolute4(normalized) ? normalized : join11(projectRoot, normalized);
|
|
7140
7893
|
let stat;
|
|
7141
7894
|
try {
|
|
7142
|
-
stat =
|
|
7895
|
+
stat = statSync6(fullPath);
|
|
7143
7896
|
} catch {
|
|
7144
7897
|
return { ok: false, reason: "file not found" };
|
|
7145
7898
|
}
|
|
@@ -7147,7 +7900,7 @@ function validateCitation(url, projectRoot) {
|
|
|
7147
7900
|
if (parts.startLine === void 0) return { ok: true };
|
|
7148
7901
|
let lineCount;
|
|
7149
7902
|
try {
|
|
7150
|
-
lineCount =
|
|
7903
|
+
lineCount = readFileSync13(fullPath, "utf8").split("\n").length;
|
|
7151
7904
|
} catch {
|
|
7152
7905
|
return { ok: false, reason: "unreadable" };
|
|
7153
7906
|
}
|
|
@@ -7184,57 +7937,57 @@ function InlineMd({
|
|
|
7184
7937
|
for (const m of text.matchAll(INLINE_RE)) {
|
|
7185
7938
|
const start = m.index ?? 0;
|
|
7186
7939
|
if (start > last) {
|
|
7187
|
-
parts.push(/* @__PURE__ */
|
|
7940
|
+
parts.push(/* @__PURE__ */ React7.createElement(Text7, { key: `t${idx++}` }, text.slice(last, start)));
|
|
7188
7941
|
}
|
|
7189
7942
|
if (m[2] !== void 0 && m[3] !== void 0) {
|
|
7190
7943
|
const linkText = m[2];
|
|
7191
7944
|
const url = m[3];
|
|
7192
7945
|
if (isExternalUrl(url)) {
|
|
7193
7946
|
parts.push(
|
|
7194
|
-
/* @__PURE__ */
|
|
7947
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `l${idx++}`, color: "blue", underline: true }, linkText)
|
|
7195
7948
|
);
|
|
7196
7949
|
} else {
|
|
7197
7950
|
const status2 = citations?.get(url);
|
|
7198
7951
|
if (status2 && !status2.ok) {
|
|
7199
7952
|
parts.push(
|
|
7200
|
-
/* @__PURE__ */
|
|
7953
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `l${idx++}`, color: "red", strikethrough: true }, `${linkText} \u2717`)
|
|
7201
7954
|
);
|
|
7202
7955
|
} else {
|
|
7203
7956
|
parts.push(
|
|
7204
|
-
/* @__PURE__ */
|
|
7957
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `l${idx++}`, color: "cyan", underline: true }, linkText)
|
|
7205
7958
|
);
|
|
7206
7959
|
}
|
|
7207
7960
|
}
|
|
7208
7961
|
} else if (m[4] !== void 0) {
|
|
7209
7962
|
parts.push(
|
|
7210
|
-
/* @__PURE__ */
|
|
7963
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `b${idx++}`, bold: true }, m[4])
|
|
7211
7964
|
);
|
|
7212
7965
|
} else if (m[5] !== void 0) {
|
|
7213
7966
|
const stripped = m[5].replace(/^(\w+)\s+/, "");
|
|
7214
7967
|
parts.push(
|
|
7215
|
-
/* @__PURE__ */
|
|
7968
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `c${idx++}`, color: "yellow" }, stripped)
|
|
7216
7969
|
);
|
|
7217
7970
|
} else if (m[6] !== void 0) {
|
|
7218
7971
|
parts.push(
|
|
7219
|
-
/* @__PURE__ */
|
|
7972
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `c${idx++}`, color: "yellow" }, m[6])
|
|
7220
7973
|
);
|
|
7221
7974
|
} else if (m[7] !== void 0) {
|
|
7222
7975
|
parts.push(
|
|
7223
|
-
/* @__PURE__ */
|
|
7976
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `i${idx++}`, italic: true }, m[7])
|
|
7224
7977
|
);
|
|
7225
7978
|
}
|
|
7226
7979
|
last = start + m[0].length;
|
|
7227
7980
|
}
|
|
7228
7981
|
if (last < text.length) {
|
|
7229
|
-
parts.push(/* @__PURE__ */
|
|
7982
|
+
parts.push(/* @__PURE__ */ React7.createElement(Text7, { key: `t${idx++}` }, text.slice(last)));
|
|
7230
7983
|
}
|
|
7231
7984
|
if (padTo !== void 0) {
|
|
7232
7985
|
const seen = visibleWidth(text);
|
|
7233
7986
|
if (seen < padTo) {
|
|
7234
|
-
parts.push(/* @__PURE__ */
|
|
7987
|
+
parts.push(/* @__PURE__ */ React7.createElement(Text7, { key: `pad${idx++}` }, " ".repeat(padTo - seen)));
|
|
7235
7988
|
}
|
|
7236
7989
|
}
|
|
7237
|
-
return /* @__PURE__ */
|
|
7990
|
+
return /* @__PURE__ */ React7.createElement(Text7, null, parts);
|
|
7238
7991
|
}
|
|
7239
7992
|
function stripInlineMarkup(s) {
|
|
7240
7993
|
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");
|
|
@@ -7430,19 +8183,19 @@ function parseBlocks(raw) {
|
|
|
7430
8183
|
function BlockView({ block, citations }) {
|
|
7431
8184
|
switch (block.kind) {
|
|
7432
8185
|
case "heading":
|
|
7433
|
-
return /* @__PURE__ */
|
|
8186
|
+
return /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, /* @__PURE__ */ React7.createElement(InlineMd, { text: block.text, citations }));
|
|
7434
8187
|
case "paragraph":
|
|
7435
|
-
return /* @__PURE__ */
|
|
8188
|
+
return /* @__PURE__ */ React7.createElement(InlineMd, { text: block.text, citations });
|
|
7436
8189
|
case "bullet":
|
|
7437
|
-
return /* @__PURE__ */
|
|
8190
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React7.createElement(Box7, { key: `${i}-${item.slice(0, 24)}` }, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, block.ordered ? ` ${block.start + i}. ` : " \u2022 "), /* @__PURE__ */ React7.createElement(InlineMd, { text: item, citations }))));
|
|
7438
8191
|
case "code":
|
|
7439
|
-
return /* @__PURE__ */
|
|
8192
|
+
return /* @__PURE__ */ React7.createElement(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, block.text));
|
|
7440
8193
|
case "edit-block":
|
|
7441
|
-
return /* @__PURE__ */
|
|
8194
|
+
return /* @__PURE__ */ React7.createElement(EditBlockRow, { block });
|
|
7442
8195
|
case "table":
|
|
7443
|
-
return /* @__PURE__ */
|
|
8196
|
+
return /* @__PURE__ */ React7.createElement(TableBlockRow, { block, citations });
|
|
7444
8197
|
case "hr":
|
|
7445
|
-
return /* @__PURE__ */
|
|
8198
|
+
return /* @__PURE__ */ React7.createElement(Text7, { 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");
|
|
7446
8199
|
}
|
|
7447
8200
|
}
|
|
7448
8201
|
function splitTableRow(line) {
|
|
@@ -7460,14 +8213,14 @@ function TableBlockRow({ block, citations }) {
|
|
|
7460
8213
|
widths.push(Math.min(40, Math.max(3, ...cellLengths)));
|
|
7461
8214
|
}
|
|
7462
8215
|
const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u253C\u2500");
|
|
7463
|
-
return /* @__PURE__ */
|
|
8216
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box7, null, block.header.map((cell, ci) => (
|
|
7464
8217
|
// biome-ignore lint/suspicious/noArrayIndexKey: table columns never reorder — derived from a static header array
|
|
7465
|
-
/* @__PURE__ */
|
|
7466
|
-
))), /* @__PURE__ */
|
|
8218
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `h-${ci}`, bold: true, color: "cyan" }, /* @__PURE__ */ React7.createElement(InlineMd, { text: cell, padTo: widths[ci] ?? 3, citations }), ci < colCount - 1 ? " \u2502 " : "")
|
|
8219
|
+
))), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, separator), block.rows.map((row2, ri) => (
|
|
7467
8220
|
// biome-ignore lint/suspicious/noArrayIndexKey: table rows render in source order and don't reorder
|
|
7468
|
-
/* @__PURE__ */
|
|
8221
|
+
/* @__PURE__ */ React7.createElement(Box7, { key: `r-${ri}` }, Array.from({ length: colCount }).map((_, ci) => (
|
|
7469
8222
|
// biome-ignore lint/suspicious/noArrayIndexKey: same — column axis is fixed by the table shape
|
|
7470
|
-
/* @__PURE__ */
|
|
8223
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `c-${ri}-${ci}` }, /* @__PURE__ */ React7.createElement(InlineMd, { text: row2[ci] ?? "", padTo: widths[ci] ?? 3, citations }), ci < colCount - 1 ? " \u2502 " : "")
|
|
7471
8224
|
)))
|
|
7472
8225
|
)));
|
|
7473
8226
|
}
|
|
@@ -7487,48 +8240,169 @@ function EditBlockRow({ block }) {
|
|
|
7487
8240
|
const isNewFile = block.search.length === 0;
|
|
7488
8241
|
const searchLines = block.search.split("\n");
|
|
7489
8242
|
const replaceLines = block.replace.split("\n");
|
|
7490
|
-
return /* @__PURE__ */
|
|
8243
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, block.filename), isNewFile ? /* @__PURE__ */ React7.createElement(Text7, { color: "green", bold: true }, " (new file)") : null), isNewFile ? null : /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, searchLines.map((line, i) => /* @__PURE__ */ React7.createElement(Text7, { key: `s-${i}-${line.length}`, color: "red" }, `- ${line}`))), /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: isNewFile ? 1 : 0 }, replaceLines.map((line, i) => /* @__PURE__ */ React7.createElement(Text7, { key: `r-${i}-${line.length}`, color: "green" }, `+ ${line}`))));
|
|
7491
8244
|
}
|
|
7492
8245
|
function Markdown({ text, projectRoot }) {
|
|
7493
8246
|
const cleaned = stripMath(text);
|
|
7494
8247
|
const root = projectRoot ?? process.cwd();
|
|
7495
|
-
const citations =
|
|
7496
|
-
const blocks =
|
|
8248
|
+
const citations = React7.useMemo(() => collectCitations(cleaned, root), [cleaned, root]);
|
|
8249
|
+
const blocks = React7.useMemo(() => parseBlocks(cleaned), [cleaned]);
|
|
7497
8250
|
const broken = [];
|
|
7498
8251
|
for (const [url, status2] of citations) {
|
|
7499
8252
|
if (!status2.ok) broken.push({ url, reason: status2.reason });
|
|
7500
8253
|
}
|
|
7501
|
-
return /* @__PURE__ */
|
|
8254
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, blocks.map((b, i) => /* @__PURE__ */ React7.createElement(BlockView, { key: `${i}-${b.kind}`, block: b, citations })), broken.length > 0 ? /* @__PURE__ */ React7.createElement(BrokenCitationsBlock, { items: broken }) : null);
|
|
7502
8255
|
}
|
|
7503
8256
|
function BrokenCitationsBlock({ items }) {
|
|
7504
|
-
return /* @__PURE__ */
|
|
8257
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { 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) => (
|
|
7505
8258
|
// biome-ignore lint/suspicious/noArrayIndexKey: list is derived from a Map iteration order, stable per render
|
|
7506
|
-
/* @__PURE__ */
|
|
8259
|
+
/* @__PURE__ */ React7.createElement(Text7, { key: `bc-${i}`, color: "red" }, ` \u2717 ${b.url} \u2192 ${b.reason}`)
|
|
7507
8260
|
)));
|
|
7508
8261
|
}
|
|
7509
8262
|
|
|
7510
8263
|
// src/cli/ui/ticker.tsx
|
|
7511
|
-
import
|
|
8264
|
+
import React8, { createContext, useContext, useEffect, useState as useState3 } from "react";
|
|
7512
8265
|
var TICK_MS = 120;
|
|
7513
8266
|
var TickContext = createContext(0);
|
|
7514
8267
|
function TickerProvider({ children, disabled }) {
|
|
7515
|
-
const [tick, setTick] =
|
|
8268
|
+
const [tick, setTick] = useState3(0);
|
|
7516
8269
|
useEffect(() => {
|
|
7517
8270
|
if (disabled) return;
|
|
7518
8271
|
const id = setInterval(() => setTick((t) => t + 1), TICK_MS);
|
|
7519
8272
|
return () => clearInterval(id);
|
|
7520
8273
|
}, [disabled]);
|
|
7521
|
-
return /* @__PURE__ */
|
|
8274
|
+
return /* @__PURE__ */ React8.createElement(TickContext.Provider, { value: tick }, children);
|
|
7522
8275
|
}
|
|
7523
8276
|
function useTick() {
|
|
7524
8277
|
return useContext(TickContext);
|
|
7525
8278
|
}
|
|
7526
8279
|
function useElapsedSeconds() {
|
|
7527
|
-
const [start] =
|
|
8280
|
+
const [start] = useState3(() => Date.now());
|
|
7528
8281
|
useTick();
|
|
7529
8282
|
return Math.floor((Date.now() - start) / 1e3);
|
|
7530
8283
|
}
|
|
7531
8284
|
|
|
8285
|
+
// src/cli/ui/tool-summary.ts
|
|
8286
|
+
var MAX_SUMMARY_CHARS = 80;
|
|
8287
|
+
var TRAILING_ELLIPSIS = "\u2026";
|
|
8288
|
+
function clip(s, max) {
|
|
8289
|
+
if (s.length <= max) return s;
|
|
8290
|
+
return s.slice(0, Math.max(1, max - TRAILING_ELLIPSIS.length)) + TRAILING_ELLIPSIS;
|
|
8291
|
+
}
|
|
8292
|
+
function firstNonEmptyLine(text) {
|
|
8293
|
+
for (const line of text.split(/\r?\n/)) {
|
|
8294
|
+
const trimmed = line.trim();
|
|
8295
|
+
if (trimmed) return trimmed;
|
|
8296
|
+
}
|
|
8297
|
+
return "";
|
|
8298
|
+
}
|
|
8299
|
+
function formatDuration(ms) {
|
|
8300
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
8301
|
+
if (ms < 100) return `${Math.round(ms)}ms`;
|
|
8302
|
+
if (ms < 1e3) return `${(ms / 1e3).toFixed(1)}s`;
|
|
8303
|
+
if (ms < 1e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
8304
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
8305
|
+
const totalSec = Math.round(ms / 1e3);
|
|
8306
|
+
const m = Math.floor(totalSec / 60);
|
|
8307
|
+
const s = totalSec % 60;
|
|
8308
|
+
return s === 0 ? `${m}m` : `${m}m${s}s`;
|
|
8309
|
+
}
|
|
8310
|
+
function formatBytes(n) {
|
|
8311
|
+
if (n < 1e3) return `${n}B`;
|
|
8312
|
+
if (n < 1e6) return `${(n / 1e3).toFixed(1)}KB`;
|
|
8313
|
+
return `${(n / 1e6).toFixed(1)}MB`;
|
|
8314
|
+
}
|
|
8315
|
+
function formatLineCount(text) {
|
|
8316
|
+
const lines = text.split(/\r?\n/).length;
|
|
8317
|
+
return `${lines} line${lines === 1 ? "" : "s"}`;
|
|
8318
|
+
}
|
|
8319
|
+
function summarizeStructured(content) {
|
|
8320
|
+
const trimmed = content.trim();
|
|
8321
|
+
if (!trimmed.startsWith("{")) return null;
|
|
8322
|
+
try {
|
|
8323
|
+
const parsed = JSON.parse(trimmed);
|
|
8324
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
8325
|
+
const obj = parsed;
|
|
8326
|
+
if (typeof obj.error === "string") {
|
|
8327
|
+
const tag = obj.error.split(":", 1)[0]?.trim() ?? "error";
|
|
8328
|
+
const detail = obj.error.slice(tag.length + 1).trim();
|
|
8329
|
+
const summary = detail ? `${tag} \u2014 ${detail}` : tag;
|
|
8330
|
+
const isControlSignal = tag === "PlanProposedError" || tag === "PlanCheckpointError" || tag === "PlanRevisionProposedError" || tag === "ChoiceRequestedError" || tag === "NeedsConfirmationError";
|
|
8331
|
+
return { summary: clip(summary, MAX_SUMMARY_CHARS), isError: !isControlSignal };
|
|
8332
|
+
}
|
|
8333
|
+
if (obj.kind === "step_completed" && typeof obj.stepId === "string") {
|
|
8334
|
+
const result = typeof obj.result === "string" ? obj.result : "";
|
|
8335
|
+
return {
|
|
8336
|
+
summary: clip(`\u2713 ${obj.stepId}: ${result}`, MAX_SUMMARY_CHARS),
|
|
8337
|
+
isError: false
|
|
8338
|
+
};
|
|
8339
|
+
}
|
|
8340
|
+
return null;
|
|
8341
|
+
} catch {
|
|
8342
|
+
return null;
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
function summarizeKnownTool(toolName, content) {
|
|
8346
|
+
if (toolName === "read_file") {
|
|
8347
|
+
const lines = formatLineCount(content);
|
|
8348
|
+
const bytes = formatBytes(content.length);
|
|
8349
|
+
const head = clip(
|
|
8350
|
+
firstNonEmptyLine(content),
|
|
8351
|
+
MAX_SUMMARY_CHARS - lines.length - bytes.length - 8
|
|
8352
|
+
);
|
|
8353
|
+
return {
|
|
8354
|
+
summary: head ? `${head} \xB7 ${lines} \xB7 ${bytes}` : `${lines} \xB7 ${bytes}`,
|
|
8355
|
+
isError: false
|
|
8356
|
+
};
|
|
8357
|
+
}
|
|
8358
|
+
if (toolName === "list_directory" || toolName === "directory_tree") {
|
|
8359
|
+
const entries = content.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
8360
|
+
return { summary: `${entries} entr${entries === 1 ? "y" : "ies"}`, isError: false };
|
|
8361
|
+
}
|
|
8362
|
+
if (toolName === "search_files" || toolName === "search_content") {
|
|
8363
|
+
const matches = content.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
8364
|
+
if (matches === 0) return { summary: "no matches", isError: false };
|
|
8365
|
+
const first = firstNonEmptyLine(content);
|
|
8366
|
+
return {
|
|
8367
|
+
summary: clip(`${matches} match${matches === 1 ? "" : "es"} \xB7 ${first}`, MAX_SUMMARY_CHARS),
|
|
8368
|
+
isError: false
|
|
8369
|
+
};
|
|
8370
|
+
}
|
|
8371
|
+
if (toolName === "run_command" || toolName === "run_background") {
|
|
8372
|
+
const exitMatch = content.match(/exit (?:code )?(-?\d+)/i);
|
|
8373
|
+
const first = firstNonEmptyLine(content);
|
|
8374
|
+
if (exitMatch) {
|
|
8375
|
+
const code = exitMatch[1];
|
|
8376
|
+
const isError = code !== "0";
|
|
8377
|
+
return {
|
|
8378
|
+
summary: clip(`exit ${code} \xB7 ${first}`, MAX_SUMMARY_CHARS),
|
|
8379
|
+
isError
|
|
8380
|
+
};
|
|
8381
|
+
}
|
|
8382
|
+
return { summary: clip(first || "(no output)", MAX_SUMMARY_CHARS), isError: false };
|
|
8383
|
+
}
|
|
8384
|
+
return null;
|
|
8385
|
+
}
|
|
8386
|
+
function summarizeToolResult(toolName, content) {
|
|
8387
|
+
const isExplicitError = content.startsWith("ERROR:");
|
|
8388
|
+
if (isExplicitError) {
|
|
8389
|
+
const stripped = content.slice("ERROR:".length).trim();
|
|
8390
|
+
return { summary: clip(stripped || "(unknown error)", MAX_SUMMARY_CHARS), isError: true };
|
|
8391
|
+
}
|
|
8392
|
+
const structured = summarizeStructured(content);
|
|
8393
|
+
if (structured) return structured;
|
|
8394
|
+
const known = summarizeKnownTool(toolName, content);
|
|
8395
|
+
if (known) return known;
|
|
8396
|
+
const first = firstNonEmptyLine(content);
|
|
8397
|
+
if (!content.trim()) return { summary: "(empty)", isError: false };
|
|
8398
|
+
if (content.length <= MAX_SUMMARY_CHARS) {
|
|
8399
|
+
return { summary: clip(first, MAX_SUMMARY_CHARS), isError: false };
|
|
8400
|
+
}
|
|
8401
|
+
const sizeHint = formatBytes(content.length);
|
|
8402
|
+
const head = clip(first, MAX_SUMMARY_CHARS - sizeHint.length - 3);
|
|
8403
|
+
return { summary: `${head} \xB7 ${sizeHint}`, isError: false };
|
|
8404
|
+
}
|
|
8405
|
+
|
|
7532
8406
|
// src/cli/ui/EventLog.tsx
|
|
7533
8407
|
var ROLE_GLYPH = {
|
|
7534
8408
|
user: "\u25C7",
|
|
@@ -7544,53 +8418,98 @@ function RoleGlyph({
|
|
|
7544
8418
|
glyph,
|
|
7545
8419
|
color
|
|
7546
8420
|
}) {
|
|
7547
|
-
return /* @__PURE__ */
|
|
8421
|
+
return /* @__PURE__ */ React9.createElement(Text8, { color, bold: true }, glyph);
|
|
7548
8422
|
}
|
|
7549
|
-
var EventRow =
|
|
8423
|
+
var EventRow = React9.memo(function EventRow2({
|
|
7550
8424
|
event,
|
|
7551
8425
|
projectRoot
|
|
7552
8426
|
}) {
|
|
7553
8427
|
if (event.role === "user") {
|
|
7554
|
-
return /* @__PURE__ */
|
|
8428
|
+
return /* @__PURE__ */ React9.createElement(Box8, { marginTop: event.leadSeparator ? 1 : 0 }, /* @__PURE__ */ React9.createElement(RoleGlyph, { glyph: ROLE_GLYPH.user, color: "cyan" }), /* @__PURE__ */ React9.createElement(Text8, null, " ", event.text));
|
|
7555
8429
|
}
|
|
7556
8430
|
if (event.role === "assistant") {
|
|
7557
|
-
if (event.streaming) return /* @__PURE__ */
|
|
7558
|
-
return /* @__PURE__ */
|
|
8431
|
+
if (event.streaming) return /* @__PURE__ */ React9.createElement(StreamingAssistant, { event });
|
|
8432
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(RoleGlyph, { glyph: ROLE_GLYPH.assistant, color: "green" }), event.stats ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${event.stats.model}`) : null), /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", paddingLeft: 2, marginTop: 1 }, event.branch ? /* @__PURE__ */ React9.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React9.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React9.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React9.createElement(Markdown, { text: event.text, projectRoot }) : /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React9.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React9.createElement(Text8, { color: "magenta" }, event.repair) : null));
|
|
7559
8433
|
}
|
|
7560
8434
|
if (event.role === "tool") {
|
|
7561
|
-
const
|
|
7562
|
-
const
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
8435
|
+
const isExplicitError = event.text.startsWith("ERROR:");
|
|
8436
|
+
const isEditFile = (event.toolName === "edit_file" || event.toolName?.endsWith("_edit_file")) && !isExplicitError;
|
|
8437
|
+
if (isEditFile) {
|
|
8438
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(RoleGlyph, { glyph: ROLE_GLYPH.toolOk, color: "yellow" }), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", bold: true }, ` ${event.toolName ?? "?"}`), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", dimColor: true }, " \u2192")), /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", paddingLeft: 2, marginTop: 1 }, /* @__PURE__ */ React9.createElement(EditFileDiff, { text: event.text })));
|
|
8439
|
+
}
|
|
8440
|
+
const summary = summarizeToolResult(event.toolName ?? "?", event.text);
|
|
8441
|
+
const color = summary.isError ? "red" : "yellow";
|
|
8442
|
+
const glyph = summary.isError ? ROLE_GLYPH.toolErr : ROLE_GLYPH.toolOk;
|
|
8443
|
+
const marker = summary.isError ? "\u2717" : "\u2192";
|
|
8444
|
+
const durationLabel = event.durationMs !== void 0 && event.durationMs >= 100 ? ` (${formatDuration(event.durationMs)})` : "";
|
|
8445
|
+
const indexHint = event.toolIndex !== void 0 ? ` /tool ${event.toolIndex}` : "";
|
|
8446
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(RoleGlyph, { glyph, color }), /* @__PURE__ */ React9.createElement(Text8, { color, bold: true }, ` ${event.toolName ?? "?"}`), durationLabel ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, durationLabel) : null, /* @__PURE__ */ React9.createElement(Text8, { color, dimColor: true }, ` ${marker} `), /* @__PURE__ */ React9.createElement(Text8, { color: summary.isError ? "red" : void 0, dimColor: !summary.isError }, summary.summary), indexHint ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, indexHint) : null);
|
|
7567
8447
|
}
|
|
7568
8448
|
if (event.role === "error") {
|
|
7569
|
-
return /* @__PURE__ */
|
|
8449
|
+
return /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(RoleGlyph, { glyph: ROLE_GLYPH.error, color: "red" }), /* @__PURE__ */ React9.createElement(Text8, { color: "red" }, " ", event.text));
|
|
7570
8450
|
}
|
|
7571
8451
|
if (event.role === "info") {
|
|
7572
|
-
return /* @__PURE__ */
|
|
8452
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, event.text));
|
|
7573
8453
|
}
|
|
7574
8454
|
if (event.role === "plan") {
|
|
7575
|
-
return /* @__PURE__ */
|
|
8455
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "\u{1F4CB} plan proposed \u2014 pick a choice below")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Markdown, { text: event.text, projectRoot })));
|
|
8456
|
+
}
|
|
8457
|
+
if (event.role === "step-progress") {
|
|
8458
|
+
const sp = event.stepProgress;
|
|
8459
|
+
const counter = sp && sp.total > 0 ? ` (${sp.completed}/${sp.total})` : "";
|
|
8460
|
+
const label = sp?.title ? `${sp.stepId} \xB7 ${sp.title}` : sp?.stepId ?? "";
|
|
8461
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "green", bold: true }, "\u2713"), /* @__PURE__ */ React9.createElement(Text8, { color: "green" }, ` ${label}`), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, counter)), event.text ? /* @__PURE__ */ React9.createElement(Box8, { paddingLeft: 2 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, event.text)) : null, sp?.notes ? /* @__PURE__ */ React9.createElement(Box8, { paddingLeft: 2 }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", dimColor: true }, `note: ${sp.notes}`)) : null);
|
|
8462
|
+
}
|
|
8463
|
+
if (event.role === "plan-resumed") {
|
|
8464
|
+
const rp = event.resumedPlan;
|
|
8465
|
+
if (!rp || rp.steps.length === 0) return null;
|
|
8466
|
+
const total = rp.steps.length;
|
|
8467
|
+
const done = rp.completedStepIds.length;
|
|
8468
|
+
const completedSet = new Set(rp.completedStepIds);
|
|
8469
|
+
const statuses = new Map(
|
|
8470
|
+
rp.steps.map((s) => [
|
|
8471
|
+
s.id,
|
|
8472
|
+
completedSet.has(s.id) ? "done" : "pending"
|
|
8473
|
+
])
|
|
8474
|
+
);
|
|
8475
|
+
const nextStep = rp.steps.find((s) => !completedSet.has(s.id));
|
|
8476
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "\u25B8 resumed plan"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${done}/${total} done \xB7 last touched ${rp.relativeTime}`)), rp.summary ? /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "cyan" }, ` ${rp.summary}`)) : null), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(PlanStepList, { steps: rp.steps, statuses, focusStepId: nextStep?.id })));
|
|
8477
|
+
}
|
|
8478
|
+
if (event.role === "plan-replay") {
|
|
8479
|
+
const r = event.replayPlan;
|
|
8480
|
+
if (!r || r.steps.length === 0) return null;
|
|
8481
|
+
const total = r.steps.length;
|
|
8482
|
+
const completedSet = new Set(r.completedStepIds);
|
|
8483
|
+
const done = completedSet.size;
|
|
8484
|
+
const statuses = new Map(
|
|
8485
|
+
r.steps.map((s) => [s.id, completedSet.has(s.id) ? "done" : "pending"])
|
|
8486
|
+
);
|
|
8487
|
+
const navHint = r.total > 1 ? ` \xB7 ${r.index}/${r.total}` : "";
|
|
8488
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, dimColor: true }, "\u23EA replay"), /* @__PURE__ */ React9.createElement(
|
|
8489
|
+
Text8,
|
|
8490
|
+
{
|
|
8491
|
+
dimColor: true
|
|
8492
|
+
},
|
|
8493
|
+
` completed ${r.relativeTime} \xB7 ${done}/${total} done${navHint}`
|
|
8494
|
+
)), r.summary ? /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${r.summary}`)) : null, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${r.archiveBasename}`))), r.body ? /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Markdown, { text: r.body, projectRoot })) : null, /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(PlanStepList, { steps: r.steps, statuses })), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, r.total > 1 ? `(read-only \xB7 /replay ${r.index === 1 ? 2 : 1} for the ${r.index === 1 ? "next" : "newest"} archive)` : "(read-only \xB7 this is an archived plan)")));
|
|
7576
8495
|
}
|
|
7577
8496
|
if (event.role === "warning") {
|
|
7578
|
-
return /* @__PURE__ */
|
|
8497
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(RoleGlyph, { glyph: ROLE_GLYPH.warning, color: "yellow" }), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, " ", event.text));
|
|
7579
8498
|
}
|
|
7580
|
-
return /* @__PURE__ */
|
|
8499
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, null, event.text));
|
|
7581
8500
|
});
|
|
7582
8501
|
function EditFileDiff({ text }) {
|
|
7583
8502
|
const lines = text.split(/\r?\n/);
|
|
7584
8503
|
const [statusHeader, hunkHeader, ...body] = lines;
|
|
7585
|
-
return /* @__PURE__ */
|
|
8504
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${statusHeader ?? ""}`), hunkHeader !== void 0 ? /* @__PURE__ */ React9.createElement(Text8, { color: "cyan", bold: true }, hunkHeader) : null, body.map((line, i) => {
|
|
7586
8505
|
const key = `${i}-${line.slice(0, 32)}`;
|
|
7587
8506
|
if (line.startsWith("- ")) {
|
|
7588
|
-
return /* @__PURE__ */
|
|
8507
|
+
return /* @__PURE__ */ React9.createElement(Text8, { key, color: "red" }, line);
|
|
7589
8508
|
}
|
|
7590
8509
|
if (line.startsWith("+ ")) {
|
|
7591
|
-
return /* @__PURE__ */
|
|
8510
|
+
return /* @__PURE__ */ React9.createElement(Text8, { key, color: "green" }, line);
|
|
7592
8511
|
}
|
|
7593
|
-
return /* @__PURE__ */
|
|
8512
|
+
return /* @__PURE__ */ React9.createElement(Text8, { key, dimColor: true }, line);
|
|
7594
8513
|
}));
|
|
7595
8514
|
}
|
|
7596
8515
|
function BranchBlock({ branch: branch2 }) {
|
|
@@ -7599,33 +8518,33 @@ function BranchBlock({ branch: branch2 }) {
|
|
|
7599
8518
|
const t = (branch2.temperatures[i] ?? 0).toFixed(1);
|
|
7600
8519
|
return `${marker} #${i} T=${t} u=${u}`;
|
|
7601
8520
|
}).join(" ");
|
|
7602
|
-
return /* @__PURE__ */
|
|
8521
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, "\u2387 branched ", /* @__PURE__ */ React9.createElement(Text8, { bold: true }, branch2.budget), ` samples \u2192 picked #${branch2.chosenIndex} `, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, per)));
|
|
7603
8522
|
}
|
|
7604
8523
|
function ReasoningBlock({ reasoning }) {
|
|
7605
8524
|
const max = 260;
|
|
7606
8525
|
const flat = reasoning.replace(/\s+/g, " ").trim();
|
|
7607
8526
|
const preview = flat.length <= max ? flat : `\u2026 (+${flat.length - max} earlier chars) ${flat.slice(-max)}`;
|
|
7608
|
-
return /* @__PURE__ */
|
|
8527
|
+
return /* @__PURE__ */ React9.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "\u258F "), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true, italic: true }, "thinking ", preview));
|
|
7609
8528
|
}
|
|
7610
8529
|
function Elapsed() {
|
|
7611
8530
|
const s = useElapsedSeconds();
|
|
7612
8531
|
const mm = String(Math.floor(s / 60)).padStart(2, "0");
|
|
7613
8532
|
const ss = String(s % 60).padStart(2, "0");
|
|
7614
|
-
return /* @__PURE__ */
|
|
8533
|
+
return /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, `${mm}:${ss}`);
|
|
7615
8534
|
}
|
|
7616
8535
|
function PulsingAssistantGlyph() {
|
|
7617
8536
|
const tick = useTick();
|
|
7618
8537
|
const on = Math.floor(tick / 4) % 2 === 0;
|
|
7619
|
-
return /* @__PURE__ */
|
|
8538
|
+
return /* @__PURE__ */ React9.createElement(Text8, { color: "green", bold: true }, on ? ROLE_GLYPH.assistant : ROLE_GLYPH.assistantPulse);
|
|
7620
8539
|
}
|
|
7621
8540
|
function StreamingAssistant({ event }) {
|
|
7622
8541
|
if (event.branchProgress) {
|
|
7623
8542
|
const p = event.branchProgress;
|
|
7624
8543
|
if (p.completed === 0) {
|
|
7625
|
-
return /* @__PURE__ */
|
|
8544
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, " \u2387 launching ", p.total, " parallel samples (R1 thinking in parallel)\u2026 "), /* @__PURE__ */ React9.createElement(Elapsed, null)), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, " ", "spread across T=0.0/0.5/1.0 \xB7 reasoner typically takes 30-90s \u2014 this is normal"));
|
|
7626
8545
|
}
|
|
7627
8546
|
const pct2 = Math.round(p.completed / p.total * 100);
|
|
7628
|
-
return /* @__PURE__ */
|
|
8547
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, " \u2387 branching ", p.completed, "/", p.total, " (", pct2, "%) "), /* @__PURE__ */ React9.createElement(Elapsed, null)), /* @__PURE__ */ React9.createElement(Text8, { 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"));
|
|
7629
8548
|
}
|
|
7630
8549
|
const tail = lastLine(event.text, 140);
|
|
7631
8550
|
const reasoningTail = event.reasoning ? lastLine(event.reasoning, 120) : "";
|
|
@@ -7655,16 +8574,16 @@ function StreamingAssistant({ event }) {
|
|
|
7655
8574
|
label = parts.join(" \xB7 ");
|
|
7656
8575
|
labelColor = "green";
|
|
7657
8576
|
}
|
|
7658
|
-
return /* @__PURE__ */
|
|
8577
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React9.createElement(Text8, null, " "), /* @__PURE__ */ React9.createElement(Pulse, null), /* @__PURE__ */ React9.createElement(Text8, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React9.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "\u25B8 ", tail) : preFirstByte ? (
|
|
7659
8578
|
// Non-dim yellow: first-time users misread the dim version as
|
|
7660
8579
|
// "app frozen". The reassurance has to be VISIBLE to do its job.
|
|
7661
|
-
/* @__PURE__ */
|
|
7662
|
-
) : reasoningOnly ? /* @__PURE__ */
|
|
8580
|
+
/* @__PURE__ */ React9.createElement(Text8, { color: "yellow", italic: true }, " waiting for first byte \u2014 this is normal, typically 5-60s depending on model + load")
|
|
8581
|
+
) : reasoningOnly ? /* @__PURE__ */ React9.createElement(Text8, { 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__ */ React9.createElement(Text8, { color: "magenta", italic: true }, " tool-call arguments streaming \u2014 the model is about to dispatch a tool") : event.reasoning ? /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", italic: true }, " R1 still reasoning \u2014 body text or tool call arrives when thinking finishes") : null);
|
|
7663
8582
|
}
|
|
7664
8583
|
function Pulse() {
|
|
7665
8584
|
const tick = useTick();
|
|
7666
8585
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7667
|
-
return /* @__PURE__ */
|
|
8586
|
+
return /* @__PURE__ */ React9.createElement(Text8, { color: "cyan" }, frames[Math.floor(tick / 4) % frames.length]);
|
|
7668
8587
|
}
|
|
7669
8588
|
function formatToolCallIndex(tb) {
|
|
7670
8589
|
if (!tb || tb.index === void 0) return "";
|
|
@@ -7683,36 +8602,17 @@ function lastLine(s, maxChars) {
|
|
|
7683
8602
|
}
|
|
7684
8603
|
function StatsLine({ stats: stats2 }) {
|
|
7685
8604
|
const hit = (stats2.cacheHitRatio * 100).toFixed(1);
|
|
7686
|
-
return /* @__PURE__ */
|
|
7687
|
-
}
|
|
7688
|
-
function truncate2(s, max) {
|
|
7689
|
-
if (s.length <= max) return s;
|
|
7690
|
-
if (s.startsWith("ERROR:")) {
|
|
7691
|
-
const firstNl = s.indexOf("\n");
|
|
7692
|
-
const firstLine = firstNl === -1 ? s : s.slice(0, firstNl);
|
|
7693
|
-
if (firstLine.length >= max) {
|
|
7694
|
-
return `${firstLine.slice(0, max)}\u2026 (+${s.length - max} chars \u2014 /tool N for full)`;
|
|
7695
|
-
}
|
|
7696
|
-
const budget = max - firstLine.length - 10;
|
|
7697
|
-
const tail = s.slice(-budget);
|
|
7698
|
-
const skipped2 = s.length - firstLine.length - tail.length;
|
|
7699
|
-
return `${firstLine}
|
|
7700
|
-
\u2026 (+${skipped2} chars) \u2026
|
|
7701
|
-
${tail}`;
|
|
7702
|
-
}
|
|
7703
|
-
const skipped = s.length - max;
|
|
7704
|
-
return `\u2026 (+${skipped} earlier chars \u2014 /tool N for full) \u2026
|
|
7705
|
-
${s.slice(-max)}`;
|
|
8605
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "\u258F "), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "cache ", hit, "% \xB7 tokens ", stats2.usage.promptTokens, " \u2192 ", stats2.usage.completionTokens, " \xB7 $", stats2.cost.toFixed(6)));
|
|
7706
8606
|
}
|
|
7707
8607
|
|
|
7708
8608
|
// src/cli/ui/LiveRows.tsx
|
|
7709
|
-
import { Box as
|
|
7710
|
-
import
|
|
8609
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
8610
|
+
import React10 from "react";
|
|
7711
8611
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7712
8612
|
function StatusRow({ text }) {
|
|
7713
8613
|
const tick = useTick();
|
|
7714
8614
|
const elapsed = useElapsedSeconds();
|
|
7715
|
-
return /* @__PURE__ */
|
|
8615
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${elapsed}s`));
|
|
7716
8616
|
}
|
|
7717
8617
|
function ModeStatusBar({
|
|
7718
8618
|
editMode,
|
|
@@ -7724,15 +8624,15 @@ function ModeStatusBar({
|
|
|
7724
8624
|
}) {
|
|
7725
8625
|
useTick();
|
|
7726
8626
|
const running = jobs2?.runningCount() ?? 0;
|
|
7727
|
-
const jobsTag = running > 0 ? /* @__PURE__ */
|
|
8627
|
+
const jobsTag = running > 0 ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow", bold: true }, ` \xB7 \u23F5 ${running} job${running === 1 ? "" : "s"}`) : null;
|
|
7728
8628
|
if (planMode) {
|
|
7729
|
-
return /* @__PURE__ */
|
|
8629
|
+
return /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", bold: true, inverse: flash }, "\u25B8 PLAN"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " writes gated \u2014 submit_plan + approval required \xB7 /plan off to leave"), jobsTag);
|
|
7730
8630
|
}
|
|
7731
8631
|
const label = editMode === "auto" ? "AUTO" : "review";
|
|
7732
8632
|
const color = editMode === "auto" ? "magenta" : "cyan";
|
|
7733
8633
|
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";
|
|
7734
8634
|
const flip = editMode === "auto" ? "Shift+Tab \u2192 review" : "Shift+Tab \u2192 AUTO";
|
|
7735
|
-
return /* @__PURE__ */
|
|
8635
|
+
return /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color, bold: true, inverse: flash }, `\u25B8 ${label}`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${mid} \xB7 ${flip}`), jobsTag);
|
|
7736
8636
|
}
|
|
7737
8637
|
function UndoBanner({
|
|
7738
8638
|
banner
|
|
@@ -7742,14 +8642,14 @@ function UndoBanner({
|
|
|
7742
8642
|
const remainingSec = Math.ceil(remainingMs / 1e3);
|
|
7743
8643
|
const ok = banner.results.filter((r) => r.status === "applied" || r.status === "created").length;
|
|
7744
8644
|
const total = banner.results.length;
|
|
7745
|
-
return /* @__PURE__ */
|
|
8645
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1, borderStyle: "round", borderColor: "magenta", paddingX: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "magenta", bold: true }, "\u2713 auto-applied "), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, `${ok}/${total} edit${total === 1 ? "" : "s"}`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \xB7 press "), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta", bold: true }, "u"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " to undo ("), /* @__PURE__ */ React10.createElement(Text9, { color: remainingSec <= 1 ? "red" : "magenta" }, `${remainingSec}s`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ")"));
|
|
7746
8646
|
}
|
|
7747
8647
|
function SubagentRow({
|
|
7748
8648
|
activity
|
|
7749
8649
|
}) {
|
|
7750
8650
|
const tick = useTick();
|
|
7751
8651
|
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
7752
|
-
return /* @__PURE__ */
|
|
8652
|
+
return /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, ` \u232C subagent: ${activity.task}`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` \xB7 iter ${activity.iter} \xB7 ${seconds}s`));
|
|
7753
8653
|
}
|
|
7754
8654
|
function OngoingToolRow({
|
|
7755
8655
|
tool: tool2,
|
|
@@ -7758,7 +8658,7 @@ function OngoingToolRow({
|
|
|
7758
8658
|
const tick = useTick();
|
|
7759
8659
|
const elapsed = useElapsedSeconds();
|
|
7760
8660
|
const summary = summarizeToolArgs(tool2.name, tool2.args);
|
|
7761
|
-
return /* @__PURE__ */
|
|
8661
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, ` tool<${tool2.name}> running\u2026`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, summary)) : null);
|
|
7762
8662
|
}
|
|
7763
8663
|
function renderProgressLine(p) {
|
|
7764
8664
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -7813,116 +8713,71 @@ function summarizeToolArgs(name, args) {
|
|
|
7813
8713
|
return args.length > 80 ? `${args.slice(0, 80)}\u2026` : args;
|
|
7814
8714
|
}
|
|
7815
8715
|
|
|
7816
|
-
// src/cli/ui/
|
|
7817
|
-
import { Box as
|
|
7818
|
-
import
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
onCancel,
|
|
7828
|
-
footer
|
|
8716
|
+
// src/cli/ui/PlanCheckpointConfirm.tsx
|
|
8717
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
8718
|
+
import React11 from "react";
|
|
8719
|
+
function PlanCheckpointConfirmInner({
|
|
8720
|
+
stepId,
|
|
8721
|
+
title,
|
|
8722
|
+
completed,
|
|
8723
|
+
total,
|
|
8724
|
+
steps,
|
|
8725
|
+
completedStepIds,
|
|
8726
|
+
onChoose
|
|
7829
8727
|
}) {
|
|
7830
|
-
const
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
);
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
if (key.upArrow) {
|
|
7837
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
7838
|
-
} else if (key.downArrow) {
|
|
7839
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
7840
|
-
} else if (key.return) {
|
|
7841
|
-
const chosen = items[index];
|
|
7842
|
-
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
7843
|
-
} else if (key.escape && onCancel) {
|
|
7844
|
-
onCancel();
|
|
7845
|
-
}
|
|
7846
|
-
});
|
|
7847
|
-
return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React8.createElement(
|
|
7848
|
-
SelectRow,
|
|
8728
|
+
const label = title ? `${stepId} \xB7 ${title}` : stepId;
|
|
8729
|
+
const counter = total > 0 ? ` (${completed}/${total})` : "";
|
|
8730
|
+
const isLast = total > 0 && completed >= total;
|
|
8731
|
+
const statuses = buildStatusMap(steps, completedStepIds, stepId, isLast);
|
|
8732
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: "green" }, "\u25B8 checkpoint \u2014 step done"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ` ${label}${counter}`)), steps && steps.length > 0 ? /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React11.createElement(PlanStepList, { steps, statuses, focusStepId: stepId })) : null, /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(
|
|
8733
|
+
SingleSelect,
|
|
7849
8734
|
{
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
8735
|
+
initialValue: isLast ? "stop" : "continue",
|
|
8736
|
+
items: [
|
|
8737
|
+
{
|
|
8738
|
+
value: "continue",
|
|
8739
|
+
label: "Continue \u2014 run the next step",
|
|
8740
|
+
hint: "Model resumes with the next step. Use this when the result looks right and you don't need to tweak the remaining plan."
|
|
8741
|
+
},
|
|
8742
|
+
{
|
|
8743
|
+
value: "revise",
|
|
8744
|
+
label: "Revise \u2014 give feedback before the next step",
|
|
8745
|
+
hint: "Stay paused, type guidance (scope changes, skip steps, alternative approach). The model adjusts the remaining plan based on your message."
|
|
8746
|
+
},
|
|
8747
|
+
{
|
|
8748
|
+
value: "stop",
|
|
8749
|
+
label: "Stop \u2014 end the plan here",
|
|
8750
|
+
hint: "Model summarizes what was done and ends. Remaining steps are skipped."
|
|
8751
|
+
}
|
|
8752
|
+
],
|
|
8753
|
+
onSubmit: (v) => onChoose(v),
|
|
8754
|
+
onCancel: () => onChoose("stop"),
|
|
8755
|
+
footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] stop"
|
|
7854
8756
|
}
|
|
7855
|
-
))
|
|
8757
|
+
)));
|
|
7856
8758
|
}
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
return first === -1 ? 0 : first;
|
|
7867
|
-
});
|
|
7868
|
-
const [selected, setSelected] = useState3(new Set(initialSelected));
|
|
7869
|
-
useInput2((input, key) => {
|
|
7870
|
-
if (key.upArrow) {
|
|
7871
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
7872
|
-
} else if (key.downArrow) {
|
|
7873
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
7874
|
-
} else if (input === " ") {
|
|
7875
|
-
const item = items[index];
|
|
7876
|
-
if (!item || item.disabled) return;
|
|
7877
|
-
setSelected((prev) => {
|
|
7878
|
-
const next = new Set(prev);
|
|
7879
|
-
if (next.has(item.value)) next.delete(item.value);
|
|
7880
|
-
else next.add(item.value);
|
|
7881
|
-
return next;
|
|
7882
|
-
});
|
|
7883
|
-
} else if (key.return) {
|
|
7884
|
-
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
7885
|
-
onSubmit(ordered);
|
|
7886
|
-
} else if (key.escape && onCancel) {
|
|
7887
|
-
onCancel();
|
|
8759
|
+
var PlanCheckpointConfirm = React11.memo(PlanCheckpointConfirmInner);
|
|
8760
|
+
function buildStatusMap(steps, completedStepIds, currentStepId, isLast) {
|
|
8761
|
+
const map = /* @__PURE__ */ new Map();
|
|
8762
|
+
if (!steps) return map;
|
|
8763
|
+
for (const step of steps) {
|
|
8764
|
+
if (completedStepIds?.has(step.id) || step.id === currentStepId) {
|
|
8765
|
+
map.set(step.id, "done");
|
|
8766
|
+
} else {
|
|
8767
|
+
map.set(step.id, "pending");
|
|
7888
8768
|
}
|
|
7889
|
-
});
|
|
7890
|
-
return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column" }, items.map((item, i) => {
|
|
7891
|
-
const checked = selected.has(item.value);
|
|
7892
|
-
const marker = checked ? "[x]" : "[ ]";
|
|
7893
|
-
return /* @__PURE__ */ React8.createElement(
|
|
7894
|
-
SelectRow,
|
|
7895
|
-
{
|
|
7896
|
-
key: item.value,
|
|
7897
|
-
item,
|
|
7898
|
-
active: i === index,
|
|
7899
|
-
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
7900
|
-
}
|
|
7901
|
-
);
|
|
7902
|
-
}), footer ? /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, footer)) : null);
|
|
7903
|
-
}
|
|
7904
|
-
function SelectRow({
|
|
7905
|
-
item,
|
|
7906
|
-
active,
|
|
7907
|
-
marker
|
|
7908
|
-
}) {
|
|
7909
|
-
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
7910
|
-
return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React8.createElement(Box7, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, item.hint)) : null);
|
|
7911
|
-
}
|
|
7912
|
-
function findNextEnabled(items, from, step) {
|
|
7913
|
-
if (items.length === 0) return 0;
|
|
7914
|
-
let i = from;
|
|
7915
|
-
for (let tries = 0; tries < items.length; tries++) {
|
|
7916
|
-
i = (i + step + items.length) % items.length;
|
|
7917
|
-
if (!items[i]?.disabled) return i;
|
|
7918
8769
|
}
|
|
7919
|
-
|
|
8770
|
+
if (isLast) {
|
|
8771
|
+
}
|
|
8772
|
+
return map;
|
|
7920
8773
|
}
|
|
7921
8774
|
|
|
7922
8775
|
// src/cli/ui/PlanConfirm.tsx
|
|
7923
|
-
|
|
8776
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
8777
|
+
import React12 from "react";
|
|
8778
|
+
function PlanConfirmInner({ plan: plan2, steps, summary, onChoose }) {
|
|
7924
8779
|
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan2) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan2);
|
|
7925
|
-
return /* @__PURE__ */
|
|
8780
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, "\u25B8 plan proposed (full text above) \u2014 approve / refine / cancel")), summary ? /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, ` ${summary}`)) : null), hasOpenQuestions ? /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, "\u25B2 the plan flags open questions or risks \u2014 pick", " ", /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, steps && steps.length > 0 ? /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(PlanStepList, { steps })) : null, /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(
|
|
7926
8781
|
SingleSelect,
|
|
7927
8782
|
{
|
|
7928
8783
|
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
@@ -7949,11 +8804,11 @@ function PlanConfirmInner({ plan: plan2, onChoose }) {
|
|
|
7949
8804
|
}
|
|
7950
8805
|
)));
|
|
7951
8806
|
}
|
|
7952
|
-
var PlanConfirm =
|
|
8807
|
+
var PlanConfirm = React12.memo(PlanConfirmInner);
|
|
7953
8808
|
|
|
7954
8809
|
// src/cli/ui/PlanRefineInput.tsx
|
|
7955
|
-
import { Box as
|
|
7956
|
-
import
|
|
8810
|
+
import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
|
|
8811
|
+
import React13, { useState as useState4 } from "react";
|
|
7957
8812
|
function PlanRefineInput({ mode: mode2, onSubmit, onCancel }) {
|
|
7958
8813
|
const [value, setValue] = useState4("");
|
|
7959
8814
|
useInput3((input, key) => {
|
|
@@ -7973,15 +8828,84 @@ function PlanRefineInput({ mode: mode2, onSubmit, onCancel }) {
|
|
|
7973
8828
|
setValue((v) => v + input);
|
|
7974
8829
|
}
|
|
7975
8830
|
});
|
|
7976
|
-
const title = mode2 === "approve" ? "\u25B8 approving \u2014 any last instructions or answers to open questions?" : "\u25B8 refining \u2014 what should the model change?";
|
|
7977
|
-
const hint = mode2 === "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.";
|
|
7978
|
-
const blankHint = mode2 === "approve" ? " (Enter with blank = approve without extra instructions.)" : " (Enter with blank = ask the model to list concrete questions.)";
|
|
7979
|
-
return /* @__PURE__ */
|
|
8831
|
+
const title = mode2 === "approve" ? "\u25B8 approving \u2014 any last instructions or answers to open questions?" : mode2 === "checkpoint-revise" ? "\u25B8 revising \u2014 what should change before the next step?" : mode2 === "choice-custom" ? "\u25B8 custom answer \u2014 type whatever fits" : "\u25B8 refining \u2014 what should the model change?";
|
|
8832
|
+
const hint = mode2 === "approve" ? "Answer questions the plan raised, add constraints, or just press Enter to approve as-is." : mode2 === "checkpoint-revise" ? "Scope change, skip steps, alternative approach \u2014 the model will adjust the remaining plan based on this." : mode2 === "choice-custom" ? "Free-form reply. The model reads it verbatim and proceeds \u2014 no need to match the listed options." : "Describe what's wrong or missing, or answer questions the plan raised.";
|
|
8833
|
+
const blankHint = mode2 === "approve" ? " (Enter with blank = approve without extra instructions.)" : mode2 === "checkpoint-revise" ? " (Enter with blank = continue with the current plan.)" : mode2 === "choice-custom" ? " (Enter with blank = ask the model what you actually want.)" : " (Enter with blank = ask the model to list concrete questions.)";
|
|
8834
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { bold: true, color: "yellow" }, title)), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, hint, " Enter to send \xB7 Esc to return to the picker.", value === "" ? blankHint : "")), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, "\u203A "), /* @__PURE__ */ React13.createElement(Text12, null, value || " "), /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, "\u258D"))));
|
|
7980
8835
|
}
|
|
7981
8836
|
|
|
8837
|
+
// src/cli/ui/PlanReviseConfirm.tsx
|
|
8838
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
8839
|
+
import React14 from "react";
|
|
8840
|
+
function computeDiff(oldSteps, newSteps) {
|
|
8841
|
+
const oldIds = new Set(oldSteps.map((s) => s.id));
|
|
8842
|
+
const newIds = new Set(newSteps.map((s) => s.id));
|
|
8843
|
+
const rows = [];
|
|
8844
|
+
for (const s of oldSteps) {
|
|
8845
|
+
if (!newIds.has(s.id)) rows.push({ kind: "removed", step: s });
|
|
8846
|
+
}
|
|
8847
|
+
for (const s of newSteps) {
|
|
8848
|
+
rows.push({ kind: oldIds.has(s.id) ? "kept" : "added", step: s });
|
|
8849
|
+
}
|
|
8850
|
+
return rows;
|
|
8851
|
+
}
|
|
8852
|
+
function riskDots2(risk) {
|
|
8853
|
+
switch (risk) {
|
|
8854
|
+
case "high":
|
|
8855
|
+
return { dots: "\u25CF\u25CF\u25CF", color: "red" };
|
|
8856
|
+
case "med":
|
|
8857
|
+
return { dots: "\u25CF\u25CF ", color: "yellow" };
|
|
8858
|
+
case "low":
|
|
8859
|
+
return { dots: "\u25CF ", color: "green" };
|
|
8860
|
+
default:
|
|
8861
|
+
return { dots: " ", color: "gray" };
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8864
|
+
function PlanReviseConfirmInner({
|
|
8865
|
+
reason,
|
|
8866
|
+
oldRemaining,
|
|
8867
|
+
newRemaining,
|
|
8868
|
+
summary,
|
|
8869
|
+
onChoose
|
|
8870
|
+
}) {
|
|
8871
|
+
const rows = computeDiff(oldRemaining, newRemaining);
|
|
8872
|
+
const removedCount = rows.filter((r) => r.kind === "removed").length;
|
|
8873
|
+
const addedCount = rows.filter((r) => r.kind === "added").length;
|
|
8874
|
+
const keptCount = rows.filter((r) => r.kind === "kept").length;
|
|
8875
|
+
return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: "yellow" }, "\u270F plan revision proposed"), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` \u2212${removedCount} +${addedCount} (${keptCount} kept)`)), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, reason)), summary ? /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, `updated summary: ${summary}`)) : null, /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1, flexDirection: "column" }, rows.map((row2) => {
|
|
8876
|
+
const risk = riskDots2(row2.step.risk);
|
|
8877
|
+
const prefix = row2.kind === "removed" ? "\u2212" : row2.kind === "added" ? "+" : " ";
|
|
8878
|
+
const prefixColor = row2.kind === "removed" ? "red" : row2.kind === "added" ? "green" : "gray";
|
|
8879
|
+
const dim = row2.kind === "kept";
|
|
8880
|
+
const strike = row2.kind === "removed";
|
|
8881
|
+
return /* @__PURE__ */ React14.createElement(Box13, { key: `${row2.kind}-${row2.step.id}` }, /* @__PURE__ */ React14.createElement(Text13, { color: prefixColor, bold: true }, `${prefix} `), /* @__PURE__ */ React14.createElement(Text13, { color: risk.color, bold: true, dimColor: dim }, risk.dots), /* @__PURE__ */ React14.createElement(Text13, { dimColor: dim, strikethrough: strike }, ` ${row2.step.id} \xB7 ${row2.step.title}`));
|
|
8882
|
+
})), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(
|
|
8883
|
+
SingleSelect,
|
|
8884
|
+
{
|
|
8885
|
+
initialValue: "accept",
|
|
8886
|
+
items: [
|
|
8887
|
+
{
|
|
8888
|
+
value: "accept",
|
|
8889
|
+
label: "Accept revision \u2014 apply the new step list",
|
|
8890
|
+
hint: "Replaces the remaining plan with the proposed steps. Done steps are untouched."
|
|
8891
|
+
},
|
|
8892
|
+
{
|
|
8893
|
+
value: "reject",
|
|
8894
|
+
label: "Reject \u2014 keep the original plan",
|
|
8895
|
+
hint: "Drops the proposal. Model continues with the original remaining steps."
|
|
8896
|
+
}
|
|
8897
|
+
],
|
|
8898
|
+
onSubmit: (v) => onChoose(v),
|
|
8899
|
+
onCancel: () => onChoose("reject"),
|
|
8900
|
+
footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] reject"
|
|
8901
|
+
}
|
|
8902
|
+
)));
|
|
8903
|
+
}
|
|
8904
|
+
var PlanReviseConfirm = React14.memo(PlanReviseConfirmInner);
|
|
8905
|
+
|
|
7982
8906
|
// src/cli/ui/PromptInput.tsx
|
|
7983
|
-
import { Box as
|
|
7984
|
-
import
|
|
8907
|
+
import { Box as Box14, Text as Text14, useInput as useInput4 } from "ink";
|
|
8908
|
+
import React15, { useRef, useState as useState5 } from "react";
|
|
7985
8909
|
|
|
7986
8910
|
// src/cli/ui/multiline-keys.ts
|
|
7987
8911
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
@@ -8141,13 +9065,13 @@ function PromptInput({
|
|
|
8141
9065
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
8142
9066
|
const borderColor = disabled ? "gray" : "cyan";
|
|
8143
9067
|
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
8144
|
-
return /* @__PURE__ */
|
|
9068
|
+
return /* @__PURE__ */ React15.createElement(Box14, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
8145
9069
|
const isFirst = i === 0;
|
|
8146
9070
|
const showPlaceholder = isFirst && value.length === 0;
|
|
8147
9071
|
const isCursorLine = i === cursorLine;
|
|
8148
9072
|
return (
|
|
8149
9073
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
8150
|
-
/* @__PURE__ */
|
|
9074
|
+
/* @__PURE__ */ React15.createElement(Box14, { key: i }, isFirst ? /* @__PURE__ */ React15.createElement(Text14, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React15.createElement(Text14, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React15.createElement(
|
|
8151
9075
|
LineWithCursor,
|
|
8152
9076
|
{
|
|
8153
9077
|
line,
|
|
@@ -8155,7 +9079,7 @@ function PromptInput({
|
|
|
8155
9079
|
showCursor,
|
|
8156
9080
|
borderColor
|
|
8157
9081
|
}
|
|
8158
|
-
) : /* @__PURE__ */
|
|
9082
|
+
) : /* @__PURE__ */ React15.createElement(Text14, null, line))
|
|
8159
9083
|
);
|
|
8160
9084
|
}));
|
|
8161
9085
|
}
|
|
@@ -8169,17 +9093,17 @@ function LineWithCursor({
|
|
|
8169
9093
|
const atCursor = line.slice(col, col + 1);
|
|
8170
9094
|
const after = line.slice(col + 1);
|
|
8171
9095
|
if (atCursor.length === 0) {
|
|
8172
|
-
return /* @__PURE__ */
|
|
9096
|
+
return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Text14, null, before), /* @__PURE__ */ React15.createElement(Text14, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
8173
9097
|
}
|
|
8174
|
-
return /* @__PURE__ */
|
|
9098
|
+
return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Text14, null, before), /* @__PURE__ */ React15.createElement(Text14, { inverse: showCursor }, atCursor), /* @__PURE__ */ React15.createElement(Text14, null, after));
|
|
8175
9099
|
}
|
|
8176
9100
|
|
|
8177
9101
|
// src/cli/ui/ShellConfirm.tsx
|
|
8178
|
-
import { Box as
|
|
8179
|
-
import
|
|
9102
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
9103
|
+
import React16 from "react";
|
|
8180
9104
|
function ShellConfirm({ command, allowPrefix, kind, onChoose }) {
|
|
8181
9105
|
const isBackground = kind === "run_background";
|
|
8182
|
-
return /* @__PURE__ */
|
|
9106
|
+
return /* @__PURE__ */ React16.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React16.createElement(Box15, null, /* @__PURE__ */ React16.createElement(Text15, { bold: true, color: "yellow" }, isBackground ? "\u25B8 model wants to start a BACKGROUND process" : "\u25B8 model wants to run a shell command")), isBackground ? /* @__PURE__ */ React16.createElement(Box15, null, /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, " (long-running: dev server / watcher; keeps running after approval, /kill to stop)")) : null, /* @__PURE__ */ React16.createElement(Box15, null, /* @__PURE__ */ React16.createElement(Text15, { 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__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text15, null, /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "$ "), /* @__PURE__ */ React16.createElement(Text15, { color: "cyan" }, command))), /* @__PURE__ */ React16.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(
|
|
8183
9107
|
SingleSelect,
|
|
8184
9108
|
{
|
|
8185
9109
|
initialValue: "run_once",
|
|
@@ -8236,8 +9160,8 @@ function derivePrefix(command) {
|
|
|
8236
9160
|
}
|
|
8237
9161
|
|
|
8238
9162
|
// src/cli/ui/SlashArgPicker.tsx
|
|
8239
|
-
import { Box as
|
|
8240
|
-
import
|
|
9163
|
+
import { Box as Box16, Text as Text16 } from "ink";
|
|
9164
|
+
import React17 from "react";
|
|
8241
9165
|
function SlashArgPicker({
|
|
8242
9166
|
matches,
|
|
8243
9167
|
selectedIndex,
|
|
@@ -8246,11 +9170,11 @@ function SlashArgPicker({
|
|
|
8246
9170
|
partial
|
|
8247
9171
|
}) {
|
|
8248
9172
|
if (kind === "hint") {
|
|
8249
|
-
return /* @__PURE__ */
|
|
9173
|
+
return /* @__PURE__ */ React17.createElement(Box16, { paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, " ", /* @__PURE__ */ React17.createElement(Text16, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary));
|
|
8250
9174
|
}
|
|
8251
9175
|
if (matches === null) return null;
|
|
8252
9176
|
if (matches.length === 0) {
|
|
8253
|
-
return /* @__PURE__ */
|
|
9177
|
+
return /* @__PURE__ */ React17.createElement(Box16, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, " ", /* @__PURE__ */ React17.createElement(Text16, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary), /* @__PURE__ */ React17.createElement(Text16, { color: "yellow" }, ' no match for "', partial, '" \u2014 keep typing, or Backspace to edit'));
|
|
8254
9178
|
}
|
|
8255
9179
|
const MAX = 8;
|
|
8256
9180
|
const total = matches.length;
|
|
@@ -8258,26 +9182,26 @@ function SlashArgPicker({
|
|
|
8258
9182
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
8259
9183
|
const hiddenAbove = windowStart;
|
|
8260
9184
|
const hiddenBelow = total - windowStart - shown.length;
|
|
8261
|
-
return /* @__PURE__ */
|
|
9185
|
+
return /* @__PURE__ */ React17.createElement(Box16, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, " ", /* @__PURE__ */ React17.createElement(Text16, { bold: true }, "/", spec.cmd), spec.argsHint ? ` ${spec.argsHint}` : "", " \u2014 ", spec.summary), hiddenAbove > 0 ? /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((value, i) => /* @__PURE__ */ React17.createElement(ArgRow, { key: value, value, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
|
|
8262
9186
|
}
|
|
8263
9187
|
function ArgRow({ value, isSelected }) {
|
|
8264
9188
|
const marker = isSelected ? "\u25B8" : " ";
|
|
8265
9189
|
if (isSelected) {
|
|
8266
|
-
return /* @__PURE__ */
|
|
9190
|
+
return /* @__PURE__ */ React17.createElement(Box16, null, /* @__PURE__ */ React17.createElement(Text16, { bold: true, color: "cyan" }, marker, " ", value));
|
|
8267
9191
|
}
|
|
8268
|
-
return /* @__PURE__ */
|
|
9192
|
+
return /* @__PURE__ */ React17.createElement(Box16, null, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, marker, " ", value));
|
|
8269
9193
|
}
|
|
8270
9194
|
|
|
8271
9195
|
// src/cli/ui/SlashSuggestions.tsx
|
|
8272
|
-
import { Box as
|
|
8273
|
-
import
|
|
9196
|
+
import { Box as Box17, Text as Text17 } from "ink";
|
|
9197
|
+
import React18 from "react";
|
|
8274
9198
|
function SlashSuggestions({
|
|
8275
9199
|
matches,
|
|
8276
9200
|
selectedIndex
|
|
8277
9201
|
}) {
|
|
8278
9202
|
if (matches === null) return null;
|
|
8279
9203
|
if (matches.length === 0) {
|
|
8280
|
-
return /* @__PURE__ */
|
|
9204
|
+
return /* @__PURE__ */ React18.createElement(Box17, { paddingX: 1 }, /* @__PURE__ */ React18.createElement(Text17, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React18.createElement(Text17, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
|
|
8281
9205
|
}
|
|
8282
9206
|
const MAX = 8;
|
|
8283
9207
|
const total = matches.length;
|
|
@@ -8285,21 +9209,21 @@ function SlashSuggestions({
|
|
|
8285
9209
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
8286
9210
|
const hiddenAbove = windowStart;
|
|
8287
9211
|
const hiddenBelow = total - windowStart - shown.length;
|
|
8288
|
-
return /* @__PURE__ */
|
|
9212
|
+
return /* @__PURE__ */ React18.createElement(Box17, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React18.createElement(Text17, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React18.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React18.createElement(Text17, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React18.createElement(Text17, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick"));
|
|
8289
9213
|
}
|
|
8290
9214
|
function SuggestionRow({ spec, isSelected }) {
|
|
8291
9215
|
const marker = isSelected ? "\u25B8" : " ";
|
|
8292
9216
|
const name = `/${spec.cmd}`;
|
|
8293
9217
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
8294
9218
|
if (isSelected) {
|
|
8295
|
-
return /* @__PURE__ */
|
|
9219
|
+
return /* @__PURE__ */ React18.createElement(Box17, null, /* @__PURE__ */ React18.createElement(Text17, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React18.createElement(Text17, { color: "cyan" }, " ", spec.summary));
|
|
8296
9220
|
}
|
|
8297
|
-
return /* @__PURE__ */
|
|
9221
|
+
return /* @__PURE__ */ React18.createElement(Box17, null, /* @__PURE__ */ React18.createElement(Text17, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
8298
9222
|
}
|
|
8299
9223
|
|
|
8300
9224
|
// src/cli/ui/StatsPanel.tsx
|
|
8301
|
-
import { Box as
|
|
8302
|
-
import
|
|
9225
|
+
import { Box as Box18, Text as Text18, useStdout as useStdout3 } from "ink";
|
|
9226
|
+
import React19 from "react";
|
|
8303
9227
|
var WORDMARK_STYLES = [
|
|
8304
9228
|
{ ch: "\u25C8", color: "#5eead4", isLogo: true },
|
|
8305
9229
|
// teal — brand mark
|
|
@@ -8325,7 +9249,7 @@ function Wordmark({ busy }) {
|
|
|
8325
9249
|
const tick = useTick();
|
|
8326
9250
|
const period = busy ? 5 : 12;
|
|
8327
9251
|
const bright = Math.floor(tick / period) % 2 === 0;
|
|
8328
|
-
return /* @__PURE__ */
|
|
9252
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, WORDMARK_STYLES.map((c) => /* @__PURE__ */ React19.createElement(Text18, { key: `${c.ch}-${c.color}`, color: c.color, bold: c.isLogo ? bright : true }, c.ch)));
|
|
8329
9253
|
}
|
|
8330
9254
|
var NARROW_BREAKPOINT = 120;
|
|
8331
9255
|
var COLD_START_TURNS = 3;
|
|
@@ -8351,7 +9275,7 @@ function StatsPanel({
|
|
|
8351
9275
|
const columns = stdout2?.columns ?? 80;
|
|
8352
9276
|
const narrow = columns < NARROW_BREAKPOINT;
|
|
8353
9277
|
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
8354
|
-
return /* @__PURE__ */
|
|
9278
|
+
return /* @__PURE__ */ React19.createElement(Box18, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React19.createElement(
|
|
8355
9279
|
Header,
|
|
8356
9280
|
{
|
|
8357
9281
|
model: model2,
|
|
@@ -8369,7 +9293,7 @@ function StatsPanel({
|
|
|
8369
9293
|
proArmed: proArmed ?? false,
|
|
8370
9294
|
escalated: escalated ?? false
|
|
8371
9295
|
}
|
|
8372
|
-
), narrow ? /* @__PURE__ */
|
|
9296
|
+
), narrow ? /* @__PURE__ */ React19.createElement(
|
|
8373
9297
|
StackedMetrics,
|
|
8374
9298
|
{
|
|
8375
9299
|
summary,
|
|
@@ -8378,7 +9302,7 @@ function StatsPanel({
|
|
|
8378
9302
|
balance,
|
|
8379
9303
|
coldStart
|
|
8380
9304
|
}
|
|
8381
|
-
) : /* @__PURE__ */
|
|
9305
|
+
) : /* @__PURE__ */ React19.createElement(
|
|
8382
9306
|
InlineMetrics,
|
|
8383
9307
|
{
|
|
8384
9308
|
summary,
|
|
@@ -8405,7 +9329,7 @@ function Header({
|
|
|
8405
9329
|
proArmed,
|
|
8406
9330
|
escalated
|
|
8407
9331
|
}) {
|
|
8408
|
-
return /* @__PURE__ */
|
|
9332
|
+
return /* @__PURE__ */ React19.createElement(Box18, { justifyContent: "space-between" }, /* @__PURE__ */ React19.createElement(Box18, null, /* @__PURE__ */ React19.createElement(Wordmark, { busy }), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React19.createElement(Text18, { color: "yellow" }, model2), narrow ? null : /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React19.createElement(Text18, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React19.createElement(Text18, { color: "blue" }, " \xB7 branch", branchBudget) : null, reasoningEffort === "max" ? /* @__PURE__ */ React19.createElement(Text18, { color: "green" }, " \xB7 max") : null, reasoningEffort === "high" ? /* @__PURE__ */ React19.createElement(Text18, { color: "yellow" }, " \xB7 high") : null, planMode ? /* @__PURE__ */ React19.createElement(Text18, { color: "red", bold: true }, " \xB7 PLAN") : null, editMode ? /* @__PURE__ */ React19.createElement(Text18, { color: editMode === "auto" ? "magenta" : "cyan", bold: true }, editMode === "auto" ? " \xB7 AUTO" : " \xB7 review") : null, escalated ? /* @__PURE__ */ React19.createElement(Text18, { color: "red", bold: true }, " \xB7 \u21E7 pro escalated") : proArmed ? /* @__PURE__ */ React19.createElement(Text18, { color: "yellow", bold: true }, " \xB7 \u21E7 pro armed") : null), /* @__PURE__ */ React19.createElement(Text18, null, updateAvailable ? /* @__PURE__ */ React19.createElement(Text18, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
|
|
8409
9333
|
}
|
|
8410
9334
|
function InlineMetrics({
|
|
8411
9335
|
summary,
|
|
@@ -8414,7 +9338,7 @@ function InlineMetrics({
|
|
|
8414
9338
|
balance,
|
|
8415
9339
|
coldStart
|
|
8416
9340
|
}) {
|
|
8417
|
-
return /* @__PURE__ */
|
|
9341
|
+
return /* @__PURE__ */ React19.createElement(Box18, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React19.createElement(ContextCell, { ratio: ctxRatio, promptTokens: summary.lastPromptTokens, ctxMax }), /* @__PURE__ */ React19.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React19.createElement(CostCell, { summary, coldStart }), balance ? /* @__PURE__ */ React19.createElement(BalanceCell, { balance }) : null);
|
|
8418
9342
|
}
|
|
8419
9343
|
function StackedMetrics({
|
|
8420
9344
|
summary,
|
|
@@ -8423,7 +9347,7 @@ function StackedMetrics({
|
|
|
8423
9347
|
balance,
|
|
8424
9348
|
coldStart
|
|
8425
9349
|
}) {
|
|
8426
|
-
return /* @__PURE__ */
|
|
9350
|
+
return /* @__PURE__ */ React19.createElement(Box18, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React19.createElement(
|
|
8427
9351
|
ContextCell,
|
|
8428
9352
|
{
|
|
8429
9353
|
ratio: ctxRatio,
|
|
@@ -8431,7 +9355,7 @@ function StackedMetrics({
|
|
|
8431
9355
|
ctxMax,
|
|
8432
9356
|
showBar: true
|
|
8433
9357
|
}
|
|
8434
|
-
), balance ? /* @__PURE__ */
|
|
9358
|
+
), balance ? /* @__PURE__ */ React19.createElement(BalanceCell, { balance }) : null, /* @__PURE__ */ React19.createElement(CacheCell, { hitRatio: summary.cacheHitRatio, coldStart, turns: summary.turns }), /* @__PURE__ */ React19.createElement(CostCell, { summary, coldStart }));
|
|
8435
9359
|
}
|
|
8436
9360
|
function ContextCell({
|
|
8437
9361
|
ratio,
|
|
@@ -8440,11 +9364,11 @@ function ContextCell({
|
|
|
8440
9364
|
showBar
|
|
8441
9365
|
}) {
|
|
8442
9366
|
if (promptTokens === 0) {
|
|
8443
|
-
return /* @__PURE__ */
|
|
9367
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "ctx "), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "\u2014 (no turns yet)"));
|
|
8444
9368
|
}
|
|
8445
9369
|
const color = ratio >= 0.8 ? "red" : ratio >= 0.6 ? "yellow" : "green";
|
|
8446
9370
|
const pct2 = Math.round(ratio * 100);
|
|
8447
|
-
return /* @__PURE__ */
|
|
9371
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "ctx "), showBar ? /* @__PURE__ */ React19.createElement(Bar, { ratio, color }) : null, showBar ? /* @__PURE__ */ React19.createElement(Text18, null, " ") : null, /* @__PURE__ */ React19.createElement(Text18, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React19.createElement(Text18, { color: "red", bold: true }, " \xB7 /compact") : null);
|
|
8448
9372
|
}
|
|
8449
9373
|
function CacheCell({
|
|
8450
9374
|
hitRatio,
|
|
@@ -8453,13 +9377,13 @@ function CacheCell({
|
|
|
8453
9377
|
}) {
|
|
8454
9378
|
const pct2 = (hitRatio * 100).toFixed(1);
|
|
8455
9379
|
if (turns === 0) {
|
|
8456
|
-
return /* @__PURE__ */
|
|
9380
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "cache "), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "\u2014"));
|
|
8457
9381
|
}
|
|
8458
9382
|
if (coldStart) {
|
|
8459
|
-
return /* @__PURE__ */
|
|
9383
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "cache "), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true, italic: true }, "(cold start)"));
|
|
8460
9384
|
}
|
|
8461
9385
|
const color = hitRatio >= 0.7 ? "green" : hitRatio >= 0.4 ? "yellow" : "red";
|
|
8462
|
-
return /* @__PURE__ */
|
|
9386
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "cache "), /* @__PURE__ */ React19.createElement(Text18, { color, bold: true }, pct2, "%"));
|
|
8463
9387
|
}
|
|
8464
9388
|
function turnCostColor(cost) {
|
|
8465
9389
|
if (cost <= 0) return void 0;
|
|
@@ -8478,21 +9402,21 @@ function CostCell({
|
|
|
8478
9402
|
coldStart
|
|
8479
9403
|
}) {
|
|
8480
9404
|
if (summary.turns === 0) {
|
|
8481
|
-
return /* @__PURE__ */
|
|
9405
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "cost "), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "\u2014"));
|
|
8482
9406
|
}
|
|
8483
9407
|
const turnColor = coldStart ? void 0 : turnCostColor(summary.lastTurnCostUsd);
|
|
8484
9408
|
const sessionColor = coldStart ? void 0 : sessionCostColor(summary.totalCostUsd);
|
|
8485
|
-
return /* @__PURE__ */
|
|
9409
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "turn "), /* @__PURE__ */ React19.createElement(Text18, { color: turnColor, bold: !coldStart, dimColor: coldStart }, "$", summary.lastTurnCostUsd.toFixed(4)), /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, " \xB7 session "), /* @__PURE__ */ React19.createElement(Text18, { color: sessionColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(4)));
|
|
8486
9410
|
}
|
|
8487
9411
|
function BalanceCell({ balance }) {
|
|
8488
9412
|
const color = balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green";
|
|
8489
|
-
return /* @__PURE__ */
|
|
9413
|
+
return /* @__PURE__ */ React19.createElement(Text18, null, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "balance "), /* @__PURE__ */ React19.createElement(Text18, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
8490
9414
|
}
|
|
8491
9415
|
function Bar({ ratio, color }) {
|
|
8492
9416
|
const cells = 10;
|
|
8493
9417
|
const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
|
|
8494
9418
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(cells - filled);
|
|
8495
|
-
return /* @__PURE__ */
|
|
9419
|
+
return /* @__PURE__ */ React19.createElement(Text18, { color }, bar);
|
|
8496
9420
|
}
|
|
8497
9421
|
function formatTokens(n) {
|
|
8498
9422
|
if (n < 1024) return String(n);
|
|
@@ -8738,7 +9662,7 @@ function formatLongPaste(input, opts = {}) {
|
|
|
8738
9662
|
if (originalChars <= charCap && originalLines <= lineCap) {
|
|
8739
9663
|
return { displayText: input, collapsed: false, originalChars, originalLines };
|
|
8740
9664
|
}
|
|
8741
|
-
const header2 = `\u25B8 pasted ${
|
|
9665
|
+
const header2 = `\u25B8 pasted ${formatBytes2(originalChars)} (${originalLines} lines) \u2014 first ${Math.min(headN, originalLines)} shown, full text sent to model`;
|
|
8742
9666
|
const head = lines.slice(0, headN).join("\n");
|
|
8743
9667
|
const remaining = originalLines - headN;
|
|
8744
9668
|
const footer = remaining > 0 ? `\u2026 (${remaining} more line${remaining === 1 ? "" : "s"})` : "";
|
|
@@ -8748,7 +9672,7 @@ ${footer}` : `${header2}
|
|
|
8748
9672
|
${head}`;
|
|
8749
9673
|
return { displayText, collapsed: true, originalChars, originalLines };
|
|
8750
9674
|
}
|
|
8751
|
-
function
|
|
9675
|
+
function formatBytes2(n) {
|
|
8752
9676
|
if (n < 1024) return `${n} B`;
|
|
8753
9677
|
const kb = n / 1024;
|
|
8754
9678
|
if (kb < 1024) return `${kb.toFixed(kb >= 10 ? 0 : 1)} KB`;
|
|
@@ -8846,6 +9770,12 @@ var SLASH_COMMANDS = [
|
|
|
8846
9770
|
summary: "shrink oversized tool results AND tool-call args (edit_file search/replace) in the log; cap in tokens, default 4000"
|
|
8847
9771
|
},
|
|
8848
9772
|
{ cmd: "keys", summary: "show all keyboard shortcuts and prompt prefixes" },
|
|
9773
|
+
{ cmd: "plans", summary: "list this session's active + archived plans, newest first" },
|
|
9774
|
+
{
|
|
9775
|
+
cmd: "replay",
|
|
9776
|
+
summary: "load an archived plan as a read-only Time Travel snapshot (default: newest)",
|
|
9777
|
+
argsHint: "[N]"
|
|
9778
|
+
},
|
|
8849
9779
|
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
8850
9780
|
{ cmd: "forget", summary: "delete the current session from disk" },
|
|
8851
9781
|
{ cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
|
|
@@ -8943,7 +9873,7 @@ function parseSlash(text) {
|
|
|
8943
9873
|
}
|
|
8944
9874
|
|
|
8945
9875
|
// src/cli/commands/stats.ts
|
|
8946
|
-
import { existsSync as
|
|
9876
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
8947
9877
|
function statsCommand(opts) {
|
|
8948
9878
|
if (opts.transcript) {
|
|
8949
9879
|
transcriptSummary(opts.transcript);
|
|
@@ -8952,11 +9882,11 @@ function statsCommand(opts) {
|
|
|
8952
9882
|
dashboard(opts);
|
|
8953
9883
|
}
|
|
8954
9884
|
function transcriptSummary(path) {
|
|
8955
|
-
if (!
|
|
9885
|
+
if (!existsSync11(path)) {
|
|
8956
9886
|
console.error(`no such transcript: ${path}`);
|
|
8957
9887
|
process.exit(1);
|
|
8958
9888
|
}
|
|
8959
|
-
const lines =
|
|
9889
|
+
const lines = readFileSync14(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
8960
9890
|
let assistantTurns = 0;
|
|
8961
9891
|
let toolCalls = 0;
|
|
8962
9892
|
let lastTurn = 0;
|
|
@@ -9800,7 +10730,7 @@ var model = (args, loop, ctx) => {
|
|
|
9800
10730
|
const id = args[0];
|
|
9801
10731
|
const known = ctx.models ?? null;
|
|
9802
10732
|
if (!id) {
|
|
9803
|
-
const hint = known && known.length > 0 ? known.join(" | ") : "try deepseek-
|
|
10733
|
+
const hint = known && known.length > 0 ? known.join(" | ") : "try deepseek-v4-flash or deepseek-v4-pro \u2014 run /models to fetch the live list";
|
|
9804
10734
|
return { info: `usage: /model <id> (${hint})` };
|
|
9805
10735
|
}
|
|
9806
10736
|
loop.configure({ model: id });
|
|
@@ -9961,7 +10891,7 @@ var think = (_args, loop) => {
|
|
|
9961
10891
|
const raw = loop.scratch.reasoning;
|
|
9962
10892
|
if (!raw || !raw.trim()) {
|
|
9963
10893
|
return {
|
|
9964
|
-
info: "no reasoning cached. `/think` shows the full
|
|
10894
|
+
info: "no reasoning cached. `/think` shows the full thinking-mode thought for the most recent turn \u2014 only thinking-mode models (deepseek-v4-flash / -v4-pro / -reasoner) produce it, and only once the turn completes."
|
|
9965
10895
|
};
|
|
9966
10896
|
}
|
|
9967
10897
|
return { info: `\u21B3 full thinking (${raw.length} chars):
|
|
@@ -10108,6 +11038,90 @@ var handlers8 = {
|
|
|
10108
11038
|
compact
|
|
10109
11039
|
};
|
|
10110
11040
|
|
|
11041
|
+
// src/cli/ui/slash/handlers/plans.ts
|
|
11042
|
+
import { basename } from "path";
|
|
11043
|
+
var plans = (_args, loop) => {
|
|
11044
|
+
const sessionName = loop.sessionName;
|
|
11045
|
+
if (!sessionName) {
|
|
11046
|
+
return {
|
|
11047
|
+
info: "no session attached \u2014 `/plans` is per-session. Run `reasonix code` in a project to get a session."
|
|
11048
|
+
};
|
|
11049
|
+
}
|
|
11050
|
+
const lines = [];
|
|
11051
|
+
const active = loadPlanState(sessionName);
|
|
11052
|
+
if (active && active.steps.length > 0) {
|
|
11053
|
+
const total = active.steps.length;
|
|
11054
|
+
const done = active.completedStepIds.length;
|
|
11055
|
+
const when = relativeTime(active.updatedAt);
|
|
11056
|
+
const label = active.summary ? `: ${active.summary}` : "";
|
|
11057
|
+
lines.push(
|
|
11058
|
+
`\u25B8 active plan${label} \u2014 ${done}/${total} step${total === 1 ? "" : "s"} done \xB7 last touched ${when}`
|
|
11059
|
+
);
|
|
11060
|
+
} else {
|
|
11061
|
+
lines.push("\u25B8 active plan: (none)");
|
|
11062
|
+
}
|
|
11063
|
+
const archives = listPlanArchives(sessionName);
|
|
11064
|
+
if (archives.length === 0) {
|
|
11065
|
+
lines.push("");
|
|
11066
|
+
lines.push(
|
|
11067
|
+
"no archived plans yet for this session \u2014 they auto-archive when every step is done"
|
|
11068
|
+
);
|
|
11069
|
+
return { info: lines.join("\n") };
|
|
11070
|
+
}
|
|
11071
|
+
lines.push("");
|
|
11072
|
+
lines.push(`Archived (${archives.length}):`);
|
|
11073
|
+
for (const a of archives) {
|
|
11074
|
+
const when = relativeTime(a.completedAt);
|
|
11075
|
+
const total = a.steps.length;
|
|
11076
|
+
const done = a.completedStepIds.length;
|
|
11077
|
+
const completion = done >= total ? "complete" : `${done}/${total}`;
|
|
11078
|
+
const label = a.summary ?? a.path.split(/[\\/]/).pop() ?? a.path;
|
|
11079
|
+
lines.push(
|
|
11080
|
+
` \u2713 ${when.padEnd(10)} ${total} step${total === 1 ? "" : "s"} \xB7 ${completion} ${label}`
|
|
11081
|
+
);
|
|
11082
|
+
}
|
|
11083
|
+
return { info: lines.join("\n") };
|
|
11084
|
+
};
|
|
11085
|
+
var replay = (args, loop) => {
|
|
11086
|
+
const sessionName = loop.sessionName;
|
|
11087
|
+
if (!sessionName) {
|
|
11088
|
+
return {
|
|
11089
|
+
info: "no session attached \u2014 `/replay` is per-session. Run `reasonix code` in a project to get a session."
|
|
11090
|
+
};
|
|
11091
|
+
}
|
|
11092
|
+
const archives = listPlanArchives(sessionName);
|
|
11093
|
+
if (archives.length === 0) {
|
|
11094
|
+
return {
|
|
11095
|
+
info: "no archived plans yet for this session \u2014 `/replay` lights up once a plan completes (auto-archives when every step is done)."
|
|
11096
|
+
};
|
|
11097
|
+
}
|
|
11098
|
+
const arg = args[0]?.trim() ?? "";
|
|
11099
|
+
const index = arg ? Number.parseInt(arg, 10) : 1;
|
|
11100
|
+
if (!Number.isFinite(index) || index < 1 || index > archives.length) {
|
|
11101
|
+
return {
|
|
11102
|
+
info: `invalid index \u2014 \`/replay\` takes 1..${archives.length} (newest = 1). Use \`/plans\` to see the list.`
|
|
11103
|
+
};
|
|
11104
|
+
}
|
|
11105
|
+
const a = archives[index - 1];
|
|
11106
|
+
return {
|
|
11107
|
+
replayPlan: {
|
|
11108
|
+
summary: a.summary,
|
|
11109
|
+
body: a.body,
|
|
11110
|
+
steps: a.steps,
|
|
11111
|
+
completedStepIds: a.completedStepIds,
|
|
11112
|
+
completedAt: a.completedAt,
|
|
11113
|
+
relativeTime: relativeTime(a.completedAt),
|
|
11114
|
+
archiveBasename: basename(a.path),
|
|
11115
|
+
index,
|
|
11116
|
+
total: archives.length
|
|
11117
|
+
}
|
|
11118
|
+
};
|
|
11119
|
+
};
|
|
11120
|
+
var handlers9 = {
|
|
11121
|
+
plans,
|
|
11122
|
+
replay
|
|
11123
|
+
};
|
|
11124
|
+
|
|
10111
11125
|
// src/cli/ui/slash/handlers/sessions.ts
|
|
10112
11126
|
var sessions = (_args, loop) => {
|
|
10113
11127
|
const items = listSessions();
|
|
@@ -10139,7 +11153,7 @@ var forget = (_args, loop) => {
|
|
|
10139
11153
|
info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
|
|
10140
11154
|
};
|
|
10141
11155
|
};
|
|
10142
|
-
var
|
|
11156
|
+
var handlers10 = {
|
|
10143
11157
|
sessions,
|
|
10144
11158
|
forget
|
|
10145
11159
|
};
|
|
@@ -10215,7 +11229,7 @@ ${found.body}${argsLine}`;
|
|
|
10215
11229
|
resubmit: payload
|
|
10216
11230
|
};
|
|
10217
11231
|
};
|
|
10218
|
-
var
|
|
11232
|
+
var handlers11 = {
|
|
10219
11233
|
skill,
|
|
10220
11234
|
skills: skill
|
|
10221
11235
|
};
|
|
@@ -10231,7 +11245,8 @@ var HANDLERS = {
|
|
|
10231
11245
|
...handlers7,
|
|
10232
11246
|
...handlers8,
|
|
10233
11247
|
...handlers9,
|
|
10234
|
-
...handlers10
|
|
11248
|
+
...handlers10,
|
|
11249
|
+
...handlers11
|
|
10235
11250
|
};
|
|
10236
11251
|
function handleSlash(cmd, args, loop, ctx = {}) {
|
|
10237
11252
|
const h = HANDLERS[cmd];
|
|
@@ -10788,6 +11803,11 @@ function App({
|
|
|
10788
11803
|
const [pendingShell, setPendingShell] = useState10(null);
|
|
10789
11804
|
const [pendingPlan, setPendingPlan] = useState10(null);
|
|
10790
11805
|
const [stagedInput, setStagedInput] = useState10(null);
|
|
11806
|
+
const [pendingCheckpoint, setPendingCheckpoint] = useState10(null);
|
|
11807
|
+
const [stagedCheckpointRevise, setStagedCheckpointRevise] = useState10(null);
|
|
11808
|
+
const [pendingRevision, setPendingRevision] = useState10(null);
|
|
11809
|
+
const [pendingChoice, setPendingChoice] = useState10(null);
|
|
11810
|
+
const [stagedChoiceCustom, setStagedChoiceCustom] = useState10(null);
|
|
10791
11811
|
const [planMode, setPlanMode] = useState10(false);
|
|
10792
11812
|
const [proArmed, setProArmed] = useState10(false);
|
|
10793
11813
|
const [turnOnPro, setTurnOnPro] = useState10(false);
|
|
@@ -10796,6 +11816,23 @@ function App({
|
|
|
10796
11816
|
const historyCursor = useRef5(-1);
|
|
10797
11817
|
const assistantIterCounter = useRef5(0);
|
|
10798
11818
|
const toolHistoryRef = useRef5([]);
|
|
11819
|
+
const planStepsRef = useRef5(null);
|
|
11820
|
+
const completedStepIdsRef = useRef5(/* @__PURE__ */ new Set());
|
|
11821
|
+
const planBodyRef = useRef5(null);
|
|
11822
|
+
const planSummaryRef = useRef5(null);
|
|
11823
|
+
const toolStartedAtRef = useRef5(null);
|
|
11824
|
+
const persistPlanState = useCallback4(() => {
|
|
11825
|
+
if (!session) return;
|
|
11826
|
+
const steps = planStepsRef.current;
|
|
11827
|
+
if (!steps || steps.length === 0) {
|
|
11828
|
+
clearPlanState(session);
|
|
11829
|
+
return;
|
|
11830
|
+
}
|
|
11831
|
+
const extras = {};
|
|
11832
|
+
if (planBodyRef.current) extras.body = planBodyRef.current;
|
|
11833
|
+
if (planSummaryRef.current) extras.summary = planSummaryRef.current;
|
|
11834
|
+
savePlanState(session, steps, completedStepIdsRef.current, extras);
|
|
11835
|
+
}, [session]);
|
|
10799
11836
|
const [summary, setSummary] = useState10({
|
|
10800
11837
|
turns: 0,
|
|
10801
11838
|
totalCostUsd: 0,
|
|
@@ -10958,6 +11995,30 @@ function App({
|
|
|
10958
11995
|
]);
|
|
10959
11996
|
}
|
|
10960
11997
|
}
|
|
11998
|
+
if (session) {
|
|
11999
|
+
const restoredPlan = loadPlanState(session);
|
|
12000
|
+
if (restoredPlan && restoredPlan.steps.length > 0) {
|
|
12001
|
+
planStepsRef.current = restoredPlan.steps;
|
|
12002
|
+
completedStepIdsRef.current = new Set(restoredPlan.completedStepIds);
|
|
12003
|
+
planBodyRef.current = restoredPlan.body ?? null;
|
|
12004
|
+
planSummaryRef.current = restoredPlan.summary ?? null;
|
|
12005
|
+
const when = relativeTime(restoredPlan.updatedAt);
|
|
12006
|
+
setHistorical((prev) => [
|
|
12007
|
+
...prev,
|
|
12008
|
+
{
|
|
12009
|
+
id: `sys-plan-${Date.now()}`,
|
|
12010
|
+
role: "plan-resumed",
|
|
12011
|
+
text: "",
|
|
12012
|
+
resumedPlan: {
|
|
12013
|
+
steps: restoredPlan.steps,
|
|
12014
|
+
completedStepIds: restoredPlan.completedStepIds,
|
|
12015
|
+
relativeTime: when,
|
|
12016
|
+
summary: restoredPlan.summary
|
|
12017
|
+
}
|
|
12018
|
+
}
|
|
12019
|
+
]);
|
|
12020
|
+
}
|
|
12021
|
+
}
|
|
10961
12022
|
if (codeMode && !editModeHintShown()) {
|
|
10962
12023
|
setHistorical((prev) => [
|
|
10963
12024
|
...prev,
|
|
@@ -10983,7 +12044,7 @@ function App({
|
|
|
10983
12044
|
loop.abort();
|
|
10984
12045
|
return;
|
|
10985
12046
|
}
|
|
10986
|
-
if (codeMode && key.shift && key.tab && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview) {
|
|
12047
|
+
if (codeMode && key.shift && key.tab && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
|
|
10987
12048
|
setEditMode((m) => {
|
|
10988
12049
|
const next = m === "auto" ? "review" : "auto";
|
|
10989
12050
|
setHistorical((prev) => [
|
|
@@ -10998,7 +12059,7 @@ function App({
|
|
|
10998
12059
|
});
|
|
10999
12060
|
return;
|
|
11000
12061
|
}
|
|
11001
|
-
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
|
|
12062
|
+
if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision && // Fire when EITHER the banner is up OR there's any non-undone
|
|
11002
12063
|
// history entry — the keybind is useful long after the 5-second
|
|
11003
12064
|
// banner expires, which users rightly want.
|
|
11004
12065
|
(undoBanner || hasUndoable())) {
|
|
@@ -11364,6 +12425,27 @@ function App({
|
|
|
11364
12425
|
}
|
|
11365
12426
|
]);
|
|
11366
12427
|
}
|
|
12428
|
+
if (result.replayPlan) {
|
|
12429
|
+
const rp = result.replayPlan;
|
|
12430
|
+
setHistorical((prev) => [
|
|
12431
|
+
...prev,
|
|
12432
|
+
{
|
|
12433
|
+
id: `replay-${Date.now()}-${Math.random()}`,
|
|
12434
|
+
role: "plan-replay",
|
|
12435
|
+
text: "",
|
|
12436
|
+
replayPlan: {
|
|
12437
|
+
summary: rp.summary,
|
|
12438
|
+
body: rp.body,
|
|
12439
|
+
steps: rp.steps,
|
|
12440
|
+
completedStepIds: rp.completedStepIds,
|
|
12441
|
+
relativeTime: rp.relativeTime,
|
|
12442
|
+
archiveBasename: rp.archiveBasename,
|
|
12443
|
+
index: rp.index,
|
|
12444
|
+
total: rp.total
|
|
12445
|
+
}
|
|
12446
|
+
}
|
|
12447
|
+
]);
|
|
12448
|
+
}
|
|
11367
12449
|
if (result.resubmit) {
|
|
11368
12450
|
text = result.resubmit;
|
|
11369
12451
|
} else {
|
|
@@ -11570,6 +12652,7 @@ function App({
|
|
|
11570
12652
|
} else if (ev.role === "tool_start") {
|
|
11571
12653
|
setOngoingTool({ name: ev.toolName ?? "?", args: ev.toolArgs });
|
|
11572
12654
|
setToolProgress(null);
|
|
12655
|
+
toolStartedAtRef.current = Date.now();
|
|
11573
12656
|
if (codeMode && ev.toolArgs) {
|
|
11574
12657
|
try {
|
|
11575
12658
|
const parsed = JSON.parse(ev.toolArgs);
|
|
@@ -11587,19 +12670,28 @@ function App({
|
|
|
11587
12670
|
flush();
|
|
11588
12671
|
setOngoingTool(null);
|
|
11589
12672
|
setToolProgress(null);
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
12673
|
+
const isStepProgressTool = ev.toolName === "mark_step_complete";
|
|
12674
|
+
const startedAt = toolStartedAtRef.current;
|
|
12675
|
+
const durationMs = startedAt !== null ? Date.now() - startedAt : void 0;
|
|
12676
|
+
toolStartedAtRef.current = null;
|
|
12677
|
+
if (!isStepProgressTool) {
|
|
12678
|
+
toolHistoryRef.current.push({
|
|
12679
|
+
toolName: ev.toolName ?? "?",
|
|
12680
|
+
text: ev.content
|
|
12681
|
+
});
|
|
12682
|
+
const toolIndex = toolHistoryRef.current.length;
|
|
12683
|
+
setHistorical((prev) => [
|
|
12684
|
+
...prev,
|
|
12685
|
+
{
|
|
12686
|
+
id: `t-${Date.now()}-${Math.random()}`,
|
|
12687
|
+
role: "tool",
|
|
12688
|
+
text: ev.content,
|
|
12689
|
+
toolName: ev.toolName,
|
|
12690
|
+
toolIndex,
|
|
12691
|
+
durationMs
|
|
12692
|
+
}
|
|
12693
|
+
]);
|
|
12694
|
+
}
|
|
11603
12695
|
if (codeMode && (ev.toolName === "run_command" || ev.toolName === "run_background") && ev.content.includes('"NeedsConfirmationError:') && ev.toolArgs) {
|
|
11604
12696
|
try {
|
|
11605
12697
|
const parsed = JSON.parse(ev.toolArgs);
|
|
@@ -11618,6 +12710,12 @@ function App({
|
|
|
11618
12710
|
if (typeof parsed.plan === "string" && parsed.plan.trim()) {
|
|
11619
12711
|
const planText = parsed.plan.trim();
|
|
11620
12712
|
setPendingPlan(planText);
|
|
12713
|
+
const steps = Array.isArray(parsed.steps) ? parsed.steps : null;
|
|
12714
|
+
planStepsRef.current = steps;
|
|
12715
|
+
completedStepIdsRef.current = /* @__PURE__ */ new Set();
|
|
12716
|
+
planBodyRef.current = planText;
|
|
12717
|
+
planSummaryRef.current = typeof parsed.summary === "string" && parsed.summary.trim() ? parsed.summary.trim() : null;
|
|
12718
|
+
persistPlanState();
|
|
11621
12719
|
setHistorical((prev) => [
|
|
11622
12720
|
...prev,
|
|
11623
12721
|
{
|
|
@@ -11630,6 +12728,85 @@ function App({
|
|
|
11630
12728
|
} catch {
|
|
11631
12729
|
}
|
|
11632
12730
|
}
|
|
12731
|
+
if (ev.toolName === "revise_plan" && ev.content.includes('"PlanRevisionProposedError:')) {
|
|
12732
|
+
try {
|
|
12733
|
+
const parsed = JSON.parse(ev.content);
|
|
12734
|
+
const reason = typeof parsed.reason === "string" ? parsed.reason.trim() : "";
|
|
12735
|
+
const remainingSteps = Array.isArray(parsed.remainingSteps) ? parsed.remainingSteps.filter(
|
|
12736
|
+
(s) => s && typeof s.id === "string" && s.id.trim() && typeof s.title === "string" && s.title.trim() && typeof s.action === "string" && s.action.trim()
|
|
12737
|
+
) : [];
|
|
12738
|
+
if (reason && remainingSteps.length > 0) {
|
|
12739
|
+
const summary2 = typeof parsed.summary === "string" ? parsed.summary.trim() || void 0 : void 0;
|
|
12740
|
+
setPendingRevision({ reason, remainingSteps, summary: summary2 });
|
|
12741
|
+
}
|
|
12742
|
+
} catch {
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
if (ev.toolName === "ask_choice" && ev.content.includes('"ChoiceRequestedError:')) {
|
|
12746
|
+
try {
|
|
12747
|
+
const parsed = JSON.parse(ev.content);
|
|
12748
|
+
const question = typeof parsed.question === "string" ? parsed.question.trim() : "";
|
|
12749
|
+
const options = Array.isArray(parsed.options) ? parsed.options.filter(
|
|
12750
|
+
(o) => o && typeof o.id === "string" && o.id.trim() && typeof o.title === "string" && o.title.trim()
|
|
12751
|
+
) : [];
|
|
12752
|
+
if (question && options.length >= 2) {
|
|
12753
|
+
setPendingChoice({
|
|
12754
|
+
question,
|
|
12755
|
+
options,
|
|
12756
|
+
allowCustom: parsed.allowCustom === true
|
|
12757
|
+
});
|
|
12758
|
+
}
|
|
12759
|
+
} catch {
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
if (ev.toolName === "mark_step_complete") {
|
|
12763
|
+
try {
|
|
12764
|
+
const parsed = JSON.parse(ev.content);
|
|
12765
|
+
const stepId = parsed.stepId;
|
|
12766
|
+
if (parsed.kind === "step_completed" && typeof stepId === "string") {
|
|
12767
|
+
completedStepIdsRef.current.add(stepId);
|
|
12768
|
+
persistPlanState();
|
|
12769
|
+
const total = planStepsRef.current?.length ?? 0;
|
|
12770
|
+
const completed = completedStepIdsRef.current.size;
|
|
12771
|
+
const stepFromPlan = planStepsRef.current?.find((s) => s.id === stepId);
|
|
12772
|
+
const title = parsed.title ?? stepFromPlan?.title;
|
|
12773
|
+
const result = typeof parsed.result === "string" ? parsed.result : "";
|
|
12774
|
+
const notes = parsed.notes;
|
|
12775
|
+
setHistorical((prev) => [
|
|
12776
|
+
...prev,
|
|
12777
|
+
{
|
|
12778
|
+
id: `step-${stepId}-${Date.now()}-${Math.random()}`,
|
|
12779
|
+
role: "step-progress",
|
|
12780
|
+
text: result,
|
|
12781
|
+
stepProgress: {
|
|
12782
|
+
stepId,
|
|
12783
|
+
title,
|
|
12784
|
+
completed,
|
|
12785
|
+
total,
|
|
12786
|
+
notes
|
|
12787
|
+
}
|
|
12788
|
+
}
|
|
12789
|
+
]);
|
|
12790
|
+
if (session && total > 0 && completed >= total) {
|
|
12791
|
+
const archive = archivePlanState(session);
|
|
12792
|
+
if (archive) {
|
|
12793
|
+
setHistorical((prev) => [
|
|
12794
|
+
...prev,
|
|
12795
|
+
{
|
|
12796
|
+
id: `plan-archived-${Date.now()}`,
|
|
12797
|
+
role: "info",
|
|
12798
|
+
text: `\u25B8 plan complete \u2014 all ${total} step${total === 1 ? "" : "s"} done \xB7 archived`
|
|
12799
|
+
}
|
|
12800
|
+
]);
|
|
12801
|
+
}
|
|
12802
|
+
}
|
|
12803
|
+
if (typeof parsed.error === "string" && parsed.error.startsWith("PlanCheckpointError:")) {
|
|
12804
|
+
setPendingCheckpoint({ stepId, title, completed, total });
|
|
12805
|
+
}
|
|
12806
|
+
}
|
|
12807
|
+
} catch {
|
|
12808
|
+
}
|
|
12809
|
+
}
|
|
11633
12810
|
} else if (ev.role === "error") {
|
|
11634
12811
|
setHistorical((prev) => [
|
|
11635
12812
|
...prev,
|
|
@@ -11717,7 +12894,8 @@ function App({
|
|
|
11717
12894
|
refreshBalance,
|
|
11718
12895
|
refreshLatestVersion,
|
|
11719
12896
|
refreshModels,
|
|
11720
|
-
proArmed
|
|
12897
|
+
proArmed,
|
|
12898
|
+
persistPlanState
|
|
11721
12899
|
]
|
|
11722
12900
|
);
|
|
11723
12901
|
const handleShellConfirm = useCallback4(
|
|
@@ -11840,6 +13018,11 @@ ${body}`;
|
|
|
11840
13018
|
return;
|
|
11841
13019
|
}
|
|
11842
13020
|
setPendingPlan(null);
|
|
13021
|
+
planStepsRef.current = null;
|
|
13022
|
+
completedStepIdsRef.current = /* @__PURE__ */ new Set();
|
|
13023
|
+
planBodyRef.current = null;
|
|
13024
|
+
planSummaryRef.current = null;
|
|
13025
|
+
persistPlanState();
|
|
11843
13026
|
togglePlanMode(false);
|
|
11844
13027
|
const marker = "\u25B8 plan cancelled";
|
|
11845
13028
|
const synthetic = "The plan was cancelled. Drop it entirely. Ask me what I actually want before proposing another plan or making any changes.";
|
|
@@ -11854,7 +13037,7 @@ ${body}`;
|
|
|
11854
13037
|
await handleSubmit(synthetic);
|
|
11855
13038
|
}
|
|
11856
13039
|
},
|
|
11857
|
-
[pendingPlan, togglePlanMode, busy, loop, handleSubmit]
|
|
13040
|
+
[pendingPlan, togglePlanMode, busy, loop, handleSubmit, persistPlanState]
|
|
11858
13041
|
);
|
|
11859
13042
|
const handlePlanConfirmRef = useRef5(handlePlanConfirm);
|
|
11860
13043
|
useEffect5(() => {
|
|
@@ -11917,104 +13100,364 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
11917
13100
|
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
11918
13101
|
setStagedInput(null);
|
|
11919
13102
|
}, [stagedInput]);
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
13103
|
+
const handleCheckpointConfirm = useCallback4(
|
|
13104
|
+
async (choice) => {
|
|
13105
|
+
const snap = pendingCheckpoint;
|
|
13106
|
+
if (!snap) return;
|
|
13107
|
+
setPendingCheckpoint(null);
|
|
13108
|
+
if (choice === "revise") {
|
|
13109
|
+
setStagedCheckpointRevise(snap);
|
|
13110
|
+
return;
|
|
13111
|
+
}
|
|
13112
|
+
const label = snap.title ? `${snap.stepId} \xB7 ${snap.title}` : snap.stepId;
|
|
13113
|
+
const counter = snap.total > 0 ? ` (${snap.completed}/${snap.total})` : "";
|
|
13114
|
+
const { marker, synthetic } = choice === "continue" ? {
|
|
13115
|
+
marker: `\u25B8 continuing after ${label}${counter}`,
|
|
13116
|
+
synthetic: `Step ${label} is complete. Proceed with the next step of the approved plan. If no steps remain, summarize the whole run.`
|
|
13117
|
+
} : {
|
|
13118
|
+
marker: `\u25B8 plan stopped at ${label}${counter}`,
|
|
13119
|
+
synthetic: `The user stopped the plan after step ${label}. Do not run any more steps and do not call any tools. Write a short summary of what was completed across all finished steps and what's left unfinished.`
|
|
13120
|
+
};
|
|
13121
|
+
setHistorical((prev) => [
|
|
13122
|
+
...prev,
|
|
13123
|
+
{ id: `cp-${choice}-${Date.now()}`, role: "info", text: marker }
|
|
13124
|
+
]);
|
|
13125
|
+
if (busy) {
|
|
13126
|
+
loop.abort();
|
|
13127
|
+
setQueuedSubmit(synthetic);
|
|
13128
|
+
} else {
|
|
13129
|
+
await handleSubmit(synthetic);
|
|
13130
|
+
}
|
|
13131
|
+
},
|
|
13132
|
+
[pendingCheckpoint, busy, loop, handleSubmit]
|
|
13133
|
+
);
|
|
13134
|
+
const handleCheckpointConfirmRef = useRef5(handleCheckpointConfirm);
|
|
13135
|
+
useEffect5(() => {
|
|
13136
|
+
handleCheckpointConfirmRef.current = handleCheckpointConfirm;
|
|
13137
|
+
}, [handleCheckpointConfirm]);
|
|
13138
|
+
const stableHandleCheckpointConfirm = useCallback4(
|
|
13139
|
+
async (choice) => handleCheckpointConfirmRef.current(choice),
|
|
13140
|
+
[]
|
|
13141
|
+
);
|
|
13142
|
+
const handleCheckpointReviseSubmit = useCallback4(
|
|
13143
|
+
async (feedback) => {
|
|
13144
|
+
const snap = stagedCheckpointRevise;
|
|
13145
|
+
setStagedCheckpointRevise(null);
|
|
13146
|
+
if (!snap) return;
|
|
13147
|
+
const label = snap.title ? `${snap.stepId} \xB7 ${snap.title}` : snap.stepId;
|
|
13148
|
+
const trimmed = feedback.trim();
|
|
13149
|
+
const synthetic = trimmed ? `Step ${label} is complete. Before running the next step, adjust based on this user feedback:
|
|
13150
|
+
|
|
13151
|
+
${trimmed}
|
|
13152
|
+
|
|
13153
|
+
If the feedback only tweaks how you execute (extra constraints, style preferences), continue with the updated guidance. If it changes which steps run (skip a step, swap two steps, add a new step), call \`revise_plan\` with the updated remainingSteps \u2014 that pops a diff picker the user can accept or reject. Only call submit_plan again if the entire approach has fundamentally changed.` : `Step ${label} is complete. Continue with the current plan.`;
|
|
13154
|
+
const marker = trimmed ? `\u25B8 revising after ${label} \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}` : `\u25B8 continuing after ${label}`;
|
|
13155
|
+
setHistorical((prev) => [
|
|
13156
|
+
...prev,
|
|
13157
|
+
{ id: `cp-revise-${Date.now()}`, role: "info", text: marker }
|
|
13158
|
+
]);
|
|
13159
|
+
if (busy) {
|
|
13160
|
+
loop.abort();
|
|
13161
|
+
setQueuedSubmit(synthetic);
|
|
13162
|
+
} else {
|
|
13163
|
+
await handleSubmit(synthetic);
|
|
13164
|
+
}
|
|
13165
|
+
},
|
|
13166
|
+
[stagedCheckpointRevise, busy, loop, handleSubmit]
|
|
13167
|
+
);
|
|
13168
|
+
const handleCheckpointReviseCancel = useCallback4(() => {
|
|
13169
|
+
const snap = stagedCheckpointRevise;
|
|
13170
|
+
setStagedCheckpointRevise(null);
|
|
13171
|
+
if (snap) setPendingCheckpoint(snap);
|
|
13172
|
+
}, [stagedCheckpointRevise]);
|
|
13173
|
+
const handleChoiceConfirm = useCallback4(
|
|
13174
|
+
async (choice) => {
|
|
13175
|
+
const snap = pendingChoice;
|
|
13176
|
+
if (!snap) return;
|
|
13177
|
+
setPendingChoice(null);
|
|
13178
|
+
if (choice.kind === "custom") {
|
|
13179
|
+
setStagedChoiceCustom(snap);
|
|
13180
|
+
return;
|
|
13181
|
+
}
|
|
13182
|
+
if (choice.kind === "cancel") {
|
|
13183
|
+
const synthetic2 = "The user cancelled the choice. Don't act on any of the options you presented. Ask what they actually want before doing anything else.";
|
|
13184
|
+
setHistorical((prev) => [
|
|
13185
|
+
...prev,
|
|
13186
|
+
{ id: `choice-cancel-${Date.now()}`, role: "info", text: "\u25B8 choice cancelled" }
|
|
13187
|
+
]);
|
|
13188
|
+
if (busy) {
|
|
13189
|
+
loop.abort();
|
|
13190
|
+
setQueuedSubmit(synthetic2);
|
|
13191
|
+
} else {
|
|
13192
|
+
await handleSubmit(synthetic2);
|
|
11968
13193
|
}
|
|
13194
|
+
return;
|
|
11969
13195
|
}
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
13196
|
+
const picked = snap.options.find((o) => o.id === choice.optionId);
|
|
13197
|
+
const label = picked ? `${picked.id} \xB7 ${picked.title}` : choice.optionId;
|
|
13198
|
+
const synthetic = `The user picked option ${choice.optionId}${picked ? ` ("${picked.title}")` : ""}. Proceed with that branch. Do not re-ask the same question.`;
|
|
13199
|
+
setHistorical((prev) => [
|
|
13200
|
+
...prev,
|
|
13201
|
+
{ id: `choice-pick-${Date.now()}`, role: "info", text: `\u25B8 chose ${label}` }
|
|
13202
|
+
]);
|
|
13203
|
+
if (busy) {
|
|
13204
|
+
loop.abort();
|
|
13205
|
+
setQueuedSubmit(synthetic);
|
|
13206
|
+
} else {
|
|
13207
|
+
await handleSubmit(synthetic);
|
|
13208
|
+
}
|
|
13209
|
+
},
|
|
13210
|
+
[pendingChoice, busy, loop, handleSubmit]
|
|
13211
|
+
);
|
|
13212
|
+
const handleChoiceConfirmRef = useRef5(handleChoiceConfirm);
|
|
13213
|
+
useEffect5(() => {
|
|
13214
|
+
handleChoiceConfirmRef.current = handleChoiceConfirm;
|
|
13215
|
+
}, [handleChoiceConfirm]);
|
|
13216
|
+
const stableHandleChoiceConfirm = useCallback4(
|
|
13217
|
+
async (choice) => handleChoiceConfirmRef.current(choice),
|
|
13218
|
+
[]
|
|
13219
|
+
);
|
|
13220
|
+
const handleChoiceCustomSubmit = useCallback4(
|
|
13221
|
+
async (answer) => {
|
|
13222
|
+
setStagedChoiceCustom(null);
|
|
13223
|
+
const trimmed = answer.trim();
|
|
13224
|
+
const synthetic = trimmed ? `The user answered with a custom reply (none of the pre-defined options fit):
|
|
13225
|
+
|
|
13226
|
+
${trimmed}
|
|
13227
|
+
|
|
13228
|
+
Read it carefully and proceed \u2014 don't snap back to the options you listed unless the user's reply clearly maps to one.` : "The user pressed Enter without typing anything. Ask what they actually want.";
|
|
13229
|
+
const marker = trimmed ? `\u25B8 custom answer \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}` : "\u25B8 custom answer \u2014 (blank)";
|
|
13230
|
+
setHistorical((prev) => [
|
|
13231
|
+
...prev,
|
|
13232
|
+
{ id: `choice-custom-${Date.now()}`, role: "info", text: marker }
|
|
13233
|
+
]);
|
|
13234
|
+
if (busy) {
|
|
13235
|
+
loop.abort();
|
|
13236
|
+
setQueuedSubmit(synthetic);
|
|
13237
|
+
} else {
|
|
13238
|
+
await handleSubmit(synthetic);
|
|
13239
|
+
}
|
|
13240
|
+
},
|
|
13241
|
+
[busy, loop, handleSubmit]
|
|
13242
|
+
);
|
|
13243
|
+
const handleChoiceCustomCancel = useCallback4(() => {
|
|
13244
|
+
const snap = stagedChoiceCustom;
|
|
13245
|
+
setStagedChoiceCustom(null);
|
|
13246
|
+
if (snap) setPendingChoice(snap);
|
|
13247
|
+
}, [stagedChoiceCustom]);
|
|
13248
|
+
const handleReviseConfirm = useCallback4(
|
|
13249
|
+
async (choice) => {
|
|
13250
|
+
const snap = pendingRevision;
|
|
13251
|
+
if (!snap) return;
|
|
13252
|
+
setPendingRevision(null);
|
|
13253
|
+
if (choice === "reject") {
|
|
13254
|
+
const synthetic2 = "The user rejected the proposed plan revision. Don't apply it. Continue executing the original plan from the next pending step. If you genuinely cannot proceed without the change, stop and explain in plain text why.";
|
|
13255
|
+
setHistorical((prev) => [
|
|
13256
|
+
...prev,
|
|
13257
|
+
{ id: `revise-reject-${Date.now()}`, role: "info", text: "\u25B8 revision rejected" }
|
|
13258
|
+
]);
|
|
13259
|
+
if (busy) {
|
|
13260
|
+
loop.abort();
|
|
13261
|
+
setQueuedSubmit(synthetic2);
|
|
13262
|
+
} else {
|
|
13263
|
+
await handleSubmit(synthetic2);
|
|
13264
|
+
}
|
|
13265
|
+
return;
|
|
13266
|
+
}
|
|
13267
|
+
const completed = completedStepIdsRef.current;
|
|
13268
|
+
const oldSteps = planStepsRef.current ?? [];
|
|
13269
|
+
const donePrefix = oldSteps.filter((s) => completed.has(s.id));
|
|
13270
|
+
const merged = [...donePrefix];
|
|
13271
|
+
for (const s of snap.remainingSteps) {
|
|
13272
|
+
if (completed.has(s.id)) continue;
|
|
13273
|
+
merged.push(s);
|
|
13274
|
+
}
|
|
13275
|
+
planStepsRef.current = merged;
|
|
13276
|
+
persistPlanState();
|
|
13277
|
+
const removedCount = oldSteps.filter(
|
|
13278
|
+
(s) => !completed.has(s.id) && !snap.remainingSteps.some((n) => n.id === s.id)
|
|
13279
|
+
).length;
|
|
13280
|
+
const addedCount = snap.remainingSteps.filter(
|
|
13281
|
+
(s) => !oldSteps.some((o) => o.id === s.id)
|
|
13282
|
+
).length;
|
|
13283
|
+
const marker = `\u25B8 revision accepted \u2014 \u2212${removedCount} +${addedCount}: ${snap.reason}`;
|
|
13284
|
+
setHistorical((prev) => [
|
|
13285
|
+
...prev,
|
|
13286
|
+
{ id: `revise-accept-${Date.now()}`, role: "info", text: marker }
|
|
13287
|
+
]);
|
|
13288
|
+
const synthetic = `Revision accepted. The remaining plan is now:
|
|
13289
|
+
${snap.remainingSteps.map((s, i) => ` ${i + 1}. ${s.id} \xB7 ${s.title} \u2014 ${s.action}`).join(
|
|
13290
|
+
"\n"
|
|
13291
|
+
)}
|
|
13292
|
+
|
|
13293
|
+
Continue executing from the next pending step. Call mark_step_complete after each one as before.`;
|
|
13294
|
+
if (busy) {
|
|
13295
|
+
loop.abort();
|
|
13296
|
+
setQueuedSubmit(synthetic);
|
|
13297
|
+
} else {
|
|
13298
|
+
await handleSubmit(synthetic);
|
|
13299
|
+
}
|
|
13300
|
+
},
|
|
13301
|
+
[pendingRevision, busy, loop, handleSubmit, persistPlanState]
|
|
13302
|
+
);
|
|
13303
|
+
const handleReviseConfirmRef = useRef5(handleReviseConfirm);
|
|
13304
|
+
useEffect5(() => {
|
|
13305
|
+
handleReviseConfirmRef.current = handleReviseConfirm;
|
|
13306
|
+
}, [handleReviseConfirm]);
|
|
13307
|
+
const stableHandleReviseConfirm = useCallback4(
|
|
13308
|
+
async (choice) => handleReviseConfirmRef.current(choice),
|
|
13309
|
+
[]
|
|
13310
|
+
);
|
|
13311
|
+
return /* @__PURE__ */ React20.createElement(
|
|
13312
|
+
TickerProvider,
|
|
11998
13313
|
{
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
13314
|
+
disabled: PLAIN_UI || !!pendingPlan || !!pendingShell || !!pendingEditReview || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
|
|
13315
|
+
},
|
|
13316
|
+
/* @__PURE__ */ React20.createElement(Box19, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(
|
|
13317
|
+
StatsPanel,
|
|
13318
|
+
{
|
|
13319
|
+
summary,
|
|
13320
|
+
model: loop.model,
|
|
13321
|
+
prefixHash,
|
|
13322
|
+
harvestOn: loop.harvestEnabled,
|
|
13323
|
+
branchBudget: loop.branchOptions.budget,
|
|
13324
|
+
reasoningEffort: loop.reasoningEffort,
|
|
13325
|
+
planMode,
|
|
13326
|
+
editMode: codeMode ? editMode : void 0,
|
|
13327
|
+
balance,
|
|
13328
|
+
busy,
|
|
13329
|
+
updateAvailable,
|
|
13330
|
+
proArmed,
|
|
13331
|
+
escalated: turnOnPro
|
|
13332
|
+
}
|
|
13333
|
+
), /* @__PURE__ */ React20.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React20.createElement(EventRow, { key: item.id, event: item, projectRoot: hookCwd })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && streaming ? /* @__PURE__ */ React20.createElement(Box19, { marginY: 1 }, /* @__PURE__ */ React20.createElement(EventRow, { event: streaming, projectRoot: hookCwd })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && ongoingTool ? /* @__PURE__ */ React20.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && subagentActivity ? /* @__PURE__ */ React20.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !ongoingTool && statusLine ? /* @__PURE__ */ React20.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision ? /* @__PURE__ */ React20.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React20.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React20.createElement(
|
|
13334
|
+
PlanRefineInput,
|
|
13335
|
+
{
|
|
13336
|
+
mode: stagedInput.mode,
|
|
13337
|
+
onSubmit: handleStagedInputSubmit,
|
|
13338
|
+
onCancel: handleStagedInputCancel
|
|
13339
|
+
}
|
|
13340
|
+
) : stagedCheckpointRevise ? /* @__PURE__ */ React20.createElement(
|
|
13341
|
+
PlanRefineInput,
|
|
13342
|
+
{
|
|
13343
|
+
mode: "checkpoint-revise",
|
|
13344
|
+
onSubmit: handleCheckpointReviseSubmit,
|
|
13345
|
+
onCancel: handleCheckpointReviseCancel
|
|
13346
|
+
}
|
|
13347
|
+
) : stagedChoiceCustom ? /* @__PURE__ */ React20.createElement(
|
|
13348
|
+
PlanRefineInput,
|
|
13349
|
+
{
|
|
13350
|
+
mode: "choice-custom",
|
|
13351
|
+
onSubmit: handleChoiceCustomSubmit,
|
|
13352
|
+
onCancel: handleChoiceCustomCancel
|
|
13353
|
+
}
|
|
13354
|
+
) : pendingChoice ? /* @__PURE__ */ React20.createElement(
|
|
13355
|
+
ChoiceConfirm,
|
|
13356
|
+
{
|
|
13357
|
+
question: pendingChoice.question,
|
|
13358
|
+
options: pendingChoice.options,
|
|
13359
|
+
allowCustom: pendingChoice.allowCustom,
|
|
13360
|
+
onChoose: stableHandleChoiceConfirm
|
|
13361
|
+
}
|
|
13362
|
+
) : pendingRevision ? /* @__PURE__ */ React20.createElement(
|
|
13363
|
+
PlanReviseConfirm,
|
|
13364
|
+
{
|
|
13365
|
+
reason: pendingRevision.reason,
|
|
13366
|
+
oldRemaining: (planStepsRef.current ?? []).filter(
|
|
13367
|
+
(s) => !completedStepIdsRef.current.has(s.id)
|
|
13368
|
+
),
|
|
13369
|
+
newRemaining: pendingRevision.remainingSteps,
|
|
13370
|
+
summary: pendingRevision.summary,
|
|
13371
|
+
onChoose: stableHandleReviseConfirm
|
|
13372
|
+
}
|
|
13373
|
+
) : pendingCheckpoint ? /* @__PURE__ */ React20.createElement(
|
|
13374
|
+
PlanCheckpointConfirm,
|
|
13375
|
+
{
|
|
13376
|
+
stepId: pendingCheckpoint.stepId,
|
|
13377
|
+
title: pendingCheckpoint.title,
|
|
13378
|
+
completed: pendingCheckpoint.completed,
|
|
13379
|
+
total: pendingCheckpoint.total,
|
|
13380
|
+
steps: planStepsRef.current ?? void 0,
|
|
13381
|
+
completedStepIds: completedStepIdsRef.current,
|
|
13382
|
+
onChoose: stableHandleCheckpointConfirm
|
|
13383
|
+
}
|
|
13384
|
+
) : pendingPlan ? /* @__PURE__ */ React20.createElement(
|
|
13385
|
+
PlanConfirm,
|
|
13386
|
+
{
|
|
13387
|
+
plan: pendingPlan,
|
|
13388
|
+
steps: planStepsRef.current ?? void 0,
|
|
13389
|
+
summary: planSummaryRef.current ?? void 0,
|
|
13390
|
+
onChoose: stableHandlePlanConfirm,
|
|
13391
|
+
projectRoot: hookCwd
|
|
13392
|
+
}
|
|
13393
|
+
) : pendingShell ? /* @__PURE__ */ React20.createElement(
|
|
13394
|
+
ShellConfirm,
|
|
13395
|
+
{
|
|
13396
|
+
command: pendingShell.command,
|
|
13397
|
+
allowPrefix: derivePrefix(pendingShell.command),
|
|
13398
|
+
kind: pendingShell.kind,
|
|
13399
|
+
onChoose: handleShellConfirm
|
|
13400
|
+
}
|
|
13401
|
+
) : pendingEditReview ? /* @__PURE__ */ React20.createElement(
|
|
13402
|
+
EditConfirm,
|
|
13403
|
+
{
|
|
13404
|
+
block: pendingEditReview,
|
|
13405
|
+
onChoose: (choice) => {
|
|
13406
|
+
const resolve8 = editReviewResolveRef.current;
|
|
13407
|
+
if (resolve8) {
|
|
13408
|
+
editReviewResolveRef.current = null;
|
|
13409
|
+
resolve8(choice);
|
|
13410
|
+
}
|
|
13411
|
+
}
|
|
13412
|
+
}
|
|
13413
|
+
) : /* @__PURE__ */ React20.createElement(React20.Fragment, null, codeMode ? /* @__PURE__ */ React20.createElement(
|
|
13414
|
+
ModeStatusBar,
|
|
13415
|
+
{
|
|
13416
|
+
editMode,
|
|
13417
|
+
pendingCount,
|
|
13418
|
+
flash: modeFlash,
|
|
13419
|
+
planMode,
|
|
13420
|
+
undoArmed: !!undoBanner || hasUndoable(),
|
|
13421
|
+
jobs: codeMode.jobs
|
|
13422
|
+
}
|
|
13423
|
+
) : null, /* @__PURE__ */ React20.createElement(
|
|
13424
|
+
PromptInput,
|
|
13425
|
+
{
|
|
13426
|
+
value: input,
|
|
13427
|
+
onChange: setInput,
|
|
13428
|
+
onSubmit: handleSubmit,
|
|
13429
|
+
disabled: busy
|
|
13430
|
+
}
|
|
13431
|
+
), /* @__PURE__ */ React20.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React20.createElement(
|
|
13432
|
+
AtMentionSuggestions,
|
|
13433
|
+
{
|
|
13434
|
+
matches: atMatches,
|
|
13435
|
+
selectedIndex: atSelected,
|
|
13436
|
+
query: atPicker?.query ?? ""
|
|
13437
|
+
}
|
|
13438
|
+
), slashArgContext ? /* @__PURE__ */ React20.createElement(
|
|
13439
|
+
SlashArgPicker,
|
|
13440
|
+
{
|
|
13441
|
+
matches: slashArgMatches,
|
|
13442
|
+
selectedIndex: slashArgSelected,
|
|
13443
|
+
spec: slashArgContext.spec,
|
|
13444
|
+
kind: slashArgContext.kind,
|
|
13445
|
+
partial: slashArgContext.partial
|
|
13446
|
+
}
|
|
13447
|
+
) : null))
|
|
13448
|
+
);
|
|
12006
13449
|
}
|
|
12007
13450
|
|
|
12008
13451
|
// src/cli/ui/SessionPicker.tsx
|
|
12009
|
-
import { Box as
|
|
12010
|
-
import
|
|
13452
|
+
import { Box as Box20, Text as Text19 } from "ink";
|
|
13453
|
+
import React21 from "react";
|
|
12011
13454
|
function SessionPicker({
|
|
12012
13455
|
sessionName,
|
|
12013
13456
|
messageCount,
|
|
12014
13457
|
lastActive,
|
|
12015
13458
|
onChoose
|
|
12016
13459
|
}) {
|
|
12017
|
-
return /* @__PURE__ */
|
|
13460
|
+
return /* @__PURE__ */ React21.createElement(Box20, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React21.createElement(Box20, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text19, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React21.createElement(Text19, { dimColor: true }, ` \xB7 last active ${relativeTime2(lastActive)}`)), /* @__PURE__ */ React21.createElement(
|
|
12018
13461
|
SingleSelect,
|
|
12019
13462
|
{
|
|
12020
13463
|
initialValue: "new",
|
|
@@ -12037,9 +13480,9 @@ function SessionPicker({
|
|
|
12037
13480
|
],
|
|
12038
13481
|
onSubmit: (v) => onChoose(v)
|
|
12039
13482
|
}
|
|
12040
|
-
), /* @__PURE__ */
|
|
13483
|
+
), /* @__PURE__ */ React21.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text19, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
|
|
12041
13484
|
}
|
|
12042
|
-
function
|
|
13485
|
+
function relativeTime2(date) {
|
|
12043
13486
|
const ms = Date.now() - date.getTime();
|
|
12044
13487
|
const mins = Math.floor(ms / 6e4);
|
|
12045
13488
|
if (mins < 1) return "just now";
|
|
@@ -12053,9 +13496,9 @@ function relativeTime(date) {
|
|
|
12053
13496
|
}
|
|
12054
13497
|
|
|
12055
13498
|
// src/cli/ui/Setup.tsx
|
|
12056
|
-
import { Box as
|
|
13499
|
+
import { Box as Box21, Text as Text20, useApp as useApp2 } from "ink";
|
|
12057
13500
|
import TextInput from "ink-text-input";
|
|
12058
|
-
import
|
|
13501
|
+
import React22, { useState as useState11 } from "react";
|
|
12059
13502
|
function Setup({ onReady }) {
|
|
12060
13503
|
const [value, setValue] = useState11("");
|
|
12061
13504
|
const [error, setError] = useState11(null);
|
|
@@ -12079,7 +13522,7 @@ function Setup({ onReady }) {
|
|
|
12079
13522
|
}
|
|
12080
13523
|
onReady(trimmed);
|
|
12081
13524
|
};
|
|
12082
|
-
return /* @__PURE__ */
|
|
13525
|
+
return /* @__PURE__ */ React22.createElement(Box21, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React22.createElement(Text20, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React22.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text20, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React22.createElement(Text20, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React22.createElement(Text20, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React22.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text20, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React22.createElement(
|
|
12083
13526
|
TextInput,
|
|
12084
13527
|
{
|
|
12085
13528
|
value,
|
|
@@ -12088,7 +13531,7 @@ function Setup({ onReady }) {
|
|
|
12088
13531
|
mask: "\u2022",
|
|
12089
13532
|
placeholder: "sk-..."
|
|
12090
13533
|
}
|
|
12091
|
-
)), error ? /* @__PURE__ */
|
|
13534
|
+
)), error ? /* @__PURE__ */ React22.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text20, { color: "red" }, error)) : value ? /* @__PURE__ */ React22.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text20, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React22.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text20, { dimColor: true }, "(Type /exit to abort.)")));
|
|
12092
13535
|
}
|
|
12093
13536
|
|
|
12094
13537
|
// src/cli/commands/chat.tsx
|
|
@@ -12104,7 +13547,7 @@ function Root({
|
|
|
12104
13547
|
const [key, setKey] = useState12(initialKey);
|
|
12105
13548
|
const [pending, setPending] = useState12(sessionPreview);
|
|
12106
13549
|
if (!key) {
|
|
12107
|
-
return /* @__PURE__ */
|
|
13550
|
+
return /* @__PURE__ */ React23.createElement(
|
|
12108
13551
|
Setup,
|
|
12109
13552
|
{
|
|
12110
13553
|
onReady: (k) => {
|
|
@@ -12116,7 +13559,7 @@ function Root({
|
|
|
12116
13559
|
}
|
|
12117
13560
|
process.env.DEEPSEEK_API_KEY = key;
|
|
12118
13561
|
if (pending && appProps.session) {
|
|
12119
|
-
return /* @__PURE__ */
|
|
13562
|
+
return /* @__PURE__ */ React23.createElement(
|
|
12120
13563
|
SessionPicker,
|
|
12121
13564
|
{
|
|
12122
13565
|
sessionName: appProps.session,
|
|
@@ -12131,7 +13574,7 @@ function Root({
|
|
|
12131
13574
|
}
|
|
12132
13575
|
);
|
|
12133
13576
|
}
|
|
12134
|
-
return /* @__PURE__ */
|
|
13577
|
+
return /* @__PURE__ */ React23.createElement(
|
|
12135
13578
|
App,
|
|
12136
13579
|
{
|
|
12137
13580
|
model: appProps.model,
|
|
@@ -12222,20 +13665,21 @@ async function chatCommand(opts) {
|
|
|
12222
13665
|
if (!opts.seedTools) {
|
|
12223
13666
|
if (!tools) tools = new ToolRegistry();
|
|
12224
13667
|
registerMemoryTools(tools, {});
|
|
13668
|
+
registerChoiceTool(tools);
|
|
12225
13669
|
}
|
|
12226
13670
|
let sessionPreview;
|
|
12227
13671
|
if (opts.session && !opts.forceResume && !opts.forceNew) {
|
|
12228
13672
|
const prior = loadSessionMessages(opts.session);
|
|
12229
13673
|
if (prior.length > 0) {
|
|
12230
13674
|
const p = sessionPath(opts.session);
|
|
12231
|
-
const mtime =
|
|
13675
|
+
const mtime = existsSync12(p) ? statSync7(p).mtime : /* @__PURE__ */ new Date();
|
|
12232
13676
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
12233
13677
|
}
|
|
12234
13678
|
} else if (opts.session && opts.forceNew) {
|
|
12235
13679
|
rewriteSession(opts.session, []);
|
|
12236
13680
|
}
|
|
12237
13681
|
const { waitUntilExit } = render(
|
|
12238
|
-
/* @__PURE__ */
|
|
13682
|
+
/* @__PURE__ */ React23.createElement(
|
|
12239
13683
|
Root,
|
|
12240
13684
|
{
|
|
12241
13685
|
initialKey,
|
|
@@ -12259,11 +13703,11 @@ async function chatCommand(opts) {
|
|
|
12259
13703
|
}
|
|
12260
13704
|
|
|
12261
13705
|
// src/cli/commands/code.tsx
|
|
12262
|
-
import { basename, resolve as resolve7 } from "path";
|
|
13706
|
+
import { basename as basename2, resolve as resolve7 } from "path";
|
|
12263
13707
|
async function codeCommand(opts = {}) {
|
|
12264
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
13708
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-2OABSPAW.js");
|
|
12265
13709
|
const rootDir = resolve7(opts.dir ?? process.cwd());
|
|
12266
|
-
const session = opts.noSession ? void 0 : `code-${sanitizeName(
|
|
13710
|
+
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
12267
13711
|
const tools = new ToolRegistry();
|
|
12268
13712
|
registerFilesystemTools(tools, { rootDir });
|
|
12269
13713
|
const jobs2 = new JobRegistry();
|
|
@@ -12278,6 +13722,7 @@ async function codeCommand(opts = {}) {
|
|
|
12278
13722
|
jobs: jobs2
|
|
12279
13723
|
});
|
|
12280
13724
|
registerPlanTool(tools);
|
|
13725
|
+
registerChoiceTool(tools);
|
|
12281
13726
|
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
12282
13727
|
process.stderr.write(
|
|
12283
13728
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
@@ -12303,37 +13748,37 @@ async function codeCommand(opts = {}) {
|
|
|
12303
13748
|
}
|
|
12304
13749
|
|
|
12305
13750
|
// src/cli/commands/diff.ts
|
|
12306
|
-
import { writeFileSync as
|
|
12307
|
-
import { basename as
|
|
13751
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
13752
|
+
import { basename as basename3 } from "path";
|
|
12308
13753
|
import { render as render2 } from "ink";
|
|
12309
|
-
import
|
|
13754
|
+
import React26 from "react";
|
|
12310
13755
|
|
|
12311
13756
|
// src/cli/ui/DiffApp.tsx
|
|
12312
|
-
import { Box as
|
|
12313
|
-
import
|
|
13757
|
+
import { Box as Box23, Static as Static2, Text as Text22, useApp as useApp3, useInput as useInput6 } from "ink";
|
|
13758
|
+
import React25, { useState as useState13 } from "react";
|
|
12314
13759
|
|
|
12315
13760
|
// src/cli/ui/RecordView.tsx
|
|
12316
|
-
import { Box as
|
|
12317
|
-
import
|
|
13761
|
+
import { Box as Box22, Text as Text21 } from "ink";
|
|
13762
|
+
import React24 from "react";
|
|
12318
13763
|
function RecordView({ rec, compact: compact2 = false }) {
|
|
12319
13764
|
const toolArgsMax = compact2 ? 120 : 200;
|
|
12320
13765
|
const toolContentMax = compact2 ? 200 : 400;
|
|
12321
13766
|
if (rec.role === "user") {
|
|
12322
|
-
return /* @__PURE__ */
|
|
13767
|
+
return /* @__PURE__ */ React24.createElement(Box22, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text21, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React24.createElement(Text21, null, rec.content));
|
|
12323
13768
|
}
|
|
12324
13769
|
if (rec.role === "assistant_final") {
|
|
12325
|
-
return /* @__PURE__ */
|
|
13770
|
+
return /* @__PURE__ */ React24.createElement(Box22, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React24.createElement(Box22, null, /* @__PURE__ */ React24.createElement(Text21, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React24.createElement(Text21, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React24.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React24.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React24.createElement(Text21, null, rec.content) : /* @__PURE__ */ React24.createElement(Text21, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
12326
13771
|
}
|
|
12327
13772
|
if (rec.role === "tool") {
|
|
12328
|
-
return /* @__PURE__ */
|
|
13773
|
+
return /* @__PURE__ */ React24.createElement(Box22, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text21, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React24.createElement(Text21, { dimColor: true }, " args: ", truncate2(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React24.createElement(Text21, { dimColor: true }, " \u2192 ", truncate2(rec.content, toolContentMax)));
|
|
12329
13774
|
}
|
|
12330
13775
|
if (rec.role === "error") {
|
|
12331
|
-
return /* @__PURE__ */
|
|
13776
|
+
return /* @__PURE__ */ React24.createElement(Box22, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text21, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React24.createElement(Text21, { color: "red" }, rec.error ?? rec.content));
|
|
12332
13777
|
}
|
|
12333
13778
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
12334
13779
|
return null;
|
|
12335
13780
|
}
|
|
12336
|
-
return /* @__PURE__ */
|
|
13781
|
+
return /* @__PURE__ */ React24.createElement(Box22, null, /* @__PURE__ */ React24.createElement(Text21, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
12337
13782
|
}
|
|
12338
13783
|
function CacheBadge({ usage }) {
|
|
12339
13784
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -12342,9 +13787,9 @@ function CacheBadge({ usage }) {
|
|
|
12342
13787
|
if (total === 0) return null;
|
|
12343
13788
|
const pct2 = hit / total * 100;
|
|
12344
13789
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
12345
|
-
return /* @__PURE__ */
|
|
13790
|
+
return /* @__PURE__ */ React24.createElement(Text21, null, /* @__PURE__ */ React24.createElement(Text21, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React24.createElement(Text21, { color }, pct2.toFixed(1), "%"));
|
|
12346
13791
|
}
|
|
12347
|
-
function
|
|
13792
|
+
function truncate2(s, max) {
|
|
12348
13793
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
12349
13794
|
}
|
|
12350
13795
|
|
|
@@ -12376,7 +13821,7 @@ function DiffApp({ report }) {
|
|
|
12376
13821
|
}
|
|
12377
13822
|
});
|
|
12378
13823
|
const pair = report.pairs[idx];
|
|
12379
|
-
return /* @__PURE__ */
|
|
13824
|
+
return /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(DiffHeader, { report }), /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React25.createElement(Text22, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React25.createElement(Text22, null, pair ? /* @__PURE__ */ React25.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React25.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React25.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React25.createElement(Text22, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React25.createElement(Text22, null, pair.divergenceNote)) : null, /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "j"), "/", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "k"), "/", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "N"), "/", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "g"), "/", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React25.createElement(Text22, { bold: true }, "q"), " ", "quit")));
|
|
12380
13825
|
}
|
|
12381
13826
|
function DiffHeader({ report }) {
|
|
12382
13827
|
const a = report.a;
|
|
@@ -12394,15 +13839,15 @@ function DiffHeader({ report }) {
|
|
|
12394
13839
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
12395
13840
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
12396
13841
|
}
|
|
12397
|
-
return /* @__PURE__ */
|
|
13842
|
+
return /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React25.createElement(Box23, { justifyContent: "space-between" }, /* @__PURE__ */ React25.createElement(Text22, null, /* @__PURE__ */ React25.createElement(Text22, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React25.createElement(Text22, { color: "blue" }, a.label), /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, " vs B="), /* @__PURE__ */ React25.createElement(Text22, { color: "magenta" }, b.label)), /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React25.createElement(Text22, null, /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, "cache "), /* @__PURE__ */ React25.createElement(Text22, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React25.createElement(Text22, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React25.createElement(Text22, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React25.createElement(Text22, null, /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, "cost "), /* @__PURE__ */ React25.createElement(Text22, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React25.createElement(Text22, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React25.createElement(Text22, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React25.createElement(Text22, null, /* @__PURE__ */ React25.createElement(Text22, { dimColor: true }, "model calls "), /* @__PURE__ */ React25.createElement(Text22, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text22, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
12398
13843
|
}
|
|
12399
13844
|
function Pane({
|
|
12400
13845
|
label,
|
|
12401
13846
|
headerColor,
|
|
12402
13847
|
records
|
|
12403
13848
|
}) {
|
|
12404
|
-
return /* @__PURE__ */
|
|
12405
|
-
|
|
13849
|
+
return /* @__PURE__ */ React25.createElement(
|
|
13850
|
+
Box23,
|
|
12406
13851
|
{
|
|
12407
13852
|
flexDirection: "column",
|
|
12408
13853
|
flexGrow: 1,
|
|
@@ -12410,21 +13855,21 @@ function Pane({
|
|
|
12410
13855
|
borderStyle: "single",
|
|
12411
13856
|
borderColor: headerColor
|
|
12412
13857
|
},
|
|
12413
|
-
/* @__PURE__ */
|
|
12414
|
-
records.length === 0 ? /* @__PURE__ */
|
|
13858
|
+
/* @__PURE__ */ React25.createElement(Text22, { color: headerColor, bold: true }, label),
|
|
13859
|
+
records.length === 0 ? /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text22, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React25.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React25.createElement(RecordView, { key, rec, compact: true }))
|
|
12415
13860
|
);
|
|
12416
13861
|
}
|
|
12417
13862
|
function KindBadge({ kind }) {
|
|
12418
13863
|
if (kind === "match") {
|
|
12419
|
-
return /* @__PURE__ */
|
|
13864
|
+
return /* @__PURE__ */ React25.createElement(Text22, { color: "green" }, "\u2713 match");
|
|
12420
13865
|
}
|
|
12421
13866
|
if (kind === "diverge") {
|
|
12422
|
-
return /* @__PURE__ */
|
|
13867
|
+
return /* @__PURE__ */ React25.createElement(Text22, { color: "yellow" }, "\u2605 diverge");
|
|
12423
13868
|
}
|
|
12424
13869
|
if (kind === "only_in_a") {
|
|
12425
|
-
return /* @__PURE__ */
|
|
13870
|
+
return /* @__PURE__ */ React25.createElement(Text22, { color: "blue" }, "\u2190 only in A");
|
|
12426
13871
|
}
|
|
12427
|
-
return /* @__PURE__ */
|
|
13872
|
+
return /* @__PURE__ */ React25.createElement(Text22, { color: "magenta" }, "\u2192 only in B");
|
|
12428
13873
|
}
|
|
12429
13874
|
function paneRecords(pair, side) {
|
|
12430
13875
|
if (!pair) return [];
|
|
@@ -12440,8 +13885,8 @@ async function diffCommand(opts) {
|
|
|
12440
13885
|
const aParsed = readTranscript(opts.a);
|
|
12441
13886
|
const bParsed = readTranscript(opts.b);
|
|
12442
13887
|
const report = diffTranscripts(
|
|
12443
|
-
{ label: opts.labelA ??
|
|
12444
|
-
{ label: opts.labelB ??
|
|
13888
|
+
{ label: opts.labelA ?? basename3(opts.a), parsed: aParsed },
|
|
13889
|
+
{ label: opts.labelB ?? basename3(opts.b), parsed: bParsed }
|
|
12445
13890
|
);
|
|
12446
13891
|
const wantMarkdown = !!opts.mdPath;
|
|
12447
13892
|
const wantPrint = opts.print || !process.stdout.isTTY;
|
|
@@ -12449,13 +13894,13 @@ async function diffCommand(opts) {
|
|
|
12449
13894
|
if (wantMarkdown) {
|
|
12450
13895
|
console.log(renderSummaryTable(report));
|
|
12451
13896
|
const md = renderMarkdown(report);
|
|
12452
|
-
|
|
13897
|
+
writeFileSync7(opts.mdPath, md, "utf8");
|
|
12453
13898
|
console.log(`
|
|
12454
13899
|
markdown report written to ${opts.mdPath}`);
|
|
12455
13900
|
return;
|
|
12456
13901
|
}
|
|
12457
13902
|
if (wantTui) {
|
|
12458
|
-
const { waitUntilExit } = render2(
|
|
13903
|
+
const { waitUntilExit } = render2(React26.createElement(DiffApp, { report }), {
|
|
12459
13904
|
exitOnCtrlC: true,
|
|
12460
13905
|
patchConsole: false
|
|
12461
13906
|
});
|
|
@@ -12596,11 +14041,11 @@ function pad2(s, width) {
|
|
|
12596
14041
|
|
|
12597
14042
|
// src/cli/commands/replay.ts
|
|
12598
14043
|
import { render as render3 } from "ink";
|
|
12599
|
-
import
|
|
14044
|
+
import React28 from "react";
|
|
12600
14045
|
|
|
12601
14046
|
// src/cli/ui/ReplayApp.tsx
|
|
12602
|
-
import { Box as
|
|
12603
|
-
import
|
|
14047
|
+
import { Box as Box24, Static as Static3, Text as Text23, useApp as useApp4, useInput as useInput7 } from "ink";
|
|
14048
|
+
import React27, { useMemo as useMemo4, useState as useState14 } from "react";
|
|
12604
14049
|
function ReplayApp({ meta, pages }) {
|
|
12605
14050
|
const { exit: exit2 } = useApp4();
|
|
12606
14051
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
@@ -12640,14 +14085,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
12640
14085
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
12641
14086
|
const currentPage = pages[idx];
|
|
12642
14087
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
12643
|
-
return /* @__PURE__ */
|
|
14088
|
+
return /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column" }, /* @__PURE__ */ React27.createElement(
|
|
12644
14089
|
StatsPanel,
|
|
12645
14090
|
{
|
|
12646
14091
|
summary,
|
|
12647
14092
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
12648
14093
|
prefixHash
|
|
12649
14094
|
}
|
|
12650
|
-
), /* @__PURE__ */
|
|
14095
|
+
), /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React27.createElement(Box24, { justifyContent: "space-between" }, /* @__PURE__ */ React27.createElement(Text23, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React27.createElement(Text23, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React27.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React27.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React27.createElement(Text23, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React27.createElement(Text23, { dimColor: true }, /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "j"), "/", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "k"), "/", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React27.createElement(Text23, { bold: true }, "q"), " quit")));
|
|
12651
14096
|
}
|
|
12652
14097
|
|
|
12653
14098
|
// src/cli/commands/replay.ts
|
|
@@ -12659,7 +14104,7 @@ async function replayCommand(opts) {
|
|
|
12659
14104
|
}
|
|
12660
14105
|
const { parsed } = replayFromFile(opts.path);
|
|
12661
14106
|
const pages = groupRecordsByTurn(parsed.records);
|
|
12662
|
-
const { waitUntilExit } = render3(
|
|
14107
|
+
const { waitUntilExit } = render3(React28.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
12663
14108
|
exitOnCtrlC: true,
|
|
12664
14109
|
patchConsole: false
|
|
12665
14110
|
});
|
|
@@ -12944,32 +14389,32 @@ function renderMessage(msg, turnIdx, verbose) {
|
|
|
12944
14389
|
if (verbose && msg.tool_calls?.length) {
|
|
12945
14390
|
for (const tc of msg.tool_calls) {
|
|
12946
14391
|
console.log(
|
|
12947
|
-
` \u2192 call ${tc.function?.name} ${
|
|
14392
|
+
` \u2192 call ${tc.function?.name} ${truncate3(tc.function?.arguments ?? "", 80)}`
|
|
12948
14393
|
);
|
|
12949
14394
|
}
|
|
12950
14395
|
}
|
|
12951
14396
|
} else if (msg.role === "tool") {
|
|
12952
|
-
console.log(`${turn} TOOL ${msg.name ?? "?"}: ${
|
|
14397
|
+
console.log(`${turn} TOOL ${msg.name ?? "?"}: ${truncate3(flat, 160)}`);
|
|
12953
14398
|
} else if (msg.role === "system") {
|
|
12954
|
-
if (verbose) console.log(`${turn} SYSTEM: ${
|
|
14399
|
+
if (verbose) console.log(`${turn} SYSTEM: ${truncate3(flat, 160)}`);
|
|
12955
14400
|
}
|
|
12956
14401
|
}
|
|
12957
14402
|
function oneLine3(s, max = 200) {
|
|
12958
14403
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
12959
14404
|
return collapsed.length > max ? `${collapsed.slice(0, max)}\u2026` : collapsed;
|
|
12960
14405
|
}
|
|
12961
|
-
function
|
|
14406
|
+
function truncate3(s, max) {
|
|
12962
14407
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026`;
|
|
12963
14408
|
}
|
|
12964
14409
|
|
|
12965
14410
|
// src/cli/commands/setup.tsx
|
|
12966
14411
|
import { render as render4 } from "ink";
|
|
12967
|
-
import
|
|
14412
|
+
import React30 from "react";
|
|
12968
14413
|
|
|
12969
14414
|
// src/cli/ui/Wizard.tsx
|
|
12970
|
-
import { Box as
|
|
14415
|
+
import { Box as Box25, Text as Text24, useApp as useApp5, useInput as useInput8 } from "ink";
|
|
12971
14416
|
import TextInput2 from "ink-text-input";
|
|
12972
|
-
import
|
|
14417
|
+
import React29, { useState as useState15 } from "react";
|
|
12973
14418
|
|
|
12974
14419
|
// src/cli/ui/presets.ts
|
|
12975
14420
|
var PRESETS = {
|
|
@@ -13017,7 +14462,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13017
14462
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
13018
14463
|
});
|
|
13019
14464
|
if (step === "apiKey") {
|
|
13020
|
-
return /* @__PURE__ */
|
|
14465
|
+
return /* @__PURE__ */ React29.createElement(
|
|
13021
14466
|
ApiKeyStep,
|
|
13022
14467
|
{
|
|
13023
14468
|
onSubmit: (key) => {
|
|
@@ -13031,7 +14476,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13031
14476
|
);
|
|
13032
14477
|
}
|
|
13033
14478
|
if (step === "preset") {
|
|
13034
|
-
return /* @__PURE__ */
|
|
14479
|
+
return /* @__PURE__ */ React29.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React29.createElement(
|
|
13035
14480
|
SingleSelect,
|
|
13036
14481
|
{
|
|
13037
14482
|
items: presetItems(),
|
|
@@ -13041,10 +14486,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13041
14486
|
setStep("mcp");
|
|
13042
14487
|
}
|
|
13043
14488
|
}
|
|
13044
|
-
), /* @__PURE__ */
|
|
14489
|
+
), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
13045
14490
|
}
|
|
13046
14491
|
if (step === "mcp") {
|
|
13047
|
-
return /* @__PURE__ */
|
|
14492
|
+
return /* @__PURE__ */ React29.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React29.createElement(
|
|
13048
14493
|
MultiSelect,
|
|
13049
14494
|
{
|
|
13050
14495
|
items: mcpItems(),
|
|
@@ -13069,7 +14514,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13069
14514
|
}
|
|
13070
14515
|
const currentName = pending[0];
|
|
13071
14516
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
13072
|
-
return /* @__PURE__ */
|
|
14517
|
+
return /* @__PURE__ */ React29.createElement(
|
|
13073
14518
|
McpArgsStep,
|
|
13074
14519
|
{
|
|
13075
14520
|
entry,
|
|
@@ -13087,7 +14532,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13087
14532
|
}
|
|
13088
14533
|
if (step === "review") {
|
|
13089
14534
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
13090
|
-
return /* @__PURE__ */
|
|
14535
|
+
return /* @__PURE__ */ React29.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React29.createElement(Box25, { flexDirection: "column" }, /* @__PURE__ */ React29.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React29.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React29.createElement(
|
|
13091
14536
|
SummaryLine,
|
|
13092
14537
|
{
|
|
13093
14538
|
label: "MCP",
|
|
@@ -13095,8 +14540,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13095
14540
|
}
|
|
13096
14541
|
), specs.map((spec, i) => (
|
|
13097
14542
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
13098
|
-
/* @__PURE__ */
|
|
13099
|
-
)), /* @__PURE__ */
|
|
14543
|
+
/* @__PURE__ */ React29.createElement(Box25, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "\xB7 ", spec))
|
|
14544
|
+
)), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "red" }, error)) : null, /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React29.createElement(
|
|
13100
14545
|
ReviewConfirm,
|
|
13101
14546
|
{
|
|
13102
14547
|
onConfirm: () => {
|
|
@@ -13122,7 +14567,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
13122
14567
|
}
|
|
13123
14568
|
));
|
|
13124
14569
|
}
|
|
13125
|
-
return /* @__PURE__ */
|
|
14570
|
+
return /* @__PURE__ */ React29.createElement(Box25, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React29.createElement(ExitOnEnter, { onExit: exit2 }));
|
|
13126
14571
|
}
|
|
13127
14572
|
function ApiKeyStep({
|
|
13128
14573
|
onSubmit,
|
|
@@ -13130,7 +14575,7 @@ function ApiKeyStep({
|
|
|
13130
14575
|
onError
|
|
13131
14576
|
}) {
|
|
13132
14577
|
const [value, setValue] = useState15("");
|
|
13133
|
-
return /* @__PURE__ */
|
|
14578
|
+
return /* @__PURE__ */ React29.createElement(Box25, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React29.createElement(
|
|
13134
14579
|
TextInput2,
|
|
13135
14580
|
{
|
|
13136
14581
|
value,
|
|
@@ -13147,7 +14592,7 @@ function ApiKeyStep({
|
|
|
13147
14592
|
mask: "\u2022",
|
|
13148
14593
|
placeholder: "sk-..."
|
|
13149
14594
|
}
|
|
13150
|
-
)), error ? /* @__PURE__ */
|
|
14595
|
+
)), error ? /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "red" }, error)) : value ? /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
13151
14596
|
}
|
|
13152
14597
|
function McpArgsStep({
|
|
13153
14598
|
entry,
|
|
@@ -13156,7 +14601,7 @@ function McpArgsStep({
|
|
|
13156
14601
|
onError
|
|
13157
14602
|
}) {
|
|
13158
14603
|
const [value, setValue] = useState15("");
|
|
13159
|
-
return /* @__PURE__ */
|
|
14604
|
+
return /* @__PURE__ */ React29.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React29.createElement(Box25, { flexDirection: "column" }, /* @__PURE__ */ React29.createElement(Text24, null, entry.summary), entry.note ? /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, null, "Required parameter: "), /* @__PURE__ */ React29.createElement(Text24, { bold: true }, entry.userArgs)), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React29.createElement(
|
|
13160
14605
|
TextInput2,
|
|
13161
14606
|
{
|
|
13162
14607
|
value,
|
|
@@ -13172,7 +14617,7 @@ function McpArgsStep({
|
|
|
13172
14617
|
},
|
|
13173
14618
|
placeholder: placeholderFor(entry)
|
|
13174
14619
|
}
|
|
13175
|
-
)), error ? /* @__PURE__ */
|
|
14620
|
+
)), error ? /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "red" }, error)) : null));
|
|
13176
14621
|
}
|
|
13177
14622
|
function ReviewConfirm({ onConfirm }) {
|
|
13178
14623
|
useInput8((_i, key) => {
|
|
@@ -13192,10 +14637,10 @@ function StepFrame({
|
|
|
13192
14637
|
total,
|
|
13193
14638
|
children
|
|
13194
14639
|
}) {
|
|
13195
|
-
return /* @__PURE__ */
|
|
14640
|
+
return /* @__PURE__ */ React29.createElement(Box25, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React29.createElement(Box25, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React29.createElement(Box25, { marginTop: 1, flexDirection: "column" }, children));
|
|
13196
14641
|
}
|
|
13197
14642
|
function SummaryLine({ label, value }) {
|
|
13198
|
-
return /* @__PURE__ */
|
|
14643
|
+
return /* @__PURE__ */ React29.createElement(Box25, null, /* @__PURE__ */ React29.createElement(Text24, null, label.padEnd(12)), /* @__PURE__ */ React29.createElement(Text24, { bold: true }, value));
|
|
13199
14644
|
}
|
|
13200
14645
|
function presetItems() {
|
|
13201
14646
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -13251,7 +14696,7 @@ async function setupCommand(_opts = {}) {
|
|
|
13251
14696
|
const existingKey = loadApiKey();
|
|
13252
14697
|
const existing = readConfig();
|
|
13253
14698
|
const { waitUntilExit, unmount } = render4(
|
|
13254
|
-
/* @__PURE__ */
|
|
14699
|
+
/* @__PURE__ */ React30.createElement(
|
|
13255
14700
|
Wizard,
|
|
13256
14701
|
{
|
|
13257
14702
|
existingApiKey: existingKey,
|