@aragon-agent/cli 0.6.34 → 0.6.36
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 +21 -0
- package/README.md +173 -7
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +67 -3
- package/dist/agent/controller.js +106 -5
- package/dist/agent/headless.js +31 -0
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- package/dist/agent/reducer.d.ts +13 -2
- package/dist/agent/reducer.js +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/agents-command.d.ts +15 -0
- package/dist/commands/agents-command.js +27 -0
- package/dist/commands/builtins.js +135 -4
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/commands/perf.d.ts +11 -0
- package/dist/commands/perf.js +2 -1
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/app-paths.d.ts +15 -0
- package/dist/config/app-paths.js +33 -2
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +28 -2
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.js +39 -0
- package/dist/config/store.js +12 -1
- package/dist/config/ui-state.d.ts +18 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +25 -0
- package/dist/exec/events.js +4 -0
- package/dist/exec/index.js +16 -1
- package/dist/exec/runner.js +10 -0
- package/dist/input/keymap.js +18 -4
- package/dist/proc/limits.d.ts +16 -0
- package/dist/proc/limits.js +16 -0
- package/dist/proc/supervisor.d.ts +20 -1
- package/dist/proc/supervisor.js +42 -14
- package/dist/prompts/command.d.ts +34 -0
- package/dist/prompts/command.js +237 -0
- package/dist/prompts/copy.d.ts +89 -0
- package/dist/prompts/copy.js +131 -0
- package/dist/prompts/fill.d.ts +51 -0
- package/dist/prompts/fill.js +46 -0
- package/dist/prompts/limits.d.ts +53 -0
- package/dist/prompts/limits.js +53 -0
- package/dist/prompts/normalize.d.ts +53 -0
- package/dist/prompts/normalize.js +190 -0
- package/dist/prompts/resolve.d.ts +31 -0
- package/dist/prompts/resolve.js +60 -0
- package/dist/prompts/search.d.ts +58 -0
- package/dist/prompts/search.js +146 -0
- package/dist/prompts/store.d.ts +86 -0
- package/dist/prompts/store.js +453 -0
- package/dist/prompts/types.d.ts +204 -0
- package/dist/prompts/types.js +13 -0
- package/dist/session/checkpoint.d.ts +8 -1
- package/dist/session/checkpoint.js +6 -0
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +13 -1
- package/dist/session/startup-session.d.ts +7 -2
- package/dist/session/startup-session.js +5 -2
- package/dist/session/store.js +3 -0
- package/dist/session/validate-session.js +64 -1
- package/dist/team/limits.d.ts +25 -0
- package/dist/team/limits.js +26 -0
- package/dist/team/runtime.js +27 -2
- package/dist/team/settled.d.ts +57 -0
- package/dist/team/settled.js +87 -0
- package/dist/team/subagent.js +48 -2
- package/dist/team/timeline.d.ts +74 -0
- package/dist/team/timeline.js +101 -0
- package/dist/team/types.d.ts +24 -1
- package/dist/todo/acceptance.d.ts +120 -0
- package/dist/todo/acceptance.js +399 -0
- package/dist/todo/follow-through.d.ts +48 -23
- package/dist/todo/follow-through.js +110 -44
- package/dist/todo/limits.d.ts +62 -1
- package/dist/todo/limits.js +62 -1
- package/dist/todo/panel-rows.d.ts +29 -39
- package/dist/todo/panel-rows.js +37 -83
- package/dist/todo/plan-sections.d.ts +60 -0
- package/dist/todo/plan-sections.js +108 -0
- package/dist/todo/prompt.d.ts +17 -12
- package/dist/todo/prompt.js +59 -40
- package/dist/todo/row-window.d.ts +51 -0
- package/dist/todo/row-window.js +107 -0
- package/dist/todo/store.d.ts +139 -31
- package/dist/todo/store.js +271 -73
- package/dist/todo/todo-tool.d.ts +33 -8
- package/dist/todo/todo-tool.js +186 -30
- package/dist/todo/types.d.ts +58 -0
- package/dist/ui/App.js +414 -54
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +17 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +22 -2
- package/dist/ui/ScrollbackShell.d.ts +22 -7
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +18 -0
- package/dist/ui/StatusBar.js +11 -3
- package/dist/ui/TeamPanel.d.ts +35 -41
- package/dist/ui/TeamPanel.js +53 -163
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +23 -3
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/entries/TodoCard.d.ts +26 -11
- package/dist/ui/entries/TodoCard.js +56 -15
- package/dist/ui/glyphs.d.ts +19 -0
- package/dist/ui/glyphs.js +4 -0
- package/dist/ui/layout/agents-board.d.ts +102 -0
- package/dist/ui/layout/agents-board.js +389 -0
- package/dist/ui/layout/cells.d.ts +61 -0
- package/dist/ui/layout/cells.js +156 -0
- package/dist/ui/layout/header-hints.d.ts +23 -3
- package/dist/ui/layout/header-hints.js +73 -7
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/status-detail-layout.js +9 -0
- package/dist/ui/layout/status-layout.d.ts +13 -0
- package/dist/ui/layout/team-row.d.ts +139 -0
- package/dist/ui/layout/team-row.js +246 -0
- package/dist/ui/layout/todo-layout.d.ts +38 -0
- package/dist/ui/layout/todo-layout.js +75 -0
- package/dist/ui/layout/virtual-window.js +32 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +24 -2
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
- package/dist/ui/overlays/PromptEditorPage.js +227 -0
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
- package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
- package/dist/ui/overlays/PromptPicker.d.ts +36 -0
- package/dist/ui/overlays/PromptPicker.js +129 -0
- package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
- package/dist/ui/overlays/PromptTextArea.js +141 -0
- package/dist/ui/overlays/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
- package/dist/ui/overlays/prompt-library-state.js +335 -0
- package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
- package/dist/ui/overlays/prompt-picker-state.js +98 -0
- package/dist/ui/scrollback/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/tail-window.d.ts +12 -0
- package/dist/ui/scrollback/tail-window.js +17 -2
- package/dist/ui/scrollback/use-scrollback-view.js +7 -0
- package/dist/ui/team-status.d.ts +166 -0
- package/dist/ui/team-status.js +252 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-startup-notices.d.ts +35 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.js +10 -2
- package/package.json +2 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings screen's kit pages: manager rows, the editor draft, and the ONE
|
|
3
|
+
* reducer that turns intents into draft mutations (agent-model-kits §4.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* TWO REDUCERS, ONE PAGE AXIS. `modelProfileReducer` owns the profile pages and
|
|
8
|
+
* `kitReducer` owns kit data; both return the same `ModelProfileState`, so the
|
|
9
|
+
* screen still has exactly one state object and one Save button. The alternative
|
|
10
|
+
* - a second state machine inside `SettingsScreen.tsx`, which is already over the
|
|
11
|
+
* 1000-line cap - is what this module exists to avoid.
|
|
12
|
+
*
|
|
13
|
+
* NOTHING HERE WRITES ANYTHING: every action mutates the transaction draft and
|
|
14
|
+
* sets `dirty`. `Save` stays the only writer (F-7/F-10).
|
|
15
|
+
*/
|
|
16
|
+
import { KIT_LIMITS, createAgentKit, duplicateAgentKit, findKit, kitCandidate, kitSlotConnections } from '../../config/model-kits.js';
|
|
17
|
+
import { resolveActiveKit } from '../../config/kit-resolution.js';
|
|
18
|
+
import { emptyModelProfiles } from '../../config/model-profiles.js';
|
|
19
|
+
/** The library the transaction would save, with a read error falling back to empty. */
|
|
20
|
+
export function draftLibrary(state) {
|
|
21
|
+
return state.draft.profiles ?? emptyModelProfiles();
|
|
22
|
+
}
|
|
23
|
+
function kitsOf(state) { return draftLibrary(state).kits ?? []; }
|
|
24
|
+
export function findKitInState(state, id) {
|
|
25
|
+
return id ? findKit(draftLibrary(state), id) : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Open the editor on an existing kit, or on a PREFILLED new one (§4.10).
|
|
29
|
+
*
|
|
30
|
+
* THE FIRST KIT IS NEVER A BLANK FORM (D-13): it starts from the three bindings
|
|
31
|
+
* this machine already runs, so "create a kit" is one rename and one Save. When
|
|
32
|
+
* no Main profile is bound the kit starts with an empty main slot and Save
|
|
33
|
+
* refuses - the picker's own `Create profile` action is where a profile is born,
|
|
34
|
+
* which keeps this module free of the profile-creating code path.
|
|
35
|
+
*/
|
|
36
|
+
export function openKitDraft(state, id) {
|
|
37
|
+
const existing = findKitInState(state, id);
|
|
38
|
+
if (existing) {
|
|
39
|
+
return { id: existing.id, name: existing.name, mainProfileId: existing.mainProfileId,
|
|
40
|
+
fastProfileId: existing.fastProfileId,
|
|
41
|
+
subagent: { ...existing.subagent }, slot: null };
|
|
42
|
+
}
|
|
43
|
+
const library = draftLibrary(state);
|
|
44
|
+
const team = state.draft.baseline.team;
|
|
45
|
+
return {
|
|
46
|
+
id: null,
|
|
47
|
+
name: `Kit ${kitsOf(state).length + 1}`,
|
|
48
|
+
mainProfileId: library.mainId ?? '',
|
|
49
|
+
// `fastId` alone is not enough: a tier that is switched OFF is off, and
|
|
50
|
+
// prefilling its endpoint would silently turn it back on at the first Save.
|
|
51
|
+
fastProfileId: state.draft.baseline.fast.enabled ? library.fastId ?? null : null,
|
|
52
|
+
subagent: { model: team.model, modelProfileId: team.modelProfileId ?? null },
|
|
53
|
+
slot: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function withDraft(state, kitDraft) {
|
|
57
|
+
return { ...state, kitDraft, error: undefined };
|
|
58
|
+
}
|
|
59
|
+
function saveKitDraft(state) {
|
|
60
|
+
const kitDraft = state.kitDraft;
|
|
61
|
+
if (!kitDraft)
|
|
62
|
+
return state;
|
|
63
|
+
const name = [...kitDraft.name.trim()];
|
|
64
|
+
if (!name.length) {
|
|
65
|
+
return withDraft(state, { ...kitDraft, error: 'Name: enter a name for this kit.',
|
|
66
|
+
field: 'name' });
|
|
67
|
+
}
|
|
68
|
+
if (!kitDraft.mainProfileId) {
|
|
69
|
+
return withDraft(state, { ...kitDraft, error: 'Main profile: choose a profile.',
|
|
70
|
+
field: 'main' });
|
|
71
|
+
}
|
|
72
|
+
if (kitDraft.subagent.model === 'profile' && !kitDraft.subagent.modelProfileId) {
|
|
73
|
+
return withDraft(state, { ...kitDraft, error: 'Subagent: choose a profile.',
|
|
74
|
+
field: 'subagent' });
|
|
75
|
+
}
|
|
76
|
+
const library = draftLibrary(state);
|
|
77
|
+
const kits = library.kits ?? [];
|
|
78
|
+
const fields = { name: name.slice(0, KIT_LIMITS.nameMaxChars).join(''),
|
|
79
|
+
mainProfileId: kitDraft.mainProfileId, fastProfileId: kitDraft.fastProfileId,
|
|
80
|
+
subagent: { ...kitDraft.subagent } };
|
|
81
|
+
let merged;
|
|
82
|
+
if (kitDraft.id) {
|
|
83
|
+
const existing = kits.find((kit) => kit.id === kitDraft.id);
|
|
84
|
+
if (!existing)
|
|
85
|
+
return { ...state, page: 'kits', kitDraft: undefined };
|
|
86
|
+
merged = kits.map((kit) => (kit.id === kitDraft.id ? { ...existing, ...fields } : kit));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (kits.length >= KIT_LIMITS.maxKits) {
|
|
90
|
+
return withDraft(state, { ...kitDraft,
|
|
91
|
+
error: `Maximum ${KIT_LIMITS.maxKits} kits. Delete an unused kit first.` });
|
|
92
|
+
}
|
|
93
|
+
merged = [...kits, createAgentKit(fields)];
|
|
94
|
+
}
|
|
95
|
+
return { ...state, page: 'kits', kitDraft: undefined, dirty: true,
|
|
96
|
+
draft: { ...state.draft, profiles: { ...library, kits: merged } } };
|
|
97
|
+
}
|
|
98
|
+
/** Exit A: the kit's three slots become the draft's bindings (§4.2). */
|
|
99
|
+
function applyKitToDraft(state, id, asDefault) {
|
|
100
|
+
const library = draftLibrary(state);
|
|
101
|
+
const kit = findKit(library, id);
|
|
102
|
+
if (!kit)
|
|
103
|
+
return state;
|
|
104
|
+
const patch = kitCandidate({ library, kit, ...(asDefault ? { defaultKitId: kit.id } : {}) });
|
|
105
|
+
return { ...state, dirty: true, page: 'kits', kitDraft: undefined,
|
|
106
|
+
draft: { ...state.draft,
|
|
107
|
+
profiles: patch.modelProfiles,
|
|
108
|
+
patch: { ...state.draft.patch, team: patch.team,
|
|
109
|
+
fast: { ...state.draft.patch.fast, ...patch.fast } },
|
|
110
|
+
// I-13: a kit that turns the tier off must NOT project the fast role.
|
|
111
|
+
activateRoles: [...new Set([...state.draft.activateRoles,
|
|
112
|
+
'main', ...(kit.fastProfileId === null ? [] : ['fast'])])],
|
|
113
|
+
// The transaction now decides all three slots, so the session kit is done
|
|
114
|
+
// (§4.3 row 4). Leaving it held would make the save ambiguous.
|
|
115
|
+
sessionKit: null } };
|
|
116
|
+
}
|
|
117
|
+
function removeKit(state, id) {
|
|
118
|
+
const library = draftLibrary(state);
|
|
119
|
+
const remaining = (library.kits ?? []).filter((kit) => kit.id !== id);
|
|
120
|
+
const released = state.draft.sessionKit?.id === id ? null : state.draft.sessionKit;
|
|
121
|
+
return { ...state, page: 'kits', kitDraft: undefined, dirty: true,
|
|
122
|
+
draft: { ...state.draft, sessionKit: released,
|
|
123
|
+
profiles: { ...library, kits: remaining,
|
|
124
|
+
defaultKitId: (library.defaultKitId ?? null) === id ? null : library.defaultKitId } } };
|
|
125
|
+
}
|
|
126
|
+
/** Every kit-page intent, applied to the ONE draft. */
|
|
127
|
+
export function kitReducer(state, action) {
|
|
128
|
+
const kitDraft = state.kitDraft;
|
|
129
|
+
switch (action.type) {
|
|
130
|
+
case 'kit-open':
|
|
131
|
+
return { ...state, page: 'kit-edit', error: undefined,
|
|
132
|
+
kitDraft: openKitDraft(state, action.id) };
|
|
133
|
+
case 'kit-name':
|
|
134
|
+
if (!kitDraft)
|
|
135
|
+
return state;
|
|
136
|
+
// Control characters are stripped at the boundary rather than refused: a
|
|
137
|
+
// pasted name with a stray newline should become a name, not an error.
|
|
138
|
+
return withDraft(state, { ...kitDraft,
|
|
139
|
+
name: [...action.value.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ')]
|
|
140
|
+
.slice(0, KIT_LIMITS.nameMaxChars).join(''), error: undefined });
|
|
141
|
+
case 'kit-slot':
|
|
142
|
+
return kitDraft ? { ...state, page: 'kit-slot',
|
|
143
|
+
kitDraft: { ...kitDraft, slot: action.slot } } : state;
|
|
144
|
+
case 'kit-main':
|
|
145
|
+
return kitDraft ? { ...state, page: 'kit-edit',
|
|
146
|
+
kitDraft: { ...kitDraft, mainProfileId: action.id ?? '', slot: null, error: undefined } }
|
|
147
|
+
: state;
|
|
148
|
+
case 'kit-fast':
|
|
149
|
+
return kitDraft ? { ...state, page: 'kit-edit',
|
|
150
|
+
kitDraft: { ...kitDraft, fastProfileId: action.id, slot: null, error: undefined } } : state;
|
|
151
|
+
case 'kit-subagent':
|
|
152
|
+
return kitDraft ? { ...state, page: 'kit-edit', kitDraft: { ...kitDraft,
|
|
153
|
+
subagent: { model: action.model, modelProfileId: action.modelProfileId },
|
|
154
|
+
slot: null, error: undefined } } : state;
|
|
155
|
+
case 'kit-save': return saveKitDraft(state);
|
|
156
|
+
case 'kit-cancel': return { ...state, page: 'kits', kitDraft: undefined, error: undefined };
|
|
157
|
+
case 'kit-duplicate': {
|
|
158
|
+
const library = draftLibrary(state);
|
|
159
|
+
const original = findKit(library, action.id);
|
|
160
|
+
if (!original || (library.kits ?? []).length >= KIT_LIMITS.maxKits)
|
|
161
|
+
return state;
|
|
162
|
+
return { ...state, page: 'kits', dirty: true, draft: { ...state.draft,
|
|
163
|
+
profiles: { ...library,
|
|
164
|
+
kits: [...(library.kits ?? []), duplicateAgentKit(original, library.kits ?? [])] } } };
|
|
165
|
+
}
|
|
166
|
+
case 'kit-delete': return { ...state, page: 'kit-delete',
|
|
167
|
+
kitDraft: { ...(kitDraft ?? openKitDraft(state, action.id)), id: action.id } };
|
|
168
|
+
case 'kit-confirm-delete': return kitDraft?.id ? removeKit(state, kitDraft.id) : state;
|
|
169
|
+
case 'kit-apply': return applyKitToDraft(state, action.id, false);
|
|
170
|
+
case 'kit-default': return applyKitToDraft(state, action.id, true);
|
|
171
|
+
default: return state;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function shortConnection(connection) {
|
|
175
|
+
return connection ? `${connection.provider}:${connection.model}` : 'Profile missing';
|
|
176
|
+
}
|
|
177
|
+
function subagentSummary(connections, kit) {
|
|
178
|
+
if (kit.subagent.model === 'main')
|
|
179
|
+
return 'same as main';
|
|
180
|
+
if (kit.subagent.model === 'fast')
|
|
181
|
+
return 'fast tier';
|
|
182
|
+
return connections.subagent.name ? `profile ${connections.subagent.name}` : 'Profile missing';
|
|
183
|
+
}
|
|
184
|
+
/** Rows in library order, each with the markers the picker also uses (§4.5). */
|
|
185
|
+
export function kitManagerRows(state) {
|
|
186
|
+
const library = draftLibrary(state);
|
|
187
|
+
return (library.kits ?? []).map((kit) => {
|
|
188
|
+
const connections = kitSlotConnections(library, kit);
|
|
189
|
+
const isDefault = (library.defaultKitId ?? null) === kit.id;
|
|
190
|
+
const inUse = state.draft.sessionKit?.id === kit.id;
|
|
191
|
+
const markers = [isDefault ? '[Default]' : '', inUse ? '[In use]' : '']
|
|
192
|
+
.filter(Boolean).join(' ');
|
|
193
|
+
const fast = kit.fastProfileId === null ? 'off' : shortConnection(connections.fast);
|
|
194
|
+
return {
|
|
195
|
+
id: kit.id,
|
|
196
|
+
label: `${markers ? `${markers} ` : ''}${kit.name}`,
|
|
197
|
+
summary: [shortConnection(connections.main), fast, subagentSummary(connections, kit)]
|
|
198
|
+
.join(' | '),
|
|
199
|
+
isDefault,
|
|
200
|
+
inUse,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Whether the kit this transaction opened on had already drifted (§4.8).
|
|
206
|
+
*
|
|
207
|
+
* READ FROM `liveBaseline`, i.e. the config the screen was opened over, because
|
|
208
|
+
* drift is a fact about the SESSION - the draft cannot change a connection
|
|
209
|
+
* without releasing the slot, so there is nothing newer to look at.
|
|
210
|
+
*/
|
|
211
|
+
function liveDrift(state, session) {
|
|
212
|
+
// THE DRAFT'S `held`, NOT `liveBaseline.sessionKit`: the transaction may already
|
|
213
|
+
// have given slots back, and the row has to describe THIS transaction.
|
|
214
|
+
const live = resolveActiveKit({ ...state.draft.liveBaseline, sessionKit: session });
|
|
215
|
+
return live.source === 'session' && live.modified;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The `Kit pack` root row's five states (§4.5 table, AC-27).
|
|
219
|
+
*
|
|
220
|
+
* READ FROM THE DRAFT, not from the live config: the row sits inside a
|
|
221
|
+
* transaction, and showing the saved state under a pending selection is how a
|
|
222
|
+
* settings screen lies to the user about what `Save` is going to do.
|
|
223
|
+
*/
|
|
224
|
+
export function kitPackRow(state) {
|
|
225
|
+
const library = draftLibrary(state);
|
|
226
|
+
const kits = library.kits ?? [];
|
|
227
|
+
const session = state.draft.sessionKit;
|
|
228
|
+
const sessionKit = session ? findKit(library, session.id) : undefined;
|
|
229
|
+
const defaultKit = findKit(library, library.defaultKitId ?? null);
|
|
230
|
+
const defaultClause = defaultKit ? ` (default: ${defaultKit.name})` : '';
|
|
231
|
+
if (sessionKit && session) {
|
|
232
|
+
const partial = session.held.length < 3;
|
|
233
|
+
const held = partial ? `: ${session.held.join('+')}` : '';
|
|
234
|
+
// THE SAME `~` THE CHIP SHOWS (D-12): dropping a slot is only ONE way to
|
|
235
|
+
// drift. The other is a connection that `/model`, `/fast` or `/team model`
|
|
236
|
+
// rewrote after the kit was applied, and a row that stayed silent about that
|
|
237
|
+
// would disagree with the status bar about the same session.
|
|
238
|
+
const drift = liveDrift(state, session) ? '~' : '';
|
|
239
|
+
return `${sessionKit.name} [session${held}]${drift}${defaultClause}`;
|
|
240
|
+
}
|
|
241
|
+
if (defaultKit)
|
|
242
|
+
return `${defaultKit.name} [default]`;
|
|
243
|
+
return kits.length ? 'Current setup' : 'Current setup (no kit yet)';
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The held slots' EFFECTIVE values (RV-P0-2).
|
|
247
|
+
*
|
|
248
|
+
* WITHOUT THIS the root page shows the file's bindings while the header shows the
|
|
249
|
+
* kit's, and the only conclusion available to the user is that one of them is
|
|
250
|
+
* broken. `Enter` on any of these rows writes an explicit choice, which releases
|
|
251
|
+
* that slot - so "what you see is what you change" stays true.
|
|
252
|
+
*/
|
|
253
|
+
export function heldSlots(state) {
|
|
254
|
+
const session = state.draft.sessionKit;
|
|
255
|
+
const kit = session ? findKitInState(state, session.id) : undefined;
|
|
256
|
+
if (!session || !kit) {
|
|
257
|
+
return { kitName: null, held: [], mainId: null, fastId: null, subagent: null };
|
|
258
|
+
}
|
|
259
|
+
const held = session.held;
|
|
260
|
+
return {
|
|
261
|
+
kitName: kit.name,
|
|
262
|
+
held,
|
|
263
|
+
mainId: held.includes('main') ? kit.mainProfileId : null,
|
|
264
|
+
fastId: held.includes('fast') ? kit.fastProfileId : null,
|
|
265
|
+
subagent: held.includes('subagent') ? { ...kit.subagent } : null,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type ModelProfile, type ModelProfilesConfig, type ModelRole } from '../../config/model-profiles.js';
|
|
2
|
+
import type { AgentKit } from '../../config/model-kits.js';
|
|
2
3
|
import type { ModelSettingsDraft } from '../../config/model-profile-store.js';
|
|
3
4
|
import type { SubagentModelMode } from '../../team/model.js';
|
|
4
|
-
|
|
5
|
+
import type { KitDraft } from './kit-settings-state.js';
|
|
6
|
+
export type ProfilePage = 'root' | 'picker' | 'manager' | 'editor' | 'models' | 'delete-confirm' | 'discard-confirm' | 'kits' | 'kit-edit' | 'kit-slot' | 'kit-delete';
|
|
5
7
|
/**
|
|
6
8
|
* Which role a PROFILE PICKER page is choosing for.
|
|
7
9
|
*
|
|
@@ -9,8 +11,13 @@ export type ProfilePage = 'root' | 'picker' | 'manager' | 'editor' | 'models' |
|
|
|
9
11
|
* `'main' | 'fast'` because it is the type basis of the config layer's
|
|
10
12
|
* `${role}Id` template keys - widening it would invent a `subagentId` library
|
|
11
13
|
* key nothing reads. The subagent picker writes `draft.patch.team` instead.
|
|
14
|
+
*
|
|
15
|
+
* `'binding'` IS A KIT SLOT CHOOSER (agent-model-kits §4.5): a plain profile
|
|
16
|
+
* list with no `Current custom` row, no built-in rows and one `Create profile`
|
|
17
|
+
* action. A kit slot can only ever name a profile, so offering "no profile" as a
|
|
18
|
+
* choice there would be offering an invalid kit.
|
|
12
19
|
*/
|
|
13
|
-
export type ProfilePickerRole = ModelRole | 'subagent';
|
|
20
|
+
export type ProfilePickerRole = ModelRole | 'subagent' | 'binding';
|
|
14
21
|
/** The picker's built-in `Fast tier model` row; never a profile id on disk. */
|
|
15
22
|
export declare const SUBAGENT_FAST_ROW_ID = "__fast__";
|
|
16
23
|
export interface ProfileEditorDraft {
|
|
@@ -30,13 +37,27 @@ export interface ModelProfileState {
|
|
|
30
37
|
page: ProfilePage;
|
|
31
38
|
role: ProfilePickerRole;
|
|
32
39
|
editor?: ProfileEditorDraft;
|
|
33
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Where the editor and the delete confirmation hand control back.
|
|
42
|
+
*
|
|
43
|
+
* `'kit-slot'` JOINS `'picker'` (agent-model-kits §4.5): the kit slot chooser
|
|
44
|
+
* is a profile picker too, and its `Create profile` action must return to the
|
|
45
|
+
* slot it was creating for rather than to the profile manager.
|
|
46
|
+
*/
|
|
47
|
+
returnPage: 'picker' | 'manager' | 'kit-slot';
|
|
34
48
|
bindNew: boolean;
|
|
35
49
|
dirty: boolean;
|
|
36
50
|
error?: string;
|
|
37
51
|
deleteId?: string;
|
|
38
52
|
/** Who referenced the entry being deleted ('Main + Subagent', or ''). */
|
|
39
53
|
deleteUsers?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The kit editor's own draft (agent-model-kits §4.5), owned by `kitReducer`.
|
|
56
|
+
*
|
|
57
|
+
* ONE FIELD, NOT A SECOND STATE OBJECT: the page axis stays shared, so `Save`
|
|
58
|
+
* and `Esc` mean the same thing on every page of this screen.
|
|
59
|
+
*/
|
|
60
|
+
kitDraft?: KitDraft;
|
|
40
61
|
}
|
|
41
62
|
export type ModelProfileAction = {
|
|
42
63
|
type: 'page';
|
|
@@ -90,6 +111,8 @@ export declare function finishProfileEditor(editor: ProfileEditorDraft): {
|
|
|
90
111
|
};
|
|
91
112
|
/** Make an isolated root draft; opening the screen never mutates live config. */
|
|
92
113
|
export declare function createModelProfileState(draft: ModelSettingsDraft): ModelProfileState;
|
|
114
|
+
/** The kits whose three slots name `id`, for the delete confirmation (D-9). */
|
|
115
|
+
export declare function kitsUsingProfile(profiles: ModelProfilesConfig | undefined, id: string): AgentKit[];
|
|
93
116
|
/** Describe references using text so ASCII and no-color modes remain usable. */
|
|
94
117
|
export declare function profileUsers(profiles: ModelProfilesConfig | undefined, id: string, subagentProfileId?: string | null): string;
|
|
95
118
|
/**
|
|
@@ -41,10 +41,38 @@ export function createModelProfileState(draft) {
|
|
|
41
41
|
return { draft: copy, page: 'root', role: 'main', returnPage: 'manager',
|
|
42
42
|
bindNew: false, dirty: false };
|
|
43
43
|
}
|
|
44
|
+
/** The kits whose three slots name `id`, for the delete confirmation (D-9). */
|
|
45
|
+
export function kitsUsingProfile(profiles, id) {
|
|
46
|
+
return (profiles?.kits ?? []).filter((kit) => kit.mainProfileId === id
|
|
47
|
+
|| kit.fastProfileId === id
|
|
48
|
+
|| (kit.subagent.model === 'profile' && kit.subagent.modelProfileId === id));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Drop `slot` from the session kit (§4.3 row 2 / AC-38).
|
|
52
|
+
*
|
|
53
|
+
* THE DRAFT IS NARROWED HERE, NOT ONLY AT SAVE TIME. `releasedSlots` already
|
|
54
|
+
* strips the same slot from the transaction before it commits, but the ROOT PAGE
|
|
55
|
+
* paints the kit's effective value for every HELD slot - so without this line a
|
|
56
|
+
* user picks a profile and the row keeps showing the one they just replaced,
|
|
57
|
+
* which is the most confusing thing a settings screen can do. Releasing the last
|
|
58
|
+
* slot ends the kit: `held: []` is not a state any surface can render.
|
|
59
|
+
*/
|
|
60
|
+
function releaseHeldSlot(draft, slot) {
|
|
61
|
+
const current = draft.sessionKit;
|
|
62
|
+
if (!current || !current.held.includes(slot))
|
|
63
|
+
return draft;
|
|
64
|
+
const held = current.held.filter((item) => item !== slot);
|
|
65
|
+
return { ...draft, sessionKit: held.length ? { id: current.id, held } : null };
|
|
66
|
+
}
|
|
44
67
|
/** Describe references using text so ASCII and no-color modes remain usable. */
|
|
45
68
|
export function profileUsers(profiles, id, subagentProfileId) {
|
|
69
|
+
// THE KITS ARE REFERENCES TOO (D-9 / RV-P2-6): "Main + kit "Deep work"" is what
|
|
70
|
+
// makes the confirmation honest, because deleting this entry also deletes that
|
|
71
|
+
// kit. The empty case must stay empty - a leading ` + ` is what a naive
|
|
72
|
+
// `join` after an unconditional push produces.
|
|
46
73
|
return [profiles?.mainId === id ? 'Main' : '', profiles?.fastId === id ? 'Fast' : '',
|
|
47
|
-
subagentProfileId === id ? 'Subagent' : ''
|
|
74
|
+
subagentProfileId === id ? 'Subagent' : '',
|
|
75
|
+
...kitsUsingProfile(profiles, id).map((kit) => `kit "${kit.name}"`)]
|
|
48
76
|
.filter(Boolean).join(' + ');
|
|
49
77
|
}
|
|
50
78
|
/**
|
|
@@ -155,14 +183,27 @@ export function modelProfileReducer(state, action) {
|
|
|
155
183
|
? { model: 'fast', modelProfileId: null }
|
|
156
184
|
: { model: 'profile', modelProfileId: action.id };
|
|
157
185
|
return { ...state, page: 'root', dirty: true, error: undefined,
|
|
158
|
-
draft: { ...state.draft,
|
|
186
|
+
draft: releaseHeldSlot({ ...state.draft,
|
|
187
|
+
patch: { ...state.draft.patch, team } }, 'subagent') };
|
|
159
188
|
}
|
|
189
|
+
// `'binding'` NEVER REACHES THIS REDUCER: a kit slot's choice is applied
|
|
190
|
+
// by `kitReducer` (`kit-main` / `kit-fast`), because the value belongs to
|
|
191
|
+
// an editor draft rather than to a `${role}Id` library key. Returning the
|
|
192
|
+
// state unchanged is the honest answer to an impossible action.
|
|
193
|
+
if (action.role === 'binding')
|
|
194
|
+
return state;
|
|
160
195
|
return { ...state, page: 'root', dirty: true, error: undefined,
|
|
161
|
-
draft: { ...state.draft,
|
|
162
|
-
|
|
196
|
+
draft: releaseHeldSlot({ ...state.draft,
|
|
197
|
+
profiles: { ...library, [`${action.role}Id`]: action.id },
|
|
198
|
+
activateRoles: [...new Set([...state.draft.activateRoles, action.role])] }, action.role) };
|
|
163
199
|
}
|
|
164
200
|
case 'edit': return { ...state, editor: createProfileEditor(action.profile), page: 'editor',
|
|
165
|
-
|
|
201
|
+
// A PROFILE BORN IN A KIT SLOT RETURNS TO THAT SLOT (§4.5): the picker's
|
|
202
|
+
// `Create profile` action is the only way to invent one and it is offered on
|
|
203
|
+
// the kit's slot chooser - landing in the profile MANAGER there would
|
|
204
|
+
// abandon the half-built kit behind a page the user never asked for.
|
|
205
|
+
returnPage: state.page === 'picker' || state.page === 'kit-slot'
|
|
206
|
+
? state.page : 'manager',
|
|
166
207
|
bindNew: action.bindNew ?? false, error: undefined };
|
|
167
208
|
case 'editor-field': return editField(state, action);
|
|
168
209
|
case 'retain-key': return state.editor?.original.apiKey ? { ...state,
|
|
@@ -206,17 +247,29 @@ export function modelProfileReducer(state, action) {
|
|
|
206
247
|
// when it named THIS entry. `removeModelProfile`'s throw stays as the
|
|
207
248
|
// bottom-line defense - the UI has already unbound, so it cannot fire
|
|
208
249
|
// on this path.
|
|
250
|
+
//
|
|
251
|
+
// AND THE KITS GO WITH IT (agent-model-kits D-9 / I-8): a kit naming a
|
|
252
|
+
// deleted profile can neither be applied nor repaired from any screen, so
|
|
253
|
+
// leaving it behind would be leaving a permanent error. The confirmation
|
|
254
|
+
// page has already named them, so this is the action the user agreed to.
|
|
255
|
+
const doomed = kitsUsingProfile(library, id);
|
|
256
|
+
const defaultKitKept = doomed.some((kit) => kit.id === (library.defaultKitId ?? null))
|
|
257
|
+
? null : library.defaultKitId;
|
|
258
|
+
const sessionReleased = doomed.some((kit) => kit.id === state.draft.sessionKit?.id)
|
|
259
|
+
? null : state.draft.sessionKit;
|
|
209
260
|
const profiles = {
|
|
210
261
|
...library,
|
|
211
262
|
mainId: library.mainId === id ? null : library.mainId,
|
|
212
263
|
fastId: library.fastId === id ? null : library.fastId,
|
|
213
264
|
entries: library.entries.filter((entry) => entry.id !== id),
|
|
265
|
+
kits: (library.kits ?? []).filter((kit) => !doomed.includes(kit)),
|
|
266
|
+
defaultKitId: defaultKitKept,
|
|
214
267
|
};
|
|
215
268
|
const teamReset = draftSubagentProfileId(state.draft) === id
|
|
216
269
|
? { team: { model: 'main', modelProfileId: null } }
|
|
217
270
|
: {};
|
|
218
271
|
return { ...state, page: 'manager', dirty: true,
|
|
219
|
-
draft: { ...state.draft, profiles,
|
|
272
|
+
draft: { ...state.draft, profiles, sessionKit: sessionReleased,
|
|
220
273
|
patch: { ...state.draft.patch, ...teamReset } },
|
|
221
274
|
deleteId: undefined, deleteUsers: undefined,
|
|
222
275
|
// No orphan editor for a deleted entry (3.7).
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prompt manager's state machine and view models (quick-prompt-library §4.4).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`. Every sentence the
|
|
5
|
+
* user reads comes from `prompts/copy.ts`; nothing in this file may hold a literal
|
|
6
|
+
* non-ASCII byte.
|
|
7
|
+
*
|
|
8
|
+
* WHY A PURE REDUCER AND NOT `useState` INSIDE THE COMPONENT: the page stack has two
|
|
9
|
+
* transitions that are easy to get wrong and impossible to see on screen - "the
|
|
10
|
+
* second Esc discards, the first does not" and "the intent is consumed exactly
|
|
11
|
+
* once" - and both are assertions, not screenshots. Everything below can be driven
|
|
12
|
+
* from a test with no Ink mounted.
|
|
13
|
+
*/
|
|
14
|
+
import type { PromptDraftInput, PromptDraftVerdict, PromptEditorDraft, PromptLibrary, PromptLibraryEntry, PromptWriteError, QuickPrompt } from '../../prompts/types.js';
|
|
15
|
+
export type PromptLibraryPage = 'list' | 'editor' | 'delete';
|
|
16
|
+
/** The six rows `Tab` cycles on the editor page, in visual order. */
|
|
17
|
+
export declare const PROMPT_EDITOR_FIELDS: readonly ["title", "content", "description", "tags", "save", "cancel"];
|
|
18
|
+
export type PromptEditorField = (typeof PROMPT_EDITOR_FIELDS)[number];
|
|
19
|
+
export interface PromptLibraryState {
|
|
20
|
+
page: PromptLibraryPage;
|
|
21
|
+
/** Focus index on the list page (clamped by the view). */
|
|
22
|
+
index: number;
|
|
23
|
+
/**
|
|
24
|
+
* Id the focus should land on when the list is next drawn.
|
|
25
|
+
*
|
|
26
|
+
* BY ID, NEVER BY POSITION: a create appends and an edit does not move the entry,
|
|
27
|
+
* but a delete shifts every row after it, and "focus row 3" would then point at a
|
|
28
|
+
* different prompt than the one the user was working on.
|
|
29
|
+
*/
|
|
30
|
+
focusId: string | null;
|
|
31
|
+
draft: PromptEditorDraft;
|
|
32
|
+
/** The entry the draft was seeded from, so `dirty` is a comparison, not a guess. */
|
|
33
|
+
baseline: PromptEditorDraft | null;
|
|
34
|
+
/** The first Esc on a dirty editor arms the discard; any edit disarms it. */
|
|
35
|
+
escArmed: boolean;
|
|
36
|
+
pendingDeleteId: string | null;
|
|
37
|
+
error: PromptWriteError | null;
|
|
38
|
+
/** The row that should own focus after a refusal (D-12). */
|
|
39
|
+
errorField: PromptEditorField | null;
|
|
40
|
+
/** Highest intent nonce this overlay has acted on (I-8). */
|
|
41
|
+
consumedIntentNonce: number;
|
|
42
|
+
}
|
|
43
|
+
export type PromptLibraryAction = {
|
|
44
|
+
type: 'init';
|
|
45
|
+
entry: PromptLibraryEntry;
|
|
46
|
+
library: PromptLibrary;
|
|
47
|
+
nonce: number;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'list';
|
|
50
|
+
focusId?: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: 'focus';
|
|
53
|
+
index: number;
|
|
54
|
+
} | {
|
|
55
|
+
type: 'create';
|
|
56
|
+
seed?: {
|
|
57
|
+
title?: string;
|
|
58
|
+
content?: string;
|
|
59
|
+
};
|
|
60
|
+
} | {
|
|
61
|
+
type: 'edit';
|
|
62
|
+
id: string;
|
|
63
|
+
library: PromptLibrary;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'delete';
|
|
66
|
+
id: string;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'draft';
|
|
69
|
+
patch: Partial<PromptEditorDraft>;
|
|
70
|
+
} | {
|
|
71
|
+
type: 'commit';
|
|
72
|
+
id: string | null;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'failed';
|
|
75
|
+
code: PromptWriteError;
|
|
76
|
+
field?: PromptEditorField;
|
|
77
|
+
} | {
|
|
78
|
+
type: 'esc';
|
|
79
|
+
} | {
|
|
80
|
+
type: 'cancelled';
|
|
81
|
+
};
|
|
82
|
+
/** A new, empty draft. `id: null` is what makes `Save` a create rather than an update. */
|
|
83
|
+
export declare const EMPTY_PROMPT_DRAFT: PromptEditorDraft;
|
|
84
|
+
export declare const INITIAL_PROMPT_LIBRARY_STATE: PromptLibraryState;
|
|
85
|
+
/** The editable projection of a stored entry. */
|
|
86
|
+
export declare function draftFromEntry(entry: QuickPrompt): PromptEditorDraft;
|
|
87
|
+
/** A create draft seeded from a command (`/prompts add title :: body`). */
|
|
88
|
+
export declare function seededDraft(seed?: {
|
|
89
|
+
title?: string;
|
|
90
|
+
content?: string;
|
|
91
|
+
}): PromptEditorDraft;
|
|
92
|
+
/** Structural equality is enough: every field is a string or a boolean. */
|
|
93
|
+
export declare function draftIsDirty(draft: PromptEditorDraft, baseline: PromptEditorDraft | null): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Validate a draft and turn it into what the store accepts (D-12).
|
|
96
|
+
*
|
|
97
|
+
* THE VALIDATOR IS `store.ts`'S, not a second copy here: the sentence the editor
|
|
98
|
+
* shows and the rule the write applies have to be the same rule, or a user can be
|
|
99
|
+
* told a title is fine by the form and refused by the store.
|
|
100
|
+
*/
|
|
101
|
+
export declare function finishPromptEditor(draft: PromptEditorDraft): PromptDraftVerdict;
|
|
102
|
+
/** Index of the first focusable row, for a refusal that named a field. */
|
|
103
|
+
export declare function fieldIndex(field: PromptEditorField | null): number;
|
|
104
|
+
/**
|
|
105
|
+
* The reducer.
|
|
106
|
+
*
|
|
107
|
+
* `init` is the ONLY action that can jump straight to the editor or the delete page:
|
|
108
|
+
* that is what makes `/prompts edit 2` land where it should (D-21). The intent's
|
|
109
|
+
* nonce is stored as `consumedIntentNonce` by the component before dispatching, so a
|
|
110
|
+
* replayed intent is a no-op at the component, not a second edit here.
|
|
111
|
+
*/
|
|
112
|
+
export declare function promptLibraryReducer(state: PromptLibraryState, action: PromptLibraryAction, library: PromptLibrary): PromptLibraryState;
|
|
113
|
+
export interface PromptLibraryRow {
|
|
114
|
+
id: string;
|
|
115
|
+
/** One-line render text: the title plus its markers. The caret is the component's. */
|
|
116
|
+
label: string;
|
|
117
|
+
markers: {
|
|
118
|
+
favorite: boolean;
|
|
119
|
+
hasTags: boolean;
|
|
120
|
+
uses: number;
|
|
121
|
+
};
|
|
122
|
+
/** Body length in code points; the detail block and the delete page share it. */
|
|
123
|
+
chars: number;
|
|
124
|
+
}
|
|
125
|
+
/** `1.2k` for anything a fixed-width column cannot show in full. */
|
|
126
|
+
export declare function formatCount(count: number): string;
|
|
127
|
+
/** `3d ago` / `2h ago` / `just now`. Deliberately coarse: it is a hint, not a clock. */
|
|
128
|
+
export declare function formatAgo(timestamp: number | undefined, now: number): string;
|
|
129
|
+
/**
|
|
130
|
+
* The list, one row per entry, in the library's own order.
|
|
131
|
+
*
|
|
132
|
+
* DUPLICATE TITLES GET AN ID PREFIX (I-2), exactly as `kit-picker-state.ts` does for
|
|
133
|
+
* duplicate kit names: two rows reading the same words make `Enter` a coin flip.
|
|
134
|
+
*/
|
|
135
|
+
export declare function libraryRows(library: PromptLibrary): PromptLibraryRow[];
|
|
136
|
+
/**
|
|
137
|
+
* The detail block under the list: at most `detailMaxLines` rows, fixed shape.
|
|
138
|
+
*
|
|
139
|
+
* FIXED, because `OverlayFrame`'s window is shared with the list (D-16): a body that
|
|
140
|
+
* participated in row height would squeeze six prompts into two visible rows. The
|
|
141
|
+
* full text has exactly one home, and it is the editor.
|
|
142
|
+
*/
|
|
143
|
+
export declare function libraryDetail(row: PromptLibraryRow | undefined, library: PromptLibrary, cols: number, now?: number): string[];
|
|
144
|
+
/**
|
|
145
|
+
* The list page's footer.
|
|
146
|
+
*
|
|
147
|
+
* THE LAST CLAUSE IS ALWAYS THE WAY OUT (§9.5): a narrow terminal clips from the
|
|
148
|
+
* left, so the reader always keeps `Esc close`. The reorder keys are named here
|
|
149
|
+
* rather than in the row above it because they are the one affordance a user cannot
|
|
150
|
+
* discover by pressing a key they already know.
|
|
151
|
+
*/
|
|
152
|
+
export declare function libraryHint(state: PromptLibraryState, count: number): string;
|
|
153
|
+
/** The delete page's four lines: what it is, how big, and what it costs. */
|
|
154
|
+
export declare function deleteDetail(library: PromptLibrary, id: string | null): string[];
|
|
155
|
+
/**
|
|
156
|
+
* The draft as the store accepts it.
|
|
157
|
+
*
|
|
158
|
+
* THE TAGS FIELD IS PARSED HERE, ONCE, and nowhere else: the editor edits a raw
|
|
159
|
+
* string, the store validates a `string[]`, and a second parser between them is how
|
|
160
|
+
* `a,b` and `a b` come to mean different things depending on which surface saved it.
|
|
161
|
+
*/
|
|
162
|
+
export declare function draftToInput(draft: PromptEditorDraft): PromptDraftInput;
|