@aragon-agent/cli 0.5.0 → 0.5.1
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 +131 -0
- package/README.md +227 -3
- package/dist/agent/agent-mode.d.ts +53 -0
- package/dist/agent/agent-mode.js +69 -0
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +154 -1
- package/dist/agent/plan-prompt.d.ts +18 -0
- package/dist/agent/plan-prompt.js +93 -0
- package/dist/agent/reducer.d.ts +14 -1
- package/dist/agent/reducer.js +9 -0
- package/dist/agent/system-prompt.d.ts +15 -0
- package/dist/agent/system-prompt.js +8 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +183 -20
- package/dist/commands/builtins.js +60 -0
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +71 -4
- package/dist/config/app-paths.js +139 -8
- package/dist/config/cli-commands.d.ts +43 -0
- package/dist/config/cli-commands.js +196 -0
- package/dist/config/env.d.ts +7 -1
- package/dist/config/env.js +45 -1
- package/dist/config/load.d.ts +8 -0
- package/dist/config/load.js +59 -2
- package/dist/config/migrate-home.d.ts +63 -0
- package/dist/config/migrate-home.js +165 -0
- package/dist/config/migrate-legacy-state.d.ts +10 -0
- package/dist/config/migrate-legacy-state.js +15 -6
- package/dist/config/schema.d.ts +61 -0
- package/dist/config/schema.js +83 -0
- package/dist/config/store.d.ts +30 -12
- package/dist/config/store.js +47 -35
- package/dist/logging/cli-commands.d.ts +25 -0
- package/dist/logging/cli-commands.js +221 -0
- package/dist/logging/file-sink.d.ts +114 -0
- package/dist/logging/file-sink.js +326 -0
- package/dist/logging/install.d.ts +163 -0
- package/dist/logging/install.js +302 -0
- package/dist/logging/levels.d.ts +40 -0
- package/dist/logging/levels.js +59 -0
- package/dist/logging/logger.d.ts +110 -0
- package/dist/logging/logger.js +237 -0
- package/dist/logging/redact.d.ts +28 -0
- package/dist/logging/redact.js +90 -0
- package/dist/logging/secret-registry.d.ts +30 -0
- package/dist/logging/secret-registry.js +55 -0
- package/dist/skills/paths.d.ts +2 -4
- package/dist/skills/paths.js +7 -9
- package/dist/tools/human-input.d.ts +179 -0
- package/dist/tools/human-input.js +227 -0
- package/dist/tools/index.d.ts +35 -3
- package/dist/tools/index.js +96 -9
- package/dist/tools/plan-tools.d.ts +64 -0
- package/dist/tools/plan-tools.js +198 -0
- package/dist/ui/App.d.ts +4 -1
- package/dist/ui/App.js +182 -11
- package/dist/ui/Composer.d.ts +4 -1
- package/dist/ui/Composer.js +26 -7
- package/dist/ui/ModeChip.d.ts +29 -0
- package/dist/ui/ModeChip.js +15 -0
- package/dist/ui/PromptInput.d.ts +8 -1
- package/dist/ui/PromptInput.js +12 -2
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +10 -2
- package/dist/ui/entries/ToolPreview.d.ts +2 -0
- package/dist/ui/entries/ToolPreview.js +64 -0
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +8 -0
- package/dist/ui/layout/wrap-rows.d.ts +24 -0
- package/dist/ui/layout/wrap-rows.js +74 -0
- package/dist/ui/overlays/HelpOverlay.js +13 -0
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +50 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +117 -0
- package/dist/ui/overlays/QuestionOverlay.d.ts +30 -0
- package/dist/ui/overlays/QuestionOverlay.js +193 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +8 -1
- package/dist/ui/use-startup-notices.d.ts +15 -0
- package/dist/ui/use-startup-notices.js +38 -0
- package/package.json +3 -2
- package/runtime/insecure-tls-warning.cjs +62 -0
- package/runtime/insecure-tls-warning.d.cts +5 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot migration of the user's on-disk state from the `env-paths`
|
|
3
|
+
* directories into `~/.aragon-agent/`.
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* WHY THIS EXISTS: up to 0.5.0 the config lived in
|
|
7
|
+
* `%APPDATA%\aragon-agent-nodejs\Config` and the sessions and skills in
|
|
8
|
+
* `%LOCALAPPDATA%\aragon-agent-nodejs\Data`. Moving the root without moving the
|
|
9
|
+
* contents would present to every existing user as a first run: no API key, no
|
|
10
|
+
* saved sessions, no installed skills — with all of it still on disk somewhere
|
|
11
|
+
* nobody thinks to look.
|
|
12
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
*
|
|
14
|
+
* ORDER MATTERS AND IS NOT INTERCHANGEABLE. `migrateLegacyState()` (chain step
|
|
15
|
+
* ①, pre-rename tree -> current tree) MUST run first. Run this one first and it
|
|
16
|
+
* finds the `aragon-agent` env-paths tree empty and does nothing; ① then moves
|
|
17
|
+
* the 0.4.x data into a location no code reads any more. Because the idempotence
|
|
18
|
+
* keys below are per-artifact, `sessions` and `skills` would still be picked up
|
|
19
|
+
* on the NEXT launch — but `config.json` would not: by then the user has re-
|
|
20
|
+
* entered their key, the target file exists, and the original is stranded.
|
|
21
|
+
*
|
|
22
|
+
* THIS FUNCTION NEVER THROWS, for the reason spelled out in
|
|
23
|
+
* `migrate-legacy-state.ts`: a user who has to re-enter an API key is
|
|
24
|
+
* inconvenienced; a user whose CLI will not start is blocked.
|
|
25
|
+
*
|
|
26
|
+
* THE IDEMPOTENCE KEY IS PER-ARTIFACT — "does this target already exist" — and
|
|
27
|
+
* deliberately NOT "does the home root exist". The logger creates `<home>/logs`
|
|
28
|
+
* and `skills/usage.ts` creates `<home>` itself, both of them possibly in this
|
|
29
|
+
* same process; keying on the root would let a half-finished migration be
|
|
30
|
+
* skipped forever on the next launch.
|
|
31
|
+
*/
|
|
32
|
+
/** Filesystem surface, so tests never touch a real user directory. */
|
|
33
|
+
export interface HomeMigrationFsPort {
|
|
34
|
+
existsSync(path: string): boolean;
|
|
35
|
+
mkdirSync(path: string, options: {
|
|
36
|
+
recursive: true;
|
|
37
|
+
}): void;
|
|
38
|
+
renameSync(from: string, to: string): void;
|
|
39
|
+
cpSync(from: string, to: string, options: {
|
|
40
|
+
recursive: true;
|
|
41
|
+
}): void;
|
|
42
|
+
writeFileSync(path: string, data: string): void;
|
|
43
|
+
}
|
|
44
|
+
export declare const nodeHomeFsPort: HomeMigrationFsPort;
|
|
45
|
+
export interface HomeMigrationResult {
|
|
46
|
+
mode: 'none' | 'renamed' | 'copied' | 'mixed';
|
|
47
|
+
/** Artifact names that actually moved (`config.json`, `sessions`, …). */
|
|
48
|
+
moved: string[];
|
|
49
|
+
/** Populated instead of throwing. */
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function migrateToHome(fs?: HomeMigrationFsPort): HomeMigrationResult;
|
|
53
|
+
/** Path of the breadcrumb, for `aragon config home`'s "previous location" line. */
|
|
54
|
+
export declare function getHomeMigrationBreadcrumbPath(): string;
|
|
55
|
+
/**
|
|
56
|
+
* The one-line notice for a migration that did something.
|
|
57
|
+
*
|
|
58
|
+
* Callers MUST write this to stderr: `aragon -p "..." > out.txt` yields model
|
|
59
|
+
* output and nothing else, and one stray line here breaks every scripted use.
|
|
60
|
+
* ASCII-only with `->` rather than an arrow glyph — this prints before any
|
|
61
|
+
* terminal capability probe has run.
|
|
62
|
+
*/
|
|
63
|
+
export declare function formatHomeMigrationNotice(result: HomeMigrationResult): string | null;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot migration of the user's on-disk state from the `env-paths`
|
|
3
|
+
* directories into `~/.aragon-agent/`.
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* WHY THIS EXISTS: up to 0.5.0 the config lived in
|
|
7
|
+
* `%APPDATA%\aragon-agent-nodejs\Config` and the sessions and skills in
|
|
8
|
+
* `%LOCALAPPDATA%\aragon-agent-nodejs\Data`. Moving the root without moving the
|
|
9
|
+
* contents would present to every existing user as a first run: no API key, no
|
|
10
|
+
* saved sessions, no installed skills — with all of it still on disk somewhere
|
|
11
|
+
* nobody thinks to look.
|
|
12
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
*
|
|
14
|
+
* ORDER MATTERS AND IS NOT INTERCHANGEABLE. `migrateLegacyState()` (chain step
|
|
15
|
+
* ①, pre-rename tree -> current tree) MUST run first. Run this one first and it
|
|
16
|
+
* finds the `aragon-agent` env-paths tree empty and does nothing; ① then moves
|
|
17
|
+
* the 0.4.x data into a location no code reads any more. Because the idempotence
|
|
18
|
+
* keys below are per-artifact, `sessions` and `skills` would still be picked up
|
|
19
|
+
* on the NEXT launch — but `config.json` would not: by then the user has re-
|
|
20
|
+
* entered their key, the target file exists, and the original is stranded.
|
|
21
|
+
*
|
|
22
|
+
* THIS FUNCTION NEVER THROWS, for the reason spelled out in
|
|
23
|
+
* `migrate-legacy-state.ts`: a user who has to re-enter an API key is
|
|
24
|
+
* inconvenienced; a user whose CLI will not start is blocked.
|
|
25
|
+
*
|
|
26
|
+
* THE IDEMPOTENCE KEY IS PER-ARTIFACT — "does this target already exist" — and
|
|
27
|
+
* deliberately NOT "does the home root exist". The logger creates `<home>/logs`
|
|
28
|
+
* and `skills/usage.ts` creates `<home>` itself, both of them possibly in this
|
|
29
|
+
* same process; keying on the root would let a half-finished migration be
|
|
30
|
+
* skipped forever on the next launch.
|
|
31
|
+
*/
|
|
32
|
+
import { cpSync, existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
33
|
+
import { dirname, join } from 'node:path';
|
|
34
|
+
import { getHomeRoot, legacyEnvPaths } from './app-paths.js';
|
|
35
|
+
import { LEGACY_MIGRATION_BREADCRUMB_FILENAME } from './migrate-legacy-state.js';
|
|
36
|
+
export const nodeHomeFsPort = {
|
|
37
|
+
existsSync,
|
|
38
|
+
mkdirSync: (path, options) => {
|
|
39
|
+
mkdirSync(path, options);
|
|
40
|
+
},
|
|
41
|
+
renameSync,
|
|
42
|
+
cpSync,
|
|
43
|
+
writeFileSync: (path, data) => writeFileSync(path, data, 'utf-8'),
|
|
44
|
+
};
|
|
45
|
+
const BREADCRUMB_FILENAME = '.migrated-from-env-paths';
|
|
46
|
+
function artifacts(homeRoot) {
|
|
47
|
+
const config = legacyEnvPaths.config;
|
|
48
|
+
const data = legacyEnvPaths.data;
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
name: 'config.json',
|
|
52
|
+
from: join(config, 'config.json'),
|
|
53
|
+
to: join(homeRoot, 'config.json'),
|
|
54
|
+
copy: true,
|
|
55
|
+
},
|
|
56
|
+
{ name: 'sessions', from: join(data, 'sessions'), to: join(homeRoot, 'sessions'), copy: false },
|
|
57
|
+
{ name: 'skills', from: join(data, 'skills'), to: join(homeRoot, 'skills'), copy: false },
|
|
58
|
+
{
|
|
59
|
+
name: 'skill-usage.json',
|
|
60
|
+
from: join(data, 'skill-usage.json'),
|
|
61
|
+
to: join(homeRoot, 'skill-usage.json'),
|
|
62
|
+
copy: false,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: LEGACY_MIGRATION_BREADCRUMB_FILENAME,
|
|
66
|
+
from: join(config, LEGACY_MIGRATION_BREADCRUMB_FILENAME),
|
|
67
|
+
to: join(homeRoot, LEGACY_MIGRATION_BREADCRUMB_FILENAME),
|
|
68
|
+
copy: false,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
export function migrateToHome(fs = nodeHomeFsPort) {
|
|
73
|
+
const result = { mode: 'none', moved: [] };
|
|
74
|
+
try {
|
|
75
|
+
const homeRoot = getHomeRoot();
|
|
76
|
+
let renamed = false;
|
|
77
|
+
let copied = false;
|
|
78
|
+
for (const artifact of artifacts(homeRoot)) {
|
|
79
|
+
// Already migrated, or built by hand. Either way: never overwrite.
|
|
80
|
+
if (fs.existsSync(artifact.to))
|
|
81
|
+
continue;
|
|
82
|
+
if (!fs.existsSync(artifact.from))
|
|
83
|
+
continue;
|
|
84
|
+
fs.mkdirSync(dirname(artifact.to), { recursive: true });
|
|
85
|
+
if (artifact.copy) {
|
|
86
|
+
fs.cpSync(artifact.from, artifact.to, { recursive: true });
|
|
87
|
+
copied = true;
|
|
88
|
+
}
|
|
89
|
+
else if (moveOrCopy(fs, artifact)) {
|
|
90
|
+
renamed = true;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
copied = true;
|
|
94
|
+
}
|
|
95
|
+
result.moved.push(artifact.name);
|
|
96
|
+
}
|
|
97
|
+
if (result.moved.length === 0)
|
|
98
|
+
return result;
|
|
99
|
+
result.mode = renamed && copied ? 'mixed' : renamed ? 'renamed' : 'copied';
|
|
100
|
+
writeBreadcrumb(fs, homeRoot, result);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return { mode: 'none', moved: [], error: err instanceof Error ? err.message : String(err) };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Rename, falling back to a copy that KEEPS the source. Returns whether the
|
|
109
|
+
* rename succeeded.
|
|
110
|
+
*
|
|
111
|
+
* On Windows `%APPDATA%` and `%LOCALAPPDATA%` can sit on different volumes, so
|
|
112
|
+
* `EXDEV` here is ordinary rather than exceptional; antivirus contributes
|
|
113
|
+
* `EPERM`. A half-written copy next to a deleted original is the only outcome
|
|
114
|
+
* worse than not migrating at all, which is why the source stays.
|
|
115
|
+
*/
|
|
116
|
+
function moveOrCopy(fs, artifact) {
|
|
117
|
+
try {
|
|
118
|
+
fs.renameSync(artifact.from, artifact.to);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
fs.cpSync(artifact.from, artifact.to, { recursive: true });
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Support-diagnosis breadcrumb, and deliberately NOT the idempotence key.
|
|
128
|
+
*
|
|
129
|
+
* It swallows its own failures for the same reason the brand migration's does:
|
|
130
|
+
* letting an ENOENT here reach the caller's outer catch would report
|
|
131
|
+
* `mode: 'none'` for a migration that actually moved the user's data — telling
|
|
132
|
+
* support the exact opposite of what happened.
|
|
133
|
+
*/
|
|
134
|
+
function writeBreadcrumb(fs, homeRoot, result) {
|
|
135
|
+
const payload = {
|
|
136
|
+
migratedAt: new Date().toISOString(),
|
|
137
|
+
mode: result.mode,
|
|
138
|
+
from: { config: legacyEnvPaths.config, data: legacyEnvPaths.data },
|
|
139
|
+
moved: result.moved,
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
fs.mkdirSync(homeRoot, { recursive: true });
|
|
143
|
+
fs.writeFileSync(join(homeRoot, BREADCRUMB_FILENAME), `${JSON.stringify(payload, null, 2)}\n`);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Intentionally ignored — see the note above.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Path of the breadcrumb, for `aragon config home`'s "previous location" line. */
|
|
150
|
+
export function getHomeMigrationBreadcrumbPath() {
|
|
151
|
+
return join(getHomeRoot(), BREADCRUMB_FILENAME);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The one-line notice for a migration that did something.
|
|
155
|
+
*
|
|
156
|
+
* Callers MUST write this to stderr: `aragon -p "..." > out.txt` yields model
|
|
157
|
+
* output and nothing else, and one stray line here breaks every scripted use.
|
|
158
|
+
* ASCII-only with `->` rather than an arrow glyph — this prints before any
|
|
159
|
+
* terminal capability probe has run.
|
|
160
|
+
*/
|
|
161
|
+
export function formatHomeMigrationNotice(result) {
|
|
162
|
+
if (result.mode === 'none')
|
|
163
|
+
return null;
|
|
164
|
+
return `moved settings to ${getHomeRoot()} (${result.moved.join(', ')})`;
|
|
165
|
+
}
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* has to re-enter an API key is inconvenienced, a user whose CLI refuses to
|
|
21
21
|
* launch is blocked. Every failure path degrades to `{ mode: 'none', error }`.
|
|
22
22
|
*
|
|
23
|
+
* CHAIN POSITION: this is step ① of two. `migrateToHome()` runs immediately
|
|
24
|
+
* after it and moves the same data on to `~/.aragon-agent/`. The order is not
|
|
25
|
+
* interchangeable — see the header of `migrate-home.ts` for what running them
|
|
26
|
+
* the other way round strands, and why the damage is silent.
|
|
27
|
+
*
|
|
28
|
+
* The directories this module reads and writes are the `env-paths` roots, which
|
|
29
|
+
* after `migrateToHome()` hold nothing the CLI reads. That is why they are
|
|
30
|
+
* imported here as `legacyEnvPaths`: no other module may use them.
|
|
31
|
+
*
|
|
23
32
|
* IDEMPOTENCE KEY is "does the new directory already exist" — not the
|
|
24
33
|
* breadcrumb file, which is written for support diagnosis only. Two cold starts
|
|
25
34
|
* racing each other therefore both observe `existsSync(next) === false` and
|
|
@@ -63,6 +72,7 @@ export interface MigrationResult {
|
|
|
63
72
|
/** Populated instead of throwing. */
|
|
64
73
|
error?: string;
|
|
65
74
|
}
|
|
75
|
+
export declare const LEGACY_MIGRATION_BREADCRUMB_FILENAME = ".migrated-from-argon-agent";
|
|
66
76
|
export declare function migrateLegacyState(fs?: MigrationFsPort): MigrationResult;
|
|
67
77
|
/**
|
|
68
78
|
* The one-line notice for a migration that did something.
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* has to re-enter an API key is inconvenienced, a user whose CLI refuses to
|
|
21
21
|
* launch is blocked. Every failure path degrades to `{ mode: 'none', error }`.
|
|
22
22
|
*
|
|
23
|
+
* CHAIN POSITION: this is step ① of two. `migrateToHome()` runs immediately
|
|
24
|
+
* after it and moves the same data on to `~/.aragon-agent/`. The order is not
|
|
25
|
+
* interchangeable — see the header of `migrate-home.ts` for what running them
|
|
26
|
+
* the other way round strands, and why the damage is silent.
|
|
27
|
+
*
|
|
28
|
+
* The directories this module reads and writes are the `env-paths` roots, which
|
|
29
|
+
* after `migrateToHome()` hold nothing the CLI reads. That is why they are
|
|
30
|
+
* imported here as `legacyEnvPaths`: no other module may use them.
|
|
31
|
+
*
|
|
23
32
|
* IDEMPOTENCE KEY is "does the new directory already exist" — not the
|
|
24
33
|
* breadcrumb file, which is written for support diagnosis only. Two cold starts
|
|
25
34
|
* racing each other therefore both observe `existsSync(next) === false` and
|
|
@@ -33,7 +42,7 @@
|
|
|
33
42
|
import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
34
43
|
import { dirname, join } from 'node:path';
|
|
35
44
|
import envPaths from 'env-paths';
|
|
36
|
-
import {
|
|
45
|
+
import { legacyEnvPaths as envPathsRoots } from './app-paths.js';
|
|
37
46
|
/**
|
|
38
47
|
* The pre-rename `env-paths` application name. Deliberately declared here and
|
|
39
48
|
* not alongside `APP_NAME`: this module is the only consumer, and it is the
|
|
@@ -55,7 +64,7 @@ export const nodeFsPort = {
|
|
|
55
64
|
const LEGACY_MANIFEST_FILENAME = '.argon-skill.json';
|
|
56
65
|
const MANIFEST_FILENAME = '.aragon-skill.json';
|
|
57
66
|
const LEGACY_LOCK_FILENAME = '.argon-skills.lock';
|
|
58
|
-
const
|
|
67
|
+
export const LEGACY_MIGRATION_BREADCRUMB_FILENAME = '.migrated-from-argon-agent';
|
|
59
68
|
/**
|
|
60
69
|
* The roots the CLI actually uses. `envPaths` also exposes `cache`, `log` and
|
|
61
70
|
* `temp`; none of them hold anything the user would miss, and migrating a
|
|
@@ -70,7 +79,7 @@ export function migrateLegacyState(fs = nodeFsPort) {
|
|
|
70
79
|
const from = {};
|
|
71
80
|
for (const root of ROOTS) {
|
|
72
81
|
const legacy = legacyAppPaths[root];
|
|
73
|
-
const next =
|
|
82
|
+
const next = envPathsRoots[root];
|
|
74
83
|
// Already migrated, or the user built the new directory by hand. Either
|
|
75
84
|
// way: do not migrate, and above all do not overwrite.
|
|
76
85
|
if (fs.existsSync(next))
|
|
@@ -95,7 +104,7 @@ export function migrateLegacyState(fs = nodeFsPort) {
|
|
|
95
104
|
return result;
|
|
96
105
|
result.mode = copied ? 'copied' : 'renamed';
|
|
97
106
|
if (result.movedRoots.includes('data')) {
|
|
98
|
-
result.manifestsRenamed = renameSkillManifests(fs, join(
|
|
107
|
+
result.manifestsRenamed = renameSkillManifests(fs, join(envPathsRoots.data, 'skills'));
|
|
99
108
|
}
|
|
100
109
|
writeBreadcrumb(fs, result, from);
|
|
101
110
|
return result;
|
|
@@ -155,8 +164,8 @@ function writeBreadcrumb(fs, result, from) {
|
|
|
155
164
|
manifestsRenamed: result.manifestsRenamed,
|
|
156
165
|
};
|
|
157
166
|
try {
|
|
158
|
-
fs.mkdirSync(
|
|
159
|
-
fs.writeFileSync(join(
|
|
167
|
+
fs.mkdirSync(envPathsRoots.config, { recursive: true });
|
|
168
|
+
fs.writeFileSync(join(envPathsRoots.config, LEGACY_MIGRATION_BREADCRUMB_FILENAME), `${JSON.stringify(payload, null, 2)}\n`);
|
|
160
169
|
}
|
|
161
170
|
catch {
|
|
162
171
|
// Intentionally ignored — see the note above.
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* invariant).
|
|
7
7
|
*/
|
|
8
8
|
import type { ThinkingLevel } from '@aragon-agent/core';
|
|
9
|
+
import { type LogLevelName } from '../logging/levels.js';
|
|
9
10
|
/**
|
|
10
11
|
* `ModelRegistry` seeds builtin lists for five providers, but `initProviders()`
|
|
11
12
|
* registers only three adapters. Running a model whose provider has no adapter
|
|
@@ -67,6 +68,16 @@ export declare const DEFAULT_TRANSCRIPT_WINDOW = 300;
|
|
|
67
68
|
export declare const MIN_TRANSCRIPT_WINDOW = 50;
|
|
68
69
|
export declare const MAX_TRANSCRIPT_WINDOW = 2000;
|
|
69
70
|
export declare function clampTranscriptWindow(v: unknown, fallback: number): number;
|
|
71
|
+
/** `ask_user` calls allowed per user turn. */
|
|
72
|
+
export declare const DEFAULT_PLAN_MAX_ASK_ROUNDS = 4;
|
|
73
|
+
/**
|
|
74
|
+
* Ceiling on ONE human wait, in ms. Thirty minutes is far outside "the user
|
|
75
|
+
* went to get coffee", but a ceiling has to exist: an unattended terminal must
|
|
76
|
+
* not wedge a CI job or a stale ssh session forever.
|
|
77
|
+
*/
|
|
78
|
+
export declare const DEFAULT_PLAN_HUMAN_TIMEOUT_MS = 1800000;
|
|
79
|
+
export declare function clampAskRounds(v: unknown, fallback: number): number;
|
|
80
|
+
export declare function clampHumanTimeout(v: unknown, fallback: number): number;
|
|
70
81
|
export interface SkillsConfig {
|
|
71
82
|
enabled: boolean;
|
|
72
83
|
/** Skill names the user has switched off. */
|
|
@@ -149,6 +160,36 @@ export interface SkillsRuntimeOptions {
|
|
|
149
160
|
toolPolicy?: SkillsToolPolicyMode;
|
|
150
161
|
}
|
|
151
162
|
export declare const DEFAULT_SKILLS_RUNTIME: SkillsRuntimeOptions;
|
|
163
|
+
export interface LogConfig {
|
|
164
|
+
level: LogLevelName;
|
|
165
|
+
/** Whether records reach a file at all. */
|
|
166
|
+
toFile: boolean;
|
|
167
|
+
/** Absolute log directory; empty string means `<home>/logs`. */
|
|
168
|
+
dir: string;
|
|
169
|
+
/** Rotate a file once it would exceed this many bytes. */
|
|
170
|
+
maxFileBytes: number;
|
|
171
|
+
/** Files kept in the log directory; the oldest beyond this are deleted. */
|
|
172
|
+
maxFiles: number;
|
|
173
|
+
/**
|
|
174
|
+
* Run every record through the redaction pipeline. The ONE switch a user can
|
|
175
|
+
* flip to put credentials on disk, which is why turning it off prints a
|
|
176
|
+
* warning and has no settings-screen entry (§4.3).
|
|
177
|
+
*/
|
|
178
|
+
redactSecrets: boolean;
|
|
179
|
+
/** Truncation length for user content at `debug`; `trace` is never truncated. */
|
|
180
|
+
previewChars: number;
|
|
181
|
+
}
|
|
182
|
+
export declare const DEFAULT_LOG_CONFIG: LogConfig;
|
|
183
|
+
/**
|
|
184
|
+
* THE single gate for every log-config read AND write, exactly like
|
|
185
|
+
* `clampSkillsConfig`: hardening only the read path leaves a bad value on disk
|
|
186
|
+
* that reverts on every launch, which presents as "my setting won't stick".
|
|
187
|
+
*
|
|
188
|
+
* `previewChars` uses its own coercion because `coercePositiveInt` rejects `0`,
|
|
189
|
+
* and `0` is a legitimate value here — it means "record that content existed,
|
|
190
|
+
* but none of it".
|
|
191
|
+
*/
|
|
192
|
+
export declare function clampLogConfig(raw: unknown): LogConfig;
|
|
152
193
|
export interface PersistedConfig {
|
|
153
194
|
version: number;
|
|
154
195
|
provider: string;
|
|
@@ -178,8 +219,16 @@ export interface PersistedConfig {
|
|
|
178
219
|
hints: boolean;
|
|
179
220
|
/** Lifetime submit count driving the progressive-disclosure fade (§4.6). */
|
|
180
221
|
submitCount: number;
|
|
222
|
+
/** Start every session in PLAN mode (plan-mode §4.4). */
|
|
223
|
+
planModeDefault: boolean;
|
|
224
|
+
/** `ask_user` rounds allowed per user turn; clamped to [1, 10]. */
|
|
225
|
+
planModeMaxAskRounds: number;
|
|
226
|
+
/** Ceiling on a single human wait in ms; clamped to [60_000, 7_200_000]. */
|
|
227
|
+
planModeHumanTimeoutMs: number;
|
|
181
228
|
/** Skill system settings. The SECOND nested object in this file after apiKeys. */
|
|
182
229
|
skills: SkillsConfig;
|
|
230
|
+
/** Logging settings. The THIRD nested object; scalars only (see LogConfig). */
|
|
231
|
+
log: LogConfig;
|
|
183
232
|
}
|
|
184
233
|
export declare const DEFAULT_CONFIG: PersistedConfig;
|
|
185
234
|
export interface CliConfig {
|
|
@@ -215,9 +264,21 @@ export interface CliConfig {
|
|
|
215
264
|
density: DensityMode;
|
|
216
265
|
hints: boolean;
|
|
217
266
|
submitCount: number;
|
|
267
|
+
/**
|
|
268
|
+
* Whether this session opens in PLAN mode.
|
|
269
|
+
*
|
|
270
|
+
* Named for what it DOES rather than mirroring the persisted key: after
|
|
271
|
+
* resolution it is a resolved starting posture, not a preference, and the
|
|
272
|
+
* live mode from here on belongs to the controller.
|
|
273
|
+
*/
|
|
274
|
+
startInPlanMode: boolean;
|
|
275
|
+
planModeMaxAskRounds: number;
|
|
276
|
+
planModeHumanTimeoutMs: number;
|
|
218
277
|
skills: SkillsConfig;
|
|
219
278
|
/** Flag/env-only skill switches for this run (§7.5). Never persisted. */
|
|
220
279
|
skillsRuntime: SkillsRuntimeOptions;
|
|
280
|
+
/** Resolved logging settings; the authoritative input to `Logger.reconfigure`. */
|
|
281
|
+
log: LogConfig;
|
|
221
282
|
/** Working directory for tools (default `process.cwd()`, overridable). */
|
|
222
283
|
cwd: string;
|
|
223
284
|
/** Whether ANSI color is enabled. */
|
package/dist/config/schema.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* See spec §3.5 (resolution pipeline), §6.1 (config file), and §3.8 (timeout
|
|
6
6
|
* invariant).
|
|
7
7
|
*/
|
|
8
|
+
import { isAbsolute } from 'node:path';
|
|
9
|
+
import { clampLogLevel } from '../logging/levels.js';
|
|
8
10
|
// ---------------------------------------------------------------------------
|
|
9
11
|
// Provider set — only the adapter-backed providers are usable at run time (R5).
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
@@ -102,6 +104,30 @@ export function clampTranscriptWindow(v, fallback) {
|
|
|
102
104
|
const n = coercePositiveInt(v, fallback);
|
|
103
105
|
return Math.min(MAX_TRANSCRIPT_WINDOW, Math.max(MIN_TRANSCRIPT_WINDOW, n));
|
|
104
106
|
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Plan mode (plan-mode §4.4)
|
|
109
|
+
//
|
|
110
|
+
// THREE FLAT KEYS, not a nested `planMode` object. `store.ts` documents that its
|
|
111
|
+
// nested-object merge is hand-written and capped at one level; three scalars do
|
|
112
|
+
// not justify a second `clampXConfig` gate plus two more merge sites, and flat
|
|
113
|
+
// keys mean `store.ts` needs ZERO changes.
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/** `ask_user` calls allowed per user turn. */
|
|
116
|
+
export const DEFAULT_PLAN_MAX_ASK_ROUNDS = 4;
|
|
117
|
+
const ASK_ROUNDS_RANGE = { min: 1, max: 10 };
|
|
118
|
+
/**
|
|
119
|
+
* Ceiling on ONE human wait, in ms. Thirty minutes is far outside "the user
|
|
120
|
+
* went to get coffee", but a ceiling has to exist: an unattended terminal must
|
|
121
|
+
* not wedge a CI job or a stale ssh session forever.
|
|
122
|
+
*/
|
|
123
|
+
export const DEFAULT_PLAN_HUMAN_TIMEOUT_MS = 1_800_000;
|
|
124
|
+
const HUMAN_TIMEOUT_RANGE = { min: 60_000, max: 7_200_000 };
|
|
125
|
+
export function clampAskRounds(v, fallback) {
|
|
126
|
+
return clampInt(v, fallback, ASK_ROUNDS_RANGE);
|
|
127
|
+
}
|
|
128
|
+
export function clampHumanTimeout(v, fallback) {
|
|
129
|
+
return clampInt(v, fallback, HUMAN_TIMEOUT_RANGE);
|
|
130
|
+
}
|
|
105
131
|
export const SKILLS_INTEGRITY_MODES = ['off', 'warn', 'strict'];
|
|
106
132
|
export function clampSkillsIntegrity(v, fallback) {
|
|
107
133
|
return typeof v === 'string' && SKILLS_INTEGRITY_MODES.includes(v)
|
|
@@ -181,6 +207,59 @@ export function clampSkillsConfig(raw) {
|
|
|
181
207
|
};
|
|
182
208
|
}
|
|
183
209
|
export const DEFAULT_SKILLS_RUNTIME = { forcedSkills: [], approveAll: false };
|
|
210
|
+
const DEFAULT_MAX_LOG_FILE_BYTES = 5 * 1024 * 1024;
|
|
211
|
+
const LOG_FILE_BYTES_RANGE = { min: 64 * 1024, max: 256 * 1024 * 1024 };
|
|
212
|
+
const LOG_MAX_FILES_RANGE = { min: 1, max: 200 };
|
|
213
|
+
const LOG_PREVIEW_CHARS_RANGE = { min: 0, max: 8192 };
|
|
214
|
+
export const DEFAULT_LOG_CONFIG = {
|
|
215
|
+
level: 'info',
|
|
216
|
+
toFile: true,
|
|
217
|
+
dir: '',
|
|
218
|
+
maxFileBytes: DEFAULT_MAX_LOG_FILE_BYTES,
|
|
219
|
+
maxFiles: 10,
|
|
220
|
+
redactSecrets: true,
|
|
221
|
+
previewChars: 512,
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Coerce a log directory. Anything relative is discarded (falling back to
|
|
225
|
+
* `<home>/logs`) rather than resolved: a relative path would silently mean a
|
|
226
|
+
* different directory for every working directory the CLI is launched from, and
|
|
227
|
+
* `debug`-level files carry prompt text (R-13).
|
|
228
|
+
*/
|
|
229
|
+
function logDir(v, fallback) {
|
|
230
|
+
if (typeof v !== 'string')
|
|
231
|
+
return fallback;
|
|
232
|
+
const trimmed = v.trim();
|
|
233
|
+
if (trimmed.length === 0)
|
|
234
|
+
return '';
|
|
235
|
+
return isAbsolute(trimmed) ? trimmed : fallback;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* THE single gate for every log-config read AND write, exactly like
|
|
239
|
+
* `clampSkillsConfig`: hardening only the read path leaves a bad value on disk
|
|
240
|
+
* that reverts on every launch, which presents as "my setting won't stick".
|
|
241
|
+
*
|
|
242
|
+
* `previewChars` uses its own coercion because `coercePositiveInt` rejects `0`,
|
|
243
|
+
* and `0` is a legitimate value here — it means "record that content existed,
|
|
244
|
+
* but none of it".
|
|
245
|
+
*/
|
|
246
|
+
export function clampLogConfig(raw) {
|
|
247
|
+
const src = (raw && typeof raw === 'object' ? raw : {});
|
|
248
|
+
const preview = typeof src.previewChars === 'number' || typeof src.previewChars === 'string'
|
|
249
|
+
? Number.parseInt(String(src.previewChars), 10)
|
|
250
|
+
: Number.NaN;
|
|
251
|
+
return {
|
|
252
|
+
level: clampLogLevel(src.level, DEFAULT_LOG_CONFIG.level),
|
|
253
|
+
toFile: bool(src.toFile, DEFAULT_LOG_CONFIG.toFile),
|
|
254
|
+
dir: logDir(src.dir, DEFAULT_LOG_CONFIG.dir),
|
|
255
|
+
maxFileBytes: clampInt(src.maxFileBytes, DEFAULT_LOG_CONFIG.maxFileBytes, LOG_FILE_BYTES_RANGE),
|
|
256
|
+
maxFiles: clampInt(src.maxFiles, DEFAULT_LOG_CONFIG.maxFiles, LOG_MAX_FILES_RANGE),
|
|
257
|
+
redactSecrets: bool(src.redactSecrets, DEFAULT_LOG_CONFIG.redactSecrets),
|
|
258
|
+
previewChars: Number.isFinite(preview)
|
|
259
|
+
? Math.min(LOG_PREVIEW_CHARS_RANGE.max, Math.max(LOG_PREVIEW_CHARS_RANGE.min, preview))
|
|
260
|
+
: DEFAULT_LOG_CONFIG.previewChars,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
184
263
|
export const DEFAULT_CONFIG = {
|
|
185
264
|
version: CONFIG_VERSION,
|
|
186
265
|
provider: 'anthropic',
|
|
@@ -202,7 +281,11 @@ export const DEFAULT_CONFIG = {
|
|
|
202
281
|
density: 'comfortable',
|
|
203
282
|
hints: true,
|
|
204
283
|
submitCount: 0,
|
|
284
|
+
planModeDefault: false,
|
|
285
|
+
planModeMaxAskRounds: DEFAULT_PLAN_MAX_ASK_ROUNDS,
|
|
286
|
+
planModeHumanTimeoutMs: DEFAULT_PLAN_HUMAN_TIMEOUT_MS,
|
|
205
287
|
skills: DEFAULT_SKILLS_CONFIG,
|
|
288
|
+
log: DEFAULT_LOG_CONFIG,
|
|
206
289
|
};
|
|
207
290
|
// ---------------------------------------------------------------------------
|
|
208
291
|
// Coercion / validation helpers
|
package/dist/config/store.d.ts
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Config store — read/atomic-write the user config JSON
|
|
3
|
-
*
|
|
4
|
-
* `0600` permissions on POSIX (spec §3.5 / §6.1).
|
|
2
|
+
* Config store — read/atomic-write the user config JSON. Paths come from
|
|
3
|
+
* `app-paths.ts`; nothing here does its own path arithmetic. The file may hold
|
|
4
|
+
* secrets, so it is written with `0600` permissions on POSIX (spec §3.5 / §6.1).
|
|
5
|
+
*
|
|
6
|
+
* There is deliberately no `getConfigDir()` / `getSkillsDir()` here any more.
|
|
7
|
+
* The first would now return the entire user root under a name that says
|
|
8
|
+
* otherwise (see the test-isolation contract in `app-paths.ts`); the second was
|
|
9
|
+
* a second name for `skills/paths.ts::getUserSkillsDir()`, which is precisely
|
|
10
|
+
* the drift the header of that module warns about.
|
|
5
11
|
*/
|
|
12
|
+
import { getConfigPath, getSessionsDir } from './app-paths.js';
|
|
6
13
|
import { type PersistedConfig } from './schema.js';
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
export { getConfigPath, getSessionsDir };
|
|
15
|
+
export interface ConfigFileRead {
|
|
16
|
+
/** Only the keys the user actually wrote, or `null` when absent/unusable. */
|
|
17
|
+
config: Partial<PersistedConfig> | null;
|
|
18
|
+
/** Set when the file EXISTS but could not be parsed. Otherwise undefined. */
|
|
19
|
+
parseError?: string;
|
|
20
|
+
}
|
|
12
21
|
/**
|
|
13
|
-
* Read the persisted config file.
|
|
14
|
-
*
|
|
22
|
+
* Read the persisted config file.
|
|
23
|
+
*
|
|
24
|
+
* A corrupt config file still must never crash the CLI — the fallback to
|
|
25
|
+
* defaults is unchanged. What changed is that it is no longer SILENT: a single
|
|
26
|
+
* stray comma used to reset the model, theme, timeouts, skills and (as far as
|
|
27
|
+
* the user could tell) the API key, with nothing anywhere saying why. Now the
|
|
28
|
+
* failure travels out of band in `parseError`, and `loadConfig()` turns it into
|
|
29
|
+
* a log record plus one visible line. Hand-editing this file is a supported
|
|
30
|
+
* workflow, so the failure mode had to become visible.
|
|
15
31
|
*/
|
|
16
|
-
export declare function readConfigFile():
|
|
32
|
+
export declare function readConfigFile(): ConfigFileRead;
|
|
17
33
|
/**
|
|
18
34
|
* Atomically write the persisted config: write a temp file in the same
|
|
19
35
|
* directory, then rename over the target, then re-apply `0600` on POSIX.
|
|
@@ -39,6 +55,8 @@ export declare function loadPersistedConfig(): PersistedConfig;
|
|
|
39
55
|
*
|
|
40
56
|
* `SkillsConfig` is therefore capped at ONE level of nesting (scalars and
|
|
41
57
|
* string arrays only). A third level requires replacing this hand-written merge
|
|
42
|
-
* with a real deep-merge utility first, or the same bug returns.
|
|
58
|
+
* with a real deep-merge utility first, or the same bug returns. `LogConfig` is
|
|
59
|
+
* a third SECTION, but it is still one level deep (scalars only), so the same
|
|
60
|
+
* hand-written merge covers it — and it must stay that way for that reason.
|
|
43
61
|
*/
|
|
44
62
|
export declare function updatePersistedConfig(patch: Partial<PersistedConfig>): PersistedConfig;
|