@aragon-agent/cli 0.6.34 → 0.6.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3071 -3007
- package/dist/agent/controller.d.ts +29 -0
- package/dist/agent/controller.js +42 -1
- 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 +1 -1
- package/dist/commands/builtins.js +4 -0
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- 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 +16 -0
- package/dist/input/keymap.js +18 -4
- package/dist/ui/App.js +108 -12
- package/dist/ui/Header.d.ts +9 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.js +1 -1
- package/dist/ui/ScrollbackShell.d.ts +18 -3
- package/dist/ui/ScrollbackShell.js +1 -1
- package/dist/ui/StatusBar.d.ts +13 -0
- package/dist/ui/StatusBar.js +10 -3
- package/dist/ui/TeamPanel.d.ts +12 -0
- package/dist/ui/TeamPanel.js +10 -31
- package/dist/ui/Transcript.js +22 -2
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/layout/header-hints.d.ts +11 -1
- package/dist/ui/layout/header-hints.js +35 -3
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/virtual-window.js +9 -0
- package/dist/ui/overlays/HelpOverlay.js +7 -0
- 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/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/scrollback/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- 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 +141 -0
- package/dist/ui/team-status.js +224 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-wheel-routing.js +2 -2
- package/package.json +2 -2
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { ADAPTER_PROVIDERS, clampContextWindow, isAutoToken } from './schema.js';
|
|
3
|
+
import { normalizeKits, validateKits } from './model-kits.js';
|
|
3
4
|
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
|
|
4
5
|
/** Return a fresh empty library without initiating a disk migration. */
|
|
5
6
|
export function emptyModelProfiles() {
|
|
6
|
-
return { version: 1, entries: [], mainId: null, fastId: null
|
|
7
|
+
return { version: 1, entries: [], mainId: null, fastId: null, kits: [],
|
|
8
|
+
defaultKitId: null, kitsVersion: 1 };
|
|
7
9
|
}
|
|
8
10
|
/** Return independently mutable role state. */
|
|
9
11
|
export function emptyModelProfileState(invalid = false) {
|
|
@@ -69,7 +71,16 @@ function validateEntry(value, index) {
|
|
|
69
71
|
}
|
|
70
72
|
return issues;
|
|
71
73
|
}
|
|
72
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Validate untrusted input; diagnostics never include supplied values.
|
|
76
|
+
*
|
|
77
|
+
* THE KIT SECTION IS STRUCTURAL ONLY (agent-model-kits RV-P1-1). `mainId` /
|
|
78
|
+
* `fastId` above are `missing_reference` because a dangling ROLE BINDING leaves
|
|
79
|
+
* the whole library unreadable (there is no "nothing bound" recovery path once
|
|
80
|
+
* a bound id is gone), while a dangling KIT SLOT degrades at resolution time
|
|
81
|
+
* into `Profile missing` and a refused apply - the same discipline
|
|
82
|
+
* `team.modelProfileId` already follows (`schema.ts` clampTeamConfig).
|
|
83
|
+
*/
|
|
73
84
|
export function validateModelProfiles(value) {
|
|
74
85
|
if (value === undefined)
|
|
75
86
|
return [];
|
|
@@ -99,6 +110,7 @@ export function validateModelProfiles(value) {
|
|
|
99
110
|
message: 'Select an existing profile or Current custom.' });
|
|
100
111
|
}
|
|
101
112
|
}
|
|
113
|
+
issues.push(...validateKits(value.kits));
|
|
102
114
|
return issues;
|
|
103
115
|
}
|
|
104
116
|
/**
|
|
@@ -116,19 +128,29 @@ export function normalizeProfileContextWindow(value) {
|
|
|
116
128
|
return null;
|
|
117
129
|
return clampContextWindow(value, null);
|
|
118
130
|
}
|
|
119
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Normalize a valid library into a safe field whitelist; throws a fixed error if invalid.
|
|
133
|
+
*
|
|
134
|
+
* THE THREE KIT KEYS ARE LISTED EXPLICITLY, NEVER `...spread` (R-2): this
|
|
135
|
+
* literal IS the whitelist (F-3), so a key missing from it disappears at the
|
|
136
|
+
* next write - a save that only touched `thinking` would silently delete every
|
|
137
|
+
* kit the user owns. `normalizeKits` is TOTAL, so a hand-edited kit degrades
|
|
138
|
+
* instead of taking the library (and the credentials in it) with it.
|
|
139
|
+
*/
|
|
120
140
|
export function normalizeModelProfiles(value) {
|
|
121
141
|
if (validateModelProfiles(value).length)
|
|
122
142
|
throw new Error('Invalid model profiles.');
|
|
123
143
|
if (value === undefined)
|
|
124
144
|
return emptyModelProfiles();
|
|
125
145
|
const library = value;
|
|
146
|
+
const { kits, defaultKitId, kitsVersion } = normalizeKits(library.kits, library.defaultKitId);
|
|
126
147
|
return { version: 1, mainId: library.mainId, fastId: library.fastId,
|
|
127
148
|
entries: library.entries.map((entry) => ({ id: entry.id, name: entry.name.trim()
|
|
128
149
|
|| [...entry.model.trim()].slice(0, 80).join(''), provider: entry.provider,
|
|
129
150
|
model: entry.model.trim(), baseUrl: entry.baseUrl?.trim() || null,
|
|
130
151
|
apiKey: entry.apiKey?.trim() || null,
|
|
131
|
-
contextWindow: normalizeProfileContextWindow(entry.contextWindow) }))
|
|
152
|
+
contextWindow: normalizeProfileContextWindow(entry.contextWindow) })),
|
|
153
|
+
kits, defaultKitId, kitsVersion };
|
|
132
154
|
}
|
|
133
155
|
/** Create an entry with a stable UUID and a frozen default name. */
|
|
134
156
|
export function createModelProfile(input) {
|
|
@@ -139,9 +161,28 @@ export function createModelProfile(input) {
|
|
|
139
161
|
export function duplicateModelProfile(entry) {
|
|
140
162
|
return { ...entry, id: randomUUID(), name: `${[...entry.name].slice(0, 73).join('')} (copy)` };
|
|
141
163
|
}
|
|
142
|
-
/**
|
|
164
|
+
/** Ids of the kits whose three slots name `id`, for the reference message. */
|
|
165
|
+
export function kitNamesForProfile(library, id) {
|
|
166
|
+
return (library.kits ?? []).filter((kit) => kit.mainProfileId === id
|
|
167
|
+
|| kit.fastProfileId === id
|
|
168
|
+
|| (kit.subagent.model === 'profile' && kit.subagent.modelProfileId === id))
|
|
169
|
+
.map((kit) => kit.name);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Remove an unused entry; binding references must be changed explicitly first.
|
|
173
|
+
*
|
|
174
|
+
* The message NAMES the kits that would be orphaned (D-9): a throw that only
|
|
175
|
+
* says "still in use" sends the user hunting through three separate screens for
|
|
176
|
+
* the reference it means, and the kits are the reference nobody thinks of.
|
|
177
|
+
* (The UI never reaches this throw - it unbinds, and deletes the kits, inside
|
|
178
|
+
* the draft first - so this is the library-level backstop, F-25.)
|
|
179
|
+
*/
|
|
143
180
|
export function removeModelProfile(library, id) {
|
|
144
181
|
if (library.mainId === id || library.fastId === id)
|
|
145
182
|
throw new Error('Profile is still in use.');
|
|
183
|
+
const kits = kitNamesForProfile(library, id);
|
|
184
|
+
if (kits.length) {
|
|
185
|
+
throw new Error(`Profile is used by kit ${kits.map((name) => `"${name}"`).join(', ')}.`);
|
|
186
|
+
}
|
|
146
187
|
return { ...library, entries: library.entries.filter((entry) => entry.id !== id) };
|
|
147
188
|
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -1044,6 +1044,22 @@ export interface CliConfig {
|
|
|
1044
1044
|
modelSettingsRestartRequired?: boolean;
|
|
1045
1045
|
modelProfiles?: import('./model-profiles.js').ModelProfilesConfig;
|
|
1046
1046
|
modelProfileState?: import('./model-profiles.js').ModelProfilesRuntime;
|
|
1047
|
+
/**
|
|
1048
|
+
* The SESSION kit (agent-model-kits D-5 / §7.2). RUNTIME-ONLY.
|
|
1049
|
+
*
|
|
1050
|
+
* IT IS NOT IN `PersistedConfig` AND IT NEVER WILL BE (I-5 / I-14): `ctrl+K`
|
|
1051
|
+
* and `/kit use` promise "this session only", and that promise is worth more
|
|
1052
|
+
* than a saved convenience - it is what makes "one keystroke changed my
|
|
1053
|
+
* defaults" structurally impossible rather than merely unlikely.
|
|
1054
|
+
*
|
|
1055
|
+
* `held` is the only fact worth storing: WHICH slots still belong to the kit.
|
|
1056
|
+
* The bindings are derivable, and a stored copy of them would be a second
|
|
1057
|
+
* truth (I-7: every held slot must have been written into the live config).
|
|
1058
|
+
*/
|
|
1059
|
+
sessionKit?: {
|
|
1060
|
+
id: string;
|
|
1061
|
+
held: import('./model-kits.js').KitSlot[];
|
|
1062
|
+
};
|
|
1047
1063
|
apiKeyOverrideTarget?: {
|
|
1048
1064
|
provider: string;
|
|
1049
1065
|
baseUrl: string | null;
|
package/dist/input/keymap.js
CHANGED
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
* does not own so the caller can drop unknown control bytes instead of
|
|
8
8
|
* inserting them into the buffer.
|
|
9
9
|
*/
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Map a Ctrl-modified key to an intent, if any.
|
|
12
|
+
*
|
|
13
|
+
* `Ctrl+K` IS DELIBERATELY NOT HERE (agent-model-kits D-15). That byte belongs
|
|
14
|
+
* to the Agent-kit picker on the main screen, and this recognizer runs BEFORE
|
|
15
|
+
* `PromptInput`'s `if (key.ctrl) return` - so keeping the binding would delete
|
|
16
|
+
* the user's draft in one listener while the other opened the overlay. The
|
|
17
|
+
* capability moved to `Alt+K` (`fromMeta`) instead of being dropped.
|
|
18
|
+
*/
|
|
11
19
|
function fromCtrl(input, key) {
|
|
12
20
|
if (key.leftArrow)
|
|
13
21
|
return 'wordLeft';
|
|
@@ -22,8 +30,6 @@ function fromCtrl(input, key) {
|
|
|
22
30
|
return 'deleteWordBack';
|
|
23
31
|
case 'u':
|
|
24
32
|
return 'killToStart';
|
|
25
|
-
case 'k':
|
|
26
|
-
return 'killToEnd';
|
|
27
33
|
default:
|
|
28
34
|
return null;
|
|
29
35
|
}
|
|
@@ -34,6 +40,11 @@ function fromMeta(input) {
|
|
|
34
40
|
return 'wordLeft';
|
|
35
41
|
if (input === 'f')
|
|
36
42
|
return 'wordRight';
|
|
43
|
+
// `killToEnd` LIVES HERE NOW (agent-model-kits D-15). `Alt+K` keeps the
|
|
44
|
+
// capability, and it keeps the kill family together: `M-k` is kill-sentence in
|
|
45
|
+
// the Emacs tradition this keymap already borrows `Ctrl+W` and `Alt+B/F` from.
|
|
46
|
+
if (input === 'k')
|
|
47
|
+
return 'killToEnd';
|
|
37
48
|
if (input === '\x7f' || input === '\b')
|
|
38
49
|
return 'deleteWordBack';
|
|
39
50
|
return null;
|
|
@@ -63,7 +74,10 @@ function fromRaw(input) {
|
|
|
63
74
|
return 'deleteWordBack';
|
|
64
75
|
case '\x15': // Ctrl+U (raw)
|
|
65
76
|
return 'killToStart';
|
|
66
|
-
case '\
|
|
77
|
+
case '\x1bk':
|
|
78
|
+
// Alt+K (raw ESC + k). `\x0b` (Ctrl+K) is deliberately ABSENT: it belongs
|
|
79
|
+
// to the Agent-kit picker, and a case for it here would be a dead binding
|
|
80
|
+
// that still looks alive to every future reader.
|
|
67
81
|
return 'killToEnd';
|
|
68
82
|
default:
|
|
69
83
|
return null;
|
package/dist/ui/App.js
CHANGED
|
@@ -47,6 +47,7 @@ import { TODO_LIMITS } from '../todo/limits.js';
|
|
|
47
47
|
import { ScrollViewport } from './layout/ScrollViewport.js';
|
|
48
48
|
import { buildTodoRailLayout } from './layout/todo-layout.js';
|
|
49
49
|
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
50
|
+
import { isLiveTeamCard, rosterCounts } from './team-status.js';
|
|
50
51
|
import { advanceBudget, decideFollowThrough, emptyBudget, } from '../todo/follow-through.js';
|
|
51
52
|
import { useTerminalSize } from './layout/useTerminalSize.js';
|
|
52
53
|
import { useHeightStore } from './use-height-store.js';
|
|
@@ -62,6 +63,9 @@ import { publishSessionView } from './session-bridge.js';
|
|
|
62
63
|
import { OverlayFrame, overlayPageSize } from './layout/OverlayFrame.js';
|
|
63
64
|
import { helpRows } from './overlays/HelpOverlay.js';
|
|
64
65
|
import { ModelPicker } from './overlays/ModelPicker.js';
|
|
66
|
+
import { KitPicker } from './overlays/KitPicker.js';
|
|
67
|
+
import { INACTIVE_KIT } from '../config/kit-resolution.js';
|
|
68
|
+
import { kitApplyMessage, kitNoteMessages } from '../agent/kit-apply.js';
|
|
65
69
|
import { createModelProbe } from '../agent/model-probe.js';
|
|
66
70
|
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom, } from './overlays/SettingsScreen.js';
|
|
67
71
|
import { ConfirmDialog } from './overlays/ConfirmDialog.js';
|
|
@@ -1724,6 +1728,39 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1724
1728
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1725
1729
|
toast('info', `Model set to ${provider}:${model}.`);
|
|
1726
1730
|
};
|
|
1731
|
+
/**
|
|
1732
|
+
* `ctrl+K` chose a row (agent-model-kits §4.7 / AC-25).
|
|
1733
|
+
*
|
|
1734
|
+
* `null` is `Current setup`, which is the RELEASE action, not a no-op: it runs
|
|
1735
|
+
* `clearSessionKit` so the live connections go back to the file's bindings.
|
|
1736
|
+
* Both paths close the overlay, and both report through the same toast funnel -
|
|
1737
|
+
* including every `KitNote`, because a kit that turns the fast tier on spends
|
|
1738
|
+
* money and must never do it silently (§4.2 / F-23).
|
|
1739
|
+
*/
|
|
1740
|
+
const handleKitChoose = (kitId) => {
|
|
1741
|
+
// NO KIT PORT, NO CRASH: a legacy host can still press the key, and the only
|
|
1742
|
+
// honest answer is a sentence rather than a stack trace.
|
|
1743
|
+
const port = controller;
|
|
1744
|
+
if (typeof port.applySessionKit !== 'function' || typeof port.clearSessionKit !== 'function') {
|
|
1745
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
1746
|
+
toast('error', 'This build cannot switch kits.');
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
const result = kitId === null
|
|
1750
|
+
? controller.clearSessionKit() : controller.applySessionKit(kitId);
|
|
1751
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
1752
|
+
// The config is already updated synchronously, so this reads the kit that is
|
|
1753
|
+
// in effect RIGHT NOW rather than the id the user asked for - if the apply
|
|
1754
|
+
// fell back to anything, the toast says so.
|
|
1755
|
+
const applied = controller.getActiveKit().name;
|
|
1756
|
+
const text = !result.ok ? kitApplyMessage(result)
|
|
1757
|
+
: kitId === null ? "Kit released; using the config file's settings."
|
|
1758
|
+
: `Kit ${applied} applied for this session.`;
|
|
1759
|
+
const notes = kitNoteMessages(result.notes);
|
|
1760
|
+
// A NOTE IS A WARNING, NOT A SUCCESS (F-23): the toast colour is the only
|
|
1761
|
+
// signal a user gets that applying a kit can start spending money.
|
|
1762
|
+
toast(result.ok ? (notes.length ? 'warn' : 'success') : 'error', [text, ...notes].join(' '));
|
|
1763
|
+
};
|
|
1727
1764
|
const closeConfirm = () => {
|
|
1728
1765
|
setConfirmState(null);
|
|
1729
1766
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
@@ -1967,6 +2004,24 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1967
2004
|
setStatusExpanded(value => !value);
|
|
1968
2005
|
return;
|
|
1969
2006
|
}
|
|
2007
|
+
// Ctrl+K: the Agent-kit picker (agent-model-kits D-15 / §4.7).
|
|
2008
|
+
//
|
|
2009
|
+
// NOT AN INPUT FRAME, unlike Ctrl+I below. Ctrl+I collides with the Tab byte,
|
|
2010
|
+
// so a stdin translator has to disambiguate it; 0x0B is unambiguous in every
|
|
2011
|
+
// encoding this app supports (utf8 byte, win32-input-mode's pass-through of
|
|
2012
|
+
// control chars, and CSI-u's `ctrlByte` fold), so the raw byte IS the key.
|
|
2013
|
+
//
|
|
2014
|
+
// `killToEnd` HAS MOVED TO Alt+K FIRST (keymap.ts): `PromptInput`'s
|
|
2015
|
+
// recognizer runs before its `key.ctrl` early-return, so leaving the old
|
|
2016
|
+
// binding would delete the draft in one listener while this one opened the
|
|
2017
|
+
// overlay. Two listeners both see the event; `return` only leaves one.
|
|
2018
|
+
const isCtrlK = (key.ctrl && (input === 'k' || input === 'K')) || input === '\x0b';
|
|
2019
|
+
if (isCtrlK) {
|
|
2020
|
+
if (tooSmall || stateRef.current.overlay)
|
|
2021
|
+
return;
|
|
2022
|
+
dispatch({ type: 'setOverlay', overlay: 'kit' });
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
1970
2025
|
// Ctrl+I arrives as INDEX_KEY_FRAME, never as key.ctrl + 'i': legacy
|
|
1971
2026
|
// terminals encode it as the Tab byte and the stdin filter's translators
|
|
1972
2027
|
// are the only layer that can tell them apart (see input/limits.ts).
|
|
@@ -2004,7 +2059,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2004
2059
|
const overlay = stateRef.current.overlay;
|
|
2005
2060
|
// Modes A only. `model` / `confirm` manage their own keys; registering an
|
|
2006
2061
|
// arrow handler for them would double every keypress (R-12).
|
|
2007
|
-
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan'
|
|
2062
|
+
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan'
|
|
2063
|
+
|| overlay === 'queue' || overlay === 'kit';
|
|
2008
2064
|
if (key.pageUp) {
|
|
2009
2065
|
if (controlled)
|
|
2010
2066
|
setOverlayScroll((n) => Math.max(0, n - overlayPage));
|
|
@@ -2114,10 +2170,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2114
2170
|
const entries = stateRef.current.entries;
|
|
2115
2171
|
const card = [...entries]
|
|
2116
2172
|
.reverse()
|
|
2117
|
-
.find((e) =>
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2173
|
+
.find((e) =>
|
|
2174
|
+
// THE LIVE DISPATCH CARD IS NOT EXPANDABLE (tui-subagent-double-roster
|
|
2175
|
+
// A-2'): it is suppressed on screen, so expanding it would print a frozen
|
|
2176
|
+
// copy of a card that is still being rewritten. Once it settles it becomes
|
|
2177
|
+
// an ordinary history card and this key reaches it again.
|
|
2178
|
+
!isLiveTeamCard(e) &&
|
|
2179
|
+
(e.kind === 'tool' ||
|
|
2180
|
+
e.kind === 'team' ||
|
|
2181
|
+
e.kind === 'compaction' ||
|
|
2182
|
+
(e.kind === 'service' && !e.terminal)));
|
|
2121
2183
|
// NATIVE MODE ONLY. The fallback exists because native mode cannot scroll inside the
|
|
2122
2184
|
// frame - the answer's head is reachable only if it is printed. Full-screen mode has a
|
|
2123
2185
|
// viewport that already reaches it, and it has no `expanded` rendering for an answer,
|
|
@@ -2224,6 +2286,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2224
2286
|
const overlay = state.overlay;
|
|
2225
2287
|
const settingsDraft = useMemo(() => overlay === 'settings'
|
|
2226
2288
|
? controller.createModelSettingsDraft() : undefined, [controller, overlay]);
|
|
2289
|
+
// THE FILE'S BINDINGS, FOR THE KIT PICKER'S FIRST ROW (§4.6 / D-13). While a
|
|
2290
|
+
// session kit is held the live config mirrors the KIT (I-6), so `Current setup`
|
|
2291
|
+
// can only describe what choosing it restores by reading the file - one read per
|
|
2292
|
+
// opening, the same price `settingsDraft` one line up already pays.
|
|
2293
|
+
const kitDiskConfig = useMemo(() => overlay === 'kit'
|
|
2294
|
+
? controller.createModelSettingsDraft().baseline : undefined, [controller, overlay]);
|
|
2227
2295
|
// ONE PROBE PER APP (model-profile-connection-test 5 row 9): the port needs the
|
|
2228
2296
|
// controller's provider registry, which the overlay has no business owning.
|
|
2229
2297
|
// Built only while the settings overlay is open, so no other screen pays for it.
|
|
@@ -2237,6 +2305,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2237
2305
|
return createModelProbe({ registry: controller.getProviderRegistry() });
|
|
2238
2306
|
}, [controller, overlay]);
|
|
2239
2307
|
const hasKey = controller.hasApiKey();
|
|
2308
|
+
// THE ACTIVE KIT, DERIVED PER FRAME (D-5 / F-22): `controller.getActiveKit()` is
|
|
2309
|
+
// pure, so it cannot go stale the way a cached copy would after `ctrl+K`,
|
|
2310
|
+
// `/model` or a saved default. Absent when no kit is in effect, which is what
|
|
2311
|
+
// keeps the status row byte-identical for users who own none (I-9).
|
|
2312
|
+
//
|
|
2313
|
+
// A TEST DOUBLE OR AN EMBEDDED HOST MAY PREDATE THIS PORT: the same tolerance
|
|
2314
|
+
// the probe port gets one screen over ("absent means the rows read
|
|
2315
|
+
// unresolved") applies here - no kit, no crash.
|
|
2316
|
+
const activeKit = typeof controller.getActiveKit === 'function'
|
|
2317
|
+
? controller.getActiveKit() : INACTIVE_KIT;
|
|
2318
|
+
const kitChip = activeKit.source === 'none' ? undefined : {
|
|
2319
|
+
name: activeKit.name,
|
|
2320
|
+
modified: activeKit.modified,
|
|
2321
|
+
otherNames: (cfg.modelProfiles?.kits ?? [])
|
|
2322
|
+
.filter((kit) => kit.id !== activeKit.id).map((kit) => kit.name),
|
|
2323
|
+
};
|
|
2240
2324
|
const empty = state.entries.length === 0;
|
|
2241
2325
|
// RENDER SCOPE, so the chip and the composer hint agree with what the Ctrl+C
|
|
2242
2326
|
// handler will do on the very next keystroke.
|
|
@@ -2340,7 +2424,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2340
2424
|
// ALWAYS CALLED, INERT WHEN `enabled` IS FALSE: hooks cannot be conditional, and
|
|
2341
2425
|
// an inert hook is what keeps `scrollMode: 'app'` byte-identical (I-S9).
|
|
2342
2426
|
const viewCols = native ? Math.max(1, cols - 1) : cols;
|
|
2343
|
-
const header = (_jsx(Header, { columns: viewCols, statusExpanded: statusExpanded, scrolledLines: scrolledLines, overlayOpen: overlay !== null, cwd: controller.getCwd(), provider: cfg.provider, model: cfg.model, hasKey: hasKey, variant: pickHeaderVariant(cols), theme: theme, caps: caps
|
|
2427
|
+
const header = (_jsx(Header, { columns: viewCols, statusExpanded: statusExpanded, scrolledLines: scrolledLines, overlayOpen: overlay !== null, cwd: controller.getCwd(), provider: cfg.provider, model: cfg.model, hasKey: hasKey, variant: pickHeaderVariant(cols), theme: theme, caps: caps,
|
|
2428
|
+
// ALWAYS TRUE (agent-model-kits §4.7 / RV-P1-2): the requirement is that
|
|
2429
|
+
// `ctrl+G` reveals `ctrl+K`, and the user with an empty library needs that
|
|
2430
|
+
// hint most - the picker itself carries the "create one in /settings" row.
|
|
2431
|
+
kitHint: true }));
|
|
2344
2432
|
// The header and the opener are SLOTS OF THE SHELL, not printed bytes (D3/D4): at
|
|
2345
2433
|
// `H = rows - 1` a printed row is pushed off the screen by the very next frame, so
|
|
2346
2434
|
// `printBanner()` was a way of making the banner invisible. Neither is passed here.
|
|
@@ -2391,7 +2479,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2391
2479
|
const openerCols = nativeBudget ? nativeBudget.previewCols : contentCols;
|
|
2392
2480
|
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(openerRows, openerCols), version: version, cwd: controller.getCwd(), hasKey: hasKey, viewportRows: openerRows, theme: theme, caps: caps })) : null;
|
|
2393
2481
|
const overlayMaxRows = nativeBudget ? nativeBudget.overlayRows : viewportBudget;
|
|
2394
|
-
const overlayNode = overlay === 'queue' ? (_jsx(QueueOverlay, { pending: state.pendingSteering, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'help' ? (_jsx(OverlayFrame, { title: "Help", hint: `Esc close ${glyphs.midDot} PgUp/PgDn scroll`, maxRows: overlayMaxRows, cols: viewCols, rows: helpRows(theme, caps, native), scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'model' ? (_jsx(ModelPicker, { isActive: !tooSmall, registry: controller.getModelRegistry(), currentProvider: cfg.provider, currentModel: cfg.model, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSelect: handleModelSelect })) : overlay === '
|
|
2482
|
+
const overlayNode = overlay === 'queue' ? (_jsx(QueueOverlay, { pending: state.pendingSteering, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'help' ? (_jsx(OverlayFrame, { title: "Help", hint: `Esc close ${glyphs.midDot} PgUp/PgDn scroll`, maxRows: overlayMaxRows, cols: viewCols, rows: helpRows(theme, caps, native), scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'model' ? (_jsx(ModelPicker, { isActive: !tooSmall, registry: controller.getModelRegistry(), currentProvider: cfg.provider, currentModel: cfg.model, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSelect: handleModelSelect })) : overlay === 'kit' ? (_jsx(KitPicker, { isActive: !tooSmall, library: cfg.modelProfiles, config: cfg, ...(kitDiskConfig ? { diskConfig: kitDiskConfig } : {}), active: activeKit,
|
|
2483
|
+
// ONLY THE APPLY IS BLOCKED while work is in flight (§4.7): a list a
|
|
2484
|
+
// user cannot even OPEN reads as a broken key.
|
|
2485
|
+
busy: controller.isModelSettingsBusy(), maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onChoose: handleKitChoose, onClose: () => dispatch({ type: 'setOverlay', overlay: null }) })) : overlay === 'settings' ? (_jsx(SettingsScreen, { isActive: !tooSmall, initial: {
|
|
2395
2486
|
provider: cfg.provider,
|
|
2396
2487
|
model: cfg.model,
|
|
2397
2488
|
baseUrl: cfg.baseUrl ?? '',
|
|
@@ -2524,8 +2615,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2524
2615
|
compacting: compactionLive || !!state.compaction?.inFlight,
|
|
2525
2616
|
mode: state.agentMode, pendingMode: state.pendingAgentMode,
|
|
2526
2617
|
servicesActive: { live: liveServices }, ecoRung: governor.rung,
|
|
2527
|
-
|
|
2528
|
-
|
|
2618
|
+
// THE STATUS-DETAIL ROW COUNTS LIKE THE ROSTER AND THE CHIP (A-1): this was the
|
|
2619
|
+
// last inline phase filter, left behind when the StatusBar chip converged, and it
|
|
2620
|
+
// feeds the `Team n/m` on the second status row.
|
|
2621
|
+
teamActive: state.team
|
|
2622
|
+
? { running: rosterCounts(state.team.runs).running, total: state.team.runs.length }
|
|
2623
|
+
: undefined,
|
|
2529
2624
|
todoActive: state.todos ? { done: state.todos.doneCount, total: state.todos.total } : undefined,
|
|
2530
2625
|
fastActive: state.fast?.live ? { inFlight: state.fast.inFlight } : undefined,
|
|
2531
2626
|
...feedbackProjection,
|
|
@@ -2558,7 +2653,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2558
2653
|
const rail = fullscreenRailVisible && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2559
2654
|
const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: overlayNode, footer: _jsxs(_Fragment, { children: [team, _jsx(QueuePanel, { layout: queueLayout, theme: theme })] }), rail: rail, scrollbar: terminal?.scrollbar, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, onGeometryChange: onGeometryChange, geometryKey: `${rows}:${cols}:${viewportBudget}:${frameBudget.composerSlotRows}:${frameBudget.statusRows}:${contentCols}:${overlay}:${tooSmall}`, tailRowsRef: tailSink, hold: selectionHold, resumeMs: cfg.scrollResumeMs, showScrollIndicator: true, cols: contentCols, theme: theme, caps: caps, children: [opener, _jsx(TranscriptList, { entries: transcriptEntries, expandedToolIds: state.expandedToolIds, thinkingVisible: state.thinkingVisible, reducedMotion: viewReducedMotion, density: density, nowSec: nowSec, theme: theme, caps: caps, windowSize: transcriptWindow, cols: contentCols, heights: heights, mountedSink: mountedCount, tailSink: tailSink })] }));
|
|
2560
2655
|
const detailsNode = _jsx(BottomStatusRow, { plan: details, columns: viewCols, theme: theme });
|
|
2561
|
-
const statusNode = (_jsx(StatusBar, { columns: viewCols, speedKnown: speedKnown, ...feedbackProjection, interruptPhase: interruptGesture.current.phase, pendingSteering: state.pendingSteering, runPhase: state.runPhase, activeTool: state.activeTool, ...(wait ? { wait } : {}), runOutcome: state.runOutcome, reducedMotion: reducedMotion, ...(sessionId ? { sessionId } : {}), waitingForConfirmation: !!humanRequest || !!confirmState, stopping: interruptGesture.current.phase === 'stopping', interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2656
|
+
const statusNode = (_jsx(StatusBar, { columns: viewCols, speedKnown: speedKnown, ...feedbackProjection, interruptPhase: interruptGesture.current.phase, pendingSteering: state.pendingSteering, runPhase: state.runPhase, activeTool: state.activeTool, ...(wait ? { wait } : {}), runOutcome: state.runOutcome, reducedMotion: reducedMotion, ...(sessionId ? { sessionId } : {}), ...(kitChip ? { kit: kitChip } : {}), waitingForConfirmation: !!humanRequest || !!confirmState, stopping: interruptGesture.current.phase === 'stopping', interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2562
2657
|
? 'Esc force-stop' : 'Esc x2 interrupt', model: cfg.model, provider: cfg.provider, usageTotal: state.usageTotal,
|
|
2563
2658
|
// ONE OBJECT INSTEAD OF FOUR PARALLEL PROPS (§4.3). The four were a
|
|
2564
2659
|
// projection of "many sources of truth" into the props layer, and a
|
|
@@ -2568,9 +2663,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2568
2663
|
// `contextWindow` override reach the bar.
|
|
2569
2664
|
context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.agentMode, pendingAgentMode: state.pendingAgentMode, ecoRung: governor.rung, ...(state.team
|
|
2570
2665
|
? {
|
|
2666
|
+
// `rosterCounts`, NOT a fourth inline phase filter (A-1): the chip and
|
|
2667
|
+
// the roster header above it must never disagree.
|
|
2571
2668
|
teamActive: {
|
|
2572
|
-
running: state.team.runs
|
|
2573
|
-
r.phase !== 'failed' && r.phase !== 'aborted').length,
|
|
2669
|
+
running: rosterCounts(state.team.runs).running,
|
|
2574
2670
|
total: state.team.runs.length,
|
|
2575
2671
|
},
|
|
2576
2672
|
}
|
package/dist/ui/Header.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ interface HeaderProps {
|
|
|
15
15
|
statusExpanded?: boolean;
|
|
16
16
|
scrolledLines?: number;
|
|
17
17
|
overlayOpen?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the expanded status row teaches `ctrl+K` (agent-model-kits §4.7).
|
|
20
|
+
*
|
|
21
|
+
* DEFAULTS TO TRUE because that is the requirement - `ctrl+G` must reveal the
|
|
22
|
+
* kit key - and because a default of `false` would leave the one caller that
|
|
23
|
+
* forgets the prop silently teaching nothing.
|
|
24
|
+
*/
|
|
25
|
+
kitHint?: boolean;
|
|
18
26
|
}
|
|
19
|
-
export declare function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns, statusExpanded, scrolledLines, overlayOpen }: HeaderProps): React.ReactElement;
|
|
27
|
+
export declare function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns, statusExpanded, scrolledLines, overlayOpen, kitHint }: HeaderProps): React.ReactElement;
|
|
20
28
|
export {};
|
package/dist/ui/Header.js
CHANGED
|
@@ -3,13 +3,19 @@ import { basename } from 'node:path';
|
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { Logo } from './Logo.js';
|
|
5
5
|
import { planHeaderHints, HEADER_HINT_LIMITS } from './layout/header-hints.js';
|
|
6
|
-
export function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns = 80, statusExpanded = false, scrolledLines = 0, overlayOpen = false }) {
|
|
6
|
+
export function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns = 80, statusExpanded = false, scrolledLines = 0, overlayOpen = false, kitHint = true }) {
|
|
7
7
|
// The right-hand hints are planned, not assembled (tui-branch-terminal-shortcut
|
|
8
8
|
// 3.9 / D-11). `plan.text` is exactly `plan.cells` wide and already carries the
|
|
9
9
|
// scroll chip's own trailing spacing, so this component can no longer clip a
|
|
10
10
|
// hint by guessing one cell short - and the ladder that decides which hints
|
|
11
11
|
// survive a narrow terminal lives in a pure function with its own tests.
|
|
12
|
-
|
|
12
|
+
//
|
|
13
|
+
// `kitHint` is the `^K switch kit` clause (agent-model-kits §4.7): `App`
|
|
14
|
+
// passes `true` always, because the requirement is that `ctrl+G` reveals the
|
|
15
|
+
// key - and the user with no kits needs that hint most.
|
|
16
|
+
const plan = planHeaderHints({
|
|
17
|
+
columns, statusExpanded, overlayOpen, scrolledLines, kitHint,
|
|
18
|
+
});
|
|
13
19
|
// THE TWO HALVES ARE SEPARATED BY CELLS, NOT BY LUCK (defect 12). The left half
|
|
14
20
|
// is `wrap="truncate"`, which draws its ellipsis in the LAST cell it owns; with
|
|
15
21
|
// the right half starting in the very next cell a narrow terminal read
|
package/dist/ui/PromptInput.js
CHANGED
|
@@ -596,7 +596,7 @@ export function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, st
|
|
|
596
596
|
return;
|
|
597
597
|
}
|
|
598
598
|
if (key.ctrl)
|
|
599
|
-
return; // App owns Ctrl+C / L / T / O.
|
|
599
|
+
return; // App owns Ctrl+C / L / T / O, and now Ctrl+K (the kit picker).
|
|
600
600
|
if (key.return) {
|
|
601
601
|
if (key.meta || key.shift) {
|
|
602
602
|
insert('\n');
|
|
@@ -19,11 +19,22 @@
|
|
|
19
19
|
* default mode showed neither. The band is a row pool inside the live region:
|
|
20
20
|
*
|
|
21
21
|
* +---------------------------------------------+-----------------+
|
|
22
|
-
* |
|
|
22
|
+
* | tail window (height = band.previewRows) | TODO rail |
|
|
23
23
|
* +---------------------------------------------+ (height = |
|
|
24
|
-
* |
|
|
24
|
+
* | team roster (height = band.teamRows) | band.railRows) |
|
|
25
25
|
* +---------------------------------------------+-----------------+
|
|
26
26
|
*
|
|
27
|
+
* THE ROSTER IS THE LEFT COLUMN'S BOTTOM SLOT, not its top one
|
|
28
|
+
* (tui-subagent-double-roster A-3). `band.ts` allocates the same two heights either
|
|
29
|
+
* way - only the drawing order changed - so every row number, and every test that
|
|
30
|
+
* asserts one, is untouched. What moves is the reading: the roster is transient
|
|
31
|
+
* status about the run in progress, so it belongs against the composer (where the
|
|
32
|
+
* user's eyes already are, and where full-screen mode's viewport `footer` puts it),
|
|
33
|
+
* and the conversation's own tail stays above it. As a bonus the rows a short tail
|
|
34
|
+
* does not use now fall between the tail and the roster instead of between the tail
|
|
35
|
+
* and the input box, which is the "blank space above the composer" shape the
|
|
36
|
+
* plan-A-prime work removed one level up.
|
|
37
|
+
*
|
|
27
38
|
* Every box in it has an explicit `height` and `width` plus `overflow="hidden"`, and
|
|
28
39
|
* that is a correctness requirement rather than tidiness: a box that grows with its
|
|
29
40
|
* content would make the region taller than `budget.liveRows`, and a frame as tall as
|
|
@@ -76,7 +87,11 @@ export interface ScrollbackShellProps {
|
|
|
76
87
|
opener?: React.ReactNode;
|
|
77
88
|
/** Real components for the tail window (`LiveTail`). */
|
|
78
89
|
tail?: React.ReactNode;
|
|
79
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* The subagent roster: the BOTTOM slot of the band's left column, i.e. the row
|
|
92
|
+
* closest to the composer. It is the only live subagent surface during a
|
|
93
|
+
* dispatch - the transcript's `team` card is suppressed while it is active.
|
|
94
|
+
*/
|
|
80
95
|
team?: React.ReactNode;
|
|
81
96
|
/** The TODO rail, drawn as the band's right-hand column. */
|
|
82
97
|
rail?: React.ReactNode;
|
|
@@ -14,6 +14,6 @@ export function ScrollbackShell({ budget, header, overlay, liveTailLines, opener
|
|
|
14
14
|
// The overlay and the band are alternatives, not siblings: the band reports
|
|
15
15
|
// `bandRows = remaining` for an overlay frame, so drawing both would double the
|
|
16
16
|
// region's height and take the frame to the terminal height (the `ESC[3J` case).
|
|
17
|
-
const top = band.reason === 'overlay' ? (_jsx(Box, { flexDirection: "column", height: budget.overlayRows, flexShrink: 0, overflow: "hidden", children: overlay })) : band.bandRows > 0 ? (_jsxs(Box, { flexDirection: "row", height: band.bandRows, flexShrink: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: budget.previewCols, flexShrink: 0, overflow: "hidden", children: [band.
|
|
17
|
+
const top = band.reason === 'overlay' ? (_jsx(Box, { flexDirection: "column", height: budget.overlayRows, flexShrink: 0, overflow: "hidden", children: overlay })) : band.bandRows > 0 ? (_jsxs(Box, { flexDirection: "row", height: band.bandRows, flexShrink: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: budget.previewCols, flexShrink: 0, overflow: "hidden", children: [band.previewRows > 0 && (_jsx(Box, { flexDirection: "column", height: band.previewRows, flexShrink: 0, justifyContent: "flex-start", overflow: "hidden", children: leftColumn ?? tailLines.map((line, index) => (_jsx(Text, { wrap: "truncate", color: theme.muted, children: line }, index))) })), band.teamRows > 0 && (_jsx(Box, { height: band.teamRows, flexShrink: 0, overflow: "hidden", children: team }))] }), band.railRows > 0 && (_jsx(Box, { height: band.railRows, width: band.railCols, flexShrink: 0, overflow: "hidden", children: rail }))] })) : null;
|
|
18
18
|
return (_jsxs(Box, { flexDirection: "column", height: budget.liveRows, width: width, overflow: "hidden", flexShrink: 0, children: [budget.headerRows > 0 && (_jsx(Box, { height: budget.headerRows, flexShrink: 0, overflow: "hidden", children: header })), top, _jsx(Box, { flexDirection: "column", height: budget.composerSlotRows, justifyContent: "flex-end", flexShrink: 0, overflow: "hidden", children: composer }), _jsx(Box, { height: budget.statusRows > 0 ? 1 : 0, flexShrink: 0, overflow: "hidden", children: status }), budget.statusRows === 2 && (_jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: details }))] }));
|
|
19
19
|
}
|
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -50,6 +50,19 @@ export interface StatusBarProps {
|
|
|
50
50
|
* existing test render exactly what they rendered before (I-6).
|
|
51
51
|
*/
|
|
52
52
|
sessionId?: string;
|
|
53
|
+
/**
|
|
54
|
+
* The agent kit in effect, or absent when there is none (agent-model-kits
|
|
55
|
+
* §4.8 / D-12).
|
|
56
|
+
*
|
|
57
|
+
* OPTIONAL AND ABSENT BY DEFAULT, for the same reason `sessionId` is: with no
|
|
58
|
+
* kit the plan is hidden, `kitCells` is 0, and this row's output is
|
|
59
|
+
* byte-identical to the pre-feature build (I-9).
|
|
60
|
+
*/
|
|
61
|
+
kit?: {
|
|
62
|
+
name: string;
|
|
63
|
+
modified: boolean;
|
|
64
|
+
otherNames: readonly string[];
|
|
65
|
+
} | null;
|
|
53
66
|
agentMode?: AgentMode;
|
|
54
67
|
pendingAgentMode?: AgentMode | null;
|
|
55
68
|
teamActive?: {
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -6,6 +6,7 @@ import { pickGlyphs } from './glyphs.js';
|
|
|
6
6
|
import { liveSpinner } from './ActivityLine.js';
|
|
7
7
|
import { planPrimaryStatusFields } from './layout/status-layout.js';
|
|
8
8
|
import { planSessionChip } from './layout/session-chip.js';
|
|
9
|
+
import { planKitChip } from './layout/kit-chip.js';
|
|
9
10
|
/** An invisible leading byte change forces Ink to repaint on Ctrl+L. */
|
|
10
11
|
const redrawChar = (nonce) => nonce % 2 ? String.fromCharCode(0x00a0) : ' ';
|
|
11
12
|
export function StatusBar(props) {
|
|
@@ -21,8 +22,14 @@ export function StatusBar(props) {
|
|
|
21
22
|
...(props.sessionId ? { sessionId: props.sessionId } : {}),
|
|
22
23
|
});
|
|
23
24
|
const chipCells = chip.visible ? chip.cells + chip.gap : 0;
|
|
25
|
+
// THE KIT CHIP IS PLANNED SECOND, OUT OF WHAT THE SESSION CHIP LEFT (D-12):
|
|
26
|
+
// the session id is never truncated, so it is not the thing that gives way.
|
|
27
|
+
// With no kit the plan is hidden and `kitCells` is 0, which keeps this row
|
|
28
|
+
// byte-identical to the build before the feature (I-9).
|
|
29
|
+
const kit = planKitChip({ columns: cols - chipCells, kit: props.kit });
|
|
30
|
+
const kitCells = kit.visible ? kit.cells + kit.gap : 0;
|
|
24
31
|
const plan = planPrimaryStatusFields({
|
|
25
|
-
reserveRight: chipCells,
|
|
32
|
+
reserveRight: chipCells + kitCells,
|
|
26
33
|
columns: cols, phase, pendingCount: props.pendingSteering?.length ?? 0,
|
|
27
34
|
context: props.context, usageTotal: props.usageTotal,
|
|
28
35
|
thinkingLevel: props.thinkingLevel, elapsedMs: props.elapsedMs, tokPerSec: props.tokPerSec,
|
|
@@ -39,7 +46,7 @@ export function StatusBar(props) {
|
|
|
39
46
|
const tones = { normal: props.theme.primary, muted: props.theme.muted,
|
|
40
47
|
warning: props.theme.noticeWarn, error: props.theme.noticeError,
|
|
41
48
|
accent: props.theme.accent };
|
|
42
|
-
return _jsxs(Box, { width: cols, height: 1, flexShrink: 0, flexDirection: "row", overflow: "hidden", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: redrawChar(props.redrawNonce ?? 0) }) }), _jsx(Box, { width: Math.max(0, cols - 1 - chipCells), flexShrink: 0, flexDirection: "row", overflow: "hidden", children: plan.fields.map((item, index) => _jsxs(React.Fragment, { children: [index > 0 && _jsx(Box, { width: plan.separatorCells, flexShrink: 0, children: _jsx(Text, { color: props.theme.muted, children: plan.separator }) }), _jsx(Box, { width: item.cells, flexShrink: 0, children: _jsx(Text, { color: tones[item.tone], wrap: "truncate", children: item.id === 'phase' && active
|
|
49
|
+
return _jsxs(Box, { width: cols, height: 1, flexShrink: 0, flexDirection: "row", overflow: "hidden", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: redrawChar(props.redrawNonce ?? 0) }) }), _jsx(Box, { width: Math.max(0, cols - 1 - chipCells - kitCells), flexShrink: 0, flexDirection: "row", overflow: "hidden", children: plan.fields.map((item, index) => _jsxs(React.Fragment, { children: [index > 0 && _jsx(Box, { width: plan.separatorCells, flexShrink: 0, children: _jsx(Text, { color: props.theme.muted, children: plan.separator }) }), _jsx(Box, { width: item.cells, flexShrink: 0, children: _jsx(Text, { color: tones[item.tone], wrap: "truncate", children: item.id === 'phase' && active
|
|
43
50
|
? _jsxs(_Fragment, { children: [spinner ?? glyph, item.text.slice(glyph.length)] })
|
|
44
|
-
: item.text }) })] }, item.id)) }), chip.visible && _jsx(Box, { width: chipCells, flexShrink: 0, justifyContent: "flex-end", overflow: "hidden", children: _jsx(Text, { color: props.theme.muted, wrap: "truncate", children: chip.text }) })] });
|
|
51
|
+
: item.text }) })] }, item.id)) }), kit.visible && _jsx(Box, { width: kitCells, flexShrink: 0, justifyContent: "flex-end", overflow: "hidden", children: _jsx(Text, { color: props.theme.muted, wrap: "truncate", children: kit.text }) }), chip.visible && _jsx(Box, { width: chipCells, flexShrink: 0, justifyContent: "flex-end", overflow: "hidden", children: _jsx(Text, { color: props.theme.muted, wrap: "truncate", children: chip.text }) })] });
|
|
45
52
|
}
|
package/dist/ui/TeamPanel.d.ts
CHANGED
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|
* with the other transient rows — toast, composer, status bar — rather than
|
|
16
16
|
* inside the scrolling transcript, where it would be interleaved with history
|
|
17
17
|
* and scroll out of sight exactly when the user wants to watch it.
|
|
18
|
+
*
|
|
19
|
+
* IT IS THE ONLY LIVE SUBAGENT SURFACE (tui-subagent-double-roster A-2). The
|
|
20
|
+
* transcript's `team` card is suppressed while its dispatch is active
|
|
21
|
+
* (`team-status.ts::isLiveTeamCard`), so this roster is what the user watches
|
|
22
|
+
* during a dispatch and the card becomes the history afterwards. Native mode
|
|
23
|
+
* draws it as the BOTTOM slot of the band's left column (`ScrollbackShell`),
|
|
24
|
+
* i.e. directly above the composer; full-screen mode draws it in the viewport
|
|
25
|
+
* footer, which is already above the composer.
|
|
26
|
+
*
|
|
27
|
+
* EVERY PHASE -> GLYPH / COLOUR / COUNT DECISION COMES FROM `team-status.ts`.
|
|
28
|
+
* The local copies this file used to carry are gone on purpose: the transcript
|
|
29
|
+
* card kept a second copy and the two disagreed about what "running" looks like.
|
|
18
30
|
*/
|
|
19
31
|
import React from 'react';
|
|
20
32
|
import type { Theme } from './theme.js';
|