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
|
@@ -27,15 +27,63 @@
|
|
|
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) {
|
|
37
43
|
return (text) => (0, skinEngine_1.getSkinEngine)().applyColors(text, kind);
|
|
38
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* v4.1.3-essentials: bold (`**foo**`) markdown emphasis renders as
|
|
47
|
+
* ANSI bold + underline. Previously painted 'brand' (orange) which
|
|
48
|
+
* collided with the heading hierarchy. Briefly tried bold + bright-
|
|
49
|
+
* white; landed on bold + underline because underline carries
|
|
50
|
+
* emphasis without consuming a color slot — the palette stays
|
|
51
|
+
* available for state semantics (yellow=degraded, red=error, etc.).
|
|
52
|
+
*
|
|
53
|
+
* ANSI sequence: `\x1b[1m\x1b[4m{text}\x1b[24m\x1b[22m` — bold ON +
|
|
54
|
+
* underline ON, then underline OFF + bold OFF. Reset order matters
|
|
55
|
+
* (underline first, bold second) so the closing codes don't reorder
|
|
56
|
+
* styles surprisingly on terminals that batch SGR updates.
|
|
57
|
+
*
|
|
58
|
+
* Bypasses the skin system intentionally — bold-as-underline is an
|
|
59
|
+
* opinionated default for this slice. Same caveat as the prior bold-
|
|
60
|
+
* as-color iteration: nested markdown loses the outer style after
|
|
61
|
+
* close (pre-existing limitation of the painter-stack architecture).
|
|
62
|
+
*
|
|
63
|
+
* Honors `NO_COLOR=1` per the standard (skips the wrap entirely).
|
|
64
|
+
* Strictly speaking `NO_COLOR` is about color and underline isn't
|
|
65
|
+
* a color, but the wrap still emits ANSI escapes; honoring the env
|
|
66
|
+
* var keeps output paste-safe in scripted contexts.
|
|
67
|
+
*/
|
|
68
|
+
function paintBoldUnderline(text) {
|
|
69
|
+
if (process.env.NO_COLOR && process.env.NO_COLOR !== '')
|
|
70
|
+
return text;
|
|
71
|
+
return `\x1b[1m\x1b[4m${text}\x1b[24m\x1b[22m`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* v4.1.3-essentials reply-polish: bold-on + skin paint + bold-off.
|
|
75
|
+
* Used by the 4-tier heading hierarchy so each level can pick its own
|
|
76
|
+
* color while sharing the bold weight. Emit order matches the rest of
|
|
77
|
+
* the painter stack: outer wrap is bold, inner wrap is fg color.
|
|
78
|
+
*
|
|
79
|
+
* Honors `NO_COLOR=1` via the skin engine's own gate; the bold ANSI
|
|
80
|
+
* still emits because bold is a weight, not a color (matches the
|
|
81
|
+
* paintBoldUnderline convention for `**bold**`).
|
|
82
|
+
*/
|
|
83
|
+
function paintBold(kind) {
|
|
84
|
+
const colorize = paint(kind);
|
|
85
|
+
return (text) => `\x1b[1m${colorize(text)}\x1b[22m`;
|
|
86
|
+
}
|
|
39
87
|
/**
|
|
40
88
|
* Render a fenced code block: top divider with language label, body
|
|
41
89
|
* with optional syntax highlighting, bottom divider.
|
|
@@ -51,10 +99,41 @@ function paint(kind) {
|
|
|
51
99
|
* the renderer with; the older positional path is kept for
|
|
52
100
|
* compatibility.
|
|
53
101
|
*/
|
|
102
|
+
/**
|
|
103
|
+
* v4.1.3-essentials reply-polish: 24-bit dark background applied per
|
|
104
|
+
* line so code stands out from prose.
|
|
105
|
+
*
|
|
106
|
+
* Color choice: `\x1b[48;2;50;50;60m` (#32323c, slightly bluish dark
|
|
107
|
+
* grey). The original `30,30,30` (#1e1e1e) was invisible against VS
|
|
108
|
+
* Code's integrated terminal default (also #1e1e1e) and barely
|
|
109
|
+
* distinct from Windows Terminal's One Half Dark. #32323c is visibly
|
|
110
|
+
* different from every common dark-terminal default (Campbell, One
|
|
111
|
+
* Half Dark, Solarized Dark, Monokai, VS Code) while staying subtle
|
|
112
|
+
* enough to read as "code chrome" rather than a jarring highlight.
|
|
113
|
+
*
|
|
114
|
+
* Used by BOTH the block path (fenced code blocks) and the inline
|
|
115
|
+
* path (`` `code` `` spans) so the two affordances visually agree —
|
|
116
|
+
* inline code reads as "this is code" via the same chrome as block
|
|
117
|
+
* code, just shorter.
|
|
118
|
+
*
|
|
119
|
+
* NOTE: \x1b[49m is "default background", terminating the per-line
|
|
120
|
+
* background scope cleanly. Each body line is wrapped individually
|
|
121
|
+
* rather than wrapping the whole block, so the background doesn't
|
|
122
|
+
* bleed across the closing horizontal rule (which already paints fg
|
|
123
|
+
* muted with its own reset).
|
|
124
|
+
*/
|
|
125
|
+
const CODE_BG_ON = '\x1b[48;2;50;50;60m';
|
|
126
|
+
const CODE_BG_OFF = '\x1b[49m';
|
|
54
127
|
function renderCodeBlock(code, lang) {
|
|
55
128
|
const sk = (0, skinEngine_1.getSkinEngine)();
|
|
56
|
-
|
|
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)();
|
|
57
134
|
const langLabel = (lang ?? '').trim();
|
|
135
|
+
// v4.1.3-essentials reply-polish: language tag on the top rule
|
|
136
|
+
// already shipped; keep it. Bottom rule unlabeled (closing fence).
|
|
58
137
|
const top = langLabel
|
|
59
138
|
? `── ${langLabel} ${'─'.repeat(Math.max(0, width - langLabel.length - 4))}`
|
|
60
139
|
: '─'.repeat(width);
|
|
@@ -62,11 +141,43 @@ function renderCodeBlock(code, lang) {
|
|
|
62
141
|
const body = (0, syntaxHighlight_1.isSupportedLang)(langLabel)
|
|
63
142
|
? (0, syntaxHighlight_1.highlightCode)(code, langLabel)
|
|
64
143
|
: code;
|
|
65
|
-
|
|
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)
|
|
157
|
+
// - left rail `│ ` painted muted (mirrors blockquote's `┃ ` rail
|
|
158
|
+
// with a different glyph so they're visually distinct)
|
|
159
|
+
// - 24-bit dark background wrapping the rail + content (subtle
|
|
160
|
+
// "this is code" affordance without going full TUI box-frame)
|
|
161
|
+
const rail = sk.applyColors('│', 'muted');
|
|
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.
|
|
66
177
|
return [
|
|
67
|
-
sk.applyColors(top, 'muted')
|
|
178
|
+
`${gutter}${sk.applyColors(top, 'muted')}`,
|
|
68
179
|
indented,
|
|
69
|
-
sk.applyColors(bot, 'muted')
|
|
180
|
+
`${gutter}${sk.applyColors(bot, 'muted')}`,
|
|
70
181
|
'',
|
|
71
182
|
].join('\n') + '\n';
|
|
72
183
|
}
|
|
@@ -82,26 +193,152 @@ function renderBlockquote(quote) {
|
|
|
82
193
|
.join('\n') + '\n';
|
|
83
194
|
}
|
|
84
195
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
196
|
+
* v4.1.3-essentials reply-polish: 4-tier heading hierarchy using the
|
|
197
|
+
* existing palette colors so visual weight differs per level even
|
|
198
|
+
* though we don't introduce a new ColorKind.
|
|
199
|
+
*
|
|
200
|
+
* H1 — brand + bold + UPPERCASE (major section heading)
|
|
201
|
+
* H2 — brand + bold (subsection — same hue as H1
|
|
202
|
+
* but sentence-case + no caps)
|
|
203
|
+
* H3 — agent + bold (off-white, lighter weight
|
|
204
|
+
* than brand)
|
|
205
|
+
* H4+ — muted + bold (quietest — same grey as the
|
|
206
|
+
* reply container's chrome)
|
|
207
|
+
*
|
|
208
|
+
* v4.1.3-essentials reply-polish: spacing tightened from `\n\n` to
|
|
209
|
+
* `\n` per level. marked-terminal contributes its own block
|
|
210
|
+
* separator (one more newline) → total `\n\n` between heading and
|
|
211
|
+
* next block = single blank line, matching paragraph rhythm.
|
|
212
|
+
* Previously this emitted `\n\n\n\n` (three blank lines) which made
|
|
213
|
+
* structured replies feel cramped at top and over-aired between
|
|
214
|
+
* sections.
|
|
87
215
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
216
|
+
// 4-tier hierarchy. Called by the prototype-level `heading` override
|
|
217
|
+
// in getReplyRenderer() which extracts depth from the token first.
|
|
218
|
+
// Plain `(text, depth)` signature; the marked v15 / v14 / positional
|
|
219
|
+
// translation happens in the override.
|
|
220
|
+
//
|
|
221
|
+
// Each tier ends with `\n\n` to fence the heading from the next block
|
|
222
|
+
// with a blank line. Earlier we tried `\n` (single trailing newline)
|
|
223
|
+
// assuming marked-terminal's `section()` wrapper added its own
|
|
224
|
+
// padding — but the prototype-level override bypasses section(), so
|
|
225
|
+
// we own the spacing end-to-end. Result with `\n\n`: heading visible
|
|
226
|
+
// on its own line, blank line separates it from the next paragraph /
|
|
227
|
+
// heading / list. Matches the paragraph rhythm (`text\n\n`).
|
|
228
|
+
function renderHeading(text, depth) {
|
|
229
|
+
if (depth <= 1)
|
|
230
|
+
return paintBold('brand')(text.toUpperCase()) + '\n\n';
|
|
231
|
+
if (depth === 2)
|
|
232
|
+
return paintBold('brand')(text) + '\n\n';
|
|
233
|
+
if (depth === 3)
|
|
234
|
+
return paintBold('agent')(text) + '\n\n';
|
|
235
|
+
return paintBold('muted')(text) + '\n\n';
|
|
94
236
|
}
|
|
95
237
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
238
|
+
* v4.1.3-essentials reply-polish: the `opts.listitem` callback used to
|
|
239
|
+
* own bullet rendering but marked-terminal's outer `list` method
|
|
240
|
+
* ALSO emits a `* ` prefix, producing visible double bullets
|
|
241
|
+
* (` * ▸ item`). The fix is a prototype-level override on BOTH
|
|
242
|
+
* `list` and `listitem` (mirrors the existing pattern for `code` and
|
|
243
|
+
* `link`). See the override block in getReplyRenderer().
|
|
244
|
+
*
|
|
245
|
+
* This callback now just returns the inner text unchanged so the
|
|
246
|
+
* prototype-level `list` override can do the bullet + indent work
|
|
247
|
+
* with full nesting-depth context.
|
|
99
248
|
*/
|
|
100
249
|
function renderListItem(text) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
250
|
+
return text;
|
|
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('');
|
|
105
342
|
}
|
|
106
343
|
/**
|
|
107
344
|
* Singleton — caching is fine since options bind to the active skin
|
|
@@ -121,14 +358,33 @@ function getReplyRenderer() {
|
|
|
121
358
|
// method directly below.
|
|
122
359
|
const opts = {
|
|
123
360
|
blockquote: renderBlockquote,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
361
|
+
// v4.1.3-essentials reply-polish: `opts.heading` and `opts.firstHeading`
|
|
362
|
+
// both removed. marked-terminal calls `opts.heading(text)` with ONLY
|
|
363
|
+
// text (audit-confirmed via toString), dropping the depth info we
|
|
364
|
+
// need for the 4-tier hierarchy. The prototype-level `renderer.heading`
|
|
365
|
+
// override below owns the depth extraction + tier selection end-to-end.
|
|
366
|
+
// marked-terminal's stripped-args call path never reaches our callback.
|
|
367
|
+
hr: () => paint('muted')('─'.repeat((0, frame_1.getBodyWidth)())) + '\n',
|
|
127
368
|
listitem: renderListItem,
|
|
128
369
|
paragraph: (text) => `${text}\n\n`,
|
|
129
|
-
|
|
370
|
+
// v4.1.3-essentials: bold renders as ANSI bold + underline
|
|
371
|
+
// (was 'brand' / orange, then bright-white; landed on underline
|
|
372
|
+
// so the color palette stays available for state semantics).
|
|
373
|
+
strong: paintBoldUnderline,
|
|
130
374
|
em: paint('muted'),
|
|
131
|
-
|
|
375
|
+
// v4.1.3-essentials reply-polish: inline `` `code` `` — strip
|
|
376
|
+
// the literal backticks (used to leak into the visible output)
|
|
377
|
+
// and wrap with the same dark background as fenced code blocks.
|
|
378
|
+
// Visual consistency: inline code reads as "this is code" via the
|
|
379
|
+
// same chrome as block code, just shorter. One leading + trailing
|
|
380
|
+
// space inside the bg span gives the chrome a bit of padding so
|
|
381
|
+
// letters don't sit flush against the bg edge.
|
|
382
|
+
//
|
|
383
|
+
// Trade-off (accepted): if an inline-code span breaks across a
|
|
384
|
+
// line wrap, the bg painting may show a visual seam at the wrap
|
|
385
|
+
// point. Acceptable for v4.1.3 — revertable to Path A (no bg) if
|
|
386
|
+
// visual smoke surfaces a real problem.
|
|
387
|
+
codespan: (text) => `${CODE_BG_ON} ${paint('accent')(text)} ${CODE_BG_OFF}`,
|
|
132
388
|
del: paint('muted'),
|
|
133
389
|
// marked-terminal calls opts.link with the ASSEMBLED visual
|
|
134
390
|
// (already OSC8-wrapped when the host terminal supports it),
|
|
@@ -136,7 +392,12 @@ function getReplyRenderer() {
|
|
|
136
392
|
link: (assembled) => paint('accent')(assembled),
|
|
137
393
|
href: paint('accent'),
|
|
138
394
|
text: (text) => text,
|
|
139
|
-
|
|
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)(),
|
|
140
401
|
showSectionPrefix: false,
|
|
141
402
|
reflowText: false,
|
|
142
403
|
tab: 2,
|
|
@@ -185,6 +446,174 @@ function getReplyRenderer() {
|
|
|
185
446
|
const painted = paint('accent')(label);
|
|
186
447
|
return `\x1b]8;;${url}\x1b\\${painted}\x1b]8;;\x1b\\`;
|
|
187
448
|
};
|
|
449
|
+
// v4.1.3-essentials reply-polish: prototype-level `heading` override.
|
|
450
|
+
//
|
|
451
|
+
// Why: marked-terminal's internal `heading` method extracts the
|
|
452
|
+
// token's depth, then calls `opts.heading(text)` with ONLY the
|
|
453
|
+
// text — dropping the level info on the floor. Our 4-tier hierarchy
|
|
454
|
+
// (H1 brand+caps, H2 brand, H3 agent, H4+ muted) needs level
|
|
455
|
+
// context, so we must own the whole method.
|
|
456
|
+
//
|
|
457
|
+
// The override accepts marked v15's token-object shape and falls
|
|
458
|
+
// through to v14 positional for unit tests that pass plain strings.
|
|
459
|
+
renderer.heading = function (textOrToken, levelArg, _raw) {
|
|
460
|
+
let text;
|
|
461
|
+
let depth;
|
|
462
|
+
if (typeof textOrToken === 'object' && textOrToken !== null) {
|
|
463
|
+
const tok = textOrToken;
|
|
464
|
+
depth = typeof tok.depth === 'number' ? tok.depth : 1;
|
|
465
|
+
// Prefer parseInline for rich heading content (e.g. `## H2 with **bold**`).
|
|
466
|
+
// Falls through to tok.text for the common plain-text case.
|
|
467
|
+
const parser = this.parser;
|
|
468
|
+
if (tok.tokens && parser?.parseInline) {
|
|
469
|
+
text = parser.parseInline(tok.tokens);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
text = String(tok.text ?? '');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
text = String(textOrToken ?? '');
|
|
477
|
+
depth = typeof levelArg === 'number' ? levelArg : 1;
|
|
478
|
+
}
|
|
479
|
+
return renderHeading(text, depth);
|
|
480
|
+
};
|
|
481
|
+
// v4.1.3-essentials reply-polish: prototype-level list overrides.
|
|
482
|
+
//
|
|
483
|
+
// Why two functions and a depth counter:
|
|
484
|
+
// - marked-terminal's default `list` injects a `* ` (or `N. `)
|
|
485
|
+
// prefix BEFORE calling our `opts.listitem` callback, producing
|
|
486
|
+
// visible double bullets — see audit. Owning `list` at the
|
|
487
|
+
// prototype level lets us suppress that and emit our own.
|
|
488
|
+
// - Nesting depth determines the bullet glyph: top-level gets `•`
|
|
489
|
+
// and any deeper level gets `▸`. marked doesn't pass depth to
|
|
490
|
+
// the renderer, so we track it on the renderer instance via a
|
|
491
|
+
// counter that increments on `list`-enter and decrements on
|
|
492
|
+
// exit. This works because marked walks the token tree
|
|
493
|
+
// synchronously: a nested list's `list` call always completes
|
|
494
|
+
// between its parent's `list`-enter and `list`-exit.
|
|
495
|
+
// - Items already had their child markdown rendered via the
|
|
496
|
+
// prototype's `listitem` (which we leave as a passthrough above
|
|
497
|
+
// in the opts block — it just returns the inner text). The
|
|
498
|
+
// body string we receive in `list` is the concatenated children;
|
|
499
|
+
// each child can itself be a nested list rendering, whose own
|
|
500
|
+
// `list` call already handled its bullets + indent.
|
|
501
|
+
//
|
|
502
|
+
// Numbered lists: `start` and `ordered` come from the token; we
|
|
503
|
+
// emit `N.` prefix in muted to keep the visual rhythm consistent
|
|
504
|
+
// with bulleted lists but preserve numeric semantics.
|
|
505
|
+
//
|
|
506
|
+
// Indent: 2 spaces per nesting level. Top-level items therefore
|
|
507
|
+
// sit at column 2 (matching the rest of the reply container's
|
|
508
|
+
// chrome); nested at column 4, 6, etc.
|
|
509
|
+
const proto = renderer;
|
|
510
|
+
proto._listDepth = 0;
|
|
511
|
+
renderer.listitem = function (text, _task, _checked) {
|
|
512
|
+
// marked v15 may pass a token object; the assembled-text fallback
|
|
513
|
+
// covers older signatures. Either way we want the inner text
|
|
514
|
+
// unchanged here — bullet + indent is owned by `list` below.
|
|
515
|
+
if (typeof text === 'object' && text !== null) {
|
|
516
|
+
const tok = text;
|
|
517
|
+
if (typeof tok.text === 'string')
|
|
518
|
+
return tok.text;
|
|
519
|
+
const parser = this.parser;
|
|
520
|
+
return parser?.parseInline?.(tok.tokens ?? []) ?? '';
|
|
521
|
+
}
|
|
522
|
+
return String(text ?? '');
|
|
523
|
+
};
|
|
524
|
+
renderer.list = function (body, ordered, start) {
|
|
525
|
+
// marked v15 token shape: { ordered, start, items: [token, ...] }
|
|
526
|
+
// Older positional shape: (body, ordered, start)
|
|
527
|
+
let isOrdered = false;
|
|
528
|
+
let startNum = 1;
|
|
529
|
+
let items;
|
|
530
|
+
// CRITICAL: increment depth BEFORE walking items. Item walking via
|
|
531
|
+
// `parser.parse(it.tokens)` recurses into our own override for any
|
|
532
|
+
// nested list tokens — those nested calls need to see the parent's
|
|
533
|
+
// incremented depth so they pick the deeper bullet glyph (▸) and
|
|
534
|
+
// indent. If we increment AFTER `parser.parse`, the nested call
|
|
535
|
+
// sees depth=0, renders at top-level styling, and the visible
|
|
536
|
+
// nesting collapses. Confirmed via runtime trace.
|
|
537
|
+
proto._listDepth = (proto._listDepth ?? 0) + 1;
|
|
538
|
+
const depth = proto._listDepth;
|
|
539
|
+
if (typeof body === 'object' && body !== null) {
|
|
540
|
+
const tok = body;
|
|
541
|
+
isOrdered = tok.ordered === true;
|
|
542
|
+
startNum = typeof tok.start === 'number' ? tok.start : 1;
|
|
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).
|
|
564
|
+
const parser = this.parser;
|
|
565
|
+
items = (tok.items ?? []).map((it) => {
|
|
566
|
+
if (!parser)
|
|
567
|
+
return it.text ?? '';
|
|
568
|
+
return renderListItemTokens(it, parser);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
isOrdered = ordered === true;
|
|
573
|
+
startNum = typeof start === 'number' ? start : 1;
|
|
574
|
+
// Positional `body` is the already-concatenated rendered items.
|
|
575
|
+
// Split on newlines that introduce a fresh item; marked emits
|
|
576
|
+
// each item as its own logical line. Best-effort — marked v15
|
|
577
|
+
// path above is the production case.
|
|
578
|
+
const raw = String(body ?? '');
|
|
579
|
+
items = raw.split('\n').filter((ln) => ln.trim().length > 0);
|
|
580
|
+
}
|
|
581
|
+
const indent = ' '.repeat(depth);
|
|
582
|
+
// Top-level bullet `•` (filled); nested `▸` (arrow-like) for
|
|
583
|
+
// visual depth differentiation. Numbered lists override with
|
|
584
|
+
// `N.` regardless of depth.
|
|
585
|
+
const bulletGlyph = depth === 1 ? '•' : '▸';
|
|
586
|
+
const lines = [];
|
|
587
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
588
|
+
const item = items[i];
|
|
589
|
+
// Each item may itself contain newlines (nested list output,
|
|
590
|
+
// multi-line paragraph). Indent every line of the rendered
|
|
591
|
+
// item AFTER the first — the first line takes the bullet, the
|
|
592
|
+
// continuation lines align under the bullet's content column.
|
|
593
|
+
const marker = isOrdered
|
|
594
|
+
? paint('muted')(`${startNum + i}.`)
|
|
595
|
+
: paint('muted')(bulletGlyph);
|
|
596
|
+
const itemLines = item.split('\n');
|
|
597
|
+
const head = itemLines[0] ?? '';
|
|
598
|
+
const tail = itemLines.slice(1);
|
|
599
|
+
lines.push(`${indent}${marker} ${head}`);
|
|
600
|
+
// Continuation lines: if they already have content, align them
|
|
601
|
+
// under the bullet's text column (indent + marker-width + 1
|
|
602
|
+
// space). marked-terminal's nested lists arrive pre-indented so
|
|
603
|
+
// we pass them through.
|
|
604
|
+
for (const tailLine of tail) {
|
|
605
|
+
if (tailLine.length === 0)
|
|
606
|
+
continue;
|
|
607
|
+
lines.push(tailLine);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
proto._listDepth -= 1;
|
|
611
|
+
// Top-level list closes with a trailing newline to separate from
|
|
612
|
+
// the next block; nested lists return without extra padding so
|
|
613
|
+
// they nest cleanly inside their parent item.
|
|
614
|
+
const out = lines.join('\n');
|
|
615
|
+
return proto._listDepth === 0 ? out + '\n' : out + '\n';
|
|
616
|
+
};
|
|
188
617
|
cachedRenderer = {
|
|
189
618
|
render(text) {
|
|
190
619
|
try {
|
|
@@ -194,7 +623,21 @@ function getReplyRenderer() {
|
|
|
194
623
|
// if other code transiently swaps the renderer.
|
|
195
624
|
marked_1.marked.setOptions({ renderer: renderer });
|
|
196
625
|
const out = marked_1.marked.parse(text);
|
|
197
|
-
|
|
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);
|
|
198
641
|
}
|
|
199
642
|
catch {
|
|
200
643
|
return text;
|
|
@@ -203,6 +646,24 @@ function getReplyRenderer() {
|
|
|
203
646
|
};
|
|
204
647
|
return cachedRenderer;
|
|
205
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
|
+
}
|
|
206
667
|
/** Test reset — drops the cached renderer so a skin change picks up. */
|
|
207
668
|
function _resetForTests() {
|
|
208
669
|
cachedRenderer = null;
|
|
@@ -49,10 +49,23 @@ const DEFAULT_SKIN = {
|
|
|
49
49
|
error: [0xf4, 0x47, 0x47],
|
|
50
50
|
warn: [0xff, 0xc1, 0x07],
|
|
51
51
|
success: [0x4c, 0xaf, 0x50],
|
|
52
|
-
//
|
|
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,
|
|
62
|
+
// v4.1.3-repl-polish: session = soft cyan (ex-muted); used for IDs
|
|
63
|
+
// and the session-end card header labels.
|
|
64
|
+
session: [0x6f, 0xb3, 0xd2],
|
|
65
|
+
// v4.1.3-repl-polish: degraded = amber yellow; distinct from warn
|
|
66
|
+
// (which shares the colour) so callers can differentiate in code
|
|
67
|
+
// even though they render identically.
|
|
68
|
+
degraded: [0xff, 0xc1, 0x07],
|
|
56
69
|
},
|
|
57
70
|
glyphs: {
|
|
58
71
|
bullet: '•',
|
|
@@ -77,8 +90,15 @@ const LIGHT_SKIN = {
|
|
|
77
90
|
error: [0xb0, 0x10, 0x10],
|
|
78
91
|
warn: [0x80, 0x60, 0x00],
|
|
79
92
|
success: [0x1b, 0x5e, 0x20],
|
|
80
|
-
|
|
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],
|
|
81
99
|
heading: [0xc4, 0x42, 0x10],
|
|
100
|
+
session: [0x00, 0x55, 0x88],
|
|
101
|
+
degraded: [0x80, 0x60, 0x00],
|
|
82
102
|
},
|
|
83
103
|
glyphs: { ...DEFAULT_SKIN.glyphs },
|
|
84
104
|
};
|
|
@@ -96,6 +116,8 @@ const MONOCHROME_SKIN = {
|
|
|
96
116
|
success: null,
|
|
97
117
|
muted: null,
|
|
98
118
|
heading: null,
|
|
119
|
+
session: null,
|
|
120
|
+
degraded: null,
|
|
99
121
|
},
|
|
100
122
|
glyphs: {
|
|
101
123
|
bullet: '*',
|