aiden-runtime 4.0.2 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +424 -7
- package/dist/cli/v4/aidenPrompt.js +317 -0
- package/dist/cli/v4/box.js +105 -39
- package/dist/cli/v4/callbacks.js +39 -6
- package/dist/cli/v4/chatSession.js +256 -55
- package/dist/cli/v4/citationFooter.js +97 -0
- package/dist/cli/v4/commands/channel.js +656 -0
- package/dist/cli/v4/commands/clear.js +1 -1
- package/dist/cli/v4/commands/compress.js +1 -1
- package/dist/cli/v4/commands/cron.js +44 -16
- package/dist/cli/v4/commands/fanout.js +236 -0
- package/dist/cli/v4/commands/help.js +15 -4
- package/dist/cli/v4/commands/history.js +84 -0
- package/dist/cli/v4/commands/index.js +16 -1
- package/dist/cli/v4/commands/mcp.js +358 -0
- package/dist/cli/v4/commands/show.js +43 -0
- package/dist/cli/v4/commands/skills.js +169 -4
- package/dist/cli/v4/commands/status.js +84 -0
- package/dist/cli/v4/commands/subagent.js +78 -0
- package/dist/cli/v4/commands/verbose.js +1 -1
- package/dist/cli/v4/commands/voice.js +218 -0
- package/dist/cli/v4/cronCli.js +103 -0
- package/dist/cli/v4/display.js +297 -13
- package/dist/cli/v4/doctor.js +102 -1
- package/dist/cli/v4/doctorLiveness.js +329 -0
- package/dist/cli/v4/envSources.js +105 -0
- package/dist/cli/v4/ghostMatch.js +74 -0
- package/dist/cli/v4/historyStore.js +163 -0
- package/dist/cli/v4/pasteCompression.js +124 -0
- package/dist/cli/v4/pasteIntercept.js +203 -0
- package/dist/cli/v4/replyRenderer.js +209 -0
- package/dist/cli/v4/resizeGuard.js +92 -0
- package/dist/cli/v4/shellInterpolation.js +139 -0
- package/dist/cli/v4/skinEngine.js +21 -1
- package/dist/cli/v4/streamingPrefix.js +121 -0
- package/dist/cli/v4/syntaxHighlight.js +345 -0
- package/dist/cli/v4/table.js +216 -0
- package/dist/cli/v4/themeDetect.js +81 -0
- package/dist/cli/v4/uiBuild.js +74 -0
- package/dist/cli/v4/voiceCli.js +113 -0
- package/dist/cli/v4/voicePromptApi.js +196 -0
- package/dist/core/channels/discord.js +16 -10
- package/dist/core/channels/email.js +13 -9
- package/dist/core/channels/imessage.js +13 -9
- package/dist/core/channels/manager.js +25 -7
- package/dist/core/channels/pdf-extract.js +180 -0
- package/dist/core/channels/photo-vision.js +157 -0
- package/dist/core/channels/signal.js +11 -7
- package/dist/core/channels/slack.js +13 -10
- package/dist/core/channels/telegram-commands.js +154 -0
- package/dist/core/channels/telegram-groups.js +198 -0
- package/dist/core/channels/telegram-rate-limit.js +124 -0
- package/dist/core/channels/telegram.js +1980 -0
- package/dist/core/channels/twilio.js +11 -7
- package/dist/core/channels/webhook.js +9 -5
- package/dist/core/channels/whatsapp.js +15 -11
- package/dist/core/channels/whisper-transcribe.js +163 -0
- package/dist/core/cronManager.js +33 -294
- package/dist/core/gateway.js +29 -8
- package/dist/core/playwrightBridge.js +90 -0
- package/dist/core/v4/aidenAgent.js +35 -0
- package/dist/core/v4/auxiliaryClient.js +2 -2
- package/dist/core/v4/cron/atomicWrite.js +18 -4
- package/dist/core/v4/cron/cronExecute.js +300 -0
- package/dist/core/v4/cron/cronManager.js +502 -0
- package/dist/core/v4/cron/cronState.js +314 -0
- package/dist/core/v4/cron/cronTick.js +90 -0
- package/dist/core/v4/cron/diagnostics.js +104 -0
- package/dist/core/v4/cron/graceWindow.js +79 -0
- package/dist/core/v4/logger/factory.js +110 -0
- package/dist/core/v4/logger/index.js +22 -0
- package/dist/core/v4/logger/logger.js +101 -0
- package/dist/core/v4/logger/sinks/fileSink.js +110 -0
- package/dist/core/v4/logger/sinks/multiSink.js +43 -0
- package/dist/core/v4/logger/sinks/nullSink.js +53 -0
- package/dist/core/v4/logger/sinks/stdSink.js +81 -0
- package/dist/core/v4/mcp/server/diagnostics.js +40 -0
- package/dist/core/v4/mcp/server/skillBridge.js +94 -0
- package/dist/core/v4/mcp/server/stdioServer.js +119 -0
- package/dist/core/v4/mcp/server/toolBridge.js +168 -0
- package/dist/core/v4/platformPaths.js +105 -0
- package/dist/core/v4/providerFallback.js +25 -0
- package/dist/core/v4/skillLoader.js +21 -5
- package/dist/core/v4/skillMining/candidateStore.js +164 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
- package/dist/core/v4/skillMining/skillMiner.js +191 -0
- package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
- package/dist/core/v4/subagent/budget.js +76 -0
- package/dist/core/v4/subagent/diagnostics.js +22 -0
- package/dist/core/v4/subagent/fanout.js +216 -0
- package/dist/core/v4/subagent/merger.js +148 -0
- package/dist/core/v4/subagent/providerRotation.js +54 -0
- package/dist/core/v4/voice/audioStream.js +373 -0
- package/dist/core/v4/voice/cliVoice.js +393 -0
- package/dist/core/v4/voice/diagnostics.js +66 -0
- package/dist/core/v4/voice/ttsStream.js +193 -0
- package/dist/core/version.js +1 -1
- package/dist/core/visionAnalyze.js +291 -90
- package/dist/core/voice/audio.js +61 -5
- package/dist/core/voice/audioBackend.js +134 -0
- package/dist/core/voice/stt.js +61 -6
- package/dist/core/voice/tts.js +19 -3
- package/dist/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +190 -0
- package/package.json +11 -2
package/dist/cli/v4/display.js
CHANGED
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.Display = exports.SPINNER_PHRASES = exports.TOOL_ICONS = void 0;
|
|
22
22
|
exports.iconForTool = iconForTool;
|
|
23
|
+
exports.detectConfiguredChannels = detectConfiguredChannels;
|
|
24
|
+
exports.summarizeConfiguredChannels = summarizeConfiguredChannels;
|
|
25
|
+
exports.summarizeChannelState = summarizeChannelState;
|
|
26
|
+
exports.voiceIndicator = voiceIndicator;
|
|
23
27
|
exports.previewToolArgs = previewToolArgs;
|
|
24
28
|
exports.formatToolDuration = formatToolDuration;
|
|
25
29
|
exports.formatCompactTokens = formatCompactTokens;
|
|
@@ -32,6 +36,12 @@ const marked_1 = require("marked");
|
|
|
32
36
|
const TerminalRenderer = require('marked-terminal').default ?? require('marked-terminal');
|
|
33
37
|
const skinEngine_1 = require("./skinEngine");
|
|
34
38
|
const box_1 = require("./box");
|
|
39
|
+
// Phase v4.1-reply-formatting: skin-aware markdown renderer that
|
|
40
|
+
// replaces marked-terminal's defaults with structured headers, lists,
|
|
41
|
+
// code blocks, blockquotes, and links.
|
|
42
|
+
const replyRenderer_1 = require("./replyRenderer");
|
|
43
|
+
// Optional "Sources" footer when AIDEN_CITATIONS=1 (default off).
|
|
44
|
+
const citationFooter_1 = require("./citationFooter");
|
|
35
45
|
/**
|
|
36
46
|
* Phase 26.2.7 — category emoji icons for the tool-row prefix when
|
|
37
47
|
* `AIDEN_UI_ICONS=1` is set in the environment. Default OFF (the
|
|
@@ -126,6 +136,74 @@ exports.SPINNER_PHRASES = [
|
|
|
126
136
|
'Smelting',
|
|
127
137
|
'Conjuring',
|
|
128
138
|
];
|
|
139
|
+
/**
|
|
140
|
+
* Phase v4.1-1 — boot card "channels" pill helpers. The CLI process
|
|
141
|
+
* doesn't actually run channel adapters (those live in the API server)
|
|
142
|
+
* so we report what we *can* honestly know from the environment: how
|
|
143
|
+
* many of the nine channel adapters have their credentials present.
|
|
144
|
+
*
|
|
145
|
+
* `summarizeConfiguredChannels` returns a render-ready label like
|
|
146
|
+
* `"3 configured (incl. telegram)"` for the Environment column.
|
|
147
|
+
*/
|
|
148
|
+
const CHANNEL_ENV_VARS = [
|
|
149
|
+
{ id: 'telegram', vars: ['TELEGRAM_BOT_TOKEN'] },
|
|
150
|
+
{ id: 'discord', vars: ['DISCORD_BOT_TOKEN'] },
|
|
151
|
+
{ id: 'slack', vars: ['SLACK_BOT_TOKEN', 'SLACK_APP_TOKEN'] },
|
|
152
|
+
{ id: 'whatsapp', vars: ['WHATSAPP_BUSINESS_API_KEY'] },
|
|
153
|
+
{ id: 'twilio', vars: ['TWILIO_AUTH_TOKEN'] },
|
|
154
|
+
{ id: 'imessage', vars: ['BLUEBUBBLES_PASSWORD'] },
|
|
155
|
+
{ id: 'email', vars: ['EMAIL_IMAP_PASSWORD', 'EMAIL_SMTP_PASSWORD'] },
|
|
156
|
+
];
|
|
157
|
+
function detectConfiguredChannels(env = process.env) {
|
|
158
|
+
const ids = [];
|
|
159
|
+
for (const c of CHANNEL_ENV_VARS) {
|
|
160
|
+
if (c.vars.some((v) => typeof env[v] === 'string' && env[v].trim() !== '')) {
|
|
161
|
+
ids.push(c.id);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { total: ids.length, ids, telegram: ids.includes('telegram') };
|
|
165
|
+
}
|
|
166
|
+
function summarizeConfiguredChannels(detection = detectConfiguredChannels()) {
|
|
167
|
+
if (detection.total === 0)
|
|
168
|
+
return '0 configured';
|
|
169
|
+
const suffix = detection.telegram ? ' (incl. telegram)' : '';
|
|
170
|
+
return `${detection.total} configured${suffix}`;
|
|
171
|
+
}
|
|
172
|
+
function summarizeChannelState(probe, envFallback = detectConfiguredChannels()) {
|
|
173
|
+
// No live probe (e.g. test harness with no manager) → env-only count.
|
|
174
|
+
if (!probe) {
|
|
175
|
+
if (envFallback.total === 0) {
|
|
176
|
+
return '0 configured (run /channel telegram add to enable)';
|
|
177
|
+
}
|
|
178
|
+
return `${envFallback.total} configured${envFallback.telegram ? ' (incl. telegram)' : ''}`;
|
|
179
|
+
}
|
|
180
|
+
// Phase v4.1-1.2 — conflict takes priority over the generic active /
|
|
181
|
+
// offline split. If any adapter is in the conflict state, surface
|
|
182
|
+
// that explicitly so the user has an unambiguous remediation hint.
|
|
183
|
+
const conflicted = probe.adapters.filter((a) => a.state === 'conflict');
|
|
184
|
+
if (conflicted.length > 0) {
|
|
185
|
+
const names = conflicted.map((a) => a.id).join(', ');
|
|
186
|
+
return `${conflicted.length} degraded: ${names} (conflict — /channel telegram takeover)`;
|
|
187
|
+
}
|
|
188
|
+
const active = probe.adapters.filter((a) => a.healthy);
|
|
189
|
+
const inactive = probe.adapters.filter((a) => !a.healthy);
|
|
190
|
+
if (active.length === 0) {
|
|
191
|
+
if (envFallback.total === 0) {
|
|
192
|
+
return '0 configured (run /channel telegram add to enable)';
|
|
193
|
+
}
|
|
194
|
+
// Token in env but adapter not healthy — frame it as offline so
|
|
195
|
+
// the user knows /channel telegram status is the next step.
|
|
196
|
+
const offlineNames = inactive
|
|
197
|
+
.filter((a) => envFallback.ids.includes(a.id))
|
|
198
|
+
.map((a) => a.id)
|
|
199
|
+
.join(', ');
|
|
200
|
+
return offlineNames
|
|
201
|
+
? `${envFallback.total} configured: ${offlineNames} (offline — /channel telegram status)`
|
|
202
|
+
: `${envFallback.total} configured`;
|
|
203
|
+
}
|
|
204
|
+
const parts = active.map((a) => (a.botHandle ? `${a.id} (@${a.botHandle})` : a.id));
|
|
205
|
+
return `${active.length} active: ${parts.join(', ')}`;
|
|
206
|
+
}
|
|
129
207
|
const AIDEN_BANNER = String.raw `
|
|
130
208
|
█████╗ ██╗██████╗ ███████╗███╗ ██╗
|
|
131
209
|
██╔══██╗██║██╔══██╗██╔════╝████╗ ██║
|
|
@@ -144,6 +222,14 @@ class Display {
|
|
|
144
222
|
// starts on its own line.
|
|
145
223
|
this.streamHeaderShown = false;
|
|
146
224
|
this.streamLastEndedNewline = false;
|
|
225
|
+
// Phase v4.1-reply-formatting: track the running buffered stream
|
|
226
|
+
// so streamComplete can re-render it as structured markdown
|
|
227
|
+
// (headers / lists / code blocks / blockquotes) once the full
|
|
228
|
+
// body is known. During streaming the raw text remains visible
|
|
229
|
+
// — the post-stream pass clears it via cursor-up + erase-line and
|
|
230
|
+
// reprints the formatted output.
|
|
231
|
+
this.streamBuffer = '';
|
|
232
|
+
this.streamLineCount = 0;
|
|
147
233
|
this.skin = opts.skin ?? (0, skinEngine_1.getSkinEngine)();
|
|
148
234
|
this.out = opts.stdout ?? process.stdout;
|
|
149
235
|
this.err = opts.stderr ?? process.stderr;
|
|
@@ -320,10 +406,13 @@ class Display {
|
|
|
320
406
|
* `cols() >= 80`, stacked vertically below that. Title in brand,
|
|
321
407
|
* keys in muted (padded to 11 visible chars), values in `agent`.
|
|
322
408
|
*/
|
|
323
|
-
twoColumnBlock(left, right) {
|
|
409
|
+
twoColumnBlock(left, right, opts = {}) {
|
|
324
410
|
const sk = this.skin;
|
|
325
411
|
const cols = this.cols();
|
|
326
|
-
|
|
412
|
+
// Tier-3.1b: callers can raise the side-by-side threshold (boot
|
|
413
|
+
// card prefers stacked at 70-119 cols, side-by-side only at ≥120).
|
|
414
|
+
// Default 80 preserves prior behaviour for any other caller.
|
|
415
|
+
const stacked = cols < (opts.sideBySideThreshold ?? 80);
|
|
327
416
|
const indent = ' ';
|
|
328
417
|
const KEY_PAD = 11;
|
|
329
418
|
const renderRows = (sec) => {
|
|
@@ -395,13 +484,10 @@ class Display {
|
|
|
395
484
|
const val = (s) => sk.applyColors(s, 'agent');
|
|
396
485
|
const heart = sk.applyColors('♥', 'brand');
|
|
397
486
|
if (this.cols() < 80) {
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
` ${lab('Web:')} ${val('aiden.taracod.com')}`,
|
|
403
|
-
` ${lab('Contact:')} ${val('contact@taracod.com')}`,
|
|
404
|
-
].join('\n');
|
|
487
|
+
// Tier-3.1b: single-line credits at narrow widths so the boot
|
|
488
|
+
// card stays compact. The 4-line plain fallback shipped earlier
|
|
489
|
+
// wastes vertical space when terminals already squeeze content.
|
|
490
|
+
return ` ${heart} ${m('built solo · github.com/taracodlabs/aiden · aiden.taracod.com')}`;
|
|
405
491
|
}
|
|
406
492
|
// Parchment.
|
|
407
493
|
const INTERIOR = 63;
|
|
@@ -477,6 +563,74 @@ class Display {
|
|
|
477
563
|
const elapsed = sk.applyColors(formatElapsedShort(args.elapsedMs), 'muted');
|
|
478
564
|
return ` ${provModel}${SEP}${ctxSeg}${SEP}${elapsed}`;
|
|
479
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Tier-3.1 (v4.1-tier3.1): pre-prompt status line.
|
|
568
|
+
*
|
|
569
|
+
* Single-line summary written ABOVE the input prompt on every
|
|
570
|
+
* fresh turn. Format (full, ≥76 cols):
|
|
571
|
+
*
|
|
572
|
+
* <provider>:<model> · ctx <N>/<M>k · MCP <state> · cron <state>
|
|
573
|
+
*
|
|
574
|
+
* Width-tier degrade:
|
|
575
|
+
* <52 cols → only `<provider>:<model> · ctx N/Mk`
|
|
576
|
+
* <76 cols → drop voice indicator
|
|
577
|
+
* ≥76 cols → full
|
|
578
|
+
*
|
|
579
|
+
* MCP serve mode: this helper is a pure builder; callers MUST
|
|
580
|
+
* gate the actual write on `isMcpServeMode()` from
|
|
581
|
+
* `cli/v4/uiBuild.ts`. The function itself never writes.
|
|
582
|
+
*/
|
|
583
|
+
renderStatusLine(args) {
|
|
584
|
+
const sk = this.skin;
|
|
585
|
+
const cols = args.cols ?? this.cols();
|
|
586
|
+
const SEP = sk.applyColors(' · ', 'muted');
|
|
587
|
+
const colourForState = (s) => {
|
|
588
|
+
if (s === 'active')
|
|
589
|
+
return 'success';
|
|
590
|
+
if (s === 'broken')
|
|
591
|
+
return 'error';
|
|
592
|
+
return 'muted';
|
|
593
|
+
};
|
|
594
|
+
const glyphForState = (s) => {
|
|
595
|
+
if (s === 'active')
|
|
596
|
+
return '✓'; // ✓
|
|
597
|
+
if (s === 'broken')
|
|
598
|
+
return '✗'; // ✗
|
|
599
|
+
return '-';
|
|
600
|
+
};
|
|
601
|
+
const provModel = sk.applyColors(args.provider, 'muted') +
|
|
602
|
+
sk.applyColors(':', 'muted') +
|
|
603
|
+
sk.applyColors(args.model, 'agent');
|
|
604
|
+
const ctxSeg = (() => {
|
|
605
|
+
if (args.ctxMax == null || args.ctxUsed == null)
|
|
606
|
+
return '';
|
|
607
|
+
const usedK = Math.round(args.ctxUsed / 1000);
|
|
608
|
+
const maxK = Math.max(1, Math.round(args.ctxMax / 1000));
|
|
609
|
+
return sk.applyColors(`ctx ${usedK}/${maxK}k`, 'muted');
|
|
610
|
+
})();
|
|
611
|
+
const mcpSeg = args.mcpState
|
|
612
|
+
? sk.applyColors(`MCP ${glyphForState(args.mcpState)}`, colourForState(args.mcpState))
|
|
613
|
+
: '';
|
|
614
|
+
const cronSeg = args.cronState
|
|
615
|
+
? sk.applyColors(`cron ${glyphForState(args.cronState)}`, colourForState(args.cronState))
|
|
616
|
+
: '';
|
|
617
|
+
const voiceSeg = args.voiceRecording
|
|
618
|
+
? sk.applyColors('[REC]', 'error')
|
|
619
|
+
: '';
|
|
620
|
+
// Compose with width-tier degrade.
|
|
621
|
+
const segs = [provModel];
|
|
622
|
+
if (ctxSeg)
|
|
623
|
+
segs.push(ctxSeg);
|
|
624
|
+
if (cols >= 52) {
|
|
625
|
+
if (mcpSeg)
|
|
626
|
+
segs.push(mcpSeg);
|
|
627
|
+
if (cronSeg)
|
|
628
|
+
segs.push(cronSeg);
|
|
629
|
+
}
|
|
630
|
+
if (cols >= 76 && voiceSeg)
|
|
631
|
+
segs.push(voiceSeg);
|
|
632
|
+
return ' ' + segs.join(SEP);
|
|
633
|
+
}
|
|
480
634
|
/**
|
|
481
635
|
* Optional provider-switch indicator line — emitted only when this
|
|
482
636
|
* turn ran on a different provider than the previous one. Format
|
|
@@ -657,14 +811,24 @@ class Display {
|
|
|
657
811
|
const arrow = sk.getActive().glyphs?.arrow ?? '>';
|
|
658
812
|
return `${sk.applyColors(arrow, 'tool')} ${sk.applyColors(name, 'tool')} ${sk.applyColors(serialized, 'muted')}`;
|
|
659
813
|
}
|
|
660
|
-
/**
|
|
814
|
+
/**
|
|
815
|
+
* Render markdown to ANSI via the v4.1-reply-formatting renderer
|
|
816
|
+
* (skin-aware headers / lists / code / quotes / links). Falls back
|
|
817
|
+
* to the marked-terminal default config if the structured renderer
|
|
818
|
+
* is unavailable, then raw text as a last resort.
|
|
819
|
+
*/
|
|
661
820
|
markdown(text) {
|
|
662
821
|
try {
|
|
663
|
-
|
|
664
|
-
return typeof out === 'string' ? out : String(out);
|
|
822
|
+
return (0, replyRenderer_1.getReplyRenderer)().render(text);
|
|
665
823
|
}
|
|
666
824
|
catch {
|
|
667
|
-
|
|
825
|
+
try {
|
|
826
|
+
const out = marked_1.marked.parse(text);
|
|
827
|
+
return typeof out === 'string' ? out : String(out);
|
|
828
|
+
}
|
|
829
|
+
catch {
|
|
830
|
+
return text;
|
|
831
|
+
}
|
|
668
832
|
}
|
|
669
833
|
}
|
|
670
834
|
/** Format a user turn (e.g. echoed back from history). */
|
|
@@ -809,9 +973,18 @@ class Display {
|
|
|
809
973
|
// open identically.
|
|
810
974
|
this.out.write(this.agentHeader());
|
|
811
975
|
this.streamHeaderShown = true;
|
|
976
|
+
this.streamBuffer = '';
|
|
977
|
+
this.streamLineCount = 0;
|
|
812
978
|
}
|
|
813
979
|
this.out.write(text);
|
|
814
980
|
this.streamLastEndedNewline = text.endsWith('\n');
|
|
981
|
+
// Phase v4.1-reply-formatting: track buffer + line count for the
|
|
982
|
+
// post-stream re-render. We count newlines in the OUTGOING bytes
|
|
983
|
+
// so the eraser later knows how many rows to clear.
|
|
984
|
+
this.streamBuffer += text;
|
|
985
|
+
for (let i = 0; i < text.length; i += 1)
|
|
986
|
+
if (text[i] === '\n')
|
|
987
|
+
this.streamLineCount += 1;
|
|
815
988
|
}
|
|
816
989
|
/**
|
|
817
990
|
* Mark the end of a streaming turn. Adds a trailing newline if the
|
|
@@ -824,8 +997,68 @@ class Display {
|
|
|
824
997
|
return;
|
|
825
998
|
if (!this.streamLastEndedNewline)
|
|
826
999
|
this.out.write('\n');
|
|
1000
|
+
// Phase v4.1-reply-formatting: re-render the buffered stream as
|
|
1001
|
+
// structured markdown — but ONLY when stdout is a TTY and the
|
|
1002
|
+
// buffer actually contains markdown structure worth rendering.
|
|
1003
|
+
// Plain prose with no headers / lists / fences gets left alone
|
|
1004
|
+
// (no flicker, identical output). Otherwise we erase the raw
|
|
1005
|
+
// streamed body via cursor-up + erase-line and reprint via the
|
|
1006
|
+
// skin-aware renderer.
|
|
1007
|
+
const buffered = this.streamBuffer;
|
|
1008
|
+
const lines = this.streamLineCount;
|
|
1009
|
+
this.streamBuffer = '';
|
|
1010
|
+
this.streamLineCount = 0;
|
|
827
1011
|
this.streamHeaderShown = false;
|
|
828
1012
|
this.streamLastEndedNewline = false;
|
|
1013
|
+
if (!this.out.isTTY)
|
|
1014
|
+
return;
|
|
1015
|
+
if (process.env.AIDEN_NO_REFORMAT === '1')
|
|
1016
|
+
return;
|
|
1017
|
+
// Cheap heuristic: only re-render when there's structure that
|
|
1018
|
+
// benefits from formatting. Avoids flicker on short prose replies.
|
|
1019
|
+
const hasStructure = /^#{1,6}\s/m.test(buffered) ||
|
|
1020
|
+
/^\s*[-*+]\s/m.test(buffered) ||
|
|
1021
|
+
/^\s*\d+\.\s/m.test(buffered) ||
|
|
1022
|
+
/^>\s/m.test(buffered) ||
|
|
1023
|
+
/```/.test(buffered);
|
|
1024
|
+
if (!hasStructure)
|
|
1025
|
+
return;
|
|
1026
|
+
try {
|
|
1027
|
+
// Erase the raw streamed body in place. We wrote `lines + 1`
|
|
1028
|
+
// rows (header + body) — the header (`┃ Aiden`) stays, so we
|
|
1029
|
+
// walk back `lines` rows and clear each.
|
|
1030
|
+
// `\x1b[<n>F` = cursor-up-and-to-column-0 N times.
|
|
1031
|
+
// `\x1b[J` = erase from cursor to end of screen.
|
|
1032
|
+
if (lines > 0) {
|
|
1033
|
+
this.out.write(`\x1b[${lines}F\x1b[J`);
|
|
1034
|
+
}
|
|
1035
|
+
const formatted = this.markdown(buffered).trimEnd();
|
|
1036
|
+
const indented = formatted
|
|
1037
|
+
.split('\n')
|
|
1038
|
+
.map((ln) => (ln ? ` ${ln}` : ''))
|
|
1039
|
+
.join('\n');
|
|
1040
|
+
this.out.write(indented + '\n');
|
|
1041
|
+
}
|
|
1042
|
+
catch {
|
|
1043
|
+
// If anything goes wrong with the re-render, leave the raw
|
|
1044
|
+
// streamed text in place — graceful degradation beats flicker
|
|
1045
|
+
// + corrupted output.
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Phase v4.1-reply-formatting: render the optional "Sources"
|
|
1050
|
+
* footer when AIDEN_CITATIONS=1 and the trace has fetch-class
|
|
1051
|
+
* tool calls. Pure write; safe to call after a turn completes
|
|
1052
|
+
* regardless of streaming/non-streaming. No-op when the env gate
|
|
1053
|
+
* is off or no sources surface.
|
|
1054
|
+
*/
|
|
1055
|
+
printCitationFooter(trace) {
|
|
1056
|
+
const footer = (0, citationFooter_1.renderCitationFooter)(trace);
|
|
1057
|
+
if (!footer)
|
|
1058
|
+
return;
|
|
1059
|
+
if (!this.out.isTTY)
|
|
1060
|
+
return;
|
|
1061
|
+
this.out.write(footer);
|
|
829
1062
|
}
|
|
830
1063
|
/**
|
|
831
1064
|
* Inline tool indicator. Printed between deltas when a tool call
|
|
@@ -842,6 +1075,57 @@ class Display {
|
|
|
842
1075
|
}
|
|
843
1076
|
}
|
|
844
1077
|
exports.Display = Display;
|
|
1078
|
+
/**
|
|
1079
|
+
* Render a voice-mode status line. Pure builder — caller writes the
|
|
1080
|
+
* result via Display.streamPartial / direct stdout. Includes a
|
|
1081
|
+
* RMS-driven block bar when state is `recording`. The bar uses 8
|
|
1082
|
+
* unicode block-fill levels (▏ to █) over a 0..1500 RMS range,
|
|
1083
|
+
* which covers the practical loud-speech ceiling without saturating
|
|
1084
|
+
* for any reasonable mic preamp.
|
|
1085
|
+
*
|
|
1086
|
+
* Tier-3.1 (v4.1-tier3.1): replaced 🎤 / 🔊 emoji with text-state
|
|
1087
|
+
* badges `[REC]` (recording, error/red) and `[PLAY]` (speaking,
|
|
1088
|
+
* success/green). Idle/listening/transcribing get the neutral
|
|
1089
|
+
* `[VOX]` badge in muted colour. Same 4-char inner width keeps
|
|
1090
|
+
* subsequent column alignment intact.
|
|
1091
|
+
*
|
|
1092
|
+
* Examples:
|
|
1093
|
+
* voiceIndicator('idle') → '[VOX] idle (Space to talk)'
|
|
1094
|
+
* voiceIndicator('listening') → '[VOX] listening...'
|
|
1095
|
+
* voiceIndicator('recording', 800) → '[REC] ▌▌▌▌▌▌ recording (Space to stop, Esc to cancel)'
|
|
1096
|
+
* voiceIndicator('transcribing') → '[VOX] transcribing...'
|
|
1097
|
+
* voiceIndicator('speaking') → '[PLAY] speaking...'
|
|
1098
|
+
*/
|
|
1099
|
+
function voiceIndicator(state, rms = 0) {
|
|
1100
|
+
const skin = (0, skinEngine_1.getSkinEngine)();
|
|
1101
|
+
const recBadge = skin.applyColors('[REC]', 'error');
|
|
1102
|
+
const playBadge = skin.applyColors('[PLAY]', 'success');
|
|
1103
|
+
const voxBadge = skin.applyColors('[VOX]', 'muted');
|
|
1104
|
+
switch (state) {
|
|
1105
|
+
case 'idle':
|
|
1106
|
+
return `${voxBadge} idle (Space to talk)`;
|
|
1107
|
+
case 'listening':
|
|
1108
|
+
return `${voxBadge} listening...`;
|
|
1109
|
+
case 'recording': {
|
|
1110
|
+
const bar = renderRmsBar(rms);
|
|
1111
|
+
return `${recBadge} ${bar} recording (Space to stop, Esc to cancel)`;
|
|
1112
|
+
}
|
|
1113
|
+
case 'transcribing':
|
|
1114
|
+
return `${voxBadge} transcribing...`;
|
|
1115
|
+
case 'speaking':
|
|
1116
|
+
return `${playBadge} speaking...`;
|
|
1117
|
+
default:
|
|
1118
|
+
return `${voxBadge} ${state}`;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
const BAR_WIDTH = 12;
|
|
1122
|
+
const BAR_FULL_RMS = 1500;
|
|
1123
|
+
/** RMS-driven horizontal block bar. 0..BAR_FULL_RMS → 0..BAR_WIDTH chars. */
|
|
1124
|
+
function renderRmsBar(rms) {
|
|
1125
|
+
const safe = Math.max(0, Math.min(rms, BAR_FULL_RMS));
|
|
1126
|
+
const filled = Math.round((safe / BAR_FULL_RMS) * BAR_WIDTH);
|
|
1127
|
+
return '▌'.repeat(filled) + ' '.repeat(BAR_WIDTH - filled);
|
|
1128
|
+
}
|
|
845
1129
|
// ── Phase 23.5 — tool row helpers ─────────────────────────────────────
|
|
846
1130
|
/** Width the tool name is padded to so brackets line up across rows. */
|
|
847
1131
|
const TOOL_ROW_NAME_PAD = 16;
|
package/dist/cli/v4/doctor.js
CHANGED
|
@@ -18,6 +18,39 @@
|
|
|
18
18
|
* will normally be sub-second.
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
21
54
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
55
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
56
|
};
|
|
@@ -34,6 +67,7 @@ exports.checkNpxAvailable = checkNpxAvailable;
|
|
|
34
67
|
exports.checkSkillsDir = checkSkillsDir;
|
|
35
68
|
exports.checkBundledManifest = checkBundledManifest;
|
|
36
69
|
exports.checkPlatformPaths = checkPlatformPaths;
|
|
70
|
+
exports.checkAudioBackend = checkAudioBackend;
|
|
37
71
|
exports.checkLicense = checkLicense;
|
|
38
72
|
exports.checkUpdate = checkUpdate;
|
|
39
73
|
exports.checkLogsWritable = checkLogsWritable;
|
|
@@ -48,6 +82,7 @@ const paths_1 = require("../../core/v4/paths");
|
|
|
48
82
|
const license_1 = require("../../core/v4/license");
|
|
49
83
|
const checkUpdate_1 = require("../../core/v4/update/checkUpdate");
|
|
50
84
|
const box_1 = require("./box");
|
|
85
|
+
const audioBackend_1 = require("../../core/voice/audioBackend");
|
|
51
86
|
const DEFAULT_TIMEOUT_MS = 3000;
|
|
52
87
|
/** Wrap a promise with a timeout. The timed-out path resolves to the fallback result. */
|
|
53
88
|
async function withTimeout(p, ms, fallback) {
|
|
@@ -421,6 +456,44 @@ async function checkPlatformPaths(paths) {
|
|
|
421
456
|
};
|
|
422
457
|
}
|
|
423
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Phase v4.1-cross-platform: probe per-OS audio backends so a user
|
|
461
|
+
* who installs Aiden fresh on Linux/macOS gets a clear "install sox"
|
|
462
|
+
* pointer instead of a stack trace the first time they run /voice.
|
|
463
|
+
*
|
|
464
|
+
* Reports as INFO not FAILURE — the agent loop works fine without
|
|
465
|
+
* voice support; we just want to surface the install hint.
|
|
466
|
+
*/
|
|
467
|
+
async function checkAudioBackend() {
|
|
468
|
+
const t0 = Date.now();
|
|
469
|
+
const playback = (0, audioBackend_1.detectBackend)('playback');
|
|
470
|
+
const record = (0, audioBackend_1.detectBackend)('record');
|
|
471
|
+
const known = {
|
|
472
|
+
playback: (0, audioBackend_1.listKnownBackends)('playback').map((b) => b.label),
|
|
473
|
+
record: (0, audioBackend_1.listKnownBackends)('record').map((b) => b.label),
|
|
474
|
+
};
|
|
475
|
+
if (playback && record) {
|
|
476
|
+
return {
|
|
477
|
+
name: 'audio backend',
|
|
478
|
+
passed: true,
|
|
479
|
+
message: `${process.platform}: playback=${playback.label} · record=${record.label}`,
|
|
480
|
+
durationMs: Date.now() - t0,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
// Pass=true — informational. The suggestion carries the fix.
|
|
484
|
+
const missing = [];
|
|
485
|
+
if (!playback)
|
|
486
|
+
missing.push((0, audioBackend_1.missingBackendMessage)('playback'));
|
|
487
|
+
if (!record)
|
|
488
|
+
missing.push((0, audioBackend_1.missingBackendMessage)('record'));
|
|
489
|
+
return {
|
|
490
|
+
name: 'audio backend',
|
|
491
|
+
passed: true,
|
|
492
|
+
message: `${process.platform}: voice features will not work — backends missing (known: ${[...new Set([...known.playback, ...known.record])].join(', ') || 'none'})`,
|
|
493
|
+
suggestion: missing.join(' || '),
|
|
494
|
+
durationMs: Date.now() - t0,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
424
497
|
/**
|
|
425
498
|
* Phase 20 Task 7: license-server reachability + local cache state.
|
|
426
499
|
* `/doctor` shouldn't block when offline — we treat both "no local cache
|
|
@@ -586,6 +659,7 @@ async function runDoctor(opts = {}) {
|
|
|
586
659
|
results.push(await checkBundledManifest(paths));
|
|
587
660
|
results.push(await checkPlatformPaths(paths));
|
|
588
661
|
results.push(await checkLogsWritable(paths));
|
|
662
|
+
results.push(await checkAudioBackend());
|
|
589
663
|
// Phase 20 Task 7: license + update health.
|
|
590
664
|
results.push(await checkLicense({ paths, fetchImpl, timeoutMs }));
|
|
591
665
|
results.push(await checkUpdate({ paths, installedVersion, timeoutMs }));
|
|
@@ -719,6 +793,33 @@ async function runDoctorCli(opts) {
|
|
|
719
793
|
}
|
|
720
794
|
}
|
|
721
795
|
process.stdout.write(`\n${report.passed ? 'all checks passed' : 'some checks failed'} in ${report.totalMs} ms\n`);
|
|
722
|
-
|
|
796
|
+
// Phase v4.1.1-oauth-fix Phase 5: discoverability hint for the deep
|
|
797
|
+
// mode. Only emitted in standard mode — if the user already passed
|
|
798
|
+
// `--providers`, the section right below this is the answer to the hint.
|
|
799
|
+
if (!opts?.liveness) {
|
|
800
|
+
process.stdout.write(' hint: Run `aiden doctor --providers` for live provider checks\n');
|
|
801
|
+
}
|
|
802
|
+
// Phase v4.1.1-oauth-fix Phase 4: opt-in provider liveness.
|
|
803
|
+
// Runs after the standard report so a failed config check is visible
|
|
804
|
+
// before the network probes start. Section header + summary line are
|
|
805
|
+
// rendered by doctorLiveness.renderProviderLivenessSection so the
|
|
806
|
+
// formatting stays alongside its consumer.
|
|
807
|
+
let livenessFailed = false;
|
|
808
|
+
if (opts?.liveness) {
|
|
809
|
+
process.stdout.write('\n Running provider liveness checks...\n');
|
|
810
|
+
const { runProviderLiveness, renderProviderLivenessSection } = await Promise.resolve().then(() => __importStar(require('./doctorLiveness')));
|
|
811
|
+
const paths = opts.paths ?? (0, paths_1.resolveAidenPaths)();
|
|
812
|
+
const { results, summary } = await runProviderLiveness({
|
|
813
|
+
paths,
|
|
814
|
+
env: opts.env,
|
|
815
|
+
fetchImpl: opts.fetchImpl,
|
|
816
|
+
timeoutMs: opts.livenessTimeoutMs,
|
|
817
|
+
});
|
|
818
|
+
process.stdout.write(renderProviderLivenessSection(results, summary));
|
|
819
|
+
livenessFailed = summary.red > 0;
|
|
820
|
+
}
|
|
821
|
+
// Liveness reds count toward the overall exit code so CI / scripts
|
|
822
|
+
// can `aiden doctor --providers && deploy`.
|
|
823
|
+
process.exitCode = (report.passed && !livenessFailed) ? 0 : 1;
|
|
723
824
|
return report;
|
|
724
825
|
}
|