@pellux/goodvibes-tui 0.27.0 → 0.29.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.
- package/CHANGELOG.md +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -21,46 +21,148 @@ export interface TabStripStyle {
|
|
|
21
21
|
readonly trailingFg?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Absolute column range a rendered tab occupies, for click hit-testing. */
|
|
25
|
+
export interface TabHitRegion {
|
|
26
|
+
/** Index into the original `tabs` array. */
|
|
27
|
+
readonly index: number;
|
|
28
|
+
/** First column (inclusive) the tab occupies on the line. */
|
|
29
|
+
readonly startCol: number;
|
|
30
|
+
/** Last column (exclusive) the tab occupies on the line. */
|
|
31
|
+
readonly endCol: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
export interface TabStripConfig {
|
|
25
35
|
readonly width: number;
|
|
26
36
|
readonly tabs: readonly TabStripItem[];
|
|
27
37
|
readonly style: TabStripStyle;
|
|
28
38
|
readonly prefixLabel?: string;
|
|
29
39
|
readonly suffixLabel?: string;
|
|
40
|
+
/** Right-side overflow glyph (more tabs after the window). Default '›'. */
|
|
30
41
|
readonly overflowIndicator?: string;
|
|
42
|
+
/** Left-side overflow glyph (more tabs before the window). Default '‹'. */
|
|
43
|
+
readonly overflowIndicatorLeft?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Optional callback invoked with the absolute column ranges of each rendered
|
|
46
|
+
* tab. Used by the compositor to hit-test mouse clicks onto tabs. Additive —
|
|
47
|
+
* the function still returns a `Line` so existing callers are unaffected.
|
|
48
|
+
*/
|
|
49
|
+
readonly onLayout?: (regions: readonly TabHitRegion[]) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SEPARATOR = '│';
|
|
53
|
+
|
|
54
|
+
interface WindowResult {
|
|
55
|
+
segments: StyledPanelSegment[];
|
|
56
|
+
/** Per-tab column ranges relative to the start of the tab block. */
|
|
57
|
+
regions: Array<{ index: number; startCol: number; endCol: number }>;
|
|
58
|
+
overflowLeft: boolean;
|
|
59
|
+
overflowRight: boolean;
|
|
31
60
|
}
|
|
32
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Build the visible tab segments, windowed so the ACTIVE tab is always shown.
|
|
64
|
+
*
|
|
65
|
+
* Tabs are laid out left-to-right, but when they don't all fit we keep a
|
|
66
|
+
* contiguous window that contains the active tab and grow it outward (biased
|
|
67
|
+
* toward following tabs) until the budget is exhausted. Left/right overflow is
|
|
68
|
+
* reported so the caller can render `‹`/`›` indicators.
|
|
69
|
+
*/
|
|
33
70
|
function makeSegments(
|
|
34
71
|
tabs: readonly TabStripItem[],
|
|
35
72
|
style: TabStripStyle,
|
|
36
73
|
maxWidth: number,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
indicatorWidth: number,
|
|
75
|
+
): WindowResult {
|
|
76
|
+
const empty: WindowResult = { segments: [], regions: [], overflowLeft: false, overflowRight: false };
|
|
77
|
+
if (tabs.length === 0 || maxWidth <= 0) return empty;
|
|
78
|
+
|
|
79
|
+
const sepWidth = getDisplayWidth(SEPARATOR);
|
|
80
|
+
// Per-tab entry width: leading separator + (bracketed if active) label + trailing space.
|
|
81
|
+
const labelText = (tab: TabStripItem) => (tab.active ? `[${tab.label}]` : tab.label);
|
|
82
|
+
const entryWidths = tabs.map((tab) => sepWidth + getDisplayWidth(labelText(tab)) + 1);
|
|
83
|
+
|
|
84
|
+
const activeIndex = Math.max(0, tabs.findIndex((t) => t.active));
|
|
85
|
+
const n = tabs.length;
|
|
86
|
+
const closing = sepWidth; // trailing │ after the last visible tab
|
|
87
|
+
const totalAll = entryWidths.reduce((a, b) => a + b, 0) + closing;
|
|
88
|
+
|
|
89
|
+
let start = activeIndex;
|
|
90
|
+
let end = activeIndex; // inclusive
|
|
91
|
+
let used = entryWidths[activeIndex] + closing;
|
|
92
|
+
|
|
93
|
+
if (totalAll <= maxWidth) {
|
|
94
|
+
// Everything fits — show all tabs, no windowing.
|
|
95
|
+
start = 0;
|
|
96
|
+
end = n - 1;
|
|
97
|
+
} else {
|
|
98
|
+
// Need windowing. Reserve room for indicators on whichever side still has
|
|
99
|
+
// hidden tabs; the reserve is recomputed each step so reaching an edge
|
|
100
|
+
// frees its indicator column.
|
|
101
|
+
const budget = maxWidth;
|
|
102
|
+
let leftDone = start === 0;
|
|
103
|
+
let rightDone = end === n - 1;
|
|
104
|
+
let preferRight = true;
|
|
105
|
+
|
|
106
|
+
const fits = (extra: number, nextStart: number, nextEnd: number) => {
|
|
107
|
+
const leftRes = nextStart > 0 ? indicatorWidth : 0;
|
|
108
|
+
const rightRes = nextEnd < n - 1 ? indicatorWidth : 0;
|
|
109
|
+
return used + extra + leftRes + rightRes <= budget;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
while (!leftDone || !rightDone) {
|
|
113
|
+
const goRight = !rightDone && (preferRight || leftDone);
|
|
114
|
+
if (goRight) {
|
|
115
|
+
const ne = end + 1;
|
|
116
|
+
if (fits(entryWidths[ne], start, ne)) {
|
|
117
|
+
end = ne;
|
|
118
|
+
used += entryWidths[ne];
|
|
119
|
+
if (end === n - 1) rightDone = true;
|
|
120
|
+
preferRight = false;
|
|
121
|
+
} else {
|
|
122
|
+
rightDone = true;
|
|
123
|
+
}
|
|
124
|
+
} else if (!leftDone) {
|
|
125
|
+
const ns = start - 1;
|
|
126
|
+
if (fits(entryWidths[ns], ns, end)) {
|
|
127
|
+
start = ns;
|
|
128
|
+
used += entryWidths[ns];
|
|
129
|
+
if (start === 0) leftDone = true;
|
|
130
|
+
preferRight = true;
|
|
131
|
+
} else {
|
|
132
|
+
leftDone = true;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
49
137
|
}
|
|
50
|
-
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const segments: StyledPanelSegment[] = [];
|
|
141
|
+
const regions: Array<{ index: number; startCol: number; endCol: number }> = [];
|
|
142
|
+
let col = 0;
|
|
143
|
+
for (let i = start; i <= end; i++) {
|
|
144
|
+
const tab = tabs[i];
|
|
145
|
+
const text = labelText(tab);
|
|
146
|
+
const entryStart = col;
|
|
147
|
+
segments.push({ text: SEPARATOR, fg: style.separatorFg });
|
|
51
148
|
segments.push({
|
|
52
|
-
text: `${
|
|
149
|
+
text: `${text} `,
|
|
53
150
|
fg: tab.active ? style.activeFg : style.inactiveFg,
|
|
54
151
|
bg: tab.active ? style.activeBg : style.inactiveBg,
|
|
55
152
|
bold: tab.active ? (style.activeBold ?? false) : false,
|
|
56
153
|
});
|
|
57
|
-
|
|
154
|
+
col += entryWidths[i];
|
|
155
|
+
regions.push({ index: i, startCol: entryStart, endCol: col });
|
|
58
156
|
}
|
|
157
|
+
// Closing separator.
|
|
158
|
+
segments.push({ text: SEPARATOR, fg: style.separatorFg });
|
|
59
159
|
|
|
60
|
-
|
|
61
|
-
segments
|
|
62
|
-
|
|
63
|
-
|
|
160
|
+
return {
|
|
161
|
+
segments,
|
|
162
|
+
regions,
|
|
163
|
+
overflowLeft: start > 0,
|
|
164
|
+
overflowRight: end < n - 1,
|
|
165
|
+
};
|
|
64
166
|
}
|
|
65
167
|
|
|
66
168
|
export function renderTabStrip(config: TabStripConfig): Line {
|
|
@@ -70,18 +172,18 @@ export function renderTabStrip(config: TabStripConfig): Line {
|
|
|
70
172
|
style,
|
|
71
173
|
prefixLabel,
|
|
72
174
|
suffixLabel,
|
|
73
|
-
|
|
175
|
+
overflowIndicator = '›',
|
|
176
|
+
overflowIndicatorLeft = '‹',
|
|
177
|
+
onLayout,
|
|
74
178
|
} = config;
|
|
75
179
|
|
|
180
|
+
const prefixWidth = prefixLabel ? getDisplayWidth(prefixLabel) + 1 : 0;
|
|
76
181
|
const reservedSuffixWidth = suffixLabel ? getDisplayWidth(suffixLabel) : 0;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
width
|
|
81
|
-
- (prefixLabel ? getDisplayWidth(prefixLabel) + 1 : 0)
|
|
82
|
-
- (suffixLabel ? reservedSuffixWidth : 0)
|
|
83
|
-
- reservedOverflowWidth,
|
|
182
|
+
const indicatorWidth = Math.max(
|
|
183
|
+
getDisplayWidth(overflowIndicator),
|
|
184
|
+
getDisplayWidth(overflowIndicatorLeft),
|
|
84
185
|
);
|
|
186
|
+
const maxTabWidth = Math.max(0, width - prefixWidth - reservedSuffixWidth);
|
|
85
187
|
|
|
86
188
|
const baseSegments: StyledPanelSegment[] = [];
|
|
87
189
|
if (prefixLabel) {
|
|
@@ -94,15 +196,27 @@ export function renderTabStrip(config: TabStripConfig): Line {
|
|
|
94
196
|
baseSegments.push({ text: ' ', fg: style.separatorFg });
|
|
95
197
|
}
|
|
96
198
|
|
|
97
|
-
const { segments: tabSegments,
|
|
199
|
+
const { segments: tabSegments, regions, overflowLeft, overflowRight } =
|
|
200
|
+
makeSegments(tabs, style, maxTabWidth, indicatorWidth);
|
|
201
|
+
|
|
202
|
+
const leftSegments: StyledPanelSegment[] = [];
|
|
203
|
+
if (overflowLeft) {
|
|
204
|
+
leftSegments.push({ text: overflowIndicatorLeft, fg: style.overflowFg ?? style.separatorFg });
|
|
205
|
+
}
|
|
206
|
+
|
|
98
207
|
const suffixSegments: StyledPanelSegment[] = [];
|
|
99
|
-
if (
|
|
208
|
+
if (overflowRight) {
|
|
209
|
+
suffixSegments.push({ text: overflowIndicator, fg: style.overflowFg ?? style.separatorFg });
|
|
210
|
+
}
|
|
100
211
|
if (suffixLabel) {
|
|
101
|
-
suffixSegments.push({
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
212
|
+
suffixSegments.push({ text: suffixLabel, fg: style.trailingFg ?? style.inactiveFg });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Report absolute hit regions: prefix + optional left indicator, then tabs.
|
|
216
|
+
if (onLayout) {
|
|
217
|
+
const offset = prefixWidth + (overflowLeft ? getDisplayWidth(overflowIndicatorLeft) : 0);
|
|
218
|
+
onLayout(regions.map((r) => ({ index: r.index, startCol: offset + r.startCol, endCol: offset + r.endCol })));
|
|
105
219
|
}
|
|
106
220
|
|
|
107
|
-
return buildStyledPanelLine(width, [...baseSegments, ...tabSegments, ...suffixSegments]);
|
|
221
|
+
return buildStyledPanelLine(width, [...baseSegments, ...leftSegments, ...tabSegments, ...suffixSegments]);
|
|
108
222
|
}
|
package/src/renderer/theme.ts
CHANGED
|
@@ -14,8 +14,19 @@
|
|
|
14
14
|
* IMPORTANT: inline code has NO background token. The bg:#1a1a1a hardcode
|
|
15
15
|
* that previously existed caused a near-black box on light terminals.
|
|
16
16
|
* Differentiate inline code via inlineCodeFg + bold only; bg inherits terminal.
|
|
17
|
+
*
|
|
18
|
+
* resolveUiTones(mode) is the sibling read path for CHROME tokens (UI_TONES
|
|
19
|
+
* in ui-primitives.ts — panel/modal/overlay/fullscreen backgrounds, borders,
|
|
20
|
+
* status colours). It composes the same ThemeMode dimension so that
|
|
21
|
+
* DEFAULT_PANEL_PALETTE, DEFAULT_STYLE, FULLSCREEN_PALETTE and
|
|
22
|
+
* DEFAULT_OVERLAY_PALETTE all read through ONE mode-resolved path instead of
|
|
23
|
+
* importing the static UI_TONES constant directly. Mode stays 'dark'
|
|
24
|
+
* everywhere until the terminal-bg-probe lands (see WO-001) — the light
|
|
25
|
+
* entry is type-complete, not a shipped light theme.
|
|
17
26
|
*/
|
|
18
27
|
|
|
28
|
+
import { UI_TONES } from './ui-primitives.ts';
|
|
29
|
+
|
|
19
30
|
/** Background mode — dark is the safe default until terminal-bg-probe lands. */
|
|
20
31
|
export type ThemeMode = 'dark' | 'light';
|
|
21
32
|
|
|
@@ -78,9 +89,9 @@ const DARK: ThemeTokens = {
|
|
|
78
89
|
searchCurrentFg: '#000000',
|
|
79
90
|
strikethrough: '244',
|
|
80
91
|
blockquote: '244',
|
|
81
|
-
assistantHeader:
|
|
82
|
-
reasoningAccent:
|
|
83
|
-
toolAccent:
|
|
92
|
+
assistantHeader: UI_TONES.accent.control,
|
|
93
|
+
reasoningAccent: UI_TONES.state.reasoning,
|
|
94
|
+
toolAccent: UI_TONES.state.info,
|
|
84
95
|
collapsedBodyBg: '#1a1a1a',
|
|
85
96
|
checkboxChecked: '#22c55e',
|
|
86
97
|
errorBarBg: '#3a1a1a',
|
|
@@ -156,3 +167,49 @@ Object.freeze(LIGHT);
|
|
|
156
167
|
* Frozen — do not mutate.
|
|
157
168
|
*/
|
|
158
169
|
export const DARK_THEME: Readonly<ThemeTokens> = DARK;
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Chrome tokens (UI_TONES) — mode-resolved sibling to resolveTheme().
|
|
173
|
+
//
|
|
174
|
+
// UI_TONES (ui-primitives.ts) is the dark entry. The light entry mirrors
|
|
175
|
+
// dark for every role that has no light-appropriate equivalent yet — the
|
|
176
|
+
// WO-001 deliverable is the mode dimension and single read path, not a
|
|
177
|
+
// shipped light chrome theme (see module doc comment above).
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/** Recursively widen the `as const` literal leaves of UI_TONES to `string`
|
|
181
|
+
* so mode variants (e.g. UI_TONES_LIGHT) can assign different colour
|
|
182
|
+
* values without fighting TypeScript's literal-type inference. */
|
|
183
|
+
type DeepWidenToString<T> = T extends string ? string : { [K in keyof T]: DeepWidenToString<T[K]> };
|
|
184
|
+
|
|
185
|
+
export type UiToneTokens = DeepWidenToString<typeof UI_TONES>;
|
|
186
|
+
|
|
187
|
+
const UI_TONES_LIGHT: UiToneTokens = {
|
|
188
|
+
...UI_TONES,
|
|
189
|
+
state: {
|
|
190
|
+
...UI_TONES.state,
|
|
191
|
+
info: LIGHT.toolAccent,
|
|
192
|
+
reasoning: LIGHT.reasoningAccent,
|
|
193
|
+
},
|
|
194
|
+
accent: {
|
|
195
|
+
...UI_TONES.accent,
|
|
196
|
+
brand: LIGHT.heading1,
|
|
197
|
+
gradientStart: LIGHT.heading1,
|
|
198
|
+
gradientEnd: LIGHT.reasoningAccent,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
Object.freeze(UI_TONES_LIGHT.state);
|
|
203
|
+
Object.freeze(UI_TONES_LIGHT.accent);
|
|
204
|
+
Object.freeze(UI_TONES_LIGHT);
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* resolveUiTones — Return the chrome (panel/modal/overlay/fullscreen) token
|
|
208
|
+
* set for the given background mode. Single read path for UI_TONES; the
|
|
209
|
+
* 'dark' resolution is byte-identical to the UI_TONES constant.
|
|
210
|
+
*
|
|
211
|
+
* Call with 'dark' (the safe default) until terminal-bg-probe lands.
|
|
212
|
+
*/
|
|
213
|
+
export function resolveUiTones(mode: ThemeMode): Readonly<UiToneTokens> {
|
|
214
|
+
return mode === 'light' ? UI_TONES_LIGHT : UI_TONES;
|
|
215
|
+
}
|
|
@@ -4,8 +4,12 @@ import { VERSION } from '../version.ts';
|
|
|
4
4
|
import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText, interpolateColor } from '../utils/terminal-width.ts';
|
|
5
5
|
import type { GitHeaderInfo } from './git-status.ts';
|
|
6
6
|
import { renderConversationFragment, renderConversationStatusLine, type ConversationStatusSegment } from './conversation-surface.ts';
|
|
7
|
-
import { GLYPHS } from './ui-primitives.ts';
|
|
7
|
+
import { GLYPHS, UI_TONES } from './ui-primitives.ts';
|
|
8
8
|
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
9
|
+
import { abbreviateCount } from '../utils/format-number.ts';
|
|
10
|
+
import { computeContextUsage } from '../core/context-usage.ts';
|
|
11
|
+
import { calcSessionCost } from '../export/cost-utils.ts';
|
|
12
|
+
import { buildFooterTip, isAgentActive } from './footer-tips.ts';
|
|
9
13
|
|
|
10
14
|
/** Number of frames before the animated gradient completes one full cycle. */
|
|
11
15
|
const GRADIENT_CYCLE_FRAMES = 50;
|
|
@@ -30,11 +34,15 @@ function buildGitSegment(gitInfo: GitHeaderInfo): { text: string; width: number
|
|
|
30
34
|
|
|
31
35
|
/** Format a number: up to 999, then 1.0k, 1.0M, 1.0B, 1.0T */
|
|
32
36
|
function fmtNum(n: number): string {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
return abbreviateCount(n, { bSuffix: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Format a running USD cost estimate with a precision that suits its magnitude. */
|
|
41
|
+
function fmtCost(usd: number): string {
|
|
42
|
+
if (!(usd > 0)) return '0.00';
|
|
43
|
+
if (usd < 0.01) return usd.toFixed(4);
|
|
44
|
+
if (usd < 1) return usd.toFixed(3);
|
|
45
|
+
return usd.toFixed(2);
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
/**
|
|
@@ -43,9 +51,9 @@ function fmtNum(n: number): string {
|
|
|
43
51
|
export class UIFactory {
|
|
44
52
|
public static createHeader(width: number, model: string, provider: string, title?: string, gitInfo?: GitHeaderInfo): Line[] {
|
|
45
53
|
const lines: Line[] = [];
|
|
46
|
-
const CYAN =
|
|
47
|
-
const GREY =
|
|
48
|
-
const TITLE_COLOR =
|
|
54
|
+
const CYAN = UI_TONES.accent.brand;
|
|
55
|
+
const GREY = UI_TONES.fg.dim;
|
|
56
|
+
const TITLE_COLOR = UI_TONES.fg.muted;
|
|
49
57
|
const brand = ` GoodVibes `;
|
|
50
58
|
const ver = `v${VERSION} `;
|
|
51
59
|
const stats = ` ${model} `;
|
|
@@ -79,11 +87,11 @@ export class UIFactory {
|
|
|
79
87
|
}
|
|
80
88
|
// Build git info segment
|
|
81
89
|
let gitStr = '';
|
|
82
|
-
let gitFg =
|
|
90
|
+
let gitFg: string = UI_TONES.fg.dim;
|
|
83
91
|
if (gitInfo) {
|
|
84
92
|
gitStr = buildGitSegment(gitInfo).text;
|
|
85
93
|
if (gitInfo.dirty || gitInfo.ahead > 0 || gitInfo.behind > 0) {
|
|
86
|
-
gitFg =
|
|
94
|
+
gitFg = UI_TONES.state.warn; // yellow when dirty or out-of-sync
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
const rightSideText = stats + prov;
|
|
@@ -93,7 +101,7 @@ export class UIFactory {
|
|
|
93
101
|
for (const char of stats) { if (rightX < width) line[rightX++] = { char, fg: CYAN, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false }; }
|
|
94
102
|
for (const char of prov) { if (rightX < width) line[rightX++] = { char, fg: GREY, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
|
|
95
103
|
lines.push(line);
|
|
96
|
-
lines.push(this.stringToLine('━'.repeat(width), width, { fg:
|
|
104
|
+
lines.push(this.stringToLine('━'.repeat(width), width, { fg: UI_TONES.fg.dim }));
|
|
97
105
|
return lines;
|
|
98
106
|
}
|
|
99
107
|
|
|
@@ -103,12 +111,12 @@ export class UIFactory {
|
|
|
103
111
|
*/
|
|
104
112
|
public static createMessageBar(
|
|
105
113
|
width: number, text: string,
|
|
106
|
-
bgColor = '#2a2a2a', textColor =
|
|
114
|
+
bgColor: string = '#2a2a2a', textColor: string = UI_TONES.fg.secondary, prefixStr: string = ' › ',
|
|
107
115
|
strikethrough = false
|
|
108
116
|
): Line[] {
|
|
109
117
|
return renderConversationFragment(text, width, {
|
|
110
118
|
prefix: prefixStr,
|
|
111
|
-
prefixFg:
|
|
119
|
+
prefixFg: UI_TONES.state.reasoning,
|
|
112
120
|
text: textColor,
|
|
113
121
|
bodyBg: bgColor,
|
|
114
122
|
strikethrough,
|
|
@@ -121,8 +129,8 @@ export class UIFactory {
|
|
|
121
129
|
public static createQueuedMessageFragment(width: number, text: string): Line[] {
|
|
122
130
|
return renderConversationFragment(text, width, {
|
|
123
131
|
prefix: ' (...) ',
|
|
124
|
-
prefixFg:
|
|
125
|
-
text:
|
|
132
|
+
prefixFg: UI_TONES.state.reasoning,
|
|
133
|
+
text: UI_TONES.fg.dim,
|
|
126
134
|
bodyBg: '#1a1a1a',
|
|
127
135
|
dim: true,
|
|
128
136
|
});
|
|
@@ -150,6 +158,7 @@ export class UIFactory {
|
|
|
150
158
|
composerStatus?: string,
|
|
151
159
|
composerFlags?: readonly string[],
|
|
152
160
|
composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote',
|
|
161
|
+
compact: boolean = false,
|
|
153
162
|
): Line[] {
|
|
154
163
|
const lines: Line[] = [];
|
|
155
164
|
const promptLines = prompt.split('\n');
|
|
@@ -255,7 +264,9 @@ export class UIFactory {
|
|
|
255
264
|
}
|
|
256
265
|
}
|
|
257
266
|
lines.push(bottomLine);
|
|
258
|
-
|
|
267
|
+
// --- Composer posture block (mode / risk / status / flags) ------------
|
|
268
|
+
// Suppressed in compact mode; the ctx-info line no longer duplicates these
|
|
269
|
+
// tokens, so this block is the single home for mode/status/flags.
|
|
259
270
|
const composerTokens: Array<{ text: string; fg: string; bold?: boolean; dim?: boolean }> = [];
|
|
260
271
|
if (composerMode) composerTokens.push({ text: ` ${GLYPHS.status.active} ${composerMode} `, fg: '#38bdf8', bold: true });
|
|
261
272
|
if (composerPendingRisk && composerPendingRisk !== 'none') {
|
|
@@ -270,7 +281,7 @@ export class UIFactory {
|
|
|
270
281
|
}
|
|
271
282
|
if (composerStatus && composerStatus !== 'idle') composerTokens.push({ text: ` state:${composerStatus} `, fg: '244', dim: true });
|
|
272
283
|
if (composerFlags && composerFlags.length > 0) composerTokens.push({ text: ` flags:${composerFlags.join(',')} `, fg: '244', dim: true });
|
|
273
|
-
if (composerTokens.length > 0) {
|
|
284
|
+
if (!compact && composerTokens.length > 0) {
|
|
274
285
|
const postureLine = createBaseLine();
|
|
275
286
|
let px = 2;
|
|
276
287
|
for (const token of composerTokens) {
|
|
@@ -291,10 +302,10 @@ export class UIFactory {
|
|
|
291
302
|
if (px >= width) break;
|
|
292
303
|
}
|
|
293
304
|
lines.push(postureLine);
|
|
294
|
-
lines.push(createBaseLine());
|
|
295
305
|
}
|
|
296
306
|
const isRecentlyCopied = Date.now() - lastCopyTime < 2000;
|
|
297
|
-
// Token usage line
|
|
307
|
+
// Token usage line + running ~$ cost estimate (derived from the usage
|
|
308
|
+
// object and the active model via cost-utils).
|
|
298
309
|
const u = usage as { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; up?: number; down?: number };
|
|
299
310
|
const inp = u.input ?? u.up ?? 0;
|
|
300
311
|
const out = u.output ?? u.down ?? 0;
|
|
@@ -302,26 +313,28 @@ export class UIFactory {
|
|
|
302
313
|
const cw = u.cacheWrite ?? 0;
|
|
303
314
|
const total = inp + out + cr + cw;
|
|
304
315
|
const tokenSep = ` ${GLYPHS.navigation.pipeSeparator} `;
|
|
305
|
-
const
|
|
316
|
+
const costSegment = model ? `${tokenSep}~$${fmtCost(calcSessionCost(inp, out, cr, cw, model))}` : '';
|
|
317
|
+
const tokenLine = ` Token Usage [ Input: ${fmtNum(inp)}${tokenSep}Output: ${fmtNum(out)}${tokenSep}Cache Read: ${fmtNum(cr)}${tokenSep}Cache Write: ${fmtNum(cw)}${tokenSep}Total: ${fmtNum(total)}${costSegment} ]`;
|
|
306
318
|
const copiedNotice = isRecentlyCopied ? ` [COPIED] ` : '';
|
|
307
319
|
const statsLine = ' ' + tokenLine + ' '.repeat(Math.max(0, width - 4 - getDisplayWidth(tokenLine) - getDisplayWidth(copiedNotice))) + copiedNotice;
|
|
308
320
|
lines.push(this.stringToLine(statsLine, width, { fg: isRecentlyCopied ? '81' : '244', bold: isRecentlyCopied }));
|
|
309
|
-
// Context usage progress bar
|
|
310
|
-
if (contextWindow && contextWindow > 0) {
|
|
321
|
+
// Context usage progress bar — suppressed in compact mode.
|
|
322
|
+
if (!compact && contextWindow && contextWindow > 0) {
|
|
311
323
|
const ctxTokens = lastInputTokens ?? 0;
|
|
312
324
|
const label = ' Context Usage: ';
|
|
313
325
|
const suffix = ` [ ${fmtNum(ctxTokens)} / ${fmtNum(contextWindow)} ]`;
|
|
314
326
|
const barWidth = Math.max(10, Math.min(30, width - getDisplayWidth(label) - getDisplayWidth(suffix) - 8));
|
|
315
|
-
const ctxPct =
|
|
327
|
+
const ctxPct = computeContextUsage(ctxTokens, contextWindow).clampedRatio;
|
|
316
328
|
// Clamp threshold to [0..1]; undefined/0 means no threshold marker.
|
|
317
329
|
const thresholdFraction = (compactThreshold !== undefined && compactThreshold > 0)
|
|
318
330
|
? Math.min(1, compactThreshold)
|
|
319
331
|
: undefined;
|
|
320
|
-
lines.push(createBaseLine());
|
|
321
332
|
lines.push(this.createProgressBarLine(label, ctxPct, barWidth, width, suffix, thresholdFraction));
|
|
322
333
|
}
|
|
323
|
-
// Context info line (working dir, model+provider, tools)
|
|
324
|
-
|
|
334
|
+
// Context info line (working dir, model+provider, tools, hitl).
|
|
335
|
+
// Suppressed in compact mode. mode/status/flags are intentionally omitted —
|
|
336
|
+
// the posture block above owns them, so they are not duplicated here.
|
|
337
|
+
if (!compact && (workingDir || model)) {
|
|
325
338
|
const home = typeof process !== 'undefined' ? process.env.HOME ?? '' : '';
|
|
326
339
|
const displayDir = workingDir && home && workingDir.startsWith(home)
|
|
327
340
|
? '~' + workingDir.slice(home.length)
|
|
@@ -333,19 +346,16 @@ export class UIFactory {
|
|
|
333
346
|
}
|
|
334
347
|
if (toolCount) ctxParts.push(`${toolCount} tools`);
|
|
335
348
|
if (hitlMode) ctxParts.push(`hitl:${hitlMode}`);
|
|
336
|
-
if (composerMode) ctxParts.push(`mode:${composerMode}`);
|
|
337
|
-
if (composerStatus && composerStatus !== 'idle') ctxParts.push(`status:${composerStatus}`);
|
|
338
|
-
if (composerFlags && composerFlags.length > 0) ctxParts.push(composerFlags.join(','));
|
|
339
349
|
const ctxLine = ' ' + ctxParts.join(` ${GLYPHS.navigation.pipeSeparator} `);
|
|
340
|
-
lines.push(createBaseLine());
|
|
341
350
|
lines.push(this.stringToLine(truncateDisplay(ctxLine, width), width, { fg: '240', dim: true }));
|
|
342
|
-
lines.push(createBaseLine());
|
|
343
351
|
}
|
|
344
352
|
if (showExitNotice) {
|
|
345
353
|
const notice = ` !!! Press Ctrl+C again to exit !!! `;
|
|
346
354
|
lines.push(this.stringToLine(fitDisplay(notice, width), width, { fg: '196', bold: true }));
|
|
347
355
|
} else {
|
|
348
|
-
|
|
356
|
+
// Persistent discoverability tip. Rotates by context (agent-aware): the
|
|
357
|
+
// process-monitor tip leads while a turn is in flight. See footer-tips.ts.
|
|
358
|
+
const help = ` ${buildFooterTip({ agentActive: isAgentActive(composerStatus) })} `;
|
|
349
359
|
const dangerWarn = dangerMode ? `! DANGER MODE - ALL CHANGES AUTO-APPROVED ` : '';
|
|
350
360
|
const helpW = getDisplayWidth(help);
|
|
351
361
|
const dangerW = getDisplayWidth(dangerWarn);
|
|
@@ -365,7 +375,6 @@ export class UIFactory {
|
|
|
365
375
|
}
|
|
366
376
|
lines.push(line);
|
|
367
377
|
}
|
|
368
|
-
lines.push(createBaseLine());
|
|
369
378
|
return lines;
|
|
370
379
|
}
|
|
371
380
|
|
|
@@ -394,8 +403,8 @@ export class UIFactory {
|
|
|
394
403
|
];
|
|
395
404
|
|
|
396
405
|
/** Gradient colors for thinking text — cyan to purple (matches splash). */
|
|
397
|
-
private static readonly THINK_GRADIENT_START =
|
|
398
|
-
private static readonly THINK_GRADIENT_END =
|
|
406
|
+
private static readonly THINK_GRADIENT_START = UI_TONES.accent.gradientStart;
|
|
407
|
+
private static readonly THINK_GRADIENT_END = UI_TONES.accent.gradientEnd;
|
|
399
408
|
|
|
400
409
|
public static createThinkingFragment(width: number, spinner: string, frame: number = 0, tokenSpeed?: number, toolPreview?: string, inputTokens?: number, outputTokens?: number, elapsedMs?: number, ttftMs?: number): Line[] {
|
|
401
410
|
// Rotate phrase every ~30 seconds (frame ticks at 80ms, so ~375 frames)
|
|
@@ -35,8 +35,9 @@ export const GLYPHS = {
|
|
|
35
35
|
failure: '✕',
|
|
36
36
|
pending: '•',
|
|
37
37
|
active: '●',
|
|
38
|
-
idle: '
|
|
39
|
-
info: '
|
|
38
|
+
idle: '◌',
|
|
39
|
+
info: '○',
|
|
40
|
+
warn: '⚠',
|
|
40
41
|
blocked: '⊘',
|
|
41
42
|
skipped: '◇',
|
|
42
43
|
review: '◈',
|
|
@@ -56,6 +57,11 @@ export const GLYPHS = {
|
|
|
56
57
|
},
|
|
57
58
|
} as const;
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* UI_TONES — the single chrome/color-token source for src/renderer and
|
|
62
|
+
* src/panels. Concrete (dark-mode) values; resolveUiTones(mode) in theme.ts
|
|
63
|
+
* is the single mode-resolved read path — this object is the 'dark' entry.
|
|
64
|
+
*/
|
|
59
65
|
export const UI_TONES = {
|
|
60
66
|
fg: {
|
|
61
67
|
primary: '#e2e8f0',
|
|
@@ -63,6 +69,8 @@ export const UI_TONES = {
|
|
|
63
69
|
muted: '#94a3b8',
|
|
64
70
|
dim: '#475569',
|
|
65
71
|
inverse: '#0f172a',
|
|
72
|
+
/** Empty-state / placeholder foreground (formerly DEFAULT_PANEL_PALETTE.empty literal). */
|
|
73
|
+
empty: '#334155',
|
|
66
74
|
},
|
|
67
75
|
bg: {
|
|
68
76
|
base: '#11131a',
|
|
@@ -75,6 +83,8 @@ export const UI_TONES = {
|
|
|
75
83
|
warning: '#2b2116',
|
|
76
84
|
error: '#2a161b',
|
|
77
85
|
success: '#14241b',
|
|
86
|
+
/** Fullscreen/shell footer background. */
|
|
87
|
+
footer: '#111827',
|
|
78
88
|
},
|
|
79
89
|
state: {
|
|
80
90
|
info: '#38bdf8',
|
|
@@ -83,6 +93,8 @@ export const UI_TONES = {
|
|
|
83
93
|
bad: '#ef4444',
|
|
84
94
|
blocked: '#f97316',
|
|
85
95
|
active: '#60a5fa',
|
|
96
|
+
/** Single canonical reasoning/thinking purple (replaces #9945FF and ad-hoc purples). */
|
|
97
|
+
reasoning: '#a855f7',
|
|
86
98
|
},
|
|
87
99
|
accent: {
|
|
88
100
|
browser: '#7dd3fc',
|
|
@@ -90,7 +102,16 @@ export const UI_TONES = {
|
|
|
90
102
|
inspector: '#c4b5fd',
|
|
91
103
|
workflow: '#fbbf24',
|
|
92
104
|
conversation: '#93c5fd',
|
|
105
|
+
/** Neon brand accent — header/splash/thinking gradient only. */
|
|
106
|
+
brand: '#00ffff',
|
|
107
|
+
gradientStart: '#00ffff',
|
|
108
|
+
gradientEnd: '#d000ff',
|
|
93
109
|
},
|
|
110
|
+
/** Canonical border stroke color for fullscreen/panel chrome. */
|
|
111
|
+
border: '#64748b',
|
|
94
112
|
} as const;
|
|
95
113
|
|
|
96
114
|
export type UiGlyphRegistry = typeof GLYPHS;
|
|
115
|
+
|
|
116
|
+
/** Single spinner-frame source — layout.ts and progress.ts both re-export this. */
|
|
117
|
+
export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;
|
|
@@ -109,12 +109,41 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
109
109
|
homeDirectory: services.homeDirectory,
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
const foundationClients = createRuntimeFoundationClients({
|
|
113
|
+
runtimeServices: services,
|
|
114
|
+
tasksReadModel: uiServices.readModels.tasks,
|
|
115
|
+
taskManager,
|
|
116
|
+
opsControlPlane,
|
|
117
|
+
});
|
|
118
|
+
const {
|
|
119
|
+
directTransport,
|
|
120
|
+
hookApi,
|
|
121
|
+
knowledgeApi,
|
|
122
|
+
mcpApi,
|
|
123
|
+
opsApi,
|
|
124
|
+
providerApi,
|
|
125
|
+
} = foundationClients;
|
|
126
|
+
const planRuntime = createShellPlanRuntime({
|
|
127
|
+
adaptivePlanner: services.adaptivePlanner,
|
|
128
|
+
runtimeBus,
|
|
129
|
+
});
|
|
130
|
+
|
|
112
131
|
const openAgentDetailRef: { fn: (agentId: string) => void } = { fn: (_agentId: string) => {} };
|
|
113
132
|
|
|
133
|
+
// WO-139: initial cost-budget alert threshold (USD; 0/unset = disabled).
|
|
134
|
+
// Once the session starts, the real control surface is the CostTrackerPanel
|
|
135
|
+
// itself — the in-panel 'b' key and /cost budget <usd> both call
|
|
136
|
+
// CostTrackerPanel.setBudgetThreshold() directly on the live panel instance.
|
|
137
|
+
const parsedBudgetThreshold = Number(process.env.GOODVIBES_COST_BUDGET_USD);
|
|
138
|
+
const initialCostBudgetThreshold = Number.isFinite(parsedBudgetThreshold) && parsedBudgetThreshold > 0
|
|
139
|
+
? parsedBudgetThreshold
|
|
140
|
+
: 0;
|
|
141
|
+
|
|
114
142
|
let commandContextRef: CommandContext | null = null;
|
|
115
143
|
registerBuiltinPanels(services.panelManager, {
|
|
116
144
|
configManager,
|
|
117
145
|
getOrchestratorUsage: () => orchestrator.usage as { input: number; output: number; cacheRead: number; cacheWrite: number; model?: string },
|
|
146
|
+
budgetThreshold: initialCostBudgetThreshold,
|
|
118
147
|
toolRegistry,
|
|
119
148
|
providerRegistry: services.providerRegistry,
|
|
120
149
|
contextWindow: services.providerRegistry.getContextWindowForModel(services.providerRegistry.getCurrentModel()),
|
|
@@ -153,6 +182,12 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
153
182
|
mcpRegistry: services.mcpRegistry,
|
|
154
183
|
openAgentDetail: (agentId: string) => openAgentDetailRef.fn(agentId),
|
|
155
184
|
daemonHomeDir: join(services.homeDirectory, '.goodvibes', 'daemon'),
|
|
185
|
+
opsApi,
|
|
186
|
+
planRuntime,
|
|
187
|
+
watcherRegistry: services.watcherRegistry,
|
|
188
|
+
runtimeStore,
|
|
189
|
+
openPanel: (panelId: string) => { services.panelManager.open(panelId); },
|
|
190
|
+
knowledgeApi,
|
|
156
191
|
});
|
|
157
192
|
services.panelManager.prewarmRegistered();
|
|
158
193
|
|
|
@@ -170,29 +205,11 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
170
205
|
|
|
171
206
|
const commandRegistry = new CommandRegistry();
|
|
172
207
|
registerBuiltinCommands(commandRegistry);
|
|
173
|
-
const foundationClients = createRuntimeFoundationClients({
|
|
174
|
-
runtimeServices: services,
|
|
175
|
-
tasksReadModel: uiServices.readModels.tasks,
|
|
176
|
-
taskManager,
|
|
177
|
-
opsControlPlane,
|
|
178
|
-
});
|
|
179
|
-
const {
|
|
180
|
-
directTransport,
|
|
181
|
-
hookApi,
|
|
182
|
-
knowledgeApi,
|
|
183
|
-
mcpApi,
|
|
184
|
-
opsApi,
|
|
185
|
-
providerApi,
|
|
186
|
-
} = foundationClients;
|
|
187
208
|
const remoteRuntime = createShellRemoteCommandService({
|
|
188
209
|
readModels: uiServices.readModels,
|
|
189
210
|
remoteRunnerRegistry: services.remoteRunnerRegistry,
|
|
190
211
|
runtimeStore,
|
|
191
212
|
});
|
|
192
|
-
const planRuntime = createShellPlanRuntime({
|
|
193
|
-
adaptivePlanner: services.adaptivePlanner,
|
|
194
|
-
runtimeBus,
|
|
195
|
-
});
|
|
196
213
|
|
|
197
214
|
const commandContext: CommandContext = createBootstrapCommandContext({
|
|
198
215
|
configManager,
|