@aragon-agent/cli 0.5.8 → 0.5.10
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 +1157 -747
- package/README.md +1552 -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/boot/guard.d.ts +135 -0
- package/dist/boot/guard.js +218 -0
- package/dist/boot/rollback.d.ts +86 -0
- package/dist/boot/rollback.js +185 -0
- package/dist/cli.js +368 -3
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +426 -2
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +31 -0
- package/dist/config/app-paths.js +37 -0
- package/dist/config/cli-commands.d.ts +79 -2
- package/dist/config/cli-commands.js +185 -3
- package/dist/config/env.d.ts +15 -3
- package/dist/config/env.js +143 -3
- package/dist/config/load.d.ts +66 -0
- package/dist/config/load.js +107 -1
- package/dist/config/schema.d.ts +268 -2
- package/dist/config/schema.js +288 -1
- package/dist/config/store.js +32 -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/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -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 +11 -4
- package/dist/ui/App.js +359 -37
- package/dist/ui/BottomStatusRow.d.ts +65 -0
- package/dist/ui/BottomStatusRow.js +17 -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/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- 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/dist/update/classify-failure.d.ts +32 -0
- package/dist/update/classify-failure.js +60 -0
- package/dist/update/install-lock.d.ts +55 -0
- package/dist/update/install-lock.js +184 -0
- package/dist/update/install-source.d.ts +97 -0
- package/dist/update/install-source.js +218 -0
- package/dist/update/installer.d.ts +108 -0
- package/dist/update/installer.js +242 -0
- package/dist/update/limits.d.ts +111 -0
- package/dist/update/limits.js +111 -0
- package/dist/update/npm-view.d.ts +62 -0
- package/dist/update/npm-view.js +133 -0
- package/dist/update/registry.d.ts +52 -0
- package/dist/update/registry.js +161 -0
- package/dist/update/semver.d.ts +89 -0
- package/dist/update/semver.js +295 -0
- package/dist/update/service.d.ts +193 -0
- package/dist/update/service.js +636 -0
- package/dist/update/state.d.ts +103 -0
- package/dist/update/state.js +142 -0
- package/dist/update/types.d.ts +169 -0
- package/dist/update/types.js +68 -0
- package/package.json +3 -3
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -1,7 +1,7 @@
|
|
|
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 { CommandRegistry } from './registry.js';
|
|
7
7
|
export declare function registerBuiltinCommands(registry: CommandRegistry): void;
|
|
@@ -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,115 @@ 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
|
+
/**
|
|
35
|
+
* `/update status` - the human-readable readout (cli-auto-update §4.4).
|
|
36
|
+
*
|
|
37
|
+
* ASCII ONLY and NO GLYPHS: this string goes into a transcript `notice`, which
|
|
38
|
+
* `Transcript.tsx` renders with its own marker; the glyph tier belongs to the
|
|
39
|
+
* bottom-row line, not here. Everything a user could want to act on is named:
|
|
40
|
+
* what is running, what is available, how it got installed, and when we look
|
|
41
|
+
* again.
|
|
42
|
+
*/
|
|
43
|
+
function formatUpdateStatus(port, override) {
|
|
44
|
+
const snapshot = override ?? port.snapshot();
|
|
45
|
+
const lines = [
|
|
46
|
+
`Auto-update: ${snapshot.phase}`,
|
|
47
|
+
` running ${snapshot.currentVersion}`,
|
|
48
|
+
` latest ${snapshot.latestVersion ?? 'unknown'}`,
|
|
49
|
+
` source ${snapshot.source}`,
|
|
50
|
+
];
|
|
51
|
+
if (snapshot.reason)
|
|
52
|
+
lines.push(` reason ${snapshot.reason}`);
|
|
53
|
+
if (snapshot.advice)
|
|
54
|
+
lines.push(` install ${snapshot.advice}`);
|
|
55
|
+
// The three hardening lines, each only when it has something to say
|
|
56
|
+
// (cli-auto-update-hardening section 6.3).
|
|
57
|
+
//
|
|
58
|
+
// THE KEY COLUMN IS TEN WIDE, WHICH IS WHY THE FIRST KEY IS `rollback` AND NOT
|
|
59
|
+
// `rolledback`. Section 6.3 asks for "a padded key, matching the six lines
|
|
60
|
+
// `formatUpdateStatus` already emits", and every one of those pads the key to
|
|
61
|
+
// ten so the values start in the same column - the entire reason a readout is
|
|
62
|
+
// padded at all. `rolledback` is ten characters on its own, so it cannot take
|
|
63
|
+
// a separating space without pushing its value one column past the other six,
|
|
64
|
+
// which is the misalignment the padding exists to prevent. The spec's sample
|
|
65
|
+
// block is internally consistent (all three new lines at eleven) and
|
|
66
|
+
// inconsistent with the six it says it matches; the prose is the requirement.
|
|
67
|
+
//
|
|
68
|
+
// THE ROLLBACK LINE CARRIES NO FAILURE COUNT on purpose: `bootFailures` is
|
|
69
|
+
// zeroed by the rollback itself, so any number printed here would be `0`. The
|
|
70
|
+
// count that mattered is in the `update_rolled_back` log record.
|
|
71
|
+
if (snapshot.rolledBackFrom) {
|
|
72
|
+
const to = snapshot.rolledBackTo ?? snapshot.currentVersion;
|
|
73
|
+
lines.push(` rollback ${snapshot.rolledBackFrom} -> ${to}`);
|
|
74
|
+
}
|
|
75
|
+
// What `aragon update --rollback` would reinstall. This is the only place a
|
|
76
|
+
// user can find that out BEFORE running the command.
|
|
77
|
+
if (snapshot.lastGoodVersion)
|
|
78
|
+
lines.push(` lastgood ${snapshot.lastGoodVersion}`);
|
|
79
|
+
// The only place a user can learn their machine updates through the fallback.
|
|
80
|
+
if (snapshot.probe) {
|
|
81
|
+
lines.push(` probe ${snapshot.probe === 'npm' ? 'npm (proxy fallback)' : 'http'}`);
|
|
82
|
+
}
|
|
83
|
+
if (snapshot.consecutiveFailures > 0) {
|
|
84
|
+
lines.push(` failures ${snapshot.consecutiveFailures} in a row`);
|
|
85
|
+
}
|
|
86
|
+
const next = override ? port.nextCheckAt() : snapshot.nextCheckAt;
|
|
87
|
+
lines.push(` next ${next ? new Date(next).toLocaleTimeString() : 'not scheduled'}`);
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
}
|
|
90
|
+
function formatFastStatus(controller) {
|
|
91
|
+
const status = controller.getFastStatus();
|
|
92
|
+
const cfg = controller.getFastConfig();
|
|
93
|
+
const state = !status.registered
|
|
94
|
+
? 'off for this session (not configured at launch)'
|
|
95
|
+
: status.enabled
|
|
96
|
+
? 'on'
|
|
97
|
+
: 'off';
|
|
98
|
+
const lines = [];
|
|
99
|
+
if (status.tier.ok) {
|
|
100
|
+
const same = status.tier.sameAsMain ? ' (same as main)' : '';
|
|
101
|
+
lines.push(`Fast tier: ${state}. Model: ${status.tier.ref.providerId}:${status.tier.ref.modelId}${same}.`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push(`Fast tier: ${state}. Model: ${cfg.model || '(not set)'}.`);
|
|
105
|
+
}
|
|
106
|
+
lines.push(`Review: ${cfg.review ? `every ${cfg.reviewEveryTurns} turns` : 'off'}. ` +
|
|
107
|
+
`Delegation: ${cfg.delegate ? 'on' : 'off'}.`);
|
|
108
|
+
const snapshot = status.snapshot;
|
|
109
|
+
const cost = snapshot.pricingUnknown
|
|
110
|
+
? `unknown (no price table for ${snapshot.model})`
|
|
111
|
+
: formatCost(computeCost(snapshot.usage, status.tier.ok ? controller.getModelInfoFor(status.tier.ref).cost : undefined));
|
|
112
|
+
// `n/N`, NEVER A BARE `n` (fast-model-tier-hardening §4.3). A bare count
|
|
113
|
+
// answers "what have I spent" only for a reader who already knows the
|
|
114
|
+
// ceiling, which is precisely what the user does not know; `12/40` answers
|
|
115
|
+
// that and "how much is left" in the same three characters, and makes the
|
|
116
|
+
// budget discoverable to someone who never read the config schema. The
|
|
117
|
+
// exhausted form names the remedy inline, because a status line that reports
|
|
118
|
+
// a stop without its escape hatch just relocates the question.
|
|
119
|
+
//
|
|
120
|
+
// `budgetReached` is READ, not re-derived (RV-H10): with the tier off both
|
|
121
|
+
// numbers are `0` placeholders and `reviews >= reviewBudget` would be true.
|
|
122
|
+
const budget = snapshot.budgetReached ? ' (budget reached - raise with /fast budget <n>)' : '';
|
|
123
|
+
lines.push(`This session: ${snapshot.reviews}/${snapshot.reviewBudget} reviews${budget}, ` +
|
|
124
|
+
`${snapshot.delegated} delegated subagents, ` +
|
|
125
|
+
`in ${formatTokens(snapshot.usage.inputTokens)} / out ` +
|
|
126
|
+
`${formatTokens(snapshot.usage.outputTokens)}, cost ${cost}.`);
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
19
129
|
const COMMANDS = [
|
|
20
130
|
{
|
|
21
131
|
name: 'help',
|
|
@@ -223,6 +333,249 @@ const COMMANDS = [
|
|
|
223
333
|
ctx.notify('warn', `Unknown argument "${verb}" - use /team [status|on|off|max <n>].`);
|
|
224
334
|
},
|
|
225
335
|
},
|
|
336
|
+
{
|
|
337
|
+
name: 'fast',
|
|
338
|
+
description: 'Fast model tier: status | on | off | model <id> | provider <id> | same | ' +
|
|
339
|
+
'review <n|off> | budget <n> | delegate on|off',
|
|
340
|
+
/**
|
|
341
|
+
* `/fast on` CANNOT turn the tier on in a session that started without it,
|
|
342
|
+
* for the reason `/team on` above records at length: the tool array is built
|
|
343
|
+
* ONCE and rebuilding it is forbidden (C-2), so a session that grew a schema
|
|
344
|
+
* field mid-flight would be a session whose request payloads change shape
|
|
345
|
+
* between turns.
|
|
346
|
+
*
|
|
347
|
+
* EVERY MUTATING BRANCH DOES THE TWO CALLS PLUS PERSIST that `/todo`
|
|
348
|
+
* documents: `controller.setFastConfig(patch)` updates the object `App`
|
|
349
|
+
* reads at render time AND re-resolves the tier AND rebuilds the prompt;
|
|
350
|
+
* `ctx.persistConfig({ fast: patch })` survives the session. Persist alone
|
|
351
|
+
* reports success and changes nothing until relaunch; the setter alone
|
|
352
|
+
* forgets by morning.
|
|
353
|
+
*/
|
|
354
|
+
run: (ctx) => {
|
|
355
|
+
const arg = ctx.args.trim();
|
|
356
|
+
const [rawVerb, value] = arg.split(/\s+/, 2);
|
|
357
|
+
const verb = (rawVerb ?? '').toLowerCase();
|
|
358
|
+
const controller = ctx.controller;
|
|
359
|
+
// Refused mid-dispatch for the reason `/team` records: half a dispatch
|
|
360
|
+
// under one tier and half under another produces a report nothing can
|
|
361
|
+
// afterwards explain. THAT ARGUMENT IS ABOUT WRITES, so the two READS are
|
|
362
|
+
// grouped and answerable (RV-H3): `/fast status` always was, and a bare
|
|
363
|
+
// `/fast budget` reports the same numbers by another name. `/fast budget
|
|
364
|
+
// 80` is a write and is still refused.
|
|
365
|
+
const isRead = verb === 'status' || (verb === 'budget' && !value);
|
|
366
|
+
if (verb && !isRead && controller.isTeamBusy()) {
|
|
367
|
+
ctx.notify('warn', 'Cannot change fast-tier settings mid-dispatch.');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const persist = (patch) => {
|
|
371
|
+
controller.setFastConfig(patch);
|
|
372
|
+
ctx.persistConfig({ fast: patch });
|
|
373
|
+
};
|
|
374
|
+
if (!verb || verb === 'status') {
|
|
375
|
+
ctx.notify('info', formatFastStatus(controller));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (verb === 'on' || verb === 'off') {
|
|
379
|
+
const enabled = verb === 'on';
|
|
380
|
+
persist({ enabled });
|
|
381
|
+
if (!controller.isFastRegistered()) {
|
|
382
|
+
// The honest branch, and the whole point of splitting by what was
|
|
383
|
+
// decided at construction (§3.3). It is one sentence, and it keeps
|
|
384
|
+
// AC-2 / AC-3 intact: a session launched without the tier never grows
|
|
385
|
+
// a schema field, whatever the user types afterwards.
|
|
386
|
+
ctx.notify('info', enabled
|
|
387
|
+
? 'The fast tier is off for this session (not configured at launch). ' +
|
|
388
|
+
'Saved for next launch.'
|
|
389
|
+
: 'The fast tier is already off for this session. Saved for next launch.');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
controller.setFastEnabled(enabled);
|
|
393
|
+
ctx.toast('success', `Fast tier ${enabled ? 'on' : 'off'}.`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (verb === 'model') {
|
|
397
|
+
const raw = (value ?? '').trim();
|
|
398
|
+
if (!raw) {
|
|
399
|
+
ctx.notify('warn', 'Usage: /fast model <id> (or provider:model)');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// `provider:model` is accepted because that is how a user thinks about a
|
|
403
|
+
// tier on another vendor, and an unknown provider is rejected with THE
|
|
404
|
+
// MESSAGE `cli.tsx` ALREADY PRINTS rather than a second wording for the
|
|
405
|
+
// same fact (RV-16).
|
|
406
|
+
const colon = raw.indexOf(':');
|
|
407
|
+
if (colon > 0) {
|
|
408
|
+
const provider = raw.slice(0, colon);
|
|
409
|
+
const model = raw.slice(colon + 1);
|
|
410
|
+
if (!isAdapterProvider(provider)) {
|
|
411
|
+
ctx.notify('warn', `Unknown provider "${provider}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
persist({ provider, model });
|
|
415
|
+
ctx.toast('success', `Fast model set to ${provider}:${model}.`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
persist({ model: raw });
|
|
419
|
+
ctx.toast('success', `Fast model set to ${raw}.`);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (verb === 'provider') {
|
|
423
|
+
const raw = (value ?? '').trim();
|
|
424
|
+
if (!raw) {
|
|
425
|
+
ctx.notify('warn', 'Usage: /fast provider <id>');
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (!isAdapterProvider(raw)) {
|
|
429
|
+
ctx.notify('warn', `Unknown provider "${raw}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
persist({ provider: raw });
|
|
433
|
+
ctx.toast('success', `Fast provider set to ${raw}.`);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (verb === 'same') {
|
|
437
|
+
// R-e IN ONE WORD. `sameAsMain` is display-only and no branch reads it
|
|
438
|
+
// (R-13): the user is allowed to run both tiers on one model, and the
|
|
439
|
+
// review still costs what it costs.
|
|
440
|
+
const cfg = controller.getConfig();
|
|
441
|
+
persist({
|
|
442
|
+
provider: cfg.provider,
|
|
443
|
+
model: cfg.model,
|
|
444
|
+
baseUrl: cfg.baseUrl ?? '',
|
|
445
|
+
});
|
|
446
|
+
ctx.toast('success', `Fast tier set to ${cfg.provider}:${cfg.model} (same as main).`);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (verb === 'review') {
|
|
450
|
+
const raw = (value ?? '').trim().toLowerCase();
|
|
451
|
+
if (raw === 'off') {
|
|
452
|
+
persist({ review: false });
|
|
453
|
+
ctx.toast('success', 'Fast review off.');
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const n = Number.parseInt(raw, 10);
|
|
457
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
458
|
+
ctx.notify('warn', 'Usage: /fast review <n> | off');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// Clamped by `setFastConfig` and again on the persist path, so a value
|
|
462
|
+
// outside the range can reach neither the runtime nor the file.
|
|
463
|
+
const applied = controller.setFastConfig({ review: true, reviewEveryTurns: n });
|
|
464
|
+
ctx.persistConfig({
|
|
465
|
+
fast: { review: true, reviewEveryTurns: applied.reviewEveryTurns },
|
|
466
|
+
});
|
|
467
|
+
ctx.toast('success', `Fast review every ${applied.reviewEveryTurns} turns.`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (verb === 'budget') {
|
|
471
|
+
const snapshot = controller.getFastStatus().snapshot;
|
|
472
|
+
const raw = (value ?? '').trim();
|
|
473
|
+
if (!raw) {
|
|
474
|
+
// The READ. Answerable mid-dispatch (see the guard above), and it
|
|
475
|
+
// reports the same pair `/fast status` renders as `n/N`.
|
|
476
|
+
ctx.notify('info', `Fast review budget: ${snapshot.reviews} of ${snapshot.reviewBudget} used ` +
|
|
477
|
+
'this session. Use /fast budget <1-500> to raise it.');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const n = Number.parseInt(raw, 10);
|
|
481
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
482
|
+
// Change NOTHING on a typo, the discipline `/max-tokens` sets: a
|
|
483
|
+
// silent resolve to the default discards the number the user meant.
|
|
484
|
+
ctx.notify('warn', 'Usage: /fast budget <1-500>');
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// BOTH CALLS, IN THIS ORDER (RV-H4 / AC-H17). `setFastConfig` is what
|
|
488
|
+
// makes a raised budget take effect LIVE - the reviewer's `budgetLimit()`
|
|
489
|
+
// reads the controller's in-memory config, never disk - and it also
|
|
490
|
+
// re-emits `tier_changed`, which is how the new denominator reaches the
|
|
491
|
+
// status chip without a restart. See the comment on this command: a
|
|
492
|
+
// persist alone reports success and changes nothing until relaunch.
|
|
493
|
+
const applied = controller.setFastConfig({ reviewMaxPerSession: n });
|
|
494
|
+
ctx.persistConfig({
|
|
495
|
+
fast: { reviewMaxPerSession: applied.reviewMaxPerSession },
|
|
496
|
+
});
|
|
497
|
+
// The APPLIED value, not the requested one, so a clamp is visible.
|
|
498
|
+
ctx.toast('success', `Fast review budget: ${applied.reviewMaxPerSession} per session. ` +
|
|
499
|
+
`${snapshot.reviews} used.`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (verb === 'delegate') {
|
|
503
|
+
const raw = (value ?? '').trim().toLowerCase();
|
|
504
|
+
if (raw !== 'on' && raw !== 'off') {
|
|
505
|
+
ctx.notify('warn', 'Usage: /fast delegate <on|off>');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
persist({ delegate: raw === 'on' });
|
|
509
|
+
ctx.toast('success', `Fast delegation ${raw}.`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use /fast ` +
|
|
513
|
+
'[status|on|off|model <id>|provider <id>|same|review <n|off>|budget <n>|' +
|
|
514
|
+
'delegate on|off].');
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: 'update',
|
|
519
|
+
description: 'Auto-update: status | now | skip | off',
|
|
520
|
+
/**
|
|
521
|
+
* THE GUARANTEED REPORTING SURFACE for the updater (C-12 / D-18).
|
|
522
|
+
*
|
|
523
|
+
* The bottom row can be preempted by a toast or by the activity line, and it
|
|
524
|
+
* is deliberately last in that precedence (D-2), so a user who is actually
|
|
525
|
+
* working may never see it. This command always answers "is it on, what did
|
|
526
|
+
* it find, and when does it look again".
|
|
527
|
+
*
|
|
528
|
+
* REACHES THE SERVICE THROUGH `ctx.update`, NOT THROUGH `ctx.controller`
|
|
529
|
+
* (C-14 / P0-2). `/fast`, `/team` and `/todo` ride on the controller because
|
|
530
|
+
* their subsystems live there; `update/` may not import `agent/` at all, so
|
|
531
|
+
* it carries its own optional port. `undefined` is a legitimate state - `off`,
|
|
532
|
+
* a non-TTY, or CI - and says so rather than pretending to have failed.
|
|
533
|
+
*/
|
|
534
|
+
run: (ctx) => {
|
|
535
|
+
const arg = ctx.args.trim();
|
|
536
|
+
const [rawVerb] = arg.split(/\s+/, 1);
|
|
537
|
+
const verb = (rawVerb ?? '').toLowerCase();
|
|
538
|
+
const port = ctx.update;
|
|
539
|
+
// `/update off` is the one branch that works WITHOUT a service: turning
|
|
540
|
+
// something off must not require it to be running, or a user who launched
|
|
541
|
+
// with `--no-update` can never make that stick.
|
|
542
|
+
if (verb === 'off') {
|
|
543
|
+
ctx.persistConfig({ update: { mode: 'off' } });
|
|
544
|
+
ctx.toast('success', 'Auto-update off. Takes effect on the next launch.');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (!port) {
|
|
548
|
+
ctx.notify('info', 'Updates are disabled for this session (update.mode: off, a non-TTY, or CI). ' +
|
|
549
|
+
'Enable with: aragon config set update.mode auto');
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (!verb || verb === 'status') {
|
|
553
|
+
ctx.notify('info', formatUpdateStatus(port));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (verb === 'now') {
|
|
557
|
+
// `force: true` bypasses the machine-wide throttle - a human typing this
|
|
558
|
+
// is the one caller entitled to ignore `lastCheckAt`.
|
|
559
|
+
void port
|
|
560
|
+
.checkNow({ force: true })
|
|
561
|
+
.then((snapshot) => ctx.notify('info', formatUpdateStatus(port, snapshot)))
|
|
562
|
+
.catch(() => ctx.notify('warn', 'Update check failed. See the log for details.'));
|
|
563
|
+
ctx.toast('info', 'Checking for updates...');
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (verb === 'skip') {
|
|
567
|
+
const version = port.snapshot().latestVersion;
|
|
568
|
+
if (!version) {
|
|
569
|
+
ctx.notify('info', 'No update is being offered, so there is nothing to skip.');
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
port.skip(version);
|
|
573
|
+
ctx.toast('success', `Skipped ${version}. You will hear about the next release.`);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use /update [status|now|skip|off].`);
|
|
577
|
+
},
|
|
578
|
+
},
|
|
226
579
|
{
|
|
227
580
|
name: 'todo',
|
|
228
581
|
description: 'Todo planning: status | on | off | panel on|off | follow <mode> | clear | continue',
|
|
@@ -377,6 +730,77 @@ const COMMANDS = [
|
|
|
377
730
|
'/todo [status|on|off|panel on|off|follow <mode>|clear|continue].');
|
|
378
731
|
},
|
|
379
732
|
},
|
|
733
|
+
{
|
|
734
|
+
name: 'retry',
|
|
735
|
+
description: 'API retry: show | on | off | max <n>',
|
|
736
|
+
/**
|
|
737
|
+
* TWO CALLS FOR EVERY MUTATION, IN THIS ORDER, and neither is optional
|
|
738
|
+
* (llm-api-retry-backoff §6.9 / §6.3):
|
|
739
|
+
*
|
|
740
|
+
* `setRetryConfig` replaces the policy on the live `ProviderRegistry`, so
|
|
741
|
+
* the change takes effect in THIS session — and, because
|
|
742
|
+
* `TeamRuntime` holds the same registry instance, for every
|
|
743
|
+
* child too. Without it `/retry off` reports success and
|
|
744
|
+
* changes nothing until the next launch.
|
|
745
|
+
* `persistConfig` is what survives the session.
|
|
746
|
+
*
|
|
747
|
+
* UNLIKE `/team on` AND `/todo on` there is no "off for this session" branch:
|
|
748
|
+
* retry is not a tool, nothing is registered at construction, and there is
|
|
749
|
+
* therefore no state a flag could have made unreachable. `--no-retry` is a
|
|
750
|
+
* starting value, not a permanent verdict.
|
|
751
|
+
*/
|
|
752
|
+
run: (ctx) => {
|
|
753
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
754
|
+
const [verb, value] = arg.split(/\s+/, 2);
|
|
755
|
+
const controller = ctx.controller;
|
|
756
|
+
if (!verb || verb === 'show' || verb === 'status') {
|
|
757
|
+
const cfg = controller.getRetryConfig();
|
|
758
|
+
if (!cfg.enabled || cfg.maxRetries <= 0) {
|
|
759
|
+
ctx.notify('info', `API retry: off (maxRetries ${cfg.maxRetries}). Use /retry on or /retry max <n>.`);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
ctx.notify('info', `API retry: on. Up to ${cfg.maxRetries} retries, ${cfg.initialDelayMs}ms doubling to ` +
|
|
763
|
+
`${cfg.maxDelayMs}ms${cfg.jitter ? ' with jitter' : ''}, ` +
|
|
764
|
+
`giving up after ${Math.round(cfg.maxElapsedMs / 1000)}s. ` +
|
|
765
|
+
`Retry-After ${cfg.respectRetryAfter ? 'honoured' : 'ignored'}.`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (verb === 'on' || verb === 'off') {
|
|
769
|
+
const enabled = verb === 'on';
|
|
770
|
+
const applied = controller.setRetryConfig({ enabled });
|
|
771
|
+
// A PARTIAL `retry` section, safe only because `updatePersistedConfig`
|
|
772
|
+
// deep-merges it — the same contract `/team on` relies on.
|
|
773
|
+
ctx.persistConfig({ retry: { enabled } });
|
|
774
|
+
if (enabled && applied.maxRetries <= 0) {
|
|
775
|
+
// Honest rather than cheerful: `enabled` is on but the count is the
|
|
776
|
+
// kill switch, so nothing will actually retry.
|
|
777
|
+
ctx.notify('warn', 'API retry enabled, but maxRetries is 0 - use /retry max <n>.');
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
ctx.toast('success', `API retry ${enabled ? 'on' : 'off'}.`);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (verb === 'max') {
|
|
784
|
+
// `Number.isFinite` and `>= 0`, NOT `> 0`: `0` is this key's documented
|
|
785
|
+
// floor and its kill switch, and rejecting it here would make `/retry max 0`
|
|
786
|
+
// the one documented control that does not work (R-16).
|
|
787
|
+
const n = Number.parseInt(value ?? '', 10);
|
|
788
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
789
|
+
ctx.notify('warn', `Usage: /retry max <0-${HARD_MAX_RETRIES}> (0 turns retry off)`);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const applied = controller.setRetryConfig({ maxRetries: n });
|
|
793
|
+
ctx.persistConfig({
|
|
794
|
+
retry: { maxRetries: applied.maxRetries },
|
|
795
|
+
});
|
|
796
|
+
ctx.toast('success', applied.maxRetries === 0
|
|
797
|
+
? 'API retry off (maxRetries 0).'
|
|
798
|
+
: `API retry: up to ${applied.maxRetries} retries.`);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use /retry [show|on|off|max <n>].`);
|
|
802
|
+
},
|
|
803
|
+
},
|
|
380
804
|
{
|
|
381
805
|
name: 'expand',
|
|
382
806
|
description: 'Expand / collapse a recent tool card (default: last)',
|
|
@@ -7,6 +7,7 @@ import type { AgentMode } from '../agent/agent-mode.js';
|
|
|
7
7
|
import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
|
|
8
8
|
import type { PersistedConfig } from '../config/schema.js';
|
|
9
9
|
import type { FollowThroughBudget } from '../todo/follow-through.js';
|
|
10
|
+
import type { UpdateCommandPort } from '../update/types.js';
|
|
10
11
|
export interface CommandContext {
|
|
11
12
|
/** Raw argument string after the command word (may be empty). */
|
|
12
13
|
args: string;
|
|
@@ -52,6 +53,22 @@ export interface CommandContext {
|
|
|
52
53
|
* change; absent means the clause degrades to naming the ceiling alone.
|
|
53
54
|
*/
|
|
54
55
|
followBudget?: FollowThroughBudget;
|
|
56
|
+
/**
|
|
57
|
+
* The updater, when this session has one (cli-auto-update §4.4 / C-14 / P0-2).
|
|
58
|
+
*
|
|
59
|
+
* `/fast`, `/team` and `/todo` reach their subsystems through `controller`,
|
|
60
|
+
* because those subsystems live on `AgentController`. This one does not, and
|
|
61
|
+
* by §3.1 it MUST NOT — `update/` may not import `agent/` — so it needs its
|
|
62
|
+
* own field, and this is the only seam a slash command has.
|
|
63
|
+
*
|
|
64
|
+
* OPTIONAL, for two independent reasons. The service is legitimately absent
|
|
65
|
+
* under `update.mode: 'off'`, on a non-TTY and in CI (§3.8), so `/update` has
|
|
66
|
+
* to have something honest to say when it is missing; and a REQUIRED field
|
|
67
|
+
* would break every existing `CommandContext` fixture in this package at
|
|
68
|
+
* compile time, for a feature none of them are about. `followBudget` above is
|
|
69
|
+
* the precedent for an optional, read-at-command-time member.
|
|
70
|
+
*/
|
|
71
|
+
update?: UpdateCommandPort;
|
|
55
72
|
/** Rebuild the system prompt and the dynamic command list after a skill change. */
|
|
56
73
|
refreshSkills: () => void;
|
|
57
74
|
/**
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
15
|
* ├── prompt-history.jsonl submitted prompts, append-only (0600 on POSIX)
|
|
16
16
|
* ├── state.json UI bookkeeping scalars (0600 on POSIX)
|
|
17
|
+
* ├── update-state.json auto-update coordination (0600 on POSIX)
|
|
18
|
+
* ├── update-install.lock held by the running `npm i -g` child
|
|
19
|
+
* ├── update-install.log that child's stdout+stderr; unlinked on exit
|
|
17
20
|
* ├── logs/ JSONL logs (0700 on POSIX)
|
|
18
21
|
* ├── sessions/ /save · /resume
|
|
19
22
|
* ├── skills/ `aragon skills install` target
|
|
@@ -79,6 +82,34 @@ export declare function getConfigBackupPath(): string;
|
|
|
79
82
|
export declare function getPromptHistoryPath(): string;
|
|
80
83
|
/** `<home>/state.json` — UI bookkeeping scalars only; see `ui-state.ts`. */
|
|
81
84
|
export declare function getUiStatePath(): string;
|
|
85
|
+
/**
|
|
86
|
+
* `<home>/update-state.json` — cross-process auto-update coordination
|
|
87
|
+
* (cli-auto-update §5.2).
|
|
88
|
+
*
|
|
89
|
+
* Deliberately NOT inside `state.json`: that file is documented as UI
|
|
90
|
+
* bookkeeping scalars only (C-6), and this is coordination state that a SECOND
|
|
91
|
+
* aragon process reads to decide whether anyone has already asked the registry.
|
|
92
|
+
* Different lifetime, different failure policy, different reader.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getUpdateStatePath(): string;
|
|
95
|
+
/**
|
|
96
|
+
* `<home>/update-install.lock` — the `wx` lock the running installer child holds
|
|
97
|
+
* (cli-auto-update §3.7).
|
|
98
|
+
*
|
|
99
|
+
* A FILE, and a direct child of the root, for the reason the two files above
|
|
100
|
+
* are: `logs/`, `sessions/` and `skills/` are all scan roots, and every extra
|
|
101
|
+
* file inside one is another thing each walk has to recognise and skip.
|
|
102
|
+
*/
|
|
103
|
+
export declare function getUpdateLockPath(): string;
|
|
104
|
+
/**
|
|
105
|
+
* `<home>/update-install.log` — the installer child's stdout+stderr
|
|
106
|
+
* (cli-auto-update §3.5 / U-5).
|
|
107
|
+
*
|
|
108
|
+
* A REAL FILE and not a pipe: the child is detached and outlives the parent
|
|
109
|
+
* (D-10), and a pipe whose parent has exited takes EPIPE on the next write. The
|
|
110
|
+
* parent tails it on the child's exit, redacts, logs, and unlinks.
|
|
111
|
+
*/
|
|
112
|
+
export declare function getUpdateInstallLogPath(): string;
|
|
82
113
|
export declare function getLogsDir(): string;
|
|
83
114
|
export declare function getSessionsDir(): string;
|
|
84
115
|
/** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
|
package/dist/config/app-paths.js
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
15
|
* ├── prompt-history.jsonl submitted prompts, append-only (0600 on POSIX)
|
|
16
16
|
* ├── state.json UI bookkeeping scalars (0600 on POSIX)
|
|
17
|
+
* ├── update-state.json auto-update coordination (0600 on POSIX)
|
|
18
|
+
* ├── update-install.lock held by the running `npm i -g` child
|
|
19
|
+
* ├── update-install.log that child's stdout+stderr; unlinked on exit
|
|
17
20
|
* ├── logs/ JSONL logs (0700 on POSIX)
|
|
18
21
|
* ├── sessions/ /save · /resume
|
|
19
22
|
* ├── skills/ `aragon skills install` target
|
|
@@ -140,6 +143,40 @@ export function getPromptHistoryPath() {
|
|
|
140
143
|
export function getUiStatePath() {
|
|
141
144
|
return join(resolution.root, 'state.json');
|
|
142
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* `<home>/update-state.json` — cross-process auto-update coordination
|
|
148
|
+
* (cli-auto-update §5.2).
|
|
149
|
+
*
|
|
150
|
+
* Deliberately NOT inside `state.json`: that file is documented as UI
|
|
151
|
+
* bookkeeping scalars only (C-6), and this is coordination state that a SECOND
|
|
152
|
+
* aragon process reads to decide whether anyone has already asked the registry.
|
|
153
|
+
* Different lifetime, different failure policy, different reader.
|
|
154
|
+
*/
|
|
155
|
+
export function getUpdateStatePath() {
|
|
156
|
+
return join(resolution.root, 'update-state.json');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* `<home>/update-install.lock` — the `wx` lock the running installer child holds
|
|
160
|
+
* (cli-auto-update §3.7).
|
|
161
|
+
*
|
|
162
|
+
* A FILE, and a direct child of the root, for the reason the two files above
|
|
163
|
+
* are: `logs/`, `sessions/` and `skills/` are all scan roots, and every extra
|
|
164
|
+
* file inside one is another thing each walk has to recognise and skip.
|
|
165
|
+
*/
|
|
166
|
+
export function getUpdateLockPath() {
|
|
167
|
+
return join(resolution.root, 'update-install.lock');
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* `<home>/update-install.log` — the installer child's stdout+stderr
|
|
171
|
+
* (cli-auto-update §3.5 / U-5).
|
|
172
|
+
*
|
|
173
|
+
* A REAL FILE and not a pipe: the child is detached and outlives the parent
|
|
174
|
+
* (D-10), and a pipe whose parent has exited takes EPIPE on the next write. The
|
|
175
|
+
* parent tails it on the child's exit, redacts, logs, and unlinks.
|
|
176
|
+
*/
|
|
177
|
+
export function getUpdateInstallLogPath() {
|
|
178
|
+
return join(resolution.root, 'update-install.log');
|
|
179
|
+
}
|
|
143
180
|
export function getLogsDir() {
|
|
144
181
|
return join(resolution.root, 'logs');
|
|
145
182
|
}
|