aiden-runtime 4.1.3 → 4.1.5
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/v4/aidenCLI.js +28 -0
- package/dist/cli/v4/callbacks.js +148 -13
- package/dist/cli/v4/chatSession.js +283 -22
- package/dist/cli/v4/defaultSoul.js +143 -4
- package/dist/cli/v4/display/frame.js +234 -0
- package/dist/cli/v4/display/progressBar.js +170 -0
- package/dist/cli/v4/display.js +663 -24
- package/dist/cli/v4/replyRenderer.js +196 -26
- package/dist/cli/v4/skinEngine.js +15 -4
- package/dist/cli/v4/toolPreview.js +78 -19
- package/dist/core/toolRegistry.js +7 -1
- package/dist/core/v4/aidenAgent.js +72 -0
- package/dist/core/v4/loopTrace.js +257 -0
- package/dist/core/v4/promptBuilder.js +2 -1
- package/dist/core/version.js +1 -1
- package/dist/core/webSearch.js +64 -24
- package/dist/providers/v4/anthropicAdapter.js +25 -2
- package/package.json +2 -1
- package/plugins/aiden-plugin-cdp-browser/.granted-permissions.json +8 -0
package/dist/cli/v4/display.js
CHANGED
|
@@ -18,13 +18,16 @@
|
|
|
18
18
|
*
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.TOOL_ROW_ARG_CAP = exports.TOOL_ROW_NAME_PAD = exports.Display = exports.SPINNER_PHRASES = exports.TOOL_ICONS = void 0;
|
|
21
|
+
exports.TRAIL_HIDE_TOOLS = exports.TOOL_ROW_ARG_CAP = exports.TOOL_ROW_NAME_PAD = exports.Display = exports.SPINNER_PHRASES = exports.TOOL_ICONS = void 0;
|
|
22
22
|
exports.iconForTool = iconForTool;
|
|
23
23
|
exports.detectConfiguredChannels = detectConfiguredChannels;
|
|
24
24
|
exports.summarizeConfiguredChannels = summarizeConfiguredChannels;
|
|
25
25
|
exports.summarizeChannelState = summarizeChannelState;
|
|
26
26
|
exports.voiceIndicator = voiceIndicator;
|
|
27
|
+
exports.makeNoOpToolRowHandle = makeNoOpToolRowHandle;
|
|
27
28
|
exports.previewToolArgs = previewToolArgs;
|
|
29
|
+
exports.verbForActivity = verbForActivity;
|
|
30
|
+
exports.isPreFramedLine = isPreFramedLine;
|
|
28
31
|
exports.countNewlines = countNewlines;
|
|
29
32
|
exports.splitAtUnclosedBold = splitAtUnclosedBold;
|
|
30
33
|
exports.formatToolDuration = formatToolDuration;
|
|
@@ -48,6 +51,12 @@ const replyRenderer_1 = require("./replyRenderer");
|
|
|
48
51
|
// Optional "Sources" footer when AIDEN_CITATIONS=1 (default off).
|
|
49
52
|
const citationFooter_1 = require("./citationFooter");
|
|
50
53
|
const toolPreview_1 = require("./toolPreview");
|
|
54
|
+
// v4.1.4 reply-quality polish: shared frame math for width + indent.
|
|
55
|
+
// `cols()`, `rule()`, `agentTurn`, and `tryRerenderInPlace` all route
|
|
56
|
+
// through frame helpers so the visible left edge / right margin / wrap
|
|
57
|
+
// targets are consistent across streaming, rerender, and one-shot
|
|
58
|
+
// reply paths. See `cli/v4/display/frame.ts` for the math.
|
|
59
|
+
const frame_1 = require("./display/frame");
|
|
51
60
|
/**
|
|
52
61
|
* v4.1.3-repl-polish — category emoji icons for the tool-row trail.
|
|
53
62
|
* Icons are ON by default (AIDEN_UI_ICONS !== '0'). Set
|
|
@@ -263,18 +272,25 @@ class Display {
|
|
|
263
272
|
// ── Phase 23.6 — v3 visual primitives ──────────────────────────────────
|
|
264
273
|
// Pure renderers (return strings, don't write) so chatSession can
|
|
265
274
|
// compose the boot card and turn rhythm without owning ANSI escapes.
|
|
266
|
-
/**
|
|
275
|
+
/**
|
|
276
|
+
* Terminal column count. v4.1.4 reply-quality polish: delegates to
|
|
277
|
+
* `frame.getTerminalCols()` so all width math shares one formula.
|
|
278
|
+
* Retains the 100-col cap via `Math.min` so existing callers that
|
|
279
|
+
* paint full-width chrome (boot card, footer) keep their visual
|
|
280
|
+
* identity — `frame.BODY_WIDTH_MAX` is the tunable.
|
|
281
|
+
*/
|
|
267
282
|
cols() {
|
|
268
|
-
return Math.min(this.out
|
|
283
|
+
return Math.min((0, frame_1.getTerminalCols)(this.out), 100);
|
|
269
284
|
}
|
|
270
285
|
/**
|
|
271
|
-
* Thin horizontal rule (`──…──`) in muted colour, full
|
|
272
|
-
*
|
|
273
|
-
* the
|
|
274
|
-
*
|
|
286
|
+
* Thin horizontal rule (`──…──`) in muted colour, full body width.
|
|
287
|
+
* v4.1.4 reply-quality polish: width sourced from `frame.getBodyWidth()`
|
|
288
|
+
* so the rule sits at the same right margin as wrapped prose and
|
|
289
|
+
* code blocks. Returns the line WITHOUT a trailing newline; caller
|
|
290
|
+
* adds one + the leading gutter.
|
|
275
291
|
*/
|
|
276
292
|
rule(width) {
|
|
277
|
-
const w = Math.max(8,
|
|
293
|
+
const w = Math.max(8, width ?? (0, frame_1.getBodyWidth)(this.out));
|
|
278
294
|
return this.skin.applyColors('─'.repeat(w), 'muted');
|
|
279
295
|
}
|
|
280
296
|
/** Render `▲` (brand-orange filled triangle) — Aiden's identity motif. */
|
|
@@ -725,6 +741,282 @@ class Display {
|
|
|
725
741
|
},
|
|
726
742
|
};
|
|
727
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* v4.1.4 reply-quality polish — Part 1.6 activity indicator.
|
|
746
|
+
*
|
|
747
|
+
* Renders `▲ {verb}{dots} (Ns) ▸▸ Ctrl+C cancel` on a single
|
|
748
|
+
* line. `verb` is the activity label; the dots pulse 0→1→2→3→0
|
|
749
|
+
* every 400ms; elapsed time `(Ns)` appears only once N >= 1 (avoids
|
|
750
|
+
* the `(0s)` flash). The "▸▸ Ctrl+C cancel" hint is folded into the
|
|
751
|
+
* same line so cursor management stays simple (single-line write,
|
|
752
|
+
* single-line erase).
|
|
753
|
+
*
|
|
754
|
+
* Pause/resume semantics:
|
|
755
|
+
* - `pause()` erases the line + stops the tick + sets paused=true.
|
|
756
|
+
* Elapsed time keeps accumulating wall-clock — when a later
|
|
757
|
+
* `resume()` re-renders, the indicator shows the TOTAL elapsed
|
|
758
|
+
* since the original `activityIndicator()` call, not just since
|
|
759
|
+
* the last resume.
|
|
760
|
+
* - `resume(verb?)` re-renders on a fresh line below the current
|
|
761
|
+
* cursor and restarts the tick. Optional `verb` swap is the
|
|
762
|
+
* supported way to transition phases ("thinking" → "drafting").
|
|
763
|
+
* - `stop()` is terminal — erases the line, marks stopped, refuses
|
|
764
|
+
* further pause/resume.
|
|
765
|
+
*
|
|
766
|
+
* Non-TTY: completely silent. No initial paint, no ticks, no erases.
|
|
767
|
+
* Pipes / CI / MCP serve mode get clean output by default.
|
|
768
|
+
*
|
|
769
|
+
* Cursor invariant on render: the indicator OWNS one line. After
|
|
770
|
+
* each render the cursor sits at column 0 of the indicator line
|
|
771
|
+
* (NOT a new line below it) — that way the next render erases the
|
|
772
|
+
* line and rewrites in place. Callers that want to write OTHER
|
|
773
|
+
* content below MUST call `pause()` first; otherwise their content
|
|
774
|
+
* lands on the indicator line and the next tick clobbers it.
|
|
775
|
+
*/
|
|
776
|
+
/**
|
|
777
|
+
* v4.1.5 Issue K — wave-bar option.
|
|
778
|
+
*
|
|
779
|
+
* When `opts.waveBar === true` (DEFAULT), the indicator paints a
|
|
780
|
+
* second row BELOW the verb line — a 10-cell `▰▱` snake-scroll
|
|
781
|
+
* heartbeat that gives visible motion during long pre-first-token
|
|
782
|
+
* gaps even when the verb doesn't change. The bar is NOT progress:
|
|
783
|
+
* it's a constant-cadence heartbeat (250ms shared with the dot
|
|
784
|
+
* pulse), explicitly not a percentage indicator.
|
|
785
|
+
*
|
|
786
|
+
* Pass `{ waveBar: false }` for back-compat with v4.1.4 tests that
|
|
787
|
+
* assert single-row geometry. Production callers (chatSession) get
|
|
788
|
+
* the wave bar by default.
|
|
789
|
+
*/
|
|
790
|
+
activityIndicator(initialVerb = 'thinking', opts = {}) {
|
|
791
|
+
const sk = this.skin;
|
|
792
|
+
const out = this.out;
|
|
793
|
+
const isTty = !!out.isTTY;
|
|
794
|
+
const startTime = Date.now();
|
|
795
|
+
let verb = initialVerb;
|
|
796
|
+
let dotFrame = 0;
|
|
797
|
+
let paused = !isTty; // non-TTY = effectively pre-paused (silent)
|
|
798
|
+
let stopped = false;
|
|
799
|
+
let printed = false;
|
|
800
|
+
let tickTimer = null;
|
|
801
|
+
// Tunable cadence. v4.1.4 Phase 3b' (Issue G): bumped from 400ms
|
|
802
|
+
// to 250ms after visual smoke — 400ms felt sluggish, made the
|
|
803
|
+
// indicator look static between seconds. 250ms gives ~4 dot
|
|
804
|
+
// updates per second so motion is always visible even when the
|
|
805
|
+
// (Ns) counter hasn't ticked. Slow enough not to flicker on SSH
|
|
806
|
+
// / slow ConPTY refresh.
|
|
807
|
+
const TICK_MS = 250;
|
|
808
|
+
// ▲ glyph in brand orange — the user's primary motif. Dots and
|
|
809
|
+
// elapsed counter paint muted to keep visual weight on the verb.
|
|
810
|
+
//
|
|
811
|
+
// v4.1.4 Phase 3b' (Issue F): the inline "▸▸ Ctrl+C cancel" hint
|
|
812
|
+
// shipped with Phase 3a was visually noisy on the activity line
|
|
813
|
+
// and collided with planner-debug dim writes. Dropped per user
|
|
814
|
+
// feedback; a separate bottom-of-screen footer can be added in
|
|
815
|
+
// v4.1.5 if wanted, but it must NOT be glued to the indicator.
|
|
816
|
+
const glyph = sk.applyColors('▲', 'brand');
|
|
817
|
+
// v4.1.5 Issue K — wave-bar state. Snake-scroll: a 3-cell `▰`
|
|
818
|
+
// block slides across 10 cells, wrapping at the right edge. Same
|
|
819
|
+
// 250ms tick as the verb dot pulse — one timer drives both rows.
|
|
820
|
+
const waveBarEnabled = opts.waveBar !== false; // default true
|
|
821
|
+
const WAVE_CELLS = 10;
|
|
822
|
+
const WAVE_BLOCK = 3;
|
|
823
|
+
let waveFrame = 0;
|
|
824
|
+
const buildLine = () => {
|
|
825
|
+
const dots = '.'.repeat(dotFrame); // 0..3 dots
|
|
826
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
|
|
827
|
+
const elapsedStr = elapsedSec >= 1
|
|
828
|
+
? ` ${sk.applyColors(`(${elapsedSec}s)`, 'muted')}`
|
|
829
|
+
: '';
|
|
830
|
+
// `▲ {verb}{dots-padded-to-3}{elapsed?}`
|
|
831
|
+
return `${glyph} ${verb}${dots.padEnd(3, ' ')}${elapsedStr}`;
|
|
832
|
+
};
|
|
833
|
+
/**
|
|
834
|
+
* v4.1.5 Issue K — render the wave-bar row. A 3-cell `▰` block at
|
|
835
|
+
* positions `[waveFrame, waveFrame+1, waveFrame+2]` mod 10. The
|
|
836
|
+
* filled cells paint brand orange, empty cells paint warm-muted.
|
|
837
|
+
* Same width + glyph set as the token progress bar so the two
|
|
838
|
+
* rows feel like a coherent palette (one is heartbeat, the other
|
|
839
|
+
* is real progress).
|
|
840
|
+
*
|
|
841
|
+
* Heartbeat semantics: this is NOT progress. The wave moves at a
|
|
842
|
+
* constant 250ms cadence regardless of any backend metric. It
|
|
843
|
+
* exists purely so the user sees motion during the unobservable
|
|
844
|
+
* TTFT (time-to-first-token) wait. The verb row above carries
|
|
845
|
+
* any real lifecycle signal via `setVerb()`.
|
|
846
|
+
*/
|
|
847
|
+
const buildWave = () => {
|
|
848
|
+
// v4.1.5 Phase 1d (Q-P1) — glyph palette switch. Was `▰`/`▱`
|
|
849
|
+
// (U+25B0/B1, Geometric Shapes) which legacy Windows console
|
|
850
|
+
// fonts render as tofu. Now `▓`/`░` (U+2593/91, Block Elements
|
|
851
|
+
// — in CP437, universally supported). Matches the existing
|
|
852
|
+
// statusFooter chrome that's shipped since v3 without ever
|
|
853
|
+
// being garbled.
|
|
854
|
+
const filled = new Set();
|
|
855
|
+
for (let i = 0; i < WAVE_BLOCK; i += 1) {
|
|
856
|
+
filled.add((waveFrame + i) % WAVE_CELLS);
|
|
857
|
+
}
|
|
858
|
+
// Render cells in order so the snake-scroll visually slides:
|
|
859
|
+
// we paint cell-by-cell with the right color, joined into one
|
|
860
|
+
// string. ANSI runs reset per cell — slight overhead but keeps
|
|
861
|
+
// glyph order true to position. Brand orange filled, warm-muted
|
|
862
|
+
// empty.
|
|
863
|
+
const cells = [];
|
|
864
|
+
for (let c = 0; c < WAVE_CELLS; c += 1) {
|
|
865
|
+
cells.push(filled.has(c)
|
|
866
|
+
? sk.applyColors('▓', 'brand')
|
|
867
|
+
: sk.applyColors('░', 'muted'));
|
|
868
|
+
}
|
|
869
|
+
return cells.join('');
|
|
870
|
+
};
|
|
871
|
+
// v4.1.5 Part 1a — Issue M (Windows ConPTY buffering fix).
|
|
872
|
+
//
|
|
873
|
+
// Prior pattern wrote `\r\x1b[K{indicator}` with NO trailing
|
|
874
|
+
// newline. On Windows ConPTY, `process.stdout` buffers no-newline
|
|
875
|
+
// writes — none of the 60 indicator ticks during a 15s gap
|
|
876
|
+
// actually rendered. The final reply's `\n` chars eventually
|
|
877
|
+
// flushed the buffer, but by then the indicator's stop()-erase
|
|
878
|
+
// had also been buffered + flushed, so the user saw 15s of blank
|
|
879
|
+
// followed by the reply dumping all at once.
|
|
880
|
+
//
|
|
881
|
+
// Fix: indicator OWNS one terminal row. Every write that paints
|
|
882
|
+
// the indicator ends with `\n`, which forces a flush on every
|
|
883
|
+
// platform. The cursor sits on the LINE BELOW the indicator
|
|
884
|
+
// while it's running (one visible empty row gap). When the
|
|
885
|
+
// indicator stops/pauses, we walk back UP to the indicator's
|
|
886
|
+
// row and erase it — the cursor then sits at col 0 of that
|
|
887
|
+
// (now empty) row, ready for the caller to write whatever
|
|
888
|
+
// content follows (header, tool row, stream output).
|
|
889
|
+
//
|
|
890
|
+
// ANSI primitives:
|
|
891
|
+
// `\x1b[1A` — cursor up 1 line
|
|
892
|
+
// `\x1b[2K` — erase the whole current line
|
|
893
|
+
// Sequence on tick: walk up → erase → paint → `\n` → cursor below.
|
|
894
|
+
// Sequence on erase: walk up → erase (no newline). Cursor on the
|
|
895
|
+
// now-empty indicator row, ready for caller.
|
|
896
|
+
const ANSI_UP_ERASE = '\x1b[1A\x1b[2K';
|
|
897
|
+
const renderTick = () => {
|
|
898
|
+
if (stopped || paused || !isTty)
|
|
899
|
+
return;
|
|
900
|
+
dotFrame = (dotFrame + 1) % 4;
|
|
901
|
+
// v4.1.5 Issue K — wave snake-scroll advances 1 cell per tick.
|
|
902
|
+
// Same 250ms cadence as the dot pulse, so both rows move in
|
|
903
|
+
// visible lockstep. Modulo WAVE_CELLS wraps the leading block
|
|
904
|
+
// back to the left edge.
|
|
905
|
+
waveFrame = (waveFrame + 1) % WAVE_CELLS;
|
|
906
|
+
if (waveBarEnabled) {
|
|
907
|
+
// 2-row layout: walk up TWO rows (two separate up-1+erase
|
|
908
|
+
// sequences, which keeps the `\x1b[1A\x1b[2K` substring
|
|
909
|
+
// assertion-compatible), repaint both, drop newlines so the
|
|
910
|
+
// cursor lands on the row below the wave bar.
|
|
911
|
+
out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}` +
|
|
912
|
+
`${buildLine()}\n` +
|
|
913
|
+
`${buildWave()}\n`);
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
// Single-row layout (back-compat with v4.1.4 tests).
|
|
917
|
+
out.write(`${ANSI_UP_ERASE}${buildLine()}\n`);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
const startTick = () => {
|
|
921
|
+
if (stopped || !isTty || tickTimer !== null)
|
|
922
|
+
return;
|
|
923
|
+
tickTimer = setInterval(renderTick, TICK_MS);
|
|
924
|
+
};
|
|
925
|
+
const stopTick = () => {
|
|
926
|
+
if (tickTimer !== null) {
|
|
927
|
+
clearInterval(tickTimer);
|
|
928
|
+
tickTimer = null;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
const eraseLine = () => {
|
|
932
|
+
// Walk up to the indicator's row(s) + erase. Cursor lands at
|
|
933
|
+
// col 0 of the (now empty) verb row. NO trailing newline here:
|
|
934
|
+
// the caller is about to write content on this row, and
|
|
935
|
+
// whatever they write will include their own `\n` to flush
|
|
936
|
+
// the buffer. If we emitted `\n` here, we'd leave a phantom
|
|
937
|
+
// blank row before the caller's content.
|
|
938
|
+
//
|
|
939
|
+
// v4.1.5 Issue K — with wave bar enabled, walk up 2 rows (two
|
|
940
|
+
// up-1+erase sequences). Without the bar, walk up 1 row.
|
|
941
|
+
if (!isTty || !printed)
|
|
942
|
+
return;
|
|
943
|
+
if (waveBarEnabled) {
|
|
944
|
+
out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}`);
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
out.write(ANSI_UP_ERASE);
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
// Initial paint — only on TTY. Indicator + `\n` so the buffer
|
|
951
|
+
// flushes and the cursor sits on the row below, ready for the
|
|
952
|
+
// first tick to walk back up.
|
|
953
|
+
//
|
|
954
|
+
// v4.1.5 Issue K — when wave bar is enabled, paint TWO rows:
|
|
955
|
+
// verb row + wave row, each with trailing `\n`. Cursor lands on
|
|
956
|
+
// the row below the wave bar. The first tick will walk up 2.
|
|
957
|
+
if (isTty) {
|
|
958
|
+
if (waveBarEnabled) {
|
|
959
|
+
out.write(`${buildLine()}\n${buildWave()}\n`);
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
out.write(`${buildLine()}\n`);
|
|
963
|
+
}
|
|
964
|
+
printed = true;
|
|
965
|
+
startTick();
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
pause: () => {
|
|
969
|
+
if (stopped || paused)
|
|
970
|
+
return;
|
|
971
|
+
paused = true;
|
|
972
|
+
stopTick();
|
|
973
|
+
eraseLine();
|
|
974
|
+
// After erase the cursor is at column 0 of the indicator's
|
|
975
|
+
// (now empty) line. Caller is expected to write its own
|
|
976
|
+
// content next; that content lands cleanly on this line.
|
|
977
|
+
},
|
|
978
|
+
resume: (newVerb) => {
|
|
979
|
+
if (stopped)
|
|
980
|
+
return;
|
|
981
|
+
if (typeof newVerb === 'string' && newVerb.length > 0)
|
|
982
|
+
verb = newVerb;
|
|
983
|
+
if (!paused)
|
|
984
|
+
return;
|
|
985
|
+
paused = false;
|
|
986
|
+
if (!isTty)
|
|
987
|
+
return;
|
|
988
|
+
// Caller has just finished writing its own content (typically
|
|
989
|
+
// ending with `\n`), so the cursor is on a fresh line below
|
|
990
|
+
// whatever was there. Paint the indicator + `\n` to claim the
|
|
991
|
+
// current row(s) and leave the cursor on the row below — same
|
|
992
|
+
// invariant the initial paint and tick maintain. Trailing `\n`
|
|
993
|
+
// also flushes Windows ConPTY buffering (Issue M).
|
|
994
|
+
//
|
|
995
|
+
// v4.1.5 Issue K — repaint BOTH rows when wave bar enabled.
|
|
996
|
+
if (waveBarEnabled) {
|
|
997
|
+
out.write(`${buildLine()}\n${buildWave()}\n`);
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
out.write(`${buildLine()}\n`);
|
|
1001
|
+
}
|
|
1002
|
+
printed = true;
|
|
1003
|
+
startTick();
|
|
1004
|
+
},
|
|
1005
|
+
setVerb: (newVerb) => {
|
|
1006
|
+
if (typeof newVerb === 'string' && newVerb.length > 0)
|
|
1007
|
+
verb = newVerb;
|
|
1008
|
+
},
|
|
1009
|
+
stop: () => {
|
|
1010
|
+
if (stopped)
|
|
1011
|
+
return;
|
|
1012
|
+
stopped = true;
|
|
1013
|
+
stopTick();
|
|
1014
|
+
eraseLine();
|
|
1015
|
+
},
|
|
1016
|
+
isPaused: () => paused,
|
|
1017
|
+
isStopped: () => stopped,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
728
1020
|
// ── Phase 23.5 — tool event row ───────────────────────────────────────
|
|
729
1021
|
// One line per tool call: a "·" gutter, the keyword `tool`, the
|
|
730
1022
|
// tool name (soft cyan, padded), a brief truncated arg preview, and
|
|
@@ -737,6 +1029,31 @@ class Display {
|
|
|
737
1029
|
// completion so each line in the log carries the final state — no
|
|
738
1030
|
// ANSI cursor games on a dumb sink.
|
|
739
1031
|
toolRow(name, args) {
|
|
1032
|
+
// v4.1.5 Phase 1d (Q-Q2-a) — TRAIL_HIDE_TOOLS suppression.
|
|
1033
|
+
//
|
|
1034
|
+
// Some tools are pure agent plumbing — the model calls them to
|
|
1035
|
+
// introspect its own registry, not to do user-visible work.
|
|
1036
|
+
// `lookup_tool_schema` is the canonical case: during planning
|
|
1037
|
+
// the agent may invoke it 30+ times to discover unfamiliar tool
|
|
1038
|
+
// shapes. Each call is a sub-millisecond in-memory lookup, but
|
|
1039
|
+
// they flood the visible trail with noise that obscures the
|
|
1040
|
+
// actual user-relevant tool calls.
|
|
1041
|
+
//
|
|
1042
|
+
// Short-circuit: hidden tools get a NO-OP handle that satisfies
|
|
1043
|
+
// the `ToolRowHandle` contract (ok/fail/degraded/retry/blocked/
|
|
1044
|
+
// emptyRetry/emptyFail all defined but write nothing). The
|
|
1045
|
+
// execution path itself is unaffected — the agent still calls
|
|
1046
|
+
// the tool, the planner / skill-enforcement trackers still
|
|
1047
|
+
// record it. Only the visual row is suppressed.
|
|
1048
|
+
//
|
|
1049
|
+
// CRITICAL invariant: `setBeforeFirstToolHook` is fired by
|
|
1050
|
+
// callbacks.ts BEFORE `toolRow()` is called (see callbacks.ts
|
|
1051
|
+
// onToolCall 'before' branch), so `turnHadTools` flips even for
|
|
1052
|
+
// hidden tools. The separator logic stays correct regardless of
|
|
1053
|
+
// whether ONLY hidden tools fired this turn.
|
|
1054
|
+
if (exports.TRAIL_HIDE_TOOLS.has(name)) {
|
|
1055
|
+
return makeNoOpToolRowHandle();
|
|
1056
|
+
}
|
|
740
1057
|
const sk = this.skin;
|
|
741
1058
|
// ── Build the fixed left portion (icon + verb + detail) ────────────
|
|
742
1059
|
// v4.1.3-repl-polish: icons default ON; set AIDEN_UI_ICONS=0 to
|
|
@@ -838,8 +1155,31 @@ class Display {
|
|
|
838
1155
|
writeFinal(`ok ${formatToolDuration(durationMs)} after ${retries} ${retries === 1 ? 'retry' : 'retries'}`, 'warn');
|
|
839
1156
|
}
|
|
840
1157
|
else {
|
|
841
|
-
//
|
|
842
|
-
|
|
1158
|
+
// v4.1.5 Issue N — persistent tool trail in scrollback.
|
|
1159
|
+
//
|
|
1160
|
+
// Prior behaviour: silent erase on clean success (`eraseLast()`
|
|
1161
|
+
// with no replacement write). Tool rows for successful tools
|
|
1162
|
+
// vanished, leaving only the markdown reply visible afterward.
|
|
1163
|
+
// The user couldn't see WHAT actions Aiden took unless a tool
|
|
1164
|
+
// failed or degraded.
|
|
1165
|
+
//
|
|
1166
|
+
// Fix: replace the silent erase with a completed-state row
|
|
1167
|
+
// painted entirely in warm-muted (`#b8a89a` from v4.1.4). The
|
|
1168
|
+
// duration suffix replaces the live `running Ns…` chrome; the
|
|
1169
|
+
// whole row reads "done" via reduced visual weight. Failed /
|
|
1170
|
+
// degraded / retry outcomes keep their existing coloured paint
|
|
1171
|
+
// (error red, degraded yellow, warn amber) — only clean success
|
|
1172
|
+
// shifts from "silent" to "muted-persistent."
|
|
1173
|
+
//
|
|
1174
|
+
// The persistence mechanism is the existing `writeFinal` path:
|
|
1175
|
+
// it walks up + erases the running row, then writes the final
|
|
1176
|
+
// row with trailing `\n`. The row sits in scrollback because
|
|
1177
|
+
// `streamComplete` rerenders only the post-tool stream chunk
|
|
1178
|
+
// (via `streamLineCount` which was reset to 0 inside
|
|
1179
|
+
// `commitStreamChunk` before this row wrote). No additional
|
|
1180
|
+
// isolation machinery needed — already verified by 13/13
|
|
1181
|
+
// `smoke-stream-rerender.ts` regressions.
|
|
1182
|
+
writeFinal(formatToolDuration(durationMs), 'muted');
|
|
843
1183
|
}
|
|
844
1184
|
},
|
|
845
1185
|
fail(durationMs, retries = 0) {
|
|
@@ -948,15 +1288,57 @@ class Display {
|
|
|
948
1288
|
const sk = this.skin;
|
|
949
1289
|
const useMd = opts.markdown !== false;
|
|
950
1290
|
const rawBody = useMd ? this.markdown(text).trimEnd() : text;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1291
|
+
// v4.1.4 reply-quality polish — F1 detect-and-skip indent + wrap.
|
|
1292
|
+
//
|
|
1293
|
+
// Walks the rendered markdown line-by-line, but applies frame
|
|
1294
|
+
// indent/wrap ONLY to plain prose lines. Lines that already carry
|
|
1295
|
+
// structural chrome (code-block rail+bg, blockquote rail,
|
|
1296
|
+
// pre-indented list bullets) pass through untouched — `renderCode-
|
|
1297
|
+
// Block`, `renderBlockquote`, and the list override already own
|
|
1298
|
+
// their own gutter + per-line wrap. Double-applying the gutter
|
|
1299
|
+
// shifts content right by 3 cols; double-wrapping breaks the rail
|
|
1300
|
+
// off the wrap-continuation row. See `isPreFramedLine` for the
|
|
1301
|
+
// detection rules.
|
|
1302
|
+
const indented = this.applyFrameToRendered(rawBody);
|
|
955
1303
|
const reasoning = opts.reasoning
|
|
956
|
-
?
|
|
1304
|
+
? `${(0, frame_1.getIndent)(0)}${sk.applyColors(opts.reasoning.trim(), 'muted')}\n`
|
|
957
1305
|
: '';
|
|
958
1306
|
return `${this.agentHeader()}${reasoning}${indented}\n`;
|
|
959
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* v4.1.4 reply-quality polish — F1 shared helper.
|
|
1310
|
+
*
|
|
1311
|
+
* Apply frame indent + soft-wrap to the prose lines of a rendered
|
|
1312
|
+
* markdown body, but pass structural lines (code-block rail+bg,
|
|
1313
|
+
* blockquote rail, pre-indented list bullets) through unchanged.
|
|
1314
|
+
*
|
|
1315
|
+
* Shared by `agentTurn` (one-shot reply) and `tryRerenderInPlace`
|
|
1316
|
+
* (post-stream rerender) so both paths produce identical output.
|
|
1317
|
+
*/
|
|
1318
|
+
applyFrameToRendered(rawBody) {
|
|
1319
|
+
const indent = (0, frame_1.getIndent)(0);
|
|
1320
|
+
const bw = (0, frame_1.getBodyWidth)(this.out);
|
|
1321
|
+
return rawBody
|
|
1322
|
+
.split('\n')
|
|
1323
|
+
.map((ln) => {
|
|
1324
|
+
if (ln.length === 0)
|
|
1325
|
+
return '';
|
|
1326
|
+
// F1 detect-and-skip: pre-framed lines (code-block chrome, list
|
|
1327
|
+
// bullets, blockquote rails) own their own gutter + wrap. Don't
|
|
1328
|
+
// re-indent or re-wrap them — that double-applies the gutter
|
|
1329
|
+
// and breaks the rail off wrap-continuation rows.
|
|
1330
|
+
if (isPreFramedLine(ln))
|
|
1331
|
+
return ln;
|
|
1332
|
+
// Plain prose: indent + wrap to bodyWidth. wrap-ansi handles
|
|
1333
|
+
// ANSI-aware width counting so bold/heading paint survives.
|
|
1334
|
+
const wrapped = (0, frame_1.wrap)(ln, bw, { trim: false, hard: true });
|
|
1335
|
+
return wrapped
|
|
1336
|
+
.split('\n')
|
|
1337
|
+
.map((vln) => `${indent}${vln}`)
|
|
1338
|
+
.join('\n');
|
|
1339
|
+
})
|
|
1340
|
+
.join('\n');
|
|
1341
|
+
}
|
|
960
1342
|
/**
|
|
961
1343
|
* Format a recoverable error with optional remediation suggestion.
|
|
962
1344
|
* Output goes through the caller (returned as string), not stderr.
|
|
@@ -1075,6 +1457,33 @@ class Display {
|
|
|
1075
1457
|
}
|
|
1076
1458
|
this.out.write('\n');
|
|
1077
1459
|
}
|
|
1460
|
+
/**
|
|
1461
|
+
* v4.1.4 reply-quality polish (Q-ResizeReflow Option B): zero the
|
|
1462
|
+
* per-chunk row counter when the terminal resizes mid-stream.
|
|
1463
|
+
*
|
|
1464
|
+
* Why: the resize guard hard-clears the viewport (`\x1b[2J\x1b[H`)
|
|
1465
|
+
* which removes ALL rows from the screen — but our `streamLineCount`
|
|
1466
|
+
* still believes those rows are there. The next `tryRerenderInPlace`
|
|
1467
|
+
* would walk the cursor back N rows that no longer exist, leaving a
|
|
1468
|
+
* ghost gap at the top of the new viewport. Zeroing the count makes
|
|
1469
|
+
* the next eraser a no-op (which is correct — there's nothing left
|
|
1470
|
+
* to erase).
|
|
1471
|
+
*
|
|
1472
|
+
* Idempotent: no-op when no stream is active. Safe to call from a
|
|
1473
|
+
* resize callback that fires unconditionally on every viewport
|
|
1474
|
+
* change. Also resets `streamBuffer` so the next commit doesn't try
|
|
1475
|
+
* to rerender content that was already wiped.
|
|
1476
|
+
*/
|
|
1477
|
+
resetStreamFrameForResize() {
|
|
1478
|
+
if (!this.streamHeaderShown)
|
|
1479
|
+
return;
|
|
1480
|
+
this.streamLineCount = 0;
|
|
1481
|
+
this.streamBuffer = '';
|
|
1482
|
+
// Header was wiped by the hard-clear too — let the next
|
|
1483
|
+
// streamPartial / agentTurn write a fresh one.
|
|
1484
|
+
this.streamHeaderShown = false;
|
|
1485
|
+
this.streamLastEndedNewline = false;
|
|
1486
|
+
}
|
|
1078
1487
|
/**
|
|
1079
1488
|
* Append a streamed text fragment. Writes a styled "Aiden" header on
|
|
1080
1489
|
* the first call of a turn, then writes raw text directly via the
|
|
@@ -1099,12 +1508,83 @@ class Display {
|
|
|
1099
1508
|
this.out.write(text);
|
|
1100
1509
|
this.streamLastEndedNewline = text.endsWith('\n');
|
|
1101
1510
|
// Phase v4.1-reply-formatting: track buffer + line count for the
|
|
1102
|
-
// post-stream re-render.
|
|
1103
|
-
// so the eraser later knows how many rows to clear.
|
|
1511
|
+
// post-stream re-render.
|
|
1104
1512
|
this.streamBuffer += text;
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1513
|
+
// v4.1.4 reply-quality polish — F-B1 wrap-aware row count.
|
|
1514
|
+
//
|
|
1515
|
+
// Prior counter just `streamLineCount += text.match(/\n/g)?.length`
|
|
1516
|
+
// — counted `\n` chars only. When the model emits a long single
|
|
1517
|
+
// line (e.g. a 100-char bullet on an 80-col terminal), the terminal
|
|
1518
|
+
// naturally wraps it across multiple screen rows, but the old
|
|
1519
|
+
// counter would still think it's 1 row. At streamComplete the
|
|
1520
|
+
// eraser walked back N rows that didn't match the wrapped row
|
|
1521
|
+
// count → raw `**markup**` from the streaming phase remained
|
|
1522
|
+
// visible above the rerendered output.
|
|
1523
|
+
//
|
|
1524
|
+
// Confirmed undercount via scripts/smoke-stream-wrap-count.ts:
|
|
1525
|
+
// 3 long bullets on 80-col counted 3, actually 6. Multi-chunk
|
|
1526
|
+
// preamble + bullets on 40-col counted 4, actually 8.
|
|
1527
|
+
//
|
|
1528
|
+
// Fix: count `ceil(visibleWidth / cols)` rows per `\n`-delimited
|
|
1529
|
+
// segment, then add 1 for the `\n` itself (cursor advances to
|
|
1530
|
+
// next row when newline is emitted). Visible width strips ANSI.
|
|
1531
|
+
this.streamLineCount += this.countStreamRows(text);
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* v4.1.4 reply-quality polish — F-B1 helper.
|
|
1535
|
+
*
|
|
1536
|
+
* Estimate how many screen rows `text` consumes when written to a
|
|
1537
|
+
* terminal of width `this.out.columns`. Counts terminal-natural-wrap
|
|
1538
|
+
* rows for each logical line, plus one row per `\n`.
|
|
1539
|
+
*
|
|
1540
|
+
* Falls back to a sane count when columns is undefined (non-TTY or
|
|
1541
|
+
* pre-resize): in that case the eraser won't fire anyway
|
|
1542
|
+
* (`tryRerenderInPlace` gates on `out.isTTY`), so the count is
|
|
1543
|
+
* effectively ignored. We still compute a defensible value so any
|
|
1544
|
+
* future TTY-detection change doesn't silently regress.
|
|
1545
|
+
*
|
|
1546
|
+
* Pure with respect to ANSI: escape sequences pass through
|
|
1547
|
+
* `visibleLength` and don't inflate the row count.
|
|
1548
|
+
*
|
|
1549
|
+
* Edge cases:
|
|
1550
|
+
* - Empty text → 0 rows (consistent with the prior counter).
|
|
1551
|
+
* - Text without `\n` → ceil(visibleLen / cols) rows.
|
|
1552
|
+
* - Trailing `\n` → counts the prior content row + 1 for the
|
|
1553
|
+
* newline. Cursor is now at the start of the next row, which is
|
|
1554
|
+
* correct screen-state — the next streamPartial extends from
|
|
1555
|
+
* col 0 of that row.
|
|
1556
|
+
*/
|
|
1557
|
+
countStreamRows(text) {
|
|
1558
|
+
if (text.length === 0)
|
|
1559
|
+
return 0;
|
|
1560
|
+
const cols = (typeof this.out.columns === 'number' && this.out.columns >= 1)
|
|
1561
|
+
? this.out.columns
|
|
1562
|
+
: 80;
|
|
1563
|
+
// Semantics: counter tracks ROW BOUNDARIES CROSSED during
|
|
1564
|
+
// emission, not "rows occupied". The eraser uses `\x1b[<N>F`
|
|
1565
|
+
// which moves the cursor up N rows; if N matches the number of
|
|
1566
|
+
// boundaries crossed from start-of-stream to current-cursor, the
|
|
1567
|
+
// eraser lands at the start row and `\x1b[J` clears the rest.
|
|
1568
|
+
//
|
|
1569
|
+
// For a segment of visible width V on a terminal of width C:
|
|
1570
|
+
// - V == 0 → 0 wrap boundaries
|
|
1571
|
+
// - V <= C → 0 wrap boundaries (single row)
|
|
1572
|
+
// - C < V <= 2C → 1 wrap boundary
|
|
1573
|
+
// - General → floor((V - 1) / C) wrap boundaries
|
|
1574
|
+
//
|
|
1575
|
+
// Each `\n` between segments crosses one boundary regardless of
|
|
1576
|
+
// visible width — that's the newline advancing the cursor.
|
|
1577
|
+
let rows = 0;
|
|
1578
|
+
const segments = text.split('\n');
|
|
1579
|
+
for (let i = 0; i < segments.length; i += 1) {
|
|
1580
|
+
const seg = segments[i] ?? '';
|
|
1581
|
+
const visible = (0, box_1.visibleLength)(seg);
|
|
1582
|
+
if (visible > 0)
|
|
1583
|
+
rows += Math.floor((visible - 1) / cols);
|
|
1584
|
+
if (i < segments.length - 1)
|
|
1585
|
+
rows += 1;
|
|
1586
|
+
}
|
|
1587
|
+
return rows;
|
|
1108
1588
|
}
|
|
1109
1589
|
/**
|
|
1110
1590
|
* v4.1.3-essentials: rerender a buffered stream chunk in place. Walks
|
|
@@ -1161,10 +1641,13 @@ class Display {
|
|
|
1161
1641
|
// \x1b[J = erase from cursor to end of screen.
|
|
1162
1642
|
this.out.write(`\x1b[${lines}F\x1b[J`);
|
|
1163
1643
|
const formatted = this.markdown(buffered).trimEnd();
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1644
|
+
// v4.1.4 reply-quality polish: same detect-and-skip indent + wrap
|
|
1645
|
+
// as agentTurn so streamed and one-shot replies share the visible
|
|
1646
|
+
// frame. wrap-ansi handles ANSI-aware width counting for prose;
|
|
1647
|
+
// structural lines (code-block chrome, list bullets, blockquote
|
|
1648
|
+
// rails) pass through unchanged so their own gutter + wrap stays
|
|
1649
|
+
// intact.
|
|
1650
|
+
const indented = this.applyFrameToRendered(formatted);
|
|
1168
1651
|
this.out.write(indented + '\n');
|
|
1169
1652
|
}
|
|
1170
1653
|
catch {
|
|
@@ -1398,6 +1881,56 @@ function renderRmsBar(rms) {
|
|
|
1398
1881
|
exports.TOOL_ROW_NAME_PAD = toolTrail_1.TRAIL_VERB_PAD;
|
|
1399
1882
|
/** @deprecated Use TRAIL_DETAIL_CAP. */
|
|
1400
1883
|
exports.TOOL_ROW_ARG_CAP = toolTrail_1.TRAIL_DETAIL_CAP;
|
|
1884
|
+
/**
|
|
1885
|
+
* v4.1.5 Phase 1d (Q-Q2-a) — names of tools that should be SUPPRESSED
|
|
1886
|
+
* from the visible tool-trail row, even though they still execute
|
|
1887
|
+
* normally through the agent loop.
|
|
1888
|
+
*
|
|
1889
|
+
* The canonical case is `lookup_tool_schema`: the agent calls it
|
|
1890
|
+
* during planning to introspect tool registry entries (in-memory
|
|
1891
|
+
* registry get, sub-millisecond per call). On complex prompts the
|
|
1892
|
+
* model may fire it 30+ times in a row, flooding the visible trail
|
|
1893
|
+
* with rows that don't represent user-meaningful work. Suppressing
|
|
1894
|
+
* them keeps the trail focused on the tools that did real work
|
|
1895
|
+
* (web_search, file_read, etc.).
|
|
1896
|
+
*
|
|
1897
|
+
* Suppression happens at `Display.toolRow()` entry — it returns a
|
|
1898
|
+
* no-op handle that satisfies the `ToolRowHandle` contract but
|
|
1899
|
+
* never writes to stdout. The agent's `callbacks.onToolCall`
|
|
1900
|
+
* dispatch is unchanged: `setBeforeFirstToolHook` still fires (so
|
|
1901
|
+
* `turnHadTools` flips for the separator-emission logic), and
|
|
1902
|
+
* skill-enforcement / honesty-trace tracking still records the
|
|
1903
|
+
* call. Only the visual row is hidden.
|
|
1904
|
+
*
|
|
1905
|
+
* Exported as a `Set` so callers can mutate at runtime if they
|
|
1906
|
+
* need to hide additional tools (e.g. user customization, MCP
|
|
1907
|
+
* plumbing tools). Mutation-of-shared-state is intentional — there's
|
|
1908
|
+
* no per-session config plumbing for "trail hidden tools" yet, so
|
|
1909
|
+
* the env-var pattern (`AIDEN_TRAIL_HIDE=tool1,tool2`) would be the
|
|
1910
|
+
* v4.1.6 evolution.
|
|
1911
|
+
*/
|
|
1912
|
+
exports.TRAIL_HIDE_TOOLS = new Set([
|
|
1913
|
+
'lookup_tool_schema',
|
|
1914
|
+
]);
|
|
1915
|
+
/**
|
|
1916
|
+
* v4.1.5 Phase 1d helper — produces a `ToolRowHandle` that satisfies
|
|
1917
|
+
* the contract but writes nothing. Used by hidden tools (see
|
|
1918
|
+
* `TRAIL_HIDE_TOOLS`) and as a safe fallback. All methods are inert.
|
|
1919
|
+
*
|
|
1920
|
+
* Pure — no side effects, no closures over Display state. Safe to
|
|
1921
|
+
* call from any thread / phase.
|
|
1922
|
+
*/
|
|
1923
|
+
function makeNoOpToolRowHandle() {
|
|
1924
|
+
return {
|
|
1925
|
+
ok: () => { },
|
|
1926
|
+
fail: () => { },
|
|
1927
|
+
degraded: () => { },
|
|
1928
|
+
retry: () => { },
|
|
1929
|
+
blocked: () => { },
|
|
1930
|
+
emptyRetry: () => { },
|
|
1931
|
+
emptyFail: () => { },
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1401
1934
|
/**
|
|
1402
1935
|
* Build a compact, single-line preview of the tool's arguments. Picks
|
|
1403
1936
|
* the most informative scalar fields when the args are an object, then
|
|
@@ -1450,6 +1983,112 @@ function truncToolArg(s) {
|
|
|
1450
1983
|
return flat;
|
|
1451
1984
|
return flat.slice(0, exports.TOOL_ROW_ARG_CAP - 1) + '…';
|
|
1452
1985
|
}
|
|
1986
|
+
/**
|
|
1987
|
+
* v4.1.4 reply-quality polish — Part 1.6 tool-aware verb mapper.
|
|
1988
|
+
*
|
|
1989
|
+
* Picks the activity-indicator verb for the gap that follows a given
|
|
1990
|
+
* tool's completion. The verb reflects "what the model is likely doing
|
|
1991
|
+
* next" rather than "what just happened" — so a `file_read` completing
|
|
1992
|
+
* leads to "reading" (model is digesting the contents) rather than
|
|
1993
|
+
* "drafted" (which would imply done). Tested in display.test.ts.
|
|
1994
|
+
*
|
|
1995
|
+
* Categories (matches against the tool-name substring, lowercased):
|
|
1996
|
+
* - read/list/view/get/inspect → 'reading'
|
|
1997
|
+
* - search/web/fetch_url/scrape → 'searching'
|
|
1998
|
+
* - shell/exec/run/compute/system → 'analyzing'
|
|
1999
|
+
* - write/edit/patch/save → 'drafting'
|
|
2000
|
+
* - everything else (or undefined) → 'thinking'
|
|
2001
|
+
*
|
|
2002
|
+
* Special caller-supplied phase override:
|
|
2003
|
+
* - When the caller knows "all tools are done, reply about to start"
|
|
2004
|
+
* they pass `phase: 'post-all'` → verb defaults to 'drafting'
|
|
2005
|
+
* regardless of the last tool name.
|
|
2006
|
+
*
|
|
2007
|
+
* Pure; exported for unit-test access.
|
|
2008
|
+
*/
|
|
2009
|
+
function verbForActivity(toolName, phase = 'post-tool') {
|
|
2010
|
+
if (phase === 'pre-tools')
|
|
2011
|
+
return 'thinking';
|
|
2012
|
+
if (phase === 'post-all')
|
|
2013
|
+
return 'drafting';
|
|
2014
|
+
const t = (toolName ?? '').toLowerCase();
|
|
2015
|
+
if (t.length === 0)
|
|
2016
|
+
return 'thinking';
|
|
2017
|
+
// Match in priority order so 'web_search' hits 'searching' (search)
|
|
2018
|
+
// before 'reading' (a hypothetical 'web_search_read' would still
|
|
2019
|
+
// map to 'searching' since search hits first).
|
|
2020
|
+
if (/(^|_)(search|web|fetch_url|scrape|crawl)(_|$)/.test(t))
|
|
2021
|
+
return 'searching';
|
|
2022
|
+
if (/(^|_)(read|list|view|get|inspect|info|status)(_|$)/.test(t))
|
|
2023
|
+
return 'reading';
|
|
2024
|
+
if (/(^|_)(write|edit|patch|save|create|append|delete|remove)(_|$)/.test(t))
|
|
2025
|
+
return 'drafting';
|
|
2026
|
+
if (/(^|_)(shell|exec|execute|run|compute|process|system|launch)(_|$)/.test(t))
|
|
2027
|
+
return 'analyzing';
|
|
2028
|
+
return 'thinking';
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* v4.1.4 reply-quality polish — F1 detect-and-skip predicate.
|
|
2032
|
+
*
|
|
2033
|
+
* Returns true when `line` is a structural / pre-framed line emitted
|
|
2034
|
+
* by replyRenderer (code-block chrome, blockquote rail, indented list
|
|
2035
|
+
* bullet, fence rules). These lines OWN their own gutter and per-line
|
|
2036
|
+
* wrap; `agentTurn` and `tryRerenderInPlace` MUST pass them through
|
|
2037
|
+
* unchanged so the post-render indent+wrap pass doesn't:
|
|
2038
|
+
* - Double the gutter (content drifts 3 cols right per pass)
|
|
2039
|
+
* - Re-wrap an already-wrapped code line (rail/bg breaks across
|
|
2040
|
+
* the new wrap continuation row)
|
|
2041
|
+
*
|
|
2042
|
+
* Detection rules (all on the ANSI-bearing line as emitted by marked
|
|
2043
|
+
* via our renderer overrides):
|
|
2044
|
+
* - Contains `\x1b[48;` anywhere → 24-bit bg paint = code-block
|
|
2045
|
+
* line. Always pre-framed (renderCodeBlock applies gutter + rail).
|
|
2046
|
+
* - Starts with ` │ ` or ` ┃ ` → explicit pre-framed rail
|
|
2047
|
+
* (code or blockquote at the frame gutter).
|
|
2048
|
+
* - Matches `^\s{2,}(•|▸|\d+\.)\s` (depth-indented list bullet) →
|
|
2049
|
+
* the list override already applied the per-depth indent.
|
|
2050
|
+
* - Matches `^\s{0,4}─{8,}` (horizontal-rule run or fence) → render-
|
|
2051
|
+
* specific divider already styled.
|
|
2052
|
+
*
|
|
2053
|
+
* Pure; exported for unit-test access.
|
|
2054
|
+
*/
|
|
2055
|
+
function isPreFramedLine(line) {
|
|
2056
|
+
if (line.length === 0)
|
|
2057
|
+
return false;
|
|
2058
|
+
// eslint-disable-next-line no-control-regex
|
|
2059
|
+
const stripped = line.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
2060
|
+
// v4.1.4 reply-quality polish — Fix D (tightened predicate).
|
|
2061
|
+
//
|
|
2062
|
+
// Code-block body lines start with the frame gutter + rail. The
|
|
2063
|
+
// 24-bit bg paint (`\x1b[48;…`) also appears on these lines, but
|
|
2064
|
+
// we MUST NOT use bg-presence alone as the trigger: the `codespan`
|
|
2065
|
+
// renderer wraps inline `` `code` `` with the same bg envelope, so
|
|
2066
|
+
// any prose line containing inline code would be wrongly classified
|
|
2067
|
+
// as a code-block line and would bypass the indent + wrap pass
|
|
2068
|
+
// (Issue D from visual smoke — prose with inline codespans
|
|
2069
|
+
// terminal-natural-wrapped past bodyWidth).
|
|
2070
|
+
//
|
|
2071
|
+
// Rail prefix (after ANSI strip) is the reliable signal: only
|
|
2072
|
+
// `renderCodeBlock` emits ` │ ` and only `renderBlockquote` emits
|
|
2073
|
+
// `┃ ` at line start (with display-layer gutter prepended).
|
|
2074
|
+
if (/^ │ /.test(stripped))
|
|
2075
|
+
return true;
|
|
2076
|
+
if (/^ ┃ /.test(stripped) || /^┃ /.test(stripped))
|
|
2077
|
+
return true;
|
|
2078
|
+
// Depth-indented list bullets emitted by the renderer.list override:
|
|
2079
|
+
// ` • prose…` (depth 1, 2-space indent)
|
|
2080
|
+
// ` ▸ prose…` (depth 2, 4-space indent)
|
|
2081
|
+
// ` 1. prose…` (numbered, depth 1)
|
|
2082
|
+
if (/^\s{2,}(•|▸|\d+\.)\s/.test(stripped))
|
|
2083
|
+
return true;
|
|
2084
|
+
// Code-block fence rules (long runs of `─` with optional leading
|
|
2085
|
+
// gutter + optional language label from renderCodeBlock). Match
|
|
2086
|
+
// ANYWHERE in the line so the language-tagged top rule
|
|
2087
|
+
// (` ── lang ──────…──`) trips alongside the unlabeled bottom rule.
|
|
2088
|
+
if (/─{8,}/.test(stripped))
|
|
2089
|
+
return true;
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
1453
2092
|
/**
|
|
1454
2093
|
* v4.1.3-essentials boldwrap-fix: count `\n` occurrences in `s`.
|
|
1455
2094
|
* Used by `commitStreamChunk` to recompute `streamLineCount` after
|