@aragon-agent/cli 0.5.8 → 0.5.9
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/CHANGELOG.md +279 -0
- package/README.md +1429 -1163
- package/dist/agent/coalesce.d.ts +13 -0
- package/dist/agent/coalesce.js +50 -11
- package/dist/agent/controller.d.ts +141 -2
- package/dist/agent/controller.js +396 -4
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +245 -3
- package/dist/agent/reducer.js +490 -17
- package/dist/agent/retry-view.d.ts +68 -0
- package/dist/agent/retry-view.js +95 -0
- package/dist/agent/system-prompt.d.ts +11 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +86 -2
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +308 -2
- package/dist/config/cli-commands.d.ts +53 -2
- package/dist/config/cli-commands.js +136 -3
- package/dist/config/env.d.ts +6 -3
- package/dist/config/env.js +103 -3
- package/dist/config/load.d.ts +49 -0
- package/dist/config/load.js +83 -1
- package/dist/config/schema.d.ts +212 -2
- package/dist/config/schema.js +207 -1
- package/dist/config/store.js +20 -1
- package/dist/fast/critique.d.ts +43 -0
- package/dist/fast/critique.js +62 -0
- package/dist/fast/digest.d.ts +42 -0
- package/dist/fast/digest.js +85 -0
- package/dist/fast/frames.d.ts +99 -0
- package/dist/fast/frames.js +180 -0
- package/dist/fast/limits.d.ts +98 -0
- package/dist/fast/limits.js +98 -0
- package/dist/fast/prompt.d.ts +72 -0
- package/dist/fast/prompt.js +94 -0
- package/dist/fast/resolve.d.ts +43 -0
- package/dist/fast/resolve.js +105 -0
- package/dist/fast/review-call.d.ts +99 -0
- package/dist/fast/review-call.js +130 -0
- package/dist/fast/reviewer.d.ts +248 -0
- package/dist/fast/reviewer.js +664 -0
- package/dist/fast/types.d.ts +128 -0
- package/dist/fast/types.js +14 -0
- package/dist/fast/wiring.d.ts +170 -0
- package/dist/fast/wiring.js +316 -0
- package/dist/logging/install.d.ts +15 -1
- package/dist/logging/install.js +37 -4
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +57 -1
- package/dist/team/normalize.d.ts +26 -1
- package/dist/team/normalize.js +23 -3
- package/dist/team/prompt.d.ts +11 -0
- package/dist/team/prompt.js +10 -0
- package/dist/team/report.d.ts +18 -0
- package/dist/team/report.js +27 -1
- package/dist/team/runtime.d.ts +13 -2
- package/dist/team/runtime.js +33 -6
- package/dist/team/subagent.d.ts +19 -1
- package/dist/team/subagent.js +72 -10
- package/dist/team/task-tool.d.ts +26 -0
- package/dist/team/task-tool.js +48 -4
- package/dist/team/types.d.ts +80 -1
- package/dist/tools/bash-tool.js +19 -3
- package/dist/tools/diff.d.ts +25 -5
- package/dist/tools/diff.js +72 -41
- package/dist/tools/file-change-store.d.ts +34 -0
- package/dist/tools/file-change-store.js +57 -0
- package/dist/tools/fs-tools.d.ts +26 -0
- package/dist/tools/fs-tools.js +62 -5
- package/dist/tools/index.d.ts +31 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch.d.ts +115 -0
- package/dist/tools/patch.js +453 -0
- package/dist/tools/terminal-output.d.ts +107 -0
- package/dist/tools/terminal-output.js +198 -0
- package/dist/tools/tool-output-store.d.ts +68 -0
- package/dist/tools/tool-output-store.js +102 -0
- package/dist/ui/ActivityLine.d.ts +48 -0
- package/dist/ui/ActivityLine.js +22 -0
- package/dist/ui/App.d.ts +1 -4
- package/dist/ui/App.js +273 -37
- package/dist/ui/BottomStatusRow.d.ts +43 -0
- package/dist/ui/BottomStatusRow.js +14 -0
- package/dist/ui/Composer.d.ts +1 -6
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +37 -0
- package/dist/ui/StatusBar.js +5 -3
- package/dist/ui/TeamPanel.d.ts +5 -4
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/TodoPanel.d.ts +5 -4
- package/dist/ui/Transcript.d.ts +26 -3
- package/dist/ui/Transcript.js +76 -10
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +20 -0
- package/dist/ui/entries/AssistantEntry.js +3 -2
- package/dist/ui/entries/DiffView.d.ts +67 -0
- package/dist/ui/entries/DiffView.js +103 -0
- package/dist/ui/entries/FastCard.d.ts +53 -0
- package/dist/ui/entries/FastCard.js +87 -0
- package/dist/ui/entries/RetryCard.d.ts +56 -0
- package/dist/ui/entries/RetryCard.js +60 -0
- package/dist/ui/entries/TeamCard.js +6 -1
- package/dist/ui/entries/ToolCard.d.ts +38 -1
- package/dist/ui/entries/ToolCard.js +68 -5
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/AppShell.d.ts +13 -22
- package/dist/ui/layout/AppShell.js +3 -38
- package/dist/ui/layout/virtual-window.d.ts +1 -1
- package/dist/ui/layout/virtual-window.js +109 -5
- package/dist/ui/overlays/HelpOverlay.js +16 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
- package/dist/ui/overlays/SettingsScreen.js +147 -1
- package/dist/ui/transcript-text.js +27 -0
- package/dist/ui/use-wheel-routing.d.ts +16 -32
- package/dist/ui/use-wheel-routing.js +21 -38
- package/package.json +2 -2
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in slash commands (spec §5.2):
|
|
3
3
|
* /help /model /settings /thinking /max-tokens /tools /clear /reset /cwd /save
|
|
4
|
-
* /resume /copy /exit (/quit).
|
|
4
|
+
* /resume /copy /exit (/quit), plus the /team, /fast and /todo subsystems.
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { existsSync, statSync } from 'node:fs';
|
|
8
8
|
import { isAbsolute, resolve } from 'node:path';
|
|
9
9
|
import process from 'node:process';
|
|
10
10
|
import { resolveOutputTokens } from '@aragon-agent/core';
|
|
11
|
-
import { clampThinkingLevel, clampTheme, isThemeName, parseMaxTokensInput, DEFAULT_MAX_TOKENS, LEGACY_DARK_THEME, THEME_NAMES, THINKING_LEVELS, } from '../config/schema.js';
|
|
11
|
+
import { clampThinkingLevel, clampTheme, isAdapterProvider, isThemeName, parseMaxTokensInput, ADAPTER_PROVIDERS, DEFAULT_MAX_TOKENS, HARD_MAX_RETRIES, LEGACY_DARK_THEME, THEME_NAMES, THINKING_LEVELS, } from '../config/schema.js';
|
|
12
|
+
import { computeCost, formatCost, formatTokens } from '../agent/usage.js';
|
|
12
13
|
import { TODO_FOLLOW_LIMITS } from '../todo/limits.js';
|
|
13
14
|
import { buildContinuationMessage } from '../todo/follow-through.js';
|
|
14
15
|
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
@@ -16,6 +17,59 @@ import { loadConfig } from '../config/load.js';
|
|
|
16
17
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
17
18
|
import { getLogger } from '../logging/logger.js';
|
|
18
19
|
import { perfCommand } from './perf.js';
|
|
20
|
+
/**
|
|
21
|
+
* `/fast status` — THE GUARANTEED REPORTING SURFACE for the tier (§4.4 / R-8).
|
|
22
|
+
*
|
|
23
|
+
* The chip degrades on a narrow terminal and the card only appears when a review
|
|
24
|
+
* happens; this is the one place that always answers "is it on, what is it, and
|
|
25
|
+
* has it done anything". The session totals are here rather than on the status
|
|
26
|
+
* bar because the aggregate there cannot answer "is the cheap tier actually
|
|
27
|
+
* saving me anything?".
|
|
28
|
+
*
|
|
29
|
+
* `unknown (no price table for <model>)` RATHER THAN A CURRENCY AMOUNT when the
|
|
30
|
+
* static table has never seen the model (C-11 / RV-4 / AC-34): `$0.00` on a
|
|
31
|
+
* feature that is spending money is the same class of lie as pricing a Haiku
|
|
32
|
+
* child at Sonnet rates.
|
|
33
|
+
*/
|
|
34
|
+
function formatFastStatus(controller) {
|
|
35
|
+
const status = controller.getFastStatus();
|
|
36
|
+
const cfg = controller.getFastConfig();
|
|
37
|
+
const state = !status.registered
|
|
38
|
+
? 'off for this session (not configured at launch)'
|
|
39
|
+
: status.enabled
|
|
40
|
+
? 'on'
|
|
41
|
+
: 'off';
|
|
42
|
+
const lines = [];
|
|
43
|
+
if (status.tier.ok) {
|
|
44
|
+
const same = status.tier.sameAsMain ? ' (same as main)' : '';
|
|
45
|
+
lines.push(`Fast tier: ${state}. Model: ${status.tier.ref.providerId}:${status.tier.ref.modelId}${same}.`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
lines.push(`Fast tier: ${state}. Model: ${cfg.model || '(not set)'}.`);
|
|
49
|
+
}
|
|
50
|
+
lines.push(`Review: ${cfg.review ? `every ${cfg.reviewEveryTurns} turns` : 'off'}. ` +
|
|
51
|
+
`Delegation: ${cfg.delegate ? 'on' : 'off'}.`);
|
|
52
|
+
const snapshot = status.snapshot;
|
|
53
|
+
const cost = snapshot.pricingUnknown
|
|
54
|
+
? `unknown (no price table for ${snapshot.model})`
|
|
55
|
+
: formatCost(computeCost(snapshot.usage, status.tier.ok ? controller.getModelInfoFor(status.tier.ref).cost : undefined));
|
|
56
|
+
// `n/N`, NEVER A BARE `n` (fast-model-tier-hardening §4.3). A bare count
|
|
57
|
+
// answers "what have I spent" only for a reader who already knows the
|
|
58
|
+
// ceiling, which is precisely what the user does not know; `12/40` answers
|
|
59
|
+
// that and "how much is left" in the same three characters, and makes the
|
|
60
|
+
// budget discoverable to someone who never read the config schema. The
|
|
61
|
+
// exhausted form names the remedy inline, because a status line that reports
|
|
62
|
+
// a stop without its escape hatch just relocates the question.
|
|
63
|
+
//
|
|
64
|
+
// `budgetReached` is READ, not re-derived (RV-H10): with the tier off both
|
|
65
|
+
// numbers are `0` placeholders and `reviews >= reviewBudget` would be true.
|
|
66
|
+
const budget = snapshot.budgetReached ? ' (budget reached - raise with /fast budget <n>)' : '';
|
|
67
|
+
lines.push(`This session: ${snapshot.reviews}/${snapshot.reviewBudget} reviews${budget}, ` +
|
|
68
|
+
`${snapshot.delegated} delegated subagents, ` +
|
|
69
|
+
`in ${formatTokens(snapshot.usage.inputTokens)} / out ` +
|
|
70
|
+
`${formatTokens(snapshot.usage.outputTokens)}, cost ${cost}.`);
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
19
73
|
const COMMANDS = [
|
|
20
74
|
{
|
|
21
75
|
name: 'help',
|
|
@@ -223,6 +277,187 @@ const COMMANDS = [
|
|
|
223
277
|
ctx.notify('warn', `Unknown argument "${verb}" - use /team [status|on|off|max <n>].`);
|
|
224
278
|
},
|
|
225
279
|
},
|
|
280
|
+
{
|
|
281
|
+
name: 'fast',
|
|
282
|
+
description: 'Fast model tier: status | on | off | model <id> | provider <id> | same | ' +
|
|
283
|
+
'review <n|off> | budget <n> | delegate on|off',
|
|
284
|
+
/**
|
|
285
|
+
* `/fast on` CANNOT turn the tier on in a session that started without it,
|
|
286
|
+
* for the reason `/team on` above records at length: the tool array is built
|
|
287
|
+
* ONCE and rebuilding it is forbidden (C-2), so a session that grew a schema
|
|
288
|
+
* field mid-flight would be a session whose request payloads change shape
|
|
289
|
+
* between turns.
|
|
290
|
+
*
|
|
291
|
+
* EVERY MUTATING BRANCH DOES THE TWO CALLS PLUS PERSIST that `/todo`
|
|
292
|
+
* documents: `controller.setFastConfig(patch)` updates the object `App`
|
|
293
|
+
* reads at render time AND re-resolves the tier AND rebuilds the prompt;
|
|
294
|
+
* `ctx.persistConfig({ fast: patch })` survives the session. Persist alone
|
|
295
|
+
* reports success and changes nothing until relaunch; the setter alone
|
|
296
|
+
* forgets by morning.
|
|
297
|
+
*/
|
|
298
|
+
run: (ctx) => {
|
|
299
|
+
const arg = ctx.args.trim();
|
|
300
|
+
const [rawVerb, value] = arg.split(/\s+/, 2);
|
|
301
|
+
const verb = (rawVerb ?? '').toLowerCase();
|
|
302
|
+
const controller = ctx.controller;
|
|
303
|
+
// Refused mid-dispatch for the reason `/team` records: half a dispatch
|
|
304
|
+
// under one tier and half under another produces a report nothing can
|
|
305
|
+
// afterwards explain. THAT ARGUMENT IS ABOUT WRITES, so the two READS are
|
|
306
|
+
// grouped and answerable (RV-H3): `/fast status` always was, and a bare
|
|
307
|
+
// `/fast budget` reports the same numbers by another name. `/fast budget
|
|
308
|
+
// 80` is a write and is still refused.
|
|
309
|
+
const isRead = verb === 'status' || (verb === 'budget' && !value);
|
|
310
|
+
if (verb && !isRead && controller.isTeamBusy()) {
|
|
311
|
+
ctx.notify('warn', 'Cannot change fast-tier settings mid-dispatch.');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const persist = (patch) => {
|
|
315
|
+
controller.setFastConfig(patch);
|
|
316
|
+
ctx.persistConfig({ fast: patch });
|
|
317
|
+
};
|
|
318
|
+
if (!verb || verb === 'status') {
|
|
319
|
+
ctx.notify('info', formatFastStatus(controller));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (verb === 'on' || verb === 'off') {
|
|
323
|
+
const enabled = verb === 'on';
|
|
324
|
+
persist({ enabled });
|
|
325
|
+
if (!controller.isFastRegistered()) {
|
|
326
|
+
// The honest branch, and the whole point of splitting by what was
|
|
327
|
+
// decided at construction (§3.3). It is one sentence, and it keeps
|
|
328
|
+
// AC-2 / AC-3 intact: a session launched without the tier never grows
|
|
329
|
+
// a schema field, whatever the user types afterwards.
|
|
330
|
+
ctx.notify('info', enabled
|
|
331
|
+
? 'The fast tier is off for this session (not configured at launch). ' +
|
|
332
|
+
'Saved for next launch.'
|
|
333
|
+
: 'The fast tier is already off for this session. Saved for next launch.');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
controller.setFastEnabled(enabled);
|
|
337
|
+
ctx.toast('success', `Fast tier ${enabled ? 'on' : 'off'}.`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (verb === 'model') {
|
|
341
|
+
const raw = (value ?? '').trim();
|
|
342
|
+
if (!raw) {
|
|
343
|
+
ctx.notify('warn', 'Usage: /fast model <id> (or provider:model)');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// `provider:model` is accepted because that is how a user thinks about a
|
|
347
|
+
// tier on another vendor, and an unknown provider is rejected with THE
|
|
348
|
+
// MESSAGE `cli.tsx` ALREADY PRINTS rather than a second wording for the
|
|
349
|
+
// same fact (RV-16).
|
|
350
|
+
const colon = raw.indexOf(':');
|
|
351
|
+
if (colon > 0) {
|
|
352
|
+
const provider = raw.slice(0, colon);
|
|
353
|
+
const model = raw.slice(colon + 1);
|
|
354
|
+
if (!isAdapterProvider(provider)) {
|
|
355
|
+
ctx.notify('warn', `Unknown provider "${provider}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
persist({ provider, model });
|
|
359
|
+
ctx.toast('success', `Fast model set to ${provider}:${model}.`);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
persist({ model: raw });
|
|
363
|
+
ctx.toast('success', `Fast model set to ${raw}.`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (verb === 'provider') {
|
|
367
|
+
const raw = (value ?? '').trim();
|
|
368
|
+
if (!raw) {
|
|
369
|
+
ctx.notify('warn', 'Usage: /fast provider <id>');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (!isAdapterProvider(raw)) {
|
|
373
|
+
ctx.notify('warn', `Unknown provider "${raw}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
persist({ provider: raw });
|
|
377
|
+
ctx.toast('success', `Fast provider set to ${raw}.`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (verb === 'same') {
|
|
381
|
+
// R-e IN ONE WORD. `sameAsMain` is display-only and no branch reads it
|
|
382
|
+
// (R-13): the user is allowed to run both tiers on one model, and the
|
|
383
|
+
// review still costs what it costs.
|
|
384
|
+
const cfg = controller.getConfig();
|
|
385
|
+
persist({
|
|
386
|
+
provider: cfg.provider,
|
|
387
|
+
model: cfg.model,
|
|
388
|
+
baseUrl: cfg.baseUrl ?? '',
|
|
389
|
+
});
|
|
390
|
+
ctx.toast('success', `Fast tier set to ${cfg.provider}:${cfg.model} (same as main).`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (verb === 'review') {
|
|
394
|
+
const raw = (value ?? '').trim().toLowerCase();
|
|
395
|
+
if (raw === 'off') {
|
|
396
|
+
persist({ review: false });
|
|
397
|
+
ctx.toast('success', 'Fast review off.');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const n = Number.parseInt(raw, 10);
|
|
401
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
402
|
+
ctx.notify('warn', 'Usage: /fast review <n> | off');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// Clamped by `setFastConfig` and again on the persist path, so a value
|
|
406
|
+
// outside the range can reach neither the runtime nor the file.
|
|
407
|
+
const applied = controller.setFastConfig({ review: true, reviewEveryTurns: n });
|
|
408
|
+
ctx.persistConfig({
|
|
409
|
+
fast: { review: true, reviewEveryTurns: applied.reviewEveryTurns },
|
|
410
|
+
});
|
|
411
|
+
ctx.toast('success', `Fast review every ${applied.reviewEveryTurns} turns.`);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (verb === 'budget') {
|
|
415
|
+
const snapshot = controller.getFastStatus().snapshot;
|
|
416
|
+
const raw = (value ?? '').trim();
|
|
417
|
+
if (!raw) {
|
|
418
|
+
// The READ. Answerable mid-dispatch (see the guard above), and it
|
|
419
|
+
// reports the same pair `/fast status` renders as `n/N`.
|
|
420
|
+
ctx.notify('info', `Fast review budget: ${snapshot.reviews} of ${snapshot.reviewBudget} used ` +
|
|
421
|
+
'this session. Use /fast budget <1-500> to raise it.');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const n = Number.parseInt(raw, 10);
|
|
425
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
426
|
+
// Change NOTHING on a typo, the discipline `/max-tokens` sets: a
|
|
427
|
+
// silent resolve to the default discards the number the user meant.
|
|
428
|
+
ctx.notify('warn', 'Usage: /fast budget <1-500>');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// BOTH CALLS, IN THIS ORDER (RV-H4 / AC-H17). `setFastConfig` is what
|
|
432
|
+
// makes a raised budget take effect LIVE - the reviewer's `budgetLimit()`
|
|
433
|
+
// reads the controller's in-memory config, never disk - and it also
|
|
434
|
+
// re-emits `tier_changed`, which is how the new denominator reaches the
|
|
435
|
+
// status chip without a restart. See the comment on this command: a
|
|
436
|
+
// persist alone reports success and changes nothing until relaunch.
|
|
437
|
+
const applied = controller.setFastConfig({ reviewMaxPerSession: n });
|
|
438
|
+
ctx.persistConfig({
|
|
439
|
+
fast: { reviewMaxPerSession: applied.reviewMaxPerSession },
|
|
440
|
+
});
|
|
441
|
+
// The APPLIED value, not the requested one, so a clamp is visible.
|
|
442
|
+
ctx.toast('success', `Fast review budget: ${applied.reviewMaxPerSession} per session. ` +
|
|
443
|
+
`${snapshot.reviews} used.`);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (verb === 'delegate') {
|
|
447
|
+
const raw = (value ?? '').trim().toLowerCase();
|
|
448
|
+
if (raw !== 'on' && raw !== 'off') {
|
|
449
|
+
ctx.notify('warn', 'Usage: /fast delegate <on|off>');
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
persist({ delegate: raw === 'on' });
|
|
453
|
+
ctx.toast('success', `Fast delegation ${raw}.`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use /fast ` +
|
|
457
|
+
'[status|on|off|model <id>|provider <id>|same|review <n|off>|budget <n>|' +
|
|
458
|
+
'delegate on|off].');
|
|
459
|
+
},
|
|
460
|
+
},
|
|
226
461
|
{
|
|
227
462
|
name: 'todo',
|
|
228
463
|
description: 'Todo planning: status | on | off | panel on|off | follow <mode> | clear | continue',
|
|
@@ -377,6 +612,77 @@ const COMMANDS = [
|
|
|
377
612
|
'/todo [status|on|off|panel on|off|follow <mode>|clear|continue].');
|
|
378
613
|
},
|
|
379
614
|
},
|
|
615
|
+
{
|
|
616
|
+
name: 'retry',
|
|
617
|
+
description: 'API retry: show | on | off | max <n>',
|
|
618
|
+
/**
|
|
619
|
+
* TWO CALLS FOR EVERY MUTATION, IN THIS ORDER, and neither is optional
|
|
620
|
+
* (llm-api-retry-backoff §6.9 / §6.3):
|
|
621
|
+
*
|
|
622
|
+
* `setRetryConfig` replaces the policy on the live `ProviderRegistry`, so
|
|
623
|
+
* the change takes effect in THIS session — and, because
|
|
624
|
+
* `TeamRuntime` holds the same registry instance, for every
|
|
625
|
+
* child too. Without it `/retry off` reports success and
|
|
626
|
+
* changes nothing until the next launch.
|
|
627
|
+
* `persistConfig` is what survives the session.
|
|
628
|
+
*
|
|
629
|
+
* UNLIKE `/team on` AND `/todo on` there is no "off for this session" branch:
|
|
630
|
+
* retry is not a tool, nothing is registered at construction, and there is
|
|
631
|
+
* therefore no state a flag could have made unreachable. `--no-retry` is a
|
|
632
|
+
* starting value, not a permanent verdict.
|
|
633
|
+
*/
|
|
634
|
+
run: (ctx) => {
|
|
635
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
636
|
+
const [verb, value] = arg.split(/\s+/, 2);
|
|
637
|
+
const controller = ctx.controller;
|
|
638
|
+
if (!verb || verb === 'show' || verb === 'status') {
|
|
639
|
+
const cfg = controller.getRetryConfig();
|
|
640
|
+
if (!cfg.enabled || cfg.maxRetries <= 0) {
|
|
641
|
+
ctx.notify('info', `API retry: off (maxRetries ${cfg.maxRetries}). Use /retry on or /retry max <n>.`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
ctx.notify('info', `API retry: on. Up to ${cfg.maxRetries} retries, ${cfg.initialDelayMs}ms doubling to ` +
|
|
645
|
+
`${cfg.maxDelayMs}ms${cfg.jitter ? ' with jitter' : ''}, ` +
|
|
646
|
+
`giving up after ${Math.round(cfg.maxElapsedMs / 1000)}s. ` +
|
|
647
|
+
`Retry-After ${cfg.respectRetryAfter ? 'honoured' : 'ignored'}.`);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (verb === 'on' || verb === 'off') {
|
|
651
|
+
const enabled = verb === 'on';
|
|
652
|
+
const applied = controller.setRetryConfig({ enabled });
|
|
653
|
+
// A PARTIAL `retry` section, safe only because `updatePersistedConfig`
|
|
654
|
+
// deep-merges it — the same contract `/team on` relies on.
|
|
655
|
+
ctx.persistConfig({ retry: { enabled } });
|
|
656
|
+
if (enabled && applied.maxRetries <= 0) {
|
|
657
|
+
// Honest rather than cheerful: `enabled` is on but the count is the
|
|
658
|
+
// kill switch, so nothing will actually retry.
|
|
659
|
+
ctx.notify('warn', 'API retry enabled, but maxRetries is 0 - use /retry max <n>.');
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
ctx.toast('success', `API retry ${enabled ? 'on' : 'off'}.`);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (verb === 'max') {
|
|
666
|
+
// `Number.isFinite` and `>= 0`, NOT `> 0`: `0` is this key's documented
|
|
667
|
+
// floor and its kill switch, and rejecting it here would make `/retry max 0`
|
|
668
|
+
// the one documented control that does not work (R-16).
|
|
669
|
+
const n = Number.parseInt(value ?? '', 10);
|
|
670
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
671
|
+
ctx.notify('warn', `Usage: /retry max <0-${HARD_MAX_RETRIES}> (0 turns retry off)`);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const applied = controller.setRetryConfig({ maxRetries: n });
|
|
675
|
+
ctx.persistConfig({
|
|
676
|
+
retry: { maxRetries: applied.maxRetries },
|
|
677
|
+
});
|
|
678
|
+
ctx.toast('success', applied.maxRetries === 0
|
|
679
|
+
? 'API retry off (maxRetries 0).'
|
|
680
|
+
: `API retry: up to ${applied.maxRetries} retries.`);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use /retry [show|on|off|max <n>].`);
|
|
684
|
+
},
|
|
685
|
+
},
|
|
380
686
|
{
|
|
381
687
|
name: 'expand',
|
|
382
688
|
description: 'Expand / collapse a recent tool card (default: last)',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `aragon config get | list | edit | home`, plus the `log
|
|
3
|
-
* branches of `config set`.
|
|
2
|
+
* `aragon config get | list | edit | home`, plus the `log.*`, `team.*`,
|
|
3
|
+
* `retry.*` and `fast.*` branches of `config set`.
|
|
4
4
|
*
|
|
5
5
|
* These live here rather than in `cli.tsx` because `runConfigSet` is already a
|
|
6
6
|
* 21-case switch — well past the complexity ceiling this package writes down —
|
|
@@ -21,6 +21,28 @@ export declare const LOG_CONFIG_SET_KEYS: readonly ["log.level", "log.toFile", "
|
|
|
21
21
|
* `Set team.enabled = false`.
|
|
22
22
|
*/
|
|
23
23
|
export declare const TEAM_CONFIG_SET_KEYS: readonly ["team.enabled", "team.maxSubagents", "team.maxConcurrent", "team.subagentTimeoutMs", "team.dispatchTimeoutMs", "team.maxTurnsPerSubagent"];
|
|
24
|
+
/**
|
|
25
|
+
* Config keys under `retry.` that `aragon config set` accepts
|
|
26
|
+
* (llm-api-retry-backoff §8.2).
|
|
27
|
+
*
|
|
28
|
+
* Handled here rather than in `cli.tsx`'s switch for the reason this module's
|
|
29
|
+
* header gives about `log.*` and `team.*`: that switch is past this package's
|
|
30
|
+
* complexity ceiling, and a key added to the membership set but forgotten in the
|
|
31
|
+
* switch falls through every case, writes nothing, and still prints
|
|
32
|
+
* `Set retry.maxRetries = 0`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const RETRY_CONFIG_SET_KEYS: readonly ["retry.enabled", "retry.maxRetries", "retry.initialDelayMs", "retry.maxDelayMs", "retry.multiplier", "retry.jitter", "retry.respectRetryAfter", "retry.maxElapsedMs", "retry.onPartialStream"];
|
|
35
|
+
/**
|
|
36
|
+
* Config keys under `fast.` that `aragon config set` accepts (fast-model-tier
|
|
37
|
+
* §4.2).
|
|
38
|
+
*
|
|
39
|
+
* A DEDICATED SETTER RATHER THAN A GENERIC DOTTED WRITER, for the reason the
|
|
40
|
+
* three sets above each record: a generic setter would write `fast` as a FLAT
|
|
41
|
+
* string key (`"fast.model": "haiku"` at the top level of the file), the section
|
|
42
|
+
* would silently never take effect, and `config set` would still print
|
|
43
|
+
* `Set fast.model = haiku`.
|
|
44
|
+
*/
|
|
45
|
+
export declare const FAST_CONFIG_SET_KEYS: readonly ["fast.enabled", "fast.provider", "fast.model", "fast.baseUrl", "fast.thinkingLevel", "fast.delegate", "fast.review", "fast.reviewEveryTurns", "fast.reviewContextTurns", "fast.reviewMaxChars", "fast.reviewMaxPerSession"];
|
|
24
46
|
/**
|
|
25
47
|
* Translate a `log.*` dotted key into a patch, or `null` for anything else.
|
|
26
48
|
*
|
|
@@ -43,6 +65,35 @@ export declare function applyLogConfigSet(key: string, value: string): Partial<P
|
|
|
43
65
|
* won't stick". `clampTeamConfig` is the one gate both directions pass through.
|
|
44
66
|
*/
|
|
45
67
|
export declare function applyTeamConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
68
|
+
/**
|
|
69
|
+
* Translate a `retry.*` dotted key into a patch, or `null` for anything else.
|
|
70
|
+
*
|
|
71
|
+
* The patch is a PARTIAL `retry` section, safe only because
|
|
72
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge,
|
|
73
|
+
* setting the retry count would silently reset `respectRetryAfter` and `jitter`.
|
|
74
|
+
*
|
|
75
|
+
* `maxRetries` GOES THROUGH `clampRetryConfig`, NOT `coercePositiveInt`, and that
|
|
76
|
+
* is not a style choice (R-16): `coercePositiveInt` returns its fallback for
|
|
77
|
+
* `n <= 0`, so `config set retry.maxRetries 0` would write **10** — the maximum
|
|
78
|
+
* instead of the kill switch, silently. The whole section is routed through the
|
|
79
|
+
* one gate so no key here can pick up the wrong coercion later; the caller echoes
|
|
80
|
+
* the STORED value, which is what makes an out-of-range input visible.
|
|
81
|
+
*/
|
|
82
|
+
export declare function applyRetryConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
83
|
+
/**
|
|
84
|
+
* Translate a `fast.*` dotted key into a patch, or `null` for anything else.
|
|
85
|
+
*
|
|
86
|
+
* The patch is a PARTIAL `fast` section, safe only because
|
|
87
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge,
|
|
88
|
+
* setting the cadence would silently switch the whole tier off (R-10).
|
|
89
|
+
*
|
|
90
|
+
* EVERY NUMERIC AND ENUM KEY ROUTES THROUGH `clampFastConfig`, not through
|
|
91
|
+
* `coercePositiveInt` or a local parse, for the reason `applyRetryConfigSet`
|
|
92
|
+
* records: one gate for both directions means no key here can pick up a
|
|
93
|
+
* different coercion later, and `config set fast.reviewEveryTurns 400` writes
|
|
94
|
+
* the clamped 50 while the caller echoes the STORED value (AC-13).
|
|
95
|
+
*/
|
|
96
|
+
export declare function applyFastConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
46
97
|
/**
|
|
47
98
|
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
48
99
|
* disk. It is command-line-only (there is no settings-screen entry) and it says
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `aragon config get | list | edit | home`, plus the `log
|
|
3
|
-
* branches of `config set`.
|
|
2
|
+
* `aragon config get | list | edit | home`, plus the `log.*`, `team.*`,
|
|
3
|
+
* `retry.*` and `fast.*` branches of `config set`.
|
|
4
4
|
*
|
|
5
5
|
* These live here rather than in `cli.tsx` because `runConfigSet` is already a
|
|
6
6
|
* 21-case switch — well past the complexity ceiling this package writes down —
|
|
@@ -13,7 +13,7 @@ import process from 'node:process';
|
|
|
13
13
|
import { getConfigBackupPath, getConfigPath, getHomeRoot, getLogsDir, getPromptHistoryPath, getUiStatePath, isHomeOverridden, } from './app-paths.js';
|
|
14
14
|
import { getHomeMigrationBreadcrumbPath } from './migrate-home.js';
|
|
15
15
|
import { loadPersistedConfig, updatePersistedConfig } from './store.js';
|
|
16
|
-
import { coercePositiveInt, maskSecret, DEFAULT_TEAM_CONFIG, } from './schema.js';
|
|
16
|
+
import { clampFastConfig, clampRetryConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, } from './schema.js';
|
|
17
17
|
import { clampLogLevel } from '../logging/levels.js';
|
|
18
18
|
/** Config keys under `log.` that `aragon config set` accepts. */
|
|
19
19
|
export const LOG_CONFIG_SET_KEYS = [
|
|
@@ -43,6 +43,50 @@ export const TEAM_CONFIG_SET_KEYS = [
|
|
|
43
43
|
'team.dispatchTimeoutMs',
|
|
44
44
|
'team.maxTurnsPerSubagent',
|
|
45
45
|
];
|
|
46
|
+
/**
|
|
47
|
+
* Config keys under `retry.` that `aragon config set` accepts
|
|
48
|
+
* (llm-api-retry-backoff §8.2).
|
|
49
|
+
*
|
|
50
|
+
* Handled here rather than in `cli.tsx`'s switch for the reason this module's
|
|
51
|
+
* header gives about `log.*` and `team.*`: that switch is past this package's
|
|
52
|
+
* complexity ceiling, and a key added to the membership set but forgotten in the
|
|
53
|
+
* switch falls through every case, writes nothing, and still prints
|
|
54
|
+
* `Set retry.maxRetries = 0`.
|
|
55
|
+
*/
|
|
56
|
+
export const RETRY_CONFIG_SET_KEYS = [
|
|
57
|
+
'retry.enabled',
|
|
58
|
+
'retry.maxRetries',
|
|
59
|
+
'retry.initialDelayMs',
|
|
60
|
+
'retry.maxDelayMs',
|
|
61
|
+
'retry.multiplier',
|
|
62
|
+
'retry.jitter',
|
|
63
|
+
'retry.respectRetryAfter',
|
|
64
|
+
'retry.maxElapsedMs',
|
|
65
|
+
'retry.onPartialStream',
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* Config keys under `fast.` that `aragon config set` accepts (fast-model-tier
|
|
69
|
+
* §4.2).
|
|
70
|
+
*
|
|
71
|
+
* A DEDICATED SETTER RATHER THAN A GENERIC DOTTED WRITER, for the reason the
|
|
72
|
+
* three sets above each record: a generic setter would write `fast` as a FLAT
|
|
73
|
+
* string key (`"fast.model": "haiku"` at the top level of the file), the section
|
|
74
|
+
* would silently never take effect, and `config set` would still print
|
|
75
|
+
* `Set fast.model = haiku`.
|
|
76
|
+
*/
|
|
77
|
+
export const FAST_CONFIG_SET_KEYS = [
|
|
78
|
+
'fast.enabled',
|
|
79
|
+
'fast.provider',
|
|
80
|
+
'fast.model',
|
|
81
|
+
'fast.baseUrl',
|
|
82
|
+
'fast.thinkingLevel',
|
|
83
|
+
'fast.delegate',
|
|
84
|
+
'fast.review',
|
|
85
|
+
'fast.reviewEveryTurns',
|
|
86
|
+
'fast.reviewContextTurns',
|
|
87
|
+
'fast.reviewMaxChars',
|
|
88
|
+
'fast.reviewMaxPerSession',
|
|
89
|
+
];
|
|
46
90
|
function isTrue(value) {
|
|
47
91
|
return value === 'true' || value === '1';
|
|
48
92
|
}
|
|
@@ -112,6 +156,95 @@ export function applyTeamConfigSet(key, value) {
|
|
|
112
156
|
return null;
|
|
113
157
|
}
|
|
114
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Translate a `retry.*` dotted key into a patch, or `null` for anything else.
|
|
161
|
+
*
|
|
162
|
+
* The patch is a PARTIAL `retry` section, safe only because
|
|
163
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge,
|
|
164
|
+
* setting the retry count would silently reset `respectRetryAfter` and `jitter`.
|
|
165
|
+
*
|
|
166
|
+
* `maxRetries` GOES THROUGH `clampRetryConfig`, NOT `coercePositiveInt`, and that
|
|
167
|
+
* is not a style choice (R-16): `coercePositiveInt` returns its fallback for
|
|
168
|
+
* `n <= 0`, so `config set retry.maxRetries 0` would write **10** — the maximum
|
|
169
|
+
* instead of the kill switch, silently. The whole section is routed through the
|
|
170
|
+
* one gate so no key here can pick up the wrong coercion later; the caller echoes
|
|
171
|
+
* the STORED value, which is what makes an out-of-range input visible.
|
|
172
|
+
*/
|
|
173
|
+
export function applyRetryConfigSet(key, value) {
|
|
174
|
+
const patch = (retry) => ({ retry });
|
|
175
|
+
const clampOne = (field) => {
|
|
176
|
+
const merged = clampRetryConfig({ ...DEFAULT_RETRY_CONFIG, [field]: value });
|
|
177
|
+
return { [field]: merged[field] };
|
|
178
|
+
};
|
|
179
|
+
switch (key) {
|
|
180
|
+
case 'retry.enabled':
|
|
181
|
+
return patch({ enabled: isTrue(value) });
|
|
182
|
+
case 'retry.maxRetries':
|
|
183
|
+
return patch(clampOne('maxRetries'));
|
|
184
|
+
case 'retry.initialDelayMs':
|
|
185
|
+
return patch(clampOne('initialDelayMs'));
|
|
186
|
+
case 'retry.maxDelayMs':
|
|
187
|
+
return patch(clampOne('maxDelayMs'));
|
|
188
|
+
case 'retry.multiplier':
|
|
189
|
+
return patch(clampOne('multiplier'));
|
|
190
|
+
case 'retry.jitter':
|
|
191
|
+
return patch({ jitter: isTrue(value) });
|
|
192
|
+
case 'retry.respectRetryAfter':
|
|
193
|
+
return patch({ respectRetryAfter: isTrue(value) });
|
|
194
|
+
case 'retry.maxElapsedMs':
|
|
195
|
+
return patch(clampOne('maxElapsedMs'));
|
|
196
|
+
case 'retry.onPartialStream':
|
|
197
|
+
return patch({ onPartialStream: isTrue(value) });
|
|
198
|
+
default:
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Translate a `fast.*` dotted key into a patch, or `null` for anything else.
|
|
204
|
+
*
|
|
205
|
+
* The patch is a PARTIAL `fast` section, safe only because
|
|
206
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge,
|
|
207
|
+
* setting the cadence would silently switch the whole tier off (R-10).
|
|
208
|
+
*
|
|
209
|
+
* EVERY NUMERIC AND ENUM KEY ROUTES THROUGH `clampFastConfig`, not through
|
|
210
|
+
* `coercePositiveInt` or a local parse, for the reason `applyRetryConfigSet`
|
|
211
|
+
* records: one gate for both directions means no key here can pick up a
|
|
212
|
+
* different coercion later, and `config set fast.reviewEveryTurns 400` writes
|
|
213
|
+
* the clamped 50 while the caller echoes the STORED value (AC-13).
|
|
214
|
+
*/
|
|
215
|
+
export function applyFastConfigSet(key, value) {
|
|
216
|
+
const patch = (fast) => ({ fast });
|
|
217
|
+
const clampOne = (field) => {
|
|
218
|
+
const merged = clampFastConfig({ ...DEFAULT_FAST_CONFIG, [field]: value });
|
|
219
|
+
return { [field]: merged[field] };
|
|
220
|
+
};
|
|
221
|
+
switch (key) {
|
|
222
|
+
case 'fast.enabled':
|
|
223
|
+
return patch({ enabled: isTrue(value) });
|
|
224
|
+
case 'fast.provider':
|
|
225
|
+
return patch(clampOne('provider'));
|
|
226
|
+
case 'fast.model':
|
|
227
|
+
return patch(clampOne('model'));
|
|
228
|
+
case 'fast.baseUrl':
|
|
229
|
+
return patch(clampOne('baseUrl'));
|
|
230
|
+
case 'fast.thinkingLevel':
|
|
231
|
+
return patch(clampOne('thinkingLevel'));
|
|
232
|
+
case 'fast.delegate':
|
|
233
|
+
return patch({ delegate: isTrue(value) });
|
|
234
|
+
case 'fast.review':
|
|
235
|
+
return patch({ review: isTrue(value) });
|
|
236
|
+
case 'fast.reviewEveryTurns':
|
|
237
|
+
return patch(clampOne('reviewEveryTurns'));
|
|
238
|
+
case 'fast.reviewContextTurns':
|
|
239
|
+
return patch(clampOne('reviewContextTurns'));
|
|
240
|
+
case 'fast.reviewMaxChars':
|
|
241
|
+
return patch(clampOne('reviewMaxChars'));
|
|
242
|
+
case 'fast.reviewMaxPerSession':
|
|
243
|
+
return patch(clampOne('reviewMaxPerSession'));
|
|
244
|
+
default:
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
115
248
|
/**
|
|
116
249
|
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
117
250
|
* disk. It is command-line-only (there is no settings-screen entry) and it says
|
package/dist/config/env.d.ts
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* - Provider secrets: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY /
|
|
6
6
|
* GEMINI_API_KEY.
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
|
+
* ARAGON_SHOW_THINKING,
|
|
8
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
9
10
|
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN, ARAGON_TEAM,
|
|
10
|
-
* ARAGON_TEAM_MAX,
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
|
+
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
|
+
* ARAGON_LOG_LEVEL,
|
|
14
|
+
* ARAGON_LOG_FILE, ARAGON_LOG_DIR, ARAGON_TRANSCRIPT_RETAIN,
|
|
15
|
+
* ARAGON_RENDER_GOVERNOR, ARAGON_MAX_RENDER_INTERVAL_MS.
|
|
13
16
|
*
|
|
14
17
|
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
15
18
|
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|