aiden-runtime 4.7.0 → 4.8.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/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 +55 -8
- 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/update.js +14 -2
- 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 +489 -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/pasteIntercept.js +214 -70
- package/dist/cli/v4/replyRenderer.js +213 -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 +51 -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/v4/update/executeInstall.js +10 -6
- package/dist/core/v4/update/installMethodDetect.js +7 -0
- package/dist/core/version.js +67 -2
- 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 -3
|
@@ -32,11 +32,14 @@ exports._resetForTests = _resetForTests;
|
|
|
32
32
|
const marked_1 = require("marked");
|
|
33
33
|
const skinEngine_1 = require("./skinEngine");
|
|
34
34
|
const syntaxHighlight_1 = require("./syntaxHighlight");
|
|
35
|
+
// v4.8.0 Slice 8 — token-sourced bullet glyphs + task-list markers.
|
|
36
|
+
const tokens_1 = require("./design/tokens");
|
|
35
37
|
// v4.1.4 reply-quality polish: single source of truth for frame math.
|
|
36
38
|
// Replaces 3 inline `Math.min(process.stdout.columns ?? 80, 100) - 4`
|
|
37
39
|
// callsites in this file with `getBodyWidth()` and adds soft-wrap for
|
|
38
40
|
// code-block lines that previously overflowed the viewport.
|
|
39
41
|
const frame_1 = require("./display/frame");
|
|
42
|
+
const box_1 = require("./box");
|
|
40
43
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
41
44
|
const TerminalRenderer = require('marked-terminal').default ?? require('marked-terminal');
|
|
42
45
|
function paint(kind) {
|
|
@@ -131,61 +134,45 @@ function paintBold(kind) {
|
|
|
131
134
|
const CODE_BG_ON = '\x1b[48;2;50;50;60m';
|
|
132
135
|
const CODE_BG_OFF = '\x1b[49m';
|
|
133
136
|
function renderCodeBlock(code, lang) {
|
|
137
|
+
// v4.8.0 Slice 9 hotfix — top-divider asymmetric chrome.
|
|
138
|
+
//
|
|
139
|
+
// ── python ─────────────────────────────────────────
|
|
140
|
+
// print("Hello, world!")
|
|
141
|
+
// greet("Aiden")
|
|
142
|
+
//
|
|
143
|
+
// The earlier Slice 9 version used `▎` left-rail on every line and
|
|
144
|
+
// visually competed with the dark-bg syntax highlighting. This
|
|
145
|
+
// revision drops the rail, keeps a single muted `──` top divider
|
|
146
|
+
// with the language label in brand orange, indents body content,
|
|
147
|
+
// and omits the bottom border (Slice 4 asymmetric signature).
|
|
148
|
+
// CODE_BG_ON/OFF envelope preserved.
|
|
134
149
|
const sk = (0, skinEngine_1.getSkinEngine)();
|
|
135
|
-
// v4.1.4 reply-quality polish: width sourced from frame.ts. Same
|
|
136
|
-
// visual budget as the v4.1.3 formula (cols capped at 100, minus
|
|
137
|
-
// gutter+2) — but expressed via the shared helper so it tracks any
|
|
138
|
-
// future width-policy change in one place.
|
|
139
150
|
const width = (0, frame_1.getBodyWidth)();
|
|
140
151
|
const langLabel = (lang ?? '').trim();
|
|
141
|
-
// v4.1.3-essentials reply-polish: language tag on the top rule
|
|
142
|
-
// already shipped; keep it. Bottom rule unlabeled (closing fence).
|
|
143
|
-
const top = langLabel
|
|
144
|
-
? `── ${langLabel} ${'─'.repeat(Math.max(0, width - langLabel.length - 4))}`
|
|
145
|
-
: '─'.repeat(width);
|
|
146
|
-
const bot = '─'.repeat(width);
|
|
147
152
|
const body = (0, syntaxHighlight_1.isSupportedLang)(langLabel)
|
|
148
153
|
? (0, syntaxHighlight_1.highlightCode)(code, langLabel)
|
|
149
154
|
: code;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// - left rail `│ ` painted muted (mirrors blockquote's `┃ ` rail
|
|
164
|
-
// with a different glyph so they're visually distinct)
|
|
165
|
-
// - 24-bit dark background wrapping the rail + content (subtle
|
|
166
|
-
// "this is code" affordance without going full TUI box-frame)
|
|
167
|
-
const rail = sk.applyColors('│', 'muted');
|
|
168
|
-
const gutter = (0, frame_1.getIndent)(0);
|
|
169
|
-
// Wrap each source line independently — code-block semantics demand
|
|
170
|
-
// that a "logical line" remains visible as one continued unit even
|
|
171
|
-
// when soft-wrapped. The CODE_BG painting closes per VISUAL line so
|
|
172
|
-
// a wrap break doesn't bleed bg across the rail of the next row.
|
|
155
|
+
const indent = ' ';
|
|
156
|
+
const hLine = tokens_1.glyphs.chrome.hLine;
|
|
157
|
+
// Top divider: `── <lang> ─────` (lang in brand) OR full-width
|
|
158
|
+
// `────────────` when no language declared.
|
|
159
|
+
const top = langLabel
|
|
160
|
+
? `${indent}${sk.applyColors(`${hLine.repeat(2)} `, 'muted')}` +
|
|
161
|
+
`${sk.applyColors(langLabel, 'brand')}` +
|
|
162
|
+
` ${sk.applyColors(hLine.repeat(Math.max(1, width - langLabel.length - 4)), 'muted')}`
|
|
163
|
+
: `${indent}${sk.applyColors(hLine.repeat(width), 'muted')}`;
|
|
164
|
+
// Body content lands at col 4 (4-space indent inside the divider).
|
|
165
|
+
// Width budget: leave room for body indent + CODE_BG envelope spaces.
|
|
166
|
+
const bodyIndent = ' ';
|
|
167
|
+
const contentBudget = Math.max(8, width - 6);
|
|
173
168
|
const wrappedLines = [];
|
|
174
169
|
for (const srcLine of body.split('\n')) {
|
|
175
170
|
const wrapped = (0, frame_1.wrap)(srcLine, contentBudget, { trim: false, hard: true });
|
|
176
171
|
for (const visualLine of wrapped.split('\n')) {
|
|
177
|
-
wrappedLines.push(`${
|
|
172
|
+
wrappedLines.push(`${bodyIndent}${CODE_BG_ON} ${visualLine} ${CODE_BG_OFF}`);
|
|
178
173
|
}
|
|
179
174
|
}
|
|
180
|
-
|
|
181
|
-
// Top + bottom fence rules sit at the gutter too — visually anchors
|
|
182
|
-
// the block as a unit inside the assistant frame.
|
|
183
|
-
return [
|
|
184
|
-
`${gutter}${sk.applyColors(top, 'muted')}`,
|
|
185
|
-
indented,
|
|
186
|
-
`${gutter}${sk.applyColors(bot, 'muted')}`,
|
|
187
|
-
'',
|
|
188
|
-
].join('\n') + '\n';
|
|
175
|
+
return [top, ...wrappedLines].join('\n') + '\n';
|
|
189
176
|
}
|
|
190
177
|
/**
|
|
191
178
|
* Render a block quote with a `┃` left rail in muted colour.
|
|
@@ -337,7 +324,16 @@ function renderListItemTokens(it, parser) {
|
|
|
337
324
|
// block parser handles these via the normal dispatch (which calls
|
|
338
325
|
// back into our own `renderer.list` override for nested lists —
|
|
339
326
|
// depth counter is already incremented before we got here).
|
|
327
|
+
//
|
|
328
|
+
// v4.8.0 Slice 8 hotfix — ensure inline text and following block
|
|
329
|
+
// tokens are separated by a newline. Without this, a tight-list
|
|
330
|
+
// item like `- Python` followed by a nested `- Django` collapses
|
|
331
|
+
// to `● Python ○ Django` on a single line because head/tail
|
|
332
|
+
// split in renderer.list takes only the first line as `head`.
|
|
340
333
|
if (parser.parse) {
|
|
334
|
+
if (out.length > 0 && !out[out.length - 1].endsWith('\n')) {
|
|
335
|
+
out.push('\n');
|
|
336
|
+
}
|
|
341
337
|
out.push(parser.parse([tk]));
|
|
342
338
|
continue;
|
|
343
339
|
}
|
|
@@ -535,6 +531,10 @@ function getReplyRenderer() {
|
|
|
535
531
|
let isOrdered = false;
|
|
536
532
|
let startNum = 1;
|
|
537
533
|
let items;
|
|
534
|
+
// v4.8.0 Slice 8 — task/checked flags collected alongside items so
|
|
535
|
+
// the marker dispatch below can pick ✔ (checked) or ○ (unchecked).
|
|
536
|
+
// Default false (not a task) so the bullet path stays unchanged.
|
|
537
|
+
let itemTasks = [];
|
|
538
538
|
// CRITICAL: increment depth BEFORE walking items. Item walking via
|
|
539
539
|
// `parser.parse(it.tokens)` recurses into our own override for any
|
|
540
540
|
// nested list tokens — those nested calls need to see the parent's
|
|
@@ -570,10 +570,14 @@ function getReplyRenderer() {
|
|
|
570
570
|
// Confirmed against marked v15 token shapes from `marked.lexer`
|
|
571
571
|
// (see scripts/smoke-issue-c-tokens.ts).
|
|
572
572
|
const parser = this.parser;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
573
|
+
// v4.8.0 Slice 8 — capture GFM task/checked flags alongside the
|
|
574
|
+
// rendered text so the marker dispatch below can pick the right
|
|
575
|
+
// glyph (✔ checked / ○ unchecked) for task-list items.
|
|
576
|
+
const rawItems = tok.items ?? [];
|
|
577
|
+
items = rawItems.map((it) => parser ? renderListItemTokens(it, parser) : (it.text ?? ''));
|
|
578
|
+
itemTasks = rawItems.map((it) => {
|
|
579
|
+
const itx = it;
|
|
580
|
+
return { task: itx.task === true, checked: itx.checked === true };
|
|
577
581
|
});
|
|
578
582
|
}
|
|
579
583
|
else {
|
|
@@ -587,20 +591,33 @@ function getReplyRenderer() {
|
|
|
587
591
|
items = raw.split('\n').filter((ln) => ln.trim().length > 0);
|
|
588
592
|
}
|
|
589
593
|
const indent = ' '.repeat(depth);
|
|
590
|
-
//
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
const bulletGlyph = depth === 1 ?
|
|
594
|
+
// v4.8.0 Slice 8 — token-sourced bullet glyphs. Top-level (depth 1)
|
|
595
|
+
// uses filled `●`, nested (depth ≥ 2) uses hollow `○`. Both painted
|
|
596
|
+
// brand orange to give lists visual identity (was `muted` grey).
|
|
597
|
+
const bulletGlyph = depth === 1 ? tokens_1.glyphs.util.bullet : tokens_1.glyphs.util.bulletOpen;
|
|
594
598
|
const lines = [];
|
|
595
599
|
for (let i = 0; i < items.length; i += 1) {
|
|
596
600
|
const item = items[i];
|
|
597
|
-
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
601
|
+
const task = itemTasks[i] ?? { task: false, checked: false };
|
|
602
|
+
// v4.8.0 Slice 8 — marker dispatch:
|
|
603
|
+
// • GFM checked task → ✔ in semantic success (green)
|
|
604
|
+
// • GFM unchecked task → ○ in tertiary dim (looks incomplete)
|
|
605
|
+
// • Ordered list → `N.` right-padded to 3 cols, brand orange
|
|
606
|
+
// • Bullet → ●/○ by depth, brand orange
|
|
607
|
+
let marker;
|
|
608
|
+
if (task.task && task.checked) {
|
|
609
|
+
marker = paint('success')(tokens_1.glyphs.util.check);
|
|
610
|
+
}
|
|
611
|
+
else if (task.task) {
|
|
612
|
+
marker = paint('tertiary')(tokens_1.glyphs.util.bulletOpen);
|
|
613
|
+
}
|
|
614
|
+
else if (isOrdered) {
|
|
615
|
+
const numStr = `${startNum + i}.`.padStart(3);
|
|
616
|
+
marker = paint('brand')(numStr);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
marker = paint('brand')(bulletGlyph);
|
|
620
|
+
}
|
|
604
621
|
const itemLines = item.split('\n');
|
|
605
622
|
const head = itemLines[0] ?? '';
|
|
606
623
|
const tail = itemLines.slice(1);
|
|
@@ -622,6 +639,144 @@ function getReplyRenderer() {
|
|
|
622
639
|
const out = lines.join('\n');
|
|
623
640
|
return proto._listDepth === 0 ? out + '\n' : out + '\n';
|
|
624
641
|
};
|
|
642
|
+
// ── v4.8.1 Slice 2 — markdown table override ──────────────────────────
|
|
643
|
+
//
|
|
644
|
+
// Why: marked-terminal's default table renderer (cli-table3) auto-
|
|
645
|
+
// wraps cells but doesn't keep wrap-continuation lines aligned to
|
|
646
|
+
// the original row — wide tables with 5+ columns fragment into
|
|
647
|
+
// vertical pipe rails that don't read as rows. The narrow 2-col
|
|
648
|
+
// tables that smoke-tested fine were within the no-wrap budget.
|
|
649
|
+
//
|
|
650
|
+
// Strategy: own the entire render from the marked v15 token object.
|
|
651
|
+
// Use `parser.parseInline(cell.tokens)` to get ANSI-painted cell
|
|
652
|
+
// text, then proportionally distribute the terminal-width budget
|
|
653
|
+
// across columns (clamping to natural max width), wrap each cell
|
|
654
|
+
// to its column width, and render the box with the same row
|
|
655
|
+
// height for every cell in the row so visual rows stay tight.
|
|
656
|
+
//
|
|
657
|
+
// Token-source the box chars from `glyphs.chrome.*` so a single
|
|
658
|
+
// glyph swap propagates here automatically (consistent with the
|
|
659
|
+
// rest of v4.8.x chrome).
|
|
660
|
+
renderer.table = function (header, body) {
|
|
661
|
+
// marked v15 token: { header: [cellTok], rows: [[cellTok]] }.
|
|
662
|
+
// Older string-based API: (headerHtml, bodyHtml) — we fall back
|
|
663
|
+
// to a naive concatenation so the reply isn't lost entirely.
|
|
664
|
+
if (typeof header !== 'object' || header === null) {
|
|
665
|
+
return String(header ?? '') + (body !== undefined ? String(body) : '') + '\n';
|
|
666
|
+
}
|
|
667
|
+
const tok = header;
|
|
668
|
+
const parser = this.parser;
|
|
669
|
+
const renderCell = (c) => {
|
|
670
|
+
if (c.tokens && parser?.parseInline) {
|
|
671
|
+
try {
|
|
672
|
+
return parser.parseInline(c.tokens).trim();
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return (c.text ?? '').trim();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return (c.text ?? '').trim();
|
|
679
|
+
};
|
|
680
|
+
const headers = (tok.header ?? []).map(renderCell);
|
|
681
|
+
const rows = (tok.rows ?? []).map((r) => r.map(renderCell));
|
|
682
|
+
const cols = headers.length;
|
|
683
|
+
if (cols === 0)
|
|
684
|
+
return '';
|
|
685
|
+
// Layout budget. Reply chrome family lives at col 2.
|
|
686
|
+
const indent = ' ';
|
|
687
|
+
const termCols = process.stdout.columns ?? 100;
|
|
688
|
+
const innerBudget = Math.max(40, Math.min(termCols, 110) - indent.length);
|
|
689
|
+
// Chrome per row = `│ ` (2) per col + trailing `│` (1) + 1 trailing
|
|
690
|
+
// space per cell already absorbed in the budget below.
|
|
691
|
+
const chromeCost = 3 * cols + 1;
|
|
692
|
+
const contentBudget = Math.max(cols * 4, innerBudget - chromeCost);
|
|
693
|
+
// Natural width = max(header, body) visible width per column.
|
|
694
|
+
const naturalW = headers.map((h, i) => {
|
|
695
|
+
const hw = (0, box_1.visibleLength)(h);
|
|
696
|
+
const cw = rows.reduce((m, r) => Math.max(m, (0, box_1.visibleLength)(r[i] ?? '')), 0);
|
|
697
|
+
return Math.max(hw, cw, 1);
|
|
698
|
+
});
|
|
699
|
+
// v4.8.1 Slice 2 hotfix #2 — header-floor + proportional allocation.
|
|
700
|
+
//
|
|
701
|
+
// Each column's minimum is `max(headerWidth, MIN_COL_W)` so column
|
|
702
|
+
// headers NEVER wrap — they are the column identifier; wrapping
|
|
703
|
+
// them ("Framework" → "Framew/ork") fragments scanability worse
|
|
704
|
+
// than wrapping body cells. Body content above the header width
|
|
705
|
+
// is what gets compressed under width pressure.
|
|
706
|
+
//
|
|
707
|
+
// Algorithm:
|
|
708
|
+
// 1. Compute `minPerCol = max(headerW[i], MIN_COL_W)` per column.
|
|
709
|
+
// 2. If sum(minPerCol) >= contentBudget (very narrow terminal),
|
|
710
|
+
// use minPerCol as-is — body cells will wrap to fit, headers
|
|
711
|
+
// stay intact.
|
|
712
|
+
// 3. Else if sum(naturalW) <= contentBudget, use natural widths
|
|
713
|
+
// (no wrap needed anywhere).
|
|
714
|
+
// 4. Else: floor at minPerCol, distribute remaining budget
|
|
715
|
+
// proportionally to each column's "extra need above min",
|
|
716
|
+
// then hand rounding leftover to widest-natural cols first.
|
|
717
|
+
const MIN_COL_W = 4;
|
|
718
|
+
const headerW = headers.map((h) => (0, box_1.visibleLength)(h));
|
|
719
|
+
const minPerCol = naturalW.map((_, i) => Math.max(headerW[i], MIN_COL_W));
|
|
720
|
+
const totalMin = minPerCol.reduce((a, b) => a + b, 0);
|
|
721
|
+
const totalNatW = naturalW.reduce((a, b) => a + b, 0);
|
|
722
|
+
let colWidths;
|
|
723
|
+
if (totalMin >= contentBudget) {
|
|
724
|
+
colWidths = minPerCol.slice();
|
|
725
|
+
}
|
|
726
|
+
else if (totalNatW <= contentBudget) {
|
|
727
|
+
colWidths = naturalW.slice();
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
colWidths = minPerCol.slice();
|
|
731
|
+
const extraNeed = naturalW.map((w, i) => Math.max(0, w - minPerCol[i]));
|
|
732
|
+
const totalNeed = extraNeed.reduce((a, b) => a + b, 0);
|
|
733
|
+
const pool = contentBudget - totalMin;
|
|
734
|
+
if (totalNeed > 0) {
|
|
735
|
+
for (let i = 0; i < cols; i += 1) {
|
|
736
|
+
colWidths[i] += Math.floor((extraNeed[i] * pool) / totalNeed);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
let leftover = contentBudget - colWidths.reduce((a, b) => a + b, 0);
|
|
740
|
+
const order = naturalW.map((_, i) => i).sort((a, b) => naturalW[b] - naturalW[a]);
|
|
741
|
+
for (let k = 0; leftover > 0 && k < cols * 2; k += 1) {
|
|
742
|
+
const idx = order[k % cols];
|
|
743
|
+
if (colWidths[idx] < naturalW[idx]) {
|
|
744
|
+
colWidths[idx] += 1;
|
|
745
|
+
leftover -= 1;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// ANSI-aware cell wrap. frameWrap handles colour-code-aware width.
|
|
750
|
+
const wrapCell = (text, w) => w <= 0 ? [''] : (0, frame_1.wrap)(text, w, { trim: false, hard: true }).split('\n');
|
|
751
|
+
const sk = (0, skinEngine_1.getSkinEngine)();
|
|
752
|
+
const ch = tokens_1.glyphs.chrome;
|
|
753
|
+
const rule = (l, m, r) => indent + sk.applyColors(l + colWidths.map((w) => ch.hLine.repeat(w + 2)).join(m) + r, 'muted');
|
|
754
|
+
const vBar = sk.applyColors(ch.vLine, 'muted');
|
|
755
|
+
const renderRow = (cells) => {
|
|
756
|
+
const height = Math.max(...cells.map((c) => c.length), 1);
|
|
757
|
+
const out = [];
|
|
758
|
+
for (let line = 0; line < height; line += 1) {
|
|
759
|
+
const cellLines = cells.map((cellLines2, ci) => {
|
|
760
|
+
const cellLine = cellLines2[line] ?? '';
|
|
761
|
+
const pad = Math.max(0, colWidths[ci] - (0, box_1.visibleLength)(cellLine));
|
|
762
|
+
return ' ' + cellLine + ' '.repeat(pad) + ' ';
|
|
763
|
+
});
|
|
764
|
+
out.push(indent + vBar + cellLines.join(vBar) + vBar);
|
|
765
|
+
}
|
|
766
|
+
return out.join('\n');
|
|
767
|
+
};
|
|
768
|
+
const wrappedHeader = headers.map((h, i) => wrapCell(h, colWidths[i]));
|
|
769
|
+
const wrappedRows = rows.map((r) => r.map((c, i) => wrapCell(c, colWidths[i])));
|
|
770
|
+
const lines = [rule(ch.topLeft, ch.teeDown, ch.topRight)];
|
|
771
|
+
if (headers.length > 0) {
|
|
772
|
+
lines.push(renderRow(wrappedHeader));
|
|
773
|
+
lines.push(rule(ch.teeRight, ch.cross, ch.teeLeft));
|
|
774
|
+
}
|
|
775
|
+
for (const row of wrappedRows)
|
|
776
|
+
lines.push(renderRow(row));
|
|
777
|
+
lines.push(rule(ch.botLeft, ch.teeUp, ch.botRight));
|
|
778
|
+
return lines.join('\n') + '\n';
|
|
779
|
+
};
|
|
625
780
|
cachedRenderer = {
|
|
626
781
|
render(text) {
|
|
627
782
|
try {
|
|
@@ -47,6 +47,8 @@ const box_1 = require("./box");
|
|
|
47
47
|
// the cost of broken unit tests under the test runtime.
|
|
48
48
|
const successScreen_1 = require("./onboarding/successScreen");
|
|
49
49
|
const providerPicker_1 = require("./onboarding/providerPicker");
|
|
50
|
+
// v4.8.0 Slice 10b — bar + chrome tokens for step headers.
|
|
51
|
+
const tokens_1 = require("./design/tokens");
|
|
50
52
|
const modelFetch_1 = require("../../core/v4/providers/modelFetch");
|
|
51
53
|
const probe_1 = require("../../core/v4/providers/probe");
|
|
52
54
|
// Phase 30.2.1 — provider order optimised for new-user time-to-first-chat.
|
|
@@ -537,8 +539,19 @@ async function runSetupWizard(opts = {}) {
|
|
|
537
539
|
if (opts.prompts) {
|
|
538
540
|
display.printBanner();
|
|
539
541
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
+
// v4.8.0 Slice 10b — step-header helper. Each major wizard step
|
|
543
|
+
// starts with ` ▎ Set up Aiden step N` painted with the orange
|
|
544
|
+
// panel bar so the flow visually consistent with /help and the
|
|
545
|
+
// approval panel. Inquirer widgets render below unchanged.
|
|
546
|
+
const stepHeader = (n) => {
|
|
547
|
+
const bar = display.applyColors(tokens_1.glyphs.panel.bar, 'brand');
|
|
548
|
+
const title = display.applyColors('Set up Aiden', 'heading');
|
|
549
|
+
const sub = display.applyColors(`step ${n}`, 'muted');
|
|
550
|
+
return `\n ${bar} ${title} ${sub}\n`;
|
|
551
|
+
};
|
|
552
|
+
display.write(stepHeader(1));
|
|
553
|
+
display.write(' Welcome — let\'s pick a provider.\n');
|
|
554
|
+
display.write(` ${kleur_1.default.dim('(Press Enter to accept Groq — free + fastest setup.)')}\n\n`);
|
|
542
555
|
// Phase 30.2.1 — Groq is the new recommended default for first-time
|
|
543
556
|
// users: free tier, fastest signup, and avoids the surprise charge
|
|
544
557
|
// path of paid providers. Together AI moved to position [8] paid.
|
|
@@ -695,6 +708,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
695
708
|
// anyway, so the reorder bought nothing there. Existing test
|
|
696
709
|
// fixtures provide inputs in legacy order; preserving custom's
|
|
697
710
|
// order keeps them green.
|
|
711
|
+
if (provider.kind === 'key' || provider.kind === 'subscription' || provider.kind === 'local') {
|
|
712
|
+
display.write(stepHeader(2));
|
|
713
|
+
}
|
|
698
714
|
let apiKey;
|
|
699
715
|
let baseUrl;
|
|
700
716
|
if (provider.kind === 'local') {
|
|
@@ -713,6 +729,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
713
729
|
}
|
|
714
730
|
// provider.kind === 'custom' — defer credential prompts until AFTER
|
|
715
731
|
// the model picker below.
|
|
732
|
+
display.write(stepHeader(3));
|
|
716
733
|
// Step 3: live model fetch + pick.
|
|
717
734
|
//
|
|
718
735
|
// Test-harness gate: when the caller injected `opts.prompts` (only
|
|
@@ -66,6 +66,13 @@ const DEFAULT_SKIN = {
|
|
|
66
66
|
// (which shares the colour) so callers can differentiate in code
|
|
67
67
|
// even though they render identically.
|
|
68
68
|
degraded: [0xff, 0xc1, 0x07],
|
|
69
|
+
// v4.8.0 Slice 7 hotfix #2 — purple accent for the turn-counter
|
|
70
|
+
// segment (⌘) in the status footer. #a48be0 reads as a soft
|
|
71
|
+
// lavender that doesn't compete with brand orange.
|
|
72
|
+
metric_turn: [0xa4, 0x8b, 0xe0],
|
|
73
|
+
// v4.8.0 Slice 8 — tertiary dim grey, dimmer than `muted` (warm
|
|
74
|
+
// tint) for lowest-priority text like unchecked task markers.
|
|
75
|
+
tertiary: [0x6a, 0x6a, 0x6a],
|
|
69
76
|
},
|
|
70
77
|
glyphs: {
|
|
71
78
|
bullet: '•',
|
|
@@ -99,6 +106,10 @@ const LIGHT_SKIN = {
|
|
|
99
106
|
heading: [0xc4, 0x42, 0x10],
|
|
100
107
|
session: [0x00, 0x55, 0x88],
|
|
101
108
|
degraded: [0x80, 0x60, 0x00],
|
|
109
|
+
// Slice 7 hotfix #2 — deeper purple on light bg keeps contrast budget.
|
|
110
|
+
metric_turn: [0x6e, 0x50, 0xaa],
|
|
111
|
+
// Slice 8 — lighter grey on light bg keeps the dim-but-readable feel.
|
|
112
|
+
tertiary: [0x9a, 0x9a, 0x9a],
|
|
102
113
|
},
|
|
103
114
|
glyphs: { ...DEFAULT_SKIN.glyphs },
|
|
104
115
|
};
|
|
@@ -118,6 +129,8 @@ const MONOCHROME_SKIN = {
|
|
|
118
129
|
heading: null,
|
|
119
130
|
session: null,
|
|
120
131
|
degraded: null,
|
|
132
|
+
metric_turn: null,
|
|
133
|
+
tertiary: null,
|
|
121
134
|
},
|
|
122
135
|
glyphs: {
|
|
123
136
|
bullet: '*',
|
package/dist/cli/v4/table.js
CHANGED
|
@@ -23,6 +23,7 @@ exports.renderTable = renderTable;
|
|
|
23
23
|
const stringWidth = require('string-width');
|
|
24
24
|
const skinEngine_1 = require("./skinEngine");
|
|
25
25
|
const box_1 = require("./box");
|
|
26
|
+
const tokens_1 = require("./design/tokens");
|
|
26
27
|
/**
|
|
27
28
|
* Visible (post-ANSI-strip) column width. Falls back to
|
|
28
29
|
* `visibleLength` from box.ts when string-width is unavailable
|
|
@@ -167,13 +168,32 @@ function renderTable(rows, cols, opts = {}) {
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
|
-
// Border characters (
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const
|
|
171
|
+
// Border characters — token-sourced from design/tokens.ts (v4.8.0 Slice 3).
|
|
172
|
+
const { topLeft: TL, topRight: TR, botLeft: BL, botRight: BR } = tokens_1.glyphs.chrome;
|
|
173
|
+
const { teeDown: T, teeUp: B, teeRight: L, teeLeft: R } = tokens_1.glyphs.chrome;
|
|
174
|
+
const { cross: X, hLine: H, vLine: V } = tokens_1.glyphs.chrome;
|
|
174
175
|
const ind = ' '.repeat(indent);
|
|
175
|
-
//
|
|
176
|
-
|
|
176
|
+
// Total inner content width across all cells + inner separators.
|
|
177
|
+
// Used by title-embedded top border + page footer.
|
|
178
|
+
const innerWidth = widths.reduce((s, w) => s + w + 2, 0) + (numCols - 1);
|
|
179
|
+
// v4.8.0 Slice 3 — top border with optional embedded title + count.
|
|
180
|
+
// Format: `┌─ title ──────────── totalCount ──┐`
|
|
181
|
+
// Pads the centre with `─` so the right edge stays aligned regardless
|
|
182
|
+
// of title / count length. Falls back to the legacy plain top border
|
|
183
|
+
// when neither field is supplied.
|
|
184
|
+
let top;
|
|
185
|
+
if (opts.title || opts.totalCount) {
|
|
186
|
+
const titleText = opts.title ? ` ${opts.title} ` : '';
|
|
187
|
+
const countText = opts.totalCount ? ` ${opts.totalCount} ` : '';
|
|
188
|
+
const fixed = vWidth(titleText) + vWidth(countText);
|
|
189
|
+
const filler = Math.max(0, innerWidth - fixed);
|
|
190
|
+
const titlePainted = opts.title ? skin.applyColors(titleText, 'heading') : '';
|
|
191
|
+
const countPainted = opts.totalCount ? skin.applyColors(countText, 'muted') : '';
|
|
192
|
+
top = TL + H + titlePainted + H.repeat(filler) + countPainted + H + TR;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
top = TL + widths.map((w) => H.repeat(w + 2)).join(T) + TR;
|
|
196
|
+
}
|
|
177
197
|
// Header row — heading colour, padded. Truncate first if the
|
|
178
198
|
// header itself is wider than the allocated width (rare, but
|
|
179
199
|
// keeps borders aligned under aggressive narrow-width pressure).
|
|
@@ -203,13 +223,50 @@ function renderTable(rows, cols, opts = {}) {
|
|
|
203
223
|
});
|
|
204
224
|
bodyLines.push(V + cells.join(V) + V);
|
|
205
225
|
});
|
|
206
|
-
//
|
|
207
|
-
|
|
226
|
+
// v4.8.0 Slice 3 — pagination footer above the bottom border. Renders
|
|
227
|
+
// `← prev · page X/Y · next →` centred inside the inner width. Side
|
|
228
|
+
// arrows are dim when the page is at the edge so users can read
|
|
229
|
+
// "at-end" cleanly. Caller wires hotkeys; we just paint the chrome.
|
|
230
|
+
let pageFooter = null;
|
|
231
|
+
if (opts.page) {
|
|
232
|
+
const { current, total } = opts.page;
|
|
233
|
+
const atStart = current <= 1;
|
|
234
|
+
const atEnd = current >= total;
|
|
235
|
+
const leftKind = atStart ? 'muted' : 'session';
|
|
236
|
+
const rightKind = atEnd ? 'muted' : 'session';
|
|
237
|
+
const left = skin.applyColors('← prev', leftKind);
|
|
238
|
+
const mid = skin.applyColors(`page ${current}/${total}`, 'muted');
|
|
239
|
+
const right = skin.applyColors('next →', rightKind);
|
|
240
|
+
const sep = skin.applyColors(' · ', 'muted');
|
|
241
|
+
const body = `${left}${sep}${mid}${sep}${right}`;
|
|
242
|
+
const bodyW = vWidth('← prev') + vWidth(` · page ${current}/${total} · `) + vWidth('next →');
|
|
243
|
+
const padW = Math.max(0, innerWidth - bodyW);
|
|
244
|
+
const lpad = Math.floor(padW / 2);
|
|
245
|
+
const rpad = padW - lpad;
|
|
246
|
+
pageFooter = V + ' '.repeat(lpad) + body + ' '.repeat(rpad) + V;
|
|
247
|
+
}
|
|
248
|
+
// Bottom border. Skip the inner tees when the title-style top was
|
|
249
|
+
// used (legacy plain bottom keeps column alignment for un-titled
|
|
250
|
+
// tables; a title-only border on top reads cleanest with a plain
|
|
251
|
+
// bottom mirror).
|
|
252
|
+
const bot = (opts.title || opts.totalCount)
|
|
253
|
+
? BL + H.repeat(innerWidth) + BR
|
|
254
|
+
: BL + widths.map((w) => H.repeat(w + 2)).join(B) + BR;
|
|
255
|
+
// v4.8.0 Slice 3 — empty-state path. Borders stay so the layout
|
|
256
|
+
// weight matches a populated table; the body is one centered line.
|
|
257
|
+
if (rows.length === 0 && opts.emptyMessage) {
|
|
258
|
+
const msg = skin.applyColors(opts.emptyMessage, 'muted');
|
|
259
|
+
const pad = Math.max(0, innerWidth - vWidth(opts.emptyMessage));
|
|
260
|
+
const lpad = Math.floor(pad / 2);
|
|
261
|
+
const emptyRow = V + ' '.repeat(lpad) + msg + ' '.repeat(pad - lpad) + V;
|
|
262
|
+
return [top, emptyRow, bot].map((l) => ind + l).join('\n') + '\n';
|
|
263
|
+
}
|
|
208
264
|
const allLines = [
|
|
209
265
|
top,
|
|
210
266
|
headerRow,
|
|
211
267
|
...(showRule ? [rule] : []),
|
|
212
268
|
...bodyLines,
|
|
269
|
+
...(pageFooter ? [pageFooter] : []),
|
|
213
270
|
bot,
|
|
214
271
|
].map((l) => ind + l);
|
|
215
272
|
return allLines.join('\n') + '\n';
|
|
@@ -166,6 +166,7 @@ class AidenAgent {
|
|
|
166
166
|
this.resolveVerifiedFlag = opts.resolveVerifiedFlag;
|
|
167
167
|
this.resolveToolset = opts.resolveToolset;
|
|
168
168
|
this.resolveMutates = opts.resolveMutates;
|
|
169
|
+
this.resolveUiOnly = opts.resolveUiOnly;
|
|
169
170
|
this.promptBuilder = opts.promptBuilder;
|
|
170
171
|
this.promptBuilderOptions = opts.promptBuilderOptions;
|
|
171
172
|
this.contextCompressor = opts.contextCompressor;
|
|
@@ -839,6 +840,28 @@ class AidenAgent {
|
|
|
839
840
|
finalContent = '';
|
|
840
841
|
break;
|
|
841
842
|
}
|
|
843
|
+
// v4.8.0 — uiOnly tools are signal channels, not executable
|
|
844
|
+
// tools. The model calls them to communicate render-time
|
|
845
|
+
// state. Dispatch loop skips execute / iteration / mutation
|
|
846
|
+
// marking / verifier / trace / observability hooks and fires
|
|
847
|
+
// onUiEvent on the caller. A '(no output)' tool_result is
|
|
848
|
+
// pushed to satisfy the provider protocol (every tool_call_id
|
|
849
|
+
// needs a matching tool_result). Listener exceptions are
|
|
850
|
+
// swallowed so a bad UI handler cannot break the turn.
|
|
851
|
+
if (this.resolveUiOnly?.(call.name) === true) {
|
|
852
|
+
turnToolMessages.push({
|
|
853
|
+
role: 'tool',
|
|
854
|
+
toolCallId: call.id,
|
|
855
|
+
content: '(no output)',
|
|
856
|
+
});
|
|
857
|
+
try {
|
|
858
|
+
runOptions.onUiEvent?.(call.name, call.arguments);
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
// defensive — UI listener faults must never break dispatch
|
|
862
|
+
}
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
842
865
|
this.onToolCall?.(call, 'before');
|
|
843
866
|
// v4.2 Phase 4 — mark any active checkpoints as containing a
|
|
844
867
|
// mutating call BEFORE dispatch. Done pre-dispatch (not post)
|
|
@@ -45,19 +45,35 @@ class AuxiliaryClient {
|
|
|
45
45
|
return this.opts.adapter;
|
|
46
46
|
if (!this.opts.resolver)
|
|
47
47
|
return null;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
// v4.8.0 Slice 11 — resolution chain: default first, then each
|
|
49
|
+
// fallback in order. The first attempt that resolves wins. This
|
|
50
|
+
// is the routing-fix entry point for the chatgpt-plus + gpt-5
|
|
51
|
+
// bug: aidenCLI hands us Groq as the default and the parent
|
|
52
|
+
// provider/model as the fallback, so auxiliary calls land on
|
|
53
|
+
// Groq when configured and the parent only sees traffic when
|
|
54
|
+
// Groq is absent.
|
|
55
|
+
const attempts = [
|
|
56
|
+
{ providerId: this.opts.defaultProvider, modelId: this.opts.defaultModel },
|
|
57
|
+
...(this.opts.fallbacks ?? []),
|
|
58
|
+
];
|
|
59
|
+
const failures = [];
|
|
60
|
+
for (const att of attempts) {
|
|
61
|
+
this.resolveCallCount += 1;
|
|
62
|
+
try {
|
|
63
|
+
const adapter = await this.opts.resolver.resolve({
|
|
64
|
+
providerId: att.providerId,
|
|
65
|
+
modelId: att.modelId,
|
|
66
|
+
});
|
|
67
|
+
this.warn(`auxiliary resolved via ${att.providerId}/${att.modelId}`);
|
|
68
|
+
return adapter;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
failures.push(`${att.providerId}/${att.modelId}: ${err.message}`);
|
|
72
|
+
}
|
|
60
73
|
}
|
|
74
|
+
this.warn(`auxiliary client unavailable (tried ${attempts.length}): ${failures.join('; ')}`);
|
|
75
|
+
this.adapterUnavailable = true;
|
|
76
|
+
return null;
|
|
61
77
|
}
|
|
62
78
|
/** Resolve count for tests (verifies single-resolution behaviour). */
|
|
63
79
|
_resolveCallCount() {
|
|
@@ -122,7 +138,24 @@ class AuxiliaryClient {
|
|
|
122
138
|
this.usage.set(purpose, cur);
|
|
123
139
|
}
|
|
124
140
|
warn(msg) {
|
|
125
|
-
|
|
141
|
+
// v4.8.0 Slice 5 — gate console output behind AIDEN_VERBOSE.
|
|
142
|
+
// Auxiliary failures are recoverable (the main loop continues;
|
|
143
|
+
// result content is just empty), so the warning is pure noise
|
|
144
|
+
// for end users. Power users set AIDEN_VERBOSE=1 to surface them.
|
|
145
|
+
// Inline env-read preserves the core → cli no-import invariant;
|
|
146
|
+
// canonical isVerbose() lives at cli/v4/design/tokens.ts.
|
|
147
|
+
//
|
|
148
|
+
// v4.8.0 Slice 11 — if opts.warn is explicitly injected, always
|
|
149
|
+
// forward (tests + advanced callers register their own sink and
|
|
150
|
+
// expect every message). The AIDEN_VERBOSE gate now applies only
|
|
151
|
+
// to the default console.warn fallback that end-users see.
|
|
152
|
+
if (this.opts.warn) {
|
|
153
|
+
this.opts.warn(msg);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (process.env.AIDEN_VERBOSE !== '1')
|
|
157
|
+
return;
|
|
158
|
+
console.warn(`[auxiliary] ${msg}`);
|
|
126
159
|
}
|
|
127
160
|
async withTimeout(p, ms) {
|
|
128
161
|
return new Promise((resolve, reject) => {
|