aiden-runtime 4.1.1 → 4.1.3
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 +78 -26
- package/dist/cli/v4/aidenCLI.js +169 -9
- package/dist/cli/v4/callbacks.js +20 -2
- package/dist/cli/v4/chatSession.js +644 -16
- package/dist/cli/v4/commands/auth.js +6 -3
- package/dist/cli/v4/commands/doctor.js +23 -27
- package/dist/cli/v4/commands/help.js +4 -0
- package/dist/cli/v4/commands/index.js +10 -1
- package/dist/cli/v4/commands/model.js +30 -1
- package/dist/cli/v4/commands/reloadSoul.js +37 -0
- package/dist/cli/v4/commands/update.js +102 -0
- package/dist/cli/v4/defaultSoul.js +68 -2
- package/dist/cli/v4/display/capabilityCard.js +135 -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 +492 -142
- package/dist/cli/v4/doctor.js +472 -58
- package/dist/cli/v4/doctorLiveness.js +65 -10
- package/dist/cli/v4/promotionPrompt.js +332 -0
- package/dist/cli/v4/providerBootSelector.js +144 -0
- package/dist/cli/v4/replyRenderer.js +311 -20
- package/dist/cli/v4/sessionSummaryGate.js +66 -0
- package/dist/cli/v4/skinEngine.js +14 -3
- package/dist/cli/v4/toolPreview.js +153 -0
- package/dist/core/tools/nowPlaying.js +7 -15
- package/dist/core/v4/aidenAgent.js +91 -29
- package/dist/core/v4/capabilities.js +89 -0
- package/dist/core/v4/contextCompressor.js +25 -8
- package/dist/core/v4/distillationIndex.js +167 -0
- package/dist/core/v4/distillationStore.js +98 -0
- package/dist/core/v4/logger/logger.js +40 -9
- package/dist/core/v4/promotionCandidates.js +234 -0
- package/dist/core/v4/promptBuilder.js +145 -1
- package/dist/core/v4/sessionDistiller.js +452 -0
- package/dist/core/v4/skillMining/skillMiner.js +43 -6
- package/dist/core/v4/skillOutcomeTracker.js +323 -0
- package/dist/core/v4/subsystemHealth.js +143 -0
- package/dist/core/v4/toolRegistry.js +16 -1
- package/dist/core/v4/update/executeInstall.js +233 -0
- package/dist/core/version.js +1 -1
- package/dist/moat/memoryGuard.js +111 -0
- package/dist/moat/plannerGuard.js +19 -0
- package/dist/moat/skillTeacher.js +14 -5
- package/dist/providers/v4/chatCompletionsAdapter.js +9 -0
- package/dist/providers/v4/errors.js +112 -4
- package/dist/providers/v4/modelDefaults.js +65 -0
- package/dist/providers/v4/registry.js +9 -2
- package/dist/providers/v4/runtimeResolver.js +6 -0
- package/dist/tools/v4/index.js +80 -1
- package/dist/tools/v4/memory/memoryRemove.js +57 -2
- package/dist/tools/v4/memory/sessionSummary.js +151 -0
- package/dist/tools/v4/sessions/recallSession.js +177 -0
- package/dist/tools/v4/sessions/sessionSearch.js +5 -1
- package/dist/tools/v4/system/_psHelpers.js +123 -0
- package/dist/tools/v4/system/aidenSelfUpdate.js +162 -0
- package/dist/tools/v4/system/appClose.js +79 -0
- package/dist/tools/v4/system/appInput.js +154 -0
- package/dist/tools/v4/system/appLaunch.js +218 -0
- package/dist/tools/v4/system/clipboardRead.js +54 -0
- package/dist/tools/v4/system/clipboardWrite.js +84 -0
- package/dist/tools/v4/system/mediaKey.js +109 -0
- package/dist/tools/v4/system/mediaSessions.js +163 -0
- package/dist/tools/v4/system/mediaTransport.js +211 -0
- package/dist/tools/v4/system/osProcessList.js +99 -0
- package/dist/tools/v4/system/screenshot.js +106 -0
- package/dist/tools/v4/system/volumeSet.js +157 -0
- package/package.json +4 -1
- package/skills/system_control.md +185 -69
|
@@ -36,6 +36,48 @@ const TerminalRenderer = require('marked-terminal').default ?? require('marked-t
|
|
|
36
36
|
function paint(kind) {
|
|
37
37
|
return (text) => (0, skinEngine_1.getSkinEngine)().applyColors(text, kind);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* v4.1.3-essentials: bold (`**foo**`) markdown emphasis renders as
|
|
41
|
+
* ANSI bold + underline. Previously painted 'brand' (orange) which
|
|
42
|
+
* collided with the heading hierarchy. Briefly tried bold + bright-
|
|
43
|
+
* white; landed on bold + underline because underline carries
|
|
44
|
+
* emphasis without consuming a color slot — the palette stays
|
|
45
|
+
* available for state semantics (yellow=degraded, red=error, etc.).
|
|
46
|
+
*
|
|
47
|
+
* ANSI sequence: `\x1b[1m\x1b[4m{text}\x1b[24m\x1b[22m` — bold ON +
|
|
48
|
+
* underline ON, then underline OFF + bold OFF. Reset order matters
|
|
49
|
+
* (underline first, bold second) so the closing codes don't reorder
|
|
50
|
+
* styles surprisingly on terminals that batch SGR updates.
|
|
51
|
+
*
|
|
52
|
+
* Bypasses the skin system intentionally — bold-as-underline is an
|
|
53
|
+
* opinionated default for this slice. Same caveat as the prior bold-
|
|
54
|
+
* as-color iteration: nested markdown loses the outer style after
|
|
55
|
+
* close (pre-existing limitation of the painter-stack architecture).
|
|
56
|
+
*
|
|
57
|
+
* Honors `NO_COLOR=1` per the standard (skips the wrap entirely).
|
|
58
|
+
* Strictly speaking `NO_COLOR` is about color and underline isn't
|
|
59
|
+
* a color, but the wrap still emits ANSI escapes; honoring the env
|
|
60
|
+
* var keeps output paste-safe in scripted contexts.
|
|
61
|
+
*/
|
|
62
|
+
function paintBoldUnderline(text) {
|
|
63
|
+
if (process.env.NO_COLOR && process.env.NO_COLOR !== '')
|
|
64
|
+
return text;
|
|
65
|
+
return `\x1b[1m\x1b[4m${text}\x1b[24m\x1b[22m`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* v4.1.3-essentials reply-polish: bold-on + skin paint + bold-off.
|
|
69
|
+
* Used by the 4-tier heading hierarchy so each level can pick its own
|
|
70
|
+
* color while sharing the bold weight. Emit order matches the rest of
|
|
71
|
+
* the painter stack: outer wrap is bold, inner wrap is fg color.
|
|
72
|
+
*
|
|
73
|
+
* Honors `NO_COLOR=1` via the skin engine's own gate; the bold ANSI
|
|
74
|
+
* still emits because bold is a weight, not a color (matches the
|
|
75
|
+
* paintBoldUnderline convention for `**bold**`).
|
|
76
|
+
*/
|
|
77
|
+
function paintBold(kind) {
|
|
78
|
+
const colorize = paint(kind);
|
|
79
|
+
return (text) => `\x1b[1m${colorize(text)}\x1b[22m`;
|
|
80
|
+
}
|
|
39
81
|
/**
|
|
40
82
|
* Render a fenced code block: top divider with language label, body
|
|
41
83
|
* with optional syntax highlighting, bottom divider.
|
|
@@ -51,10 +93,37 @@ function paint(kind) {
|
|
|
51
93
|
* the renderer with; the older positional path is kept for
|
|
52
94
|
* compatibility.
|
|
53
95
|
*/
|
|
96
|
+
/**
|
|
97
|
+
* v4.1.3-essentials reply-polish: 24-bit dark background applied per
|
|
98
|
+
* line so code stands out from prose.
|
|
99
|
+
*
|
|
100
|
+
* Color choice: `\x1b[48;2;50;50;60m` (#32323c, slightly bluish dark
|
|
101
|
+
* grey). The original `30,30,30` (#1e1e1e) was invisible against VS
|
|
102
|
+
* Code's integrated terminal default (also #1e1e1e) and barely
|
|
103
|
+
* distinct from Windows Terminal's One Half Dark. #32323c is visibly
|
|
104
|
+
* different from every common dark-terminal default (Campbell, One
|
|
105
|
+
* Half Dark, Solarized Dark, Monokai, VS Code) while staying subtle
|
|
106
|
+
* enough to read as "code chrome" rather than a jarring highlight.
|
|
107
|
+
*
|
|
108
|
+
* Used by BOTH the block path (fenced code blocks) and the inline
|
|
109
|
+
* path (`` `code` `` spans) so the two affordances visually agree —
|
|
110
|
+
* inline code reads as "this is code" via the same chrome as block
|
|
111
|
+
* code, just shorter.
|
|
112
|
+
*
|
|
113
|
+
* NOTE: \x1b[49m is "default background", terminating the per-line
|
|
114
|
+
* background scope cleanly. Each body line is wrapped individually
|
|
115
|
+
* rather than wrapping the whole block, so the background doesn't
|
|
116
|
+
* bleed across the closing horizontal rule (which already paints fg
|
|
117
|
+
* muted with its own reset).
|
|
118
|
+
*/
|
|
119
|
+
const CODE_BG_ON = '\x1b[48;2;50;50;60m';
|
|
120
|
+
const CODE_BG_OFF = '\x1b[49m';
|
|
54
121
|
function renderCodeBlock(code, lang) {
|
|
55
122
|
const sk = (0, skinEngine_1.getSkinEngine)();
|
|
56
123
|
const width = Math.min(process.stdout.columns ?? 80, 100) - 4;
|
|
57
124
|
const langLabel = (lang ?? '').trim();
|
|
125
|
+
// v4.1.3-essentials reply-polish: language tag on the top rule
|
|
126
|
+
// already shipped; keep it. Bottom rule unlabeled (closing fence).
|
|
58
127
|
const top = langLabel
|
|
59
128
|
? `── ${langLabel} ${'─'.repeat(Math.max(0, width - langLabel.length - 4))}`
|
|
60
129
|
: '─'.repeat(width);
|
|
@@ -62,7 +131,22 @@ function renderCodeBlock(code, lang) {
|
|
|
62
131
|
const body = (0, syntaxHighlight_1.isSupportedLang)(langLabel)
|
|
63
132
|
? (0, syntaxHighlight_1.highlightCode)(code, langLabel)
|
|
64
133
|
: code;
|
|
65
|
-
|
|
134
|
+
// v4.1.3-essentials reply-polish: each body line gets:
|
|
135
|
+
// - 2-space outer indent (existing reply container indent)
|
|
136
|
+
// - left rail `│ ` painted muted (mirrors blockquote's `┃ ` rail
|
|
137
|
+
// with a different glyph so they're visually distinct)
|
|
138
|
+
// - 24-bit dark background wrapping the rail + content (subtle
|
|
139
|
+
// "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
|
+
const rail = sk.applyColors('│', 'muted');
|
|
146
|
+
const indented = body
|
|
147
|
+
.split('\n')
|
|
148
|
+
.map((ln) => ` ${rail} ${CODE_BG_ON} ${ln} ${CODE_BG_OFF}`)
|
|
149
|
+
.join('\n');
|
|
66
150
|
return [
|
|
67
151
|
sk.applyColors(top, 'muted'),
|
|
68
152
|
indented,
|
|
@@ -82,26 +166,61 @@ function renderBlockquote(quote) {
|
|
|
82
166
|
.join('\n') + '\n';
|
|
83
167
|
}
|
|
84
168
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
169
|
+
* v4.1.3-essentials reply-polish: 4-tier heading hierarchy using the
|
|
170
|
+
* existing palette colors so visual weight differs per level even
|
|
171
|
+
* though we don't introduce a new ColorKind.
|
|
172
|
+
*
|
|
173
|
+
* H1 — brand + bold + UPPERCASE (major section heading)
|
|
174
|
+
* H2 — brand + bold (subsection — same hue as H1
|
|
175
|
+
* but sentence-case + no caps)
|
|
176
|
+
* H3 — agent + bold (off-white, lighter weight
|
|
177
|
+
* than brand)
|
|
178
|
+
* H4+ — muted + bold (quietest — same grey as the
|
|
179
|
+
* reply container's chrome)
|
|
180
|
+
*
|
|
181
|
+
* v4.1.3-essentials reply-polish: spacing tightened from `\n\n` to
|
|
182
|
+
* `\n` per level. marked-terminal contributes its own block
|
|
183
|
+
* separator (one more newline) → total `\n\n` between heading and
|
|
184
|
+
* next block = single blank line, matching paragraph rhythm.
|
|
185
|
+
* Previously this emitted `\n\n\n\n` (three blank lines) which made
|
|
186
|
+
* structured replies feel cramped at top and over-aired between
|
|
187
|
+
* sections.
|
|
87
188
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
189
|
+
// 4-tier hierarchy. Called by the prototype-level `heading` override
|
|
190
|
+
// in getReplyRenderer() which extracts depth from the token first.
|
|
191
|
+
// Plain `(text, depth)` signature; the marked v15 / v14 / positional
|
|
192
|
+
// translation happens in the override.
|
|
193
|
+
//
|
|
194
|
+
// Each tier ends with `\n\n` to fence the heading from the next block
|
|
195
|
+
// with a blank line. Earlier we tried `\n` (single trailing newline)
|
|
196
|
+
// assuming marked-terminal's `section()` wrapper added its own
|
|
197
|
+
// padding — but the prototype-level override bypasses section(), so
|
|
198
|
+
// we own the spacing end-to-end. Result with `\n\n`: heading visible
|
|
199
|
+
// on its own line, blank line separates it from the next paragraph /
|
|
200
|
+
// heading / list. Matches the paragraph rhythm (`text\n\n`).
|
|
201
|
+
function renderHeading(text, depth) {
|
|
202
|
+
if (depth <= 1)
|
|
203
|
+
return paintBold('brand')(text.toUpperCase()) + '\n\n';
|
|
204
|
+
if (depth === 2)
|
|
205
|
+
return paintBold('brand')(text) + '\n\n';
|
|
206
|
+
if (depth === 3)
|
|
207
|
+
return paintBold('agent')(text) + '\n\n';
|
|
208
|
+
return paintBold('muted')(text) + '\n\n';
|
|
94
209
|
}
|
|
95
210
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
211
|
+
* v4.1.3-essentials reply-polish: the `opts.listitem` callback used to
|
|
212
|
+
* own bullet rendering but marked-terminal's outer `list` method
|
|
213
|
+
* ALSO emits a `* ` prefix, producing visible double bullets
|
|
214
|
+
* (` * ▸ item`). The fix is a prototype-level override on BOTH
|
|
215
|
+
* `list` and `listitem` (mirrors the existing pattern for `code` and
|
|
216
|
+
* `link`). See the override block in getReplyRenderer().
|
|
217
|
+
*
|
|
218
|
+
* This callback now just returns the inner text unchanged so the
|
|
219
|
+
* prototype-level `list` override can do the bullet + indent work
|
|
220
|
+
* with full nesting-depth context.
|
|
99
221
|
*/
|
|
100
222
|
function renderListItem(text) {
|
|
101
|
-
|
|
102
|
-
// default tab prefix so our two-space indent stays consistent.
|
|
103
|
-
const body = text.replace(/^\s+/, '');
|
|
104
|
-
return ` ${paint('muted')('▸')} ${body}\n`;
|
|
223
|
+
return text;
|
|
105
224
|
}
|
|
106
225
|
/**
|
|
107
226
|
* Singleton — caching is fine since options bind to the active skin
|
|
@@ -121,14 +240,33 @@ function getReplyRenderer() {
|
|
|
121
240
|
// method directly below.
|
|
122
241
|
const opts = {
|
|
123
242
|
blockquote: renderBlockquote,
|
|
124
|
-
|
|
125
|
-
|
|
243
|
+
// v4.1.3-essentials reply-polish: `opts.heading` and `opts.firstHeading`
|
|
244
|
+
// both removed. marked-terminal calls `opts.heading(text)` with ONLY
|
|
245
|
+
// text (audit-confirmed via toString), dropping the depth info we
|
|
246
|
+
// need for the 4-tier hierarchy. The prototype-level `renderer.heading`
|
|
247
|
+
// override below owns the depth extraction + tier selection end-to-end.
|
|
248
|
+
// marked-terminal's stripped-args call path never reaches our callback.
|
|
126
249
|
hr: () => paint('muted')('─'.repeat(Math.min(process.stdout.columns ?? 80, 100) - 4)) + '\n',
|
|
127
250
|
listitem: renderListItem,
|
|
128
251
|
paragraph: (text) => `${text}\n\n`,
|
|
129
|
-
|
|
252
|
+
// v4.1.3-essentials: bold renders as ANSI bold + underline
|
|
253
|
+
// (was 'brand' / orange, then bright-white; landed on underline
|
|
254
|
+
// so the color palette stays available for state semantics).
|
|
255
|
+
strong: paintBoldUnderline,
|
|
130
256
|
em: paint('muted'),
|
|
131
|
-
|
|
257
|
+
// v4.1.3-essentials reply-polish: inline `` `code` `` — strip
|
|
258
|
+
// the literal backticks (used to leak into the visible output)
|
|
259
|
+
// and wrap with the same dark background as fenced code blocks.
|
|
260
|
+
// Visual consistency: inline code reads as "this is code" via the
|
|
261
|
+
// same chrome as block code, just shorter. One leading + trailing
|
|
262
|
+
// space inside the bg span gives the chrome a bit of padding so
|
|
263
|
+
// letters don't sit flush against the bg edge.
|
|
264
|
+
//
|
|
265
|
+
// Trade-off (accepted): if an inline-code span breaks across a
|
|
266
|
+
// line wrap, the bg painting may show a visual seam at the wrap
|
|
267
|
+
// point. Acceptable for v4.1.3 — revertable to Path A (no bg) if
|
|
268
|
+
// visual smoke surfaces a real problem.
|
|
269
|
+
codespan: (text) => `${CODE_BG_ON} ${paint('accent')(text)} ${CODE_BG_OFF}`,
|
|
132
270
|
del: paint('muted'),
|
|
133
271
|
// marked-terminal calls opts.link with the ASSEMBLED visual
|
|
134
272
|
// (already OSC8-wrapped when the host terminal supports it),
|
|
@@ -185,6 +323,159 @@ function getReplyRenderer() {
|
|
|
185
323
|
const painted = paint('accent')(label);
|
|
186
324
|
return `\x1b]8;;${url}\x1b\\${painted}\x1b]8;;\x1b\\`;
|
|
187
325
|
};
|
|
326
|
+
// v4.1.3-essentials reply-polish: prototype-level `heading` override.
|
|
327
|
+
//
|
|
328
|
+
// Why: marked-terminal's internal `heading` method extracts the
|
|
329
|
+
// token's depth, then calls `opts.heading(text)` with ONLY the
|
|
330
|
+
// text — dropping the level info on the floor. Our 4-tier hierarchy
|
|
331
|
+
// (H1 brand+caps, H2 brand, H3 agent, H4+ muted) needs level
|
|
332
|
+
// context, so we must own the whole method.
|
|
333
|
+
//
|
|
334
|
+
// The override accepts marked v15's token-object shape and falls
|
|
335
|
+
// through to v14 positional for unit tests that pass plain strings.
|
|
336
|
+
renderer.heading = function (textOrToken, levelArg, _raw) {
|
|
337
|
+
let text;
|
|
338
|
+
let depth;
|
|
339
|
+
if (typeof textOrToken === 'object' && textOrToken !== null) {
|
|
340
|
+
const tok = textOrToken;
|
|
341
|
+
depth = typeof tok.depth === 'number' ? tok.depth : 1;
|
|
342
|
+
// Prefer parseInline for rich heading content (e.g. `## H2 with **bold**`).
|
|
343
|
+
// Falls through to tok.text for the common plain-text case.
|
|
344
|
+
const parser = this.parser;
|
|
345
|
+
if (tok.tokens && parser?.parseInline) {
|
|
346
|
+
text = parser.parseInline(tok.tokens);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
text = String(tok.text ?? '');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
text = String(textOrToken ?? '');
|
|
354
|
+
depth = typeof levelArg === 'number' ? levelArg : 1;
|
|
355
|
+
}
|
|
356
|
+
return renderHeading(text, depth);
|
|
357
|
+
};
|
|
358
|
+
// v4.1.3-essentials reply-polish: prototype-level list overrides.
|
|
359
|
+
//
|
|
360
|
+
// Why two functions and a depth counter:
|
|
361
|
+
// - marked-terminal's default `list` injects a `* ` (or `N. `)
|
|
362
|
+
// prefix BEFORE calling our `opts.listitem` callback, producing
|
|
363
|
+
// visible double bullets — see audit. Owning `list` at the
|
|
364
|
+
// prototype level lets us suppress that and emit our own.
|
|
365
|
+
// - Nesting depth determines the bullet glyph: top-level gets `•`
|
|
366
|
+
// and any deeper level gets `▸`. marked doesn't pass depth to
|
|
367
|
+
// the renderer, so we track it on the renderer instance via a
|
|
368
|
+
// counter that increments on `list`-enter and decrements on
|
|
369
|
+
// exit. This works because marked walks the token tree
|
|
370
|
+
// synchronously: a nested list's `list` call always completes
|
|
371
|
+
// between its parent's `list`-enter and `list`-exit.
|
|
372
|
+
// - Items already had their child markdown rendered via the
|
|
373
|
+
// prototype's `listitem` (which we leave as a passthrough above
|
|
374
|
+
// in the opts block — it just returns the inner text). The
|
|
375
|
+
// body string we receive in `list` is the concatenated children;
|
|
376
|
+
// each child can itself be a nested list rendering, whose own
|
|
377
|
+
// `list` call already handled its bullets + indent.
|
|
378
|
+
//
|
|
379
|
+
// Numbered lists: `start` and `ordered` come from the token; we
|
|
380
|
+
// emit `N.` prefix in muted to keep the visual rhythm consistent
|
|
381
|
+
// with bulleted lists but preserve numeric semantics.
|
|
382
|
+
//
|
|
383
|
+
// Indent: 2 spaces per nesting level. Top-level items therefore
|
|
384
|
+
// sit at column 2 (matching the rest of the reply container's
|
|
385
|
+
// chrome); nested at column 4, 6, etc.
|
|
386
|
+
const proto = renderer;
|
|
387
|
+
proto._listDepth = 0;
|
|
388
|
+
renderer.listitem = function (text, _task, _checked) {
|
|
389
|
+
// marked v15 may pass a token object; the assembled-text fallback
|
|
390
|
+
// covers older signatures. Either way we want the inner text
|
|
391
|
+
// unchanged here — bullet + indent is owned by `list` below.
|
|
392
|
+
if (typeof text === 'object' && text !== null) {
|
|
393
|
+
const tok = text;
|
|
394
|
+
if (typeof tok.text === 'string')
|
|
395
|
+
return tok.text;
|
|
396
|
+
const parser = this.parser;
|
|
397
|
+
return parser?.parseInline?.(tok.tokens ?? []) ?? '';
|
|
398
|
+
}
|
|
399
|
+
return String(text ?? '');
|
|
400
|
+
};
|
|
401
|
+
renderer.list = function (body, ordered, start) {
|
|
402
|
+
// marked v15 token shape: { ordered, start, items: [token, ...] }
|
|
403
|
+
// Older positional shape: (body, ordered, start)
|
|
404
|
+
let isOrdered = false;
|
|
405
|
+
let startNum = 1;
|
|
406
|
+
let items;
|
|
407
|
+
// CRITICAL: increment depth BEFORE walking items. Item walking via
|
|
408
|
+
// `parser.parse(it.tokens)` recurses into our own override for any
|
|
409
|
+
// nested list tokens — those nested calls need to see the parent's
|
|
410
|
+
// incremented depth so they pick the deeper bullet glyph (▸) and
|
|
411
|
+
// indent. If we increment AFTER `parser.parse`, the nested call
|
|
412
|
+
// sees depth=0, renders at top-level styling, and the visible
|
|
413
|
+
// nesting collapses. Confirmed via runtime trace.
|
|
414
|
+
proto._listDepth = (proto._listDepth ?? 0) + 1;
|
|
415
|
+
const depth = proto._listDepth;
|
|
416
|
+
if (typeof body === 'object' && body !== null) {
|
|
417
|
+
const tok = body;
|
|
418
|
+
isOrdered = tok.ordered === true;
|
|
419
|
+
startNum = typeof tok.start === 'number' ? tok.start : 1;
|
|
420
|
+
// marked v15: renderer instance has a `parser` field pointing
|
|
421
|
+
// back to the Parser; `Parser.parse(tokens)` walks the token
|
|
422
|
+
// tree dispatching back to renderer methods (including this
|
|
423
|
+
// very `list` override for nested lists, which is what makes
|
|
424
|
+
// the depth counter increment properly).
|
|
425
|
+
const parser = this.parser;
|
|
426
|
+
items = (tok.items ?? []).map((it) => {
|
|
427
|
+
if (it.tokens && parser?.parse) {
|
|
428
|
+
return parser.parse(it.tokens);
|
|
429
|
+
}
|
|
430
|
+
return it.text ?? '';
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
isOrdered = ordered === true;
|
|
435
|
+
startNum = typeof start === 'number' ? start : 1;
|
|
436
|
+
// Positional `body` is the already-concatenated rendered items.
|
|
437
|
+
// Split on newlines that introduce a fresh item; marked emits
|
|
438
|
+
// each item as its own logical line. Best-effort — marked v15
|
|
439
|
+
// path above is the production case.
|
|
440
|
+
const raw = String(body ?? '');
|
|
441
|
+
items = raw.split('\n').filter((ln) => ln.trim().length > 0);
|
|
442
|
+
}
|
|
443
|
+
const indent = ' '.repeat(depth);
|
|
444
|
+
// Top-level bullet `•` (filled); nested `▸` (arrow-like) for
|
|
445
|
+
// visual depth differentiation. Numbered lists override with
|
|
446
|
+
// `N.` regardless of depth.
|
|
447
|
+
const bulletGlyph = depth === 1 ? '•' : '▸';
|
|
448
|
+
const lines = [];
|
|
449
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
450
|
+
const item = items[i];
|
|
451
|
+
// Each item may itself contain newlines (nested list output,
|
|
452
|
+
// multi-line paragraph). Indent every line of the rendered
|
|
453
|
+
// item AFTER the first — the first line takes the bullet, the
|
|
454
|
+
// continuation lines align under the bullet's content column.
|
|
455
|
+
const marker = isOrdered
|
|
456
|
+
? paint('muted')(`${startNum + i}.`)
|
|
457
|
+
: paint('muted')(bulletGlyph);
|
|
458
|
+
const itemLines = item.split('\n');
|
|
459
|
+
const head = itemLines[0] ?? '';
|
|
460
|
+
const tail = itemLines.slice(1);
|
|
461
|
+
lines.push(`${indent}${marker} ${head}`);
|
|
462
|
+
// Continuation lines: if they already have content, align them
|
|
463
|
+
// under the bullet's text column (indent + marker-width + 1
|
|
464
|
+
// space). marked-terminal's nested lists arrive pre-indented so
|
|
465
|
+
// we pass them through.
|
|
466
|
+
for (const tailLine of tail) {
|
|
467
|
+
if (tailLine.length === 0)
|
|
468
|
+
continue;
|
|
469
|
+
lines.push(tailLine);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
proto._listDepth -= 1;
|
|
473
|
+
// Top-level list closes with a trailing newline to separate from
|
|
474
|
+
// the next block; nested lists return without extra padding so
|
|
475
|
+
// they nest cleanly inside their parent item.
|
|
476
|
+
const out = lines.join('\n');
|
|
477
|
+
return proto._listDepth === 0 ? out + '\n' : out + '\n';
|
|
478
|
+
};
|
|
188
479
|
cachedRenderer = {
|
|
189
480
|
render(text) {
|
|
190
481
|
try {
|
|
@@ -0,0 +1,66 @@
|
|
|
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/sessionSummaryGate.ts — Phase v4.1.2-followup-2.
|
|
10
|
+
*
|
|
11
|
+
* Pure decision helpers extracted from `ChatSession.maybeAutoSummarize`
|
|
12
|
+
* so the threshold + mtime/size-grew logic is unit-testable without
|
|
13
|
+
* standing up a full ChatSession + mocked agent loop.
|
|
14
|
+
*
|
|
15
|
+
* shouldAutoSummarize → returns {fire: true} or
|
|
16
|
+
* {fire: false, reason: 'short'|'unconfigured'|'no-paths'}.
|
|
17
|
+
* ChatSession uses the reason tag to log the right
|
|
18
|
+
* user-visible message.
|
|
19
|
+
*
|
|
20
|
+
* memoryGrewBetween → strict size-or-mtime comparison so the caller can
|
|
21
|
+
* detect "the model actually fired session_summary"
|
|
22
|
+
* even when the tool wrote without growing the file
|
|
23
|
+
* length (e.g. replaced a previous same-length entry).
|
|
24
|
+
*
|
|
25
|
+
* No I/O here. ChatSession owns the fs.stat + display.warn / display.dim.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.SESSION_SUMMARY_MIN_TURNS = void 0;
|
|
29
|
+
exports.shouldAutoSummarize = shouldAutoSummarize;
|
|
30
|
+
exports.memoryGrewBetween = memoryGrewBetween;
|
|
31
|
+
/** Minimum user-message turns required before auto-summary triggers. */
|
|
32
|
+
exports.SESSION_SUMMARY_MIN_TURNS = 3;
|
|
33
|
+
/**
|
|
34
|
+
* Decide whether the /quit auto-summary should fire. Threshold lives
|
|
35
|
+
* here as the single source of truth; ChatSession imports the constant
|
|
36
|
+
* so the user-facing log message ("need 3+") cites the exact value.
|
|
37
|
+
*/
|
|
38
|
+
function shouldAutoSummarize(input) {
|
|
39
|
+
if (input.userTurns < exports.SESSION_SUMMARY_MIN_TURNS) {
|
|
40
|
+
return { fire: false, reason: 'short' };
|
|
41
|
+
}
|
|
42
|
+
if (input.unconfigured) {
|
|
43
|
+
return { fire: false, reason: 'unconfigured' };
|
|
44
|
+
}
|
|
45
|
+
if (!input.memoryPath) {
|
|
46
|
+
return { fire: false, reason: 'no-paths' };
|
|
47
|
+
}
|
|
48
|
+
return { fire: true };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* True iff MEMORY.md grew (longer) or was touched (newer mtime) between
|
|
52
|
+
* the two snapshots. Used to detect whether the agent actually fired
|
|
53
|
+
* the session_summary tool inside the synthetic turn — if not, the
|
|
54
|
+
* user sees a warning instead of a misleading "saved" message.
|
|
55
|
+
*
|
|
56
|
+
* The size-OR-mtime disjunction (not just size>before) covers the case
|
|
57
|
+
* where session_summary replaces an existing same-length entry: file
|
|
58
|
+
* size stays the same but mtime advances.
|
|
59
|
+
*/
|
|
60
|
+
function memoryGrewBetween(before, after) {
|
|
61
|
+
if (after.size > before.size)
|
|
62
|
+
return true;
|
|
63
|
+
if (after.mtime > before.mtime)
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
@@ -49,10 +49,17 @@ const DEFAULT_SKIN = {
|
|
|
49
49
|
error: [0xf4, 0x47, 0x47],
|
|
50
50
|
warn: [0xff, 0xc1, 0x07],
|
|
51
51
|
success: [0x4c, 0xaf, 0x50],
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
muted: [
|
|
52
|
+
// v4.1.3-repl-polish: muted is now true grey (#888) so it reads as
|
|
53
|
+
// genuinely secondary. The soft-cyan that was here moved to 'session'.
|
|
54
|
+
muted: [0x88, 0x88, 0x88],
|
|
55
55
|
heading: BRAND_ORANGE,
|
|
56
|
+
// v4.1.3-repl-polish: session = soft cyan (ex-muted); used for IDs
|
|
57
|
+
// and the session-end card header labels.
|
|
58
|
+
session: [0x6f, 0xb3, 0xd2],
|
|
59
|
+
// v4.1.3-repl-polish: degraded = amber yellow; distinct from warn
|
|
60
|
+
// (which shares the colour) so callers can differentiate in code
|
|
61
|
+
// even though they render identically.
|
|
62
|
+
degraded: [0xff, 0xc1, 0x07],
|
|
56
63
|
},
|
|
57
64
|
glyphs: {
|
|
58
65
|
bullet: '•',
|
|
@@ -79,6 +86,8 @@ const LIGHT_SKIN = {
|
|
|
79
86
|
success: [0x1b, 0x5e, 0x20],
|
|
80
87
|
muted: [0x60, 0x60, 0x60],
|
|
81
88
|
heading: [0xc4, 0x42, 0x10],
|
|
89
|
+
session: [0x00, 0x55, 0x88],
|
|
90
|
+
degraded: [0x80, 0x60, 0x00],
|
|
82
91
|
},
|
|
83
92
|
glyphs: { ...DEFAULT_SKIN.glyphs },
|
|
84
93
|
};
|
|
@@ -96,6 +105,8 @@ const MONOCHROME_SKIN = {
|
|
|
96
105
|
success: null,
|
|
97
106
|
muted: null,
|
|
98
107
|
heading: null,
|
|
108
|
+
session: null,
|
|
109
|
+
degraded: null,
|
|
99
110
|
},
|
|
100
111
|
glyphs: {
|
|
101
112
|
bullet: '*',
|
|
@@ -0,0 +1,153 @@
|
|
|
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/toolPreview.ts — Phase v4.1.2 alive-core.
|
|
10
|
+
*
|
|
11
|
+
* Clean per-tool argument previews. Replaces the old
|
|
12
|
+
* `JSON.stringify(args)` blob in `display.toolPreview` with a
|
|
13
|
+
* tool-aware lookup that extracts the primary argument (the one
|
|
14
|
+
* actually useful at a glance — `command` for terminal, `path` for
|
|
15
|
+
* file ops, `query` for search, etc.).
|
|
16
|
+
*
|
|
17
|
+
* Falls back to the original full-JSON stringification when the tool
|
|
18
|
+
* isn't in the map or the primary arg is absent. This keeps unknown
|
|
19
|
+
* tools rendering exactly as before — additive only.
|
|
20
|
+
*
|
|
21
|
+
* Adding a new tool with a non-obvious primary arg? Add it here.
|
|
22
|
+
* Tools whose `args` shape is "the arg is meaningful at-a-glance"
|
|
23
|
+
* (a path, a query, a command, a URL, an id, a name) belong in this map.
|
|
24
|
+
* Tools whose args are a small flag bag (e.g. system_info has no args
|
|
25
|
+
* worth showing) can be omitted — the renderer hides the args block
|
|
26
|
+
* entirely when the map returns `null` and the arg object is empty.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.TOOL_PRIMARY_ARG = void 0;
|
|
30
|
+
exports.buildToolPreview = buildToolPreview;
|
|
31
|
+
/**
|
|
32
|
+
* Map of tool-name → name of the property in `args` that should render
|
|
33
|
+
* as the at-a-glance preview. Stable contract; tests assert specific
|
|
34
|
+
* entries.
|
|
35
|
+
*/
|
|
36
|
+
exports.TOOL_PRIMARY_ARG = {
|
|
37
|
+
// ── terminal / execution ─────────────────────────────────────────────
|
|
38
|
+
shell_exec: 'command',
|
|
39
|
+
execute_code: 'code',
|
|
40
|
+
// ── file ops ─────────────────────────────────────────────────────────
|
|
41
|
+
file_read: 'path',
|
|
42
|
+
file_write: 'path',
|
|
43
|
+
file_patch: 'path',
|
|
44
|
+
file_list: 'path',
|
|
45
|
+
file_copy: 'source',
|
|
46
|
+
file_move: 'source',
|
|
47
|
+
file_delete: 'path',
|
|
48
|
+
// ── web ──────────────────────────────────────────────────────────────
|
|
49
|
+
web_search: 'query',
|
|
50
|
+
deep_research: 'query',
|
|
51
|
+
youtube_search: 'query',
|
|
52
|
+
fetch_url: 'url',
|
|
53
|
+
fetch_page: 'url',
|
|
54
|
+
open_url: 'url',
|
|
55
|
+
// ── browser ──────────────────────────────────────────────────────────
|
|
56
|
+
browser_navigate: 'url',
|
|
57
|
+
browser_click: 'selector',
|
|
58
|
+
browser_fill: 'selector',
|
|
59
|
+
browser_type: 'selector',
|
|
60
|
+
browser_scroll: 'selector',
|
|
61
|
+
browser_extract: 'selector',
|
|
62
|
+
browser_get_url: '', // no args — present so map lookup hits
|
|
63
|
+
browser_screenshot: 'path',
|
|
64
|
+
browser_close: '',
|
|
65
|
+
// ── memory ───────────────────────────────────────────────────────────
|
|
66
|
+
memory_add: 'content',
|
|
67
|
+
memory_remove: 'content',
|
|
68
|
+
memory_replace: 'old',
|
|
69
|
+
// ── skills ───────────────────────────────────────────────────────────
|
|
70
|
+
skill_view: 'name',
|
|
71
|
+
skill_manage: 'action',
|
|
72
|
+
skills_list: '',
|
|
73
|
+
// ── sessions ─────────────────────────────────────────────────────────
|
|
74
|
+
session_search: 'query',
|
|
75
|
+
session_list: '',
|
|
76
|
+
session_summary: 'trigger',
|
|
77
|
+
// ── process ──────────────────────────────────────────────────────────
|
|
78
|
+
process_spawn: 'command',
|
|
79
|
+
process_kill: 'pid',
|
|
80
|
+
process_list: '',
|
|
81
|
+
process_wait: 'pid',
|
|
82
|
+
process_log_read: 'pid',
|
|
83
|
+
// ── subagent ─────────────────────────────────────────────────────────
|
|
84
|
+
subagent_fanout: 'mode',
|
|
85
|
+
// ── system / misc ────────────────────────────────────────────────────
|
|
86
|
+
system_info: '',
|
|
87
|
+
now_playing: '',
|
|
88
|
+
get_natural_events: '',
|
|
89
|
+
// ── v4.1.4-media — three-layer media-control bundle ──────────────────
|
|
90
|
+
// `media_sessions` has no args by schema; the empty-arg preview is
|
|
91
|
+
// suppressed by buildToolPreview returning ''.
|
|
92
|
+
// `media_transport` → preview by target ("spotify"), the actionable
|
|
93
|
+
// identifier the user typed. `action` is intentionally NOT chosen —
|
|
94
|
+
// GSMTC actions (play/pause/toggle) are short, the target is the
|
|
95
|
+
// discriminator.
|
|
96
|
+
// `media_key` is layer-3 fallback; show `action` since there's no
|
|
97
|
+
// target to surface (it's a blind keystroke).
|
|
98
|
+
// `app_input` shows `app` so the user sees which window got the keys.
|
|
99
|
+
media_sessions: '',
|
|
100
|
+
media_transport: 'target',
|
|
101
|
+
media_key: 'action',
|
|
102
|
+
app_input: 'app',
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Maximum visible characters for the preview value. Long commands /
|
|
106
|
+
* full file contents get truncated with an ellipsis so a single tool
|
|
107
|
+
* row stays on one line at typical terminal widths.
|
|
108
|
+
*/
|
|
109
|
+
const PREVIEW_MAX_CHARS = 120;
|
|
110
|
+
/**
|
|
111
|
+
* Build the per-tool preview string for `args`. Returns:
|
|
112
|
+
* - `null` when the tool isn't in the map (caller falls back to the
|
|
113
|
+
* legacy JSON.stringify path),
|
|
114
|
+
* - `''` (empty string) when the tool is in the map but has no
|
|
115
|
+
* meaningful primary arg (caller renders just the tool name),
|
|
116
|
+
* - the truncated string value of the primary arg otherwise.
|
|
117
|
+
*
|
|
118
|
+
* Exposed for unit tests. Pure function, no side effects.
|
|
119
|
+
*/
|
|
120
|
+
function buildToolPreview(toolName, args) {
|
|
121
|
+
if (!Object.prototype.hasOwnProperty.call(exports.TOOL_PRIMARY_ARG, toolName)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const argKey = exports.TOOL_PRIMARY_ARG[toolName];
|
|
125
|
+
if (argKey === '')
|
|
126
|
+
return '';
|
|
127
|
+
if (!args || typeof args !== 'object')
|
|
128
|
+
return '';
|
|
129
|
+
const raw = args[argKey];
|
|
130
|
+
if (raw === undefined || raw === null)
|
|
131
|
+
return '';
|
|
132
|
+
let str;
|
|
133
|
+
if (typeof raw === 'string') {
|
|
134
|
+
str = raw;
|
|
135
|
+
}
|
|
136
|
+
else if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
137
|
+
str = String(raw);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
try {
|
|
141
|
+
str = JSON.stringify(raw);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
str = String(raw);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Collapse whitespace so multi-line commands stay on one preview row.
|
|
148
|
+
str = str.replace(/\s+/g, ' ').trim();
|
|
149
|
+
if (str.length > PREVIEW_MAX_CHARS) {
|
|
150
|
+
str = `${str.slice(0, PREVIEW_MAX_CHARS - 1)}…`;
|
|
151
|
+
}
|
|
152
|
+
return str;
|
|
153
|
+
}
|