aiden-runtime 4.1.2 → 4.1.4
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 +10 -0
- package/dist/cli/v4/callbacks.js +85 -13
- package/dist/cli/v4/chatSession.js +250 -24
- package/dist/cli/v4/commands/doctor.js +23 -27
- package/dist/cli/v4/commands/model.js +30 -1
- package/dist/cli/v4/defaultSoul.js +69 -2
- package/dist/cli/v4/display/capabilityCard.js +135 -0
- package/dist/cli/v4/display/frame.js +234 -0
- package/dist/cli/v4/display/progressBar.js +137 -0
- package/dist/cli/v4/display/sessionEndCard.js +127 -0
- package/dist/cli/v4/display/toolTrail.js +172 -0
- package/dist/cli/v4/display.js +891 -153
- package/dist/cli/v4/doctor.js +377 -75
- package/dist/cli/v4/promotionPrompt.js +135 -5
- package/dist/cli/v4/replyRenderer.js +487 -26
- package/dist/cli/v4/skinEngine.js +26 -4
- package/dist/cli/v4/toolPreview.js +82 -19
- package/dist/core/tools/nowPlaying.js +7 -15
- package/dist/core/v4/aidenAgent.js +9 -0
- package/dist/core/v4/promptBuilder.js +2 -1
- package/dist/core/v4/sessionDistiller.js +48 -1
- package/dist/core/v4/toolRegistry.js +16 -1
- package/dist/core/version.js +1 -1
- package/dist/moat/plannerGuard.js +19 -0
- package/dist/providers/v4/anthropicAdapter.js +25 -2
- package/dist/providers/v4/errors.js +92 -0
- package/dist/tools/v4/index.js +24 -1
- package/dist/tools/v4/sessions/recallSession.js +14 -0
- package/dist/tools/v4/system/_psHelpers.js +70 -2
- package/dist/tools/v4/system/appInput.js +154 -0
- package/dist/tools/v4/system/appLaunch.js +136 -10
- package/dist/tools/v4/system/mediaKey.js +35 -4
- package/dist/tools/v4/system/mediaSessions.js +163 -0
- package/dist/tools/v4/system/mediaTransport.js +211 -0
- package/package.json +2 -1
- package/skills/system_control.md +56 -6
|
@@ -106,7 +106,36 @@ exports.model = {
|
|
|
106
106
|
return {};
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
// v4.1.3-prebump: persist the selection to config.yaml so the NEXT
|
|
110
|
+
// boot honours the user's choice. Without this, `/model` only
|
|
111
|
+
// updated the live session — and the persisted `model.provider /
|
|
112
|
+
// model.modelId` keys (which Case 3 in providerBootSelector
|
|
113
|
+
// consults first) silently kept their stale values from the
|
|
114
|
+
// previous wizard run. Result: every reboot snapped the user back
|
|
115
|
+
// to the wizard's original pick (typically groq + llama-3.3-70b),
|
|
116
|
+
// confusing /model into looking like a "session-only" switch.
|
|
117
|
+
//
|
|
118
|
+
// The `aiden model` CLI subcommand (aidenCLI.ts:1773-1777) has
|
|
119
|
+
// always persisted; this brings the REPL `/model` path in line.
|
|
120
|
+
// Best-effort: if `ctx.config` isn't plumbed (test harness, etc.)
|
|
121
|
+
// we still succeeded for the live session — emit a subtle warning
|
|
122
|
+
// instead of failing the whole switch.
|
|
123
|
+
let persisted = false;
|
|
124
|
+
if (ctx.config) {
|
|
125
|
+
try {
|
|
126
|
+
ctx.config.set('model.provider', providerId);
|
|
127
|
+
ctx.config.set('model.modelId', modelId);
|
|
128
|
+
await ctx.config.save();
|
|
129
|
+
persisted = true;
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
ctx.display.warn(`Switched the live session but could not persist to config.yaml: ` +
|
|
133
|
+
`${err.message}. Next boot may revert.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
ctx.display.success(persisted
|
|
137
|
+
? `Now using ${providerId}:${modelId} (saved to config.yaml)`
|
|
138
|
+
: `Now using ${providerId}:${modelId} (session only — not persisted)`);
|
|
110
139
|
return {};
|
|
111
140
|
},
|
|
112
141
|
};
|
|
@@ -30,7 +30,7 @@ exports.PREVIOUS_BUNDLED_SOULS = exports.DEFAULT_SOUL_MD = exports.BUNDLED_SOUL_
|
|
|
30
30
|
// <act_dont_ask>. ensureSoulMdSeeded compares this against the user's
|
|
31
31
|
// on-disk SOUL.md to decide whether to silent-replace (matches a prior
|
|
32
32
|
// bundled default) or preserve+notify (user-edited).
|
|
33
|
-
exports.BUNDLED_SOUL_VERSION = 'v4.1.
|
|
33
|
+
exports.BUNDLED_SOUL_VERSION = 'v4.1.4';
|
|
34
34
|
exports.DEFAULT_SOUL_MD = `You are Aiden — a local-first AI agent built by Taracod.
|
|
35
35
|
|
|
36
36
|
Identity:
|
|
@@ -40,7 +40,8 @@ Identity:
|
|
|
40
40
|
- You have 40 tools spanning files, browser, terminal, web, memory.
|
|
41
41
|
|
|
42
42
|
Voice:
|
|
43
|
-
-
|
|
43
|
+
- Match the user's energy. When the user asks a thoughtful question (opinion, exploration, comparison), engage thoughtfully. When the user asks transactionally, stay tight.
|
|
44
|
+
- On thoughtful questions, share the reasoning before the answer — what you considered, what you discarded, why.
|
|
44
45
|
- Honest above all — if you didn't do something, say so. If you're not sure, say so.
|
|
45
46
|
- You never claim to "have run" a tool unless the trace shows it.
|
|
46
47
|
|
|
@@ -254,5 +255,71 @@ Limits:
|
|
|
254
255
|
- You're a CLI agent in v4.0.0. No voice, no scheduled jobs, no messaging gateway yet — those are v4.1.
|
|
255
256
|
- You can't bypass approval prompts for dangerous commands.
|
|
256
257
|
- You don't lie to look smart. If you don't know, you say so.
|
|
258
|
+
`,
|
|
259
|
+
// v4.1.2 default — shipped through v4.1.3 (no SOUL change in v4.1.3).
|
|
260
|
+
// v4.1.4 rewrites the Voice block to make conciseness conditional:
|
|
261
|
+
// thoughtful questions get thoughtful engagement, transactional
|
|
262
|
+
// questions stay tight. Adds an explicit reasoning-visibility line.
|
|
263
|
+
// Users on the v4.1.2 / v4.1.3 install have this verbatim text on
|
|
264
|
+
// disk; silent-upgrade picks them up here.
|
|
265
|
+
`You are Aiden — a local-first AI agent built by Taracod.
|
|
266
|
+
|
|
267
|
+
Identity:
|
|
268
|
+
- You run on the user's machine, native Windows/Linux/macOS (not WSL2).
|
|
269
|
+
- You have 72 bundled skills + access to install more via skills.sh.
|
|
270
|
+
- You remember past sessions via persistent storage.
|
|
271
|
+
- You have 40 tools spanning files, browser, terminal, web, memory.
|
|
272
|
+
|
|
273
|
+
Voice:
|
|
274
|
+
- Direct. No fluff. Match the user's energy.
|
|
275
|
+
- Honest above all — if you didn't do something, say so. If you're not sure, say so.
|
|
276
|
+
- You never claim to "have run" a tool unless the trace shows it.
|
|
277
|
+
|
|
278
|
+
Behavior:
|
|
279
|
+
- Default to action over discussion. The user wants results.
|
|
280
|
+
- When asked who you are, identify as Aiden. Not "a large language model."
|
|
281
|
+
- When asked what you can do, mention specific skills/tools, not generic capabilities.
|
|
282
|
+
- If user mentions trading/NSE/markets, you have specialized skills for that.
|
|
283
|
+
|
|
284
|
+
<act_dont_ask>
|
|
285
|
+
When a request has an obvious default interpretation, act on it
|
|
286
|
+
immediately instead of asking for clarification. Examples:
|
|
287
|
+
- "play me a popular song" / "play X on youtube" → load skill_view(media-search)
|
|
288
|
+
and follow it. Substitute fuzzy phrases ("popular song") with a specific
|
|
289
|
+
chart-topper BEFORE searching, then open_url a /watch?v= URL once.
|
|
290
|
+
NEVER search verbatim "popular song" — that returns articles, not music.
|
|
291
|
+
- "what files are in my Downloads?" → file_list on Downloads. Don't ask
|
|
292
|
+
"which user?" — it's the current user.
|
|
293
|
+
- "is port 443 open?" → check this machine. Don't ask "open where?"
|
|
294
|
+
Only ask for clarification when the ambiguity genuinely changes which
|
|
295
|
+
tool you would call.
|
|
296
|
+
</act_dont_ask>
|
|
297
|
+
|
|
298
|
+
<prerequisite_checks>
|
|
299
|
+
Before acting, check whether prerequisite discovery, lookup, or
|
|
300
|
+
context-gathering steps are needed. If a step depends on output from a
|
|
301
|
+
prior step, resolve that dependency first. Don't skip prerequisite
|
|
302
|
+
steps just because the final action seems obvious.
|
|
303
|
+
</prerequisite_checks>
|
|
304
|
+
|
|
305
|
+
<missing_context>
|
|
306
|
+
If required context is missing, do NOT guess or hallucinate. Use the
|
|
307
|
+
appropriate lookup tool when missing information is retrievable
|
|
308
|
+
(file_read, file_list, web_search, fetch_url, session_search,
|
|
309
|
+
system_info). Ask a clarifying question ONLY when no tool can resolve
|
|
310
|
+
the ambiguity.
|
|
311
|
+
</missing_context>
|
|
312
|
+
|
|
313
|
+
<keep_going>
|
|
314
|
+
Work autonomously until the task is fully resolved. Don't stop with a
|
|
315
|
+
plan — execute it. Multi-step tasks (open browser → search → click
|
|
316
|
+
result; or list files → read each → summarise) are expected; chain
|
|
317
|
+
the tool calls within a single turn instead of returning halfway and
|
|
318
|
+
asking the user what to do next.
|
|
319
|
+
</keep_going>
|
|
320
|
+
|
|
321
|
+
Limits:
|
|
322
|
+
- You can't bypass approval prompts for dangerous commands.
|
|
323
|
+
- You don't lie to look smart. If you don't know, you say so.
|
|
257
324
|
`,
|
|
258
325
|
];
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/display/capabilityCard.ts — Aiden v4.1.3-essentials.
|
|
10
|
+
*
|
|
11
|
+
* Renders the structured "capability card" tools return on certain
|
|
12
|
+
* failure classes:
|
|
13
|
+
* 1. Platform unsupported (e.g. media_transport called on Linux)
|
|
14
|
+
* 2. Auth missing (provider 401/403, or a tool that needs a specific
|
|
15
|
+
* OAuth/API key)
|
|
16
|
+
*
|
|
17
|
+
* Distinct from the one-line tool-trail row (`display.toolRow`) because
|
|
18
|
+
* it's a different category of information — a state assessment of
|
|
19
|
+
* what the user CAN still do versus what they CANNOT, with a one-line
|
|
20
|
+
* fix hint. Rendered as a box-bordered multi-line block.
|
|
21
|
+
*
|
|
22
|
+
* Pure module — takes the data + a colorize callback, returns lines.
|
|
23
|
+
* No I/O, no SkinEngine reach-through. Caller writes the result.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.renderCapabilityCard = renderCapabilityCard;
|
|
27
|
+
exports.truncToContent = truncToContent;
|
|
28
|
+
const box_1 = require("../box");
|
|
29
|
+
/** Total card width (chars). Wide enough for typical action labels;
|
|
30
|
+
* short enough to stay readable on narrow terminals. */
|
|
31
|
+
const CARD_WIDTH = 64;
|
|
32
|
+
/** Box-content width = CARD_WIDTH minus 2 border chars and 2 padding. */
|
|
33
|
+
const CONTENT_WIDTH = CARD_WIDTH - 4;
|
|
34
|
+
/**
|
|
35
|
+
* Render a capability card from `data`. Returns an array of lines
|
|
36
|
+
* (no trailing newlines). Caller writes them with appended `\n`:
|
|
37
|
+
*
|
|
38
|
+
* for (const line of renderCapabilityCard(data, colorize)) {
|
|
39
|
+
* display.write(line + '\n');
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* Layout:
|
|
43
|
+
*
|
|
44
|
+
* ┌── ⚠ <title> ──────────────────────────────┐
|
|
45
|
+
* │ │
|
|
46
|
+
* │ Can still: │
|
|
47
|
+
* │ ✓ <action 1> │
|
|
48
|
+
* │ ✓ <action 2> │
|
|
49
|
+
* │ │
|
|
50
|
+
* │ Cannot reliably: │
|
|
51
|
+
* │ ✗ <action 1> │
|
|
52
|
+
* │ ✗ <action 2> │
|
|
53
|
+
* │ │
|
|
54
|
+
* │ Fix: <one-line guidance> │
|
|
55
|
+
* └───────────────────────────────────────────┘
|
|
56
|
+
*
|
|
57
|
+
* Empty `canStill` or `cannotReliably` arrays cause the corresponding
|
|
58
|
+
* section to be omitted (no empty heading). Always renders at least
|
|
59
|
+
* the title + fix line so the user has actionable signal.
|
|
60
|
+
*/
|
|
61
|
+
function renderCapabilityCard(data, colorize) {
|
|
62
|
+
// Pre-color the section headings + bullet markers.
|
|
63
|
+
const heading = (s) => colorize(s, 'warn');
|
|
64
|
+
const okMark = colorize('✓', 'success');
|
|
65
|
+
const noMark = colorize('✗', 'error');
|
|
66
|
+
const fixLbl = colorize('Fix:', 'tool');
|
|
67
|
+
// Compose the inner rows that boxSharp will wrap. Each row is the
|
|
68
|
+
// CONTENT (no border) — boxSharp adds the side borders + padding.
|
|
69
|
+
const rows = [];
|
|
70
|
+
if (data.canStill.length > 0) {
|
|
71
|
+
rows.push('');
|
|
72
|
+
rows.push(heading('Can still:'));
|
|
73
|
+
for (const action of data.canStill) {
|
|
74
|
+
rows.push(` ${okMark} ${truncToContent(action)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (data.cannotReliably.length > 0) {
|
|
78
|
+
rows.push('');
|
|
79
|
+
rows.push(heading('Cannot reliably:'));
|
|
80
|
+
for (const action of data.cannotReliably) {
|
|
81
|
+
rows.push(` ${noMark} ${truncToContent(action)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
rows.push('');
|
|
85
|
+
// Fix line — split-wrap to two lines if needed so long guidance
|
|
86
|
+
// doesn't get cut off mid-sentence by boxSharp's clipper.
|
|
87
|
+
const fixText = data.fix;
|
|
88
|
+
const fixPrefix = 'Fix: ';
|
|
89
|
+
const fixPrefixVis = (0, box_1.visibleLength)(fixPrefix);
|
|
90
|
+
if ((0, box_1.visibleLength)(fixText) + fixPrefixVis <= CONTENT_WIDTH) {
|
|
91
|
+
rows.push(`${fixLbl} ${fixText}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
rows.push(fixLbl);
|
|
95
|
+
// Wrap the fix text across content-width lines.
|
|
96
|
+
let remaining = fixText;
|
|
97
|
+
const wrapLimit = CONTENT_WIDTH - 2; // 2-space indent
|
|
98
|
+
while (remaining.length > 0) {
|
|
99
|
+
const chunk = remaining.length <= wrapLimit
|
|
100
|
+
? remaining
|
|
101
|
+
: breakAtWord(remaining, wrapLimit);
|
|
102
|
+
rows.push(` ${chunk}`);
|
|
103
|
+
remaining = remaining.slice(chunk.length).trimStart();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
rows.push('');
|
|
107
|
+
// Title is rendered into the top border by boxSharp. Prefix with
|
|
108
|
+
// a warning glyph (yellow) so the user reads it as an attention card.
|
|
109
|
+
const title = `${colorize('⚠', 'warn')} ${data.title}`;
|
|
110
|
+
return (0, box_1.boxSharp)(rows, CARD_WIDTH, title).split('\n');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Shorten an action label so it fits the bullet column with room for
|
|
114
|
+
* the marker (" ✓ ") and the border padding. Appends an ellipsis when
|
|
115
|
+
* truncated. Pure — exported for unit tests.
|
|
116
|
+
*/
|
|
117
|
+
function truncToContent(s) {
|
|
118
|
+
// Reserve 6 chars for " ✓ " prefix + 2 for border padding margin.
|
|
119
|
+
const cap = CONTENT_WIDTH - 6;
|
|
120
|
+
if ((0, box_1.visibleLength)(s) <= cap)
|
|
121
|
+
return s;
|
|
122
|
+
return s.slice(0, cap - 1) + '…';
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Break `s` at the last word boundary at-or-before `limit`. Falls back
|
|
126
|
+
* to a hard cut at `limit` when no whitespace appears in the prefix.
|
|
127
|
+
* Pure helper used by the Fix-line wrapper.
|
|
128
|
+
*/
|
|
129
|
+
function breakAtWord(s, limit) {
|
|
130
|
+
if (s.length <= limit)
|
|
131
|
+
return s;
|
|
132
|
+
const slice = s.slice(0, limit);
|
|
133
|
+
const lastSpace = slice.lastIndexOf(' ');
|
|
134
|
+
return lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
|
|
135
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/display/frame.ts — Phase v4.1.4 reply-quality polish (Part 1.5).
|
|
10
|
+
*
|
|
11
|
+
* Single source of truth for the reply-frame math: terminal width,
|
|
12
|
+
* left gutter, body width, indent-by-depth, and ANSI-aware soft wrap.
|
|
13
|
+
*
|
|
14
|
+
* Before this module the same width formula
|
|
15
|
+
* (`Math.min(out.columns ?? 80, 100)`) lived at 4 different sites with
|
|
16
|
+
* inconsistent offsets, no shared floor, and no actual wrap engine —
|
|
17
|
+
* `marked-terminal` has `reflowText: false` so long prose just spilled
|
|
18
|
+
* to terminal-natural wrap (continuation lines at column 0, not at the
|
|
19
|
+
* gutter). frame.ts replaces all four with one formula and one wrapper.
|
|
20
|
+
*
|
|
21
|
+
* Public surface:
|
|
22
|
+
* - GUTTER 3-col left gutter (assistant body)
|
|
23
|
+
* - BODY_WIDTH_MAX 100 (tunable cap; export so future skin/theme
|
|
24
|
+
* work can override without touching consumers)
|
|
25
|
+
* - getTerminalCols() live `process.stdout.columns` with 80 fallback
|
|
26
|
+
* - getBodyWidth() `max(20, cols - gutter - 2)` capped at
|
|
27
|
+
* `BODY_WIDTH_MAX - gutter - 2`
|
|
28
|
+
* - getIndent(depth) `' '.repeat(GUTTER + depth*2)` — gutter +
|
|
29
|
+
* depth-aware. depth=0 = bare gutter, depth=1 =
|
|
30
|
+
* gutter+2, etc.
|
|
31
|
+
* - wrap(text, opts) ANSI-aware soft wrap via wrap-ansi defaults
|
|
32
|
+
* `{ trim: false, hard: true }`. Returns string
|
|
33
|
+
* with embedded `\n` per wrap point.
|
|
34
|
+
* - applyFrame(body) convenience: indents every line of `body` by
|
|
35
|
+
* GUTTER. No wrap (caller pre-wraps to bodyWidth).
|
|
36
|
+
*
|
|
37
|
+
* Wrap engine: wrap-ansi@9 (ESM-only). Loaded via cached dynamic
|
|
38
|
+
* `import()` so a CJS TypeScript build can still consume it. Until the
|
|
39
|
+
* first wrap finishes resolving, `wrap()` falls back to a passthrough
|
|
40
|
+
* that preserves the input verbatim — wrong visual but never incorrect
|
|
41
|
+
* data. Boot-time prime via `primeFrameAsync()` (best-effort) so the
|
|
42
|
+
* first user-visible wrap call already has the module loaded.
|
|
43
|
+
*/
|
|
44
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
45
|
+
if (k2 === undefined) k2 = k;
|
|
46
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
47
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
48
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
49
|
+
}
|
|
50
|
+
Object.defineProperty(o, k2, desc);
|
|
51
|
+
}) : (function(o, m, k, k2) {
|
|
52
|
+
if (k2 === undefined) k2 = k;
|
|
53
|
+
o[k2] = m[k];
|
|
54
|
+
}));
|
|
55
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
56
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
57
|
+
}) : function(o, v) {
|
|
58
|
+
o["default"] = v;
|
|
59
|
+
});
|
|
60
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
61
|
+
var ownKeys = function(o) {
|
|
62
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
63
|
+
var ar = [];
|
|
64
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
65
|
+
return ar;
|
|
66
|
+
};
|
|
67
|
+
return ownKeys(o);
|
|
68
|
+
};
|
|
69
|
+
return function (mod) {
|
|
70
|
+
if (mod && mod.__esModule) return mod;
|
|
71
|
+
var result = {};
|
|
72
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
73
|
+
__setModuleDefault(result, mod);
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
})();
|
|
77
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
78
|
+
exports.BODY_WIDTH_MIN = exports.BODY_WIDTH_MAX = exports.GUTTER = void 0;
|
|
79
|
+
exports.getTerminalCols = getTerminalCols;
|
|
80
|
+
exports.getBodyWidth = getBodyWidth;
|
|
81
|
+
exports.getIndent = getIndent;
|
|
82
|
+
exports.primeFrameAsync = primeFrameAsync;
|
|
83
|
+
exports.wrap = wrap;
|
|
84
|
+
exports.applyFrame = applyFrame;
|
|
85
|
+
exports._resetForTests = _resetForTests;
|
|
86
|
+
exports._injectWrapForTests = _injectWrapForTests;
|
|
87
|
+
// ── Tunable constants ─────────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Left gutter for assistant body. 3 columns matches the visual rhythm
|
|
90
|
+
* established by the boot card, tool trail, and status footer once
|
|
91
|
+
* Part 1.5 lands. Was 2 before this slice — bumped one column so the
|
|
92
|
+
* body breathes against the left edge.
|
|
93
|
+
*/
|
|
94
|
+
exports.GUTTER = 3;
|
|
95
|
+
/**
|
|
96
|
+
* Maximum body width before the visual frame stops growing. Wide
|
|
97
|
+
* terminals (150+ cols) get a body capped at this minus gutter+2
|
|
98
|
+
* because long lines (~120 chars) are harder to read than mid-length
|
|
99
|
+
* (~70-90 chars). Tunable: skin or theme code can override.
|
|
100
|
+
*/
|
|
101
|
+
exports.BODY_WIDTH_MAX = 100;
|
|
102
|
+
/**
|
|
103
|
+
* Hard floor on body width. Below this we render at 20 cols and let
|
|
104
|
+
* the terminal-natural wrap pick up the rest — better than crashing
|
|
105
|
+
* with a negative-width wrap-ansi call on a 5-col terminal.
|
|
106
|
+
*/
|
|
107
|
+
exports.BODY_WIDTH_MIN = 20;
|
|
108
|
+
// ── Width helpers ─────────────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Live terminal column count. Reads `process.stdout.columns` on every
|
|
111
|
+
* call so resize events propagate without us needing a cache. Falls
|
|
112
|
+
* back to 80 when the stream is non-TTY or hasn't reported a size yet
|
|
113
|
+
* (pipes, CI logs, MCP serve).
|
|
114
|
+
*
|
|
115
|
+
* `out` override exists for testability — display.test.ts injects a
|
|
116
|
+
* fake stream with explicit `columns` to assert various widths.
|
|
117
|
+
*/
|
|
118
|
+
function getTerminalCols(out = process.stdout) {
|
|
119
|
+
const c = out.columns;
|
|
120
|
+
if (typeof c !== 'number' || !Number.isFinite(c) || c < 1)
|
|
121
|
+
return 80;
|
|
122
|
+
return c;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Computed body width — the safe horizontal space inside the frame.
|
|
126
|
+
* Math: `min(BODY_WIDTH_MAX, cols) - GUTTER - 2`. The trailing `-2`
|
|
127
|
+
* leaves visual breathing room on the right margin (mirrors the boot
|
|
128
|
+
* card / tool-row right-pad convention). Floored at BODY_WIDTH_MIN so
|
|
129
|
+
* pathological narrow terminals still get a usable wrap.
|
|
130
|
+
*/
|
|
131
|
+
function getBodyWidth(out = process.stdout) {
|
|
132
|
+
const cols = Math.min(getTerminalCols(out), exports.BODY_WIDTH_MAX);
|
|
133
|
+
const raw = cols - exports.GUTTER - 2;
|
|
134
|
+
return Math.max(exports.BODY_WIDTH_MIN, raw);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Indent string for the given nesting depth. Depth 0 = bare gutter
|
|
138
|
+
* (3 spaces). Each additional level adds 2 spaces. Used by list,
|
|
139
|
+
* blockquote, and code-block renderers so every element shares one
|
|
140
|
+
* indent algebra.
|
|
141
|
+
*/
|
|
142
|
+
function getIndent(depth = 0) {
|
|
143
|
+
const d = Math.max(0, Math.floor(depth));
|
|
144
|
+
return ' '.repeat(exports.GUTTER + d * 2);
|
|
145
|
+
}
|
|
146
|
+
let cachedWrap = null;
|
|
147
|
+
let primePromise = null;
|
|
148
|
+
/**
|
|
149
|
+
* Best-effort load of wrap-ansi. Idempotent. Safe to call from boot.
|
|
150
|
+
* Returns a promise that resolves once the module is loaded (or
|
|
151
|
+
* rejects silently — wrap() will just keep using the passthrough).
|
|
152
|
+
*/
|
|
153
|
+
function primeFrameAsync() {
|
|
154
|
+
if (cachedWrap)
|
|
155
|
+
return Promise.resolve();
|
|
156
|
+
if (primePromise)
|
|
157
|
+
return primePromise;
|
|
158
|
+
primePromise = (async () => {
|
|
159
|
+
try {
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
|
+
const mod = await Promise.resolve().then(() => __importStar(require('wrap-ansi')));
|
|
162
|
+
const fn = (mod.default ?? mod);
|
|
163
|
+
if (typeof fn === 'function')
|
|
164
|
+
cachedWrap = fn;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Swallow — fallback is a passthrough, never crashes.
|
|
168
|
+
}
|
|
169
|
+
})();
|
|
170
|
+
return primePromise;
|
|
171
|
+
}
|
|
172
|
+
// Kick off the import at module load. Best effort — if it fails (e.g.
|
|
173
|
+
// missing dep, broken install) we degrade to passthrough.
|
|
174
|
+
primeFrameAsync();
|
|
175
|
+
/**
|
|
176
|
+
* ANSI-aware soft wrap. Defaults `{ trim: false, hard: true }`:
|
|
177
|
+
* - trim: false → preserves leading/trailing whitespace on each
|
|
178
|
+
* visual line (important for code-block indent + alignment).
|
|
179
|
+
* - hard: true → break extremely long words mid-character at width
|
|
180
|
+
* instead of overflowing. Code blocks especially need this.
|
|
181
|
+
*
|
|
182
|
+
* Pure with respect to ANSI: escape sequences pass through wrap-ansi
|
|
183
|
+
* untouched and don't count toward the column budget.
|
|
184
|
+
*
|
|
185
|
+
* Synchronous. When wrap-ansi hasn't finished loading yet (the rare
|
|
186
|
+
* boot-race window), returns `text` unchanged. The user sees the
|
|
187
|
+
* un-wrapped paint exactly once; by the next render the cache is hot.
|
|
188
|
+
*/
|
|
189
|
+
function wrap(text, cols, options = {}) {
|
|
190
|
+
const w = cachedWrap;
|
|
191
|
+
if (!w)
|
|
192
|
+
return text;
|
|
193
|
+
const opts = { trim: options.trim ?? false, hard: options.hard ?? true };
|
|
194
|
+
try {
|
|
195
|
+
return w(text, cols, opts);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return text;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Indent every line of `body` by the bare gutter. Empty lines are
|
|
203
|
+
* passed through unindented so blank visual rows don't carry
|
|
204
|
+
* trailing whitespace into the transcript.
|
|
205
|
+
*
|
|
206
|
+
* Caller is responsible for pre-wrapping to `getBodyWidth()` — this
|
|
207
|
+
* function is purely the indent step, not the wrap step. Keeping them
|
|
208
|
+
* separate so callers that already have their own indent (lists,
|
|
209
|
+
* code blocks) can opt out of this and still consume `wrap()`.
|
|
210
|
+
*/
|
|
211
|
+
function applyFrame(body) {
|
|
212
|
+
const ind = getIndent(0);
|
|
213
|
+
return body
|
|
214
|
+
.split('\n')
|
|
215
|
+
.map((ln) => (ln.length === 0 ? '' : `${ind}${ln}`))
|
|
216
|
+
.join('\n');
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Test reset — drops the cached wrap function so a fresh prime can be
|
|
220
|
+
* forced. Used by unit tests to exercise the fallback path AND to
|
|
221
|
+
* confirm post-prime behaviour.
|
|
222
|
+
*/
|
|
223
|
+
function _resetForTests() {
|
|
224
|
+
cachedWrap = null;
|
|
225
|
+
primePromise = null;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Test injection — set the wrap function explicitly. Used by tests
|
|
229
|
+
* that want deterministic behaviour without depending on dynamic
|
|
230
|
+
* `import()` resolution timing.
|
|
231
|
+
*/
|
|
232
|
+
function _injectWrapForTests(fn) {
|
|
233
|
+
cachedWrap = fn;
|
|
234
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/display/progressBar.ts — Phase v4.1.4 Part 1.6.
|
|
10
|
+
*
|
|
11
|
+
* Per-turn token progress bar. Renders `▰▰▰▰▰▱▱▱▱▱ 412/4096 tokens`
|
|
12
|
+
* on a single line below the activity indicator (or in place of it,
|
|
13
|
+
* once the stream takes over). Event-driven: each `update(n, max)`
|
|
14
|
+
* call redraws the bar with the new counter — no `setInterval`,
|
|
15
|
+
* because the source of truth is the adapter's incremental
|
|
16
|
+
* `progress` stream events, which already fire at the granularity
|
|
17
|
+
* the model produces tokens.
|
|
18
|
+
*
|
|
19
|
+
* Honest degradation: if the adapter never calls `update()`, the bar
|
|
20
|
+
* never paints. No client-side estimation (per v4.1.4 spec — token
|
|
21
|
+
* count only, no time-based fakery).
|
|
22
|
+
*
|
|
23
|
+
* Visual:
|
|
24
|
+
*
|
|
25
|
+
* ▰▰▰▰▰▱▱▱▱▱ 412/4096 tokens
|
|
26
|
+
* │└────┬───┘ └────┬─────┘
|
|
27
|
+
* │ │ └── current/max (compact via formatCompactTokens)
|
|
28
|
+
* │ └── 10 cells, filled ratio ∝ outputTokens/maxTokens
|
|
29
|
+
* └── leading gutter aligns with frame
|
|
30
|
+
*
|
|
31
|
+
* Bar cells:
|
|
32
|
+
* - filled: ▰ (U+25B0, dark shade block-fill)
|
|
33
|
+
* - empty: ▱ (U+25B1, light shade)
|
|
34
|
+
*
|
|
35
|
+
* Cursor invariant on render: bar OWNS one line. After each `update`
|
|
36
|
+
* the cursor sits at column 0 of the bar line (single-line `\r\x1b[K`
|
|
37
|
+
* overwrite pattern, same as activityIndicator). Callers that want to
|
|
38
|
+
* write OTHER content below MUST call `hide()` first.
|
|
39
|
+
*
|
|
40
|
+
* Non-TTY: completely silent — pipes/CI/MCP serve mode get clean
|
|
41
|
+
* output by default. The handle still accepts updates so callers
|
|
42
|
+
* don't need to branch on TTY-ness.
|
|
43
|
+
*/
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.createProgressBar = createProgressBar;
|
|
46
|
+
const frame_1 = require("./frame");
|
|
47
|
+
const display_1 = require("../display");
|
|
48
|
+
/**
|
|
49
|
+
* Number of bar cells. 10 cells gives clean fractions (every 10% of
|
|
50
|
+
* fill ratio == one cell). Wider bars feel noisy at the standard
|
|
51
|
+
* frame width (75 visible cols on an 80-col terminal).
|
|
52
|
+
*/
|
|
53
|
+
const BAR_CELLS = 10;
|
|
54
|
+
/** Glyphs. Chosen for clean visual weight at the standard mono font. */
|
|
55
|
+
const FILLED = '▰';
|
|
56
|
+
const EMPTY = '▱';
|
|
57
|
+
/**
|
|
58
|
+
* Create a progress-bar handle bound to a writable stream + skin.
|
|
59
|
+
* The bar paints on the next `update` call — there's no initial
|
|
60
|
+
* paint at creation time, because we don't know the token counts yet.
|
|
61
|
+
*
|
|
62
|
+
* `out` is the stream to write on (usually `process.stdout` via
|
|
63
|
+
* `Display.out`). `skin` is the active skin engine. Both are captured
|
|
64
|
+
* by closure — the handle survives skin swaps for the rest of the turn.
|
|
65
|
+
*/
|
|
66
|
+
function createProgressBar(out, skin) {
|
|
67
|
+
const isTty = !!out.isTTY;
|
|
68
|
+
let outputTokens = 0;
|
|
69
|
+
let maxTokens = undefined;
|
|
70
|
+
let printed = false;
|
|
71
|
+
let hidden = false;
|
|
72
|
+
let lastPaintTokens = -1;
|
|
73
|
+
const buildLine = () => {
|
|
74
|
+
// Fill ratio: 0..1, then snap to a cell count 0..BAR_CELLS.
|
|
75
|
+
const denom = maxTokens && maxTokens > 0 ? maxTokens : 0;
|
|
76
|
+
const ratio = denom > 0 ? Math.min(1, outputTokens / denom) : 0;
|
|
77
|
+
const filled = Math.min(BAR_CELLS, Math.round(ratio * BAR_CELLS));
|
|
78
|
+
const empty = BAR_CELLS - filled;
|
|
79
|
+
const bar = skin.applyColors(FILLED.repeat(filled), 'brand') +
|
|
80
|
+
skin.applyColors(EMPTY.repeat(empty), 'muted');
|
|
81
|
+
// Label: compact "412/4096 tokens". If no maxTokens, render
|
|
82
|
+
// "412 tokens" (denominator unknown). The model name doesn't
|
|
83
|
+
// belong here — that's the status footer's job post-turn.
|
|
84
|
+
const left = (0, display_1.formatCompactTokens)(outputTokens);
|
|
85
|
+
const right = denom > 0 ? (0, display_1.formatCompactTokens)(denom) : '?';
|
|
86
|
+
const label = denom > 0
|
|
87
|
+
? skin.applyColors(`${left}/${right} tokens`, 'muted')
|
|
88
|
+
: skin.applyColors(`${left} tokens`, 'muted');
|
|
89
|
+
const gutter = (0, frame_1.getIndent)(0);
|
|
90
|
+
return `${gutter}${bar} ${label}`;
|
|
91
|
+
};
|
|
92
|
+
const paint = () => {
|
|
93
|
+
if (!isTty || hidden)
|
|
94
|
+
return;
|
|
95
|
+
// `\r\x1b[K` — carriage return + erase to end of line, then
|
|
96
|
+
// rewrite. Same single-line overwrite pattern as the activity
|
|
97
|
+
// indicator and tool-row live tick.
|
|
98
|
+
out.write(`\r\x1b[K${buildLine()}`);
|
|
99
|
+
printed = true;
|
|
100
|
+
lastPaintTokens = outputTokens;
|
|
101
|
+
};
|
|
102
|
+
const erase = () => {
|
|
103
|
+
if (isTty && printed)
|
|
104
|
+
out.write('\r\x1b[K');
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
update(n, max) {
|
|
108
|
+
if (hidden)
|
|
109
|
+
return;
|
|
110
|
+
// Coerce + clamp. Non-finite or negative inputs are ignored —
|
|
111
|
+
// never crash the stream consumer with a malformed event.
|
|
112
|
+
if (typeof n === 'number' && Number.isFinite(n) && n >= 0) {
|
|
113
|
+
outputTokens = Math.floor(n);
|
|
114
|
+
}
|
|
115
|
+
if (typeof max === 'number' && Number.isFinite(max) && max > 0) {
|
|
116
|
+
maxTokens = Math.floor(max);
|
|
117
|
+
}
|
|
118
|
+
// Dedup: skip the repaint if the visible state didn't change.
|
|
119
|
+
// Anthropic emits message_delta events with the SAME running
|
|
120
|
+
// counter when no new tokens were produced; without this
|
|
121
|
+
// gate we'd flicker on every duplicate.
|
|
122
|
+
if (outputTokens === lastPaintTokens && printed)
|
|
123
|
+
return;
|
|
124
|
+
paint();
|
|
125
|
+
},
|
|
126
|
+
hide() {
|
|
127
|
+
if (hidden)
|
|
128
|
+
return;
|
|
129
|
+
hidden = true;
|
|
130
|
+
erase();
|
|
131
|
+
},
|
|
132
|
+
isHidden() { return hidden; },
|
|
133
|
+
getTokens() {
|
|
134
|
+
return { output: outputTokens, max: maxTokens };
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|