@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.
Files changed (106) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +155 -5
  3. package/dist/agent/controller.d.ts +51 -1
  4. package/dist/agent/controller.js +177 -2
  5. package/dist/agent/headless.d.ts +12 -0
  6. package/dist/agent/headless.js +41 -1
  7. package/dist/agent/reducer.d.ts +48 -0
  8. package/dist/agent/reducer.js +91 -2
  9. package/dist/agent/system-prompt.d.ts +17 -0
  10. package/dist/agent/system-prompt.js +4 -0
  11. package/dist/cli.js +131 -6
  12. package/dist/commands/builtins.js +82 -0
  13. package/dist/config/app-paths.d.ts +17 -0
  14. package/dist/config/app-paths.js +21 -0
  15. package/dist/config/cli-commands.d.ts +26 -2
  16. package/dist/config/cli-commands.js +63 -4
  17. package/dist/config/env.d.ts +2 -2
  18. package/dist/config/env.js +36 -2
  19. package/dist/config/history-commands.d.ts +20 -0
  20. package/dist/config/history-commands.js +71 -0
  21. package/dist/config/load.d.ts +15 -0
  22. package/dist/config/load.js +88 -5
  23. package/dist/config/migrate-state-out-of-config.d.ts +32 -0
  24. package/dist/config/migrate-state-out-of-config.js +85 -0
  25. package/dist/config/prompt-history.d.ts +86 -0
  26. package/dist/config/prompt-history.js +332 -0
  27. package/dist/config/schema.d.ts +125 -9
  28. package/dist/config/schema.js +90 -5
  29. package/dist/config/store.js +13 -4
  30. package/dist/config/ui-state.d.ts +52 -0
  31. package/dist/config/ui-state.js +149 -0
  32. package/dist/input/mouse-events.d.ts +61 -0
  33. package/dist/input/mouse-events.js +153 -0
  34. package/dist/input/stdin-mouse-filter.d.ts +42 -0
  35. package/dist/input/stdin-mouse-filter.js +153 -0
  36. package/dist/logging/install.d.ts +85 -0
  37. package/dist/logging/install.js +94 -0
  38. package/dist/logging/logger.d.ts +1 -1
  39. package/dist/session/persist.d.ts +16 -0
  40. package/dist/session/persist.js +19 -1
  41. package/dist/team/activity.d.ts +67 -0
  42. package/dist/team/activity.js +130 -0
  43. package/dist/team/bus.d.ts +190 -0
  44. package/dist/team/bus.js +366 -0
  45. package/dist/team/comm-tools.d.ts +61 -0
  46. package/dist/team/comm-tools.js +221 -0
  47. package/dist/team/human-queue.d.ts +55 -0
  48. package/dist/team/human-queue.js +72 -0
  49. package/dist/team/limits.d.ts +123 -0
  50. package/dist/team/limits.js +128 -0
  51. package/dist/team/normalize.d.ts +39 -0
  52. package/dist/team/normalize.js +106 -0
  53. package/dist/team/panel-rows.d.ts +48 -0
  54. package/dist/team/panel-rows.js +75 -0
  55. package/dist/team/prompt.d.ts +49 -0
  56. package/dist/team/prompt.js +87 -0
  57. package/dist/team/report.d.ts +58 -0
  58. package/dist/team/report.js +205 -0
  59. package/dist/team/retry.d.ts +48 -0
  60. package/dist/team/retry.js +56 -0
  61. package/dist/team/runtime.d.ts +122 -0
  62. package/dist/team/runtime.js +406 -0
  63. package/dist/team/subagent.d.ts +90 -0
  64. package/dist/team/subagent.js +294 -0
  65. package/dist/team/task-tool.d.ts +47 -0
  66. package/dist/team/task-tool.js +118 -0
  67. package/dist/team/types.d.ts +172 -0
  68. package/dist/team/types.js +13 -0
  69. package/dist/tools/index.d.ts +48 -2
  70. package/dist/tools/index.js +26 -1
  71. package/dist/ui/App.d.ts +9 -1
  72. package/dist/ui/App.js +140 -21
  73. package/dist/ui/Composer.d.ts +6 -1
  74. package/dist/ui/Composer.js +2 -2
  75. package/dist/ui/PromptInput.d.ts +15 -1
  76. package/dist/ui/PromptInput.js +38 -1
  77. package/dist/ui/StatusBar.d.ts +21 -0
  78. package/dist/ui/StatusBar.js +5 -2
  79. package/dist/ui/TeamPanel.d.ts +57 -0
  80. package/dist/ui/TeamPanel.js +134 -0
  81. package/dist/ui/Transcript.js +10 -0
  82. package/dist/ui/entries/TeamCard.d.ts +31 -0
  83. package/dist/ui/entries/TeamCard.js +39 -0
  84. package/dist/ui/glyphs.d.ts +22 -0
  85. package/dist/ui/glyphs.js +10 -0
  86. package/dist/ui/layout/AppShell.d.ts +20 -1
  87. package/dist/ui/layout/AppShell.js +39 -4
  88. package/dist/ui/layout/ScrollIndicator.d.ts +31 -0
  89. package/dist/ui/layout/ScrollIndicator.js +15 -0
  90. package/dist/ui/layout/ScrollViewport.d.ts +8 -2
  91. package/dist/ui/layout/ScrollViewport.js +24 -4
  92. package/dist/ui/layout/regions.d.ts +55 -0
  93. package/dist/ui/layout/regions.js +52 -0
  94. package/dist/ui/layout/scroll-indicator.d.ts +27 -0
  95. package/dist/ui/layout/scroll-indicator.js +35 -0
  96. package/dist/ui/layout/scroll.d.ts +9 -0
  97. package/dist/ui/layout/scroll.js +23 -0
  98. package/dist/ui/overlays/HelpOverlay.js +14 -0
  99. package/dist/ui/screen.d.ts +14 -1
  100. package/dist/ui/screen.js +37 -3
  101. package/dist/ui/transcript-text.js +20 -0
  102. package/dist/ui/use-startup-notices.d.ts +11 -1
  103. package/dist/ui/use-startup-notices.js +29 -2
  104. package/dist/ui/use-wheel-routing.d.ts +68 -0
  105. package/dist/ui/use-wheel-routing.js +139 -0
  106. 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
+ }
@@ -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. */
@@ -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
- return {
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
- recentModels: file.recentModels ?? [],
177
- promptHistory: file.promptHistory ?? [],
227
+ historyEnabled,
178
228
  density,
179
229
  hints,
180
- submitCount: coercePositiveInt(file.submitCount, 0),
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;