@aragon-agent/cli 0.5.9 → 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 -1026
- package/README.md +123 -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 +283 -2
- package/dist/commands/builtins.js +118 -0
- 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 +27 -1
- package/dist/config/cli-commands.js +51 -2
- package/dist/config/env.d.ts +10 -1
- package/dist/config/env.js +41 -1
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +25 -1
- package/dist/config/schema.d.ts +56 -0
- package/dist/config/schema.js +81 -0
- package/dist/config/store.js +13 -1
- package/dist/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/ui/App.d.ts +11 -1
- package/dist/ui/App.js +88 -2
- package/dist/ui/BottomStatusRow.d.ts +25 -3
- package/dist/ui/BottomStatusRow.js +10 -7
- package/dist/ui/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- 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
|
@@ -31,6 +31,62 @@ import { perfCommand } from './perf.js';
|
|
|
31
31
|
* feature that is spending money is the same class of lie as pricing a Haiku
|
|
32
32
|
* child at Sonnet rates.
|
|
33
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
|
+
}
|
|
34
90
|
function formatFastStatus(controller) {
|
|
35
91
|
const status = controller.getFastStatus();
|
|
36
92
|
const cfg = controller.getFastConfig();
|
|
@@ -458,6 +514,68 @@ const COMMANDS = [
|
|
|
458
514
|
'delegate on|off].');
|
|
459
515
|
},
|
|
460
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
|
+
},
|
|
461
579
|
{
|
|
462
580
|
name: 'todo',
|
|
463
581
|
description: 'Todo planning: status | on | off | panel on|off | follow <mode> | clear | continue',
|
|
@@ -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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `aragon config get | list | edit | home`, plus the `log.*`, `team.*`,
|
|
3
|
-
* `retry.*` and `
|
|
3
|
+
* `retry.*`, `fast.*` and `update.*` 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 —
|
|
@@ -43,6 +43,17 @@ export declare const RETRY_CONFIG_SET_KEYS: readonly ["retry.enabled", "retry.ma
|
|
|
43
43
|
* `Set fast.model = haiku`.
|
|
44
44
|
*/
|
|
45
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"];
|
|
46
|
+
/**
|
|
47
|
+
* Config keys under `update.` that `aragon config set` accepts (cli-auto-update
|
|
48
|
+
* §4.1).
|
|
49
|
+
*
|
|
50
|
+
* A DEDICATED SETTER RATHER THAN A GENERIC DOTTED WRITER, for the reason the
|
|
51
|
+
* four sets above each record: a generic setter would write `update` as a FLAT
|
|
52
|
+
* string key (`"update.mode": "notify"` at the top level of the file), the
|
|
53
|
+
* section would silently never take effect, and `config set` would still print
|
|
54
|
+
* `Set update.mode = notify`.
|
|
55
|
+
*/
|
|
56
|
+
export declare const UPDATE_CONFIG_SET_KEYS: readonly ["update.mode", "update.checkIntervalMs", "update.registry", "update.distTag"];
|
|
46
57
|
/**
|
|
47
58
|
* Translate a `log.*` dotted key into a patch, or `null` for anything else.
|
|
48
59
|
*
|
|
@@ -94,6 +105,21 @@ export declare function applyRetryConfigSet(key: string, value: string): Partial
|
|
|
94
105
|
* the clamped 50 while the caller echoes the STORED value (AC-13).
|
|
95
106
|
*/
|
|
96
107
|
export declare function applyFastConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
108
|
+
/**
|
|
109
|
+
* Translate an `update.*` dotted key into a patch, or `null` for anything else.
|
|
110
|
+
*
|
|
111
|
+
* The patch is a PARTIAL `update` section, safe only because
|
|
112
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge,
|
|
113
|
+
* setting the mode would silently reset the interval, the registry mirror and
|
|
114
|
+
* the dist-tag (C-4 / R-10).
|
|
115
|
+
*
|
|
116
|
+
* EVERY KEY ROUTES THROUGH `clampUpdateConfig`, not through a local parse, for
|
|
117
|
+
* the reason `applyRetryConfigSet` and `applyFastConfigSet` both record: one
|
|
118
|
+
* gate for both directions means no key here can pick up a different coercion
|
|
119
|
+
* later, and `config set update.checkIntervalMs 5` writes the clamped 900000
|
|
120
|
+
* while the caller echoes the STORED value (AC-20).
|
|
121
|
+
*/
|
|
122
|
+
export declare function applyUpdateConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
97
123
|
/**
|
|
98
124
|
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
99
125
|
* disk. It is command-line-only (there is no settings-screen entry) and it says
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `aragon config get | list | edit | home`, plus the `log.*`, `team.*`,
|
|
3
|
-
* `retry.*` and `
|
|
3
|
+
* `retry.*`, `fast.*` and `update.*` 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 { clampFastConfig, clampRetryConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, } from './schema.js';
|
|
16
|
+
import { clampFastConfig, clampRetryConfig, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_UPDATE_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 = [
|
|
@@ -87,6 +87,22 @@ export const FAST_CONFIG_SET_KEYS = [
|
|
|
87
87
|
'fast.reviewMaxChars',
|
|
88
88
|
'fast.reviewMaxPerSession',
|
|
89
89
|
];
|
|
90
|
+
/**
|
|
91
|
+
* Config keys under `update.` that `aragon config set` accepts (cli-auto-update
|
|
92
|
+
* §4.1).
|
|
93
|
+
*
|
|
94
|
+
* A DEDICATED SETTER RATHER THAN A GENERIC DOTTED WRITER, for the reason the
|
|
95
|
+
* four sets above each record: a generic setter would write `update` as a FLAT
|
|
96
|
+
* string key (`"update.mode": "notify"` at the top level of the file), the
|
|
97
|
+
* section would silently never take effect, and `config set` would still print
|
|
98
|
+
* `Set update.mode = notify`.
|
|
99
|
+
*/
|
|
100
|
+
export const UPDATE_CONFIG_SET_KEYS = [
|
|
101
|
+
'update.mode',
|
|
102
|
+
'update.checkIntervalMs',
|
|
103
|
+
'update.registry',
|
|
104
|
+
'update.distTag',
|
|
105
|
+
];
|
|
90
106
|
function isTrue(value) {
|
|
91
107
|
return value === 'true' || value === '1';
|
|
92
108
|
}
|
|
@@ -245,6 +261,39 @@ export function applyFastConfigSet(key, value) {
|
|
|
245
261
|
return null;
|
|
246
262
|
}
|
|
247
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Translate an `update.*` dotted key into a patch, or `null` for anything else.
|
|
266
|
+
*
|
|
267
|
+
* The patch is a PARTIAL `update` section, safe only because
|
|
268
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge,
|
|
269
|
+
* setting the mode would silently reset the interval, the registry mirror and
|
|
270
|
+
* the dist-tag (C-4 / R-10).
|
|
271
|
+
*
|
|
272
|
+
* EVERY KEY ROUTES THROUGH `clampUpdateConfig`, not through a local parse, for
|
|
273
|
+
* the reason `applyRetryConfigSet` and `applyFastConfigSet` both record: one
|
|
274
|
+
* gate for both directions means no key here can pick up a different coercion
|
|
275
|
+
* later, and `config set update.checkIntervalMs 5` writes the clamped 900000
|
|
276
|
+
* while the caller echoes the STORED value (AC-20).
|
|
277
|
+
*/
|
|
278
|
+
export function applyUpdateConfigSet(key, value) {
|
|
279
|
+
const patch = (update) => ({ update });
|
|
280
|
+
const clampOne = (field) => {
|
|
281
|
+
const merged = clampUpdateConfig({ ...DEFAULT_UPDATE_CONFIG, [field]: value });
|
|
282
|
+
return { [field]: merged[field] };
|
|
283
|
+
};
|
|
284
|
+
switch (key) {
|
|
285
|
+
case 'update.mode':
|
|
286
|
+
return patch(clampOne('mode'));
|
|
287
|
+
case 'update.checkIntervalMs':
|
|
288
|
+
return patch(clampOne('checkIntervalMs'));
|
|
289
|
+
case 'update.registry':
|
|
290
|
+
return patch(clampOne('registry'));
|
|
291
|
+
case 'update.distTag':
|
|
292
|
+
return patch(clampOne('distTag'));
|
|
293
|
+
default:
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
248
297
|
/**
|
|
249
298
|
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
250
299
|
* disk. It is command-line-only (there is no settings-screen entry) and it says
|
package/dist/config/env.d.ts
CHANGED
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
13
|
* ARAGON_LOG_LEVEL,
|
|
14
14
|
* ARAGON_LOG_FILE, ARAGON_LOG_DIR, ARAGON_TRANSCRIPT_RETAIN,
|
|
15
|
-
* ARAGON_RENDER_GOVERNOR, ARAGON_MAX_RENDER_INTERVAL_MS
|
|
15
|
+
* ARAGON_RENDER_GOVERNOR, ARAGON_MAX_RENDER_INTERVAL_MS,
|
|
16
|
+
* ARAGON_UPDATE.
|
|
17
|
+
*
|
|
18
|
+
* READ ELSEWHERE, ON PURPOSE: `ARAGON_UPDATE_REGISTRY` and `npm_config_registry`
|
|
19
|
+
* are resolved by `update/registry.ts::resolveRegistryUrl`, NOT here, because
|
|
20
|
+
* §3.3 puts them BELOW `config.update.registry` in precedence — the opposite of
|
|
21
|
+
* the env-beats-file rule this file implements. Writing them into `partial`
|
|
22
|
+
* would silently invert that order and make a config-file mirror unreachable on
|
|
23
|
+
* any machine where npm exports its own registry (which is every machine npm
|
|
24
|
+
* runs a script on).
|
|
16
25
|
*
|
|
17
26
|
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
18
27
|
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|
package/dist/config/env.js
CHANGED
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
13
|
* ARAGON_LOG_LEVEL,
|
|
14
14
|
* ARAGON_LOG_FILE, ARAGON_LOG_DIR, ARAGON_TRANSCRIPT_RETAIN,
|
|
15
|
-
* ARAGON_RENDER_GOVERNOR, ARAGON_MAX_RENDER_INTERVAL_MS
|
|
15
|
+
* ARAGON_RENDER_GOVERNOR, ARAGON_MAX_RENDER_INTERVAL_MS,
|
|
16
|
+
* ARAGON_UPDATE.
|
|
17
|
+
*
|
|
18
|
+
* READ ELSEWHERE, ON PURPOSE: `ARAGON_UPDATE_REGISTRY` and `npm_config_registry`
|
|
19
|
+
* are resolved by `update/registry.ts::resolveRegistryUrl`, NOT here, because
|
|
20
|
+
* §3.3 puts them BELOW `config.update.registry` in precedence — the opposite of
|
|
21
|
+
* the env-beats-file rule this file implements. Writing them into `partial`
|
|
22
|
+
* would silently invert that order and make a config-file mirror unreachable on
|
|
23
|
+
* any machine where npm exports its own registry (which is every machine npm
|
|
24
|
+
* runs a script on).
|
|
16
25
|
*
|
|
17
26
|
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
18
27
|
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|
|
@@ -279,6 +288,37 @@ export function readEnvConfig() {
|
|
|
279
288
|
if (Object.keys(section).length > 0)
|
|
280
289
|
partial.fast = section;
|
|
281
290
|
}
|
|
291
|
+
// ARAGON_UPDATE=0|off|false turns auto-update off; `notify` reports without
|
|
292
|
+
// installing; 1|on|true|yes turns it on (cli-auto-update §4.2).
|
|
293
|
+
//
|
|
294
|
+
// A TRI-STATE, so it is parsed by an EXPLICIT POSITIVE LIST PER OUTCOME rather
|
|
295
|
+
// than by the boolean positive list its neighbours use — and emphatically not
|
|
296
|
+
// by `envBool` below, whose NEGATIVE list would resolve `ARAGON_UPDATE=disable`
|
|
297
|
+
// to `true`.
|
|
298
|
+
//
|
|
299
|
+
// ANYTHING UNRECOGNISED IS LEFT ABSENT, which is the invariant this file
|
|
300
|
+
// states three times over (`ARAGON_TEAM_MAX`, `ARAGON_RETRY_MAX`,
|
|
301
|
+
// `ARAGON_MAX_TOKENS`): a value that resolves to a default looks PERMANENTLY
|
|
302
|
+
// SUPPLIED, and the config file underneath can then never win again. For a
|
|
303
|
+
// kill switch that is the worst possible failure — `ARAGON_UPDATE=disabled`
|
|
304
|
+
// would read as "on" while the user believes they turned it off.
|
|
305
|
+
//
|
|
306
|
+
// `ARAGON_TODO_FOLLOW` is the tri-state precedent: the recognised string is
|
|
307
|
+
// written through and `clampUpdateConfig` stays the single gate, so there is
|
|
308
|
+
// never a second validator that can disagree with the first.
|
|
309
|
+
const update = process.env.ARAGON_UPDATE?.trim().toLowerCase();
|
|
310
|
+
if (update !== undefined && update.length > 0) {
|
|
311
|
+
const mode = update === '0' || update === 'false' || update === 'off' || update === 'no'
|
|
312
|
+
? 'off'
|
|
313
|
+
: update === 'notify'
|
|
314
|
+
? 'notify'
|
|
315
|
+
: update === '1' || update === 'true' || update === 'on' || update === 'yes'
|
|
316
|
+
? 'auto'
|
|
317
|
+
: undefined;
|
|
318
|
+
if (mode !== undefined) {
|
|
319
|
+
partial.update = { mode };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
282
322
|
// Render bounds (tui-render-performance §5.3). Same channel story as
|
|
283
323
|
// ARAGON_FULLSCREEN: a container or a wrapper that owns argv can reach neither
|
|
284
324
|
// a flag nor a config file, and a session that freezes is exactly the case
|
package/dist/config/load.d.ts
CHANGED
|
@@ -148,6 +148,23 @@ export interface CliFlags {
|
|
|
148
148
|
* to keep in step with `toFlags`.
|
|
149
149
|
*/
|
|
150
150
|
fastReview?: string;
|
|
151
|
+
/**
|
|
152
|
+
* `--update` ⇒ true, `--no-update` ⇒ false, absent ⇒ undefined
|
|
153
|
+
* (cli-auto-update §4.2).
|
|
154
|
+
*
|
|
155
|
+
* DECLARED AS A PAIR — MANDATORY (C-11), for the reason `--team`,
|
|
156
|
+
* `--todo-panel`, `--render-governor`, `--retry` and `--fast` each record in
|
|
157
|
+
* turn. `update.mode` is PERSISTED and defaults to `'auto'`, so a lone
|
|
158
|
+
* `--no-update` would make commander default `opts.update` to `true` and
|
|
159
|
+
* silently overwrite a stored `off` on every run that passed no flag at all —
|
|
160
|
+
* i.e. the kill switch would un-set itself.
|
|
161
|
+
*
|
|
162
|
+
* A BOOLEAN FOR A TRI-STATE KEY, deliberately: the flag is a SESSION override
|
|
163
|
+
* and `notify` is a preference, not something a user reaches for once. `true`
|
|
164
|
+
* maps to `'auto'`, `false` to `'off'`; `notify` is reachable through the
|
|
165
|
+
* config file and `ARAGON_UPDATE=notify`.
|
|
166
|
+
*/
|
|
167
|
+
update?: boolean;
|
|
151
168
|
}
|
|
152
169
|
/**
|
|
153
170
|
* Resolve the effective config by merging all layers, then derive the runtime
|
package/dist/config/load.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import process from 'node:process';
|
|
9
|
-
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampTheme, clampThinkingLevel, clampMaxTokens, clampMaxRenderInterval, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, } from './schema.js';
|
|
9
|
+
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampMaxTokens, clampMaxRenderInterval, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
12
|
import { getConfigPath } from './app-paths.js';
|
|
@@ -185,6 +185,29 @@ function resolveFastConfig(flags, env, file) {
|
|
|
185
185
|
...reviewPatch,
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Merge the `update` section across all four layers (defaults › file › env ›
|
|
190
|
+
* flags), cli-auto-update §4.2.
|
|
191
|
+
*
|
|
192
|
+
* `!== undefined` ON `flags.update`, which is the rule `resolveTeamConfig`,
|
|
193
|
+
* `resolveTodoConfig`, `resolveRetryConfig` and `resolveFastConfig` each state
|
|
194
|
+
* once (C-11): commander materialises a lone `--no-update` as
|
|
195
|
+
* `opts.update = true` when the flag is absent, so a truthiness check cannot
|
|
196
|
+
* tell `--no-update` from "not passed" — and here that would silently re-enable
|
|
197
|
+
* a subsystem the user turned off, which is the one direction this feature must
|
|
198
|
+
* never fail in.
|
|
199
|
+
*
|
|
200
|
+
* The flag maps onto `mode` rather than carrying its own key, so there is
|
|
201
|
+
* exactly one place that says whether the updater runs.
|
|
202
|
+
*/
|
|
203
|
+
function resolveUpdateConfig(flags, env, file) {
|
|
204
|
+
return clampUpdateConfig({
|
|
205
|
+
...DEFAULT_CONFIG.update,
|
|
206
|
+
...(file.update ?? {}),
|
|
207
|
+
...(env.update ?? {}),
|
|
208
|
+
...(flags.update !== undefined ? { mode: flags.update ? 'auto' : 'off' } : {}),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
188
211
|
/**
|
|
189
212
|
* Resolve the adaptive render governor: flag › env › file › default `true`
|
|
190
213
|
* (tui-render-performance L4).
|
|
@@ -428,6 +451,7 @@ export function loadConfig(flags = {}) {
|
|
|
428
451
|
todo: resolveTodoConfig(flags, env.partial, file),
|
|
429
452
|
retry: resolveRetryConfig(flags, env.partial, file),
|
|
430
453
|
fast: resolveFastConfig(flags, env.partial, file),
|
|
454
|
+
update: resolveUpdateConfig(flags, env.partial, file),
|
|
431
455
|
cwd,
|
|
432
456
|
color,
|
|
433
457
|
colorLevel,
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -434,6 +434,44 @@ export declare const DEFAULT_FAST_CONFIG: FastConfig;
|
|
|
434
434
|
* user really did configure a dead vendor as the MAIN one.
|
|
435
435
|
*/
|
|
436
436
|
export declare function clampFastConfig(raw: unknown): FastConfig;
|
|
437
|
+
/** `auto` installs; `notify` only reports; `off` disables the subsystem. */
|
|
438
|
+
export declare const UPDATE_MODES: readonly ["auto", "notify", "off"];
|
|
439
|
+
export type UpdateMode = (typeof UPDATE_MODES)[number];
|
|
440
|
+
export declare function isUpdateMode(v: unknown): v is UpdateMode;
|
|
441
|
+
export interface UpdateConfig {
|
|
442
|
+
/**
|
|
443
|
+
* Whether the updater installs, merely reports, or does nothing at all.
|
|
444
|
+
*
|
|
445
|
+
* DEFAULTS TO `'auto'`, and that is a PRODUCT decision rather than a technical
|
|
446
|
+
* one: this CLI's users are overwhelmingly on a stale version they did not
|
|
447
|
+
* choose. The trade it makes — a wider supply-chain window in exchange for
|
|
448
|
+
* users who are actually on the current release — is stated plainly in the
|
|
449
|
+
* README (R-1), and `'off'` plus `ARAGON_UPDATE=0` are the documented kill
|
|
450
|
+
* switches.
|
|
451
|
+
*/
|
|
452
|
+
mode: UpdateMode;
|
|
453
|
+
/** Minimum wall-clock between registry checks, MACHINE-WIDE (§3.7). */
|
|
454
|
+
checkIntervalMs: number;
|
|
455
|
+
/** `''` = derive: `ARAGON_UPDATE_REGISTRY` › `npm_config_registry` › npmjs. */
|
|
456
|
+
registry: string;
|
|
457
|
+
/** The dist-tag to track. */
|
|
458
|
+
distTag: string;
|
|
459
|
+
}
|
|
460
|
+
export declare const DEFAULT_UPDATE_CONFIG: UpdateConfig;
|
|
461
|
+
/**
|
|
462
|
+
* THE single gate for every update-config read AND write, mirroring
|
|
463
|
+
* `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
|
|
464
|
+
* / `clampRetryConfig` / `clampFastConfig` for the reason all six record:
|
|
465
|
+
* hardening only the read path leaves a bad value on disk that reverts to the
|
|
466
|
+
* default on every launch, which presents to the user as "my setting won't
|
|
467
|
+
* stick".
|
|
468
|
+
*
|
|
469
|
+
* `registry` is CLAMPED TO `''` rather than rejected when it is not an
|
|
470
|
+
* `http(s):` URL. `''` means "derive it" (§3.3), which is the safe reading of a
|
|
471
|
+
* typo — the alternative is a background timer that throws on a value the user
|
|
472
|
+
* cannot see it reading.
|
|
473
|
+
*/
|
|
474
|
+
export declare function clampUpdateConfig(raw: unknown): UpdateConfig;
|
|
437
475
|
export interface PersistedConfig {
|
|
438
476
|
version: number;
|
|
439
477
|
provider: string;
|
|
@@ -537,6 +575,8 @@ export interface PersistedConfig {
|
|
|
537
575
|
retry: RetryConfig;
|
|
538
576
|
/** Fast model tier. The SEVENTH nested object; scalars only (see FastConfig). */
|
|
539
577
|
fast: FastConfig;
|
|
578
|
+
/** Auto-update. The EIGHTH nested object; scalars only (see UpdateConfig). */
|
|
579
|
+
update: UpdateConfig;
|
|
540
580
|
}
|
|
541
581
|
export declare const DEFAULT_CONFIG: PersistedConfig;
|
|
542
582
|
/**
|
|
@@ -709,6 +749,22 @@ export interface CliConfig {
|
|
|
709
749
|
* `/provider`, `/reload` and the settings screen.
|
|
710
750
|
*/
|
|
711
751
|
fast: FastConfig;
|
|
752
|
+
/**
|
|
753
|
+
* Resolved auto-update settings (cli-auto-update §4.1).
|
|
754
|
+
*
|
|
755
|
+
* `mode` here decides whether `runInteractive` CONSTRUCTS the service at all
|
|
756
|
+
* (§3.8), which is what makes `'off'` byte-identical to a pre-feature build:
|
|
757
|
+
* no timer, no file read, no socket. The other three keys are read by the
|
|
758
|
+
* service on every check, so a `/update off` mid-session stops the next one.
|
|
759
|
+
*
|
|
760
|
+
* IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason
|
|
761
|
+
* `showThinking` and `liveToolOutput` each record above: the gate reads
|
|
762
|
+
* `cfg.update`, where `cfg` is a `CliConfig`, so a key added to the persisted
|
|
763
|
+
* shape alone would round-trip through `config set`, appear in `config list` —
|
|
764
|
+
* and never reach the gate. `--no-update` and `ARAGON_UPDATE=0` would parse
|
|
765
|
+
* and be discarded, with no compile error and no runtime error.
|
|
766
|
+
*/
|
|
767
|
+
update: UpdateConfig;
|
|
712
768
|
/**
|
|
713
769
|
* Lifetime submit count driving the composer hint fade (`HINT_FADE_AFTER`).
|
|
714
770
|
*
|