@standardagents/code 0.6.6 → 0.7.1
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.js +330 -109
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1916,6 +1916,90 @@ var SystemEvents = class {
|
|
|
1916
1916
|
}
|
|
1917
1917
|
};
|
|
1918
1918
|
|
|
1919
|
+
// src/subagent-streams.ts
|
|
1920
|
+
var MAX_PHRASE = 120;
|
|
1921
|
+
function cleanPhrase(raw) {
|
|
1922
|
+
const s = raw.replace(/\s+/g, " ").trim();
|
|
1923
|
+
return s.length > MAX_PHRASE ? `${s.slice(0, MAX_PHRASE - 1)}\u2026` : s;
|
|
1924
|
+
}
|
|
1925
|
+
function prettyToolName(name) {
|
|
1926
|
+
return name.replace(/^provider:/, "").replace(/[_:-]+/g, " ").trim();
|
|
1927
|
+
}
|
|
1928
|
+
var SubagentActivity = class {
|
|
1929
|
+
constructor(api, onChange, makeStream = (threadId, hooks) => new MessageStream(api, threadId, hooks)) {
|
|
1930
|
+
this.onChange = onChange;
|
|
1931
|
+
this.makeStream = makeStream;
|
|
1932
|
+
}
|
|
1933
|
+
onChange;
|
|
1934
|
+
makeStream;
|
|
1935
|
+
entries = /* @__PURE__ */ new Map();
|
|
1936
|
+
/** The current activity phrase for a subagent's child thread, if any. */
|
|
1937
|
+
phraseFor(threadId) {
|
|
1938
|
+
return this.entries.get(threadId)?.phrase ?? null;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Reconcile the open streams against the authoritative set of RUNNING
|
|
1942
|
+
* subagent child-thread ids (from the parent's registry): open newcomers,
|
|
1943
|
+
* close and forget departed ones.
|
|
1944
|
+
*/
|
|
1945
|
+
sync(activeIds) {
|
|
1946
|
+
const want = new Set(activeIds);
|
|
1947
|
+
for (const [id, entry] of [...this.entries]) {
|
|
1948
|
+
if (!want.has(id)) {
|
|
1949
|
+
entry.stream.close();
|
|
1950
|
+
this.entries.delete(id);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
for (const id of want) {
|
|
1954
|
+
if (!this.entries.has(id)) this.open(id);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
open(threadId) {
|
|
1958
|
+
const entry = { stream: null, steps: /* @__PURE__ */ new Map(), phrase: null };
|
|
1959
|
+
const setPhrase = (phrase) => {
|
|
1960
|
+
if (phrase === entry.phrase) return;
|
|
1961
|
+
entry.phrase = phrase;
|
|
1962
|
+
this.onChange(threadId);
|
|
1963
|
+
};
|
|
1964
|
+
const phraseFromSteps = () => {
|
|
1965
|
+
let last = null;
|
|
1966
|
+
for (const v of entry.steps.values()) last = v;
|
|
1967
|
+
return last ?? entry.phrase;
|
|
1968
|
+
};
|
|
1969
|
+
entry.stream = this.makeStream(threadId, {
|
|
1970
|
+
// Streamed output with no tool in flight means the model is composing —
|
|
1971
|
+
// reasoning reads as "thinking", visible answer text as "writing". These
|
|
1972
|
+
// only fire on TRANSITIONS (phrase comparison), not per chunk.
|
|
1973
|
+
onChunk: () => {
|
|
1974
|
+
if (entry.steps.size === 0) setPhrase("writing");
|
|
1975
|
+
},
|
|
1976
|
+
onReasoningChunk: () => {
|
|
1977
|
+
if (entry.steps.size === 0) setPhrase("thinking");
|
|
1978
|
+
},
|
|
1979
|
+
onAssistantText: () => {
|
|
1980
|
+
},
|
|
1981
|
+
onEvent: (eventType, data) => {
|
|
1982
|
+
if (eventType === "tool_call_started" && data?.id) {
|
|
1983
|
+
entry.steps.set(String(data.id), cleanPhrase(String(data.progress || prettyToolName(String(data.name || "")) || "working")));
|
|
1984
|
+
setPhrase(phraseFromSteps());
|
|
1985
|
+
} else if (eventType === "tool_call_done" && data?.id) {
|
|
1986
|
+
entry.steps.delete(String(data.id));
|
|
1987
|
+
setPhrase(phraseFromSteps());
|
|
1988
|
+
}
|
|
1989
|
+
},
|
|
1990
|
+
onError: () => {
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
this.entries.set(threadId, entry);
|
|
1994
|
+
void entry.stream.connect();
|
|
1995
|
+
}
|
|
1996
|
+
/** Close every stream (session teardown). */
|
|
1997
|
+
closeAll() {
|
|
1998
|
+
for (const entry of this.entries.values()) entry.stream.close();
|
|
1999
|
+
this.entries.clear();
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
|
|
1919
2003
|
// src/wordmill.ts
|
|
1920
2004
|
var MILL_WORDS = [
|
|
1921
2005
|
"Working",
|
|
@@ -2144,6 +2228,92 @@ async function readClipboardImage() {
|
|
|
2144
2228
|
function imagePlaceholder(seq) {
|
|
2145
2229
|
return `[#Image ${seq}]`;
|
|
2146
2230
|
}
|
|
2231
|
+
var INPUT_BOX_MARGIN = 1;
|
|
2232
|
+
function inputBoxBorderColor() {
|
|
2233
|
+
return "\x1B[38;5;240m";
|
|
2234
|
+
}
|
|
2235
|
+
function borderLevelDots(level) {
|
|
2236
|
+
const n = Math.max(1, Math.min(5, level));
|
|
2237
|
+
return "\u25CF".repeat(n) + "\u25CB".repeat(5 - n);
|
|
2238
|
+
}
|
|
2239
|
+
function borderLevelDotsStyled(level, levelColor, emptyColor = "\x1B[38;5;240m", reset = "\x1B[0m") {
|
|
2240
|
+
const n = Math.max(1, Math.min(5, level));
|
|
2241
|
+
let out = "";
|
|
2242
|
+
for (let i = 1; i <= 5; i++) {
|
|
2243
|
+
out += i <= n ? `${levelColor}\u25CF${reset}` : `${emptyColor}\u25CB${reset}`;
|
|
2244
|
+
}
|
|
2245
|
+
return out;
|
|
2246
|
+
}
|
|
2247
|
+
function inputBoxGeometry(cols2) {
|
|
2248
|
+
const margin = INPUT_BOX_MARGIN;
|
|
2249
|
+
const boxW = Math.max(12, cols2 - 1 - margin);
|
|
2250
|
+
const contentW = Math.max(4, boxW - 4);
|
|
2251
|
+
const leftPad = margin + 2;
|
|
2252
|
+
return { margin, boxW, contentW, leftPad };
|
|
2253
|
+
}
|
|
2254
|
+
function boxVisibleWidth(s) {
|
|
2255
|
+
let w = 0;
|
|
2256
|
+
for (const ch of s.replace(/\x1b\[[0-9;]*m/g, "")) {
|
|
2257
|
+
const cp = ch.codePointAt(0);
|
|
2258
|
+
w += isWideCodePoint(cp) ? 2 : 1;
|
|
2259
|
+
}
|
|
2260
|
+
return w;
|
|
2261
|
+
}
|
|
2262
|
+
function buildBoxTop(boxW, levelText, borderColor, levelColor, reset = "\x1B[0m", levelStyled) {
|
|
2263
|
+
const labelPlain = ` ${levelText} `;
|
|
2264
|
+
const rightFill = 1;
|
|
2265
|
+
const budget = Math.max(1, boxW - 2 - rightFill);
|
|
2266
|
+
const plain = labelPlain.length > budget ? labelPlain.slice(0, Math.max(1, budget)) : labelPlain;
|
|
2267
|
+
const leftFill = Math.max(1, boxW - 2 - plain.length - rightFill);
|
|
2268
|
+
const mid = levelStyled !== void 0 ? ` ${levelStyled} ` : `${levelColor}${plain}${reset}`;
|
|
2269
|
+
return `${borderColor}\u256D${"\u2500".repeat(leftFill)}${reset}` + mid + `${borderColor}${"\u2500".repeat(rightFill)}\u256E${reset}`;
|
|
2270
|
+
}
|
|
2271
|
+
function buildBoxBottom(boxW, borderColor, reset = "\x1B[0m") {
|
|
2272
|
+
return `${borderColor}\u2570${"\u2500".repeat(Math.max(1, boxW - 2))}\u256F${reset}`;
|
|
2273
|
+
}
|
|
2274
|
+
function buildBoxTopPlain(boxW, borderColor, reset = "\x1B[0m") {
|
|
2275
|
+
return `${borderColor}\u256D${"\u2500".repeat(Math.max(1, boxW - 2))}\u256E${reset}`;
|
|
2276
|
+
}
|
|
2277
|
+
function buildBoxBody(content, contentW, borderColor, reset = "\x1B[0m") {
|
|
2278
|
+
const vw = boxVisibleWidth(content);
|
|
2279
|
+
const pad = Math.max(0, contentW - vw);
|
|
2280
|
+
return `${borderColor}\u2502${reset} ${content}${" ".repeat(pad)} ${borderColor}\u2502${reset}`;
|
|
2281
|
+
}
|
|
2282
|
+
function wrapInputBodyLines(inputBuffer, prefix, prefixWidth, contentW) {
|
|
2283
|
+
const indent = " ".repeat(Math.max(0, prefixWidth));
|
|
2284
|
+
const lines = inputBuffer.split("\n");
|
|
2285
|
+
const out = [];
|
|
2286
|
+
let isFirstPhysical = true;
|
|
2287
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2288
|
+
const text = lines[i];
|
|
2289
|
+
let remaining = text;
|
|
2290
|
+
do {
|
|
2291
|
+
const lead = isFirstPhysical ? prefix : indent;
|
|
2292
|
+
const avail = Math.max(0, contentW - prefixWidth);
|
|
2293
|
+
let take = Math.min(remaining.length, avail);
|
|
2294
|
+
if (take === 0 && avail === 0 && remaining.length > 0) take = 1;
|
|
2295
|
+
const chunk = remaining.slice(0, take);
|
|
2296
|
+
remaining = remaining.slice(take);
|
|
2297
|
+
out.push(lead + chunk);
|
|
2298
|
+
isFirstPhysical = false;
|
|
2299
|
+
if (remaining.length === 0) break;
|
|
2300
|
+
} while (remaining.length > 0);
|
|
2301
|
+
}
|
|
2302
|
+
if (out.length === 0) out.push(prefix);
|
|
2303
|
+
return out;
|
|
2304
|
+
}
|
|
2305
|
+
function buildInputBoxRows(opts) {
|
|
2306
|
+
const { cols: cols2, inputBuffer, prefix, prefixWidth, level, levelColor } = opts;
|
|
2307
|
+
const borderColor = opts.borderColor ?? inputBoxBorderColor();
|
|
2308
|
+
const geo = inputBoxGeometry(cols2);
|
|
2309
|
+
const body = wrapInputBodyLines(inputBuffer, prefix, prefixWidth, geo.contentW);
|
|
2310
|
+
const dots = borderLevelDots(level);
|
|
2311
|
+
const dotsStyled = borderLevelDotsStyled(level, levelColor);
|
|
2312
|
+
const top = buildBoxTop(geo.boxW, dots, borderColor, levelColor, "\x1B[0m", dotsStyled);
|
|
2313
|
+
const bottom = buildBoxBottom(geo.boxW, borderColor);
|
|
2314
|
+
const pad = " ".repeat(geo.margin);
|
|
2315
|
+
return [pad + top, ...body.map((b) => pad + buildBoxBody(b, geo.contentW, borderColor)), pad + bottom];
|
|
2316
|
+
}
|
|
2147
2317
|
var C = {
|
|
2148
2318
|
reset: "\x1B[0m",
|
|
2149
2319
|
dim: "\x1B[2m",
|
|
@@ -2331,10 +2501,21 @@ var Tui = class _Tui {
|
|
|
2331
2501
|
}
|
|
2332
2502
|
/** Tear down the bottom region and restore the terminal (called on quit). */
|
|
2333
2503
|
end() {
|
|
2504
|
+
this.started = false;
|
|
2505
|
+
this.working = false;
|
|
2506
|
+
this.subagents = [];
|
|
2334
2507
|
if (this.quitTimer) clearTimeout(this.quitTimer);
|
|
2335
2508
|
this.quitTimer = null;
|
|
2336
2509
|
if (this.streamIdleTimer) clearTimeout(this.streamIdleTimer);
|
|
2337
2510
|
this.streamIdleTimer = null;
|
|
2511
|
+
if (this.streamRedrawTimer) {
|
|
2512
|
+
clearTimeout(this.streamRedrawTimer);
|
|
2513
|
+
this.streamRedrawTimer = null;
|
|
2514
|
+
}
|
|
2515
|
+
if (this.spinnerTimer) {
|
|
2516
|
+
clearInterval(this.spinnerTimer);
|
|
2517
|
+
this.spinnerTimer = null;
|
|
2518
|
+
}
|
|
2338
2519
|
this.clearBottom();
|
|
2339
2520
|
process.stdout.write("\x1B[?2004l\x1B[?25h");
|
|
2340
2521
|
}
|
|
@@ -2567,45 +2748,58 @@ var Tui = class _Tui {
|
|
|
2567
2748
|
}
|
|
2568
2749
|
// ─── input layout + vertical caret movement ────────────────────────────────
|
|
2569
2750
|
/**
|
|
2570
|
-
* The input's physical rows (same wrapping math as
|
|
2571
|
-
* lines split on "\n", line 0 led by the prompt prefix, each wrapping
|
|
2572
|
-
*
|
|
2573
|
-
* buffer index of its first character, its character count, and
|
|
2574
|
-
* column its first character renders at (only
|
|
2575
|
-
* prompt). Drives ↑/↓: row 0 is "the top
|
|
2576
|
-
* anything below moves the caret instead.
|
|
2751
|
+
* The input's physical rows (same wrapping math as the boxed input body:
|
|
2752
|
+
* logical lines split on "\n", line 0 led by the prompt prefix, each wrapping
|
|
2753
|
+
* at the box content width) plus where the caret sits among them. Each row
|
|
2754
|
+
* records the buffer index of its first character, its character count, and
|
|
2755
|
+
* the visual column its first character renders at (only the first physical
|
|
2756
|
+
* row of line 0 is offset, by the prompt). Drives ↑/↓: row 0 is "the top
|
|
2757
|
+
* line" (history recall territory), anything below moves the caret instead.
|
|
2577
2758
|
*/
|
|
2578
2759
|
inputLayout() {
|
|
2579
2760
|
const cols2 = process.stdout.columns || 80;
|
|
2761
|
+
const { contentW } = inputBoxGeometry(cols2);
|
|
2580
2762
|
const pw = this.visibleWidth(this.promptPrefix());
|
|
2581
2763
|
const lines = this.inputBuffer.split("\n");
|
|
2582
2764
|
const rows = [];
|
|
2583
2765
|
let offset = 0;
|
|
2584
2766
|
for (let i = 0; i < lines.length; i++) {
|
|
2585
|
-
const lead = i === 0 ? pw : 0;
|
|
2586
2767
|
const len = lines[i].length;
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
const
|
|
2591
|
-
|
|
2592
|
-
|
|
2768
|
+
let remaining = len;
|
|
2769
|
+
let charStart = 0;
|
|
2770
|
+
do {
|
|
2771
|
+
const avail = Math.max(0, contentW - pw);
|
|
2772
|
+
let take = Math.min(remaining, avail);
|
|
2773
|
+
if (take === 0 && avail === 0 && remaining > 0) take = 1;
|
|
2774
|
+
rows.push({ start: offset + charStart, len: take, colOffset: pw });
|
|
2775
|
+
charStart += take;
|
|
2776
|
+
remaining -= take;
|
|
2777
|
+
} while (remaining > 0);
|
|
2593
2778
|
offset += len + 1;
|
|
2594
2779
|
}
|
|
2595
|
-
let
|
|
2596
|
-
let
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2780
|
+
let caretRow = 0;
|
|
2781
|
+
let caretCol = 0;
|
|
2782
|
+
let found = false;
|
|
2783
|
+
for (let r = 0; r < rows.length; r++) {
|
|
2784
|
+
const row = rows[r];
|
|
2785
|
+
const rowEnd = row.start + row.len;
|
|
2786
|
+
const isLast = r === rows.length - 1;
|
|
2787
|
+
const nextStart = isLast ? rowEnd : rows[r + 1].start;
|
|
2788
|
+
if (this.cursorPos < rowEnd || this.cursorPos === rowEnd && (isLast || nextStart > rowEnd)) {
|
|
2789
|
+
caretRow = r;
|
|
2790
|
+
caretCol = row.colOffset + (this.cursorPos - row.start);
|
|
2791
|
+
found = true;
|
|
2792
|
+
break;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
if (!found && rows.length) {
|
|
2796
|
+
caretRow = rows.length - 1;
|
|
2797
|
+
const row = rows[caretRow];
|
|
2798
|
+
caretCol = row.colOffset + row.len;
|
|
2600
2799
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
for (let i = 0; i < caretLine; i++) {
|
|
2604
|
-
const lead = i === 0 ? pw : 0;
|
|
2605
|
-
caretRow += Math.max(1, Math.ceil((lead + lines[i].length) / cols2));
|
|
2800
|
+
while (caretRow >= rows.length) {
|
|
2801
|
+
rows.push({ start: this.inputBuffer.length, len: 0, colOffset: 0 });
|
|
2606
2802
|
}
|
|
2607
|
-
const caretCol = caretCell % cols2;
|
|
2608
|
-
while (caretRow >= rows.length) rows.push({ start: this.inputBuffer.length, len: 0, colOffset: 0 });
|
|
2609
2803
|
return { rows, caretRow, caretCol, rowCount: rows.length };
|
|
2610
2804
|
}
|
|
2611
2805
|
/** Move the caret one visual row up/down, keeping the column when possible. */
|
|
@@ -2730,19 +2924,19 @@ var Tui = class _Tui {
|
|
|
2730
2924
|
return parts.length ? `${C.gray}${parts.join(" ")}${C.reset}` : "";
|
|
2731
2925
|
}
|
|
2732
2926
|
/**
|
|
2733
|
-
*
|
|
2734
|
-
*
|
|
2735
|
-
*
|
|
2736
|
-
*
|
|
2927
|
+
* Context-to-compaction gauge, right-aligned on the status line: just "N%"
|
|
2928
|
+
* in dull grey. Shown whenever we know a value (any fill — 5%, 10%, 20%…).
|
|
2929
|
+
* The percentage is scaled so 100% = where background compaction triggers,
|
|
2930
|
+
* not the raw model window.
|
|
2737
2931
|
*/
|
|
2738
2932
|
contextGaugeText() {
|
|
2739
|
-
if (this.contextPct == null
|
|
2740
|
-
return
|
|
2933
|
+
if (this.contextPct == null) return "";
|
|
2934
|
+
return `\x1B[38;5;240m${this.contextPct}%${C.reset}`;
|
|
2741
2935
|
}
|
|
2742
2936
|
/**
|
|
2743
2937
|
* Set the context-window fill percentage (0–100), or null to hide it.
|
|
2744
|
-
* Driven by the runtime's `context_usage` KV
|
|
2745
|
-
*
|
|
2938
|
+
* Driven by the runtime's `context_usage` KV, scaled to the compaction
|
|
2939
|
+
* trigger (see index.ts poll). Always painted on the status line far right.
|
|
2746
2940
|
*/
|
|
2747
2941
|
setContextPct(pct) {
|
|
2748
2942
|
const next = pct == null ? null : Math.max(0, Math.min(100, Math.round(pct)));
|
|
@@ -2940,10 +3134,14 @@ var Tui = class _Tui {
|
|
|
2940
3134
|
if (up > 0) process.stdout.write(`\x1B[${up}A`);
|
|
2941
3135
|
}
|
|
2942
3136
|
/**
|
|
2943
|
-
* Render the bottom region:
|
|
2944
|
-
* (
|
|
2945
|
-
* Uses only relative cursor moves so it survives terminal scrolling when
|
|
2946
|
-
* region grows near the bottom of the screen.
|
|
3137
|
+
* Render the bottom region: status + subagents above a side-margined expanding
|
|
3138
|
+
* input box (level in the top border far right), with the caret on the input
|
|
3139
|
+
* body. Uses only relative cursor moves so it survives terminal scrolling when
|
|
3140
|
+
* the region grows near the bottom of the screen.
|
|
3141
|
+
*
|
|
3142
|
+
* Layout (top → bottom):
|
|
3143
|
+
* preview · ruler · notice · quit · status · subagents · goal ·
|
|
3144
|
+
* box top (level) · box body · box bottom · palette
|
|
2947
3145
|
*/
|
|
2948
3146
|
renderBottom() {
|
|
2949
3147
|
if (!this.started || this.takeoverHandler) return;
|
|
@@ -2959,69 +3157,66 @@ var Tui = class _Tui {
|
|
|
2959
3157
|
};
|
|
2960
3158
|
const previewLines = this.streamPreviewLines(cols2);
|
|
2961
3159
|
for (const line of previewLines) writeHudRow(line);
|
|
2962
|
-
const rulerRows = 1;
|
|
2963
3160
|
writeHudRow(`${C.dim}${"\u2500".repeat(cols2)}${C.reset}`);
|
|
3161
|
+
const geo = inputBoxGeometry(cols2);
|
|
3162
|
+
const workPad = " ".repeat(geo.margin);
|
|
3163
|
+
const workCols = Math.max(8, cols2 - geo.margin);
|
|
2964
3164
|
const noticeLine = this.connected ? null : `${C.yellow}\u26A0 lost connection to the workspace \u2014 reconnecting\u2026${C.reset}`;
|
|
2965
|
-
|
|
2966
|
-
if (noticeLine) writeHudRow(noticeLine);
|
|
3165
|
+
if (noticeLine) writeHudRow(workPad + noticeLine);
|
|
2967
3166
|
const quitLine = this.quitArmed ? `${C.dim}Press Control-C again to exit${C.reset}` : null;
|
|
2968
|
-
|
|
2969
|
-
|
|
3167
|
+
if (quitLine) writeHudRow(workPad + quitLine);
|
|
3168
|
+
const statusLine = this.statusLineText(workCols);
|
|
3169
|
+
if (statusLine) writeHudRow(workPad + statusLine);
|
|
2970
3170
|
const frame = FRAMES[Math.floor(Date.now() / SPINNER_MS) % FRAMES.length];
|
|
2971
3171
|
for (const sub of this.subagents) {
|
|
2972
3172
|
const color = sub.agentName === COMPACTION_AGENT ? COMPACTION_COLOR : SUBAGENT_COLORS[this.subagentColorByID.get(sub.id) ?? 0];
|
|
2973
|
-
const budget =
|
|
3173
|
+
const budget = workCols - 10;
|
|
2974
3174
|
let label = sub.label;
|
|
2975
3175
|
if (budget < 1) label = "";
|
|
2976
3176
|
else if (label.length > budget) label = label.slice(0, Math.max(0, budget - 1)) + "\u2026";
|
|
2977
3177
|
const line = `${color}${frame}${C.reset} ${color}${label}${C.reset} ${C.dim}working${C.reset}`;
|
|
2978
|
-
writeHudRow(line);
|
|
2979
|
-
}
|
|
2980
|
-
const
|
|
2981
|
-
const
|
|
2982
|
-
if (statusLine) writeHudRow(statusLine);
|
|
2983
|
-
const goalLines = this.goalLines(cols2);
|
|
2984
|
-
for (const line of goalLines) writeHudRow(line);
|
|
2985
|
-
const aboveRows = previewLines.length + rulerRows + noticeRows + quitRows + this.subagents.length + statusRows + goalLines.length;
|
|
3178
|
+
writeHudRow(workPad + line);
|
|
3179
|
+
}
|
|
3180
|
+
const goalLines = this.goalLines(workCols);
|
|
3181
|
+
for (const line of goalLines) writeHudRow(workPad + line);
|
|
2986
3182
|
const prefix = this.promptPrefix();
|
|
2987
3183
|
const pw = this.visibleWidth(prefix);
|
|
2988
|
-
const
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
const
|
|
3184
|
+
const boxRows = buildInputBoxRows({
|
|
3185
|
+
cols: cols2,
|
|
3186
|
+
inputBuffer: this.inputBuffer,
|
|
3187
|
+
prefix,
|
|
3188
|
+
prefixWidth: pw,
|
|
3189
|
+
level: this.level,
|
|
3190
|
+
levelColor: this.levelColor(),
|
|
3191
|
+
borderColor: inputBoxBorderColor()
|
|
3192
|
+
});
|
|
3193
|
+
const boxTop = boxRows[0];
|
|
3194
|
+
const boxBottom = boxRows[boxRows.length - 1];
|
|
3195
|
+
const boxBody = boxRows.slice(1, -1);
|
|
3196
|
+
writeHudRow(boxTop);
|
|
3197
|
+
const aboveBodyWidths = hudWidths.slice();
|
|
3198
|
+
const aboveBodyRows = aboveBodyWidths.length;
|
|
3199
|
+
for (const row of boxBody) writeHudRow(row);
|
|
3200
|
+
writeHudRow(boxBottom);
|
|
3004
3201
|
const paletteBlock = this.paletteBlockLines(cols2);
|
|
3005
|
-
for (const line of paletteBlock)
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
tail.push("\r");
|
|
3014
|
-
const up = inputRows - 1 - caretRow;
|
|
3015
|
-
if (up > 0) tail.push(`\x1B[${up}A`);
|
|
3016
|
-
if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
|
|
3017
|
-
this.lastCursorRow = aboveRows + caretRow;
|
|
3018
|
-
} else {
|
|
3019
|
-
this.lastCursorRow = aboveRows + (inputRows - 1);
|
|
3020
|
-
}
|
|
3021
|
-
this.drawnHudWidths = hudWidths;
|
|
3202
|
+
for (const line of paletteBlock) writeHudRow(line);
|
|
3203
|
+
const layout = this.inputLayout();
|
|
3204
|
+
const bodyRowCount = Math.max(1, boxBody.length);
|
|
3205
|
+
const caretBodyRow = Math.max(0, Math.min(layout.caretRow, bodyRowCount - 1));
|
|
3206
|
+
const caretRegionRow = aboveBodyRows + caretBodyRow;
|
|
3207
|
+
const caretScreenCol = Math.min(rowCap - 1, geo.leftPad + layout.caretCol);
|
|
3208
|
+
this.lastCursorRow = caretRegionRow;
|
|
3209
|
+
this.drawnHudWidths = aboveBodyWidths;
|
|
3022
3210
|
this.lastDrawnCols = cols2;
|
|
3023
3211
|
this.bottomDrawn = true;
|
|
3024
|
-
const
|
|
3212
|
+
const totalRows = hudRows.length;
|
|
3213
|
+
const up = Math.max(0, totalRows - 1 - caretRegionRow);
|
|
3214
|
+
let out = "\x1B[J" + hudRows.join("\r\n");
|
|
3215
|
+
if (totalRows > 0) {
|
|
3216
|
+
out += "\r";
|
|
3217
|
+
if (up > 0) out += `\x1B[${up}A`;
|
|
3218
|
+
if (caretScreenCol > 0) out += `\x1B[${caretScreenCol}C`;
|
|
3219
|
+
}
|
|
3025
3220
|
process.stdout.write(out);
|
|
3026
3221
|
}
|
|
3027
3222
|
clearBottom() {
|
|
@@ -3436,44 +3631,62 @@ var Tui = class _Tui {
|
|
|
3436
3631
|
};
|
|
3437
3632
|
});
|
|
3438
3633
|
}
|
|
3439
|
-
/**
|
|
3634
|
+
/**
|
|
3635
|
+
* Arrow-key selection menu (slash menu, process menu, resume). Pauses input.
|
|
3636
|
+
* Boxed chrome matches the bottom input HUD: side margin + grey rounded
|
|
3637
|
+
* border, selected row with a brand-tinted pointer.
|
|
3638
|
+
*/
|
|
3440
3639
|
select(title, items) {
|
|
3441
3640
|
return new Promise((resolve) => {
|
|
3442
3641
|
let idx = 0;
|
|
3443
3642
|
this.beginTakeover();
|
|
3643
|
+
const cols2 = process.stdout.columns || 80;
|
|
3644
|
+
const geo = inputBoxGeometry(cols2);
|
|
3645
|
+
const border = inputBoxBorderColor();
|
|
3646
|
+
const pad = " ".repeat(geo.margin);
|
|
3647
|
+
const rowCap = Math.max(1, cols2 - 1);
|
|
3444
3648
|
if (title) process.stdout.write(`
|
|
3445
|
-
${title}
|
|
3446
|
-
|
|
3649
|
+
${pad}${title}
|
|
3447
3650
|
`);
|
|
3448
|
-
|
|
3449
|
-
|
|
3651
|
+
else process.stdout.write("\n");
|
|
3652
|
+
const renderItemContent = (i) => {
|
|
3450
3653
|
const it = items[i];
|
|
3451
3654
|
const sel = i === idx;
|
|
3452
3655
|
const hint = it.hint ?? "";
|
|
3453
3656
|
const hintW = hint.length;
|
|
3454
3657
|
const pointerW = 2;
|
|
3455
|
-
const labelMax = Math.max(
|
|
3456
|
-
let label = it.label;
|
|
3457
|
-
if (label.length > labelMax) label = label.slice(0, labelMax - 1) + "\u2026";
|
|
3658
|
+
const labelMax = Math.max(4, geo.contentW - pointerW - (hintW ? hintW + 2 : 0));
|
|
3659
|
+
let label = it.label.replace(/\s+/g, " ").trim();
|
|
3660
|
+
if (label.length > labelMax) label = label.slice(0, Math.max(0, labelMax - 1)) + "\u2026";
|
|
3458
3661
|
const pointer = sel ? `${C.magenta}\u276F${C.reset} ` : " ";
|
|
3459
|
-
const styledLabel = sel ? `${C.bold}${C.cyan}${label}${C.reset}` : label
|
|
3460
|
-
let
|
|
3662
|
+
const styledLabel = sel ? `${C.bold}${C.cyan}${label}${C.reset}` : `${C.dim}${label}${C.reset}`;
|
|
3663
|
+
let content = `${pointer}${styledLabel}`;
|
|
3461
3664
|
if (hintW) {
|
|
3462
|
-
const
|
|
3463
|
-
|
|
3665
|
+
const used = pointerW + label.length;
|
|
3666
|
+
const gap = Math.max(1, geo.contentW - used - hintW);
|
|
3667
|
+
content += `${" ".repeat(gap)}${sel ? C.gray : C.dim}${hint}${C.reset}`;
|
|
3464
3668
|
}
|
|
3465
|
-
return
|
|
3669
|
+
return content;
|
|
3466
3670
|
};
|
|
3467
|
-
const
|
|
3468
|
-
|
|
3469
|
-
|
|
3671
|
+
const writeRow = (line) => {
|
|
3672
|
+
const row = this.clampVisible(sanitizeHudRow(line), rowCap);
|
|
3673
|
+
process.stdout.write(`\r\x1B[K${row}
|
|
3470
3674
|
`);
|
|
3471
3675
|
};
|
|
3676
|
+
const draw = (moveUp) => {
|
|
3677
|
+
if (moveUp) process.stdout.write(`\x1B[${items.length + 1}A`);
|
|
3678
|
+
else writeRow(pad + buildBoxTopPlain(geo.boxW, border));
|
|
3679
|
+
for (let i = 0; i < items.length; i++) {
|
|
3680
|
+
writeRow(pad + buildBoxBody(renderItemContent(i), geo.contentW, border));
|
|
3681
|
+
}
|
|
3682
|
+
writeRow(pad + buildBoxBottom(geo.boxW, border));
|
|
3683
|
+
};
|
|
3472
3684
|
draw(false);
|
|
3473
|
-
const titleRows = title ?
|
|
3685
|
+
const titleRows = title ? 2 : 1;
|
|
3686
|
+
const boxRows = 1 + items.length + 1;
|
|
3474
3687
|
const erase = () => {
|
|
3475
3688
|
process.stdout.write("\r");
|
|
3476
|
-
const up = titleRows +
|
|
3689
|
+
const up = titleRows + boxRows;
|
|
3477
3690
|
if (up > 0) process.stdout.write(`\x1B[${up}A`);
|
|
3478
3691
|
process.stdout.write("\x1B[J");
|
|
3479
3692
|
};
|
|
@@ -4449,11 +4662,12 @@ function printAssistant(tui, text) {
|
|
|
4449
4662
|
}
|
|
4450
4663
|
function startLoader(label) {
|
|
4451
4664
|
const frames = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FB", "\u28FD", "\u28FE"];
|
|
4665
|
+
const pad = " ".repeat(INPUT_BOX_MARGIN);
|
|
4452
4666
|
stdout.write("\x1B[?25l");
|
|
4453
4667
|
const draw = () => {
|
|
4454
4668
|
const now = Date.now();
|
|
4455
4669
|
const f = frames[Math.floor(now / 70) % frames.length];
|
|
4456
|
-
stdout.write(`\r\x1B[K${brandCycleColor(now)}${f}${c.reset} ${c.dim}${label}\u2026${c.reset}`);
|
|
4670
|
+
stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c.reset} ${c.dim}${label}\u2026${c.reset}`);
|
|
4457
4671
|
};
|
|
4458
4672
|
draw();
|
|
4459
4673
|
const timer = setInterval(draw, 70);
|
|
@@ -4776,7 +4990,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
|
|
|
4776
4990
|
const tilde = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
|
|
4777
4991
|
const shortDir = tilde.length > 38 ? "\u2026" + tilde.slice(-37) : tilde;
|
|
4778
4992
|
const picked = await tui.select(
|
|
4779
|
-
`${c.bold}${
|
|
4993
|
+
`${c.bold}${gradientText("Resume a session")}${c.reset} ${c.gray}${shortDir}${c.reset} ${c.dim}\u2191\u2193 \xB7 enter \xB7 esc${c.reset}`,
|
|
4780
4994
|
items
|
|
4781
4995
|
);
|
|
4782
4996
|
if (typeof picked === "string") {
|
|
@@ -5025,8 +5239,12 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5025
5239
|
const agentTitlesReady = api.listAgents().then((list) => list.forEach((a) => agentTitles.set(a.name, a.title))).catch(() => {
|
|
5026
5240
|
});
|
|
5027
5241
|
const pushSubagents = () => tui.setSubagents(
|
|
5028
|
-
[...activeSubagents.entries()].map(([id, s]) =>
|
|
5242
|
+
[...activeSubagents.entries()].map(([id, s]) => {
|
|
5243
|
+
const detail = subActivity.phraseFor(id) ?? s.registryDetail;
|
|
5244
|
+
return { id, label: `${s.title}${detail ? ` \u2014 ${detail}` : ""}`, agentName: s.agentName };
|
|
5245
|
+
})
|
|
5029
5246
|
);
|
|
5247
|
+
const subActivity = new SubagentActivity(api, () => pushSubagents());
|
|
5030
5248
|
const reconcileSubagents = async () => {
|
|
5031
5249
|
try {
|
|
5032
5250
|
await agentTitlesReady;
|
|
@@ -5036,12 +5254,13 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5036
5254
|
const status = (s.status || "").trim();
|
|
5037
5255
|
if (status === "idle" || status === "terminated") continue;
|
|
5038
5256
|
const oneLineStatus = status.replace(/\s+/g, " ");
|
|
5039
|
-
const detail = oneLineStatus && oneLineStatus !== "running" ? ` \u2014 ${oneLineStatus.slice(0, 80)}` : "";
|
|
5040
5257
|
activeSubagents.set(s.id, {
|
|
5041
|
-
|
|
5258
|
+
title: subagentLabel(s, agentTitles),
|
|
5259
|
+
registryDetail: oneLineStatus && oneLineStatus !== "running" ? oneLineStatus.slice(0, 80) : "",
|
|
5042
5260
|
agentName: s.agent_name ?? void 0
|
|
5043
5261
|
});
|
|
5044
5262
|
}
|
|
5263
|
+
subActivity.sync(activeSubagents.keys());
|
|
5045
5264
|
pushSubagents();
|
|
5046
5265
|
} catch {
|
|
5047
5266
|
}
|
|
@@ -5088,6 +5307,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5088
5307
|
bridge.close();
|
|
5089
5308
|
stream.close();
|
|
5090
5309
|
events.close();
|
|
5310
|
+
subActivity.closeAll();
|
|
5091
5311
|
mcp.closeAll();
|
|
5092
5312
|
const [, killed2] = await Promise.race([
|
|
5093
5313
|
Promise.all([stopped2, procsStopped2]),
|
|
@@ -5508,6 +5728,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5508
5728
|
bridge.close();
|
|
5509
5729
|
stream.close();
|
|
5510
5730
|
events.close();
|
|
5731
|
+
subActivity.closeAll();
|
|
5511
5732
|
mcp.closeAll();
|
|
5512
5733
|
const [, killed] = await Promise.race([
|
|
5513
5734
|
Promise.all([stopped, procsStopped]),
|