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
package/dist/cli/v4/aidenCLI.js
CHANGED
|
@@ -1449,6 +1449,12 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
1449
1449
|
mcpClient,
|
|
1450
1450
|
providerId,
|
|
1451
1451
|
modelId,
|
|
1452
|
+
// v4.1.3-prebump: forward the precedence-case label so the boot
|
|
1453
|
+
// card can render a "where this choice came from" annotation.
|
|
1454
|
+
// The case-3 (persisted-config) branch was confusing users who
|
|
1455
|
+
// expected auto-pick to kick in — surfacing the source closes the
|
|
1456
|
+
// information asymmetry.
|
|
1457
|
+
bootSource,
|
|
1452
1458
|
resumeSessionId,
|
|
1453
1459
|
fallbackAdapter,
|
|
1454
1460
|
personalityManager,
|
|
@@ -1475,6 +1481,10 @@ async function runInteractiveChat(cliOpts, opts) {
|
|
|
1475
1481
|
config: runtime.config,
|
|
1476
1482
|
initialProviderId: runtime.providerId,
|
|
1477
1483
|
initialModelId: runtime.modelId,
|
|
1484
|
+
// v4.1.3-prebump: pass through the precedence-case label so the
|
|
1485
|
+
// boot card can render a dim source annotation under the version
|
|
1486
|
+
// pill ("persisted from prior session" / "auto-picked" / …).
|
|
1487
|
+
initialBootSource: runtime.bootSource,
|
|
1478
1488
|
resumeSessionId: runtime.resumeSessionId,
|
|
1479
1489
|
yoloMode: !!cliOpts.yolo,
|
|
1480
1490
|
fallbackAdapter: runtime.fallbackAdapter,
|
package/dist/cli/v4/callbacks.js
CHANGED
|
@@ -82,6 +82,15 @@ class CliCallbacks {
|
|
|
82
82
|
}
|
|
83
83
|
this.beforeFirstToolHook = undefined;
|
|
84
84
|
}
|
|
85
|
+
// v4.1.4 reply-quality polish — Part 1.6. Pause activity
|
|
86
|
+
// indicator BEFORE the tool row writes so the indicator's line
|
|
87
|
+
// is clean when the row lands. Fires for every tool, not just
|
|
88
|
+
// the first. Defensive try/catch — a misbehaving hook must not
|
|
89
|
+
// block tool dispatch.
|
|
90
|
+
try {
|
|
91
|
+
this.beforeToolHook?.();
|
|
92
|
+
}
|
|
93
|
+
catch { /* defensive */ }
|
|
85
94
|
const handle = this.display.toolRow(call.name, call.arguments);
|
|
86
95
|
this.toolRows.set(call.id, handle);
|
|
87
96
|
this.toolStartTimes.set(call.id, Date.now());
|
|
@@ -92,19 +101,61 @@ class CliCallbacks {
|
|
|
92
101
|
const startedAt = this.toolStartTimes.get(call.id);
|
|
93
102
|
this.toolRows.delete(call.id);
|
|
94
103
|
this.toolStartTimes.delete(call.id);
|
|
95
|
-
if (!handle || startedAt === undefined)
|
|
104
|
+
if (!handle || startedAt === undefined) {
|
|
105
|
+
// Even if we lost the handle, the indicator may still need to
|
|
106
|
+
// be re-armed so the next gap shows activity. Tool-name-aware
|
|
107
|
+
// verb selection happens in the hook itself.
|
|
108
|
+
try {
|
|
109
|
+
this.afterEachToolHook?.(call.name);
|
|
110
|
+
}
|
|
111
|
+
catch { /* defensive */ }
|
|
96
112
|
return;
|
|
113
|
+
}
|
|
97
114
|
const ms = Date.now() - startedAt;
|
|
98
115
|
const err = result?.error;
|
|
99
116
|
if (typeof err === 'string' && err.includes('URL provenance gate')) {
|
|
100
117
|
handle.blocked();
|
|
101
118
|
return;
|
|
102
119
|
}
|
|
120
|
+
// v4.1.4 reply-quality polish — Part 1.6. Helper used by ALL
|
|
121
|
+
// outcome branches below so the activity indicator gets re-armed
|
|
122
|
+
// for the gap that follows this tool (next tool, or final reply).
|
|
123
|
+
// Tool-name-aware verb selection happens in the hook (chatSession
|
|
124
|
+
// wires it through `verbForActivity`).
|
|
125
|
+
const fireAfter = () => {
|
|
126
|
+
try {
|
|
127
|
+
this.afterEachToolHook?.(call.name);
|
|
128
|
+
}
|
|
129
|
+
catch { /* defensive */ }
|
|
130
|
+
};
|
|
131
|
+
if (typeof err === 'string' && err.includes('URL provenance gate')) {
|
|
132
|
+
handle.blocked();
|
|
133
|
+
fireAfter();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
103
136
|
if (err) {
|
|
104
137
|
handle.fail(ms);
|
|
138
|
+
// v4.1.3-essentials: when the tool's failure payload includes a
|
|
139
|
+
// structured capability card (auth missing, platform unsupported),
|
|
140
|
+
// render the card immediately after the fail row. The card sits
|
|
141
|
+
// on its own multi-line block — the fail row is still useful as
|
|
142
|
+
// the action timeline anchor; the card adds the state assessment
|
|
143
|
+
// the user actually needs. No card → plain failure surface.
|
|
144
|
+
if (result?.capabilityCard) {
|
|
145
|
+
this.display.capabilityCard(result.capabilityCard);
|
|
146
|
+
}
|
|
147
|
+
fireAfter();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// v4.1.3-repl-polish: degraded outcome — tool completed but with a
|
|
151
|
+
// partial / best-effort result. Show in trail yellow instead of silent.
|
|
152
|
+
if (result?.degraded) {
|
|
153
|
+
handle.degraded(ms, result.degradedReason);
|
|
154
|
+
fireAfter();
|
|
105
155
|
return;
|
|
106
156
|
}
|
|
107
157
|
handle.ok(ms);
|
|
158
|
+
fireAfter();
|
|
108
159
|
};
|
|
109
160
|
/** ApprovalEngine.callbacks.promptUser */
|
|
110
161
|
this.promptApproval = async (req) => {
|
|
@@ -171,23 +222,29 @@ Reply with ONE word: safe, caution, or dangerous.`;
|
|
|
171
222
|
return false;
|
|
172
223
|
}
|
|
173
224
|
};
|
|
174
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* PlannerGuard sink. v4.1.4 Phase 3b' (Q-Planner): moved to
|
|
227
|
+
* verbose-only. The default `normal` mode previously emitted
|
|
228
|
+
* `[planner] kept N tools (reason)` mid-execution, which collided
|
|
229
|
+
* visually with the activity indicator's single-line paint and
|
|
230
|
+
* with streamed deltas. Users running with the default verbose
|
|
231
|
+
* level should see a clean execution surface — planner-guard
|
|
232
|
+
* decisions are useful for debugging but noise during normal use.
|
|
233
|
+
*
|
|
234
|
+
* `verbose` mode keeps the full breakdown for debugging. `compact`
|
|
235
|
+
* stays silent (unchanged).
|
|
236
|
+
*/
|
|
175
237
|
this.onPlannerGuardDecision = (decision) => {
|
|
176
238
|
if (this.verboseMode === 'compact')
|
|
177
239
|
return;
|
|
178
|
-
if (
|
|
240
|
+
if (this.verboseMode !== 'verbose')
|
|
179
241
|
return;
|
|
180
|
-
if (
|
|
181
|
-
const conf = decision.confidence !== undefined
|
|
182
|
-
? ` (conf ${decision.confidence.toFixed(2)})`
|
|
183
|
-
: '';
|
|
184
|
-
this.display.dim(`[planner] ${decision.reason}${conf}: kept ${decision.selectedTools.length} / dropped ${decision.excludedTools.length}`);
|
|
242
|
+
if (decision.reason === 'no_filter')
|
|
185
243
|
return;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
244
|
+
const conf = decision.confidence !== undefined
|
|
245
|
+
? ` (conf ${decision.confidence.toFixed(2)})`
|
|
246
|
+
: '';
|
|
247
|
+
this.display.dim(`[planner] ${decision.reason}${conf}: kept ${decision.selectedTools.length} / dropped ${decision.excludedTools.length}`);
|
|
191
248
|
};
|
|
192
249
|
/**
|
|
193
250
|
* Phase v4.1-skill-mining — post-turn cue when the miner has
|
|
@@ -265,6 +322,21 @@ Reply with ONE word: safe, caution, or dangerous.`;
|
|
|
265
322
|
this.beforeFirstToolHook = fn;
|
|
266
323
|
this.firstToolFiredThisTurn = false;
|
|
267
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* v4.1.4 reply-quality polish — Part 1.6.
|
|
327
|
+
*
|
|
328
|
+
* Register paired hooks so chatSession can pause the activity
|
|
329
|
+
* indicator while a tool row writes, and resume it (with a fresh
|
|
330
|
+
* verb derived from the just-completed tool) in the gap before the
|
|
331
|
+
* next tool fires or the final reply arrives.
|
|
332
|
+
*
|
|
333
|
+
* Both fire for EVERY tool, not just the first. Either can be
|
|
334
|
+
* omitted independently. Cleared between turns by passing `undefined`.
|
|
335
|
+
*/
|
|
336
|
+
setActivityIndicatorHooks(opts) {
|
|
337
|
+
this.beforeToolHook = opts.beforeTool;
|
|
338
|
+
this.afterEachToolHook = opts.afterEachTool;
|
|
339
|
+
}
|
|
268
340
|
}
|
|
269
341
|
exports.CliCallbacks = CliCallbacks;
|
|
270
342
|
// Tier-3.1 (v4.1-tier3.1): replaced 🟢/🟡/🔴 emoji badges with
|
|
@@ -59,6 +59,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
59
59
|
exports.BOOT_TRY_HINT = exports.ChatSession = void 0;
|
|
60
60
|
exports.parseSessionBulletsResponse = parseSessionBulletsResponse;
|
|
61
61
|
exports.renderCommandLabel = renderCommandLabel;
|
|
62
|
+
exports.bootSourceLabel = bootSourceLabel;
|
|
62
63
|
exports.detectOS = detectOS;
|
|
63
64
|
exports.detectShell = detectShell;
|
|
64
65
|
exports.formatStatusState = formatStatusState;
|
|
@@ -69,12 +70,21 @@ exports.formatTokens = formatTokens;
|
|
|
69
70
|
exports.formatDuration = formatDuration;
|
|
70
71
|
exports.renderMemoryConfirmations = renderMemoryConfirmations;
|
|
71
72
|
const display_1 = require("./display");
|
|
73
|
+
// v4.1.4 Part 1.6 — per-turn token progress bar. Fed by `onProgress`
|
|
74
|
+
// events from the streaming adapter; hidden when the adapter doesn't
|
|
75
|
+
// emit progress (honest degradation).
|
|
76
|
+
const progressBar_1 = require("./display/progressBar");
|
|
72
77
|
const uiBuild_1 = require("./uiBuild");
|
|
73
78
|
const sessionSummaryGate_1 = require("./sessionSummaryGate");
|
|
74
79
|
const aidenPrompt_1 = __importDefault(require("./aidenPrompt"));
|
|
75
80
|
const historyStore_1 = require("./historyStore");
|
|
76
81
|
const modelMetadata_1 = require("../../core/v4/modelMetadata");
|
|
82
|
+
// v4.1.3-prebump: classify provider errors so the catch path can show
|
|
83
|
+
// a tailored action hint (e.g. groq 413 → "switch to chatgpt-plus")
|
|
84
|
+
// instead of the generic "/model or aiden doctor" line.
|
|
85
|
+
const errors_1 = require("../../providers/v4/errors");
|
|
77
86
|
const sessionDistiller_1 = require("../../core/v4/sessionDistiller");
|
|
87
|
+
const sessionEndCard_1 = require("./display/sessionEndCard");
|
|
78
88
|
const version_1 = require("../../core/version");
|
|
79
89
|
const distillationStore_1 = require("../../core/v4/distillationStore");
|
|
80
90
|
const promotionCandidates_1 = require("../../core/v4/promotionCandidates");
|
|
@@ -154,7 +164,15 @@ const STATUS_BAR_WIDTH = 10;
|
|
|
154
164
|
* Above this we abandon the LLM half (still write a deterministic-
|
|
155
165
|
* only distillation so the session isn't lost) and exit honestly.
|
|
156
166
|
*/
|
|
157
|
-
|
|
167
|
+
/**
|
|
168
|
+
* v4.1.3-essentials distillation-fix: bumped 4000 → 12000ms in
|
|
169
|
+
* lockstep with `sessionDistiller.DEFAULT_TIMEOUT_MS`. Same
|
|
170
|
+
* rationale — chatgpt-plus Codex cold-start latency for 800-token
|
|
171
|
+
* summaries regularly exceeds 4s, killing the distillation +
|
|
172
|
+
* promotion-prompt path. Env override `AIDEN_SUMMARY_TIMEOUT_MS`
|
|
173
|
+
* still respected.
|
|
174
|
+
*/
|
|
175
|
+
const SUMMARY_TIMEOUT_MS_DEFAULT = 12000;
|
|
158
176
|
function resolveSummaryTimeoutMs() {
|
|
159
177
|
const raw = process.env.AIDEN_SUMMARY_TIMEOUT_MS;
|
|
160
178
|
if (!raw)
|
|
@@ -162,6 +180,35 @@ function resolveSummaryTimeoutMs() {
|
|
|
162
180
|
const parsed = Number.parseInt(raw, 10);
|
|
163
181
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : SUMMARY_TIMEOUT_MS_DEFAULT;
|
|
164
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* v4.1.3-prebump: map a providerBootSelector precedence-case label to
|
|
185
|
+
* a human-readable hint rendered under the boot card's status pills.
|
|
186
|
+
*
|
|
187
|
+
* Returns `null` for the explicit-selection cases (`cli-flag`, with-or-
|
|
188
|
+
* without -partial) where the source isn't surprising. Annotates the
|
|
189
|
+
* persisted-config / auto-priority / hardcoded-fallback paths so users
|
|
190
|
+
* understand "why this provider, why now".
|
|
191
|
+
*
|
|
192
|
+
* Pure helper — exported for unit testing.
|
|
193
|
+
*/
|
|
194
|
+
function bootSourceLabel(source) {
|
|
195
|
+
switch (source) {
|
|
196
|
+
case 'persisted-config':
|
|
197
|
+
return '(persisted from prior session — /model to change)';
|
|
198
|
+
case 'config-partial':
|
|
199
|
+
return '(partial config + auto-resolved companion)';
|
|
200
|
+
case 'auto-priority':
|
|
201
|
+
return '(auto-picked — first authed provider)';
|
|
202
|
+
case 'hardcoded-fallback':
|
|
203
|
+
return '(no authed providers — using legacy default)';
|
|
204
|
+
case 'cli-flag':
|
|
205
|
+
case 'cli-flag-partial':
|
|
206
|
+
// Explicit CLI override — user knows why; no annotation.
|
|
207
|
+
return null;
|
|
208
|
+
default:
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
165
212
|
class ChatSession {
|
|
166
213
|
constructor(opts) {
|
|
167
214
|
this.opts = opts;
|
|
@@ -208,6 +255,13 @@ class ChatSession {
|
|
|
208
255
|
* populated alongside it after a verified write.
|
|
209
256
|
*/
|
|
210
257
|
this.lastDistillation = null;
|
|
258
|
+
/**
|
|
259
|
+
* Absolute path the most recent distillation JSON was written to.
|
|
260
|
+
* Captured at write-time and surfaced in the session-end card so the
|
|
261
|
+
* user has a concrete artifact to inspect or feed to recall_session.
|
|
262
|
+
* Null when the write failed or no distillation has been produced.
|
|
263
|
+
*/
|
|
264
|
+
this.lastDistillationPath = null;
|
|
211
265
|
this.currentProviderId = opts.initialProviderId;
|
|
212
266
|
this.currentModelId = opts.initialModelId;
|
|
213
267
|
this.modelMetadata = opts.modelMetadata ?? new modelMetadata_1.ModelMetadata();
|
|
@@ -298,6 +352,14 @@ class ChatSession {
|
|
|
298
352
|
catch (err) {
|
|
299
353
|
this.opts.display.warn(`Session summary skipped on ${sig}: ${err.message}`);
|
|
300
354
|
}
|
|
355
|
+
// v4.1.3-repl-polish: render session-end card before farewell when
|
|
356
|
+
// a distillation was written this session. Pass the on-disk path
|
|
357
|
+
// so the card surfaces the artifact location to the user.
|
|
358
|
+
if (this.lastDistillation) {
|
|
359
|
+
for (const line of (0, sessionEndCard_1.renderSessionEndCard)(this.lastDistillation, (t, k) => this.opts.display.applyColors(t, k), this.lastDistillationPath)) {
|
|
360
|
+
this.opts.display.write(line + '\n');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
301
363
|
this.opts.display.dim('Goodbye.');
|
|
302
364
|
process.exit(0);
|
|
303
365
|
};
|
|
@@ -356,9 +418,22 @@ class ChatSession {
|
|
|
356
418
|
// Tier-3-essentials: hard-clear the screen on terminal resize so
|
|
357
419
|
// dropdown re-renders + previous prompt frames don't ghost into
|
|
358
420
|
// the new viewport. No-op on non-TTY / MCP serve mode.
|
|
421
|
+
//
|
|
422
|
+
// v4.1.4 reply-quality polish: also drop the per-chunk stream row
|
|
423
|
+
// counter so a mid-stream resize doesn't try to erase rows that
|
|
424
|
+
// the hard-clear already removed. See `resetStreamFrameForResize`
|
|
425
|
+
// in display.ts for the rationale.
|
|
359
426
|
const restoreResizeGuard = this.opts.promptApi
|
|
360
427
|
? () => { }
|
|
361
|
-
: (0, resizeGuard_1.installResizeGuard)(
|
|
428
|
+
: (0, resizeGuard_1.installResizeGuard)({
|
|
429
|
+
onCleared: () => {
|
|
430
|
+
try {
|
|
431
|
+
this.opts.display
|
|
432
|
+
.resetStreamFrameForResize?.();
|
|
433
|
+
}
|
|
434
|
+
catch { /* defensive — never break the resize listener */ }
|
|
435
|
+
},
|
|
436
|
+
});
|
|
362
437
|
try {
|
|
363
438
|
while (iter < max) {
|
|
364
439
|
iter += 1;
|
|
@@ -430,6 +505,12 @@ class ChatSession {
|
|
|
430
505
|
// is resumed in the same process (not today's behavior),
|
|
431
506
|
// otherwise they're skipped — documented in commit.
|
|
432
507
|
await this.maybeRunPromotion(promptApi);
|
|
508
|
+
// v4.1.3-repl-polish: session-end card before farewell.
|
|
509
|
+
if (this.lastDistillation) {
|
|
510
|
+
for (const line of (0, sessionEndCard_1.renderSessionEndCard)(this.lastDistillation, (t, k) => this.opts.display.applyColors(t, k), this.lastDistillationPath)) {
|
|
511
|
+
this.opts.display.write(line + '\n');
|
|
512
|
+
}
|
|
513
|
+
}
|
|
433
514
|
break;
|
|
434
515
|
}
|
|
435
516
|
if (result.clearHistory)
|
|
@@ -555,6 +636,15 @@ class ChatSession {
|
|
|
555
636
|
toolTrace: this.sessionToolTrace,
|
|
556
637
|
auxiliaryClient: this.opts.auxiliaryClient,
|
|
557
638
|
timeoutMs,
|
|
639
|
+
// v4.1.3-essentials distillation-fix: route the new
|
|
640
|
+
// diagnostic signal to a dim line so the user can see WHICH
|
|
641
|
+
// of the three failure classes fired (timeout / call-fail /
|
|
642
|
+
// unparseable JSON). Before this hook, all three converged
|
|
643
|
+
// on a silent `partial:true` and the downstream "no bullets"
|
|
644
|
+
// warning didn't distinguish them.
|
|
645
|
+
onDiagnostic: (msg) => {
|
|
646
|
+
this.opts.display.dim(`[distill] ${msg}`);
|
|
647
|
+
},
|
|
558
648
|
});
|
|
559
649
|
}
|
|
560
650
|
catch (err) {
|
|
@@ -569,6 +659,7 @@ class ChatSession {
|
|
|
569
659
|
const dir = node_path_1.default.join(this.opts.paths.root, 'distillations');
|
|
570
660
|
try {
|
|
571
661
|
const file = await (0, distillationStore_1.writeDistillation)(dir, dist);
|
|
662
|
+
this.lastDistillationPath = file;
|
|
572
663
|
this.opts.display.dim(`Session distillation${dist.partial ? ' (partial)' : ''} saved to ${file}`);
|
|
573
664
|
}
|
|
574
665
|
catch (err) {
|
|
@@ -811,40 +902,105 @@ class ChatSession {
|
|
|
811
902
|
const baseHistory = newHistory.length > 0
|
|
812
903
|
? [...this.history, ...newHistory, userMsg]
|
|
813
904
|
: [...this.history, userMsg];
|
|
814
|
-
// Phase 16c: streaming gated on display.streaming config
|
|
815
|
-
//
|
|
816
|
-
//
|
|
905
|
+
// Phase 16c: streaming gated on display.streaming config.
|
|
906
|
+
// v4.1.4 Part 1.6: PRODUCTION DEFAULT FLIPPED FROM FALSE TO TRUE.
|
|
907
|
+
// Streaming delivers the activity indicator, tool-row live tick,
|
|
908
|
+
// and token progress bar that the user feedback ("after prompt i
|
|
909
|
+
// just see output") was specifically asking for. Users who
|
|
910
|
+
// explicitly set `display.streaming: false` in config still opt
|
|
911
|
+
// out; the change affects only the default for users who never
|
|
912
|
+
// touched the flag.
|
|
913
|
+
//
|
|
914
|
+
// Test-stub fallback (no ConfigManager) stays at `false` so
|
|
915
|
+
// existing tests that depended on the non-streaming code path
|
|
916
|
+
// don't have to be rewritten in this slice — they exercise the
|
|
917
|
+
// batch-call path that production users on Ollama / non-streaming
|
|
918
|
+
// adapters still hit naturally.
|
|
817
919
|
const streamingEnabled = typeof this.opts.config?.getValue === 'function'
|
|
818
|
-
? this.opts.config.getValue('display.streaming',
|
|
920
|
+
? this.opts.config.getValue('display.streaming', true) === true
|
|
819
921
|
: false;
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
|
|
922
|
+
// v4.1.4 reply-quality polish — Part 1.6. Activity indicator
|
|
923
|
+
// replaces the prior single-shot spinner. Pause/resume hooks make
|
|
924
|
+
// the indicator cooperate with tool rows: it pauses before each
|
|
925
|
+
// tool row writes and resumes (with a tool-aware verb) in the
|
|
926
|
+
// gap that follows, so the user always sees activity feedback
|
|
927
|
+
// during model-thinking time — not just the pre-first-token gap.
|
|
928
|
+
//
|
|
929
|
+
// Initial verb is "thinking" (pre-tools phase). After each tool
|
|
930
|
+
// completes, `verbForActivity(toolName, 'post-tool')` picks a
|
|
931
|
+
// category-aware verb (reading / searching / analyzing / drafting).
|
|
932
|
+
// When the first stream delta arrives OR the final agentTurn is
|
|
933
|
+
// about to write, the indicator stops permanently.
|
|
934
|
+
const indicator = this.opts.display.activityIndicator('thinking');
|
|
935
|
+
let indicatorStopped = false;
|
|
824
936
|
let streamingActive = false;
|
|
825
|
-
const
|
|
826
|
-
if (
|
|
937
|
+
const stopIndicatorOnce = () => {
|
|
938
|
+
if (indicatorStopped)
|
|
827
939
|
return;
|
|
828
|
-
|
|
829
|
-
|
|
940
|
+
indicatorStopped = true;
|
|
941
|
+
indicator.stop();
|
|
942
|
+
// Clear the per-turn pause/resume hooks so they don't fire
|
|
943
|
+
// against a stopped indicator on a subsequent turn. The next
|
|
944
|
+
// turn re-registers fresh hooks.
|
|
945
|
+
try {
|
|
946
|
+
this.opts.callbacks.setActivityIndicatorHooks?.({});
|
|
947
|
+
}
|
|
948
|
+
catch { /* defensive */ }
|
|
830
949
|
};
|
|
831
|
-
// Phase 23.5: stop the
|
|
832
|
-
// tool row prints
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
|
|
836
|
-
|
|
950
|
+
// Phase 23.5 carried forward: stop the indicator the moment the
|
|
951
|
+
// first tool row prints — the row itself is the activity surface
|
|
952
|
+
// during a tool. Part 1.6 then resumes via `afterEachTool` so the
|
|
953
|
+
// post-tool gap has its own indicator paint.
|
|
954
|
+
this.opts.callbacks.setBeforeFirstToolHook?.(stopIndicatorOnce);
|
|
955
|
+
// Part 1.6: pause/resume hooks around every tool row. The
|
|
956
|
+
// `beforeTool` hook fires before EACH tool row writes (not just
|
|
957
|
+
// the first), so multi-tool sequences also keep the indicator
|
|
958
|
+
// off the tool-row line. `afterEachTool` resumes with a verb
|
|
959
|
+
// chosen from the just-completed tool's category — best guess
|
|
960
|
+
// for "what the model is doing next". `lastToolName` is captured
|
|
961
|
+
// for tests / observability; the verb decision happens inline.
|
|
962
|
+
this.opts.callbacks.setActivityIndicatorHooks?.({
|
|
963
|
+
beforeTool: () => {
|
|
964
|
+
if (indicatorStopped)
|
|
965
|
+
return;
|
|
966
|
+
indicator.pause();
|
|
967
|
+
// v4.1.4 Part 1.6: hide the progress bar while the tool row
|
|
968
|
+
// owns the screen. The bar paints below the indicator, so
|
|
969
|
+
// it'd otherwise sit between the tool row and any subsequent
|
|
970
|
+
// stream output — visual clutter for tool-heavy turns. The
|
|
971
|
+
// bar is per-turn, not per-stream-segment; once hidden it
|
|
972
|
+
// stays hidden until the next turn's bar is created.
|
|
973
|
+
progressBar?.hide();
|
|
974
|
+
},
|
|
975
|
+
afterEachTool: (toolName) => {
|
|
976
|
+
if (indicatorStopped)
|
|
977
|
+
return;
|
|
978
|
+
indicator.resume((0, display_1.verbForActivity)(toolName, 'post-tool'));
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
// v4.1.4 Part 1.6: per-turn progress bar. Created lazily on the
|
|
982
|
+
// first `onProgress` event from the streaming adapter so the bar
|
|
983
|
+
// line doesn't paint until there's something to show. Adapters
|
|
984
|
+
// that don't emit progress (Ollama, most OpenAI-compat) never
|
|
985
|
+
// trigger creation — honest degradation, no fake estimates.
|
|
986
|
+
let progressBar = null;
|
|
837
987
|
try {
|
|
838
988
|
const result = await this.opts.agent.runConversation(baseHistory, {
|
|
839
989
|
stream: streamingEnabled,
|
|
840
990
|
onFirstDelta: streamingEnabled
|
|
841
991
|
? () => {
|
|
842
|
-
|
|
992
|
+
stopIndicatorOnce();
|
|
843
993
|
streamingActive = true;
|
|
844
994
|
}
|
|
845
995
|
: undefined,
|
|
846
996
|
onDelta: streamingEnabled
|
|
847
997
|
? (text) => {
|
|
998
|
+
// v4.1.4 Part 1.6: bar lives ABOVE streamed text. Hide
|
|
999
|
+
// it before each delta writes so the stream output
|
|
1000
|
+
// doesn't land on the bar's line. The bar repaints on
|
|
1001
|
+
// the next `onProgress` event (which Anthropic emits
|
|
1002
|
+
// frequently enough that the bar stays usefully visible).
|
|
1003
|
+
progressBar?.hide();
|
|
848
1004
|
this.opts.display.streamPartial(text);
|
|
849
1005
|
}
|
|
850
1006
|
: undefined,
|
|
@@ -853,8 +1009,30 @@ class ChatSession {
|
|
|
853
1009
|
this.opts.display.streamToolIndicator(call.name);
|
|
854
1010
|
}
|
|
855
1011
|
: undefined,
|
|
1012
|
+
onProgress: streamingEnabled
|
|
1013
|
+
? (outputTokens, maxTokens) => {
|
|
1014
|
+
if (indicatorStopped === false)
|
|
1015
|
+
return;
|
|
1016
|
+
// Lazy-create on first event. The indicator must already
|
|
1017
|
+
// be stopped (first delta arrived) so the bar paints on
|
|
1018
|
+
// its own line below where the indicator was. If the
|
|
1019
|
+
// indicator is still up, skip — the bar would land on
|
|
1020
|
+
// the indicator line and get clobbered by the next tick.
|
|
1021
|
+
if (!progressBar) {
|
|
1022
|
+
progressBar = (0, progressBar_1.createProgressBar)(process.stdout,
|
|
1023
|
+
// Display exposes its skin via getter on the
|
|
1024
|
+
// implementation; cast to any to avoid widening
|
|
1025
|
+
// the public Display surface for one-shot use.
|
|
1026
|
+
this.opts.display.skin);
|
|
1027
|
+
}
|
|
1028
|
+
progressBar.update(outputTokens, maxTokens);
|
|
1029
|
+
}
|
|
1030
|
+
: undefined,
|
|
856
1031
|
});
|
|
857
|
-
|
|
1032
|
+
stopIndicatorOnce();
|
|
1033
|
+
// Hide the progress bar before any post-stream content
|
|
1034
|
+
// (statusFooter, the next prompt) lands on its line.
|
|
1035
|
+
progressBar?.hide();
|
|
858
1036
|
if (streamingActive)
|
|
859
1037
|
this.opts.display.streamComplete();
|
|
860
1038
|
this.history = result.messages;
|
|
@@ -892,11 +1070,50 @@ class ChatSession {
|
|
|
892
1070
|
this.renderStatusLine();
|
|
893
1071
|
}
|
|
894
1072
|
catch (err) {
|
|
895
|
-
|
|
1073
|
+
stopIndicatorOnce();
|
|
1074
|
+
// v4.1.4 Part 1.6: error path must also hide the progress bar
|
|
1075
|
+
// so it doesn't leak across the boundary into the error chrome
|
|
1076
|
+
// or the next prompt.
|
|
1077
|
+
progressBar?.hide();
|
|
896
1078
|
if (streamingActive)
|
|
897
1079
|
this.opts.display.streamComplete();
|
|
898
1080
|
const msg = err?.message ?? String(err);
|
|
899
|
-
|
|
1081
|
+
// v4.1.3-prebump: classify the error so the suggestion below
|
|
1082
|
+
// points at the actual fix instead of the generic "/model or
|
|
1083
|
+
// doctor" line. 413 / 429 / auth get tailored hints; everything
|
|
1084
|
+
// else keeps the legacy fallback. Use the live providerId so
|
|
1085
|
+
// the user sees WHICH provider blew up (matters when fallback
|
|
1086
|
+
// adapters rotate slots mid-turn).
|
|
1087
|
+
const cls = (0, errors_1.classifyProviderError)(err);
|
|
1088
|
+
const tailored = (0, errors_1.suggestForErrorClass)(cls, this.currentProviderId);
|
|
1089
|
+
// v4.1.3-essentials: on `auth` class errors we have enough state
|
|
1090
|
+
// (which provider, what to run) to render a capability card —
|
|
1091
|
+
// structured "what auth's missing, what you can still do, how to
|
|
1092
|
+
// fix" is more useful than the bare message + one-line hint.
|
|
1093
|
+
// Other classes keep the printError single-line surface; their
|
|
1094
|
+
// hints are already specific.
|
|
1095
|
+
if (cls === 'auth') {
|
|
1096
|
+
const p = this.currentProviderId;
|
|
1097
|
+
this.opts.display.printError(msg);
|
|
1098
|
+
this.opts.display.capabilityCard({
|
|
1099
|
+
title: `${p} authentication required`,
|
|
1100
|
+
canStill: [
|
|
1101
|
+
'Continue chatting if a non-auth provider is configured (run `/model`)',
|
|
1102
|
+
'Run `/auth status` to see which providers are signed in',
|
|
1103
|
+
'Run `aiden doctor --providers` for a fuller liveness probe',
|
|
1104
|
+
],
|
|
1105
|
+
cannotReliably: [
|
|
1106
|
+
`Call ${p} until credentials are refreshed`,
|
|
1107
|
+
'Trust any cached responses that depended on this provider',
|
|
1108
|
+
],
|
|
1109
|
+
fix: `Run \`/auth login ${p}\` if it's an OAuth provider, or set the ` +
|
|
1110
|
+
`relevant API key env var. Then retry — no need to restart Aiden.`,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
this.opts.display.printError(msg, tailored
|
|
1115
|
+
?? 'Run `/model` to switch providers or `aiden doctor` to diagnose.');
|
|
1116
|
+
}
|
|
900
1117
|
this.setStatusState({ kind: 'ready' });
|
|
901
1118
|
this.lastTurnElapsedMs = Date.now() - turnStartedAt;
|
|
902
1119
|
}
|
|
@@ -979,6 +1196,15 @@ class ChatSession {
|
|
|
979
1196
|
providerOk: !this.opts.unconfigured,
|
|
980
1197
|
version: version_1.VERSION,
|
|
981
1198
|
}) + '\n');
|
|
1199
|
+
// v4.1.3-prebump: dim source annotation under the pills row so the
|
|
1200
|
+
// user can see WHY this provider/model was chosen — closes the
|
|
1201
|
+
// information gap that made Case 3 (persisted-config) look like a
|
|
1202
|
+
// bug ("why is it still on groq when I auth'd chatgpt-plus?"). One
|
|
1203
|
+
// line, dim, only when the source is informative.
|
|
1204
|
+
const sourceLabel = bootSourceLabel(this.opts.initialBootSource);
|
|
1205
|
+
if (sourceLabel) {
|
|
1206
|
+
display.write(` ${display.muted(sourceLabel)}\n`);
|
|
1207
|
+
}
|
|
982
1208
|
// Tier-3.1b: rule + environment/capabilities block + rule + scroll
|
|
983
1209
|
// + bottom prompt hint. Skipped at <70 cols to keep the narrow
|
|
984
1210
|
// boot card from wrapping into noise.
|
|
@@ -32,35 +32,31 @@ exports.doctor = {
|
|
|
32
32
|
}
|
|
33
33
|
ctx.display.info('Running diagnostic checks...');
|
|
34
34
|
const report = await (0, doctor_1.runDoctor)({ paths: ctx.paths });
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// `aiden doctor` CLI subcommand correctly omits this — the
|
|
43
|
-
// counters would always be zero there.
|
|
35
|
+
// v4.1.3-essentials doctor-polish: pull in-process subsystem
|
|
36
|
+
// health + skill-outcome data into the same report so they
|
|
37
|
+
// render as additional grouped sections inside the health box,
|
|
38
|
+
// not as disconnected blocks below it. `subsystemHealthResults`
|
|
39
|
+
// / `skillOutcomeResults` return empty arrays when their
|
|
40
|
+
// sources are unavailable so the grouped-renderer simply drops
|
|
41
|
+
// those sections.
|
|
44
42
|
if (ctx.agent) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
ctx.display.write(`[url-provenance] blocked=${u.blocked} recovered=${u.recovered} failed=${u.failed} (session)\n`);
|
|
57
|
-
// Phase 23.4a-fix2: empty-response counters. detected =
|
|
58
|
-
// Codex backend completed a turn with no content and no tool
|
|
59
|
-
// calls; retried = corrective system message injected (cap
|
|
60
|
-
// 1/turn); recovered = retry yielded a non-empty reply.
|
|
61
|
-
const e = ctx.agent.getEmptyResponseMetrics();
|
|
62
|
-
ctx.display.write(`[empty-response] detected=${e.detected} retried=${e.retried} recovered=${e.recovered} (session)\n`);
|
|
43
|
+
const a = ctx.agent;
|
|
44
|
+
report.results.push(...(0, doctor_1.subsystemHealthResults)(a.subsystemHealthRegistry));
|
|
45
|
+
report.results.push(...(0, doctor_1.skillOutcomeResults)(a.skillOutcomeTracker));
|
|
46
|
+
// v4.1.3-essentials doctor-polish: session-scoped counters
|
|
47
|
+
// (skill enforcement / URL provenance / empty response) now
|
|
48
|
+
// fold into the same report so they render as a "Session
|
|
49
|
+
// counters" group INSIDE the box instead of as orphan
|
|
50
|
+
// `display.write` lines below it. Previous code emitted them
|
|
51
|
+
// as 3 separate `[bracket-prefix] key=N ...` lines after
|
|
52
|
+
// renderHealthBox closed — visually disconnected.
|
|
53
|
+
report.results.push(...(0, doctor_1.sessionCounterResults)(ctx.agent));
|
|
63
54
|
}
|
|
55
|
+
// v4.1.3-essentials doctor-polish: renderHealthBox now groups
|
|
56
|
+
// results by section header with a top summary. Same renderer
|
|
57
|
+
// is used by `aiden doctor` CLI path so both surfaces stay in
|
|
58
|
+
// visual sync (Path-A unification).
|
|
59
|
+
ctx.display.write((0, doctor_1.renderHealthBox)(report, ctx.display) + '\n');
|
|
64
60
|
return {};
|
|
65
61
|
},
|
|
66
62
|
};
|