aiden-runtime 4.7.0 → 4.8.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/README.md +12 -1
- package/dist/cli/v4/aidenCLI.js +40 -5
- package/dist/cli/v4/callbacks.js +52 -31
- package/dist/cli/v4/chatSession.js +46 -1
- package/dist/cli/v4/commands/help.js +22 -11
- package/dist/cli/v4/commands/runs.js +42 -24
- package/dist/cli/v4/commands/skills.js +15 -17
- package/dist/cli/v4/commands/usage.js +17 -5
- package/dist/cli/v4/daemonAgentBuilder.js +1 -0
- package/dist/cli/v4/design/tokens.js +265 -0
- package/dist/cli/v4/display/framedPanel.js +116 -0
- package/dist/cli/v4/display/toolTrail.js +2 -2
- package/dist/cli/v4/display.js +446 -164
- package/dist/cli/v4/onboarding/disclaimer.js +42 -10
- package/dist/cli/v4/onboarding/loading.js +24 -1
- package/dist/cli/v4/onboarding/successScreen.js +17 -8
- package/dist/cli/v4/replyRenderer.js +74 -58
- package/dist/cli/v4/setupWizard.js +19 -2
- package/dist/cli/v4/skinEngine.js +13 -0
- package/dist/cli/v4/table.js +65 -8
- package/dist/core/v4/aidenAgent.js +23 -0
- package/dist/core/v4/auxiliaryClient.js +46 -13
- package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +13 -8
- package/dist/core/v4/promptBuilder.js +45 -0
- package/dist/core/v4/subagent/childBuilder.js +1 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +7 -1
- package/dist/core/v4/ui/banner.js +16 -16
- package/dist/core/version.js +1 -1
- package/dist/moat/approvalEngine.js +14 -0
- package/dist/tools/v4/index.js +54 -0
- package/dist/tools/v4/subagent/spawnSubAgentTool.js +23 -0
- package/package.json +1 -1
package/dist/cli/v4/display.js
CHANGED
|
@@ -41,6 +41,7 @@ const marked_1 = require("marked");
|
|
|
41
41
|
const TerminalRenderer = require('marked-terminal').default ?? require('marked-terminal');
|
|
42
42
|
const skinEngine_1 = require("./skinEngine");
|
|
43
43
|
const box_1 = require("./box");
|
|
44
|
+
const tokens_1 = require("./design/tokens");
|
|
44
45
|
const toolTrail_1 = require("./display/toolTrail");
|
|
45
46
|
// v4.1.3-essentials — capability card renderer (auth/platform failures).
|
|
46
47
|
const capabilityCard_1 = require("./display/capabilityCard");
|
|
@@ -231,6 +232,16 @@ class Display {
|
|
|
231
232
|
// reprints the formatted output.
|
|
232
233
|
this.streamBuffer = '';
|
|
233
234
|
this.streamLineCount = 0;
|
|
235
|
+
// v4.8.0 Phase 2.3 — task_id → label map for in-flight ui_task_update
|
|
236
|
+
// rows. ui_task_done looks up the label so the completion row can
|
|
237
|
+
// echo it even when the model only sends task_id + status. Cleared
|
|
238
|
+
// on done. Map is per-Display-instance; one REPL session.
|
|
239
|
+
this.uiTaskRows = new Map();
|
|
240
|
+
// v4.8.0 Phase 2.3 fix — set true by renderUiEvent; tryRerenderInPlace
|
|
241
|
+
// early-returns when set so the cursor-up + erase-to-end-of-screen
|
|
242
|
+
// sequence can't wipe our ui_* rows. Reset at stream lifecycle
|
|
243
|
+
// boundaries (streamPartial first-delta init + streamComplete).
|
|
244
|
+
this.uiEventsFiredThisTurn = false;
|
|
234
245
|
this.skin = opts.skin ?? (0, skinEngine_1.getSkinEngine)();
|
|
235
246
|
this.out = opts.stdout ?? process.stdout;
|
|
236
247
|
this.err = opts.stderr ?? process.stderr;
|
|
@@ -345,12 +356,16 @@ class Display {
|
|
|
345
356
|
* Returns one indented line with a trailing newline.
|
|
346
357
|
*/
|
|
347
358
|
agentHeader() {
|
|
348
|
-
|
|
359
|
+
// v4.8.0 Slice 7 hotfix — replace v4.7 ┃ heavy-vertical with the
|
|
360
|
+
// Slice 4 framedPanel bar `▎` so reply chrome matches /help,
|
|
361
|
+
// approval prompt, and other Slice 4+ surfaces. Trailing `\n\n`
|
|
362
|
+
// (was `\n`) puts one blank between header and first content row.
|
|
363
|
+
const bar = this.skin.applyColors(tokens_1.glyphs.panel.bar, 'brand');
|
|
349
364
|
const head = this.skin.applyColors('Aiden', 'brand');
|
|
350
365
|
if (process.env.AIDEN_UI_TIMESTAMPS === '1') {
|
|
351
|
-
return `${this.timestampPrefix()} ${bar} ${head}\n`;
|
|
366
|
+
return `${this.timestampPrefix()} ${bar} ${head}\n\n`;
|
|
352
367
|
}
|
|
353
|
-
return ` ${bar} ${head}\n`;
|
|
368
|
+
return ` ${bar} ${head}\n\n`;
|
|
354
369
|
}
|
|
355
370
|
/**
|
|
356
371
|
* Phase 26.2.3 — turn boundary marker. Writes a thin muted rule
|
|
@@ -360,7 +375,12 @@ class Display {
|
|
|
360
375
|
* surface.
|
|
361
376
|
*/
|
|
362
377
|
printTurnSeparator() {
|
|
363
|
-
|
|
378
|
+
// v4.8.0 Slice 7 hotfix — drop the trailing blank line. Inquirer's
|
|
379
|
+
// own prompt leading newline + the Aiden header's leading 2-space
|
|
380
|
+
// indent provide enough breathing room; the extra blank was
|
|
381
|
+
// stacking with other emit points to produce 3+ blank lines
|
|
382
|
+
// between user prompt and reply.
|
|
383
|
+
this.out.write(` ${this.rule()}\n`);
|
|
364
384
|
}
|
|
365
385
|
/**
|
|
366
386
|
* Render the v3-style boot status line:
|
|
@@ -500,55 +520,61 @@ class Display {
|
|
|
500
520
|
* across lines.
|
|
501
521
|
*/
|
|
502
522
|
scrollFooter() {
|
|
523
|
+
// v4.8.0 Slice 10d — rounded heavy frame for identity / credits.
|
|
524
|
+
// The Slice 10b/c orange-bar chrome lacked visual containment for
|
|
525
|
+
// an identity surface (the bar reads as panel-content, not as a
|
|
526
|
+
// credits card). This restores a heavy frame — but with rounded
|
|
527
|
+
// corners (╭╮╰╯) sourced from glyphs.box, and muted chrome so the
|
|
528
|
+
// brand `♥` + brand kv labels carry the visual weight inside.
|
|
503
529
|
const sk = this.skin;
|
|
504
530
|
const m = (s) => sk.applyColors(s, 'muted');
|
|
505
531
|
const lab = (s) => sk.applyColors(s, 'brand');
|
|
506
532
|
const val = (s) => sk.applyColors(s, 'agent');
|
|
507
533
|
const heart = sk.applyColors('♥', 'brand');
|
|
508
534
|
if (this.cols() < 80) {
|
|
509
|
-
//
|
|
510
|
-
// card stays compact. The 4-line plain fallback shipped earlier
|
|
511
|
-
// wastes vertical space when terminals already squeeze content.
|
|
535
|
+
// Narrow fallback unchanged — single-line credits stays compact.
|
|
512
536
|
return ` ${heart} ${m('built solo · github.com/taracodlabs/aiden · aiden.taracod.com')}`;
|
|
513
537
|
}
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return
|
|
538
|
+
const indent = ' ';
|
|
539
|
+
const innerW = Math.min(this.cols() - 4, 70);
|
|
540
|
+
const tL = m(tokens_1.glyphs.box.topLeft);
|
|
541
|
+
const tR = m(tokens_1.glyphs.box.topRight);
|
|
542
|
+
const bL = m(tokens_1.glyphs.box.bottomLeft);
|
|
543
|
+
const bR = m(tokens_1.glyphs.box.bottomRight);
|
|
544
|
+
const side = m(tokens_1.glyphs.chrome.vLine);
|
|
545
|
+
const hRun = m(tokens_1.glyphs.chrome.hLine.repeat(innerW));
|
|
546
|
+
const pad = (visible, width) => {
|
|
547
|
+
const v = (0, box_1.visibleLength)(visible);
|
|
548
|
+
return visible + ' '.repeat(Math.max(0, width - v));
|
|
525
549
|
};
|
|
526
|
-
|
|
527
|
-
// box has visual breathing room from the lines above/below, and
|
|
528
|
-
// a trailing interior blank so contact info doesn't crowd the
|
|
529
|
-
// bottom border.
|
|
550
|
+
const row = (content) => `${indent}${side} ${pad(content, innerW - 2)} ${side}`;
|
|
530
551
|
return [
|
|
531
552
|
'',
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
wallIndent + pipe + lid + pipe,
|
|
553
|
+
`${indent}${tL}${hRun}${tR}`,
|
|
554
|
+
row(`${heart} ${val('Built solo')}`),
|
|
555
|
+
row(''),
|
|
556
|
+
row(`${lab('GitHub:'.padEnd(10))}${val('github.com/taracodlabs/aiden')}`),
|
|
557
|
+
row(`${lab('Web:'.padEnd(10))}${val('aiden.taracod.com')}`),
|
|
558
|
+
row(`${lab('Contact:'.padEnd(10))}${val('contact@taracod.com')}`),
|
|
559
|
+
`${indent}${bL}${hRun}${bR}`,
|
|
540
560
|
'',
|
|
541
561
|
].join('\n');
|
|
542
562
|
}
|
|
543
563
|
/**
|
|
544
564
|
* Bottom prompt hint that replaces the prior `ready ▸ /help` +
|
|
545
|
-
* `✦ Tip:` lines.
|
|
565
|
+
* `✦ Tip:` lines.
|
|
566
|
+
*
|
|
567
|
+
* v4.8.0 Slice 11 — dropped the leading `▲` glyph. The inquirer
|
|
568
|
+
* prompt that paints immediately below this hint already carries
|
|
569
|
+
* the brand triangle as its input prefix (`display.promptPrefix()`),
|
|
570
|
+
* so the hint's own `▲` read as a duplicate orphan sitting one row
|
|
571
|
+
* above the active cursor. Hint is now text-only-muted; `▲` stays
|
|
572
|
+
* exclusively as the user-input identity glyph.
|
|
546
573
|
*/
|
|
547
574
|
bottomPromptHint() {
|
|
548
575
|
const sk = this.skin;
|
|
549
|
-
const tri = sk.applyColors('▲', 'brand');
|
|
550
576
|
const text = sk.applyColors('Type your message · /help for commands · /skills to add more', 'muted');
|
|
551
|
-
return `
|
|
577
|
+
return ` ${text}`;
|
|
552
578
|
}
|
|
553
579
|
/**
|
|
554
580
|
* v3-style "ready" line:
|
|
@@ -564,33 +590,90 @@ class Display {
|
|
|
564
590
|
return ` ${ready} ${arrow} ${sk.applyColors(hint, 'muted')}`;
|
|
565
591
|
}
|
|
566
592
|
/**
|
|
567
|
-
* v3-style post-turn status footer
|
|
593
|
+
* v3-style post-turn status footer, extended in v4.8.0 Slice 7 with
|
|
594
|
+
* packed info density (turn counter, session uptime, per-turn state
|
|
595
|
+
* dot) and progressive disclosure based on terminal width.
|
|
568
596
|
*
|
|
569
|
-
*
|
|
597
|
+
* Layout tiers:
|
|
598
|
+
* ≥120 cols: ▲ provider · model │ N/M <bar> N% │ ⌘ N │ ⏱ Hms │ ● state
|
|
599
|
+
* ≥100 cols: ▲ provider · model │ N/M <bar> N% │ ⌘ N │ Ns
|
|
600
|
+
* < 100: ▲ provider · model │ <bar> N% │ Ns
|
|
570
601
|
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
* elapsed in muted. Returns string sans trailing newline.
|
|
602
|
+
* `turnCount`, `sessionMs`, `state` are optional for backward compat;
|
|
603
|
+
* old call sites continue to work unchanged.
|
|
574
604
|
*/
|
|
575
605
|
statusFooter(args) {
|
|
576
606
|
const sk = this.skin;
|
|
577
607
|
const SEP = sk.applyColors(' │ ', 'muted');
|
|
578
608
|
const tri = this.triangle();
|
|
609
|
+
// v4.8.0 Slice 7 hotfix #2 — per-metric accent palette.
|
|
610
|
+
// Model: cyan (tool kind). Token counts: amber (warn). Bar/pct:
|
|
611
|
+
// semantic tier. Turn: purple (metric_turn). Timer: teal (success).
|
|
579
612
|
const provModel = `${tri} ${sk.applyColors(args.provider, 'muted')}` +
|
|
580
613
|
`${sk.applyColors(' · ', 'muted')}` +
|
|
581
|
-
sk.applyColors(args.model, '
|
|
614
|
+
sk.applyColors(args.model, 'tool');
|
|
582
615
|
const pct = args.ctxMax > 0
|
|
583
616
|
? Math.min(100, Math.round((args.ctxUsed / args.ctxMax) * 100))
|
|
584
617
|
: 0;
|
|
585
|
-
|
|
618
|
+
// 5-cell bar with single-space separators — reads as discrete dots
|
|
619
|
+
// rather than a continuous line. Total visible width ≈ 9 cells,
|
|
620
|
+
// similar to the prior 10-cell solid bar so 80-col tier stays tight.
|
|
621
|
+
const barW = 5;
|
|
586
622
|
const filled = Math.round((pct / 100) * barW);
|
|
587
623
|
const ctxKind = pct < 60 ? 'success' : pct < 85 ? 'warn' : 'error';
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const elapsed = sk.applyColors(formatElapsedShort(args.elapsedMs), '
|
|
593
|
-
|
|
624
|
+
const cells = Array.from({ length: barW }, (_, i) => i < filled ? tokens_1.glyphs.bar.filled : tokens_1.glyphs.bar.empty);
|
|
625
|
+
const bar = sk.applyColors(cells.join(' '), ctxKind);
|
|
626
|
+
const ctxRatio = sk.applyColors(`${formatCompactTokens(args.ctxUsed)}/${formatCompactTokens(args.ctxMax)}`, 'warn');
|
|
627
|
+
const ctxPctText = sk.applyColors(`${pct}%`, ctxKind);
|
|
628
|
+
const elapsed = sk.applyColors(formatElapsedShort(args.elapsedMs), 'success');
|
|
629
|
+
// Progressive disclosure: pick layout based on RAW terminal width.
|
|
630
|
+
// `this.cols()` caps at 100 (frame budget for body content), but
|
|
631
|
+
// the footer wants the full physical width to choose its tier.
|
|
632
|
+
const cols = (typeof this.out.columns === 'number' && this.out.columns >= 1)
|
|
633
|
+
? this.out.columns
|
|
634
|
+
: 100;
|
|
635
|
+
// Tier ≥120: full density (ratio + bar + pct + turn + session + state).
|
|
636
|
+
// Tier ≥100: ratio + bar + pct + turn + elapsed.
|
|
637
|
+
// Tier <100: bar + pct + elapsed.
|
|
638
|
+
const stateDot = args.state
|
|
639
|
+
? sk.applyColors(tokens_1.glyphs.status.dot, this.stateKind(args.state))
|
|
640
|
+
: '';
|
|
641
|
+
// v4.8.0 Slice 9 hotfix — turn glyph dropped; bare colored number
|
|
642
|
+
// matches the timer pattern. Color alone (purple metric_turn)
|
|
643
|
+
// carries the semantic.
|
|
644
|
+
const turnSeg = args.turnCount !== undefined
|
|
645
|
+
? sk.applyColors(String(args.turnCount), 'metric_turn')
|
|
646
|
+
: '';
|
|
647
|
+
// v4.8.0 Slice 9 hotfix — ⌛ restored ahead of the bare elapsed
|
|
648
|
+
// string. Wider font support than the retired ⏱. `sessionMs` arg
|
|
649
|
+
// stays plumbed-but-unused for backward compat with the field name.
|
|
650
|
+
const sessionSeg = args.elapsedMs !== undefined
|
|
651
|
+
? `${sk.applyColors(tokens_1.glyphs.status.timer, 'success')} ${sk.applyColors(formatElapsedShort(args.elapsedMs), 'success')}`
|
|
652
|
+
: '';
|
|
653
|
+
// ctxRatio + ctxPctText are pre-painted (warn + ctxKind respectively).
|
|
654
|
+
const ctxSegFull = `${ctxRatio} ${bar} ${ctxPctText}`;
|
|
655
|
+
const ctxSegCompact = `${bar} ${ctxPctText}`;
|
|
656
|
+
let segments;
|
|
657
|
+
if (cols >= 120 && stateDot && turnSeg && sessionSeg) {
|
|
658
|
+
segments = [provModel, ctxSegFull, turnSeg, sessionSeg, stateDot];
|
|
659
|
+
}
|
|
660
|
+
else if (cols >= 100 && turnSeg) {
|
|
661
|
+
segments = [provModel, ctxSegFull, turnSeg, elapsed];
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
segments = [provModel, ctxSegCompact, elapsed];
|
|
665
|
+
}
|
|
666
|
+
return ` ${segments.join(SEP)}`;
|
|
667
|
+
}
|
|
668
|
+
/** Map a per-turn outcome to the colour kind used by the state dot. */
|
|
669
|
+
stateKind(state) {
|
|
670
|
+
if (state === 'ok')
|
|
671
|
+
return 'success';
|
|
672
|
+
if (state === 'warn')
|
|
673
|
+
return 'warn';
|
|
674
|
+
if (state === 'error')
|
|
675
|
+
return 'error';
|
|
676
|
+
return 'muted';
|
|
594
677
|
}
|
|
595
678
|
/**
|
|
596
679
|
* Tier-3.1 (v4.1-tier3.1): pre-prompt status line.
|
|
@@ -682,11 +765,15 @@ class Display {
|
|
|
682
765
|
* `12:41:02 ▲ <input>`. Default OFF preserves `▲ <input>`.
|
|
683
766
|
*/
|
|
684
767
|
promptPrefix() {
|
|
768
|
+
// v4.8.0 Slice 7 hotfix #2 — 2-space lead matches the rest of the
|
|
769
|
+
// surface family (▎ Aiden header, status footer, bottom hint).
|
|
770
|
+
// Timestamp variant unchanged — the timestamp gutter already
|
|
771
|
+
// provides its own consistent left edge.
|
|
685
772
|
const tri = this.skin.applyColors('▲', 'brand');
|
|
686
773
|
if (process.env.AIDEN_UI_TIMESTAMPS === '1') {
|
|
687
774
|
return `${this.timestampPrefix()} ${tri} `;
|
|
688
775
|
}
|
|
689
|
-
return
|
|
776
|
+
return ` ${tri} `;
|
|
690
777
|
}
|
|
691
778
|
/**
|
|
692
779
|
* Phase 26.2.6 — pick a random phrase from `SPINNER_PHRASES`,
|
|
@@ -812,69 +899,60 @@ class Display {
|
|
|
812
899
|
// (Ns) counter hasn't ticked. Slow enough not to flicker on SSH
|
|
813
900
|
// / slow ConPTY refresh.
|
|
814
901
|
const TICK_MS = 250;
|
|
815
|
-
//
|
|
816
|
-
//
|
|
902
|
+
// v4.8.0 Slice 11 — leading glyph is no longer a static `⌛` (or
|
|
903
|
+
// a separate 2nd-row wave bar). Now it's a single-row sliding
|
|
904
|
+
// shimmer: a 4-cell brand-orange `█` segment that scrolls L→R
|
|
905
|
+
// across a muted `─` track, wrapping at the right edge. The dots
|
|
906
|
+
// pulse + (Ns) timer keep their roles as secondary motion cues;
|
|
907
|
+
// the shimmer is the primary "something is happening" affordance
|
|
908
|
+
// in TTFT space. Token-sourced from `glyphs.shimmer` so the glyph
|
|
909
|
+
// pair lives next to the rest of the v4.8.0 design system.
|
|
817
910
|
//
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
// and
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
// 250ms tick as the verb dot pulse — one timer drives both rows.
|
|
827
|
-
const waveBarEnabled = opts.waveBar !== false; // default true
|
|
828
|
-
const WAVE_CELLS = 10;
|
|
829
|
-
const WAVE_BLOCK = 3;
|
|
830
|
-
let waveFrame = 0;
|
|
831
|
-
const buildLine = () => {
|
|
832
|
-
const dots = '.'.repeat(dotFrame); // 0..3 dots
|
|
833
|
-
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
|
|
834
|
-
const elapsedStr = elapsedSec >= 1
|
|
835
|
-
? ` ${sk.applyColors(`(${elapsedSec}s)`, 'muted')}`
|
|
836
|
-
: '';
|
|
837
|
-
// `▲ {verb}{dots-padded-to-3}{elapsed?}`
|
|
838
|
-
return `${glyph} ${verb}${dots.padEnd(3, ' ')}${elapsedStr}`;
|
|
839
|
-
};
|
|
911
|
+
// `opts.waveBar` is preserved as a back-compat option that maps
|
|
912
|
+
// to "shimmer enabled". Pass `{ waveBar: false }` to drop the
|
|
913
|
+
// shimmer cluster and render the bare verb row (the legacy
|
|
914
|
+
// v4.1.4 single-row indicator). Default ON.
|
|
915
|
+
const shimmerEnabled = opts.waveBar !== false;
|
|
916
|
+
const SHIMMER_CELLS = 10;
|
|
917
|
+
const SHIMMER_BLOCK = 4;
|
|
918
|
+
let shimmerFrame = 0;
|
|
840
919
|
/**
|
|
841
|
-
* v4.
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
920
|
+
* v4.8.0 Slice 11 — render the sliding-block shimmer. A 4-cell
|
|
921
|
+
* `█` (U+2588 FULL BLOCK) segment at positions `[frame,
|
|
922
|
+
* frame+1, frame+2, frame+3]` mod 10, on a muted `─` track.
|
|
923
|
+
* Brand-orange block, muted track. Token-sourced glyphs;
|
|
924
|
+
* cell-by-cell paint keeps glyph order true to position so
|
|
925
|
+
* the wrap visibly slides rather than jumping.
|
|
847
926
|
*
|
|
848
|
-
* Heartbeat semantics: this is NOT progress. The
|
|
849
|
-
* constant 250ms cadence regardless of any backend metric.
|
|
850
|
-
* exists purely so the user sees motion during the
|
|
851
|
-
* TTFT (time-to-first-token) wait. The verb
|
|
852
|
-
*
|
|
927
|
+
* Heartbeat semantics: this is NOT progress. The block moves
|
|
928
|
+
* at a constant 250ms cadence regardless of any backend metric.
|
|
929
|
+
* It exists purely so the user sees motion during the
|
|
930
|
+
* unobservable TTFT (time-to-first-token) wait. The verb +
|
|
931
|
+
* dot pulse + (Ns) timer carry the real lifecycle signal.
|
|
853
932
|
*/
|
|
854
|
-
const
|
|
855
|
-
// v4.1.5 Phase 1d (Q-P1) — glyph palette switch. Was `▰`/`▱`
|
|
856
|
-
// (U+25B0/B1, Geometric Shapes) which legacy Windows console
|
|
857
|
-
// fonts render as tofu. Now `▓`/`░` (U+2593/91, Block Elements
|
|
858
|
-
// — in CP437, universally supported). Matches the existing
|
|
859
|
-
// statusFooter chrome that's shipped since v3 without ever
|
|
860
|
-
// being garbled.
|
|
933
|
+
const buildShimmer = () => {
|
|
861
934
|
const filled = new Set();
|
|
862
|
-
for (let i = 0; i <
|
|
863
|
-
filled.add((
|
|
935
|
+
for (let i = 0; i < SHIMMER_BLOCK; i += 1) {
|
|
936
|
+
filled.add((shimmerFrame + i) % SHIMMER_CELLS);
|
|
864
937
|
}
|
|
865
|
-
// Render cells in order so the snake-scroll visually slides:
|
|
866
|
-
// we paint cell-by-cell with the right color, joined into one
|
|
867
|
-
// string. ANSI runs reset per cell — slight overhead but keeps
|
|
868
|
-
// glyph order true to position. Brand orange filled, warm-muted
|
|
869
|
-
// empty.
|
|
870
938
|
const cells = [];
|
|
871
|
-
for (let c = 0; c <
|
|
939
|
+
for (let c = 0; c < SHIMMER_CELLS; c += 1) {
|
|
872
940
|
cells.push(filled.has(c)
|
|
873
|
-
? sk.applyColors(
|
|
874
|
-
: sk.applyColors(
|
|
941
|
+
? sk.applyColors(tokens_1.glyphs.shimmer.block, 'brand')
|
|
942
|
+
: sk.applyColors(tokens_1.glyphs.shimmer.track, 'muted'));
|
|
875
943
|
}
|
|
876
944
|
return cells.join('');
|
|
877
945
|
};
|
|
946
|
+
const buildLine = () => {
|
|
947
|
+
const dots = '.'.repeat(dotFrame); // 0..3 dots
|
|
948
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
|
|
949
|
+
const elapsedStr = elapsedSec >= 1
|
|
950
|
+
? ` ${sk.applyColors(`(${elapsedSec}s)`, 'muted')}`
|
|
951
|
+
: '';
|
|
952
|
+
// Shimmer prefix (or none, when opts.waveBar === false).
|
|
953
|
+
const prefix = shimmerEnabled ? `${buildShimmer()} ` : '';
|
|
954
|
+
return `${prefix}${verb}${dots.padEnd(3, ' ')}${elapsedStr}`;
|
|
955
|
+
};
|
|
878
956
|
// v4.1.5 Part 1a — Issue M (Windows ConPTY buffering fix).
|
|
879
957
|
//
|
|
880
958
|
// Prior pattern wrote `\r\x1b[K{indicator}` with NO trailing
|
|
@@ -905,24 +983,15 @@ class Display {
|
|
|
905
983
|
if (stopped || paused || !isTty)
|
|
906
984
|
return;
|
|
907
985
|
dotFrame = (dotFrame + 1) % 4;
|
|
908
|
-
// v4.
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
// cursor lands on the row below the wave bar.
|
|
918
|
-
out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}` +
|
|
919
|
-
`${buildLine()}\n` +
|
|
920
|
-
`${buildWave()}\n`);
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
923
|
-
// Single-row layout (back-compat with v4.1.4 tests).
|
|
924
|
-
out.write(`${ANSI_UP_ERASE}${buildLine()}\n`);
|
|
925
|
-
}
|
|
986
|
+
// v4.8.0 Slice 11 — shimmer slides 1 cell per tick. Same 250ms
|
|
987
|
+
// cadence as the dot pulse, so block + dots move in visible
|
|
988
|
+
// lockstep. Modulo SHIMMER_CELLS wraps the leading block back
|
|
989
|
+
// to the left edge.
|
|
990
|
+
shimmerFrame = (shimmerFrame + 1) % SHIMMER_CELLS;
|
|
991
|
+
// Single-row layout: walk up 1, erase, repaint, newline. Cursor
|
|
992
|
+
// lands on the row below the indicator, ready for the next
|
|
993
|
+
// tick to walk back up.
|
|
994
|
+
out.write(`${ANSI_UP_ERASE}${buildLine()}\n`);
|
|
926
995
|
};
|
|
927
996
|
const startTick = () => {
|
|
928
997
|
if (stopped || !isTty || tickTimer !== null)
|
|
@@ -936,41 +1005,23 @@ class Display {
|
|
|
936
1005
|
}
|
|
937
1006
|
};
|
|
938
1007
|
const eraseLine = () => {
|
|
939
|
-
// Walk up
|
|
940
|
-
//
|
|
941
|
-
//
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
// the indicator had been. v4.1.5 visual smoke flagged the
|
|
945
|
-
// wave-bar→`┃ Aiden` proximity as feeling cramped. The
|
|
946
|
-
// trailing `\n` gains one visible blank row of breathing space
|
|
947
|
-
// AND adds another Windows ConPTY flush trigger (Issue M).
|
|
948
|
-
//
|
|
949
|
-
// v4.1.5 Issue K — with wave bar enabled, walk up 2 rows (two
|
|
950
|
-
// up-1+erase sequences). Without the bar, walk up 1 row.
|
|
1008
|
+
// Walk up 1 row + erase + drop a newline so the cursor lands
|
|
1009
|
+
// on a blank line BELOW the indicator's old footprint. The
|
|
1010
|
+
// trailing `\n` provides one visible blank row of breathing
|
|
1011
|
+
// space and acts as a Windows ConPTY flush trigger (v4.1.5
|
|
1012
|
+
// Issue M). Slice 11 collapsed the prior 2-row layout to 1.
|
|
951
1013
|
if (!isTty || !printed)
|
|
952
1014
|
return;
|
|
953
|
-
|
|
954
|
-
out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}\n`);
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
957
|
-
out.write(`${ANSI_UP_ERASE}\n`);
|
|
958
|
-
}
|
|
1015
|
+
out.write(`${ANSI_UP_ERASE}\n`);
|
|
959
1016
|
};
|
|
960
|
-
// Initial paint — only on TTY.
|
|
961
|
-
//
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
//
|
|
965
|
-
//
|
|
966
|
-
// the row below the wave bar. The first tick will walk up 2.
|
|
1017
|
+
// Initial paint — only on TTY. v4.8.0 Slice 11 — prepend a blank
|
|
1018
|
+
// `\n` so the indicator gets one visible row of breathing space
|
|
1019
|
+
// above it. Prior behaviour butted the indicator flush against
|
|
1020
|
+
// the user-prompt row, which read as cramped. The trailing `\n`
|
|
1021
|
+
// on the verb row sits the cursor below the indicator, ready
|
|
1022
|
+
// for the first tick to walk back up.
|
|
967
1023
|
if (isTty) {
|
|
968
|
-
|
|
969
|
-
out.write(`${buildLine()}\n${buildWave()}\n`);
|
|
970
|
-
}
|
|
971
|
-
else {
|
|
972
|
-
out.write(`${buildLine()}\n`);
|
|
973
|
-
}
|
|
1024
|
+
out.write(`\n${buildLine()}\n`);
|
|
974
1025
|
printed = true;
|
|
975
1026
|
startTick();
|
|
976
1027
|
}
|
|
@@ -998,17 +1049,16 @@ class Display {
|
|
|
998
1049
|
// Caller has just finished writing its own content (typically
|
|
999
1050
|
// ending with `\n`), so the cursor is on a fresh line below
|
|
1000
1051
|
// whatever was there. Paint the indicator + `\n` to claim the
|
|
1001
|
-
// current row
|
|
1052
|
+
// current row and leave the cursor on the row below — same
|
|
1002
1053
|
// invariant the initial paint and tick maintain. Trailing `\n`
|
|
1003
1054
|
// also flushes Windows ConPTY buffering (Issue M).
|
|
1004
1055
|
//
|
|
1005
|
-
// v4.
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
}
|
|
1056
|
+
// v4.8.0 Slice 11 — single-row layout: paint one row only.
|
|
1057
|
+
// (Initial paint includes a leading `\n` for breathing space;
|
|
1058
|
+
// resume omits it because the caller has already written its
|
|
1059
|
+
// own content above this point and an extra blank would
|
|
1060
|
+
// double up.)
|
|
1061
|
+
out.write(`${buildLine()}\n`);
|
|
1012
1062
|
printed = true;
|
|
1013
1063
|
startTick();
|
|
1014
1064
|
},
|
|
@@ -1088,18 +1138,25 @@ class Display {
|
|
|
1088
1138
|
// Running row — muted pipe, raw icon, tool-colored verb, muted detail.
|
|
1089
1139
|
// The optional `running Ns…` tail appears once the tool crosses the
|
|
1090
1140
|
// 1-second mark; the tick interval below redraws this row every 1s.
|
|
1141
|
+
//
|
|
1142
|
+
// v4.8.0 Slice 11c — double-space between `${glyph}` and `${padVerb}`.
|
|
1143
|
+
// Emoji-class icons (`👁️`, `✏️`, `📋`, `🌐`, etc.) render 2-cell-wide
|
|
1144
|
+
// visually on Windows ConPTY but the cursor/column tracker treats
|
|
1145
|
+
// them as 1 cell, so a single trailing space is visually swallowed
|
|
1146
|
+
// by the emoji's right cell. Two spaces guarantees one visible gap
|
|
1147
|
+
// regardless of how the terminal measures the glyph.
|
|
1091
1148
|
const runningRow = () => {
|
|
1092
1149
|
const elapsed = Date.now() - startedAt;
|
|
1093
1150
|
const liveSuffix = elapsed >= 1000
|
|
1094
1151
|
? ` ${sk.applyColors(`running ${formatToolDuration(elapsed)}…`, 'muted')}`
|
|
1095
1152
|
: '';
|
|
1096
|
-
return `${sk.applyColors(toolTrail_1.TRAIL_PIPE, 'muted')} ${glyph}
|
|
1153
|
+
return `${sk.applyColors(toolTrail_1.TRAIL_PIPE, 'muted')} ${glyph} ` +
|
|
1097
1154
|
`${sk.applyColors((0, toolTrail_1.padVerb)(verb), 'tool')} ` +
|
|
1098
1155
|
`${sk.applyColors(detail, 'muted')}${liveSuffix}\n`;
|
|
1099
1156
|
};
|
|
1100
1157
|
// Outcome row — entire line colored by outcome kind.
|
|
1101
1158
|
const outcomeRow = (suffix, kind) => {
|
|
1102
|
-
const content = `${toolTrail_1.TRAIL_PIPE} ${glyph}
|
|
1159
|
+
const content = `${toolTrail_1.TRAIL_PIPE} ${glyph} ${(0, toolTrail_1.padVerb)(verb)} ${detail}` +
|
|
1103
1160
|
(suffix ? ` ${suffix}` : '');
|
|
1104
1161
|
return `${sk.applyColors(content, kind)}\n`;
|
|
1105
1162
|
};
|
|
@@ -1211,8 +1268,10 @@ class Display {
|
|
|
1211
1268
|
// back over the retry counter with `running Ns…`.
|
|
1212
1269
|
stopTick();
|
|
1213
1270
|
// Update the running row with retry count.
|
|
1271
|
+
// v4.8.0 Slice 11c — double-space between glyph and verb (see
|
|
1272
|
+
// runningRow comment above for the emoji-width rationale).
|
|
1214
1273
|
eraseLast();
|
|
1215
|
-
const content = `${toolTrail_1.TRAIL_PIPE} ${glyph}
|
|
1274
|
+
const content = `${toolTrail_1.TRAIL_PIPE} ${glyph} ${(0, toolTrail_1.padVerb)(verb)} ${detail} retry ${n}/${m} …`;
|
|
1216
1275
|
out.write(sk.applyColors(content, 'warn') + '\n');
|
|
1217
1276
|
printed = true;
|
|
1218
1277
|
},
|
|
@@ -1326,7 +1385,12 @@ class Display {
|
|
|
1326
1385
|
* (post-stream rerender) so both paths produce identical output.
|
|
1327
1386
|
*/
|
|
1328
1387
|
applyFrameToRendered(rawBody) {
|
|
1329
|
-
|
|
1388
|
+
// v4.8.0 Slice 7 hotfix #2 — override frame.GUTTER (3) to 2 cells
|
|
1389
|
+
// locally so Aiden reply prose aligns with the ▎ bar of agentHeader
|
|
1390
|
+
// (col 2). The GUTTER constant stays at 3 for other consumers
|
|
1391
|
+
// (markdown list/blockquote/code-block renderers in frame.ts) where
|
|
1392
|
+
// a 3-cell gutter is part of their own visual algebra.
|
|
1393
|
+
const indent = ' ';
|
|
1330
1394
|
const bw = (0, frame_1.getBodyWidth)(this.out);
|
|
1331
1395
|
return rawBody
|
|
1332
1396
|
.split('\n')
|
|
@@ -1507,15 +1571,31 @@ class Display {
|
|
|
1507
1571
|
if (!text)
|
|
1508
1572
|
return;
|
|
1509
1573
|
if (!this.streamHeaderShown) {
|
|
1510
|
-
// Phase 26.2.3 — share the
|
|
1511
|
-
//
|
|
1512
|
-
// open identically.
|
|
1574
|
+
// Phase 26.2.3 — share the `▎ Aiden` header with non-streaming
|
|
1575
|
+
// agentTurn so streamed + non-streamed responses open identically.
|
|
1513
1576
|
this.out.write(this.agentHeader());
|
|
1514
1577
|
this.streamHeaderShown = true;
|
|
1515
1578
|
this.streamBuffer = '';
|
|
1516
1579
|
this.streamLineCount = 0;
|
|
1580
|
+
this.uiEventsFiredThisTurn = false;
|
|
1581
|
+
// agentHeader emits trailing `\n\n`; cursor is at col 0 of a fresh
|
|
1582
|
+
// line, so the very next chunk needs the leading indent.
|
|
1583
|
+
this.streamLastEndedNewline = true;
|
|
1517
1584
|
}
|
|
1518
|
-
|
|
1585
|
+
// v4.8.0 Slice 7 hotfix #3 — inject a 2-cell indent at every line
|
|
1586
|
+
// start so streamed content aligns with the ▎ bar in agentHeader.
|
|
1587
|
+
// Pre-this-fix the raw chunk wrote at col 0; the post-stream
|
|
1588
|
+
// rerender in applyFrameToRendered would indent eventually, but
|
|
1589
|
+
// mid-stream the user saw col-0 content. streamBuffer stays raw
|
|
1590
|
+
// (the rerender path applies its own indent).
|
|
1591
|
+
const indent = ' ';
|
|
1592
|
+
let toWrite = text;
|
|
1593
|
+
if (this.streamLastEndedNewline)
|
|
1594
|
+
toWrite = indent + toWrite;
|
|
1595
|
+
const endsNl = toWrite.endsWith('\n');
|
|
1596
|
+
const body = endsNl ? toWrite.slice(0, -1) : toWrite;
|
|
1597
|
+
toWrite = body.replace(/\n/g, '\n' + indent) + (endsNl ? '\n' : '');
|
|
1598
|
+
this.out.write(toWrite);
|
|
1519
1599
|
this.streamLastEndedNewline = text.endsWith('\n');
|
|
1520
1600
|
// Phase v4.1-reply-formatting: track buffer + line count for the
|
|
1521
1601
|
// post-stream re-render.
|
|
@@ -1620,6 +1700,15 @@ class Display {
|
|
|
1620
1700
|
return;
|
|
1621
1701
|
if (lines === 0)
|
|
1622
1702
|
return;
|
|
1703
|
+
// v4.8.0 Phase 2.3 fix — when ui_* events painted this turn, skip the
|
|
1704
|
+
// cursor-up + erase-to-end-of-screen rerender. The eraser wipes
|
|
1705
|
+
// anything below where the stream started, including our event rows.
|
|
1706
|
+
// Tradeoff: assistant text on a ui-event turn stays raw (no in-place
|
|
1707
|
+
// markdown beautification). Acceptable — when the model is using
|
|
1708
|
+
// structured ui events, it's signalling state, not relying on prose
|
|
1709
|
+
// formatting.
|
|
1710
|
+
if (this.uiEventsFiredThisTurn)
|
|
1711
|
+
return;
|
|
1623
1712
|
// Cheap structural heuristic — only re-render when formatting
|
|
1624
1713
|
// actually helps. Plain prose chunks stay raw (no flicker).
|
|
1625
1714
|
//
|
|
@@ -1781,6 +1870,8 @@ class Display {
|
|
|
1781
1870
|
this.streamLineCount = 0;
|
|
1782
1871
|
this.streamHeaderShown = false;
|
|
1783
1872
|
this.streamLastEndedNewline = false;
|
|
1873
|
+
// v4.8.0 Phase 2.3 fix — turn ends; clear the ui-fired flag.
|
|
1874
|
+
this.uiEventsFiredThisTurn = false;
|
|
1784
1875
|
}
|
|
1785
1876
|
/**
|
|
1786
1877
|
* Phase v4.1-reply-formatting: render the optional "Sources"
|
|
@@ -1815,6 +1906,197 @@ class Display {
|
|
|
1815
1906
|
this.out.write(`${sk.applyColors(`${arrow} ${name}…`, 'tool')}\n`);
|
|
1816
1907
|
this.streamLastEndedNewline = true;
|
|
1817
1908
|
}
|
|
1909
|
+
/**
|
|
1910
|
+
* v4.8.0 Phase 2.3 — render a semantic ui_* event signalled by the
|
|
1911
|
+
* model via a uiOnly tool call. Append-only: each event paints one
|
|
1912
|
+
* row; in-place mutation is a v4.8.x upgrade if UX demands it.
|
|
1913
|
+
*
|
|
1914
|
+
* Currently handles `ui_task_update` and `ui_task_done`; other 5
|
|
1915
|
+
* names land in Phase 2.4 (silent ignore until then). Non-TTY out
|
|
1916
|
+
* surfaces silent — matches the activityIndicator precedent.
|
|
1917
|
+
*/
|
|
1918
|
+
/**
|
|
1919
|
+
* v4.8.0 Phase 2.3 fix-2 — reset the per-turn ui-event flag. Called
|
|
1920
|
+
* by chatSession at the top of each turn. The existing reset sites
|
|
1921
|
+
* (streamPartial first-delta + streamComplete) only fire when the
|
|
1922
|
+
* turn actually streamed text deltas. Tool-only turns never reset,
|
|
1923
|
+
* leaving the flag sticky into subsequent turns. This is the
|
|
1924
|
+
* authoritative reset for turn-start.
|
|
1925
|
+
*/
|
|
1926
|
+
resetUiTurnState() {
|
|
1927
|
+
this.uiEventsFiredThisTurn = false;
|
|
1928
|
+
}
|
|
1929
|
+
renderUiEvent(name, args) {
|
|
1930
|
+
if (!this.out.isTTY)
|
|
1931
|
+
return;
|
|
1932
|
+
// v4.8.0 Phase 2.3 fix — Option C. The post-stream markdown rerender
|
|
1933
|
+
// (`tryRerenderInPlace`) does `cursor-up-N + erase-to-end-of-screen`,
|
|
1934
|
+
// which wipes anything painted between stream start and stream end —
|
|
1935
|
+
// including our ui_* rows. Mark the turn so the rerender skips this
|
|
1936
|
+
// turn entirely. Resets when the next streaming turn begins (see
|
|
1937
|
+
// streamPartial header init) and on streamComplete cleanup.
|
|
1938
|
+
this.uiEventsFiredThisTurn = true;
|
|
1939
|
+
if (name === 'ui_task_update') {
|
|
1940
|
+
this.renderUiTaskUpdate(args);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
if (name === 'ui_task_done') {
|
|
1944
|
+
this.renderUiTaskDone(args);
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
if (name === 'ui_command_result') {
|
|
1948
|
+
this.renderUiCommandResult(args);
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
if (name === 'ui_test_result') {
|
|
1952
|
+
this.renderUiTestResult(args);
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
if (name === 'ui_approval_request') {
|
|
1956
|
+
this.renderUiApprovalRequest(args);
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
if (name === 'ui_toast') {
|
|
1960
|
+
this.renderUiToast(args);
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
if (name === 'ui_artifact_created') {
|
|
1964
|
+
this.renderUiArtifactCreated(args);
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
// Unknown event names silent-ignore (defensive — future registrations).
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* v4.8.0 Phase 2.4 polish — build one trail-gutter row matching the
|
|
1971
|
+
* `toolRow` chrome (muted `┊` + space + colored content + `\n`).
|
|
1972
|
+
* Splits on embedded newlines so multi-line surfaces (capped stdout,
|
|
1973
|
+
* preview tails, optional reasons) carry the gutter on every line.
|
|
1974
|
+
*/
|
|
1975
|
+
uiTrailRow(content, kind) {
|
|
1976
|
+
const pipe = this.skin.applyColors(toolTrail_1.TRAIL_PIPE, 'muted');
|
|
1977
|
+
return content.split('\n').map(l => `${pipe} ${this.skin.applyColors(l, kind)}\n`).join('');
|
|
1978
|
+
}
|
|
1979
|
+
renderUiTaskUpdate(args) {
|
|
1980
|
+
const taskId = typeof args.task_id === 'string' ? args.task_id : '';
|
|
1981
|
+
const label = typeof args.label === 'string' ? args.label : '';
|
|
1982
|
+
const status = typeof args.status === 'string' ? args.status : '';
|
|
1983
|
+
const kindArg = typeof args.kind === 'string' ? args.kind : 'task';
|
|
1984
|
+
const depth = typeof args.depth === 'number' && args.depth > 0 ? args.depth : 0;
|
|
1985
|
+
if (!taskId || !label)
|
|
1986
|
+
return;
|
|
1987
|
+
this.commitStreamChunk();
|
|
1988
|
+
const glyph = status === 'paused' ? '⏸' : status === 'blocked' ? '⛔' : '⟳';
|
|
1989
|
+
const colorKind = status === 'running' ? 'tool' : 'warn';
|
|
1990
|
+
this.uiTaskRows.set(taskId, { label });
|
|
1991
|
+
const short = label.length > 80 ? label.slice(0, 79) + '…' : label;
|
|
1992
|
+
// v4.8.0 Phase 2.4 — subagent kind: indent by depth inside the
|
|
1993
|
+
// gutter so nested rows tier below their parent.
|
|
1994
|
+
const indent = kindArg === 'subagent' ? ' '.repeat(depth) : '';
|
|
1995
|
+
this.out.write(this.uiTrailRow(`${indent}${glyph} ${short}`, colorKind));
|
|
1996
|
+
this.streamLastEndedNewline = true;
|
|
1997
|
+
}
|
|
1998
|
+
renderUiTaskDone(args) {
|
|
1999
|
+
const taskId = typeof args.task_id === 'string' ? args.task_id : '';
|
|
2000
|
+
const status = typeof args.status === 'string' ? args.status : '';
|
|
2001
|
+
const summary = typeof args.summary === 'string' ? args.summary : '';
|
|
2002
|
+
if (!taskId)
|
|
2003
|
+
return;
|
|
2004
|
+
this.commitStreamChunk();
|
|
2005
|
+
const tracked = this.uiTaskRows.get(taskId);
|
|
2006
|
+
const label = tracked?.label ?? taskId;
|
|
2007
|
+
this.uiTaskRows.delete(taskId);
|
|
2008
|
+
const glyph = status === 'success' ? '✓' : status === 'failure' ? '✗' : '⊘';
|
|
2009
|
+
const kind = status === 'success' ? 'success' :
|
|
2010
|
+
status === 'failure' ? 'error' : 'warn';
|
|
2011
|
+
const shortLabel = label.length > 80 ? label.slice(0, 79) + '…' : label;
|
|
2012
|
+
const shortSum = summary.length > 120 ? summary.slice(0, 119) + '…' : summary;
|
|
2013
|
+
const tail = shortSum ? ` — ${shortSum}` : '';
|
|
2014
|
+
this.out.write(this.uiTrailRow(`${glyph} ${shortLabel}${tail}`, kind));
|
|
2015
|
+
this.streamLastEndedNewline = true;
|
|
2016
|
+
}
|
|
2017
|
+
renderUiCommandResult(args) {
|
|
2018
|
+
const command = typeof args.command === 'string' ? args.command : '';
|
|
2019
|
+
if (!command)
|
|
2020
|
+
return;
|
|
2021
|
+
const stdout = typeof args.stdout === 'string' ? args.stdout : '';
|
|
2022
|
+
const stderr = typeof args.stderr === 'string' ? args.stderr : '';
|
|
2023
|
+
const exitCode = typeof args.exit_code === 'number' ? args.exit_code : 0;
|
|
2024
|
+
this.commitStreamChunk();
|
|
2025
|
+
const ok = exitCode === 0;
|
|
2026
|
+
const cap = (t) => t.split('\n').slice(0, 5).join('\n');
|
|
2027
|
+
let out = this.uiTrailRow(`▸ ${command}`, ok ? 'success' : 'error');
|
|
2028
|
+
if (stdout)
|
|
2029
|
+
out += this.uiTrailRow(cap(stdout), 'muted');
|
|
2030
|
+
if (stderr)
|
|
2031
|
+
out += this.uiTrailRow(cap(stderr), 'error');
|
|
2032
|
+
if (!ok)
|
|
2033
|
+
out += this.uiTrailRow(`(exit ${exitCode})`, 'error');
|
|
2034
|
+
this.out.write(out);
|
|
2035
|
+
this.streamLastEndedNewline = true;
|
|
2036
|
+
}
|
|
2037
|
+
renderUiTestResult(args) {
|
|
2038
|
+
const framework = typeof args.framework === 'string' ? args.framework : '';
|
|
2039
|
+
if (!framework)
|
|
2040
|
+
return;
|
|
2041
|
+
const passed = typeof args.passed === 'number' ? args.passed : 0;
|
|
2042
|
+
const failed = typeof args.failed === 'number' ? args.failed : 0;
|
|
2043
|
+
const skipped = typeof args.skipped === 'number' ? args.skipped : 0;
|
|
2044
|
+
const durationMs = typeof args.duration_ms === 'number' ? args.duration_ms : 0;
|
|
2045
|
+
this.commitStreamChunk();
|
|
2046
|
+
const ok = failed === 0;
|
|
2047
|
+
const parts = [`${passed} passed`, `${failed} failed`];
|
|
2048
|
+
if (skipped > 0)
|
|
2049
|
+
parts.push(`${skipped} skipped`);
|
|
2050
|
+
const dur = durationMs > 0 ? ` in ${durationMs}ms` : '';
|
|
2051
|
+
this.out.write(this.uiTrailRow(`${ok ? '✓' : '✗'} ${framework}: ${parts.join(', ')}${dur}`, ok ? 'success' : 'error'));
|
|
2052
|
+
this.streamLastEndedNewline = true;
|
|
2053
|
+
}
|
|
2054
|
+
renderUiApprovalRequest(args) {
|
|
2055
|
+
const prompt = typeof args.prompt === 'string' ? args.prompt : '';
|
|
2056
|
+
if (!prompt)
|
|
2057
|
+
return;
|
|
2058
|
+
const riskTier = typeof args.risk_tier === 'string' ? args.risk_tier : 'medium';
|
|
2059
|
+
const reason = typeof args.reason === 'string' ? args.reason : '';
|
|
2060
|
+
this.commitStreamChunk();
|
|
2061
|
+
const kind = riskTier === 'low' ? 'success'
|
|
2062
|
+
: (riskTier === 'high' || riskTier === 'critical') ? 'error' : 'warn';
|
|
2063
|
+
const shortP = prompt.length > 160 ? prompt.slice(0, 159) + '…' : prompt;
|
|
2064
|
+
let out = this.uiTrailRow(`⚠ Approval needed: ${shortP}`, kind);
|
|
2065
|
+
if (reason) {
|
|
2066
|
+
const shortR = reason.length > 200 ? reason.slice(0, 199) + '…' : reason;
|
|
2067
|
+
out += this.uiTrailRow(` ${shortR}`, 'muted');
|
|
2068
|
+
}
|
|
2069
|
+
this.out.write(out);
|
|
2070
|
+
this.streamLastEndedNewline = true;
|
|
2071
|
+
}
|
|
2072
|
+
renderUiToast(args) {
|
|
2073
|
+
const message = typeof args.message === 'string' ? args.message : '';
|
|
2074
|
+
if (!message)
|
|
2075
|
+
return;
|
|
2076
|
+
const kindArg = typeof args.kind === 'string' ? args.kind : 'info';
|
|
2077
|
+
this.commitStreamChunk();
|
|
2078
|
+
const glyph = kindArg === 'success' ? '✓' : kindArg === 'warning' ? '⚠' : kindArg === 'error' ? '✗' : 'ℹ';
|
|
2079
|
+
const kind = kindArg === 'success' ? 'success' : kindArg === 'warning' ? 'warn' : kindArg === 'error' ? 'error' : 'tool';
|
|
2080
|
+
const short = message.length > 120 ? message.slice(0, 119) + '…' : message;
|
|
2081
|
+
this.out.write(this.uiTrailRow(`${glyph} ${short}`, kind));
|
|
2082
|
+
this.streamLastEndedNewline = true;
|
|
2083
|
+
}
|
|
2084
|
+
renderUiArtifactCreated(args) {
|
|
2085
|
+
const path = typeof args.path === 'string' ? args.path : '';
|
|
2086
|
+
if (!path)
|
|
2087
|
+
return;
|
|
2088
|
+
const kindArg = typeof args.kind === 'string' ? args.kind : 'file';
|
|
2089
|
+
const preview = typeof args.preview === 'string' ? args.preview : '';
|
|
2090
|
+
this.commitStreamChunk();
|
|
2091
|
+
const glyph = kindArg === 'skill' ? '🛠' : kindArg === 'directory' ? '📁' : '📄';
|
|
2092
|
+
let out = this.uiTrailRow(`${glyph} Created: ${path}`, 'accent');
|
|
2093
|
+
if (preview) {
|
|
2094
|
+
const shortP = preview.length > 200 ? preview.slice(0, 199) + '…' : preview;
|
|
2095
|
+
out += this.uiTrailRow(` ${shortP}`, 'muted');
|
|
2096
|
+
}
|
|
2097
|
+
this.out.write(out);
|
|
2098
|
+
this.streamLastEndedNewline = true;
|
|
2099
|
+
}
|
|
1818
2100
|
}
|
|
1819
2101
|
exports.Display = Display;
|
|
1820
2102
|
/**
|