aiden-runtime 4.1.3 → 4.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/v4/aidenCLI.js +28 -0
- package/dist/cli/v4/callbacks.js +148 -13
- package/dist/cli/v4/chatSession.js +283 -22
- package/dist/cli/v4/defaultSoul.js +143 -4
- package/dist/cli/v4/display/frame.js +234 -0
- package/dist/cli/v4/display/progressBar.js +170 -0
- package/dist/cli/v4/display.js +663 -24
- package/dist/cli/v4/replyRenderer.js +196 -26
- package/dist/cli/v4/skinEngine.js +15 -4
- package/dist/cli/v4/toolPreview.js +78 -19
- package/dist/core/toolRegistry.js +7 -1
- package/dist/core/v4/aidenAgent.js +72 -0
- package/dist/core/v4/loopTrace.js +257 -0
- package/dist/core/v4/promptBuilder.js +2 -1
- package/dist/core/version.js +1 -1
- package/dist/core/webSearch.js +64 -24
- package/dist/providers/v4/anthropicAdapter.js +25 -2
- package/package.json +2 -1
- package/plugins/aiden-plugin-cdp-browser/.granted-permissions.json +8 -0
|
@@ -27,10 +27,16 @@
|
|
|
27
27
|
*/
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.getReplyRenderer = getReplyRenderer;
|
|
30
|
+
exports.normalizeBlankLines = normalizeBlankLines;
|
|
30
31
|
exports._resetForTests = _resetForTests;
|
|
31
32
|
const marked_1 = require("marked");
|
|
32
33
|
const skinEngine_1 = require("./skinEngine");
|
|
33
34
|
const syntaxHighlight_1 = require("./syntaxHighlight");
|
|
35
|
+
// v4.1.4 reply-quality polish: single source of truth for frame math.
|
|
36
|
+
// Replaces 3 inline `Math.min(process.stdout.columns ?? 80, 100) - 4`
|
|
37
|
+
// callsites in this file with `getBodyWidth()` and adds soft-wrap for
|
|
38
|
+
// code-block lines that previously overflowed the viewport.
|
|
39
|
+
const frame_1 = require("./display/frame");
|
|
34
40
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
35
41
|
const TerminalRenderer = require('marked-terminal').default ?? require('marked-terminal');
|
|
36
42
|
function paint(kind) {
|
|
@@ -120,7 +126,11 @@ const CODE_BG_ON = '\x1b[48;2;50;50;60m';
|
|
|
120
126
|
const CODE_BG_OFF = '\x1b[49m';
|
|
121
127
|
function renderCodeBlock(code, lang) {
|
|
122
128
|
const sk = (0, skinEngine_1.getSkinEngine)();
|
|
123
|
-
|
|
129
|
+
// v4.1.4 reply-quality polish: width sourced from frame.ts. Same
|
|
130
|
+
// visual budget as the v4.1.3 formula (cols capped at 100, minus
|
|
131
|
+
// gutter+2) — but expressed via the shared helper so it tracks any
|
|
132
|
+
// future width-policy change in one place.
|
|
133
|
+
const width = (0, frame_1.getBodyWidth)();
|
|
124
134
|
const langLabel = (lang ?? '').trim();
|
|
125
135
|
// v4.1.3-essentials reply-polish: language tag on the top rule
|
|
126
136
|
// already shipped; keep it. Bottom rule unlabeled (closing fence).
|
|
@@ -131,26 +141,43 @@ function renderCodeBlock(code, lang) {
|
|
|
131
141
|
const body = (0, syntaxHighlight_1.isSupportedLang)(langLabel)
|
|
132
142
|
? (0, syntaxHighlight_1.highlightCode)(code, langLabel)
|
|
133
143
|
: code;
|
|
134
|
-
// v4.1.
|
|
135
|
-
//
|
|
144
|
+
// v4.1.4 reply-quality polish: per-line soft wrap. The rail + bg
|
|
145
|
+
// chrome adds 4 visible columns (` │ `, padding spaces around the
|
|
146
|
+
// line). Subtract those so wrap math targets the actual content
|
|
147
|
+
// budget. `hard: true` ensures even pathological long tokens
|
|
148
|
+
// (minified JS, hashes) break instead of escaping the frame.
|
|
149
|
+
//
|
|
150
|
+
// Width inside the body of a code line:
|
|
151
|
+
// gutter (3) + `│ ` (2) + leading-space (1) + CONTENT + trailing-space (1)
|
|
152
|
+
// → content budget = width - gutter - 4. We further cap at width to
|
|
153
|
+
// keep the fence rule aligned with the body's right margin.
|
|
154
|
+
const contentBudget = Math.max(8, width - frame_1.GUTTER - 4);
|
|
155
|
+
// v4.1.3-essentials reply-polish (preserved): each body line gets:
|
|
156
|
+
// - frame gutter (was 2-space outer indent; now uses shared GUTTER)
|
|
136
157
|
// - left rail `│ ` painted muted (mirrors blockquote's `┃ ` rail
|
|
137
158
|
// with a different glyph so they're visually distinct)
|
|
138
159
|
// - 24-bit dark background wrapping the rail + content (subtle
|
|
139
160
|
// "this is code" affordance without going full TUI box-frame)
|
|
140
|
-
//
|
|
141
|
-
// Strip the optional ANSI-only NO_COLOR gate by emitting bg codes
|
|
142
|
-
// unconditionally — the skin engine already short-circuits inner
|
|
143
|
-
// paint calls when NO_COLOR is set, and bare bg codes degrade
|
|
144
|
-
// gracefully on terminals that don't render them.
|
|
145
161
|
const rail = sk.applyColors('│', 'muted');
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
const gutter = (0, frame_1.getIndent)(0);
|
|
163
|
+
// Wrap each source line independently — code-block semantics demand
|
|
164
|
+
// that a "logical line" remains visible as one continued unit even
|
|
165
|
+
// when soft-wrapped. The CODE_BG painting closes per VISUAL line so
|
|
166
|
+
// a wrap break doesn't bleed bg across the rail of the next row.
|
|
167
|
+
const wrappedLines = [];
|
|
168
|
+
for (const srcLine of body.split('\n')) {
|
|
169
|
+
const wrapped = (0, frame_1.wrap)(srcLine, contentBudget, { trim: false, hard: true });
|
|
170
|
+
for (const visualLine of wrapped.split('\n')) {
|
|
171
|
+
wrappedLines.push(`${gutter}${rail} ${CODE_BG_ON} ${visualLine} ${CODE_BG_OFF}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const indented = wrappedLines.join('\n');
|
|
175
|
+
// Top + bottom fence rules sit at the gutter too — visually anchors
|
|
176
|
+
// the block as a unit inside the assistant frame.
|
|
150
177
|
return [
|
|
151
|
-
sk.applyColors(top, 'muted')
|
|
178
|
+
`${gutter}${sk.applyColors(top, 'muted')}`,
|
|
152
179
|
indented,
|
|
153
|
-
sk.applyColors(bot, 'muted')
|
|
180
|
+
`${gutter}${sk.applyColors(bot, 'muted')}`,
|
|
154
181
|
'',
|
|
155
182
|
].join('\n') + '\n';
|
|
156
183
|
}
|
|
@@ -222,6 +249,97 @@ function renderHeading(text, depth) {
|
|
|
222
249
|
function renderListItem(text) {
|
|
223
250
|
return text;
|
|
224
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* v4.1.4 reply-quality polish — Fix C helper.
|
|
254
|
+
*
|
|
255
|
+
* Render a single list item's tokens correctly, expanding inline
|
|
256
|
+
* emphasis (strong, em, codespan) that the prior `parser.parse` path
|
|
257
|
+
* silently stranded as raw text.
|
|
258
|
+
*
|
|
259
|
+
* Marked v15 token shapes for list items:
|
|
260
|
+
*
|
|
261
|
+
* Tight list (default — no blank lines between items):
|
|
262
|
+
* list_item.tokens = [
|
|
263
|
+
* { type: 'text', text: '**bold**',
|
|
264
|
+
* tokens: [ { type: 'strong', ... } ] ← inline children
|
|
265
|
+
* }
|
|
266
|
+
* ]
|
|
267
|
+
*
|
|
268
|
+
* Loose list (blank line between items, OR an item with multiple
|
|
269
|
+
* paragraphs):
|
|
270
|
+
* list_item.tokens = [
|
|
271
|
+
* { type: 'paragraph', tokens: [ inline children… ] },
|
|
272
|
+
* { type: 'paragraph', tokens: [ … ] },
|
|
273
|
+
* ]
|
|
274
|
+
*
|
|
275
|
+
* Nested list (a list-token inside an item):
|
|
276
|
+
* list_item.tokens = [
|
|
277
|
+
* { type: 'text', tokens: [...] }, ← the item's own text first
|
|
278
|
+
* { type: 'list', items: [...] }, ← then the nested list
|
|
279
|
+
* ]
|
|
280
|
+
*
|
|
281
|
+
* Item with fenced code block:
|
|
282
|
+
* list_item.tokens = [
|
|
283
|
+
* { type: 'text', ... },
|
|
284
|
+
* { type: 'code', text: '…', lang: '…' },
|
|
285
|
+
* ]
|
|
286
|
+
*
|
|
287
|
+
* Dispatch rules:
|
|
288
|
+
* - `text` with nested `.tokens` → parseInline(tokens)
|
|
289
|
+
* - `text` with only `.text` → fall through to raw text
|
|
290
|
+
* - `paragraph` → parseInline(paragraph.tokens) + '\n'
|
|
291
|
+
* - `list` / `code` / other block → parser.parse([token]) (block path)
|
|
292
|
+
*
|
|
293
|
+
* Returns the joined rendered string. Pure-ish: depends on marked's
|
|
294
|
+
* parser instance (closure-captured) but never mutates it.
|
|
295
|
+
*/
|
|
296
|
+
function renderListItemTokens(it, parser) {
|
|
297
|
+
const toks = Array.isArray(it.tokens) ? it.tokens : [];
|
|
298
|
+
if (toks.length === 0)
|
|
299
|
+
return it.text ?? '';
|
|
300
|
+
const out = [];
|
|
301
|
+
for (const raw of toks) {
|
|
302
|
+
if (typeof raw !== 'object' || raw === null)
|
|
303
|
+
continue;
|
|
304
|
+
const tk = raw;
|
|
305
|
+
const type = tk.type;
|
|
306
|
+
// Inline-only wrapper (tight-list common case). The `text` outer
|
|
307
|
+
// token holds inline children we want to expand into ANSI.
|
|
308
|
+
if (type === 'text') {
|
|
309
|
+
if (Array.isArray(tk.tokens) && tk.tokens.length > 0 && parser.parseInline) {
|
|
310
|
+
out.push(parser.parseInline(tk.tokens));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
out.push(tk.text ?? '');
|
|
314
|
+
}
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
// Paragraph block (loose-list case). Marked wraps each paragraph's
|
|
318
|
+
// inline content in `.tokens`; render those inline + append a
|
|
319
|
+
// newline so multi-paragraph items stack visually.
|
|
320
|
+
if (type === 'paragraph') {
|
|
321
|
+
if (Array.isArray(tk.tokens) && tk.tokens.length > 0 && parser.parseInline) {
|
|
322
|
+
out.push(parser.parseInline(tk.tokens));
|
|
323
|
+
out.push('\n');
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
out.push(tk.text ?? '');
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
// Nested list, fenced code, or any other block-level token. The
|
|
331
|
+
// block parser handles these via the normal dispatch (which calls
|
|
332
|
+
// back into our own `renderer.list` override for nested lists —
|
|
333
|
+
// depth counter is already incremented before we got here).
|
|
334
|
+
if (parser.parse) {
|
|
335
|
+
out.push(parser.parse([tk]));
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
// Last-resort fallback: drop the token's text in raw.
|
|
339
|
+
out.push(tk.text ?? '');
|
|
340
|
+
}
|
|
341
|
+
return out.join('');
|
|
342
|
+
}
|
|
225
343
|
/**
|
|
226
344
|
* Singleton — caching is fine since options bind to the active skin
|
|
227
345
|
* via paint callbacks (which read getSkinEngine() each call).
|
|
@@ -246,7 +364,7 @@ function getReplyRenderer() {
|
|
|
246
364
|
// need for the 4-tier hierarchy. The prototype-level `renderer.heading`
|
|
247
365
|
// override below owns the depth extraction + tier selection end-to-end.
|
|
248
366
|
// marked-terminal's stripped-args call path never reaches our callback.
|
|
249
|
-
hr: () => paint('muted')('─'.repeat(
|
|
367
|
+
hr: () => paint('muted')('─'.repeat((0, frame_1.getBodyWidth)())) + '\n',
|
|
250
368
|
listitem: renderListItem,
|
|
251
369
|
paragraph: (text) => `${text}\n\n`,
|
|
252
370
|
// v4.1.3-essentials: bold renders as ANSI bold + underline
|
|
@@ -274,7 +392,12 @@ function getReplyRenderer() {
|
|
|
274
392
|
link: (assembled) => paint('accent')(assembled),
|
|
275
393
|
href: paint('accent'),
|
|
276
394
|
text: (text) => text,
|
|
277
|
-
|
|
395
|
+
// v4.1.4 reply-quality polish: marked-terminal's `width` is the
|
|
396
|
+
// *outer* canvas it formats into. Frame-aware body width keeps the
|
|
397
|
+
// tables / hr / hard-wrap targets inside our gutter envelope.
|
|
398
|
+
// `reflowText: false` (below) stays off — we own prose wrap via
|
|
399
|
+
// frame.wrap() in the display layer, not here.
|
|
400
|
+
width: (0, frame_1.getBodyWidth)(),
|
|
278
401
|
showSectionPrefix: false,
|
|
279
402
|
reflowText: false,
|
|
280
403
|
tab: 2,
|
|
@@ -417,17 +540,32 @@ function getReplyRenderer() {
|
|
|
417
540
|
const tok = body;
|
|
418
541
|
isOrdered = tok.ordered === true;
|
|
419
542
|
startNum = typeof tok.start === 'number' ? tok.start : 1;
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
// the
|
|
543
|
+
// v4.1.4 reply-quality polish — Fix C (token-type dispatch).
|
|
544
|
+
//
|
|
545
|
+
// Prior implementation called `parser.parse(it.tokens)` and let
|
|
546
|
+
// marked's block-parser dispatch each token. For tight-list items
|
|
547
|
+
// marked v15 wraps the item's content in a `text`-type outer
|
|
548
|
+
// token whose `.tokens` array holds the actual inline tokens
|
|
549
|
+
// (strong, em, codespan…). `parser.parse` dispatched the outer
|
|
550
|
+
// wrapper to `renderer.text` (our `opts.text` = identity), which
|
|
551
|
+
// returned the RAW raw `**bold**` source string — never recursing
|
|
552
|
+
// into the inline children. Result: literal asterisks in every
|
|
553
|
+
// bullet that contained inline emphasis.
|
|
554
|
+
//
|
|
555
|
+
// Fix: walk each top-level token by type. Tight-list items have
|
|
556
|
+
// a `text` wrapper → use `parseInline` on its nested tokens to
|
|
557
|
+
// expand strong/em/codespan. Loose-list items have block-level
|
|
558
|
+
// `paragraph`/`list`/`code` tokens → those need block-level
|
|
559
|
+
// recursion (delegates back to our list override for nested
|
|
560
|
+
// lists, preserving the depth counter).
|
|
561
|
+
//
|
|
562
|
+
// Confirmed against marked v15 token shapes from `marked.lexer`
|
|
563
|
+
// (see scripts/smoke-issue-c-tokens.ts).
|
|
425
564
|
const parser = this.parser;
|
|
426
565
|
items = (tok.items ?? []).map((it) => {
|
|
427
|
-
if (
|
|
428
|
-
return
|
|
429
|
-
|
|
430
|
-
return it.text ?? '';
|
|
566
|
+
if (!parser)
|
|
567
|
+
return it.text ?? '';
|
|
568
|
+
return renderListItemTokens(it, parser);
|
|
431
569
|
});
|
|
432
570
|
}
|
|
433
571
|
else {
|
|
@@ -485,7 +623,21 @@ function getReplyRenderer() {
|
|
|
485
623
|
// if other code transiently swaps the renderer.
|
|
486
624
|
marked_1.marked.setOptions({ renderer: renderer });
|
|
487
625
|
const out = marked_1.marked.parse(text);
|
|
488
|
-
|
|
626
|
+
const raw = typeof out === 'string' ? out : String(out);
|
|
627
|
+
// v4.1.4 Part 1.6 Issue I — collapse excess vertical spacing.
|
|
628
|
+
//
|
|
629
|
+
// Our `opts.paragraph` callback emits `text\n\n`, our
|
|
630
|
+
// `renderCodeBlock` ends with `\n\n`, and marked-terminal's
|
|
631
|
+
// outer block dispatch ALSO emits `\n\n` between adjacent
|
|
632
|
+
// blocks. Result: 4 newlines (3 visible blank lines) between
|
|
633
|
+
// paragraphs, after code blocks, between paragraphs and lists.
|
|
634
|
+
// Root-cause fix would require auditing marked-terminal's
|
|
635
|
+
// between-block separator across every override (risk-prone).
|
|
636
|
+
// Band-aid: collapse any run of 3+ newlines down to exactly 2
|
|
637
|
+
// (= one blank line). Mechanically safe — can only REMOVE
|
|
638
|
+
// excess whitespace, never add bad spacing. Existing single-
|
|
639
|
+
// blank-line gaps pass through unchanged.
|
|
640
|
+
return normalizeBlankLines(raw);
|
|
489
641
|
}
|
|
490
642
|
catch {
|
|
491
643
|
return text;
|
|
@@ -494,6 +646,24 @@ function getReplyRenderer() {
|
|
|
494
646
|
};
|
|
495
647
|
return cachedRenderer;
|
|
496
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* v4.1.4 Part 1.6 Issue I — collapse runs of 3+ consecutive newlines
|
|
651
|
+
* down to exactly 2 (a single blank line). Exported for unit-test
|
|
652
|
+
* access; pure with no side effects.
|
|
653
|
+
*
|
|
654
|
+
* Confirmed via `scripts/smoke-issue-i-spacing.ts`:
|
|
655
|
+
* - "A\n\n\n\nB" → "A\n\nB" (2 paras → 1 blank line)
|
|
656
|
+
* - "A\n\n\n\n\nB" → "A\n\nB" (3+ blanks all collapse)
|
|
657
|
+
* - "A\n\nB" → "A\n\nB" (already correct, unchanged)
|
|
658
|
+
* - "A\nB" → "A\nB" (single newline preserved)
|
|
659
|
+
* - "A\n" → "A\n" (trailing pass-through)
|
|
660
|
+
*
|
|
661
|
+
* Does NOT touch the list-under-padding case (lists ending with a
|
|
662
|
+
* single `\n` before a paragraph) — that's a v4.1.5 follow-up.
|
|
663
|
+
*/
|
|
664
|
+
function normalizeBlankLines(text) {
|
|
665
|
+
return text.replace(/\n{3,}/g, '\n\n');
|
|
666
|
+
}
|
|
497
667
|
/** Test reset — drops the cached renderer so a skin change picks up. */
|
|
498
668
|
function _resetForTests() {
|
|
499
669
|
cachedRenderer = null;
|
|
@@ -49,9 +49,15 @@ const DEFAULT_SKIN = {
|
|
|
49
49
|
error: [0xf4, 0x47, 0x47],
|
|
50
50
|
warn: [0xff, 0xc1, 0x07],
|
|
51
51
|
success: [0x4c, 0xaf, 0x50],
|
|
52
|
-
// v4.1.
|
|
53
|
-
//
|
|
54
|
-
|
|
52
|
+
// v4.1.4 reply-quality polish: muted shifts from neutral grey
|
|
53
|
+
// (#888888) to warm Aiden-tinted dim (#b8a89a). Mid-grey at +56
|
|
54
|
+
// brightness on red/green channels with a slight cool-down on
|
|
55
|
+
// blue, putting muted in the same warm family as brand orange
|
|
56
|
+
// (#FF6B35) without competing with it. Reads as "intentional
|
|
57
|
+
// dim" rather than washed-out terminal grey. Used by tool-trail
|
|
58
|
+
// gutter, status footer, code-block rail, blockquote rail, and
|
|
59
|
+
// display.dim() — surfaces the user reads constantly.
|
|
60
|
+
muted: [0xb8, 0xa8, 0x9a],
|
|
55
61
|
heading: BRAND_ORANGE,
|
|
56
62
|
// v4.1.3-repl-polish: session = soft cyan (ex-muted); used for IDs
|
|
57
63
|
// and the session-end card header labels.
|
|
@@ -84,7 +90,12 @@ const LIGHT_SKIN = {
|
|
|
84
90
|
error: [0xb0, 0x10, 0x10],
|
|
85
91
|
warn: [0x80, 0x60, 0x00],
|
|
86
92
|
success: [0x1b, 0x5e, 0x20],
|
|
87
|
-
|
|
93
|
+
// v4.1.4 reply-quality polish: proportional warm-shift for the
|
|
94
|
+
// light skin too. Was neutral #606060; new value #7a6e5e keeps the
|
|
95
|
+
// dark-on-light contrast budget but adds the same warm tint as the
|
|
96
|
+
// default skin's muted so themed surfaces feel coherent across
|
|
97
|
+
// skin switches.
|
|
98
|
+
muted: [0x7a, 0x6e, 0x5e],
|
|
88
99
|
heading: [0xc4, 0x42, 0x10],
|
|
89
100
|
session: [0x00, 0x55, 0x88],
|
|
90
101
|
degraded: [0x80, 0x60, 0x00],
|
|
@@ -29,9 +29,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.TOOL_PRIMARY_ARG = void 0;
|
|
30
30
|
exports.buildToolPreview = buildToolPreview;
|
|
31
31
|
/**
|
|
32
|
-
* Map of tool-name →
|
|
33
|
-
*
|
|
34
|
-
* entries.
|
|
32
|
+
* Map of tool-name → preview extractor (string key OR function).
|
|
33
|
+
* Stable contract; tests assert specific entries.
|
|
35
34
|
*/
|
|
36
35
|
exports.TOOL_PRIMARY_ARG = {
|
|
37
36
|
// ── terminal / execution ─────────────────────────────────────────────
|
|
@@ -70,6 +69,16 @@ exports.TOOL_PRIMARY_ARG = {
|
|
|
70
69
|
skill_view: 'name',
|
|
71
70
|
skill_manage: 'action',
|
|
72
71
|
skills_list: '',
|
|
72
|
+
// v4.1.5 Phase 1d (Q-Q1-a) — registry introspection tool. Args
|
|
73
|
+
// shape: `{ toolName: 'web_search' }`. The agent uses this to
|
|
74
|
+
// discover unfamiliar tool schemas during planning. Surface the
|
|
75
|
+
// target tool name so the trail row (when not suppressed via
|
|
76
|
+
// TRAIL_HIDE_TOOLS) reads as the introspected tool, not raw JSON.
|
|
77
|
+
// Note: most callers see this tool suppressed entirely from the
|
|
78
|
+
// visible trail via the TRAIL_HIDE_TOOLS set in display.ts; the
|
|
79
|
+
// extractor exists for code paths that DON'T suppress (verbose
|
|
80
|
+
// mode, log-file capture).
|
|
81
|
+
lookup_tool_schema: 'toolName',
|
|
73
82
|
// ── sessions ─────────────────────────────────────────────────────────
|
|
74
83
|
session_search: 'query',
|
|
75
84
|
session_list: '',
|
|
@@ -100,6 +109,37 @@ exports.TOOL_PRIMARY_ARG = {
|
|
|
100
109
|
media_transport: 'target',
|
|
101
110
|
media_key: 'action',
|
|
102
111
|
app_input: 'app',
|
|
112
|
+
// ── v4.1.4 Phase 3b' (Issue H) ───────────────────────────────────────
|
|
113
|
+
// app_launch needs custom logic: when `app === 'explorer.exe'` the
|
|
114
|
+
// binary is just the URI dispatcher and the meaningful target is in
|
|
115
|
+
// `args[0]` (e.g. 'spotify:track/...'). Surface the protocol scheme
|
|
116
|
+
// ('spotify') rather than the dispatch binary. Falls through to the
|
|
117
|
+
// app name for normal exe launches.
|
|
118
|
+
app_launch: (args) => {
|
|
119
|
+
if (!args || typeof args !== 'object')
|
|
120
|
+
return '';
|
|
121
|
+
const a = args;
|
|
122
|
+
const appRaw = typeof a.app === 'string' ? a.app.trim() : '';
|
|
123
|
+
// URI-protocol case: explorer.exe + 'scheme:...' in args[0].
|
|
124
|
+
if (appRaw.toLowerCase() === 'explorer.exe' && Array.isArray(a.args)) {
|
|
125
|
+
const first = a.args[0];
|
|
126
|
+
if (typeof first === 'string' && first.length > 0) {
|
|
127
|
+
// Scheme requires ≥2 chars so Windows drive letters
|
|
128
|
+
// (`C:/path`) don't mis-detect as the scheme `C`. Real URI
|
|
129
|
+
// schemes (spotify, vscode, http, file, etc.) are all
|
|
130
|
+
// multi-char by RFC.
|
|
131
|
+
const m = first.match(/^([A-Za-z][A-Za-z0-9+.-]+):/);
|
|
132
|
+
if (m)
|
|
133
|
+
return m[1]; // 'spotify:track/...' → 'spotify'
|
|
134
|
+
return first; // No protocol — surface the raw arg
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return appRaw;
|
|
138
|
+
},
|
|
139
|
+
// Clipboard write — the actual text being copied is the meaningful
|
|
140
|
+
// target. Reads have no args worth showing (empty schema).
|
|
141
|
+
clipboard_write: 'text',
|
|
142
|
+
clipboard_read: '',
|
|
103
143
|
};
|
|
104
144
|
/**
|
|
105
145
|
* Maximum visible characters for the preview value. Long commands /
|
|
@@ -121,28 +161,47 @@ function buildToolPreview(toolName, args) {
|
|
|
121
161
|
if (!Object.prototype.hasOwnProperty.call(exports.TOOL_PRIMARY_ARG, toolName)) {
|
|
122
162
|
return null;
|
|
123
163
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return '';
|
|
129
|
-
const raw = args[argKey];
|
|
130
|
-
if (raw === undefined || raw === null)
|
|
131
|
-
return '';
|
|
164
|
+
const extractor = exports.TOOL_PRIMARY_ARG[toolName];
|
|
165
|
+
// v4.1.4 Phase 3b' (Issue H1): function extractor path. Used by
|
|
166
|
+
// tools whose preview can't be expressed as a single key lookup
|
|
167
|
+
// (e.g. app_launch with URI-protocol routing through explorer.exe).
|
|
132
168
|
let str;
|
|
133
|
-
if (typeof
|
|
134
|
-
str = raw;
|
|
135
|
-
}
|
|
136
|
-
else if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
137
|
-
str = String(raw);
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
169
|
+
if (typeof extractor === 'function') {
|
|
140
170
|
try {
|
|
141
|
-
|
|
171
|
+
const out = extractor(args);
|
|
172
|
+
str = typeof out === 'string' ? out : '';
|
|
142
173
|
}
|
|
143
174
|
catch {
|
|
175
|
+
// Extractor threw — degrade to empty preview rather than crash
|
|
176
|
+
// the tool-row render. The tool name + state cluster still
|
|
177
|
+
// carries enough info.
|
|
178
|
+
str = '';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// String-key path (legacy, unchanged behaviour).
|
|
183
|
+
const argKey = extractor;
|
|
184
|
+
if (argKey === '')
|
|
185
|
+
return '';
|
|
186
|
+
if (!args || typeof args !== 'object')
|
|
187
|
+
return '';
|
|
188
|
+
const raw = args[argKey];
|
|
189
|
+
if (raw === undefined || raw === null)
|
|
190
|
+
return '';
|
|
191
|
+
if (typeof raw === 'string') {
|
|
192
|
+
str = raw;
|
|
193
|
+
}
|
|
194
|
+
else if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
144
195
|
str = String(raw);
|
|
145
196
|
}
|
|
197
|
+
else {
|
|
198
|
+
try {
|
|
199
|
+
str = JSON.stringify(raw);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
str = String(raw);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
146
205
|
}
|
|
147
206
|
// Collapse whitespace so multi-line commands stay on one preview row.
|
|
148
207
|
str = str.replace(/\s+/g, ' ').trim();
|
|
@@ -1388,7 +1388,13 @@ exports.TOOLS = {
|
|
|
1388
1388
|
return { success: false, output: '', error: `No research results for: ${topic}` };
|
|
1389
1389
|
}
|
|
1390
1390
|
const combined = results.join('\n\n');
|
|
1391
|
-
|
|
1391
|
+
// v4.1.5 Issue O — gated behind AIDEN_DEBUG_WEB to match the
|
|
1392
|
+
// webSearch.ts debug-helper convention. Default off; power users
|
|
1393
|
+
// export the env var to see the research chain.
|
|
1394
|
+
if (process.env.AIDEN_DEBUG_WEB === '1') {
|
|
1395
|
+
// eslint-disable-next-line no-console
|
|
1396
|
+
console.log(`[deep_research] Complete: ${combined.length} chars across ${results.length} passes`);
|
|
1397
|
+
}
|
|
1392
1398
|
return { success: true, output: combined.slice(0, 15000) };
|
|
1393
1399
|
},
|
|
1394
1400
|
// Activate a specialist agent persona — actual synthesis happens in respond phase
|
|
@@ -103,6 +103,10 @@ class AidenAgent {
|
|
|
103
103
|
this.onCompression = opts.onCompression;
|
|
104
104
|
this.refreshMemorySnapshot = opts.refreshMemorySnapshot;
|
|
105
105
|
this.onMemoryRefresh = opts.onMemoryRefresh;
|
|
106
|
+
// v4.1.5 Issue K — phase hooks (all optional, fire defensively).
|
|
107
|
+
this.onMemoryRefreshStart = opts.onMemoryRefreshStart;
|
|
108
|
+
this.onPromptBuilt = opts.onPromptBuilt;
|
|
109
|
+
this.onProviderRequestStart = opts.onProviderRequestStart;
|
|
106
110
|
this.lookupSkillRequiredTools = opts.lookupSkillRequiredTools;
|
|
107
111
|
// Phase v4.1.2-slice3: optional health registry (constructor-
|
|
108
112
|
// injected per the slice3 decision tree — no singleton). When
|
|
@@ -386,6 +390,14 @@ class AidenAgent {
|
|
|
386
390
|
// / 'user' need a snapshot refresh first.
|
|
387
391
|
const needsSnapshot = this.memoryDirty.has('memory') || this.memoryDirty.has('user');
|
|
388
392
|
if (needsSnapshot && this.refreshMemorySnapshot) {
|
|
393
|
+
// v4.1.5 Issue K — fire BEFORE the file I/O so the display layer
|
|
394
|
+
// can switch the activity verb to "refreshing memory" while the
|
|
395
|
+
// read is in flight. Defensive try/catch so a misbehaving hook
|
|
396
|
+
// never blocks the refresh.
|
|
397
|
+
try {
|
|
398
|
+
this.onMemoryRefreshStart?.();
|
|
399
|
+
}
|
|
400
|
+
catch { /* defensive */ }
|
|
389
401
|
let snapshot;
|
|
390
402
|
try {
|
|
391
403
|
snapshot = await this.refreshMemorySnapshot();
|
|
@@ -410,6 +422,21 @@ class AidenAgent {
|
|
|
410
422
|
if (this.cachedSystemPrompt !== null)
|
|
411
423
|
return this.cachedSystemPrompt;
|
|
412
424
|
this.cachedSystemPrompt = await this.promptBuilder.build(this.promptBuilderOptions);
|
|
425
|
+
// v4.1.5 Issue K — fire AFTER the prompt has been assembled, with
|
|
426
|
+
// cardinality so the display layer can surface "preparing prompt:
|
|
427
|
+
// N tools, M skills" or similar. Only fires when the cache MISSED
|
|
428
|
+
// (which is what made us actually build); cached returns skip the
|
|
429
|
+
// hook because nothing was prepared this turn. Defensive try/catch.
|
|
430
|
+
if (this.onPromptBuilt) {
|
|
431
|
+
try {
|
|
432
|
+
this.onPromptBuilt({
|
|
433
|
+
tools: this.tools.length,
|
|
434
|
+
skills: this.promptBuilderOptions.skillsList?.length ?? 0,
|
|
435
|
+
memoryFacts: countMemoryFacts(this.promptBuilderOptions.memorySnapshot),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch { /* defensive */ }
|
|
439
|
+
}
|
|
413
440
|
return this.cachedSystemPrompt;
|
|
414
441
|
}
|
|
415
442
|
async narrowTools(userMsg, history) {
|
|
@@ -629,6 +656,18 @@ class AidenAgent {
|
|
|
629
656
|
*/
|
|
630
657
|
async callProvider(messages, tools, runOptions) {
|
|
631
658
|
const wantStream = runOptions.stream === true && typeof this.provider.callStream === 'function';
|
|
659
|
+
// v4.1.5 Issue K — fire just before the HTTP request opens, so the
|
|
660
|
+
// display layer can transition the activity verb from local-prep
|
|
661
|
+
// ("preparing prompt", "selecting tools") to a network verb
|
|
662
|
+
// ("calling provider"). The wait for TTFT (time-to-first-token) is
|
|
663
|
+
// the longest gap in most turns and is what the wave bar covers.
|
|
664
|
+
// Fires for both streaming and non-streaming paths — caller may use
|
|
665
|
+
// it to add a one-shot indicator on non-streaming providers too.
|
|
666
|
+
// Defensive try/catch (a misbehaving hook must not block dispatch).
|
|
667
|
+
try {
|
|
668
|
+
this.onProviderRequestStart?.(this.providerId);
|
|
669
|
+
}
|
|
670
|
+
catch { /* defensive */ }
|
|
632
671
|
if (!wantStream) {
|
|
633
672
|
return this.provider.call({ messages, tools });
|
|
634
673
|
}
|
|
@@ -650,6 +689,15 @@ class AidenAgent {
|
|
|
650
689
|
else if (evt.type === 'tool_call') {
|
|
651
690
|
runOptions.onToolCallStart?.(evt.toolCall);
|
|
652
691
|
}
|
|
692
|
+
else if (evt.type === 'progress') {
|
|
693
|
+
// v4.1.4 Part 1.6 — drive the per-turn token progress bar.
|
|
694
|
+
// Defensive try/catch — a misbehaving display sink must not
|
|
695
|
+
// tear down the stream consumer.
|
|
696
|
+
try {
|
|
697
|
+
runOptions.onProgress?.(evt.outputTokens, evt.maxTokens);
|
|
698
|
+
}
|
|
699
|
+
catch { /* progress sink errors don't block streaming */ }
|
|
700
|
+
}
|
|
653
701
|
else if (evt.type === 'done') {
|
|
654
702
|
finalOutput = evt.output;
|
|
655
703
|
}
|
|
@@ -662,6 +710,30 @@ class AidenAgent {
|
|
|
662
710
|
}
|
|
663
711
|
exports.AidenAgent = AidenAgent;
|
|
664
712
|
// ── Free helpers ────────────────────────────────────────────────────────
|
|
713
|
+
/**
|
|
714
|
+
* v4.1.5 Issue K — best-effort count of "memory facts" from a
|
|
715
|
+
* MemorySnapshot. Counts markdown bullet-list lines (`- `) in both
|
|
716
|
+
* MEMORY.md and USER.md. This is a fuzzy proxy — the agent stores
|
|
717
|
+
* facts as bullets by convention but free-form prose can also carry
|
|
718
|
+
* fact-like content. Surfaced verbatim to the display layer; treat as
|
|
719
|
+
* "approximately N items in the persistent memory file" rather than
|
|
720
|
+
* a precise inventory.
|
|
721
|
+
*/
|
|
722
|
+
function countMemoryFacts(snapshot) {
|
|
723
|
+
if (!snapshot || typeof snapshot !== 'object')
|
|
724
|
+
return 0;
|
|
725
|
+
const s = snapshot;
|
|
726
|
+
let count = 0;
|
|
727
|
+
for (const md of [s.memoryMd, s.userMd]) {
|
|
728
|
+
if (typeof md !== 'string' || md.length === 0)
|
|
729
|
+
continue;
|
|
730
|
+
for (const line of md.split('\n')) {
|
|
731
|
+
if (line.trim().startsWith('- '))
|
|
732
|
+
count += 1;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return count;
|
|
736
|
+
}
|
|
665
737
|
function lastUserMessageContent(history) {
|
|
666
738
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
667
739
|
const m = history[i];
|