@aragon-agent/cli 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/README.md +193 -3
- package/dist/agent/controller.d.ts +51 -1
- package/dist/agent/controller.js +177 -2
- package/dist/agent/headless.d.ts +12 -0
- package/dist/agent/headless.js +41 -1
- package/dist/agent/reducer.d.ts +48 -0
- package/dist/agent/reducer.js +91 -2
- package/dist/agent/system-prompt.d.ts +17 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/cli.js +98 -7
- package/dist/commands/builtins.d.ts +2 -2
- package/dist/commands/builtins.js +133 -3
- package/dist/config/app-paths.d.ts +17 -0
- package/dist/config/app-paths.js +21 -0
- package/dist/config/cli-commands.d.ts +26 -2
- package/dist/config/cli-commands.js +83 -6
- package/dist/config/env.d.ts +3 -2
- package/dist/config/env.js +44 -6
- package/dist/config/history-commands.d.ts +20 -0
- package/dist/config/history-commands.js +71 -0
- package/dist/config/load.d.ts +13 -0
- package/dist/config/load.js +107 -6
- package/dist/config/migrate-state-out-of-config.d.ts +32 -0
- package/dist/config/migrate-state-out-of-config.js +85 -0
- package/dist/config/prompt-history.d.ts +86 -0
- package/dist/config/prompt-history.js +332 -0
- package/dist/config/schema.d.ts +176 -18
- package/dist/config/schema.js +162 -8
- package/dist/config/store.js +40 -4
- package/dist/config/ui-state.d.ts +52 -0
- package/dist/config/ui-state.js +149 -0
- package/dist/logging/install.d.ts +85 -0
- package/dist/logging/install.js +94 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.d.ts +16 -0
- package/dist/session/persist.js +19 -1
- package/dist/team/activity.d.ts +67 -0
- package/dist/team/activity.js +130 -0
- package/dist/team/bus.d.ts +190 -0
- package/dist/team/bus.js +366 -0
- package/dist/team/comm-tools.d.ts +61 -0
- package/dist/team/comm-tools.js +221 -0
- package/dist/team/human-queue.d.ts +55 -0
- package/dist/team/human-queue.js +72 -0
- package/dist/team/limits.d.ts +123 -0
- package/dist/team/limits.js +128 -0
- package/dist/team/normalize.d.ts +39 -0
- package/dist/team/normalize.js +106 -0
- package/dist/team/panel-rows.d.ts +48 -0
- package/dist/team/panel-rows.js +75 -0
- package/dist/team/prompt.d.ts +49 -0
- package/dist/team/prompt.js +87 -0
- package/dist/team/report.d.ts +58 -0
- package/dist/team/report.js +205 -0
- package/dist/team/retry.d.ts +48 -0
- package/dist/team/retry.js +56 -0
- package/dist/team/runtime.d.ts +122 -0
- package/dist/team/runtime.js +406 -0
- package/dist/team/subagent.d.ts +90 -0
- package/dist/team/subagent.js +294 -0
- package/dist/team/task-tool.d.ts +47 -0
- package/dist/team/task-tool.js +118 -0
- package/dist/team/types.d.ts +172 -0
- package/dist/team/types.js +13 -0
- package/dist/tools/index.d.ts +48 -2
- package/dist/tools/index.js +26 -1
- package/dist/ui/App.d.ts +22 -1
- package/dist/ui/App.js +144 -18
- package/dist/ui/StatusBar.d.ts +21 -0
- package/dist/ui/StatusBar.js +5 -2
- package/dist/ui/TeamPanel.d.ts +57 -0
- package/dist/ui/TeamPanel.js +134 -0
- package/dist/ui/Transcript.js +10 -0
- package/dist/ui/entries/TeamCard.d.ts +31 -0
- package/dist/ui/entries/TeamCard.js +39 -0
- package/dist/ui/glyphs.d.ts +10 -0
- package/dist/ui/glyphs.js +6 -0
- package/dist/ui/layout/AppShell.d.ts +11 -1
- package/dist/ui/layout/AppShell.js +3 -3
- package/dist/ui/overlays/HelpOverlay.js +13 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +12 -1
- package/dist/ui/overlays/SettingsScreen.js +38 -1
- package/dist/ui/transcript-text.js +20 -0
- package/dist/ui/use-startup-notices.d.ts +1 -1
- package/dist/ui/use-startup-notices.js +2 -1
- package/package.json +2 -2
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prompts the user submitted, as an append-only JSON Lines file at
|
|
3
|
+
* `<home>/prompt-history.jsonl` (config-state-separation §4.3).
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* WHY THIS IS NOT IN config.json ANY MORE: it used to be a `promptHistory`
|
|
7
|
+
* array inside the config file, which meant every single submit rewrote the
|
|
8
|
+
* whole file — API key section included — and left a user who opened
|
|
9
|
+
* `config.json` to change their model scrolling past their own typing. A
|
|
10
|
+
* config file holds what the user DECLARED; this holds what they DID.
|
|
11
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
*
|
|
13
|
+
* APPEND, never read-modify-write. Two `aragon` windows are an ordinary setup,
|
|
14
|
+
* and `O_APPEND` + one `writeSync` per entry means they interleave instead of
|
|
15
|
+
* overwriting each other's snapshots. It also means a process killed mid-write
|
|
16
|
+
* costs one broken line rather than a truncated array that reads back as "your
|
|
17
|
+
* history is gone" — hence the per-line tolerance in `parseLine`.
|
|
18
|
+
*
|
|
19
|
+
* FAILURE POLICY (C2), the same one `skills/usage.ts` documents: every function
|
|
20
|
+
* here swallows its errors. This is recall convenience; a read-only home
|
|
21
|
+
* directory must degrade to "no history", never to a CLI that will not start.
|
|
22
|
+
* The one thing that policy makes dangerous is a MISSING WRITE — it would be
|
|
23
|
+
* invisible — which is why the write path creates `<home>` first and why a
|
|
24
|
+
* failed append is logged rather than merely ignored.
|
|
25
|
+
*/
|
|
26
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, writeSync, } from 'node:fs';
|
|
27
|
+
import process from 'node:process';
|
|
28
|
+
import { getHomeRoot, getPromptHistoryPath } from './app-paths.js';
|
|
29
|
+
import { PROMPT_HISTORY_CAP } from './schema.js';
|
|
30
|
+
import { getLogger } from '../logging/logger.js';
|
|
31
|
+
export const PROMPT_HISTORY_FILENAME = 'prompt-history.jsonl';
|
|
32
|
+
export const PROMPT_HISTORY_LINE_VERSION = 1;
|
|
33
|
+
/**
|
|
34
|
+
* Longer submissions are not recorded at all (D-6).
|
|
35
|
+
*
|
|
36
|
+
* Storing a TRUNCATED copy would be worse than storing nothing: the user
|
|
37
|
+
* presses `↑`, gets back something that looks complete, and sends half a
|
|
38
|
+
* prompt. 16 KiB is far past anything typed by hand.
|
|
39
|
+
*/
|
|
40
|
+
export const PROMPT_ENTRY_MAX_CHARS = 16_384;
|
|
41
|
+
/** Physical lines tolerated before the file is rewritten down to the cap. */
|
|
42
|
+
export const HISTORY_COMPACT_AT_LINES = 400;
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// In-memory state
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
let memory = null;
|
|
47
|
+
let loadedLines = 0;
|
|
48
|
+
let appendedLines = 0;
|
|
49
|
+
/**
|
|
50
|
+
* Starts `true`, and that initial value is load-bearing (RV-5).
|
|
51
|
+
*
|
|
52
|
+
* `loadConfig()` calls `setHistoryEnabled()`, but the startup migration runs
|
|
53
|
+
* BEFORE any `loadConfig()`. Something has to be true in that window, and
|
|
54
|
+
* `true` is what the config key itself defaults to. The one case in that window
|
|
55
|
+
* that must honour an explicit opt-out — importing a legacy `promptHistory` —
|
|
56
|
+
* reads the value straight off the config file instead of trusting this flag
|
|
57
|
+
* (see `migrate-state-out-of-config.ts`).
|
|
58
|
+
*/
|
|
59
|
+
let historyEnabled = true;
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Parsing
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
function parseLine(line) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(line);
|
|
66
|
+
if (!parsed || typeof parsed !== 'object')
|
|
67
|
+
return null;
|
|
68
|
+
// Unknown versions are skipped rather than coerced: that is what lets a
|
|
69
|
+
// future field be added without an older CLI reading the file as garbage.
|
|
70
|
+
if (parsed.v !== PROMPT_HISTORY_LINE_VERSION)
|
|
71
|
+
return null;
|
|
72
|
+
if (typeof parsed.text !== 'string' || parsed.text.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
const ts = typeof parsed.ts === 'number' && Number.isFinite(parsed.ts) ? parsed.ts : 0;
|
|
75
|
+
return { v: PROMPT_HISTORY_LINE_VERSION, ts, text: parsed.text };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Read and parse the file. `null` means the read itself failed — distinct from
|
|
83
|
+
* "no file yet", because compaction must not rewrite a file it could not read.
|
|
84
|
+
*/
|
|
85
|
+
function readFromDisk() {
|
|
86
|
+
const path = getPromptHistoryPath();
|
|
87
|
+
if (!existsSync(path))
|
|
88
|
+
return { entries: [], lines: 0 };
|
|
89
|
+
let raw;
|
|
90
|
+
try {
|
|
91
|
+
raw = readFileSync(path, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const entries = [];
|
|
97
|
+
let lines = 0;
|
|
98
|
+
for (const line of raw.split('\n')) {
|
|
99
|
+
if (line.trim().length === 0)
|
|
100
|
+
continue;
|
|
101
|
+
lines += 1;
|
|
102
|
+
const entry = parseLine(line);
|
|
103
|
+
if (entry)
|
|
104
|
+
entries.push(entry);
|
|
105
|
+
}
|
|
106
|
+
return { entries, lines };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Drop repeats, keeping the LAST occurrence, then keep the newest `cap`.
|
|
110
|
+
*
|
|
111
|
+
* Oldest-first ordering is the contract `PromptInput` walks with `↑`/`↓`, and
|
|
112
|
+
* "last occurrence wins" reproduces what the old in-config implementation did
|
|
113
|
+
* with `filter(p => p !== text)` followed by an append.
|
|
114
|
+
*/
|
|
115
|
+
function dedupeEntries(entries) {
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const reversed = [];
|
|
118
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
119
|
+
const entry = entries[i];
|
|
120
|
+
if (seen.has(entry.text))
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(entry.text);
|
|
123
|
+
reversed.push(entry);
|
|
124
|
+
}
|
|
125
|
+
reversed.reverse();
|
|
126
|
+
return reversed.slice(-PROMPT_HISTORY_CAP);
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Read
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* The recall list: oldest first, de-duplicated, capped. Cached for the life of
|
|
133
|
+
* the process so `/reload` — which calls `loadConfig()` again — cannot discard
|
|
134
|
+
* entries added during this session.
|
|
135
|
+
*
|
|
136
|
+
* Reads even when `historyEnabled` is false (RV-12): turning recording off
|
|
137
|
+
* stops new writes, but someone who wants to see what is already stored before
|
|
138
|
+
* deleting it must not be told "nothing here" while the file still exists.
|
|
139
|
+
*/
|
|
140
|
+
export function loadPromptHistory() {
|
|
141
|
+
if (memory !== null)
|
|
142
|
+
return memory;
|
|
143
|
+
const read = readFromDisk() ?? { entries: [], lines: 0 };
|
|
144
|
+
memory = dedupeEntries(read.entries).map((entry) => entry.text);
|
|
145
|
+
loadedLines = read.lines;
|
|
146
|
+
appendedLines = 0;
|
|
147
|
+
return memory;
|
|
148
|
+
}
|
|
149
|
+
/** Every entry on disk with its timestamp, newest first. For `history list`. */
|
|
150
|
+
export function listPromptHistory() {
|
|
151
|
+
const read = readFromDisk();
|
|
152
|
+
if (!read)
|
|
153
|
+
return [];
|
|
154
|
+
return [...read.entries].reverse();
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Write
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
/**
|
|
160
|
+
* Append one line. Returns whether it reached the disk.
|
|
161
|
+
*
|
|
162
|
+
* `mkdirSync` IS NOT DEFENSIVE NOISE — deleting it breaks a real user. `<home>`
|
|
163
|
+
* is created by the logger (only when `log.toFile` is on) or by the first
|
|
164
|
+
* config write (only when the user changes a setting); someone on a fresh
|
|
165
|
+
* install who did neither has no directory here, `openSync` throws `ENOENT`,
|
|
166
|
+
* the swallow below hides it, and their history silently never persists.
|
|
167
|
+
*
|
|
168
|
+
* A short-lived descriptor rather than a kept-open one: this is a once-per-
|
|
169
|
+
* submit write, and a descriptor held for the life of the process would make
|
|
170
|
+
* `aragon history clear` unable to delete the file on Windows.
|
|
171
|
+
*/
|
|
172
|
+
function appendLine(entry) {
|
|
173
|
+
try {
|
|
174
|
+
mkdirSync(getHomeRoot(), { recursive: true });
|
|
175
|
+
const fd = openSync(getPromptHistoryPath(), 'a', 0o600);
|
|
176
|
+
try {
|
|
177
|
+
writeSync(fd, `${JSON.stringify(entry)}\n`);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
closeSync(fd);
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
getLogger().warn('history', 'history_append_failed', {
|
|
186
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
187
|
+
});
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Record one submitted prompt and return THE UPDATED LIST.
|
|
193
|
+
*
|
|
194
|
+
* The return value is the whole point of the signature (RV-1). `App.tsx` writes
|
|
195
|
+
* `setPromptHistory(appendPrompt(text))`: the recall list rendered by the
|
|
196
|
+
* composer is React state, and a module-level array updated in here re-renders
|
|
197
|
+
* nothing. Drop the return value and `↑` stops recalling what was just
|
|
198
|
+
* submitted — but only until the next restart, which makes it about as hard to
|
|
199
|
+
* diagnose as a bug in this area gets.
|
|
200
|
+
*
|
|
201
|
+
* Returns the current list unchanged when recording is off, the text is blank,
|
|
202
|
+
* or the text is oversized, so callers never need a branch.
|
|
203
|
+
*/
|
|
204
|
+
export function appendPrompt(text, now = Date.now()) {
|
|
205
|
+
const current = loadPromptHistory();
|
|
206
|
+
if (!historyEnabled)
|
|
207
|
+
return current;
|
|
208
|
+
if (text.trim().length === 0)
|
|
209
|
+
return current;
|
|
210
|
+
if (text.length > PROMPT_ENTRY_MAX_CHARS) {
|
|
211
|
+
getLogger().debug('history', 'prompt_history_entry_too_large', {
|
|
212
|
+
chars: text.length,
|
|
213
|
+
max: PROMPT_ENTRY_MAX_CHARS,
|
|
214
|
+
});
|
|
215
|
+
return current;
|
|
216
|
+
}
|
|
217
|
+
memory = [...current.filter((prompt) => prompt !== text), text].slice(-PROMPT_HISTORY_CAP);
|
|
218
|
+
if (appendLine({ v: PROMPT_HISTORY_LINE_VERSION, ts: now, text })) {
|
|
219
|
+
appendedLines += 1;
|
|
220
|
+
if (loadedLines + appendedLines > HISTORY_COMPACT_AT_LINES)
|
|
221
|
+
compact();
|
|
222
|
+
}
|
|
223
|
+
return memory;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Rewrite the file down to the cap.
|
|
227
|
+
*
|
|
228
|
+
* THE RE-READ ON THE FIRST LINE IS THE WHOLE POINT (RV-3). Writing this
|
|
229
|
+
* process's in-memory array over the file would reintroduce exactly the
|
|
230
|
+
* multi-instance clobbering that append-only exists to avoid — and not "a few
|
|
231
|
+
* trailing entries" either, but everything every other instance wrote since
|
|
232
|
+
* this one last read. A failed read therefore skips compaction entirely: the
|
|
233
|
+
* file keeps growing and the next append tries again, which is strictly better
|
|
234
|
+
* than replacing it with a stale snapshot.
|
|
235
|
+
*/
|
|
236
|
+
function compact() {
|
|
237
|
+
const disk = readFromDisk();
|
|
238
|
+
if (!disk)
|
|
239
|
+
return;
|
|
240
|
+
// Entries this session has but the file does not — normally none, since the
|
|
241
|
+
// appends above are already on disk. It only matters if a read raced a write.
|
|
242
|
+
const onDisk = new Set(disk.entries.map((entry) => entry.text));
|
|
243
|
+
const carried = (memory ?? [])
|
|
244
|
+
.filter((text) => !onDisk.has(text))
|
|
245
|
+
.map((text) => ({ v: PROMPT_HISTORY_LINE_VERSION, ts: Date.now(), text }));
|
|
246
|
+
const kept = dedupeEntries([...disk.entries, ...carried]);
|
|
247
|
+
const path = getPromptHistoryPath();
|
|
248
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
249
|
+
try {
|
|
250
|
+
mkdirSync(getHomeRoot(), { recursive: true });
|
|
251
|
+
const payload = kept.map((entry) => `${JSON.stringify(entry)}\n`).join('');
|
|
252
|
+
writeFileSync(tmp, payload, { encoding: 'utf-8', mode: 0o600 });
|
|
253
|
+
renameSync(tmp, path);
|
|
254
|
+
// rename does not preserve mode everywhere, and this file holds the user's
|
|
255
|
+
// prompts verbatim — re-apply it rather than hope (RV-6, as `store.ts` does).
|
|
256
|
+
if (process.platform !== 'win32')
|
|
257
|
+
chmodSync(path, 0o600);
|
|
258
|
+
memory = kept.map((entry) => entry.text);
|
|
259
|
+
loadedLines = kept.length;
|
|
260
|
+
appendedLines = 0;
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
try {
|
|
264
|
+
rmSync(tmp, { force: true });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Nothing further to try; the original file is untouched either way.
|
|
268
|
+
}
|
|
269
|
+
getLogger().warn('history', 'history_compact_failed', {
|
|
270
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Migration-only: append the texts that are not in the file yet, returning how
|
|
276
|
+
* many were written.
|
|
277
|
+
*
|
|
278
|
+
* Ignores `historyEnabled` on purpose — the caller decides, because it is the
|
|
279
|
+
* only code that can see an explicit `historyEnabled: false` in the config file
|
|
280
|
+
* before `loadConfig()` has run (§4.6 step 3).
|
|
281
|
+
*/
|
|
282
|
+
export function importLegacyPromptHistory(entries) {
|
|
283
|
+
const read = readFromDisk();
|
|
284
|
+
if (!read)
|
|
285
|
+
return 0;
|
|
286
|
+
const known = new Set(read.entries.map((entry) => entry.text));
|
|
287
|
+
let written = 0;
|
|
288
|
+
for (const text of entries) {
|
|
289
|
+
if (typeof text !== 'string')
|
|
290
|
+
continue;
|
|
291
|
+
if (text.trim().length === 0)
|
|
292
|
+
continue;
|
|
293
|
+
if (text.length > PROMPT_ENTRY_MAX_CHARS)
|
|
294
|
+
continue;
|
|
295
|
+
if (known.has(text))
|
|
296
|
+
continue;
|
|
297
|
+
if (!appendLine({ v: PROMPT_HISTORY_LINE_VERSION, ts: Date.now(), text }))
|
|
298
|
+
break;
|
|
299
|
+
known.add(text);
|
|
300
|
+
written += 1;
|
|
301
|
+
}
|
|
302
|
+
// The cache, if anything already built one, no longer matches the file.
|
|
303
|
+
if (written > 0)
|
|
304
|
+
memory = null;
|
|
305
|
+
return written;
|
|
306
|
+
}
|
|
307
|
+
/** Delete the file and empty the cache. Returns how many entries were removed. */
|
|
308
|
+
export function clearPromptHistory() {
|
|
309
|
+
const removed = readFromDisk()?.entries.length ?? 0;
|
|
310
|
+
try {
|
|
311
|
+
rmSync(getPromptHistoryPath(), { force: true });
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// Same policy as every other failure here: a file we could not delete is
|
|
315
|
+
// not worth an error the user can do nothing about.
|
|
316
|
+
}
|
|
317
|
+
memory = [];
|
|
318
|
+
loadedLines = 0;
|
|
319
|
+
appendedLines = 0;
|
|
320
|
+
return removed;
|
|
321
|
+
}
|
|
322
|
+
/** Called once by `loadConfig()` after `historyEnabled` has been resolved. */
|
|
323
|
+
export function setHistoryEnabled(enabled) {
|
|
324
|
+
historyEnabled = enabled;
|
|
325
|
+
}
|
|
326
|
+
/** Test-only: forget the cache, the line counters and the switch. */
|
|
327
|
+
export function resetPromptHistoryForTests() {
|
|
328
|
+
memory = null;
|
|
329
|
+
loadedLines = 0;
|
|
330
|
+
appendedLines = 0;
|
|
331
|
+
historyEnabled = true;
|
|
332
|
+
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* See spec §3.5 (resolution pipeline), §6.1 (config file), and §3.8 (timeout
|
|
6
6
|
* invariant).
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type ThinkingLevel } from '@aragon-agent/core';
|
|
9
9
|
import { type LogLevelName } from '../logging/levels.js';
|
|
10
10
|
/**
|
|
11
11
|
* `ModelRegistry` seeds builtin lists for five providers, but `initProviders()`
|
|
@@ -55,9 +55,13 @@ export declare const DEFAULT_TOOL_TIMEOUT_MS = 180000;
|
|
|
55
55
|
export declare const IDLE_TIMEOUT_MARGIN_MS = 30000;
|
|
56
56
|
/** Watchdog idle timeout (§3.8) — must stay ≥ toolTimeout so long tools survive. */
|
|
57
57
|
export declare const DEFAULT_IDLE_TIMEOUT_MS: number;
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Cap on the recall list.
|
|
60
|
+
*
|
|
61
|
+
* The list itself now lives in `<home>/prompt-history.jsonl`; this constant is
|
|
62
|
+
* consumed by `config/prompt-history.ts`, not by the UI.
|
|
63
|
+
*/
|
|
59
64
|
export declare const PROMPT_HISTORY_CAP = 100;
|
|
60
|
-
export declare const RECENT_MODELS_CAP = 10;
|
|
61
65
|
/**
|
|
62
66
|
* How many trailing entries the full-screen viewport renders (invariant I-3).
|
|
63
67
|
* Yoga lays out every child even when `overflow: hidden` clips it, so the cost
|
|
@@ -190,12 +194,69 @@ export declare const DEFAULT_LOG_CONFIG: LogConfig;
|
|
|
190
194
|
* but none of it".
|
|
191
195
|
*/
|
|
192
196
|
export declare function clampLogConfig(raw: unknown): LogConfig;
|
|
197
|
+
export interface TeamConfig {
|
|
198
|
+
/** Register the `task` tool at all. Defaults to TRUE (R-b). */
|
|
199
|
+
enabled: boolean;
|
|
200
|
+
/** Children per dispatch. Clamped to [1, HARD_MAX_SUBAGENTS] (R-g). */
|
|
201
|
+
maxSubagents: number;
|
|
202
|
+
/**
|
|
203
|
+
* Children in flight at once.
|
|
204
|
+
*
|
|
205
|
+
* A SEPARATE KNOB FROM `maxSubagents` on purpose (D-6): provider rate limits
|
|
206
|
+
* are a different constraint from context economics, and folding them into one
|
|
207
|
+
* number makes both wrong. Five simultaneous streams against one API key is a
|
|
208
|
+
* reliable way to collect HTTP 429s, each of which lands in the report as a
|
|
209
|
+
* failed subagent.
|
|
210
|
+
*/
|
|
211
|
+
maxConcurrent: number;
|
|
212
|
+
/** One child's wall clock before its own abort. */
|
|
213
|
+
subagentTimeoutMs: number;
|
|
214
|
+
/** The whole dispatch's wall clock. Also the `task` tool-timeout override. */
|
|
215
|
+
dispatchTimeoutMs: number;
|
|
216
|
+
/** Runaway-loop cap: turns one child may take before it is stopped. */
|
|
217
|
+
maxTurnsPerSubagent: number;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The requirement's ceiling on the fan-out (R-g).
|
|
221
|
+
*
|
|
222
|
+
* Re-exported from `TEAM_LIMITS` rather than re-spelled, because it is enforced
|
|
223
|
+
* in TWO places — here on the config value, and again in
|
|
224
|
+
* `normalizeSubagentSpecs` on what the model asked for — and two copies of the
|
|
225
|
+
* same 10 is exactly how a hand-edited `config.json` ends up being able to raise
|
|
226
|
+
* a ceiling the requirement fixed.
|
|
227
|
+
*/
|
|
228
|
+
export declare const HARD_MAX_SUBAGENTS: 10;
|
|
229
|
+
export declare const DEFAULT_TEAM_CONFIG: TeamConfig;
|
|
230
|
+
/**
|
|
231
|
+
* THE single gate for every team-config read AND write, mirroring
|
|
232
|
+
* `clampSkillsConfig` / `clampLogConfig` for the reason those two record:
|
|
233
|
+
* hardening only the read path leaves a bad value on disk that reverts to the
|
|
234
|
+
* default on every launch, which presents to the user as "my setting won't
|
|
235
|
+
* stick".
|
|
236
|
+
*
|
|
237
|
+
* `maxConcurrent` is additionally capped at `maxSubagents`: a pool wider than
|
|
238
|
+
* the population it draws from is not wrong so much as meaningless, and letting
|
|
239
|
+
* the two disagree would make the panel's `n/m` readout confusing for no gain.
|
|
240
|
+
*/
|
|
241
|
+
export declare function clampTeamConfig(raw: unknown): TeamConfig;
|
|
193
242
|
export interface PersistedConfig {
|
|
194
243
|
version: number;
|
|
195
244
|
provider: string;
|
|
196
245
|
model: string;
|
|
197
246
|
baseUrl: string | null;
|
|
198
247
|
thinkingLevel: ThinkingLevel;
|
|
248
|
+
/**
|
|
249
|
+
* Output token cap.
|
|
250
|
+
*
|
|
251
|
+
* a number — an explicit cap, clamped to `[256, 200000]` on the way in AND
|
|
252
|
+
* on the way out, then clamped again at request time down to the
|
|
253
|
+
* target model's real ceiling when that ceiling is lower.
|
|
254
|
+
* `null` — AUTO: use each model's own ceiling, never above 64000.
|
|
255
|
+
* absent — the product default, 64000.
|
|
256
|
+
*
|
|
257
|
+
* `null` MEANS AUTO. It used to resolve to 64000 by accident (`pick()` skips
|
|
258
|
+
* `null`), which left the user no way to express "let the model decide".
|
|
259
|
+
*/
|
|
199
260
|
maxTokens: number | null;
|
|
200
261
|
theme: ThemeName;
|
|
201
262
|
/** Replace spinners with a static glyph for calmer, low-motion output. */
|
|
@@ -211,8 +272,15 @@ export interface PersistedConfig {
|
|
|
211
272
|
idleTimeoutMs: number;
|
|
212
273
|
/** provider -> key. Masked in the UI; the file is written 0600 on POSIX. */
|
|
213
274
|
apiKeys: Record<string, string | null>;
|
|
214
|
-
|
|
215
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Record submitted prompts to `<home>/prompt-history.jsonl` at all (D-9).
|
|
277
|
+
*
|
|
278
|
+
* A real preference, and the reason it belongs here rather than in
|
|
279
|
+
* `state.json`: the user declares it, and switching it off is a decision they
|
|
280
|
+
* expect to survive. Command-line only, like `log.redactSecrets` — there is
|
|
281
|
+
* no settings-screen entry, because it is set once and then forgotten.
|
|
282
|
+
*/
|
|
283
|
+
historyEnabled: boolean;
|
|
216
284
|
/** Entry spacing: `comfortable` keeps a blank line at turn boundaries (§4.3). */
|
|
217
285
|
density: DensityMode;
|
|
218
286
|
/** Show the composer hint row at all. */
|
|
@@ -225,15 +293,6 @@ export interface PersistedConfig {
|
|
|
225
293
|
* heuristics for an explicit `true` to override (D-10).
|
|
226
294
|
*/
|
|
227
295
|
mouse: boolean;
|
|
228
|
-
/**
|
|
229
|
-
* Whether the one-time "hold Shift to select text" notice has been shown
|
|
230
|
-
* (§6.1). Bookkeeping for a one-shot notice, NOT a preference — hence no
|
|
231
|
-
* flag, no env var and no `config set` key. Giving it a user-facing channel
|
|
232
|
-
* would invite it to be treated as one.
|
|
233
|
-
*/
|
|
234
|
-
mouseNoticeSeen: boolean;
|
|
235
|
-
/** Lifetime submit count driving the progressive-disclosure fade (§4.6). */
|
|
236
|
-
submitCount: number;
|
|
237
296
|
/** Start every session in PLAN mode (plan-mode §4.4). */
|
|
238
297
|
planModeDefault: boolean;
|
|
239
298
|
/** `ask_user` rounds allowed per user turn; clamped to [1, 10]. */
|
|
@@ -244,13 +303,40 @@ export interface PersistedConfig {
|
|
|
244
303
|
skills: SkillsConfig;
|
|
245
304
|
/** Logging settings. The THIRD nested object; scalars only (see LogConfig). */
|
|
246
305
|
log: LogConfig;
|
|
306
|
+
/** Team mode. The FOURTH nested object; scalars only (see TeamConfig). */
|
|
307
|
+
team: TeamConfig;
|
|
247
308
|
}
|
|
248
309
|
export declare const DEFAULT_CONFIG: PersistedConfig;
|
|
310
|
+
/**
|
|
311
|
+
* Runtime state that used to be stored in `config.json` by mistake
|
|
312
|
+
* (config-state-separation §4.5). Each one now lives in its own file:
|
|
313
|
+
* `promptHistory` → `prompt-history.jsonl`, `submitCount` / `mouseNoticeSeen`
|
|
314
|
+
* → `state.json`, and `recentModels` was dead — nothing ever wrote it.
|
|
315
|
+
*/
|
|
316
|
+
export declare const LEGACY_STATE_KEYS: readonly ["promptHistory", "submitCount", "mouseNoticeSeen", "recentModels"];
|
|
317
|
+
/**
|
|
318
|
+
* THE single gate that keeps those keys out of `config.json`, and the one thing
|
|
319
|
+
* in this change that cannot be skipped.
|
|
320
|
+
*
|
|
321
|
+
* `loadPersistedConfig()` merges `{ ...DEFAULT_CONFIG, ...partial }` where
|
|
322
|
+
* `partial` is the raw parse of whatever is on disk. Removing the fields from
|
|
323
|
+
* the interface therefore removes NOTHING from the file: the keys ride through
|
|
324
|
+
* the spread and `updatePersistedConfig` writes them straight back. The user
|
|
325
|
+
* deletes `promptHistory` by hand and watches it grow back.
|
|
326
|
+
*
|
|
327
|
+
* Applied on the READ side on purpose, which is what makes it self-healing:
|
|
328
|
+
* every write goes read-strip-merge-write, so a hand-edited file, a downgrade
|
|
329
|
+
* to 0.5.x and back, or a half-finished migration all converge on a clean file
|
|
330
|
+
* at the next write. Same shape as `clampSkillsConfig` / `clampLogConfig` —
|
|
331
|
+
* one door for both directions.
|
|
332
|
+
*/
|
|
333
|
+
export declare function stripLegacyStateKeys<T extends object>(raw: T): T;
|
|
249
334
|
export interface CliConfig {
|
|
250
335
|
provider: string;
|
|
251
336
|
model: string;
|
|
252
337
|
baseUrl?: string;
|
|
253
338
|
thinkingLevel: ThinkingLevel;
|
|
339
|
+
/** The resolved cap, or `undefined` for AUTO (per-model ceiling, ≤ 64000). */
|
|
254
340
|
maxTokens?: number;
|
|
255
341
|
theme: ThemeName;
|
|
256
342
|
reducedMotion: boolean;
|
|
@@ -274,8 +360,15 @@ export interface CliConfig {
|
|
|
274
360
|
idleTimeoutMs: number;
|
|
275
361
|
/** provider -> resolved key (config-file key ?? provider env var). */
|
|
276
362
|
apiKeys: Record<string, string | undefined>;
|
|
277
|
-
|
|
278
|
-
|
|
363
|
+
/**
|
|
364
|
+
* Whether submitted prompts are recorded (D-9).
|
|
365
|
+
*
|
|
366
|
+
* The recall LIST is deliberately not here: its only consumer is one
|
|
367
|
+
* `useState` seed in `App.tsx`, and carrying it on `CliConfig` would make
|
|
368
|
+
* every `loadConfig()` — `aragon logs tail` and `aragon skills list`
|
|
369
|
+
* included — read a file it has no use for (D-4).
|
|
370
|
+
*/
|
|
371
|
+
historyEnabled: boolean;
|
|
279
372
|
density: DensityMode;
|
|
280
373
|
hints: boolean;
|
|
281
374
|
/**
|
|
@@ -283,7 +376,6 @@ export interface CliConfig {
|
|
|
283
376
|
* only). Resolved by `resolveMouse` in `load.ts`.
|
|
284
377
|
*/
|
|
285
378
|
mouse: boolean;
|
|
286
|
-
submitCount: number;
|
|
287
379
|
/**
|
|
288
380
|
* Whether this session opens in PLAN mode.
|
|
289
381
|
*
|
|
@@ -299,6 +391,23 @@ export interface CliConfig {
|
|
|
299
391
|
skillsRuntime: SkillsRuntimeOptions;
|
|
300
392
|
/** Resolved logging settings; the authoritative input to `Logger.reconfigure`. */
|
|
301
393
|
log: LogConfig;
|
|
394
|
+
/**
|
|
395
|
+
* Resolved team-mode settings (team-subagents §3.10).
|
|
396
|
+
*
|
|
397
|
+
* `enabled` here decides whether `task` is REGISTERED, which happens once at
|
|
398
|
+
* construction and can never be undone (§4.5 / D-17). The live on/off switch
|
|
399
|
+
* `/team` flips is a separate flag on the controller.
|
|
400
|
+
*/
|
|
401
|
+
team: TeamConfig;
|
|
402
|
+
/**
|
|
403
|
+
* Lifetime submit count driving the composer hint fade (`HINT_FADE_AFTER`).
|
|
404
|
+
*
|
|
405
|
+
* Read from `<home>/state.json` rather than the config file. It stays on
|
|
406
|
+
* `CliConfig` — unlike the recall list — because it is one scalar that is
|
|
407
|
+
* already plumbed through `controller.getConfig()` / `setSubmitCount()` and
|
|
408
|
+
* read during render (D-4).
|
|
409
|
+
*/
|
|
410
|
+
submitCount: number;
|
|
302
411
|
/** Working directory for tools (default `process.cwd()`, overridable). */
|
|
303
412
|
cwd: string;
|
|
304
413
|
/** Whether ANSI color is enabled. */
|
|
@@ -310,8 +419,57 @@ export interface CliConfig {
|
|
|
310
419
|
/** One-shot `--api-key` override; applies to the active provider only. */
|
|
311
420
|
apiKeyOverride?: string;
|
|
312
421
|
}
|
|
313
|
-
/**
|
|
422
|
+
/**
|
|
423
|
+
* Coerce an arbitrary value into a positive integer, or `undefined`.
|
|
424
|
+
*
|
|
425
|
+
* Kept because callers outside this feature still use it, and deliberately
|
|
426
|
+
* WITHOUT the range clamp so its behaviour for in-range values is byte-identical
|
|
427
|
+
* to what it always was. New code wants `clampMaxTokens`.
|
|
428
|
+
*/
|
|
314
429
|
export declare function coerceMaxTokens(v: unknown): number | undefined;
|
|
430
|
+
/**
|
|
431
|
+
* The accepted range for an explicit output cap.
|
|
432
|
+
*
|
|
433
|
+
* The floor is the engine's: below it a turn cannot produce a usable tool call.
|
|
434
|
+
* The ceiling is a sanity bound on a hand-edited file, not a model limit — the
|
|
435
|
+
* real per-model ceiling is applied later, by `resolveOutputTokens`.
|
|
436
|
+
*/
|
|
437
|
+
export declare const MAX_TOKENS_RANGE: {
|
|
438
|
+
min: number;
|
|
439
|
+
max: number;
|
|
440
|
+
};
|
|
441
|
+
/** `auto`, an empty string and `0` all mean AUTO — "let the model decide". */
|
|
442
|
+
export declare function isAutoToken(v: unknown): boolean;
|
|
443
|
+
/**
|
|
444
|
+
* THE single gate for every `maxTokens` read AND write, in the shape
|
|
445
|
+
* `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` already use.
|
|
446
|
+
*
|
|
447
|
+
* Both directions on purpose: hardening only the read path leaves a bad value on
|
|
448
|
+
* disk that reverts to a default on every launch, which presents to the user as
|
|
449
|
+
* "my setting won't stick".
|
|
450
|
+
*
|
|
451
|
+
* Returns `undefined` for AUTO and for anything unusable — the caller decides
|
|
452
|
+
* which of the two it was, because they mean different things.
|
|
453
|
+
*/
|
|
454
|
+
export declare function clampMaxTokens(v: unknown): number | undefined;
|
|
455
|
+
/**
|
|
456
|
+
* Parse text typed into the settings screen, a slash command, or `config set`.
|
|
457
|
+
*
|
|
458
|
+
* Distinguishes AUTO from "the user typed nonsense", which `clampMaxTokens`
|
|
459
|
+
* cannot: both come back as `undefined` there, and treating a typo as AUTO would
|
|
460
|
+
* silently discard the number the user meant to set.
|
|
461
|
+
*/
|
|
462
|
+
export declare function parseMaxTokensInput(raw: string): {
|
|
463
|
+
kind: 'auto';
|
|
464
|
+
} | {
|
|
465
|
+
kind: 'value';
|
|
466
|
+
value: number;
|
|
467
|
+
clamped: boolean;
|
|
468
|
+
} | {
|
|
469
|
+
kind: 'invalid';
|
|
470
|
+
};
|
|
471
|
+
/** The product default, re-exported so the CLI never spells the number itself. */
|
|
472
|
+
export declare const DEFAULT_MAX_TOKENS = 64000;
|
|
315
473
|
/** Coerce a value into a positive integer with a fallback (for timeouts). */
|
|
316
474
|
export declare function coercePositiveInt(v: unknown, fallback: number): number;
|
|
317
475
|
/**
|