@aragon-agent/cli 0.5.1 → 0.5.3
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 +117 -0
- package/README.md +155 -5
- 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 +131 -6
- package/dist/commands/builtins.js +82 -0
- 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 +63 -4
- package/dist/config/env.d.ts +2 -2
- package/dist/config/env.js +36 -2
- package/dist/config/history-commands.d.ts +20 -0
- package/dist/config/history-commands.js +71 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.js +88 -5
- 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 +125 -9
- package/dist/config/schema.js +90 -5
- package/dist/config/store.js +13 -4
- package/dist/config/ui-state.d.ts +52 -0
- package/dist/config/ui-state.js +149 -0
- package/dist/input/mouse-events.d.ts +61 -0
- package/dist/input/mouse-events.js +153 -0
- package/dist/input/stdin-mouse-filter.d.ts +42 -0
- package/dist/input/stdin-mouse-filter.js +153 -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 +9 -1
- package/dist/ui/App.js +140 -21
- package/dist/ui/Composer.d.ts +6 -1
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +15 -1
- package/dist/ui/PromptInput.js +38 -1
- 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 +22 -0
- package/dist/ui/glyphs.js +10 -0
- package/dist/ui/layout/AppShell.d.ts +20 -1
- package/dist/ui/layout/AppShell.js +39 -4
- package/dist/ui/layout/ScrollIndicator.d.ts +31 -0
- package/dist/ui/layout/ScrollIndicator.js +15 -0
- package/dist/ui/layout/ScrollViewport.d.ts +8 -2
- package/dist/ui/layout/ScrollViewport.js +24 -4
- package/dist/ui/layout/regions.d.ts +55 -0
- package/dist/ui/layout/regions.js +52 -0
- package/dist/ui/layout/scroll-indicator.d.ts +27 -0
- package/dist/ui/layout/scroll-indicator.js +35 -0
- package/dist/ui/layout/scroll.d.ts +9 -0
- package/dist/ui/layout/scroll.js +23 -0
- package/dist/ui/overlays/HelpOverlay.js +14 -0
- package/dist/ui/screen.d.ts +14 -1
- package/dist/ui/screen.js +37 -3
- package/dist/ui/transcript-text.js +20 -0
- package/dist/ui/use-startup-notices.d.ts +11 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.d.ts +68 -0
- package/dist/ui/use-wheel-routing.js +139 -0
- package/package.json +2 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aragon history path | list | clear` — the prompt history's operations
|
|
3
|
+
* surface (config-state-separation §5.5).
|
|
4
|
+
*
|
|
5
|
+
* Exit codes follow the existing convention: 0 success · 1 run-time failure ·
|
|
6
|
+
* 2 usage error.
|
|
7
|
+
*
|
|
8
|
+
* EXEMPT from the "every subcommand records one info line" rule, for the same
|
|
9
|
+
* reason `logging/cli-commands.ts` is: recording a line opens today's log file,
|
|
10
|
+
* and a command whose job is to delete the user's data should not be creating
|
|
11
|
+
* files as a side effect of announcing itself.
|
|
12
|
+
*/
|
|
13
|
+
export interface HistoryCliOptions {
|
|
14
|
+
/** `-n <N>` — entries printed by `list` (default 20). */
|
|
15
|
+
lines?: string;
|
|
16
|
+
json?: boolean;
|
|
17
|
+
/** Required by `clear`; without it the command refuses. */
|
|
18
|
+
yes?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function runHistoryCommand(subcommand: string, opts: HistoryCliOptions): number;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aragon history path | list | clear` — the prompt history's operations
|
|
3
|
+
* surface (config-state-separation §5.5).
|
|
4
|
+
*
|
|
5
|
+
* Exit codes follow the existing convention: 0 success · 1 run-time failure ·
|
|
6
|
+
* 2 usage error.
|
|
7
|
+
*
|
|
8
|
+
* EXEMPT from the "every subcommand records one info line" rule, for the same
|
|
9
|
+
* reason `logging/cli-commands.ts` is: recording a line opens today's log file,
|
|
10
|
+
* and a command whose job is to delete the user's data should not be creating
|
|
11
|
+
* files as a side effect of announcing itself.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import process from 'node:process';
|
|
15
|
+
import { getPromptHistoryPath } from './app-paths.js';
|
|
16
|
+
import { clearPromptHistory, listPromptHistory } from './prompt-history.js';
|
|
17
|
+
const EXIT_OK = 0;
|
|
18
|
+
const EXIT_USAGE = 2;
|
|
19
|
+
const DEFAULT_LIST_ENTRIES = 20;
|
|
20
|
+
export function runHistoryCommand(subcommand, opts) {
|
|
21
|
+
switch (subcommand) {
|
|
22
|
+
case 'path':
|
|
23
|
+
return runPath();
|
|
24
|
+
case 'list':
|
|
25
|
+
return runList(opts);
|
|
26
|
+
case 'clear':
|
|
27
|
+
return runClear(opts);
|
|
28
|
+
default:
|
|
29
|
+
process.stderr.write(`Unknown history subcommand "${subcommand}". Try: path | list | clear.\n`);
|
|
30
|
+
return EXIT_USAGE;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function runPath() {
|
|
34
|
+
const path = getPromptHistoryPath();
|
|
35
|
+
process.stdout.write(`${path}\n`);
|
|
36
|
+
if (!existsSync(path))
|
|
37
|
+
process.stdout.write('(no history recorded yet)\n');
|
|
38
|
+
// The file holds prompts verbatim — the one thing to say out loud before
|
|
39
|
+
// someone attaches it to a bug report or copies it to another machine.
|
|
40
|
+
process.stdout.write('entries are stored verbatim; clear them with: aragon history clear --yes\n');
|
|
41
|
+
return EXIT_OK;
|
|
42
|
+
}
|
|
43
|
+
function runList(opts) {
|
|
44
|
+
const entries = listPromptHistory();
|
|
45
|
+
if (entries.length === 0) {
|
|
46
|
+
process.stdout.write('No prompt history recorded.\n');
|
|
47
|
+
return EXIT_OK;
|
|
48
|
+
}
|
|
49
|
+
const parsed = Number.parseInt(opts.lines ?? '', 10);
|
|
50
|
+
const wanted = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_LIST_ENTRIES;
|
|
51
|
+
const shown = entries.slice(0, wanted);
|
|
52
|
+
if (opts.json) {
|
|
53
|
+
process.stdout.write(`${JSON.stringify(shown, null, 2)}\n`);
|
|
54
|
+
return EXIT_OK;
|
|
55
|
+
}
|
|
56
|
+
for (const entry of shown) {
|
|
57
|
+
// Newlines would break the one-entry-per-line reading this output invites.
|
|
58
|
+
const text = entry.text.replace(/\r?\n/g, ' ');
|
|
59
|
+
process.stdout.write(`${new Date(entry.ts).toISOString()} ${text}\n`);
|
|
60
|
+
}
|
|
61
|
+
return EXIT_OK;
|
|
62
|
+
}
|
|
63
|
+
function runClear(opts) {
|
|
64
|
+
if (!opts.yes) {
|
|
65
|
+
process.stderr.write('Refusing to delete the prompt history without --yes.\n');
|
|
66
|
+
return EXIT_USAGE;
|
|
67
|
+
}
|
|
68
|
+
const removed = clearPromptHistory();
|
|
69
|
+
process.stdout.write(`Removed ${removed} entries.\n`);
|
|
70
|
+
return EXIT_OK;
|
|
71
|
+
}
|
package/dist/config/load.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface CliFlags {
|
|
|
27
27
|
compact?: boolean;
|
|
28
28
|
/** `--hints` ⇒ true, `--no-hints` ⇒ false, absent ⇒ undefined. */
|
|
29
29
|
hints?: boolean;
|
|
30
|
+
/** `--mouse` ⇒ true, `--no-mouse` ⇒ false, absent ⇒ undefined. */
|
|
31
|
+
mouse?: boolean;
|
|
30
32
|
/** `--no-skills` ⇒ false. Turns the whole skill subsystem off (§7.5). */
|
|
31
33
|
skills?: boolean;
|
|
32
34
|
/** `--skill <name>` ×N — force Level 2 injection for this run. */
|
|
@@ -37,6 +39,19 @@ export interface CliFlags {
|
|
|
37
39
|
skillToolPolicy?: string;
|
|
38
40
|
/** `--plan` ⇒ true, `--no-plan` ⇒ false, absent ⇒ undefined (plan-mode §4.4). */
|
|
39
41
|
plan?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* `--team` ⇒ true, `--no-team` ⇒ false, absent ⇒ undefined (team-subagents §4.4).
|
|
44
|
+
*
|
|
45
|
+
* BOTH commander forms must be declared and this field must be carried through
|
|
46
|
+
* `toFlags`, or the flag is inert with no compile error and no runtime error —
|
|
47
|
+
* the failure `cli.tsx` already records in its own words for `--no-mouse`
|
|
48
|
+
* (P1-4). Declaring only the negative form additionally makes commander
|
|
49
|
+
* default `opts.team` to `true`, at which point the flag is indistinguishable
|
|
50
|
+
* from its own default and silently overrides `config.json` on EVERY run.
|
|
51
|
+
*/
|
|
52
|
+
team?: boolean;
|
|
53
|
+
/** `--team-max <n>` — override `team.maxSubagents` for this run (still clamped). */
|
|
54
|
+
teamMax?: string | number;
|
|
40
55
|
/** `--log-level <lv>` — this run only, never persisted. */
|
|
41
56
|
logLevel?: string;
|
|
42
57
|
/** `--log-file` ⇒ true, `--no-log-file` ⇒ false, absent ⇒ undefined. */
|
package/dist/config/load.js
CHANGED
|
@@ -6,9 +6,12 @@
|
|
|
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, clampSkillsConfig, clampTheme, clampThinkingLevel, clampTranscriptWindow, coerceMaxTokens, coercePositiveInt, isSkillsToolPolicyMode, } from './schema.js';
|
|
9
|
+
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampLogConfig, clampSkillsConfig, clampTeamConfig, clampTheme, clampThinkingLevel, clampTranscriptWindow, coerceMaxTokens, coercePositiveInt, isSkillsToolPolicyMode, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
|
+
import { getConfigPath } from './app-paths.js';
|
|
13
|
+
import { setHistoryEnabled } from './prompt-history.js';
|
|
14
|
+
import { readSubmitCount } from './ui-state.js';
|
|
12
15
|
import { getLogger } from '../logging/logger.js';
|
|
13
16
|
import { registerSecret, registerSecretsFrom } from '../logging/secret-registry.js';
|
|
14
17
|
import { detectCapabilities } from '../ui/capabilities.js';
|
|
@@ -33,6 +36,28 @@ function resolveFullscreen(flags, env, file) {
|
|
|
33
36
|
return false;
|
|
34
37
|
return undefined;
|
|
35
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve wheel region routing: flag › env › file › default `true`.
|
|
41
|
+
*
|
|
42
|
+
* THIS NEEDS ITS OWN RESOLVER; it does not "match every other boolean" in this
|
|
43
|
+
* file. `hints` is `flags.hints !== undefined ? … : file.hints ?? DEFAULT` and
|
|
44
|
+
* reads NO ENV AT ALL — copying that line would ship `ARAGON_MOUSE` as
|
|
45
|
+
* documented-but-dead (P1-9). Only `fullscreen` has an env layer, and only
|
|
46
|
+
* because `resolveFullscreen` above was written to give it one.
|
|
47
|
+
*
|
|
48
|
+
* Unlike `fullscreen` this is a plain boolean rather than a tri-state: there
|
|
49
|
+
* are no heuristics for an explicit `true` to override, so a persisted `true`
|
|
50
|
+
* is just a value and not an opinion that has to be told apart from silence.
|
|
51
|
+
*/
|
|
52
|
+
function resolveMouse(flags, env, file) {
|
|
53
|
+
if (flags.mouse !== undefined)
|
|
54
|
+
return flags.mouse;
|
|
55
|
+
if (env.mouse !== undefined)
|
|
56
|
+
return env.mouse;
|
|
57
|
+
if (file.mouse !== undefined)
|
|
58
|
+
return file.mouse;
|
|
59
|
+
return DEFAULT_CONFIG.mouse;
|
|
60
|
+
}
|
|
36
61
|
/**
|
|
37
62
|
* Resolve whether logs reach a file: flag › env › an explicit `false` in the
|
|
38
63
|
* file › default `true`.
|
|
@@ -51,6 +76,27 @@ function resolveLogToFile(flags, env, file) {
|
|
|
51
76
|
return false;
|
|
52
77
|
return true;
|
|
53
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Merge the `team` section across all four layers (defaults › file › env ›
|
|
81
|
+
* flags), team-subagents §3.10.
|
|
82
|
+
*
|
|
83
|
+
* `flags.team !== undefined` rather than a truthiness check, and that is the
|
|
84
|
+
* whole point (P1-4): a truthiness check cannot tell `--no-team` from "not
|
|
85
|
+
* passed", so `--no-team` would resolve to the config file's value and the off
|
|
86
|
+
* switch would not be off. `--no-skills` gets away with a one-directional test
|
|
87
|
+
* only because nothing ever needs to distinguish `--skills` from silence.
|
|
88
|
+
*/
|
|
89
|
+
function resolveTeamConfig(flags, env, file) {
|
|
90
|
+
return clampTeamConfig({
|
|
91
|
+
...DEFAULT_CONFIG.team,
|
|
92
|
+
...(file.team ?? {}),
|
|
93
|
+
...(env.team ?? {}),
|
|
94
|
+
...(flags.team !== undefined ? { enabled: flags.team } : {}),
|
|
95
|
+
...(flags.teamMax !== undefined && flags.teamMax !== ''
|
|
96
|
+
? { maxSubagents: flags.teamMax }
|
|
97
|
+
: {}),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
54
100
|
/** Merge the `log` section across all four layers (defaults › file › env › flags). */
|
|
55
101
|
function resolveLogConfig(flags, env, file) {
|
|
56
102
|
const merged = clampLogConfig({
|
|
@@ -156,7 +202,12 @@ export function loadConfig(flags = {}) {
|
|
|
156
202
|
// Detect terminal capabilities once; force monochrome when color is disabled.
|
|
157
203
|
const caps = detectCapabilities(process.env, process.stdout);
|
|
158
204
|
const colorLevel = color ? caps.colorLevel : 0;
|
|
159
|
-
|
|
205
|
+
// No flag and no env var: a privacy switch you set once, not a per-run knob.
|
|
206
|
+
const historyEnabled = file.historyEnabled ?? DEFAULT_CONFIG.historyEnabled;
|
|
207
|
+
// The prompt-history store is a module-level singleton, so the resolved value
|
|
208
|
+
// has to be pushed into it — this is the ONLY place that happens.
|
|
209
|
+
setHistoryEnabled(historyEnabled);
|
|
210
|
+
const config = {
|
|
160
211
|
provider,
|
|
161
212
|
model,
|
|
162
213
|
baseUrl,
|
|
@@ -173,11 +224,12 @@ export function loadConfig(flags = {}) {
|
|
|
173
224
|
toolTimeoutMs,
|
|
174
225
|
idleTimeoutMs,
|
|
175
226
|
apiKeys,
|
|
176
|
-
|
|
177
|
-
promptHistory: file.promptHistory ?? [],
|
|
227
|
+
historyEnabled,
|
|
178
228
|
density,
|
|
179
229
|
hints,
|
|
180
|
-
|
|
230
|
+
mouse: resolveMouse(flags, env.partial, file),
|
|
231
|
+
// From `<home>/state.json`, not the config file — see `ui-state.ts`.
|
|
232
|
+
submitCount: readSubmitCount(),
|
|
181
233
|
startInPlanMode,
|
|
182
234
|
planModeMaxAskRounds,
|
|
183
235
|
planModeHumanTimeoutMs,
|
|
@@ -194,12 +246,43 @@ export function loadConfig(flags = {}) {
|
|
|
194
246
|
: {}),
|
|
195
247
|
},
|
|
196
248
|
log: resolveLogConfig(flags, env.partial, file),
|
|
249
|
+
team: resolveTeamConfig(flags, env.partial, file),
|
|
197
250
|
cwd,
|
|
198
251
|
color,
|
|
199
252
|
colorLevel,
|
|
200
253
|
unicode: caps.unicode,
|
|
201
254
|
apiKeyOverride: flags.apiKey && flags.apiKey.trim().length > 0 ? flags.apiKey.trim() : undefined,
|
|
202
255
|
};
|
|
256
|
+
recordResolvedConfig(config);
|
|
257
|
+
return config;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* One record naming the values this run actually resolved to.
|
|
261
|
+
*
|
|
262
|
+
* "I changed the model in config.json and it still uses the old one" was, until
|
|
263
|
+
* this line existed, unanswerable after the fact: four layers feed this
|
|
264
|
+
* function and none of them left a trace. Now the answer is in the log next to
|
|
265
|
+
* the path the values came from.
|
|
266
|
+
*
|
|
267
|
+
* NO SECRET VALUES — only whether a key was found. `logging/redact.ts` would
|
|
268
|
+
* catch one anyway, but a record that never carries the secret does not depend
|
|
269
|
+
* on that backstop being configured.
|
|
270
|
+
*/
|
|
271
|
+
function recordResolvedConfig(config) {
|
|
272
|
+
getLogger().info('config', 'config_loaded', {
|
|
273
|
+
provider: config.provider,
|
|
274
|
+
model: config.model,
|
|
275
|
+
baseUrl: config.baseUrl,
|
|
276
|
+
thinkingLevel: config.thinkingLevel,
|
|
277
|
+
theme: config.theme,
|
|
278
|
+
fullscreen: config.fullscreen,
|
|
279
|
+
toolTimeoutMs: config.toolTimeoutMs,
|
|
280
|
+
idleTimeoutMs: config.idleTimeoutMs,
|
|
281
|
+
historyEnabled: config.historyEnabled,
|
|
282
|
+
logLevel: config.log.level,
|
|
283
|
+
hasApiKey: Boolean(config.apiKeys[config.provider]),
|
|
284
|
+
configPath: getConfigPath(),
|
|
285
|
+
});
|
|
203
286
|
}
|
|
204
287
|
/**
|
|
205
288
|
* Build the `getApiKey(providerId)` resolver the core `Agent` consumes.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot move of the four runtime-state keys out of `config.json`
|
|
3
|
+
* (config-state-separation §4.6).
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* WHY THIS EXISTS: the schema change alone would strip `promptHistory`,
|
|
7
|
+
* `submitCount` and `mouseNoticeSeen` from an existing user's config file on
|
|
8
|
+
* the first write after upgrading, with nowhere for the values to go. That is
|
|
9
|
+
* the only path in this feature that can actually LOSE data, which is why the
|
|
10
|
+
* strip and this migration must ship together.
|
|
11
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
*
|
|
13
|
+
* THE IDEMPOTENCE KEY IS THE SOURCE DATA — "are those keys still in
|
|
14
|
+
* config.json" — not a breadcrumb and not "does prompt-history.jsonl exist".
|
|
15
|
+
* Same reasoning as `migrate-home.ts`: a breadcrumb written before a failed
|
|
16
|
+
* step makes a half-finished migration skip itself forever. Keyed on the source,
|
|
17
|
+
* a failure simply retries next launch, and the two import functions are
|
|
18
|
+
* written so that retrying imports nothing twice.
|
|
19
|
+
*
|
|
20
|
+
* NEVER THROWS (C3) and never prints (D-8). The user sees no difference —
|
|
21
|
+
* history still recalls, the counter still counts — so a line on stderr would
|
|
22
|
+
* be noise, and `aragon config home` names the new files for anyone curious.
|
|
23
|
+
*/
|
|
24
|
+
export interface StateMigrationResult {
|
|
25
|
+
/** The keys actually carried out of the file, e.g. `['promptHistory']`. */
|
|
26
|
+
moved: string[];
|
|
27
|
+
/** History entries imported, for the log record. */
|
|
28
|
+
promptEntries: number;
|
|
29
|
+
/** Returned rather than thrown (C3). */
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function migrateStateOutOfConfig(): StateMigrationResult;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot move of the four runtime-state keys out of `config.json`
|
|
3
|
+
* (config-state-separation §4.6).
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* WHY THIS EXISTS: the schema change alone would strip `promptHistory`,
|
|
7
|
+
* `submitCount` and `mouseNoticeSeen` from an existing user's config file on
|
|
8
|
+
* the first write after upgrading, with nowhere for the values to go. That is
|
|
9
|
+
* the only path in this feature that can actually LOSE data, which is why the
|
|
10
|
+
* strip and this migration must ship together.
|
|
11
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
*
|
|
13
|
+
* THE IDEMPOTENCE KEY IS THE SOURCE DATA — "are those keys still in
|
|
14
|
+
* config.json" — not a breadcrumb and not "does prompt-history.jsonl exist".
|
|
15
|
+
* Same reasoning as `migrate-home.ts`: a breadcrumb written before a failed
|
|
16
|
+
* step makes a half-finished migration skip itself forever. Keyed on the source,
|
|
17
|
+
* a failure simply retries next launch, and the two import functions are
|
|
18
|
+
* written so that retrying imports nothing twice.
|
|
19
|
+
*
|
|
20
|
+
* NEVER THROWS (C3) and never prints (D-8). The user sees no difference —
|
|
21
|
+
* history still recalls, the counter still counts — so a line on stderr would
|
|
22
|
+
* be noise, and `aragon config home` names the new files for anyone curious.
|
|
23
|
+
*/
|
|
24
|
+
import { readConfigFile, updatePersistedConfig } from './store.js';
|
|
25
|
+
import { importLegacyPromptHistory } from './prompt-history.js';
|
|
26
|
+
import { importLegacyUiState } from './ui-state.js';
|
|
27
|
+
import { LEGACY_STATE_KEYS } from './schema.js';
|
|
28
|
+
import { getLogger } from '../logging/logger.js';
|
|
29
|
+
export function migrateStateOutOfConfig() {
|
|
30
|
+
const result = { moved: [], promptEntries: 0 };
|
|
31
|
+
try {
|
|
32
|
+
const { config } = readConfigFile();
|
|
33
|
+
// A missing file has nothing to move; an UNPARSEABLE one must be left
|
|
34
|
+
// exactly as it is, or the user's chance to fix their typo is gone.
|
|
35
|
+
if (!config)
|
|
36
|
+
return result;
|
|
37
|
+
const legacy = config;
|
|
38
|
+
const present = LEGACY_STATE_KEYS.filter((key) => legacy[key] !== undefined);
|
|
39
|
+
if (present.length === 0)
|
|
40
|
+
return result;
|
|
41
|
+
result.promptEntries = importHistory(legacy);
|
|
42
|
+
importCounters(legacy);
|
|
43
|
+
// An empty patch still goes read-strip-merge-write, so this is what makes
|
|
44
|
+
// the legacy keys leave the file — `stripLegacyStateKeys` does the work.
|
|
45
|
+
updatePersistedConfig({});
|
|
46
|
+
result.moved = [...present];
|
|
47
|
+
getLogger().info('migrate', 'migrate_state_split', {
|
|
48
|
+
moved: result.moved,
|
|
49
|
+
promptEntries: result.promptEntries,
|
|
50
|
+
});
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
return { moved: [], promptEntries: 0, error: err instanceof Error ? err.message : String(err) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Import the recall list, unless the user turned recording off.
|
|
59
|
+
*
|
|
60
|
+
* THE `historyEnabled` CHECK IS NOT REDUNDANT with the one inside
|
|
61
|
+
* `appendPrompt`. This runs before any `loadConfig()`, so the module-level
|
|
62
|
+
* switch is still at its `true` default; the only place the user's real answer
|
|
63
|
+
* exists at this moment is the config object read above. The path that makes it
|
|
64
|
+
* matter: upgrade, switch recording off, downgrade to 0.5.x (which writes
|
|
65
|
+
* `promptHistory` back into config.json), upgrade again. Without this, prompts
|
|
66
|
+
* the user explicitly refused to have recorded get copied into the new file.
|
|
67
|
+
*/
|
|
68
|
+
function importHistory(legacy) {
|
|
69
|
+
if (!Array.isArray(legacy.promptHistory) || legacy.promptHistory.length === 0)
|
|
70
|
+
return 0;
|
|
71
|
+
if (legacy.historyEnabled === false)
|
|
72
|
+
return 0;
|
|
73
|
+
const entries = legacy.promptHistory.filter((item) => typeof item === 'string');
|
|
74
|
+
return importLegacyPromptHistory(entries);
|
|
75
|
+
}
|
|
76
|
+
function importCounters(legacy) {
|
|
77
|
+
const patch = {};
|
|
78
|
+
if (typeof legacy.submitCount === 'number')
|
|
79
|
+
patch.submitCount = legacy.submitCount;
|
|
80
|
+
if (typeof legacy.mouseNoticeSeen === 'boolean')
|
|
81
|
+
patch.mouseNoticeSeen = legacy.mouseNoticeSeen;
|
|
82
|
+
if (Object.keys(patch).length === 0)
|
|
83
|
+
return;
|
|
84
|
+
importLegacyUiState(patch);
|
|
85
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
export declare const PROMPT_HISTORY_FILENAME = "prompt-history.jsonl";
|
|
27
|
+
export declare const PROMPT_HISTORY_LINE_VERSION = 1;
|
|
28
|
+
/**
|
|
29
|
+
* Longer submissions are not recorded at all (D-6).
|
|
30
|
+
*
|
|
31
|
+
* Storing a TRUNCATED copy would be worse than storing nothing: the user
|
|
32
|
+
* presses `↑`, gets back something that looks complete, and sends half a
|
|
33
|
+
* prompt. 16 KiB is far past anything typed by hand.
|
|
34
|
+
*/
|
|
35
|
+
export declare const PROMPT_ENTRY_MAX_CHARS = 16384;
|
|
36
|
+
/** Physical lines tolerated before the file is rewritten down to the cap. */
|
|
37
|
+
export declare const HISTORY_COMPACT_AT_LINES = 400;
|
|
38
|
+
export interface PromptHistoryEntry {
|
|
39
|
+
/** Line schema version. Lines with any other value are skipped, not fatal. */
|
|
40
|
+
v: number;
|
|
41
|
+
/** `Date.now()` at submit time. */
|
|
42
|
+
ts: number;
|
|
43
|
+
/** The prompt verbatim — never redacted (D-5). */
|
|
44
|
+
text: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The recall list: oldest first, de-duplicated, capped. Cached for the life of
|
|
48
|
+
* the process so `/reload` — which calls `loadConfig()` again — cannot discard
|
|
49
|
+
* entries added during this session.
|
|
50
|
+
*
|
|
51
|
+
* Reads even when `historyEnabled` is false (RV-12): turning recording off
|
|
52
|
+
* stops new writes, but someone who wants to see what is already stored before
|
|
53
|
+
* deleting it must not be told "nothing here" while the file still exists.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loadPromptHistory(): string[];
|
|
56
|
+
/** Every entry on disk with its timestamp, newest first. For `history list`. */
|
|
57
|
+
export declare function listPromptHistory(): PromptHistoryEntry[];
|
|
58
|
+
/**
|
|
59
|
+
* Record one submitted prompt and return THE UPDATED LIST.
|
|
60
|
+
*
|
|
61
|
+
* The return value is the whole point of the signature (RV-1). `App.tsx` writes
|
|
62
|
+
* `setPromptHistory(appendPrompt(text))`: the recall list rendered by the
|
|
63
|
+
* composer is React state, and a module-level array updated in here re-renders
|
|
64
|
+
* nothing. Drop the return value and `↑` stops recalling what was just
|
|
65
|
+
* submitted — but only until the next restart, which makes it about as hard to
|
|
66
|
+
* diagnose as a bug in this area gets.
|
|
67
|
+
*
|
|
68
|
+
* Returns the current list unchanged when recording is off, the text is blank,
|
|
69
|
+
* or the text is oversized, so callers never need a branch.
|
|
70
|
+
*/
|
|
71
|
+
export declare function appendPrompt(text: string, now?: number): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Migration-only: append the texts that are not in the file yet, returning how
|
|
74
|
+
* many were written.
|
|
75
|
+
*
|
|
76
|
+
* Ignores `historyEnabled` on purpose — the caller decides, because it is the
|
|
77
|
+
* only code that can see an explicit `historyEnabled: false` in the config file
|
|
78
|
+
* before `loadConfig()` has run (§4.6 step 3).
|
|
79
|
+
*/
|
|
80
|
+
export declare function importLegacyPromptHistory(entries: string[]): number;
|
|
81
|
+
/** Delete the file and empty the cache. Returns how many entries were removed. */
|
|
82
|
+
export declare function clearPromptHistory(): number;
|
|
83
|
+
/** Called once by `loadConfig()` after `historyEnabled` has been resolved. */
|
|
84
|
+
export declare function setHistoryEnabled(enabled: boolean): void;
|
|
85
|
+
/** Test-only: forget the cache, the line counters and the switch. */
|
|
86
|
+
export declare function resetPromptHistoryForTests(): void;
|