@aragon-agent/cli 0.6.22 → 0.6.24
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/README.md +20 -2
- package/dist/agent/controller.d.ts +11 -0
- package/dist/agent/controller.js +42 -4
- package/dist/agent/model-profile-settings.js +29 -2
- package/dist/agent/reducer.js +8 -6
- package/dist/commands/builtins.js +87 -3
- package/dist/compaction/child.d.ts +15 -0
- package/dist/compaction/child.js +14 -3
- package/dist/compaction/compactor.js +1 -0
- package/dist/compaction/context-command.js +10 -1
- package/dist/compaction/meter.d.ts +9 -0
- package/dist/compaction/meter.js +16 -1
- package/dist/compaction/pressure.d.ts +7 -0
- package/dist/compaction/pressure.js +5 -0
- package/dist/compaction/types.d.ts +11 -0
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +12 -0
- package/dist/config/load.js +66 -9
- package/dist/config/model-profile-resolution.d.ts +8 -0
- package/dist/config/model-profile-resolution.js +13 -6
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/model-profiles.d.ts +13 -0
- package/dist/config/model-profiles.js +18 -2
- package/dist/config/schema.d.ts +86 -0
- package/dist/config/schema.js +74 -0
- package/dist/team/model.d.ts +88 -0
- package/dist/team/model.js +136 -0
- package/dist/team/overseer.d.ts +5 -3
- package/dist/team/overseer.js +15 -4
- package/dist/team/report.d.ts +11 -0
- package/dist/team/report.js +24 -1
- package/dist/team/runtime.js +20 -3
- package/dist/team/subagent.d.ts +10 -0
- package/dist/team/subagent.js +57 -27
- package/dist/team/task-tool.d.ts +25 -6
- package/dist/team/task-tool.js +15 -9
- package/dist/team/types.d.ts +4 -3
- package/dist/ui/App.js +5 -0
- package/dist/ui/overlays/ModelProfileEditor.js +18 -4
- package/dist/ui/overlays/ModelProfilePicker.d.ts +14 -2
- package/dist/ui/overlays/ModelProfilePicker.js +51 -10
- package/dist/ui/overlays/QuestionOverlay.js +47 -6
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -0
- package/dist/ui/overlays/SettingsScreen.js +119 -17
- package/dist/ui/overlays/model-profile-state.d.ts +53 -4
- package/dist/ui/overlays/model-profile-state.js +111 -14
- package/package.json +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ModelProfile, ModelProfilesConfig
|
|
2
|
+
import type { ModelProfile, ModelProfilesConfig } from '../../config/model-profiles.js';
|
|
3
3
|
import type { Theme } from '../theme.js';
|
|
4
4
|
import type { TermCapabilities } from '../capabilities.js';
|
|
5
|
+
import { type ProfilePickerRole } from './model-profile-state.js';
|
|
5
6
|
export interface ProfileFrameProps {
|
|
6
7
|
isActive?: boolean;
|
|
7
8
|
theme: Theme;
|
|
@@ -19,11 +20,22 @@ export declare function profileEndpointSummary(baseUrl: string | null): string;
|
|
|
19
20
|
interface ModelProfilePickerProps extends ProfileFrameProps {
|
|
20
21
|
profiles: ModelProfilesConfig;
|
|
21
22
|
savedProfiles?: ModelProfilesConfig;
|
|
22
|
-
role
|
|
23
|
+
/** Which role this page selects for; `'subagent'` swaps the built-in rows (D-5). */
|
|
24
|
+
role: ProfilePickerRole;
|
|
23
25
|
manager?: boolean;
|
|
24
26
|
error?: string;
|
|
25
27
|
apiKeys: Record<string, string | undefined>;
|
|
26
28
|
customProfile?: ModelProfile;
|
|
29
|
+
/**
|
|
30
|
+
* The selection id for a role the library has no `${role}Id` key for: the
|
|
31
|
+
* subagent projection (`Same as main` = null, fast = the sentinel, profile
|
|
32
|
+
* = its id). Takes precedence over the `${role}Id` lookup when present.
|
|
33
|
+
*/
|
|
34
|
+
selectedIdOverride?: string | null;
|
|
35
|
+
/** The subagent binding, for the reference suffix on profile labels (3.7). */
|
|
36
|
+
subagentProfileId?: string | null;
|
|
37
|
+
/** Whether to annotate the built-in `Fast tier model` row with `(fast tier off)`. */
|
|
38
|
+
fastTierOff?: boolean;
|
|
27
39
|
onSelect: (id: string | null) => void;
|
|
28
40
|
onCreate: () => void;
|
|
29
41
|
onEdit: (profile: ModelProfile) => void;
|
|
@@ -6,7 +6,7 @@ import { pickGlyphs } from '../glyphs.js';
|
|
|
6
6
|
import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
|
|
7
7
|
import { stripPasteFrames } from '../paste-frames.js';
|
|
8
8
|
import { stripEnterFrames } from '../enter-frames.js';
|
|
9
|
-
import { profileUsers } from './model-profile-state.js';
|
|
9
|
+
import { profileUsers, SUBAGENT_FAST_ROW_ID } from './model-profile-state.js';
|
|
10
10
|
/** Move focus into view while accepting App's independent wheel/page scrolling. */
|
|
11
11
|
export function useProfileScroll(props, focus) {
|
|
12
12
|
const [offset, setOffset] = useState(0);
|
|
@@ -62,7 +62,9 @@ function usePickerState(props) {
|
|
|
62
62
|
const entries = props.profiles.entries.filter((entry) => `${entry.name} ${entry.provider} ${entry.model}`.toLocaleLowerCase()
|
|
63
63
|
.includes(query.toLocaleLowerCase()));
|
|
64
64
|
const actions = props.manager ? ['New', 'Edit', 'Duplicate', 'Delete'] : ['Create profile'];
|
|
65
|
-
|
|
65
|
+
// THE SUBAGENT PICKER swaps the `Current custom` row for TWO built-in rows:
|
|
66
|
+
// `Same as main` (selection id null) and `Fast tier model` (the sentinel).
|
|
67
|
+
const prefix = props.role === 'subagent' ? 2 : props.manager ? 0 : 1;
|
|
66
68
|
const count = entries.length + prefix + actions.length;
|
|
67
69
|
const cursor = Math.min(index, count - 1);
|
|
68
70
|
const focused = entries[cursor - prefix];
|
|
@@ -76,7 +78,18 @@ function usePickerState(props) {
|
|
|
76
78
|
function usePickerInput(props, state) {
|
|
77
79
|
const { cursor, focused, selected, actions, prefix, entries, count, searching, searchable, setIndex, setQuery, setSearching } = state;
|
|
78
80
|
function activate() {
|
|
79
|
-
if (
|
|
81
|
+
if (props.role === 'subagent') {
|
|
82
|
+
// Built-in rows first: `Same as main`, then `Fast tier model`.
|
|
83
|
+
if (cursor === 0) {
|
|
84
|
+
props.onSelect(null);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (cursor === 1) {
|
|
88
|
+
props.onSelect(SUBAGENT_FAST_ROW_ID);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (!props.manager && cursor === 0) {
|
|
80
93
|
props.onSelect(null);
|
|
81
94
|
return;
|
|
82
95
|
}
|
|
@@ -138,11 +151,26 @@ function usePickerInput(props, state) {
|
|
|
138
151
|
else if (!key.ctrl && !key.meta && input.toLowerCase() === 'e' && focused) {
|
|
139
152
|
props.onEdit(focused);
|
|
140
153
|
}
|
|
154
|
+
// `d` deletes the focused entry (D-7), symmetric with `e`: same guards,
|
|
155
|
+
// same no-ops on the built-in and action rows (`focused` is undefined
|
|
156
|
+
// there), and the `searching` early-return above keeps `d` a query
|
|
157
|
+
// character while typing a search.
|
|
158
|
+
else if (!key.ctrl && !key.meta && input.toLowerCase() === 'd' && focused) {
|
|
159
|
+
props.onDelete(focused.id);
|
|
160
|
+
}
|
|
141
161
|
});
|
|
142
162
|
}
|
|
163
|
+
/** The selection id this page marks with `[Selected]`, by role (6.2). */
|
|
164
|
+
function currentSelectionId(props) {
|
|
165
|
+
if (props.selectedIdOverride !== undefined)
|
|
166
|
+
return props.selectedIdOverride;
|
|
167
|
+
if (props.role === 'subagent')
|
|
168
|
+
return undefined;
|
|
169
|
+
return props.profiles[`${props.role}Id`];
|
|
170
|
+
}
|
|
143
171
|
function profilePickerLabel(props, entry) {
|
|
144
|
-
const saved = profileUsers(props.savedProfiles, entry.id);
|
|
145
|
-
const checked = props
|
|
172
|
+
const saved = profileUsers(props.savedProfiles, entry.id, props.subagentProfileId);
|
|
173
|
+
const checked = currentSelectionId(props) === entry.id ? '[Selected] ' : '';
|
|
146
174
|
const duplicate = props.profiles.entries.some((other) => other.id !== entry.id
|
|
147
175
|
&& other.name === entry.name);
|
|
148
176
|
const identity = duplicate ? `#${uniqueId(entry, props.profiles.entries)} ` : '';
|
|
@@ -154,18 +182,30 @@ function buildPickerRows(props, state) {
|
|
|
154
182
|
const { entries, actions, cursor, searching, searchable, query, focused, selected } = state;
|
|
155
183
|
const glyphs = pickGlyphs(props.caps);
|
|
156
184
|
const lines = [];
|
|
157
|
-
if (
|
|
185
|
+
if (props.role === 'subagent') {
|
|
186
|
+
// TWO BUILT-IN ROWS (D-5), marked with the same `[Selected]` prefix the
|
|
187
|
+
// profile rows use; the fast row annotates an unusable tier honestly.
|
|
188
|
+
const current = props.selectedIdOverride ?? null;
|
|
189
|
+
lines.push(`${current === null ? '[Selected] ' : ''}Same as main`);
|
|
190
|
+
lines.push(`${current === SUBAGENT_FAST_ROW_ID ? '[Selected] ' : ''}Fast tier model`
|
|
191
|
+
+ `${props.fastTierOff === true ? ' (fast tier off)' : ''}`);
|
|
192
|
+
}
|
|
193
|
+
else if (!props.manager)
|
|
158
194
|
lines.push('Current custom');
|
|
159
195
|
lines.push(...entries.map((entry) => profilePickerLabel(props, entry)));
|
|
160
196
|
lines.push(...actions);
|
|
161
197
|
const rows = lines.map((line, row) => _jsxs(Text, { wrap: "truncate", color: row === cursor ? props.theme.accent : props.theme.muted, children: [`${row === cursor && !searching ? glyphs.caret : ' '} `, clipProfileText(line, props.cols - 8)] }, row));
|
|
162
198
|
if (searchable)
|
|
163
199
|
rows.unshift(_jsxs(Text, { wrap: "truncate", color: props.theme.primary, children: [searching ? `${glyphs.caret} ` : ' ', "Search: ", query || '(/ to search)'] }, "search"));
|
|
164
|
-
|
|
200
|
+
// The subagent picker has no `Current custom` row to describe; its
|
|
201
|
+
// built-in rows already say everything there is to say.
|
|
202
|
+
const detail = props.role === 'subagent' ? focused
|
|
203
|
+
: !props.manager && cursor === 0 ? props.customProfile : focused ?? selected;
|
|
165
204
|
if (detail) {
|
|
166
205
|
rows.push(_jsxs(Text, { wrap: "truncate", color: props.theme.muted, children: [detail.provider, ":", detail.model || '(not set)', " ", host(detail), detail === props.customProfile ? '' : ` #${detail.id}`] }, "detail"));
|
|
167
206
|
rows.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: detail.apiKey ? 'Profile key: Configured'
|
|
168
207
|
: props.apiKeys[detail.provider] ? 'Shared / environment: Configured' : 'Missing API key' }, "key"));
|
|
208
|
+
rows.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: `Window: ${detail.contextWindow ?? 'auto'}` }, "window"));
|
|
169
209
|
}
|
|
170
210
|
return rows;
|
|
171
211
|
}
|
|
@@ -174,7 +214,8 @@ export function ModelProfilePicker(props) {
|
|
|
174
214
|
const state = usePickerState(props);
|
|
175
215
|
usePickerInput(props, state);
|
|
176
216
|
return _jsx(OverlayFrame, { ...props, title: props.error ?? (props.manager ? 'Manage profiles'
|
|
177
|
-
:
|
|
178
|
-
|
|
179
|
-
|
|
217
|
+
: props.role === 'subagent' ? 'Subagent model'
|
|
218
|
+
: `${props.role === 'main' ? 'Main' : 'Fast'} profile`), rows: buildPickerRows(props, state), scrollOffset: state.searching ? 0 : state.offset, hint: state.searching ? 'Type search | Enter done | Esc back'
|
|
219
|
+
: props.manager ? 'Enter action | e edit | d delete | Esc back'
|
|
220
|
+
: 'Enter select | e edit | d delete | Esc back' });
|
|
180
221
|
}
|
|
@@ -38,6 +38,21 @@ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, the
|
|
|
38
38
|
const [phase, setPhase] = useState('choosing');
|
|
39
39
|
const [cursors, setCursors] = useState(() => initialCursors(questions));
|
|
40
40
|
const [selections, setSelections] = useState(() => initialSelections(questions));
|
|
41
|
+
/**
|
|
42
|
+
* THE PER-QUESTION EXPLICIT-MARK BIT (V-1 / D-8).
|
|
43
|
+
*
|
|
44
|
+
* `initialSelections` PRE-SELECTS the recommended option on EVERY question
|
|
45
|
+
* (or the first option when there is none), so "the selection set is empty"
|
|
46
|
+
* is unreachable and can never be the test for "did the user choose?". The
|
|
47
|
+
* only reliable discriminator is the fact itself: the user pressed SPACE on
|
|
48
|
+
* this question. Without it, a user who only MOVES the cursor and presses
|
|
49
|
+
* Enter would submit the RECOMMENDED option instead of the highlighted one -
|
|
50
|
+
* a silent regression of the wizard's most common path. The bit survives
|
|
51
|
+
* Left/Right navigation exactly like `selections` and `cursors` do, and a
|
|
52
|
+
* single-choice mark is idempotent (no un-mark; Enter always has at least
|
|
53
|
+
* the marked item to submit).
|
|
54
|
+
*/
|
|
55
|
+
const [markedSteps, setMarkedSteps] = useState(() => questions.map(() => false));
|
|
41
56
|
const [customs, setCustoms] = useState(() => questions.map(() => ''));
|
|
42
57
|
const [draft, setDraft] = useState('');
|
|
43
58
|
const [offset, setOffset] = useState(0);
|
|
@@ -56,6 +71,7 @@ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, the
|
|
|
56
71
|
return next;
|
|
57
72
|
}));
|
|
58
73
|
const chooseAt = (index, option) => setSelections((prev) => prev.map((set, i) => (i === index ? new Set([option]) : set)));
|
|
74
|
+
const markAt = (index) => setMarkedSteps((prev) => prev.map((marked, i) => (i === index ? true : marked)));
|
|
59
75
|
const buildAnswers = () => questions.map((q, i) => {
|
|
60
76
|
const chosen = [...(selections[i] ?? new Set())].sort((a, b) => a - b);
|
|
61
77
|
const labels = chosen.map((index) => q.options[index]?.label ?? '').filter((l) => l.length > 0);
|
|
@@ -159,8 +175,21 @@ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, the
|
|
|
159
175
|
goBack();
|
|
160
176
|
return;
|
|
161
177
|
}
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
// SPACE, BY QUESTION SHAPE (D-8). The strict `input === ' '` keeps a
|
|
179
|
+
// multi-character paste frame from ever matching (R-8); the Other row is
|
|
180
|
+
// a no-op in both shapes because it needs TEXT, and Enter is its key.
|
|
181
|
+
if (input === ' ' && !question.options[cursor]?.isOther) {
|
|
182
|
+
if (question.allowMultiple) {
|
|
183
|
+
toggleAt(step, cursor);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// SINGLE CHOICE: mark the highlighted item and STAY - the terminal
|
|
187
|
+
// convention of "space selects, enter confirms". The mark is recorded
|
|
188
|
+
// both in the selection set (so `[x]` renders) and in the explicit
|
|
189
|
+
// mark bit (so a later Enter submits THIS item even after the cursor
|
|
190
|
+
// has moved on).
|
|
191
|
+
chooseAt(step, cursor);
|
|
192
|
+
markAt(step);
|
|
164
193
|
return;
|
|
165
194
|
}
|
|
166
195
|
if (key.return) {
|
|
@@ -170,8 +199,15 @@ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, the
|
|
|
170
199
|
setPhase('typingOther');
|
|
171
200
|
return;
|
|
172
201
|
}
|
|
173
|
-
|
|
174
|
-
|
|
202
|
+
// A single-choice Enter submits the CURSOR item unless the user has
|
|
203
|
+
// explicitly space-marked this question, in which case the marked item
|
|
204
|
+
// wins even if the cursor has since moved (V-1's regression guard:
|
|
205
|
+
// cursor + Enter alone keeps today's behavior, recommended preselect
|
|
206
|
+
// and all).
|
|
207
|
+
if (!question.allowMultiple) {
|
|
208
|
+
if (!markedSteps[step])
|
|
209
|
+
chooseAt(step, cursor);
|
|
210
|
+
}
|
|
175
211
|
else if (!(selections[step] ?? new Set()).has(cursor))
|
|
176
212
|
toggleAt(step, cursor);
|
|
177
213
|
advance();
|
|
@@ -183,7 +219,9 @@ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, the
|
|
|
183
219
|
? `type your answer ${glyphs.midDot} ${glyphs.enterKey} commit ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`
|
|
184
220
|
: phase === 'review'
|
|
185
221
|
? `${glyphs.enterKey} submit ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`
|
|
186
|
-
:
|
|
222
|
+
: question.options[cursor]?.isOther
|
|
223
|
+
? `${glyphs.arrowUp}${glyphs.arrowDown} choose ${glyphs.midDot} ${glyphs.enterKey} to type ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`
|
|
224
|
+
: `${glyphs.arrowUp}${glyphs.arrowDown} choose ${glyphs.midDot} space select ${glyphs.midDot} ${glyphs.enterKey} next ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`;
|
|
187
225
|
const rowTexts = [];
|
|
188
226
|
const optionStarts = [];
|
|
189
227
|
const push = (text) => { rowTexts.push(...wrapToRows(text, Math.max(1, cols - 4))); };
|
|
@@ -208,8 +246,11 @@ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, the
|
|
|
208
246
|
setOffset(previous => focus < previous ? focus : focus >= previous + height
|
|
209
247
|
? focus - height + 1 : previous);
|
|
210
248
|
}, [focus, maxRows, cols, step, phase]);
|
|
249
|
+
// `space select` shows for BOTH shapes (AC-10) - 42 characters on the
|
|
250
|
+
// single-choice shape without `cancel`, a deliberate 5-column growth inside
|
|
251
|
+
// the `cols <= 48` budget this hint exists for (V-12).
|
|
211
252
|
const shortHint = phase === 'typingOther' ? 'Enter commit | Left back | Esc cancel'
|
|
212
253
|
: phase === 'review' ? 'Enter submit | Left back | Esc cancel'
|
|
213
|
-
:
|
|
254
|
+
: '^v choose Space select Enter next Esc';
|
|
214
255
|
return _jsx(OverlayFrame, { title: `${title}${position}`, hint: cols <= 48 ? shortHint : hint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, rows: rowTexts.map((line, index) => _jsx(Text, { wrap: "truncate", children: line || ' ' }, index)), scrollOffset: offset, onScrollClamp: setOffset });
|
|
215
256
|
}
|
|
@@ -36,6 +36,15 @@ export interface SettingsValues {
|
|
|
36
36
|
*/
|
|
37
37
|
liveToolOutput: 'on' | 'off';
|
|
38
38
|
maxTokens: string;
|
|
39
|
+
/**
|
|
40
|
+
* The top-level context-window override, as text (profile-context-window
|
|
41
|
+
* §6.3). EDITED ONLY WHILE MAIN IS UNBOUND: once a profile is bound as Main
|
|
42
|
+
* the profile owns the denominator, this row hides, and the editor becomes
|
|
43
|
+
* the only surface that writes the key - two entries on one key can only
|
|
44
|
+
* drift. Empty renders as `auto` because an empty field IS AUTO here, the
|
|
45
|
+
* reading `maxTokens` established.
|
|
46
|
+
*/
|
|
47
|
+
contextWindow: string;
|
|
39
48
|
apiKey: string;
|
|
40
49
|
/**
|
|
41
50
|
* Only the LEVEL is editable here. The rest of `LogConfig` — and above all
|
|
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { useEffect, useReducer, useRef, useState } from 'react';
|
|
12
12
|
import { Text, useInput } from 'ink';
|
|
13
13
|
import { DEFAULT_MAX_OUTPUT_TOKENS, resolveOutputTokens, } from '@aragon-agent/core';
|
|
14
|
-
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret, parseMaxTokensInput, parseThresholdInput, } from '../../config/schema.js';
|
|
14
|
+
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret, parseContextWindowInput, parseMaxTokensInput, parseThresholdInput, } from '../../config/schema.js';
|
|
15
15
|
import { LOG_LEVEL_NAMES } from '../../logging/levels.js';
|
|
16
16
|
import { pickGlyphs } from '../glyphs.js';
|
|
17
17
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
@@ -21,7 +21,20 @@ import { emptyModelProfiles } from '../../config/model-profiles.js';
|
|
|
21
21
|
import { registerSecret } from '../../logging/secret-registry.js';
|
|
22
22
|
import { ModelProfilePicker, useProfileScroll } from './ModelProfilePicker.js';
|
|
23
23
|
import { ModelProfileEditor } from './ModelProfileEditor.js';
|
|
24
|
-
import { createModelProfileState, createCustomProfile, modelProfileReducer, profileUsers } from './model-profile-state.js';
|
|
24
|
+
import { createModelProfileState, createCustomProfile, modelProfileReducer, profileUsers, draftTeam, draftSubagentProfileId, subagentModelSummary, subagentSelectedId } from './model-profile-state.js';
|
|
25
|
+
/**
|
|
26
|
+
* THE ROOT PAGE'S ROW INDEXES, AS NAMES (subagent-model V-5).
|
|
27
|
+
*
|
|
28
|
+
* Five offset sites used to agree by accident on the number 3; the Subagent
|
|
29
|
+
* model row moves every one of them. Each site below now cites its constant,
|
|
30
|
+
* so adding a row later is a one-line change instead of a silent regression
|
|
31
|
+
* that focuses an error message on the wrong field.
|
|
32
|
+
*/
|
|
33
|
+
const ROOT_MAIN = 0;
|
|
34
|
+
const ROOT_FAST = 1;
|
|
35
|
+
const ROOT_SUBAGENT = 2;
|
|
36
|
+
const ROOT_MANAGE = 3;
|
|
37
|
+
const ROOT_FIRST_FIELD = 4;
|
|
25
38
|
const SHOW_THINKING_OPTIONS = ['off', 'on'];
|
|
26
39
|
const LIVE_OUTPUT_OPTIONS = ['off', 'on'];
|
|
27
40
|
const FAST_ENABLED_OPTIONS = ['off', 'on'];
|
|
@@ -172,6 +185,11 @@ const FIELDS = [
|
|
|
172
185
|
// `.padEnd(18)` enforces below and that IF-H7 records as silently overflowing.
|
|
173
186
|
{ key: 'liveToolOutput', label: 'Live output', kind: 'enum', options: LIVE_OUTPUT_OPTIONS },
|
|
174
187
|
{ key: 'maxTokens', label: 'Max tokens', kind: 'text' },
|
|
188
|
+
// DIRECTLY UNDER `Max tokens`: the two token rows read together. `Context
|
|
189
|
+
// window` is 14 characters, inside the 18-column label budget `.padEnd(18)`
|
|
190
|
+
// enforces below (IF-H7). Only ever RENDERED while Main is unbound - see
|
|
191
|
+
// `SettingsValues.contextWindow` and the unbound-group filter below.
|
|
192
|
+
{ key: 'contextWindow', label: 'Context window', kind: 'text' },
|
|
175
193
|
// The fast tier sits AFTER `Max tokens` and BEFORE `API key`, because the key
|
|
176
194
|
// row is the one a user scrolls to deliberately and putting five rows past it
|
|
177
195
|
// would bury it (fast-model-tier §4.5). The rationale survives a fifth row;
|
|
@@ -348,6 +366,11 @@ function LegacySettingsScreen({ isActive = true, initial, apiKeys, maxRows, cols
|
|
|
348
366
|
// setting, which is the one thing this field can never be.
|
|
349
367
|
if (f.key === 'maxTokens' && raw.length === 0)
|
|
350
368
|
return 'auto';
|
|
369
|
+
// An empty window field is AUTO, not "unset" - the same reading, and the
|
|
370
|
+
// second of the TWO render points that must agree (P2-1: the profile
|
|
371
|
+
// screen's row fallback below is the other).
|
|
372
|
+
if (f.key === 'contextWindow' && raw.length === 0)
|
|
373
|
+
return 'auto';
|
|
351
374
|
// `(not set)` and NOT `(same as main)`, which is the honest reading of §3.2
|
|
352
375
|
// rule 3: an empty `fast.model` does not inherit, it means the tier is
|
|
353
376
|
// unconfigured. `(same as main)` is a fact the derived line below states
|
|
@@ -385,15 +408,23 @@ function LegacySettingsScreen({ isActive = true, initial, apiKeys, maxRows, cols
|
|
|
385
408
|
rows.push(_jsx(Text, { wrap: "truncate", color: theme.muted, children: "Enter saves. Keys persist to the config file (0600)." }, "__save"));
|
|
386
409
|
return (_jsx(OverlayFrame, { title: "Settings", hint: `${glyphs.arrowUp}${glyphs.arrowDown} field ${glyphs.midDot} ${glyphs.arrowLeft}${glyphs.arrowRight} change ${glyphs.midDot} Enter save ${glyphs.midDot} Esc close`, maxRows: maxRows, cols: cols, rows: rows, scrollOffset: focusedOffset, onScrollClamp: onScrollClamp, theme: theme, caps: caps }));
|
|
387
410
|
}
|
|
411
|
+
// `contextWindow` joins the exclusion list so a bound Main can never leak a
|
|
412
|
+
// SECOND copy of the row through PROFILE_FIELDS - the root row belongs to the
|
|
413
|
+
// unbound group below, exclusively (AC-11).
|
|
388
414
|
const PROFILE_FIELDS = FIELDS.filter((field) => ![
|
|
389
415
|
'provider', 'model', 'baseUrl', 'apiKey', 'fastProvider', 'fastModel', 'fastEnabled',
|
|
416
|
+
'contextWindow',
|
|
390
417
|
].includes(field.key));
|
|
391
418
|
function profileSettingsValues(initial, draft) {
|
|
392
419
|
const file = draft.baseline;
|
|
393
420
|
return { ...initial, provider: file.provider ?? initial.provider,
|
|
394
421
|
model: file.model ?? initial.model,
|
|
395
422
|
baseUrl: file.baseUrl ?? '', apiKey: '', fastModel: file.fast?.model ?? '',
|
|
396
|
-
fastProvider: file.fast?.provider || FAST_INHERIT_PROVIDER
|
|
423
|
+
fastProvider: file.fast?.provider || FAST_INHERIT_PROVIDER,
|
|
424
|
+
// SEEDED FROM THE FILE VALUE, like its provider/model/baseUrl siblings
|
|
425
|
+
// (AC-11 / P2-3): when env holds an override the file value is not the
|
|
426
|
+
// effective one, and this screen edits the FILE.
|
|
427
|
+
contextWindow: file.contextWindow == null ? 'auto' : String(file.contextWindow) };
|
|
397
428
|
}
|
|
398
429
|
/** Only fields changed by the user belong in a settings transaction. */
|
|
399
430
|
export function readDirtySettings(initial, values) {
|
|
@@ -422,6 +453,15 @@ export function readDirtySettings(initial, values) {
|
|
|
422
453
|
throw new Error('Max tokens: use a number or auto.');
|
|
423
454
|
patch.maxTokens = parsed.kind === 'auto' ? null : parsed.value;
|
|
424
455
|
}
|
|
456
|
+
if (dirty('contextWindow')) {
|
|
457
|
+
// TYPO CHANGES NOTHING: the message's `Context window:` prefix is what
|
|
458
|
+
// `saveProfileSettings`'s focus logic matches to jump to this row. The
|
|
459
|
+
// silent clamp on a value mirrors `config set contextWindow`.
|
|
460
|
+
const parsed = parseContextWindowInput(values.contextWindow);
|
|
461
|
+
if (parsed.kind === 'invalid')
|
|
462
|
+
throw new Error('Context window: use a number or auto.');
|
|
463
|
+
patch.contextWindow = parsed.kind === 'auto' ? null : parsed.value;
|
|
464
|
+
}
|
|
425
465
|
const fast = {};
|
|
426
466
|
const parsedFast = readFastSettings(values);
|
|
427
467
|
if (dirty('fastModel'))
|
|
@@ -501,11 +541,13 @@ function useProfileSettingsState(props) {
|
|
|
501
541
|
const dirty = state.dirty || Object.keys(values).some((key) => values[key] !== initial[key]);
|
|
502
542
|
const fields = [FIELDS.find((item) => item.key === 'fastEnabled'),
|
|
503
543
|
...FIELDS.filter((item) => library.mainId === null
|
|
504
|
-
&& ['provider', 'model', 'baseUrl', 'apiKey'].includes(item.key)),
|
|
544
|
+
&& ['provider', 'model', 'baseUrl', 'contextWindow', 'apiKey'].includes(item.key)),
|
|
505
545
|
...FIELDS.filter((item) => library.fastId === null
|
|
506
546
|
&& ['fastModel', 'fastProvider'].includes(item.key)), ...PROFILE_FIELDS];
|
|
507
|
-
|
|
508
|
-
|
|
547
|
+
// SIX FIXED ROWS now (Main, Fast, SUBAGENT, Manage, Save, Reload) around
|
|
548
|
+
// the `fields` block - see the ROOT_* constants for the five offset sites.
|
|
549
|
+
const count = fields.length + 6;
|
|
550
|
+
const field = fields[index - ROOT_FIRST_FIELD];
|
|
509
551
|
const readError = state.draft.readError;
|
|
510
552
|
const confirming = state.page === 'delete-confirm' || state.page === 'discard-confirm';
|
|
511
553
|
return { state, dispatch, initial, setInitial, values, setValues, index, setIndex,
|
|
@@ -526,7 +568,7 @@ function saveProfileSettings(props, view) {
|
|
|
526
568
|
setError(message);
|
|
527
569
|
const at = fields.findIndex((item) => message.startsWith(`${item.label}:`));
|
|
528
570
|
if (at >= 0)
|
|
529
|
-
setIndex(at +
|
|
571
|
+
setIndex(at + ROOT_FIRST_FIELD);
|
|
530
572
|
return;
|
|
531
573
|
}
|
|
532
574
|
const result = props.onProfileSave({
|
|
@@ -549,6 +591,7 @@ function reloadProfileSettings(props, view) {
|
|
|
549
591
|
showThinking: live.showThinking ? 'on' : 'off',
|
|
550
592
|
liveToolOutput: live.liveToolOutput ? 'on' : 'off',
|
|
551
593
|
maxTokens: live.maxTokens === undefined ? '' : String(live.maxTokens),
|
|
594
|
+
contextWindow: live.contextWindow == null ? 'auto' : String(live.contextWindow),
|
|
552
595
|
logLevel: live.log?.level ?? props.initial.logLevel,
|
|
553
596
|
...(live.fast ? fastSettingsFrom(live.fast) : {}),
|
|
554
597
|
...(live.compaction ? compactionSettingsFrom(live.compaction) : {}),
|
|
@@ -585,9 +628,15 @@ function activateRootSetting(props, view) {
|
|
|
585
628
|
}
|
|
586
629
|
if (readError || persisted)
|
|
587
630
|
return;
|
|
588
|
-
if (index
|
|
589
|
-
dispatch({ type: 'page', page: 'picker', role: index ===
|
|
590
|
-
|
|
631
|
+
if (index === ROOT_MAIN || index === ROOT_FAST) {
|
|
632
|
+
dispatch({ type: 'page', page: 'picker', role: index === ROOT_MAIN ? 'main' : 'fast' });
|
|
633
|
+
}
|
|
634
|
+
else if (index === ROOT_SUBAGENT) {
|
|
635
|
+
// The user-owned cost switch (D-5): its own picker mode, writing
|
|
636
|
+
// `draft.patch.team` rather than a `${role}Id` library key.
|
|
637
|
+
dispatch({ type: 'page', page: 'picker', role: 'subagent' });
|
|
638
|
+
}
|
|
639
|
+
else if (index === ROOT_MANAGE)
|
|
591
640
|
dispatch({ type: 'page', page: 'manager' });
|
|
592
641
|
else
|
|
593
642
|
saveProfileSettings(props, view);
|
|
@@ -669,16 +718,48 @@ function renderSettingsConfirmation(props, view) {
|
|
|
669
718
|
const { state, library, confirmIndex, glyphs } = view;
|
|
670
719
|
const deleting = state.page === 'delete-confirm';
|
|
671
720
|
const name = library.entries.find((entry) => entry.id === state.deleteId)?.name ?? '';
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
721
|
+
// UNBIND-AND-DELETE (D-6): a referenced entry stays deletable in ONE flow,
|
|
722
|
+
// and the button names what will happen.
|
|
723
|
+
const labels = deleting
|
|
724
|
+
? ['Cancel', state.deleteUsers ? 'Unbind and delete' : 'Delete']
|
|
725
|
+
: ['Continue', 'Discard'];
|
|
726
|
+
const rows = labels
|
|
727
|
+
.map((label, at) => _jsxs(Text, { wrap: "truncate", color: props.theme.primary, children: [confirmIndex === at ? `${glyphs.caret} ` : ' ', label] }, label));
|
|
728
|
+
// V-8: A RENDER-ONLY ROW, pushed AFTER the buttons. `confirmIndex` stays a
|
|
729
|
+
// 0..1 index over the two buttons and `scrollOffset` follows it, so this
|
|
730
|
+
// line must never sit between them or take an array slot of its own.
|
|
731
|
+
if (deleting && state.deleteUsers) {
|
|
732
|
+
rows.push(_jsxs(Text, { wrap: "truncate", color: props.theme.muted, children: ["Used by ", state.deleteUsers, ". The binding(s) will be cleared."] }, "users"));
|
|
733
|
+
}
|
|
734
|
+
return _jsx(OverlayFrame, { ...props, title: deleting ? `Delete ${name}?` : 'Discard unsaved changes?', hint: "Enter choose | Esc continue", rows: rows, scrollOffset: confirmIndex });
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Whether the fast tier resolves FOR DISPLAY while the subagent picker is
|
|
738
|
+
* open (D-5): the built-in `Fast tier model` row then annotates itself with
|
|
739
|
+
* `(fast tier off)`, so choosing it is never a surprise fallback. The same
|
|
740
|
+
* enabled + model + config-file-key approximation `fastTierLine` uses - and
|
|
741
|
+
* the same env-key blindness (V-10), accepted for consistency with it.
|
|
742
|
+
*/
|
|
743
|
+
function fastTierUnavailable(props, view) {
|
|
744
|
+
const { values } = view;
|
|
745
|
+
if (values.fastEnabled !== 'on')
|
|
746
|
+
return true;
|
|
747
|
+
if (values.fastModel.trim().length === 0)
|
|
748
|
+
return true;
|
|
749
|
+
const provider = values.fastProvider === FAST_INHERIT_PROVIDER
|
|
750
|
+
|| values.fastProvider.trim().length === 0 ? values.provider : values.fastProvider;
|
|
751
|
+
return (props.apiKeys[provider] ?? '').length === 0;
|
|
675
752
|
}
|
|
676
753
|
function renderProfileSubpage(props, view) {
|
|
677
754
|
const { state, library, dispatch, setConfirmIndex } = view;
|
|
678
755
|
if (state.page === 'editor' && state.editor)
|
|
679
|
-
return _jsx(ModelProfileEditor, { ...props, editor: state.editor, users: profileUsers(library, state.editor.original.id), error: state.error, dispatch: dispatch });
|
|
756
|
+
return _jsx(ModelProfileEditor, { ...props, editor: state.editor, users: profileUsers(library, state.editor.original.id, draftSubagentProfileId(state.draft)), error: state.error, dispatch: dispatch });
|
|
757
|
+
// A NEW PROFILE SEEDED FROM THE MAIN CONNECTION while the subagent picker is
|
|
758
|
+
// open: 'subagent' is picker-only (6.2), so the seed role falls back to
|
|
759
|
+
// 'main' while `state.role` keeps driving the select that binds it.
|
|
760
|
+
const createRole = state.role === 'subagent' ? 'main' : state.role;
|
|
680
761
|
if (state.page === 'picker' || state.page === 'manager')
|
|
681
|
-
return _jsx(ModelProfilePicker, { ...props, profiles: library, savedProfiles: state.draft.baseline.modelProfiles, role: state.role, customProfile: createCustomProfile(state.draft,
|
|
762
|
+
return _jsx(ModelProfilePicker, { ...props, profiles: library, savedProfiles: state.draft.baseline.modelProfiles, role: state.role, customProfile: createCustomProfile(state.draft, createRole), manager: state.page === 'manager', error: state.error, selectedIdOverride: state.role === 'subagent' ? subagentSelectedId(state.draft) : undefined, subagentProfileId: draftSubagentProfileId(state.draft), fastTierOff: state.role === 'subagent' && fastTierUnavailable(props, view), onSelect: (id) => dispatch({ type: 'select', role: state.role, id }), onCreate: () => dispatch({ type: 'edit', profile: createCustomProfile(state.draft, createRole),
|
|
682
763
|
bindNew: state.page === 'picker' }), onEdit: (profile) => dispatch({ type: 'edit', profile }), onDuplicate: (id) => dispatch({ type: 'duplicate', id }), onDelete: (id) => { setConfirmIndex(0); dispatch({ type: 'delete', id }); }, onCancel: () => dispatch({ type: 'page', page: 'root' }) });
|
|
683
764
|
return null;
|
|
684
765
|
}
|
|
@@ -691,13 +772,34 @@ function buildRootSettingsRows(props, view) {
|
|
|
691
772
|
const missing = !entry.apiKey && !props.apiKeys[entry.provider];
|
|
692
773
|
return `${entry.name} ${entry.provider}:${entry.model}${missing ? ' [Missing API key]' : ''}`;
|
|
693
774
|
};
|
|
775
|
+
// THE SUBAGENT MODEL ROW (D-5), third on the page, summarized from the
|
|
776
|
+
// same draft/baseline sources every other derived row here reads. The fast
|
|
777
|
+
// tier check mirrors `fastTierLine`'s approximation (enabled + model + a
|
|
778
|
+
// config-file key) so the two derived lines can never disagree.
|
|
779
|
+
const team = draftTeam(state.draft);
|
|
780
|
+
const mainBound = library.entries.find((entry) => entry.id === library.mainId);
|
|
781
|
+
const mainConnection = mainBound
|
|
782
|
+
? `${mainBound.provider}:${mainBound.model}`
|
|
783
|
+
: `${values.provider}:${values.model}`;
|
|
784
|
+
const fastModel = values.fastModel.trim();
|
|
785
|
+
const fastProvider = values.fastProvider === FAST_INHERIT_PROVIDER
|
|
786
|
+
|| values.fastProvider.trim().length === 0 ? values.provider : values.fastProvider;
|
|
787
|
+
const fastConnection = values.fastEnabled === 'on' && fastModel.length > 0
|
|
788
|
+
&& (props.apiKeys[fastProvider] ?? '').length > 0
|
|
789
|
+
? `${fastProvider}:${fastModel}` : null;
|
|
790
|
+
const subagentLine = subagentModelSummary({ mode: team.model,
|
|
791
|
+
modelProfileId: team.modelProfileId, mainConnection, fastConnection,
|
|
792
|
+
profiles: state.draft.profiles,
|
|
793
|
+
hasKey: (id) => (props.apiKeys[id] ?? '').length > 0 });
|
|
694
794
|
const lines = [`Main profile ${name(library.mainId)}`,
|
|
695
795
|
`Fast profile ${name(library.fastId)}`,
|
|
796
|
+
`Subagent model ${subagentLine}`,
|
|
696
797
|
`Manage profiles ${library.entries.length} saved`,
|
|
697
798
|
...fields.map((item) => `${item.label.padEnd(18)}${item.key === 'apiKey'
|
|
698
799
|
? values.apiKey ? 'Configured (replacement)' : props.apiKeys[values.provider]
|
|
699
800
|
? 'Configured (type to replace)' : '(type to set)'
|
|
700
|
-
: values[item.key] || (item.key === 'maxTokens'
|
|
801
|
+
: values[item.key] || (item.key === 'maxTokens' || item.key === 'contextWindow'
|
|
802
|
+
? 'auto' : '(not set)')}`),
|
|
701
803
|
persisted || readError ? 'Save (disabled)' : 'Save settings', 'Reload settings'];
|
|
702
804
|
const rows = lines.map((line, at) => _jsxs(Text, { wrap: "truncate", color: at === index ? props.theme.accent : props.theme.muted, children: [at === index ? `${glyphs.caret} ` : ' ', line] }, at));
|
|
703
805
|
rows.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: effectiveCapLine(values) }, "effective"));
|
|
@@ -720,7 +822,7 @@ function ProfileSettingsScreen(props) {
|
|
|
720
822
|
return subpage;
|
|
721
823
|
const { error, readError, dirty, persisted, index, offset } = view;
|
|
722
824
|
return _jsx(OverlayFrame, { ...props, rows: buildRootSettingsRows(props, view), scrollOffset: offset, title: error ?? (readError ? 'Cannot read config. Repair with aragon config edit; Reload.'
|
|
723
|
-
: dirty ? 'Settings | Unsaved changes' : 'Settings'), hint: persisted ? 'Esc close; restart to apply' : index <
|
|
825
|
+
: dirty ? 'Settings | Unsaved changes' : 'Settings'), hint: persisted ? 'Esc close; restart to apply' : index < ROOT_FIRST_FIELD
|
|
724
826
|
? 'Enter select/edit | Ctrl+S save | Esc cancel'
|
|
725
827
|
: 'Enter save/action | Ctrl+S save | Esc cancel' });
|
|
726
828
|
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { type ModelProfile, type ModelProfilesConfig, type ModelRole } from '../../config/model-profiles.js';
|
|
2
2
|
import type { ModelSettingsDraft } from '../../config/model-profile-store.js';
|
|
3
|
+
import type { SubagentModelMode } from '../../team/model.js';
|
|
3
4
|
export type ProfilePage = 'root' | 'picker' | 'manager' | 'editor' | 'delete-confirm' | 'discard-confirm';
|
|
5
|
+
/**
|
|
6
|
+
* Which role a PROFILE PICKER page is choosing for.
|
|
7
|
+
*
|
|
8
|
+
* `'subagent'` is a PICKER-ONLY role (subagent-model 6.2): `ModelRole` stays
|
|
9
|
+
* `'main' | 'fast'` because it is the type basis of the config layer's
|
|
10
|
+
* `${role}Id` template keys - widening it would invent a `subagentId` library
|
|
11
|
+
* key nothing reads. The subagent picker writes `draft.patch.team` instead.
|
|
12
|
+
*/
|
|
13
|
+
export type ProfilePickerRole = ModelRole | 'subagent';
|
|
14
|
+
/** The picker's built-in `Fast tier model` row; never a profile id on disk. */
|
|
15
|
+
export declare const SUBAGENT_FAST_ROW_ID = "__fast__";
|
|
4
16
|
export interface ProfileEditorDraft {
|
|
5
17
|
original: ModelProfile;
|
|
6
18
|
name: string;
|
|
7
19
|
provider: string;
|
|
8
20
|
model: string;
|
|
21
|
+
/** Text exactly as typed; `''` renders `(auto)` and parses to AUTO (§6.5). */
|
|
22
|
+
contextWindow: string;
|
|
9
23
|
baseUrl: string;
|
|
10
24
|
credentialMode: 'keep' | 'replace' | 'shared';
|
|
11
25
|
apiKey: string;
|
|
@@ -14,21 +28,23 @@ export interface ProfileEditorDraft {
|
|
|
14
28
|
export interface ModelProfileState {
|
|
15
29
|
draft: ModelSettingsDraft;
|
|
16
30
|
page: ProfilePage;
|
|
17
|
-
role:
|
|
31
|
+
role: ProfilePickerRole;
|
|
18
32
|
editor?: ProfileEditorDraft;
|
|
19
33
|
returnPage: 'picker' | 'manager';
|
|
20
34
|
bindNew: boolean;
|
|
21
35
|
dirty: boolean;
|
|
22
36
|
error?: string;
|
|
23
37
|
deleteId?: string;
|
|
38
|
+
/** Who referenced the entry being deleted ('Main + Subagent', or ''). */
|
|
39
|
+
deleteUsers?: string;
|
|
24
40
|
}
|
|
25
41
|
export type ModelProfileAction = {
|
|
26
42
|
type: 'page';
|
|
27
43
|
page: ProfilePage;
|
|
28
|
-
role?:
|
|
44
|
+
role?: ProfilePickerRole;
|
|
29
45
|
} | {
|
|
30
46
|
type: 'select';
|
|
31
|
-
role:
|
|
47
|
+
role: ProfilePickerRole;
|
|
32
48
|
id: string | null;
|
|
33
49
|
} | {
|
|
34
50
|
type: 'edit';
|
|
@@ -67,7 +83,40 @@ export declare function finishProfileEditor(editor: ProfileEditorDraft): {
|
|
|
67
83
|
/** Make an isolated root draft; opening the screen never mutates live config. */
|
|
68
84
|
export declare function createModelProfileState(draft: ModelSettingsDraft): ModelProfileState;
|
|
69
85
|
/** Describe references using text so ASCII and no-color modes remain usable. */
|
|
70
|
-
export declare function profileUsers(profiles: ModelProfilesConfig | undefined, id: string): string;
|
|
86
|
+
export declare function profileUsers(profiles: ModelProfilesConfig | undefined, id: string, subagentProfileId?: string | null): string;
|
|
87
|
+
/**
|
|
88
|
+
* The team section a draft would save: the patch first, then the disk baseline
|
|
89
|
+
* (subagent-model §3.6). The explicit-`undefined` test is what makes an
|
|
90
|
+
* explicit `null` in the patch win over the baseline - `??` would fold it away.
|
|
91
|
+
*/
|
|
92
|
+
export declare function draftTeam(draft: ModelSettingsDraft): {
|
|
93
|
+
model: SubagentModelMode;
|
|
94
|
+
modelProfileId: string | null;
|
|
95
|
+
};
|
|
96
|
+
/** The profile id the draft's team section binds, for reference checks (3.7). */
|
|
97
|
+
export declare function draftSubagentProfileId(draft: ModelSettingsDraft): string | null;
|
|
98
|
+
/** The projected picker selection for the subagent role (6.2). */
|
|
99
|
+
export declare function subagentSelectedId(draft: ModelSettingsDraft): string | null;
|
|
100
|
+
/** What the settings root row and `/team model` show for the current mode. */
|
|
101
|
+
export interface SubagentModelSummaryInput {
|
|
102
|
+
mode: SubagentModelMode;
|
|
103
|
+
modelProfileId: string | null;
|
|
104
|
+
/** `<provider>:<model>` of the connection the main role runs on. */
|
|
105
|
+
mainConnection: string;
|
|
106
|
+
/** The fast tier's `<provider>:<model>`, or null when it does not resolve. */
|
|
107
|
+
fastConnection: string | null;
|
|
108
|
+
profiles: ModelProfilesConfig | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* KEY EXISTENCE AS THE SETTINGS SCREEN SEES IT (V-10): derived from the
|
|
111
|
+
* config-file keys, exactly like the `name()` row and `fastTierLine` beside
|
|
112
|
+
* it. A key that exists ONLY as an environment variable is invisible here,
|
|
113
|
+
* so an env-key user may see `(off ...)` / `[Missing API key]` where the
|
|
114
|
+
* runtime would succeed - the accepted approximation both sibling lines
|
|
115
|
+
* already ship.
|
|
116
|
+
*/
|
|
117
|
+
hasKey: (providerId: string) => boolean;
|
|
118
|
+
}
|
|
119
|
+
export declare function subagentModelSummary(input: SubagentModelSummaryInput): string;
|
|
71
120
|
/** Adopt persisted fields only, resolving the old fast inheritance once. */
|
|
72
121
|
export declare function createCustomProfile(draft: ModelSettingsDraft, role: ModelRole): ModelProfile;
|
|
73
122
|
/** All child actions modify only the root draft; persistence belongs to Save. */
|