@aiwayds/dsh-tui-pi 0.26.0 → 0.27.0

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.
@@ -0,0 +1,291 @@
1
+ /**
2
+ * User model profiles — named snapshots of the whole model configuration
3
+ * (default model + think level, plus per-subagent model/thinking) that can
4
+ * be saved, reviewed and applied in one step, so switching between contexts
5
+ * (work ↔ personal) is a single command instead of a /model + per-agent
6
+ * /agents tour.
7
+ *
8
+ * Storage: `$DSH_HOME/model-profiles.json` — a small JSON document the TUI
9
+ * owns outright (same model as tui-command-usage.json, src/usage.ts). Reads
10
+ * never throw: a missing, corrupt or wrong-version file degrades to the
11
+ * seeded default document (work / personal / other, empty until configured).
12
+ * Writes are atomic (tmp sibling + rename) and whole-document
13
+ * last-write-wins — the accepted loss model for a human-paced UI feature
14
+ * (same as settings.yaml).
15
+ *
16
+ * Snapshot semantics — the part to keep straight:
17
+ * - `profile.agents[name]` PRESENT → applying writes that agent's
18
+ * frontmatter model/thinking to exactly the recorded values (absent keys
19
+ * CLEAR the line — explicit inherit).
20
+ * - `profile.agents[name]` ABSENT → applying leaves the agent untouched
21
+ * (an agent file created after the last save — the profile never knew it).
22
+ * - "Save current" records EVERY discovered agent, inherit ones as empty
23
+ * entries, so a round-trip switch restores inherit where inherit was.
24
+ */
25
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
26
+ import { dirname, join } from 'node:path';
27
+ import { dshHome } from "./append-system.js";
28
+ /** On-disk schema version; bump only for breaking changes. */
29
+ export const MODEL_PROFILES_VERSION = 1;
30
+ /** Profiles seeded into a fresh document (and re-seeded when the stored one is empty). */
31
+ export const DEFAULT_PROFILE_NAMES = ['work', 'personal', 'other'];
32
+ /** `$DSH_HOME/model-profiles.json` (or an explicit home override). */
33
+ export function modelProfilesPath(home = dshHome()) {
34
+ return join(home, 'model-profiles.json');
35
+ }
36
+ /** Narrow an unknown JSON value into a route; anything else is dropped. */
37
+ function narrowRoute(value) {
38
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
39
+ return undefined;
40
+ const { provider, model, reasoningEffort } = value;
41
+ if (typeof provider !== 'string' || provider === '')
42
+ return undefined;
43
+ if (typeof model !== 'string' || model === '')
44
+ return undefined;
45
+ const route = { provider, model };
46
+ if (typeof reasoningEffort === 'string' && reasoningEffort !== '')
47
+ route.reasoningEffort = reasoningEffort;
48
+ return route;
49
+ }
50
+ /** Narrow one agents-map value; `undefined` drops the entry, `{}` keeps it. */
51
+ function narrowEntry(value) {
52
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
53
+ return undefined;
54
+ const { model, thinking } = value;
55
+ const entry = {};
56
+ if (typeof model === 'string' && model !== '')
57
+ entry.model = model;
58
+ if (typeof thinking === 'string' && thinking !== '')
59
+ entry.thinking = thinking;
60
+ return entry;
61
+ }
62
+ /** Narrow one profile; a missing/invalid name drops the whole profile. */
63
+ function narrowProfile(value) {
64
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
65
+ return undefined;
66
+ const { name, defaultModel, agents } = value;
67
+ if (typeof name !== 'string' || name.trim() === '')
68
+ return undefined;
69
+ const profile = { name: name.trim(), agents: {} };
70
+ const route = narrowRoute(defaultModel);
71
+ if (route !== undefined)
72
+ profile.defaultModel = route;
73
+ if (agents !== null && typeof agents === 'object' && !Array.isArray(agents)) {
74
+ for (const [agentName, entry] of Object.entries(agents)) {
75
+ const key = agentName.trim();
76
+ if (key === '')
77
+ continue;
78
+ const narrowed = narrowEntry(entry);
79
+ if (narrowed !== undefined)
80
+ profile.agents[key] = narrowed;
81
+ }
82
+ }
83
+ return profile;
84
+ }
85
+ /** A fresh document with the three seeded, unconfigured default profiles. */
86
+ export function seedModelProfilesDoc() {
87
+ return { profiles: DEFAULT_PROFILE_NAMES.map(name => ({ name, agents: {} })) };
88
+ }
89
+ /**
90
+ * Validate an unknown parsed document. A wrong version, a non-object or an
91
+ * empty/invalid profiles array degrades to the seeded defaults — the store
92
+ * is self-healing, never fatal. Duplicate names (case-insensitive) keep the
93
+ * first occurrence; a `current` pointing at a dropped profile is unset.
94
+ */
95
+ export function normalizeModelProfiles(raw) {
96
+ const doc = seedModelProfilesDoc();
97
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
98
+ return doc;
99
+ const { version, current, profiles } = raw;
100
+ if (version !== MODEL_PROFILES_VERSION)
101
+ return doc;
102
+ const narrowed = [];
103
+ const seen = new Set();
104
+ if (Array.isArray(profiles)) {
105
+ for (const value of profiles) {
106
+ const profile = narrowProfile(value);
107
+ if (profile === undefined)
108
+ continue;
109
+ const key = profile.name.toLowerCase();
110
+ if (seen.has(key))
111
+ continue;
112
+ seen.add(key);
113
+ narrowed.push(profile);
114
+ }
115
+ }
116
+ if (narrowed.length === 0)
117
+ return doc;
118
+ doc.profiles = narrowed;
119
+ if (typeof current === 'string' && narrowed.some(profile => profile.name === current)) {
120
+ doc.current = current;
121
+ }
122
+ return doc;
123
+ }
124
+ /** Read + validate the store; any failure degrades to the seeded document. */
125
+ export function loadModelProfiles(path) {
126
+ let text;
127
+ try {
128
+ text = readFileSync(path, 'utf8');
129
+ }
130
+ catch {
131
+ return seedModelProfilesDoc();
132
+ }
133
+ try {
134
+ return normalizeModelProfiles(JSON.parse(text));
135
+ }
136
+ catch {
137
+ return seedModelProfilesDoc();
138
+ }
139
+ }
140
+ /**
141
+ * Atomically persist the document (tmp sibling + rename). Creates the
142
+ * directory when missing. Resolves an error message on failure, or
143
+ * `undefined` on success — callers flash it, never throw.
144
+ */
145
+ export function saveModelProfiles(path, doc) {
146
+ const body = JSON.stringify({
147
+ version: MODEL_PROFILES_VERSION,
148
+ current: doc.current,
149
+ profiles: doc.profiles,
150
+ });
151
+ const dir = dirname(path);
152
+ const base = path.split('/').pop() ?? 'model-profiles.json';
153
+ const tmp = join(dir, `.${base}.tmp-${process.pid}`);
154
+ try {
155
+ mkdirSync(dir, { recursive: true });
156
+ writeFileSync(tmp, body);
157
+ renameSync(tmp, path);
158
+ return undefined;
159
+ }
160
+ catch (error) {
161
+ try {
162
+ rmSync(tmp, { force: true });
163
+ }
164
+ catch { /* best-effort cleanup of the failed tmp sibling */ }
165
+ return error instanceof Error ? error.message : String(error);
166
+ }
167
+ }
168
+ /** Find a profile by (case-insensitive) name. */
169
+ export function findProfile(doc, name) {
170
+ const needle = name.trim().toLowerCase();
171
+ return doc.profiles.find(profile => profile.name.toLowerCase() === needle);
172
+ }
173
+ /** Trim one candidate profile name; empty (or whitespace-only) is invalid. */
174
+ export function normalizeProfileName(name) {
175
+ const trimmed = name.trim();
176
+ return trimmed === '' ? undefined : trimmed;
177
+ }
178
+ /** Create a new empty profile; the name must be valid and not taken. */
179
+ export function createProfile(doc, rawName) {
180
+ const name = normalizeProfileName(rawName);
181
+ if (name === undefined)
182
+ return { error: 'profile name must not be empty' };
183
+ if (findProfile(doc, name) !== undefined)
184
+ return { error: `a profile named "${name}" already exists` };
185
+ const profile = { name, agents: {} };
186
+ doc.profiles.push(profile);
187
+ return { profile };
188
+ }
189
+ /** Rename a profile in place, keeping `current` consistent. Error or undefined. */
190
+ export function renameProfile(doc, from, to) {
191
+ const profile = findProfile(doc, from);
192
+ if (profile === undefined)
193
+ return `no profile named "${from}"`;
194
+ const name = normalizeProfileName(to);
195
+ if (name === undefined)
196
+ return 'profile name must not be empty';
197
+ if (name.toLowerCase() !== profile.name.toLowerCase() && findProfile(doc, name) !== undefined) {
198
+ return `a profile named "${name}" already exists`;
199
+ }
200
+ if (doc.current === profile.name)
201
+ doc.current = name;
202
+ profile.name = name;
203
+ return undefined;
204
+ }
205
+ /** Delete a profile; the last remaining one is refused (the store never goes empty). */
206
+ export function deleteProfile(doc, name) {
207
+ const index = doc.profiles.findIndex(profile => profile.name.toLowerCase() === name.trim().toLowerCase());
208
+ if (index < 0)
209
+ return `no profile named "${name}"`;
210
+ if (doc.profiles.length <= 1)
211
+ return 'the last profile cannot be deleted';
212
+ const [removed] = doc.profiles.splice(index, 1);
213
+ if (doc.current === removed.name)
214
+ doc.current = undefined;
215
+ return undefined;
216
+ }
217
+ /**
218
+ * Record every discovered agent's model/thinking into a fresh agents map —
219
+ * the "save current" capture. Inherit agents become EMPTY entries so the
220
+ * snapshot is complete: applying it later restores inherit where inherit
221
+ * was (see the module header's snapshot semantics).
222
+ */
223
+ export function captureAgentsSnapshot(agents) {
224
+ const snapshot = {};
225
+ for (const agent of agents) {
226
+ snapshot[agent.meta.name] = {
227
+ ...(agent.meta.model !== undefined ? { model: agent.meta.model } : {}),
228
+ ...(agent.meta.thinking !== undefined ? { thinking: agent.meta.thinking } : {}),
229
+ };
230
+ }
231
+ return snapshot;
232
+ }
233
+ /**
234
+ * The frontmatter writes that apply `profile` onto the discovered `agents`:
235
+ * every agent LISTED in the profile gets exactly the recorded overrides
236
+ * (absent keys clear the line); agents absent from the profile are skipped
237
+ * — the profile never knew them, applying must not guess. The caller
238
+ * executes each plan through `updateAgentFrontmatter`.
239
+ */
240
+ export function planAgentApply(profile, agents) {
241
+ const planned = [];
242
+ for (const agent of agents) {
243
+ const entry = profile.agents[agent.meta.name];
244
+ if (entry === undefined)
245
+ continue;
246
+ planned.push({
247
+ agent,
248
+ updates: {
249
+ model: entry.model ?? null,
250
+ thinking: entry.thinking ?? null,
251
+ },
252
+ });
253
+ }
254
+ return planned;
255
+ }
256
+ /** `provider/model · think high` label for a route; `fallback` when unset. */
257
+ export function formatProfileRoute(route, fallback = '(not set)') {
258
+ if (route === undefined)
259
+ return fallback;
260
+ const base = `${route.provider}/${route.model}`;
261
+ return route.reasoningEffort === undefined ? base : `${base} · think ${route.reasoningEffort}`;
262
+ }
263
+ /**
264
+ * The full review dump of one profile (the `v` viewer): header, default
265
+ * model, then every discovered agent's recorded state — plus stale entries
266
+ * whose agent file no longer exists, marked so the user can prune them.
267
+ */
268
+ export function profileReviewLines(profile, agents, isCurrent = false) {
269
+ const lines = [
270
+ `Profile: ${profile.name}${isCurrent ? ' · current' : ''}`,
271
+ `Default model: ${formatProfileRoute(profile.defaultModel)}`,
272
+ ];
273
+ const discovered = new Set(agents.map(agent => agent.meta.name));
274
+ const rows = agents.map(agent => ({ name: agent.meta.name, entry: profile.agents[agent.meta.name], stale: false }));
275
+ for (const [name, entry] of Object.entries(profile.agents)) {
276
+ if (!discovered.has(name))
277
+ rows.push({ name, entry, stale: true });
278
+ }
279
+ if (rows.length === 0) {
280
+ lines.push('Agents: none recorded — pick models under /profile-cfg, or press s to save the current configuration');
281
+ return lines;
282
+ }
283
+ lines.push(`Agents (${String(rows.length)}):`);
284
+ for (const { name, entry, stale } of rows) {
285
+ const model = entry?.model ?? (entry === undefined ? '(not saved — apply leaves untouched)' : '(inherit)');
286
+ const think = entry?.thinking !== undefined ? `think ${entry.thinking}` : 'inherit';
287
+ lines.push(` ${name}${stale ? ' (file missing)' : ''} · ${model} · ${think}`);
288
+ }
289
+ return lines;
290
+ }
291
+ //# sourceMappingURL=model-profiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-profiles.js","sourceRoot":"","sources":["../src/model-profiles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `/profile-switch` + `/profile-cfg` — user model profiles: named snapshots
3
+ * of the whole model configuration (default model + think level, every
4
+ * subagent's model/thinking) that switch in one step between contexts like
5
+ * work and personal.
6
+ *
7
+ * Layers, all on the select-panel framework (src/panels.ts):
8
+ *
9
+ * 1. `/profile-switch` switcher (TablePanel) — one row per profile (name |
10
+ * default model | think | agents), Enter applies it to the live
11
+ * selection AND the agent markdown files, Esc backs out.
12
+ * 2. `/profile-cfg` manager (TablePanel) — the roster: n new, d delete
13
+ * (double-press confirm), Enter opens the profile's fields.
14
+ * 3. profile fields (FieldPanel) — model / think / agents rows plus the
15
+ * s save-current, r rename, v review shortcuts.
16
+ * 4. agents sub-table (TablePanel) — every discovered agent's recorded
17
+ * model/thinking; Enter picks through the SAME favorites/hidden model
18
+ * table /model uses (pickModel with the shared PanelHost), i sets
19
+ * explicit inherit, t edits think alone.
20
+ *
21
+ * Every edit persists to `$DSH_HOME/model-profiles.json` immediately
22
+ * (src/model-profiles.ts). Applying a profile writes the agent frontmatter
23
+ * files (updateAgentFrontmatter) and the default model through the same
24
+ * chain as /model: live bridge selection + persistDefaultModel.
25
+ */
26
+ import type { Context } from '@deepseek-ai/cordis';
27
+ import type { ModelSelection } from '@deepseek-ai/dsh-agent';
28
+ import type { TUI } from '@earendil-works/pi-tui';
29
+ import { type TuiTheme } from './theme/index.ts';
30
+ /** Live-session handles the profile flows need (bridged in from index.ts). */
31
+ export interface ProfileDeps {
32
+ getSelection(): ModelSelection | undefined;
33
+ setSelection(selection: ModelSelection): void;
34
+ }
35
+ /**
36
+ * Open the `/profile-switch` switcher. Resolves the apply summary when a profile
37
+ * was switched, or `undefined` when cancelled / nothing applied.
38
+ */
39
+ export declare function openProfileSwitcher(ctx: Context, tui: TUI, theme: TuiTheme, deps: ProfileDeps, restoreFocus: () => void): Promise<string | undefined>;
40
+ /**
41
+ * Open the `/profile-cfg` manager. Resolves a summary when anything changed,
42
+ * or `undefined` when the manager closed untouched. `preselect` jumps
43
+ * straight into one profile's fields window.
44
+ */
45
+ export declare function openProfileManager(ctx: Context, tui: TUI, theme: TuiTheme, deps: ProfileDeps, restoreFocus: () => void, preselect?: string): Promise<string | undefined>;