@aragon-agent/cli 0.6.33 → 0.6.35

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 (76) hide show
  1. package/CHANGELOG.md +2305 -2242
  2. package/README.md +119 -18
  3. package/dist/agent/controller.d.ts +29 -0
  4. package/dist/agent/controller.js +42 -1
  5. package/dist/agent/kit-apply.d.ts +56 -0
  6. package/dist/agent/kit-apply.js +209 -0
  7. package/dist/agent/model-profile-settings.d.ts +28 -0
  8. package/dist/agent/model-profile-settings.js +142 -29
  9. package/dist/agent/reducer.d.ts +1 -1
  10. package/dist/commands/builtins.js +4 -0
  11. package/dist/commands/kit-command.d.ts +20 -0
  12. package/dist/commands/kit-command.js +131 -0
  13. package/dist/commands/perf.d.ts +10 -0
  14. package/dist/commands/perf.js +1 -1
  15. package/dist/config/kit-resolution.d.ts +93 -0
  16. package/dist/config/kit-resolution.js +226 -0
  17. package/dist/config/model-kits.d.ts +224 -0
  18. package/dist/config/model-kits.js +302 -0
  19. package/dist/config/model-profile-store.d.ts +25 -0
  20. package/dist/config/model-profiles.d.ts +41 -3
  21. package/dist/config/model-profiles.js +46 -5
  22. package/dist/config/schema.d.ts +16 -0
  23. package/dist/input/keymap.js +18 -4
  24. package/dist/ui/App.js +135 -15
  25. package/dist/ui/Header.d.ts +9 -1
  26. package/dist/ui/Header.js +8 -2
  27. package/dist/ui/PromptInput.js +1 -1
  28. package/dist/ui/ScrollbackShell.d.ts +38 -13
  29. package/dist/ui/ScrollbackShell.js +4 -3
  30. package/dist/ui/StatusBar.d.ts +13 -0
  31. package/dist/ui/StatusBar.js +10 -3
  32. package/dist/ui/TeamPanel.d.ts +12 -0
  33. package/dist/ui/TeamPanel.js +10 -31
  34. package/dist/ui/Transcript.js +22 -2
  35. package/dist/ui/entries/TeamCard.d.ts +18 -2
  36. package/dist/ui/entries/TeamCard.js +47 -13
  37. package/dist/ui/layout/header-hints.d.ts +11 -1
  38. package/dist/ui/layout/header-hints.js +35 -3
  39. package/dist/ui/layout/kit-chip.d.ts +50 -0
  40. package/dist/ui/layout/kit-chip.js +81 -0
  41. package/dist/ui/layout/virtual-window.js +9 -0
  42. package/dist/ui/overlays/HelpOverlay.js +7 -0
  43. package/dist/ui/overlays/KitEditor.d.ts +23 -0
  44. package/dist/ui/overlays/KitEditor.js +101 -0
  45. package/dist/ui/overlays/KitManager.d.ts +23 -0
  46. package/dist/ui/overlays/KitManager.js +104 -0
  47. package/dist/ui/overlays/KitPicker.d.ts +49 -0
  48. package/dist/ui/overlays/KitPicker.js +58 -0
  49. package/dist/ui/overlays/ModelProfilePicker.js +16 -6
  50. package/dist/ui/overlays/SettingsScreen.js +139 -16
  51. package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
  52. package/dist/ui/overlays/kit-picker-state.js +136 -0
  53. package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
  54. package/dist/ui/overlays/kit-settings-state.js +267 -0
  55. package/dist/ui/overlays/model-profile-state.d.ts +26 -3
  56. package/dist/ui/overlays/model-profile-state.js +59 -6
  57. package/dist/ui/scrollback/LiveTail.d.ts +44 -10
  58. package/dist/ui/scrollback/LiveTail.js +20 -4
  59. package/dist/ui/scrollback/limits.d.ts +16 -0
  60. package/dist/ui/scrollback/limits.js +16 -0
  61. package/dist/ui/scrollback/live-preview.js +10 -3
  62. package/dist/ui/scrollback/overflow-print.d.ts +108 -0
  63. package/dist/ui/scrollback/overflow-print.js +120 -0
  64. package/dist/ui/scrollback/renderer.d.ts +11 -1
  65. package/dist/ui/scrollback/renderer.js +53 -25
  66. package/dist/ui/scrollback/tail-anchor.d.ts +68 -0
  67. package/dist/ui/scrollback/tail-anchor.js +77 -0
  68. package/dist/ui/scrollback/tail-window.d.ts +21 -8
  69. package/dist/ui/scrollback/tail-window.js +26 -10
  70. package/dist/ui/scrollback/use-scrollback-view.d.ts +38 -10
  71. package/dist/ui/scrollback/use-scrollback-view.js +372 -85
  72. package/dist/ui/team-status.d.ts +141 -0
  73. package/dist/ui/team-status.js +224 -0
  74. package/dist/ui/transcript-text.js +13 -4
  75. package/dist/ui/use-wheel-routing.js +2 -2
  76. package/package.json +2 -2
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Where the ACTIVE kit comes from, and the two places a session kit has to be
3
+ * re-applied (agent-model-kits §4.3 / D-5 / D-6).
4
+ *
5
+ * ASCII ONLY - `src/config/**` is inside `glyphs.test.ts::inScope`.
6
+ *
7
+ * ONE TRUTH, PURELY DERIVED. `CliConfig` carries only `sessionKit: {id, held}`;
8
+ * everything a surface prints about "which kit is running" comes out of
9
+ * `resolveActiveKit(config)`. There is no `config.activeKit` field, because a
10
+ * second copy of that answer is a copy that can disagree with the first.
11
+ */
12
+ import { getLogger } from '../logging/logger.js';
13
+ import { normalizeProfileBaseUrl, projectModelProfiles, resolveModelProfileState } from './model-profile-resolution.js';
14
+ import { findKit, kitCandidate, kitSlotConnections } from './model-kits.js';
15
+ /** The three slots, in the order every surface prints them. */
16
+ export const ALL_SLOTS = ['main', 'fast', 'subagent'];
17
+ /** The name the UI gives "no kit is in effect" (D-13). */
18
+ export const CURRENT_SETUP_NAME = 'Current setup';
19
+ /**
20
+ * The answer when there is no kit, or when the host has no kit support at all.
21
+ *
22
+ * A CONSTANT SO CALLERS CANNOT INVENT A SECOND SHAPE: `App` needs one value that
23
+ * renders "no chip, `Current setup` row" for a controller that predates this
24
+ * port (a test double, an embedded host), and re-deriving it there would be a
25
+ * second definition of "nothing".
26
+ */
27
+ export const INACTIVE_KIT = { source: 'none', id: null,
28
+ name: CURRENT_SETUP_NAME, held: [], modified: false, driftReasons: [],
29
+ missingProfiles: [] };
30
+ function driftOfMain(config, connection) {
31
+ if (!connection)
32
+ return [];
33
+ const changed = [];
34
+ if (config.provider !== connection.provider)
35
+ changed.push('provider');
36
+ if (config.model !== connection.model)
37
+ changed.push('model');
38
+ if (normalizeProfileBaseUrl(config.baseUrl) !== normalizeProfileBaseUrl(connection.baseUrl)) {
39
+ changed.push('endpoint');
40
+ }
41
+ return changed.length ? [`main: ${changed.join(', ')} changed`] : [];
42
+ }
43
+ function driftOfFast(config, kit, connection) {
44
+ if (kit.fastProfileId === null) {
45
+ // D-8: this kit TURNS THE TIER OFF, so the endpoints are irrelevant - only
46
+ // "is it on" can disagree. Comparing endpoints here would report drift for
47
+ // every user who ever ran a fast tier before applying a fast-off kit.
48
+ return config.fast.enabled ? ['fast: turned back on'] : [];
49
+ }
50
+ if (!connection)
51
+ return [];
52
+ const changed = [];
53
+ if (!config.fast.enabled)
54
+ changed.push('off');
55
+ if (config.fast.provider !== connection.provider)
56
+ changed.push('provider');
57
+ if (config.fast.model !== connection.model)
58
+ changed.push('model');
59
+ if (normalizeProfileBaseUrl(config.fast.baseUrl)
60
+ !== normalizeProfileBaseUrl(connection.baseUrl))
61
+ changed.push('endpoint');
62
+ return changed.length ? [`fast: ${changed.join(', ')} changed`] : [];
63
+ }
64
+ function driftOfSubagent(config, kit, connection) {
65
+ if (!connection)
66
+ return [];
67
+ const sameMode = config.team?.model === kit.subagent.model;
68
+ const sameId = kit.subagent.model === 'profile'
69
+ ? (config.team?.modelProfileId ?? null) === kit.subagent.modelProfileId : true;
70
+ return sameMode && sameId ? [] : ['subagent: changed'];
71
+ }
72
+ function subagentConnection(config, kit) {
73
+ if (kit.subagent.model === 'profile')
74
+ return kit.subagent.modelProfileId;
75
+ return kit.subagent.model === 'main' ? 'main' : 'fast';
76
+ }
77
+ /** Build the verdict for a kit that exists, whatever installed it. */
78
+ function describe(config, library, kit, source, held) {
79
+ const connections = kitSlotConnections(library, kit);
80
+ const drift = { reasons: [], missing: [] };
81
+ if (held.includes('main')) {
82
+ if (!connections.main)
83
+ drift.missing.push('main');
84
+ else
85
+ drift.reasons.push(...driftOfMain(config, connections.main));
86
+ }
87
+ if (held.includes('fast')) {
88
+ if (kit.fastProfileId !== null && !connections.fast)
89
+ drift.missing.push('fast');
90
+ else
91
+ drift.reasons.push(...driftOfFast(config, kit, connections.fast));
92
+ }
93
+ if (held.includes('subagent')) {
94
+ const missingProfile = kit.subagent.model === 'profile' && !connections.subagent.name;
95
+ if (missingProfile)
96
+ drift.missing.push('subagent');
97
+ else
98
+ drift.reasons.push(...driftOfSubagent(config, kit, subagentConnection(config, kit)));
99
+ }
100
+ if (held.length < ALL_SLOTS.length)
101
+ drift.reasons.push(`holds ${held.join('+') || 'nothing'}`);
102
+ return { source, id: kit.id, name: kit.name, held: [...held],
103
+ modified: held.length < ALL_SLOTS.length || drift.reasons.length > 0
104
+ || drift.missing.length > 0,
105
+ driftReasons: drift.reasons, missingProfiles: drift.missing };
106
+ }
107
+ /**
108
+ * Which kit is in effect, and whether it has drifted (§4.8).
109
+ *
110
+ * Precedence is D-6's: session kit, then the persisted default, then the disk
111
+ * bindings (which the UI calls `Current setup`).
112
+ */
113
+ export function resolveActiveKit(config) {
114
+ const library = config.modelProfiles;
115
+ const state = config.sessionKit;
116
+ const session = state ? findKit(library, state.id) : undefined;
117
+ if (state && session) {
118
+ const held = state.held.filter((slot) => ALL_SLOTS.includes(slot));
119
+ return describe(config, library, session, 'session', held.length ? held : []);
120
+ }
121
+ const fallback = findKit(library, library?.defaultKitId ?? null);
122
+ if (library && fallback)
123
+ return describe(config, library, fallback, 'default', [...ALL_SLOTS]);
124
+ return { source: 'none', id: null, name: CURRENT_SETUP_NAME, held: [], modified: false,
125
+ driftReasons: [], missingProfiles: [] };
126
+ }
127
+ function slotSurvives(library, kit, slot) {
128
+ const ids = new Set(library.entries.map((entry) => entry.id));
129
+ if (slot === 'main')
130
+ return ids.has(kit.mainProfileId);
131
+ if (slot === 'fast')
132
+ return kit.fastProfileId === null || ids.has(kit.fastProfileId);
133
+ return kit.subagent.model !== 'profile'
134
+ || (!!kit.subagent.modelProfileId && ids.has(kit.subagent.modelProfileId));
135
+ }
136
+ /**
137
+ * Re-apply the still-`held` slots of a session kit onto a candidate.
138
+ *
139
+ * THE RETURN VALUE MAY ONLY EVER FEED `liveCandidate`. It must NEVER be handed
140
+ * to `commitModelSettings` (I-14 / RV-P0-1): "this session only" is a claim
141
+ * about the FILE, and a held slot that reaches the commit turns one unrelated
142
+ * save into a permanent, invisible change of the user's defaults. The one
143
+ * caller is `prepareModelSettings` in `agent/model-profile-settings.ts`, which
144
+ * keeps the two candidates in separate variables for exactly this reason.
145
+ *
146
+ * Slots whose kit, or whose referenced profile, disappeared are DROPPED and
147
+ * reported, so the caller can log the release instead of silently forgetting it.
148
+ */
149
+ export function applyHeldToLive(candidate, sessionKit) {
150
+ const heldIn = sessionKit?.held ?? [];
151
+ if (!sessionKit || heldIn.length === 0)
152
+ return { candidate, dropped: [] };
153
+ const library = candidate.modelProfiles;
154
+ const kit = findKit(library, sessionKit.id);
155
+ if (!library || !kit)
156
+ return { candidate, dropped: [...heldIn] };
157
+ const held = heldIn.filter((slot) => slotSurvives(library, kit, slot));
158
+ const dropped = heldIn.filter((slot) => !held.includes(slot));
159
+ if (!held.length)
160
+ return { candidate, dropped };
161
+ const patch = kitCandidate({ library, kit });
162
+ const next = {
163
+ ...candidate,
164
+ modelProfiles: { ...library,
165
+ ...(held.includes('main') ? { mainId: kit.mainProfileId } : {}),
166
+ ...(held.includes('fast') ? { fastId: kit.fastProfileId } : {}) },
167
+ };
168
+ if (held.includes('subagent')) {
169
+ next.team = { ...candidate.team, ...patch.team };
170
+ }
171
+ if (held.includes('fast')) {
172
+ next.fast = { ...candidate.fast, enabled: kit.fastProfileId !== null };
173
+ }
174
+ return { candidate: next, dropped };
175
+ }
176
+ /**
177
+ * Keep a session kit alive across `Reload settings` (D-6).
178
+ *
179
+ * THE PROJECTION HAS TO BE RECOMPUTED, not just the bindings: `I-3` says a
180
+ * library's projections never depend on whether a kit exists, so a live config
181
+ * whose `mainId` says "kit" while `provider`/`model` still name the reloaded
182
+ * file would break the equivalence every other surface relies on. Hence the
183
+ * round trip through `projectModelProfiles` + `resolveModelProfileState`.
184
+ */
185
+ export function carrySessionKit(input) {
186
+ const state = input.previous.sessionKit;
187
+ const { resolved } = input;
188
+ if (!state)
189
+ return resolved;
190
+ const library = resolved.modelProfiles;
191
+ const kit = findKit(library, state.id);
192
+ if (!library || !kit) {
193
+ getLogger().warn('config', 'session_kit_released', { reason: 'kit_missing' });
194
+ return { ...resolved, sessionKit: undefined };
195
+ }
196
+ const applied = applyHeldToLive(resolved, state);
197
+ const held = state.held.filter((slot) => !applied.dropped.includes(slot));
198
+ if (!held.length) {
199
+ getLogger().warn('config', 'session_kit_released', { reason: 'slots_dropped' });
200
+ return { ...resolved, sessionKit: undefined };
201
+ }
202
+ const file = applied.candidate;
203
+ const projected = projectModelProfiles(file).file;
204
+ const next = { ...resolved, modelProfiles: projected.modelProfiles };
205
+ if (held.includes('main')) {
206
+ next.provider = projected.provider;
207
+ next.model = projected.model;
208
+ next.baseUrl = projected.baseUrl ?? undefined;
209
+ if (next.contextWindowOrigin !== 'env') {
210
+ next.contextWindow = projected.contextWindow ?? null;
211
+ next.contextWindowOrigin = next.contextWindow === null ? undefined : 'profile';
212
+ }
213
+ }
214
+ if (held.includes('fast') && projected.fast) {
215
+ next.fast = { ...next.fast, provider: projected.fast.provider ?? '',
216
+ model: projected.fast.model ?? '', baseUrl: projected.fast.baseUrl ?? '' };
217
+ }
218
+ if (held.includes('subagent') && file.team)
219
+ next.team = { ...next.team, ...file.team };
220
+ next.modelProfileState = resolveModelProfileState(next);
221
+ next.sessionKit = { id: kit.id, held };
222
+ if (applied.dropped.length) {
223
+ getLogger().warn('config', 'session_kit_slots_dropped', { slots: applied.dropped });
224
+ }
225
+ return next;
226
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Agent model kits: a named preset that pins the THREE model roles
3
+ * (main / fast / subagent) to profiles that already live in the library
4
+ * (agent-model-kits D-1 / D-2).
5
+ *
6
+ * ASCII ONLY - `src/config/**` is inside `glyphs.test.ts::inScope`.
7
+ *
8
+ * A KIT STORES REFERENCES, NEVER COPIES (D-1): every slot is a profile id, so
9
+ * editing the profile changes the meaning of every kit that names it, which is
10
+ * exactly the promise a "preset" makes. That is also why deleting a profile has
11
+ * to delete the kits that reference it (D-9) instead of leaving a preset nobody
12
+ * can repair from the UI.
13
+ *
14
+ * EVERY CROSS-LAYER IMPORT HERE IS `import type` (RV-P2-5): `schema.ts` names
15
+ * `model-profiles.ts` through an inline `import('./model-profiles.js')` type
16
+ * query, so `model-profiles -> model-kits -> schema` is already a TYPE cycle. A
17
+ * value import (`DEFAULT_FAST_CONFIG`, `clampTeamConfig`) would turn it into a
18
+ * runtime cycle, and vitest surfaces that as an empty module at import time.
19
+ */
20
+ import type { CliConfig, FastConfig, TeamConfig } from './schema.js';
21
+ import type { ModelProfilesConfig, ProfileValidationIssue } from './model-profiles.js';
22
+ import type { ModelSettingsSaveResult } from './model-profile-store.js';
23
+ export type KitSlot = 'main' | 'fast' | 'subagent';
24
+ /**
25
+ * The session kit's runtime shape (D-5): the kit plus the slots it still owns.
26
+ *
27
+ * `held` is the ONLY mutable part. It shrinks when a save explicitly decides a
28
+ * slot (§4.3 / D-6), so "the user changed subagent, so subagent is theirs now"
29
+ * needs no second bookkeeping structure.
30
+ */
31
+ export interface SessionKitState {
32
+ id: string;
33
+ held: KitSlot[];
34
+ }
35
+ /**
36
+ * The kit's subagent slot.
37
+ *
38
+ * `model` is typed with `TeamConfig['model']` rather than by importing
39
+ * `team/model.ts`: `team/model.ts` already imports `config/`, so the reverse
40
+ * edge would be a cycle. `import type` keeps even this one erased.
41
+ */
42
+ export interface AgentKitSubagent {
43
+ model: TeamConfig['model'];
44
+ /** Non-null IF AND ONLY IF `model === 'profile'` (I-2, guaranteed by normalize). */
45
+ modelProfileId: string | null;
46
+ }
47
+ export interface AgentKit {
48
+ id: string;
49
+ /** 1..48 code points, no control characters. Names MAY repeat (§6.6). */
50
+ name: string;
51
+ /** Required (D-8). A dangling reference is NOT a validation error - see I-8. */
52
+ mainProfileId: string;
53
+ /** `null` = this kit turns the fast tier OFF for its session (D-8). */
54
+ fastProfileId: string | null;
55
+ subagent: AgentKitSubagent;
56
+ }
57
+ export declare const KIT_LIMITS: {
58
+ /**
59
+ * Structural ceiling. The EFFECTIVE ceiling is lower and implicit: every kit
60
+ * references at least one profile, so 200 entries cap the reachable kits
61
+ * (RV-P2-7) - recorded, not cross-validated, because the two numbers answer
62
+ * different questions.
63
+ */
64
+ readonly maxKits: 50;
65
+ readonly nameMaxChars: 48;
66
+ readonly maxSlots: 3;
67
+ };
68
+ /**
69
+ * STRUCTURAL ONLY (RV-P1-1). Reference existence is a RESOLUTION-time concern,
70
+ * exactly as it is for `team.modelProfileId` (`schema.ts` clampTeamConfig):
71
+ * `createModelProfileState` blanks the whole settings page when validation
72
+ * fails, so promoting "a hand-edited file left one dangling id" into "the page
73
+ * and `/team model` are both dead" is a wildly disproportionate blast radius.
74
+ *
75
+ * `duplicate_name` is deliberately NOT a code: two kits may share a name
76
+ * (§6.6), and `/kit use` disambiguates with an `#id` prefix.
77
+ */
78
+ export interface KitIssue {
79
+ path: string;
80
+ code: 'invalid' | 'duplicate_id';
81
+ message: string;
82
+ }
83
+ /** One resolved slot; `null` on the slot itself means the entry is gone. */
84
+ export interface KitConnection {
85
+ provider: string;
86
+ model: string;
87
+ baseUrl: string | null;
88
+ name: string;
89
+ }
90
+ export interface KitSlotConnections {
91
+ /** `null` = the referenced entry is gone; render `Profile missing`, never throw. */
92
+ main: KitConnection | null;
93
+ fast: KitConnection | null;
94
+ subagent: {
95
+ /**
96
+ * The SLOT MODE (`main` / `fast` / `profile`). Named `mode` rather than
97
+ * `model` because the resolved connection below carries a model ID of its
98
+ * own, and two `model` keys in one object literal is a TypeScript error
99
+ * (recorded as an implementation-time fix in the spec's issues section).
100
+ */
101
+ mode: TeamConfig['model'];
102
+ provider: string | null;
103
+ model: string | null;
104
+ baseUrl: string | null;
105
+ name: string | null;
106
+ };
107
+ }
108
+ /**
109
+ * A consequence of applying a kit that the user MUST be told about (§4.2).
110
+ *
111
+ * Not one of these is a failure: applying a kit is allowed to turn the fast tier
112
+ * on (which is real money - `DEFAULT_FAST_CONFIG.review` is `true`), to leave a
113
+ * profile without a usable key, or to name a tier this kit just switched off.
114
+ * The rule is the one every other subsystem in this package follows: no silent
115
+ * decisions, especially about spend.
116
+ */
117
+ export type KitNote = 'fast_tier_enabled' | 'fast_review_enabled' | 'fast_tier_off' | 'restart_required' | 'main_key_missing' | 'subagent_key_missing' | 'subagent_fast_unavailable';
118
+ /**
119
+ * What a session-kit action did, or why it did nothing.
120
+ *
121
+ * THE CODE SET IS THE SAVE SET PLUS TWO (RV-P1-3): every code the reused
122
+ * prepare/commit chain can produce is spelled here, so `kitApplyMessage` has no
123
+ * gap to fall through. A missing message would be rendered by a non-null
124
+ * assertion as the literal string `undefined`.
125
+ */
126
+ export interface KitApplyResult {
127
+ ok: boolean;
128
+ code?: ModelSettingsSaveResult['code'] | 'kit_missing' | 'kit_references_missing_profile';
129
+ /** `clearSessionKit` only: the session is back on the config file. */
130
+ released?: boolean;
131
+ notes: KitNote[];
132
+ /** Set when `kit_references_missing_profile` refused the apply (I-8). */
133
+ issues?: ProfileValidationIssue[];
134
+ }
135
+ export interface KitsNormalization {
136
+ kits: AgentKit[];
137
+ /** Self-healed: a pointer at a kit that did not survive becomes `null`. */
138
+ defaultKitId: string | null;
139
+ /** Forward migration marker (D-3). Never anything but 1. */
140
+ kitsVersion: 1;
141
+ /** Entries thrown away because they were structurally unsalvageable. */
142
+ dropped: number;
143
+ }
144
+ export declare const SUBAGENT_MODES: readonly ["main", "fast", "profile"];
145
+ /** The one mapping from a kit to a persisted-config patch (§4.2). */
146
+ export interface KitCandidate {
147
+ modelProfiles: ModelProfilesConfig;
148
+ team: Partial<TeamConfig>;
149
+ fast: Partial<FastConfig>;
150
+ }
151
+ /**
152
+ * Validate the `kits` array STRUCTURALLY (RV-P1-1).
153
+ *
154
+ * Returns `[]` for `undefined`/`null`/`[]` so a library written by a pre-KIT
155
+ * build keeps validating - the whole point of D-3's additive migration.
156
+ */
157
+ export declare function validateKits(value: unknown): KitIssue[];
158
+ /**
159
+ * White-list normalization of `kits` + `defaultKitId` (F-3 / R-2).
160
+ *
161
+ * TOTAL: no input throws. `normalizeModelProfiles` calls this on whatever the
162
+ * file holds, and a throw there would take the whole library - including the
163
+ * user's credentials - down with one malformed kit.
164
+ *
165
+ * `defaultKitRef` is a SEPARATE argument because `defaultKitId` is a sibling of
166
+ * `kits` in the library, not a member of the array; keeping it here is what
167
+ * lets the pointer self-heal against the kits that actually survived.
168
+ */
169
+ export declare function normalizeKits(kitsValue: unknown, defaultKitRef?: unknown): KitsNormalization;
170
+ /** Create a kit with a stable UUID, normalized like `createModelProfile`. */
171
+ export declare function createAgentKit(input: Omit<AgentKit, 'id'>): AgentKit;
172
+ /** Copy a kit under a free ` (copy)` name - the manager's `Duplicate` action. */
173
+ export declare function duplicateAgentKit(kit: AgentKit, existing: readonly AgentKit[]): AgentKit;
174
+ /**
175
+ * Every kit a reference text may mean.
176
+ *
177
+ * ONE parser for `ctrl+K`, `/kit use` and the settings manager, so "what did I
178
+ * just type" has a single answer. `ref` is an exact id, an `#id` prefix, or an
179
+ * exact name; an ambiguous reference returns more than one hit and the caller
180
+ * reports it rather than silently taking the first (§4.9).
181
+ */
182
+ export declare function kitMatches(library: ModelProfilesConfig | undefined, ref: string | null | undefined): AgentKit[];
183
+ /** Resolve one kit, or `undefined` when the reference is missing or ambiguous. */
184
+ export declare function findKit(library: ModelProfilesConfig | undefined, ref: string | null | undefined): AgentKit | undefined;
185
+ /**
186
+ * THE ONE mapping from a kit to a persisted-config patch (§4.2).
187
+ *
188
+ * Both consumption paths - "set as default" (writes the result) and `ctrl+K`
189
+ * (never writes it) - call this, so a default kit and a session kit produce
190
+ * structurally identical configurations. There is no second interpretation.
191
+ *
192
+ * `defaultKitId` is only touched when the caller PASSES it (it is
193
+ * `undefined` for a session kit), which is what keeps `ctrl+K` from moving the
194
+ * persisted pointer.
195
+ */
196
+ export declare function kitCandidate(input: {
197
+ library: ModelProfilesConfig;
198
+ kit: AgentKit;
199
+ defaultKitId?: string | null;
200
+ }): KitCandidate;
201
+ /**
202
+ * Dereference the three slots for every surface that prints them (picker,
203
+ * management page, `/kit list`).
204
+ *
205
+ * A MISSING ENTRY RETURNS `null` INSTEAD OF THROWING (RV-P1-1 / I-8): the UI
206
+ * renders `Profile missing`, the library stays readable, and only APPLYING the
207
+ * kit is refused, with a stable code.
208
+ */
209
+ export declare function kitSlotConnections(library: ModelProfilesConfig | undefined, kit: AgentKit): KitSlotConnections;
210
+ /**
211
+ * Pre-fill the first kit from what this machine already runs (§4.10 / D-13).
212
+ *
213
+ * `mainProfileId` comes from the caller because CREATING a profile is UI work:
214
+ * `createCustomProfile` lives in `ui/overlays/model-profile-state.ts`, and a
215
+ * `config/` module importing `ui/` would invert the dependency direction this
216
+ * package keeps everywhere else. The caller creates (or reuses) the entry and
217
+ * hands its id in; when it is absent the kit is still built, because a
218
+ * half-filled editor is a better first form than an empty one.
219
+ */
220
+ export declare function createKitFromCurrent(input: {
221
+ config: CliConfig;
222
+ mainProfileId?: string | null;
223
+ name?: string;
224
+ }): AgentKit;