@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.
Files changed (156) hide show
  1. package/CHANGELOG.md +1157 -747
  2. package/README.md +1552 -1163
  3. package/dist/agent/coalesce.d.ts +13 -0
  4. package/dist/agent/coalesce.js +50 -11
  5. package/dist/agent/controller.d.ts +141 -2
  6. package/dist/agent/controller.js +396 -4
  7. package/dist/agent/headless.d.ts +8 -0
  8. package/dist/agent/headless.js +40 -0
  9. package/dist/agent/reducer.d.ts +245 -3
  10. package/dist/agent/reducer.js +490 -17
  11. package/dist/agent/retry-view.d.ts +68 -0
  12. package/dist/agent/retry-view.js +95 -0
  13. package/dist/agent/system-prompt.d.ts +11 -0
  14. package/dist/agent/system-prompt.js +2 -0
  15. package/dist/boot/guard.d.ts +135 -0
  16. package/dist/boot/guard.js +218 -0
  17. package/dist/boot/rollback.d.ts +86 -0
  18. package/dist/boot/rollback.js +185 -0
  19. package/dist/cli.js +368 -3
  20. package/dist/commands/builtins.d.ts +1 -1
  21. package/dist/commands/builtins.js +426 -2
  22. package/dist/commands/registry.d.ts +17 -0
  23. package/dist/config/app-paths.d.ts +31 -0
  24. package/dist/config/app-paths.js +37 -0
  25. package/dist/config/cli-commands.d.ts +79 -2
  26. package/dist/config/cli-commands.js +185 -3
  27. package/dist/config/env.d.ts +15 -3
  28. package/dist/config/env.js +143 -3
  29. package/dist/config/load.d.ts +66 -0
  30. package/dist/config/load.js +107 -1
  31. package/dist/config/schema.d.ts +268 -2
  32. package/dist/config/schema.js +288 -1
  33. package/dist/config/store.js +32 -1
  34. package/dist/fast/critique.d.ts +43 -0
  35. package/dist/fast/critique.js +62 -0
  36. package/dist/fast/digest.d.ts +42 -0
  37. package/dist/fast/digest.js +85 -0
  38. package/dist/fast/frames.d.ts +99 -0
  39. package/dist/fast/frames.js +180 -0
  40. package/dist/fast/limits.d.ts +98 -0
  41. package/dist/fast/limits.js +98 -0
  42. package/dist/fast/prompt.d.ts +72 -0
  43. package/dist/fast/prompt.js +94 -0
  44. package/dist/fast/resolve.d.ts +43 -0
  45. package/dist/fast/resolve.js +105 -0
  46. package/dist/fast/review-call.d.ts +99 -0
  47. package/dist/fast/review-call.js +130 -0
  48. package/dist/fast/reviewer.d.ts +248 -0
  49. package/dist/fast/reviewer.js +664 -0
  50. package/dist/fast/types.d.ts +128 -0
  51. package/dist/fast/types.js +14 -0
  52. package/dist/fast/wiring.d.ts +170 -0
  53. package/dist/fast/wiring.js +316 -0
  54. package/dist/launcher.d.ts +14 -0
  55. package/dist/launcher.js +43 -0
  56. package/dist/logging/install.d.ts +15 -1
  57. package/dist/logging/install.js +37 -4
  58. package/dist/logging/logger.d.ts +1 -1
  59. package/dist/session/persist.js +57 -1
  60. package/dist/team/normalize.d.ts +26 -1
  61. package/dist/team/normalize.js +23 -3
  62. package/dist/team/prompt.d.ts +11 -0
  63. package/dist/team/prompt.js +10 -0
  64. package/dist/team/report.d.ts +18 -0
  65. package/dist/team/report.js +27 -1
  66. package/dist/team/runtime.d.ts +13 -2
  67. package/dist/team/runtime.js +33 -6
  68. package/dist/team/subagent.d.ts +19 -1
  69. package/dist/team/subagent.js +72 -10
  70. package/dist/team/task-tool.d.ts +26 -0
  71. package/dist/team/task-tool.js +48 -4
  72. package/dist/team/types.d.ts +80 -1
  73. package/dist/tools/bash-tool.js +19 -3
  74. package/dist/tools/diff.d.ts +25 -5
  75. package/dist/tools/diff.js +72 -41
  76. package/dist/tools/file-change-store.d.ts +34 -0
  77. package/dist/tools/file-change-store.js +57 -0
  78. package/dist/tools/fs-tools.d.ts +26 -0
  79. package/dist/tools/fs-tools.js +62 -5
  80. package/dist/tools/index.d.ts +31 -0
  81. package/dist/tools/index.js +7 -1
  82. package/dist/tools/patch.d.ts +115 -0
  83. package/dist/tools/patch.js +453 -0
  84. package/dist/tools/terminal-output.d.ts +107 -0
  85. package/dist/tools/terminal-output.js +198 -0
  86. package/dist/tools/tool-output-store.d.ts +68 -0
  87. package/dist/tools/tool-output-store.js +102 -0
  88. package/dist/ui/ActivityLine.d.ts +48 -0
  89. package/dist/ui/ActivityLine.js +22 -0
  90. package/dist/ui/App.d.ts +11 -4
  91. package/dist/ui/App.js +359 -37
  92. package/dist/ui/BottomStatusRow.d.ts +65 -0
  93. package/dist/ui/BottomStatusRow.js +17 -0
  94. package/dist/ui/Composer.d.ts +1 -6
  95. package/dist/ui/Composer.js +2 -2
  96. package/dist/ui/PromptInput.d.ts +1 -15
  97. package/dist/ui/PromptInput.js +1 -38
  98. package/dist/ui/StatusBar.d.ts +37 -0
  99. package/dist/ui/StatusBar.js +5 -3
  100. package/dist/ui/TeamPanel.d.ts +5 -4
  101. package/dist/ui/TeamPanel.js +1 -1
  102. package/dist/ui/TodoPanel.d.ts +5 -4
  103. package/dist/ui/Transcript.d.ts +26 -3
  104. package/dist/ui/Transcript.js +76 -10
  105. package/dist/ui/UpdateLine.d.ts +44 -0
  106. package/dist/ui/UpdateLine.js +129 -0
  107. package/dist/ui/activity-phrases.d.ts +33 -0
  108. package/dist/ui/activity-phrases.js +50 -0
  109. package/dist/ui/entries/AssistantEntry.d.ts +20 -0
  110. package/dist/ui/entries/AssistantEntry.js +3 -2
  111. package/dist/ui/entries/DiffView.d.ts +67 -0
  112. package/dist/ui/entries/DiffView.js +103 -0
  113. package/dist/ui/entries/FastCard.d.ts +53 -0
  114. package/dist/ui/entries/FastCard.js +87 -0
  115. package/dist/ui/entries/RetryCard.d.ts +56 -0
  116. package/dist/ui/entries/RetryCard.js +60 -0
  117. package/dist/ui/entries/TeamCard.js +6 -1
  118. package/dist/ui/entries/ToolCard.d.ts +38 -1
  119. package/dist/ui/entries/ToolCard.js +68 -5
  120. package/dist/ui/glyphs.d.ts +9 -0
  121. package/dist/ui/glyphs.js +2 -0
  122. package/dist/ui/layout/AppShell.d.ts +13 -22
  123. package/dist/ui/layout/AppShell.js +3 -38
  124. package/dist/ui/layout/virtual-window.d.ts +1 -1
  125. package/dist/ui/layout/virtual-window.js +109 -5
  126. package/dist/ui/overlays/HelpOverlay.js +16 -2
  127. package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
  128. package/dist/ui/overlays/SettingsScreen.js +147 -1
  129. package/dist/ui/transcript-text.js +27 -0
  130. package/dist/ui/use-wheel-routing.d.ts +16 -32
  131. package/dist/ui/use-wheel-routing.js +21 -38
  132. package/dist/update/classify-failure.d.ts +32 -0
  133. package/dist/update/classify-failure.js +60 -0
  134. package/dist/update/install-lock.d.ts +55 -0
  135. package/dist/update/install-lock.js +184 -0
  136. package/dist/update/install-source.d.ts +97 -0
  137. package/dist/update/install-source.js +218 -0
  138. package/dist/update/installer.d.ts +108 -0
  139. package/dist/update/installer.js +242 -0
  140. package/dist/update/limits.d.ts +111 -0
  141. package/dist/update/limits.js +111 -0
  142. package/dist/update/npm-view.d.ts +62 -0
  143. package/dist/update/npm-view.js +133 -0
  144. package/dist/update/registry.d.ts +52 -0
  145. package/dist/update/registry.js +161 -0
  146. package/dist/update/semver.d.ts +89 -0
  147. package/dist/update/semver.js +295 -0
  148. package/dist/update/service.d.ts +193 -0
  149. package/dist/update/service.js +636 -0
  150. package/dist/update/state.d.ts +103 -0
  151. package/dist/update/state.js +142 -0
  152. package/dist/update/types.d.ts +169 -0
  153. package/dist/update/types.js +68 -0
  154. package/package.json +3 -3
  155. package/dist/ui/layout/regions.d.ts +0 -55
  156. package/dist/ui/layout/regions.js +0 -52
@@ -1,6 +1,6 @@
1
1
  /**
2
- * `aragon config get | list | edit | home`, plus the `log.*` and `team.*`
3
- * branches of `config set`.
2
+ * `aragon config get | list | edit | home`, plus the `log.*`, `team.*`,
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 —
@@ -21,6 +21,39 @@ 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"];
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"];
24
57
  /**
25
58
  * Translate a `log.*` dotted key into a patch, or `null` for anything else.
26
59
  *
@@ -43,6 +76,50 @@ export declare function applyLogConfigSet(key: string, value: string): Partial<P
43
76
  * won't stick". `clampTeamConfig` is the one gate both directions pass through.
44
77
  */
45
78
  export declare function applyTeamConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
79
+ /**
80
+ * Translate a `retry.*` dotted key into a patch, or `null` for anything else.
81
+ *
82
+ * The patch is a PARTIAL `retry` section, safe only because
83
+ * `updatePersistedConfig` deep-merges that section — with a shallow merge,
84
+ * setting the retry count would silently reset `respectRetryAfter` and `jitter`.
85
+ *
86
+ * `maxRetries` GOES THROUGH `clampRetryConfig`, NOT `coercePositiveInt`, and that
87
+ * is not a style choice (R-16): `coercePositiveInt` returns its fallback for
88
+ * `n <= 0`, so `config set retry.maxRetries 0` would write **10** — the maximum
89
+ * instead of the kill switch, silently. The whole section is routed through the
90
+ * one gate so no key here can pick up the wrong coercion later; the caller echoes
91
+ * the STORED value, which is what makes an out-of-range input visible.
92
+ */
93
+ export declare function applyRetryConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
94
+ /**
95
+ * Translate a `fast.*` dotted key into a patch, or `null` for anything else.
96
+ *
97
+ * The patch is a PARTIAL `fast` section, safe only because
98
+ * `updatePersistedConfig` deep-merges that section — with a shallow merge,
99
+ * setting the cadence would silently switch the whole tier off (R-10).
100
+ *
101
+ * EVERY NUMERIC AND ENUM KEY ROUTES THROUGH `clampFastConfig`, not through
102
+ * `coercePositiveInt` or a local parse, for the reason `applyRetryConfigSet`
103
+ * records: one gate for both directions means no key here can pick up a
104
+ * different coercion later, and `config set fast.reviewEveryTurns 400` writes
105
+ * the clamped 50 while the caller echoes the STORED value (AC-13).
106
+ */
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;
46
123
  /**
47
124
  * `log.redactSecrets false` is the one setting that lets credentials reach the
48
125
  * 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.*` and `team.*`
3
- * branches of `config set`.
2
+ * `aragon config get | list | edit | home`, plus the `log.*`, `team.*`,
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 { coercePositiveInt, maskSecret, 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 = [
@@ -43,6 +43,66 @@ 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
+ ];
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
+ ];
46
106
  function isTrue(value) {
47
107
  return value === 'true' || value === '1';
48
108
  }
@@ -112,6 +172,128 @@ export function applyTeamConfigSet(key, value) {
112
172
  return null;
113
173
  }
114
174
  }
175
+ /**
176
+ * Translate a `retry.*` dotted key into a patch, or `null` for anything else.
177
+ *
178
+ * The patch is a PARTIAL `retry` section, safe only because
179
+ * `updatePersistedConfig` deep-merges that section — with a shallow merge,
180
+ * setting the retry count would silently reset `respectRetryAfter` and `jitter`.
181
+ *
182
+ * `maxRetries` GOES THROUGH `clampRetryConfig`, NOT `coercePositiveInt`, and that
183
+ * is not a style choice (R-16): `coercePositiveInt` returns its fallback for
184
+ * `n <= 0`, so `config set retry.maxRetries 0` would write **10** — the maximum
185
+ * instead of the kill switch, silently. The whole section is routed through the
186
+ * one gate so no key here can pick up the wrong coercion later; the caller echoes
187
+ * the STORED value, which is what makes an out-of-range input visible.
188
+ */
189
+ export function applyRetryConfigSet(key, value) {
190
+ const patch = (retry) => ({ retry });
191
+ const clampOne = (field) => {
192
+ const merged = clampRetryConfig({ ...DEFAULT_RETRY_CONFIG, [field]: value });
193
+ return { [field]: merged[field] };
194
+ };
195
+ switch (key) {
196
+ case 'retry.enabled':
197
+ return patch({ enabled: isTrue(value) });
198
+ case 'retry.maxRetries':
199
+ return patch(clampOne('maxRetries'));
200
+ case 'retry.initialDelayMs':
201
+ return patch(clampOne('initialDelayMs'));
202
+ case 'retry.maxDelayMs':
203
+ return patch(clampOne('maxDelayMs'));
204
+ case 'retry.multiplier':
205
+ return patch(clampOne('multiplier'));
206
+ case 'retry.jitter':
207
+ return patch({ jitter: isTrue(value) });
208
+ case 'retry.respectRetryAfter':
209
+ return patch({ respectRetryAfter: isTrue(value) });
210
+ case 'retry.maxElapsedMs':
211
+ return patch(clampOne('maxElapsedMs'));
212
+ case 'retry.onPartialStream':
213
+ return patch({ onPartialStream: isTrue(value) });
214
+ default:
215
+ return null;
216
+ }
217
+ }
218
+ /**
219
+ * Translate a `fast.*` dotted key into a patch, or `null` for anything else.
220
+ *
221
+ * The patch is a PARTIAL `fast` section, safe only because
222
+ * `updatePersistedConfig` deep-merges that section — with a shallow merge,
223
+ * setting the cadence would silently switch the whole tier off (R-10).
224
+ *
225
+ * EVERY NUMERIC AND ENUM KEY ROUTES THROUGH `clampFastConfig`, not through
226
+ * `coercePositiveInt` or a local parse, for the reason `applyRetryConfigSet`
227
+ * records: one gate for both directions means no key here can pick up a
228
+ * different coercion later, and `config set fast.reviewEveryTurns 400` writes
229
+ * the clamped 50 while the caller echoes the STORED value (AC-13).
230
+ */
231
+ export function applyFastConfigSet(key, value) {
232
+ const patch = (fast) => ({ fast });
233
+ const clampOne = (field) => {
234
+ const merged = clampFastConfig({ ...DEFAULT_FAST_CONFIG, [field]: value });
235
+ return { [field]: merged[field] };
236
+ };
237
+ switch (key) {
238
+ case 'fast.enabled':
239
+ return patch({ enabled: isTrue(value) });
240
+ case 'fast.provider':
241
+ return patch(clampOne('provider'));
242
+ case 'fast.model':
243
+ return patch(clampOne('model'));
244
+ case 'fast.baseUrl':
245
+ return patch(clampOne('baseUrl'));
246
+ case 'fast.thinkingLevel':
247
+ return patch(clampOne('thinkingLevel'));
248
+ case 'fast.delegate':
249
+ return patch({ delegate: isTrue(value) });
250
+ case 'fast.review':
251
+ return patch({ review: isTrue(value) });
252
+ case 'fast.reviewEveryTurns':
253
+ return patch(clampOne('reviewEveryTurns'));
254
+ case 'fast.reviewContextTurns':
255
+ return patch(clampOne('reviewContextTurns'));
256
+ case 'fast.reviewMaxChars':
257
+ return patch(clampOne('reviewMaxChars'));
258
+ case 'fast.reviewMaxPerSession':
259
+ return patch(clampOne('reviewMaxPerSession'));
260
+ default:
261
+ return null;
262
+ }
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
+ }
115
297
  /**
116
298
  * `log.redactSecrets false` is the one setting that lets credentials reach the
117
299
  * disk. It is command-line-only (there is no settings-screen entry) and it says
@@ -5,11 +5,23 @@
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, ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR,
11
- * ARAGON_TRANSCRIPT_RETAIN, ARAGON_RENDER_GOVERNOR,
12
- * ARAGON_MAX_RENDER_INTERVAL_MS.
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,
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).
13
25
  *
14
26
  * NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
15
27
  * evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
@@ -5,11 +5,23 @@
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, ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR,
11
- * ARAGON_TRANSCRIPT_RETAIN, ARAGON_RENDER_GOVERNOR,
12
- * ARAGON_MAX_RENDER_INTERVAL_MS.
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,
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).
13
25
  *
14
26
  * NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
15
27
  * evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
@@ -61,6 +73,27 @@ export function readEnvConfig() {
61
73
  if (process.env.ARAGON_THINKING) {
62
74
  partial.thinkingLevel = clampThinkingLevel(process.env.ARAGON_THINKING.trim(), 'off');
63
75
  }
76
+ // Parsed by the POSITIVE list, like ARAGON_FULLSCREEN: anything else is left
77
+ // ABSENT so the config file underneath still wins.
78
+ const showThinking = process.env.ARAGON_SHOW_THINKING?.trim().toLowerCase();
79
+ if (showThinking !== undefined && showThinking.length > 0) {
80
+ partial.showThinking =
81
+ showThinking === '1' ||
82
+ showThinking === 'true' ||
83
+ showThinking === 'on' ||
84
+ showThinking === 'yes';
85
+ }
86
+ // The same positive-list parse as its neighbour, and the same "a non-empty
87
+ // value is an ANSWER" rule -- which is what lets `ARAGON_LIVE_TOOL_OUTPUT=0`
88
+ // turn a default-ON feature off (agent-activity-presentation-live §4.1).
89
+ const liveToolOutput = process.env.ARAGON_LIVE_TOOL_OUTPUT?.trim().toLowerCase();
90
+ if (liveToolOutput !== undefined && liveToolOutput.length > 0) {
91
+ partial.liveToolOutput =
92
+ liveToolOutput === '1' ||
93
+ liveToolOutput === 'true' ||
94
+ liveToolOutput === 'on' ||
95
+ liveToolOutput === 'yes';
96
+ }
64
97
  // `auto` / `0` mean AUTO (`null`); a number is clamped to the accepted range.
65
98
  // Anything else is LEFT ABSENT rather than written as `null`: a typo here used
66
99
  // to become AUTO and silently discard the number in the config file under it,
@@ -179,6 +212,113 @@ export function readEnvConfig() {
179
212
  if (Object.keys(section).length > 0)
180
213
  partial.todo = section;
181
214
  }
215
+ // ARAGON_RETRY=0 is the kill switch for API retry; ARAGON_RETRY_MAX=<n>
216
+ // overrides the count. Parsed by the POSITIVE list, exactly as
217
+ // ARAGON_FULLSCREEN / ARAGON_MOUSE / ARAGON_PLAN / ARAGON_TEAM / ARAGON_TODO
218
+ // are — NOT by `envBool` below, which uses a negative list.
219
+ //
220
+ // TWO VARS ONLY, and no third for the delays: every env var in this file exists
221
+ // because a flag and a config file are BOTH unreachable in the channel it
222
+ // serves, and nobody tunes a backoff multiplier from a container entrypoint.
223
+ //
224
+ // `>= 0`, NOT `> 0`, and the difference is the whole point (R-16).
225
+ // `ARAGON_TEAM_MAX` above guards `parsed > 0`, which is right for a fan-out
226
+ // width and wrong here: `0` is this key's documented floor AND its kill switch,
227
+ // so a `> 0` guard would drop `ARAGON_RETRY_MAX=0` on the floor and leave the
228
+ // user with the default 10. "Left ABSENT when unparseable" still holds and
229
+ // still matters — an unparseable value must not look permanently supplied, or
230
+ // the config file can never win again.
231
+ const retry = process.env.ARAGON_RETRY?.trim().toLowerCase();
232
+ const retryMax = process.env.ARAGON_RETRY_MAX?.trim();
233
+ if ((retry !== undefined && retry.length > 0) || (retryMax !== undefined && retryMax.length > 0)) {
234
+ const section = {};
235
+ if (retry !== undefined && retry.length > 0) {
236
+ section.enabled = retry === '1' || retry === 'true' || retry === 'on' || retry === 'yes';
237
+ }
238
+ if (retryMax !== undefined && retryMax.length > 0) {
239
+ const parsed = Number.parseInt(retryMax, 10);
240
+ if (Number.isFinite(parsed) && parsed >= 0)
241
+ section.maxRetries = parsed;
242
+ }
243
+ // A PARTIAL section, safe only because every consumer deep-merges it — the
244
+ // same contract `partial.skills` / `partial.log` / `partial.team` rely on.
245
+ if (Object.keys(section).length > 0)
246
+ partial.retry = section;
247
+ }
248
+ // ARAGON_FAST=1 turns the fast model tier on; the other three name it
249
+ // (fast-model-tier §4.3). Parsed by the POSITIVE list, exactly as
250
+ // ARAGON_FULLSCREEN / ARAGON_MOUSE / ARAGON_PLAN / ARAGON_TEAM / ARAGON_TODO /
251
+ // ARAGON_RETRY are — NOT by `envBool` below, whose NEGATIVE list disagrees with
252
+ // it on values like `ARAGON_FAST=disable`.
253
+ //
254
+ // FOUR VARS, AND DELIBERATELY NO `ARAGON_FAST_REVIEW_EVERY` / `_MAX_CHARS` /
255
+ // `_DELEGATE` (D-14). The rule this file states is that an environment variable
256
+ // exists only where a flag AND a config file are BOTH unreachable — a
257
+ // container, an ssh session, being spawned by another tool. "Which model, and
258
+ // is it on" passes that test; tuning a review cadence does not, and every key
259
+ // that fails it is documentation debt plus a fifth place for the value to
260
+ // disagree with itself.
261
+ //
262
+ // ONE ACCUMULATED SECTION, ASSIGNED ONCE — the shape the `team`, `todo` and
263
+ // `retry` branches above all use, and NOT four `if` blocks each assigning
264
+ // `partial.fast`. THE CAST IS WHAT WOULD MAKE THAT MISTAKE SILENT: a second
265
+ // assignment compiles clean and drops `enabled`, so
266
+ // `ARAGON_FAST=1 ARAGON_FAST_MODEL=x` would quietly resolve with the tier off
267
+ // (the P1-4 defect this file records for `todo`).
268
+ const fast = process.env.ARAGON_FAST?.trim().toLowerCase();
269
+ const fastProvider = process.env.ARAGON_FAST_PROVIDER?.trim();
270
+ const fastModel = process.env.ARAGON_FAST_MODEL?.trim();
271
+ const fastBaseUrl = process.env.ARAGON_FAST_BASE_URL?.trim();
272
+ if ((fast !== undefined && fast.length > 0) ||
273
+ (fastProvider !== undefined && fastProvider.length > 0) ||
274
+ (fastModel !== undefined && fastModel.length > 0) ||
275
+ (fastBaseUrl !== undefined && fastBaseUrl.length > 0)) {
276
+ const section = {};
277
+ if (fast !== undefined && fast.length > 0) {
278
+ section.enabled = fast === '1' || fast === 'true' || fast === 'on' || fast === 'yes';
279
+ }
280
+ if (fastProvider !== undefined && fastProvider.length > 0)
281
+ section.provider = fastProvider;
282
+ if (fastModel !== undefined && fastModel.length > 0)
283
+ section.model = fastModel;
284
+ if (fastBaseUrl !== undefined && fastBaseUrl.length > 0)
285
+ section.baseUrl = fastBaseUrl;
286
+ // A PARTIAL section, safe only because every consumer deep-merges it — the
287
+ // same contract the four sections above rely on.
288
+ if (Object.keys(section).length > 0)
289
+ partial.fast = section;
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
+ }
182
322
  // Render bounds (tui-render-performance §5.3). Same channel story as
183
323
  // ARAGON_FULLSCREEN: a container or a wrapper that owns argv can reach neither
184
324
  // a flag nor a config file, and a session that freezes is exactly the case
@@ -27,6 +27,13 @@ export interface CliFlags {
27
27
  compact?: boolean;
28
28
  /** `--hints` ⇒ true, `--no-hints` ⇒ false, absent ⇒ undefined. */
29
29
  hints?: boolean;
30
+ /** `--show-thinking` ⇒ true, `--no-show-thinking` ⇒ false, absent ⇒ undefined. */
31
+ showThinking?: boolean;
32
+ /**
33
+ * `--live-tool-output` ⇒ true, `--no-live-tool-output` ⇒ false, absent ⇒ undefined
34
+ * (agent-activity-presentation-live §4.1).
35
+ */
36
+ liveToolOutput?: boolean;
30
37
  /** `--mouse` ⇒ true, `--no-mouse` ⇒ false, absent ⇒ undefined. */
31
38
  mouse?: boolean;
32
39
  /** `--no-skills` ⇒ false. Turns the whole skill subsystem off (§7.5). */
@@ -99,6 +106,65 @@ export interface CliFlags {
99
106
  renderGovernor?: boolean;
100
107
  /** `--max-render-interval <ms>` — the governor ceiling for this run (L4). */
101
108
  maxRenderInterval?: string | number;
109
+ /**
110
+ * `--retry` ⇒ true, `--no-retry` ⇒ false, absent ⇒ undefined
111
+ * (llm-api-retry-backoff §6.2).
112
+ *
113
+ * DECLARED AS A PAIR, for the reason `--todo-panel` and `--render-governor`
114
+ * each record: `retry.enabled` is PERSISTED, so a lone `--no-retry` would make
115
+ * commander default `opts.retry` to `true` and silently overwrite a stored
116
+ * `enabled: false` on every run that passed no flag at all.
117
+ */
118
+ retry?: boolean;
119
+ /**
120
+ * `--retry-max <n>` — override `retry.maxRetries` for this run (still clamped).
121
+ *
122
+ * `0` IS A LEGITIMATE VALUE HERE and is the flag's kill switch, which is why
123
+ * `resolveRetryConfig` tests `!== undefined` rather than truthiness and why the
124
+ * clamp is `clampIntAllowingZero`.
125
+ */
126
+ retryMax?: string | number;
127
+ /**
128
+ * `--fast` ⇒ true, `--no-fast` ⇒ false, absent ⇒ undefined
129
+ * (fast-model-tier §4.3).
130
+ *
131
+ * DECLARED AS A PAIR — MANDATORY (C-9), for the reason `--team`,
132
+ * `--todo-panel`, `--render-governor` and `--retry` each record in turn.
133
+ * `fast.enabled` is PERSISTED and defaults to `false`, so a lone `--fast`
134
+ * would be indistinguishable from silence and the DEFAULT-OFF guarantee (R-d)
135
+ * would depend on a commander detail.
136
+ */
137
+ fast?: boolean;
138
+ /** `--fast-model <id>` — implies nothing about `enabled` on its own. */
139
+ fastModel?: string;
140
+ /** `--fast-provider <id>` — `''` (absent) inherits the main provider. */
141
+ fastProvider?: string;
142
+ /**
143
+ * `--fast-review <n|off>` — `off` sets `fast.review: false`, a number sets
144
+ * `reviewEveryTurns`.
145
+ *
146
+ * ONE FLAG FOR TWO KEYS, and deliberately: "how often" and "at all" are one
147
+ * decision to a user, and a separate `--no-fast-review` would be a fifth pair
148
+ * to keep in step with `toFlags`.
149
+ */
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;
102
168
  }
103
169
  /**
104
170
  * Resolve the effective config by merging all layers, then derive the runtime