@wrongstack/tui 0.236.0 → 0.250.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/index.d.ts +49 -1
- package/dist/index.js +134 -32
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -417,6 +417,12 @@ interface RunTuiOptions {
|
|
|
417
417
|
* Used by the TUI to display and auto-submit next steps in 'auto' mode.
|
|
418
418
|
*/
|
|
419
419
|
getSuggestions?: (() => string[]) | undefined;
|
|
420
|
+
/**
|
|
421
|
+
* Write parsed next steps into the shared suggestion store.
|
|
422
|
+
* Called by the Entry component after parsing each assistant message
|
|
423
|
+
* so /next 1 and the auto-submit countdown can access them.
|
|
424
|
+
*/
|
|
425
|
+
setSuggestions?: ((steps: string[]) => void) | undefined;
|
|
420
426
|
/**
|
|
421
427
|
* Messages restored from a previous session. When provided (non-empty),
|
|
422
428
|
* the TUI renders the prior conversation as history entries so a resumed
|
|
@@ -531,4 +537,46 @@ declare function parseInline(text: string): InlineToken[];
|
|
|
531
537
|
*/
|
|
532
538
|
declare function replaySessionEvents(events: SessionEvent[], startId: number): HistoryEntry[];
|
|
533
539
|
|
|
534
|
-
|
|
540
|
+
/**
|
|
541
|
+
* Unified next-steps suggestion parser.
|
|
542
|
+
*
|
|
543
|
+
* Three code paths feed into the suggestion store:
|
|
544
|
+
* 1. TUI rendering — entry.tsx parses "💡 Next steps" from assistant output
|
|
545
|
+
* 2. REPL store — repl.ts parses "💡 Next steps" from final agent output
|
|
546
|
+
* 3. /suggest output — suggest.ts parses LLM-generated numbered lists
|
|
547
|
+
*
|
|
548
|
+
* Heading mode (`requireHeading = true`):
|
|
549
|
+
* strict=true — only 💡 emoji heading (TUI rendering)
|
|
550
|
+
* strict=false — 💡, ##, plain "Next steps" headings (REPL store)
|
|
551
|
+
*
|
|
552
|
+
* Raw mode (`requireHeading = false`):
|
|
553
|
+
* Parses numbered/bullet items from anywhere in text (subagent /suggest output).
|
|
554
|
+
*/
|
|
555
|
+
interface ParsedNextStep {
|
|
556
|
+
index: number;
|
|
557
|
+
text: string;
|
|
558
|
+
}
|
|
559
|
+
interface ParseNextStepsResult {
|
|
560
|
+
/** Matched steps with their original index and stripped text. */
|
|
561
|
+
steps: ParsedNextStep[];
|
|
562
|
+
/** Flat string array — what gets stored in the suggestion store. */
|
|
563
|
+
texts: string[];
|
|
564
|
+
/**
|
|
565
|
+
* Content with the entire "💡 Next steps" block removed.
|
|
566
|
+
* Used by entry.tsx to strip suggestions from the rendered message body.
|
|
567
|
+
*/
|
|
568
|
+
stripped: string;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Parse "💡 Next steps" blocks from assistant output (or raw numbered lines).
|
|
572
|
+
*
|
|
573
|
+
* @param content — raw assistant message text or subagent output
|
|
574
|
+
* @param strict — when true, only the 💡 emoji heading is accepted (TUI rendering).
|
|
575
|
+
* when false, also accepts ## / plain "Next steps" headings (REPL store).
|
|
576
|
+
* @param requireHeading — when true, a heading must precede the item list.
|
|
577
|
+
* when false, numbered/bullet items are parsed from anywhere in text
|
|
578
|
+
* (used by /suggest subagent output which has no heading).
|
|
579
|
+
*/
|
|
580
|
+
declare function parseNextSteps(content: string, strict?: boolean, requireHeading?: boolean): ParseNextStepsResult;
|
|
581
|
+
|
|
582
|
+
export { type ParseNextStepsResult, type ParsedNextStep, type RunTuiOptions, type Settings, parseInline, parseNextSteps, replaySessionEvents, runTui };
|
package/dist/index.js
CHANGED
|
@@ -295,6 +295,9 @@ function StatusBar({
|
|
|
295
295
|
"/",
|
|
296
296
|
indexState.totalFiles
|
|
297
297
|
] })
|
|
298
|
+
] }) : indexState?.circuit?.state === "open" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
299
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
300
|
+
/* @__PURE__ */ jsx(Text, { color: "red", children: "\u2699 index paused (/reindex)" })
|
|
298
301
|
] }) : null
|
|
299
302
|
] })
|
|
300
303
|
) }),
|
|
@@ -3756,19 +3759,107 @@ function Banner({
|
|
|
3756
3759
|
] })
|
|
3757
3760
|
] });
|
|
3758
3761
|
}
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3762
|
+
|
|
3763
|
+
// src/components/suggestions.ts
|
|
3764
|
+
var STRICT_HEADING_RE = /💡\s*Next steps?\s*\n+/i;
|
|
3765
|
+
var PERMISSIVE_HEADING_PATTERNS = [
|
|
3766
|
+
{ re: /💡\s*Next steps?\s*\n+/i, label: "emoji" },
|
|
3767
|
+
{ re: /##?\s*Next steps?\s*\n+/i, label: "markdown" },
|
|
3768
|
+
{ re: /\n{1,2}Next steps?\s*\n+/i, label: "plain" }
|
|
3769
|
+
];
|
|
3770
|
+
var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+)$/;
|
|
3771
|
+
var MAX_STEPS = 6;
|
|
3772
|
+
function parseNextSteps(content, strict = false, requireHeading = true) {
|
|
3773
|
+
if (requireHeading) {
|
|
3774
|
+
return parseWithHeading(content, strict);
|
|
3775
|
+
}
|
|
3776
|
+
return parseRawNumbered(content);
|
|
3777
|
+
}
|
|
3778
|
+
function parseRawNumbered(content) {
|
|
3779
|
+
const lines = content.split("\n");
|
|
3764
3780
|
const steps = [];
|
|
3765
|
-
const
|
|
3766
|
-
for (const
|
|
3767
|
-
const
|
|
3768
|
-
if (
|
|
3781
|
+
const seenNumbers = /* @__PURE__ */ new Set();
|
|
3782
|
+
for (const rawLine of lines) {
|
|
3783
|
+
const line = rawLine.trim();
|
|
3784
|
+
if (!line) continue;
|
|
3785
|
+
const m = ITEM_RE.exec(line);
|
|
3786
|
+
if (!m) continue;
|
|
3787
|
+
const numPart = m[1];
|
|
3788
|
+
let text = m[2].trim();
|
|
3789
|
+
let index;
|
|
3790
|
+
if (numPart !== void 0) {
|
|
3791
|
+
index = Number.parseInt(numPart, 10);
|
|
3792
|
+
} else {
|
|
3793
|
+
index = steps.length + 1;
|
|
3794
|
+
}
|
|
3795
|
+
if (seenNumbers.has(index)) continue;
|
|
3796
|
+
if (text.length < 3) continue;
|
|
3797
|
+
seenNumbers.add(index);
|
|
3798
|
+
steps.push({ index, text });
|
|
3799
|
+
if (steps.length >= MAX_STEPS) break;
|
|
3769
3800
|
}
|
|
3770
|
-
|
|
3771
|
-
|
|
3801
|
+
return { steps, texts: steps.map((s2) => s2.text), stripped: content };
|
|
3802
|
+
}
|
|
3803
|
+
function parseWithHeading(content, strict) {
|
|
3804
|
+
const headingRe = strict ? STRICT_HEADING_RE : buildPermissiveHeadingRe();
|
|
3805
|
+
const headingMatch = headingRe.exec(content);
|
|
3806
|
+
if (!headingMatch) {
|
|
3807
|
+
return { steps: [], texts: [], stripped: content };
|
|
3808
|
+
}
|
|
3809
|
+
const headingEnd = headingMatch.index + headingMatch[0].length;
|
|
3810
|
+
const afterHeading = content.slice(headingEnd);
|
|
3811
|
+
const lines = afterHeading.split("\n");
|
|
3812
|
+
const steps = [];
|
|
3813
|
+
const seenNumbers = /* @__PURE__ */ new Set();
|
|
3814
|
+
for (const rawLine of lines) {
|
|
3815
|
+
const line = rawLine.trim();
|
|
3816
|
+
if (!line) continue;
|
|
3817
|
+
const m = ITEM_RE.exec(line);
|
|
3818
|
+
if (!m) break;
|
|
3819
|
+
const numPart = m[1];
|
|
3820
|
+
let text = m[2].trim();
|
|
3821
|
+
let index;
|
|
3822
|
+
if (numPart !== void 0) {
|
|
3823
|
+
index = Number.parseInt(numPart, 10);
|
|
3824
|
+
} else {
|
|
3825
|
+
index = steps.length + 1;
|
|
3826
|
+
}
|
|
3827
|
+
if (seenNumbers.has(index)) continue;
|
|
3828
|
+
if (text.length < 3) continue;
|
|
3829
|
+
seenNumbers.add(index);
|
|
3830
|
+
steps.push({ index, text });
|
|
3831
|
+
if (steps.length >= MAX_STEPS) break;
|
|
3832
|
+
}
|
|
3833
|
+
if (steps.length === 0) {
|
|
3834
|
+
return { steps: [], texts: [], stripped: content };
|
|
3835
|
+
}
|
|
3836
|
+
const texts = steps.map((s2) => s2.text);
|
|
3837
|
+
const blockStart = headingMatch.index;
|
|
3838
|
+
const blockEnd = headingEnd + findBlockEnd(afterHeading, steps.length);
|
|
3839
|
+
const stripped = (content.slice(0, blockStart) + content.slice(blockStart + blockEnd)).replace(/\n{3,}/g, "\n\n").trim();
|
|
3840
|
+
return { steps, texts, stripped };
|
|
3841
|
+
}
|
|
3842
|
+
function buildPermissiveHeadingRe() {
|
|
3843
|
+
const variants = PERMISSIVE_HEADING_PATTERNS.map(({ re }) => `(?:${re.source})`).join("|");
|
|
3844
|
+
return new RegExp(variants, "i");
|
|
3845
|
+
}
|
|
3846
|
+
function findBlockEnd(afterHeading, stepCount) {
|
|
3847
|
+
const lines = afterHeading.split("\n");
|
|
3848
|
+
let consumed = 0;
|
|
3849
|
+
let found = 0;
|
|
3850
|
+
for (const rawLine of lines) {
|
|
3851
|
+
const line = rawLine.trim();
|
|
3852
|
+
if (!line) {
|
|
3853
|
+
consumed += rawLine.length + 1;
|
|
3854
|
+
continue;
|
|
3855
|
+
}
|
|
3856
|
+
const m = ITEM_RE.exec(line);
|
|
3857
|
+
if (!m) break;
|
|
3858
|
+
consumed += rawLine.length + 1;
|
|
3859
|
+
found++;
|
|
3860
|
+
if (found >= stepCount) break;
|
|
3861
|
+
}
|
|
3862
|
+
return consumed;
|
|
3772
3863
|
}
|
|
3773
3864
|
function brainStatusStyle(status) {
|
|
3774
3865
|
switch (status) {
|
|
@@ -3798,12 +3889,19 @@ function brainRiskColor(risk) {
|
|
|
3798
3889
|
}
|
|
3799
3890
|
var Entry = React5.memo(function Entry2({
|
|
3800
3891
|
entry,
|
|
3801
|
-
termWidth
|
|
3892
|
+
termWidth,
|
|
3893
|
+
setSuggestions
|
|
3802
3894
|
}) {
|
|
3803
3895
|
const nextSteps = useMemo(() => {
|
|
3804
3896
|
if (entry.kind !== "assistant") return { steps: [], stripped: "" };
|
|
3805
|
-
return parseNextSteps(entry.text);
|
|
3897
|
+
return parseNextSteps(entry.text, true);
|
|
3806
3898
|
}, [entry.kind, entry.text]);
|
|
3899
|
+
useEffect(() => {
|
|
3900
|
+
if (!setSuggestions) return;
|
|
3901
|
+
const text = entry.text ?? "";
|
|
3902
|
+
const { texts } = parseNextSteps(text, true);
|
|
3903
|
+
if (texts.length > 0) setSuggestions(texts);
|
|
3904
|
+
}, [entry.kind, entry.text, setSuggestions]);
|
|
3807
3905
|
switch (entry.kind) {
|
|
3808
3906
|
case "user":
|
|
3809
3907
|
return /* @__PURE__ */ jsx(
|
|
@@ -4044,7 +4142,7 @@ var Entry = React5.memo(function Entry2({
|
|
|
4044
4142
|
}
|
|
4045
4143
|
}
|
|
4046
4144
|
});
|
|
4047
|
-
function History({ entries, generation, streamingText, toolStream }) {
|
|
4145
|
+
function History({ entries, generation, streamingText, toolStream, setSuggestions }) {
|
|
4048
4146
|
const { stdout } = useStdout();
|
|
4049
4147
|
const [termSize, setTermSize] = useState({
|
|
4050
4148
|
columns: stdout?.columns ?? 80,
|
|
@@ -4062,7 +4160,7 @@ function History({ entries, generation, streamingText, toolStream }) {
|
|
|
4062
4160
|
const termWidth = termSize.columns;
|
|
4063
4161
|
const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
|
|
4064
4162
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4065
|
-
/* @__PURE__ */ jsx(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id) }, generation ?? 0),
|
|
4163
|
+
/* @__PURE__ */ jsx(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth, setSuggestions }) }, entry.id) }, generation ?? 0),
|
|
4066
4164
|
/* @__PURE__ */ jsx(Box, { flexGrow: 1, children: tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail, termWidth }) : null })
|
|
4067
4165
|
] });
|
|
4068
4166
|
}
|
|
@@ -4111,7 +4209,8 @@ function ScrollableHistory({
|
|
|
4111
4209
|
viewportRows,
|
|
4112
4210
|
totalLines,
|
|
4113
4211
|
onMeasure,
|
|
4114
|
-
maxWidth
|
|
4212
|
+
maxWidth,
|
|
4213
|
+
setSuggestions
|
|
4115
4214
|
}) {
|
|
4116
4215
|
const { stdout } = useStdout();
|
|
4117
4216
|
const rawWidth = stdout?.columns ?? 80;
|
|
@@ -4150,7 +4249,7 @@ function ScrollableHistory({
|
|
|
4150
4249
|
flexShrink: 0,
|
|
4151
4250
|
children: [
|
|
4152
4251
|
hiddenCount > 0 ? /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2191 ${hiddenCount} earlier ${hiddenCount === 1 ? "entry" : "entries"} (scroll lives in this session; full log on disk)` }) }) : null,
|
|
4153
|
-
shown.map((entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, flexShrink: 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id)),
|
|
4252
|
+
shown.map((entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, flexShrink: 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth, setSuggestions }) }, entry.id)),
|
|
4154
4253
|
tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail, termWidth }) : null,
|
|
4155
4254
|
toolTail && toolStream ? /* @__PURE__ */ jsx(
|
|
4156
4255
|
ToolStreamBox,
|
|
@@ -5396,7 +5495,7 @@ var MODE_DESC = {
|
|
|
5396
5495
|
suggest: "Shows next-step suggestions after each turn",
|
|
5397
5496
|
auto: "Self-driving \u2014 agent continues automatically"
|
|
5398
5497
|
};
|
|
5399
|
-
var SETTINGS_FIELD_COUNT =
|
|
5498
|
+
var SETTINGS_FIELD_COUNT = 25;
|
|
5400
5499
|
var CONFIG_SCOPES = ["global", "project"];
|
|
5401
5500
|
function SettingsPicker({
|
|
5402
5501
|
field,
|
|
@@ -7756,20 +7855,20 @@ function reducer(state, action) {
|
|
|
7756
7855
|
const enext = (ebase + action.delta + ENHANCE_DELAY_PRESETS.length) % ENHANCE_DELAY_PRESETS.length;
|
|
7757
7856
|
return { ...state, settingsPicker: { ...sp, enhanceDelayMs: expectDefined$1(ENHANCE_DELAY_PRESETS[enext]), hint: void 0 } };
|
|
7758
7857
|
}
|
|
7759
|
-
if (f ===
|
|
7760
|
-
if (f ===
|
|
7761
|
-
const i = CONFIG_SCOPES.indexOf(sp.configScope);
|
|
7762
|
-
const base = i < 0 ? 0 : i;
|
|
7763
|
-
const next = (base + action.delta + CONFIG_SCOPES.length) % CONFIG_SCOPES.length;
|
|
7764
|
-
return { ...state, settingsPicker: { ...sp, configScope: expectDefined$1(CONFIG_SCOPES[next]), hint: void 0 } };
|
|
7765
|
-
}
|
|
7766
|
-
if (f === 22) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
|
|
7767
|
-
if (f === 23) {
|
|
7858
|
+
if (f === 21) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
|
|
7859
|
+
if (f === 22) {
|
|
7768
7860
|
const i = ENHANCE_LANGUAGES.indexOf(sp.enhanceLanguage);
|
|
7769
7861
|
const base = i < 0 ? 0 : i;
|
|
7770
7862
|
const next = (base + action.delta + ENHANCE_LANGUAGES.length) % ENHANCE_LANGUAGES.length;
|
|
7771
7863
|
return { ...state, settingsPicker: { ...sp, enhanceLanguage: expectDefined$1(ENHANCE_LANGUAGES[next]), hint: void 0 } };
|
|
7772
7864
|
}
|
|
7865
|
+
if (f === 23) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
|
|
7866
|
+
if (f === 24) {
|
|
7867
|
+
const i = CONFIG_SCOPES.indexOf(sp.configScope);
|
|
7868
|
+
const base = i < 0 ? 0 : i;
|
|
7869
|
+
const next = (base + action.delta + CONFIG_SCOPES.length) % CONFIG_SCOPES.length;
|
|
7870
|
+
return { ...state, settingsPicker: { ...sp, configScope: expectDefined$1(CONFIG_SCOPES[next]), hint: void 0 } };
|
|
7871
|
+
}
|
|
7773
7872
|
return state;
|
|
7774
7873
|
}
|
|
7775
7874
|
case "settingsHint":
|
|
@@ -8610,6 +8709,7 @@ function App({
|
|
|
8610
8709
|
predictNext,
|
|
8611
8710
|
onSuggestionsParsed,
|
|
8612
8711
|
getSuggestions,
|
|
8712
|
+
setSuggestions,
|
|
8613
8713
|
switchAutonomy,
|
|
8614
8714
|
effectiveMaxContext,
|
|
8615
8715
|
onExit,
|
|
@@ -10639,14 +10739,14 @@ function App({
|
|
|
10639
10739
|
return;
|
|
10640
10740
|
}
|
|
10641
10741
|
if (item.kind === "project") {
|
|
10642
|
-
onProjectSelect?.(item.key, item.kind);
|
|
10742
|
+
await onProjectSelect?.(item.key, item.kind);
|
|
10643
10743
|
dispatch({ type: "projectPickerClose" });
|
|
10644
10744
|
requestExit?.(42);
|
|
10645
10745
|
return;
|
|
10646
10746
|
}
|
|
10647
10747
|
dispatch({ type: "projectPickerClose" });
|
|
10648
10748
|
if (item.key === "new-session") {
|
|
10649
|
-
onProjectSelect?.(item.key, item.kind);
|
|
10749
|
+
await onProjectSelect?.(item.key, item.kind);
|
|
10650
10750
|
requestExit?.(42);
|
|
10651
10751
|
} else if (item.key === "prev-sessions") {
|
|
10652
10752
|
void submit("/resume");
|
|
@@ -11887,7 +11987,8 @@ User message:
|
|
|
11887
11987
|
scrollOffset: state.scrollOffset,
|
|
11888
11988
|
viewportRows: state.viewportRows,
|
|
11889
11989
|
totalLines: state.totalLines,
|
|
11890
|
-
onMeasure: (totalLines) => dispatch({ type: "setMeasuredLines", totalLines })
|
|
11990
|
+
onMeasure: (totalLines) => dispatch({ type: "setMeasuredLines", totalLines }),
|
|
11991
|
+
setSuggestions
|
|
11891
11992
|
}
|
|
11892
11993
|
) : /* @__PURE__ */ jsx(
|
|
11893
11994
|
History,
|
|
@@ -11895,7 +11996,8 @@ User message:
|
|
|
11895
11996
|
entries: state.entries,
|
|
11896
11997
|
generation: state.historyGen,
|
|
11897
11998
|
streamingText: state.streamingText,
|
|
11898
|
-
toolStream: state.toolStream
|
|
11999
|
+
toolStream: state.toolStream,
|
|
12000
|
+
setSuggestions
|
|
11899
12001
|
}
|
|
11900
12002
|
),
|
|
11901
12003
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexShrink: 0, ref: bottomRegionRef, children: [
|
|
@@ -12751,6 +12853,6 @@ function eventToEntry(ev, pendingTools, completedTools) {
|
|
|
12751
12853
|
}
|
|
12752
12854
|
}
|
|
12753
12855
|
|
|
12754
|
-
export { parseInline, replaySessionEvents, runTui };
|
|
12856
|
+
export { parseInline, parseNextSteps, replaySessionEvents, runTui };
|
|
12755
12857
|
//# sourceMappingURL=index.js.map
|
|
12756
12858
|
//# sourceMappingURL=index.js.map
|