zidane 4.1.8 → 4.1.9
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/dist/{index-bgh-k8Mv.d.ts → agent-CMIhYhDz.d.ts} +2032 -1993
- package/dist/agent-CMIhYhDz.d.ts.map +1 -0
- package/dist/chat.d.ts +7 -6
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/contexts.d.ts +1 -1
- package/dist/{index-BB4kuRh3.d.ts → index-CXVvqTQj.d.ts} +1 -1
- package/dist/{index-BB4kuRh3.d.ts.map → index-CXVvqTQj.d.ts.map} +1 -1
- package/dist/{index-Ds5YpvfZ.d.ts → index-D6Dd6Kc0.d.ts} +3 -3
- package/dist/{index-Ds5YpvfZ.d.ts.map → index-D6Dd6Kc0.d.ts.map} +1 -1
- package/dist/{index-DRoG_udt.d.ts → index-DAaKyadO.d.ts} +2 -2
- package/dist/{index-DRoG_udt.d.ts.map → index-DAaKyadO.d.ts.map} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +6 -6
- package/dist/{interpolate-CukJwP2G.js → interpolate-BydkV1eT.js} +3 -1
- package/dist/interpolate-BydkV1eT.js.map +1 -0
- package/dist/{mcp-8wClKY-3.js → mcp-Dw-fRPVk.js} +61 -65
- package/dist/mcp-Dw-fRPVk.js.map +1 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{presets-BzkJDW1K.js → presets-4zCJzCYw.js} +2 -2
- package/dist/{presets-BzkJDW1K.js.map → presets-4zCJzCYw.js.map} +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/session/sqlite.d.ts +13 -2
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +70 -27
- package/dist/session/sqlite.js.map +1 -1
- package/dist/{session-Cn68UASv.js → session-B1RN0uoi.js} +42 -30
- package/dist/{session-Cn68UASv.js.map → session-B1RN0uoi.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -1
- package/dist/{stats-BT9l57RS.js → stats-DZIsGqzu.js} +15 -5
- package/dist/stats-DZIsGqzu.js.map +1 -0
- package/dist/{theme-BlXO6yHe.d.ts → theme-Caf4AvTO.d.ts} +147 -13
- package/dist/theme-Caf4AvTO.d.ts.map +1 -0
- package/dist/{theme-context-MungM3SY.js → theme-context-DQM2lx4U.js} +212 -72
- package/dist/theme-context-DQM2lx4U.js.map +1 -0
- package/dist/{tools-C8kDot0H.js → tools-BdQENveS.js} +409 -312
- package/dist/tools-BdQENveS.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/tui.d.ts +64 -7
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +481 -143
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/package.json +6 -1
- package/dist/index-bgh-k8Mv.d.ts.map +0 -1
- package/dist/interpolate-CukJwP2G.js.map +0 -1
- package/dist/mcp-8wClKY-3.js.map +0 -1
- package/dist/stats-BT9l57RS.js.map +0 -1
- package/dist/theme-BlXO6yHe.d.ts.map +0 -1
- package/dist/theme-context-MungM3SY.js.map +0 -1
- package/dist/tools-C8kDot0H.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,11 +1,211 @@
|
|
|
1
|
-
import { d as createAgent } from "./tools-
|
|
2
|
-
import { n as formatTokenUsage } from "./stats-
|
|
3
|
-
import { n as loadSession, t as createSession } from "./session-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { d as createAgent } from "./tools-BdQENveS.js";
|
|
2
|
+
import { n as formatTokenUsage } from "./stats-DZIsGqzu.js";
|
|
3
|
+
import { n as loadSession, t as createSession } from "./session-B1RN0uoi.js";
|
|
4
|
+
import { createTuiStore } from "./session/sqlite.js";
|
|
5
|
+
import { $ as toolResultText, A as isOnSafelist, B as shortId, E as useSafeModeQueue, G as eventsFromTurns, H as useConfig, I as runOAuthLogin, K as lastContextSizeFromTurns, L as supportsOAuth, O as addToSafelist, P as suggestSafelistEntry, Q as toolCallPreview, R as ageString, T as useSafeModeActions, U as resolveConfig, V as ConfigProvider, X as stripSpawnTokensLine, Z as titleFromTurns, c as finalizeStreamingMarkdownForOwner, d as DEFAULT_SETTINGS, et as detectAuth, f as SETTINGS_CHOICES, ft as getContextWindow, h as useSettings, i as useSurfaces, k as getSafelist, l as turnContextSize, m as SettingsProvider, n as useColors, o as useTheme, ot as setProviderCredential, p as SETTINGS_TOGGLES, q as listSessionMeta, r as useSelectStyle, s as finalizeStreamingMarkdown, t as ThemeProvider, u as useStreamBuffer, v as resolveTheme, w as SafeModeProvider, z as fmtTokens } from "./theme-context-DQM2lx4U.js";
|
|
6
|
+
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { RGBA, SyntaxStyle, addDefaultParsers, createCliRenderer, defaultTextareaKeyBindings, getTreeSitterClient } from "@opentui/core";
|
|
8
8
|
import { createRoot, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react";
|
|
9
|
+
import { jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
10
|
+
//#region src/tui/modal.tsx
|
|
11
|
+
const ModalContext = createContext(null);
|
|
12
|
+
function ModalRoot({ children }) {
|
|
13
|
+
const [active, setActive] = useState(null);
|
|
14
|
+
const api = useMemo(() => ({
|
|
15
|
+
open: (node) => setActive(node),
|
|
16
|
+
close: () => setActive(null),
|
|
17
|
+
get isOpen() {
|
|
18
|
+
return active !== null;
|
|
19
|
+
}
|
|
20
|
+
}), [active]);
|
|
21
|
+
return /* @__PURE__ */ jsxs(ModalContext.Provider, {
|
|
22
|
+
value: api,
|
|
23
|
+
children: [/* @__PURE__ */ jsx("box", {
|
|
24
|
+
style: {
|
|
25
|
+
flexDirection: "column",
|
|
26
|
+
flexGrow: 1
|
|
27
|
+
},
|
|
28
|
+
children
|
|
29
|
+
}), active && /* @__PURE__ */ jsx("box", {
|
|
30
|
+
style: {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
top: 0,
|
|
33
|
+
left: 0,
|
|
34
|
+
right: 0,
|
|
35
|
+
bottom: 0,
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
justifyContent: "center",
|
|
38
|
+
zIndex: 100
|
|
39
|
+
},
|
|
40
|
+
children: active
|
|
41
|
+
})]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function useModal() {
|
|
45
|
+
const ctx = useContext(ModalContext);
|
|
46
|
+
if (!ctx) throw new Error("useModal must be used inside <ModalRoot>");
|
|
47
|
+
return ctx;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Focus computed against the modal layer.
|
|
51
|
+
*
|
|
52
|
+
* Pass a component's preferred focus state and this returns `false` whenever a
|
|
53
|
+
* modal is open — so focused inputs (textarea, selects) release their focus and
|
|
54
|
+
* stop intercepting keys behind the overlay. Pair with `focusable={false}` on
|
|
55
|
+
* "passive" focusables (scrollbox) so the renderer doesn't cycle focus into
|
|
56
|
+
* them when the primary input blurs.
|
|
57
|
+
*/
|
|
58
|
+
function useModalAwareFocus(preferred = true) {
|
|
59
|
+
const { isOpen } = useModal();
|
|
60
|
+
return preferred && !isOpen;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Responsive modal — picks a width based on the live terminal size.
|
|
64
|
+
*
|
|
65
|
+
* - On a wide terminal, the modal grows to `maxWidth` so descriptions sit on
|
|
66
|
+
* one line and don't wrap.
|
|
67
|
+
* - On a narrow terminal, the modal shrinks down to `minWidth`, keeping a
|
|
68
|
+
* small horizontal margin from the screen edges. Text inside wraps naturally.
|
|
69
|
+
*
|
|
70
|
+
* Uses `useTerminalDimensions()` so it reflows on `SIGWINCH` without remount.
|
|
71
|
+
*/
|
|
72
|
+
function Modal({ title, onClose, children, maxWidth = 92, minWidth = 44, horizontalMargin = 4 }) {
|
|
73
|
+
const ctx = useContext(ModalContext);
|
|
74
|
+
const dismiss = onClose ?? ctx?.close;
|
|
75
|
+
const COLOR = useColors();
|
|
76
|
+
const SURFACE = useSurfaces();
|
|
77
|
+
useKeyboard((key) => {
|
|
78
|
+
if (key.name === "escape") dismiss?.();
|
|
79
|
+
});
|
|
80
|
+
const { width: termWidth } = useTerminalDimensions();
|
|
81
|
+
const width = Math.max(minWidth, Math.min(maxWidth, termWidth - horizontalMargin * 2));
|
|
82
|
+
return /* @__PURE__ */ jsx("box", {
|
|
83
|
+
title: title ? ` ${title} ` : void 0,
|
|
84
|
+
style: {
|
|
85
|
+
border: true,
|
|
86
|
+
borderColor: COLOR.borderActive,
|
|
87
|
+
backgroundColor: SURFACE.modal,
|
|
88
|
+
paddingTop: 1,
|
|
89
|
+
paddingBottom: 1,
|
|
90
|
+
paddingLeft: 2,
|
|
91
|
+
paddingRight: 2,
|
|
92
|
+
width,
|
|
93
|
+
flexDirection: "column",
|
|
94
|
+
gap: 1
|
|
95
|
+
},
|
|
96
|
+
children
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/tui/agent-picker.tsx
|
|
101
|
+
/** Cap the scroll window — a long custom registry shouldn't push the modal off-screen. */
|
|
102
|
+
const VISIBLE_ROW_CAP$1 = 10;
|
|
103
|
+
/**
|
|
104
|
+
* Modal that lists the registered {@link AgentProfile}s and lets the user
|
|
105
|
+
* pick one. Rows show: `● selected · label description`.
|
|
106
|
+
*
|
|
107
|
+
* The accent column is intentionally compact (single-char marker) — the
|
|
108
|
+
* profile's `accent` color is read from the active theme so Build and Plan
|
|
109
|
+
* stand apart at a glance without taking horizontal space.
|
|
110
|
+
*
|
|
111
|
+
* Used by `App` (Ctrl+A binding) and exported for hosts that want to drive
|
|
112
|
+
* agent switching from elsewhere in their own composition.
|
|
113
|
+
*/
|
|
114
|
+
function AgentPickerModal({ agents, currentAgentId, onPick }) {
|
|
115
|
+
const COLOR = useColors();
|
|
116
|
+
const SELECT_THEME = useSelectStyle();
|
|
117
|
+
const profiles = useMemo(() => Object.values(agents), [agents]);
|
|
118
|
+
const initialIndex = useMemo(() => profiles.findIndex((p) => p.id === currentAgentId), [profiles, currentAgentId]);
|
|
119
|
+
const options = useMemo(() => profiles.map((p) => ({
|
|
120
|
+
name: `${p.id === currentAgentId ? "● " : " "}${p.label}`,
|
|
121
|
+
description: p.description,
|
|
122
|
+
value: p.id
|
|
123
|
+
})), [profiles, currentAgentId]);
|
|
124
|
+
if (profiles.length === 0) return /* @__PURE__ */ jsx(EmptyState$2, {});
|
|
125
|
+
const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP$1);
|
|
126
|
+
const currentMissing = initialIndex < 0;
|
|
127
|
+
const safeIndex = currentMissing ? 0 : initialIndex;
|
|
128
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
129
|
+
title: "select agent",
|
|
130
|
+
children: [
|
|
131
|
+
currentMissing && /* @__PURE__ */ jsx("text", {
|
|
132
|
+
fg: COLOR.warn,
|
|
133
|
+
children: `Current agent "${currentAgentId}" is not in this registry — pick one below.`
|
|
134
|
+
}),
|
|
135
|
+
/* @__PURE__ */ jsx("select", {
|
|
136
|
+
...SELECT_THEME,
|
|
137
|
+
focused: true,
|
|
138
|
+
options,
|
|
139
|
+
wrapSelection: true,
|
|
140
|
+
selectedIndex: safeIndex,
|
|
141
|
+
showScrollIndicator: options.length > visibleRows,
|
|
142
|
+
style: { height: visibleRows },
|
|
143
|
+
onSelect: (_idx, option) => {
|
|
144
|
+
if (option) onPick(option.value);
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
/* @__PURE__ */ jsxs("text", {
|
|
148
|
+
fg: COLOR.mute,
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ jsx("span", {
|
|
151
|
+
fg: COLOR.warn,
|
|
152
|
+
children: "↑↓"
|
|
153
|
+
}),
|
|
154
|
+
" navigate · ",
|
|
155
|
+
/* @__PURE__ */ jsx("span", {
|
|
156
|
+
fg: COLOR.warn,
|
|
157
|
+
children: "↵"
|
|
158
|
+
}),
|
|
159
|
+
" select · ",
|
|
160
|
+
/* @__PURE__ */ jsx("span", {
|
|
161
|
+
fg: COLOR.warn,
|
|
162
|
+
children: "esc"
|
|
163
|
+
}),
|
|
164
|
+
" close"
|
|
165
|
+
]
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function EmptyState$2() {
|
|
171
|
+
const COLOR = useColors();
|
|
172
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
173
|
+
title: "select agent",
|
|
174
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
175
|
+
fg: COLOR.dim,
|
|
176
|
+
children: "No agents registered."
|
|
177
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
178
|
+
fg: COLOR.mute,
|
|
179
|
+
children: [
|
|
180
|
+
"Pass an",
|
|
181
|
+
/* @__PURE__ */ jsx("span", {
|
|
182
|
+
fg: COLOR.model,
|
|
183
|
+
children: " agents "
|
|
184
|
+
}),
|
|
185
|
+
"registry to",
|
|
186
|
+
/* @__PURE__ */ jsx("span", {
|
|
187
|
+
fg: COLOR.model,
|
|
188
|
+
children: " runTui({ agents }) "
|
|
189
|
+
}),
|
|
190
|
+
"to populate this list."
|
|
191
|
+
]
|
|
192
|
+
})]
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Resolve a profile's `accent` token to a concrete theme color via the
|
|
197
|
+
* caller's color palette. Exposed for the Footer badge so all surfaces
|
|
198
|
+
* stay in sync with the picker's row tinting.
|
|
199
|
+
*/
|
|
200
|
+
function accentColor(accent, COLOR) {
|
|
201
|
+
switch (accent) {
|
|
202
|
+
case "brand": return COLOR.brand;
|
|
203
|
+
case "warn": return COLOR.warn;
|
|
204
|
+
case "model": return COLOR.model;
|
|
205
|
+
default: return COLOR.accent;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
9
209
|
//#region src/tui/theme.ts
|
|
10
210
|
/**
|
|
11
211
|
* Convert the renderer-agnostic `Theme.syntax` map (hex strings + plain
|
|
@@ -29,14 +229,25 @@ function buildMdStyle(theme) {
|
|
|
29
229
|
}
|
|
30
230
|
return SyntaxStyle.fromStyles(styles);
|
|
31
231
|
}
|
|
232
|
+
const MdStyleContext = createContext(null);
|
|
233
|
+
function MdStyleProvider({ children }) {
|
|
234
|
+
const theme = useTheme();
|
|
235
|
+
const style = useMemo(() => buildMdStyle(theme), [theme]);
|
|
236
|
+
return createElement(MdStyleContext.Provider, { value: style }, children);
|
|
237
|
+
}
|
|
32
238
|
/**
|
|
33
|
-
* Active markdown / syntax-highlighting style
|
|
34
|
-
*
|
|
35
|
-
*
|
|
239
|
+
* Active markdown / syntax-highlighting style. Returns a single shared
|
|
240
|
+
* `SyntaxStyle` instance for the active theme — built once at provider
|
|
241
|
+
* mount, re-built on theme switch. A `Settings.theme` flip re-paints every
|
|
242
|
+
* `<markdown>` that reads this hook.
|
|
243
|
+
*
|
|
244
|
+
* Throws if used outside `<MdStyleProvider>` so a missing wiring shows up
|
|
245
|
+
* loudly in development rather than silently rendering plain text.
|
|
36
246
|
*/
|
|
37
247
|
function useMdStyle() {
|
|
38
|
-
const
|
|
39
|
-
|
|
248
|
+
const style = useContext(MdStyleContext);
|
|
249
|
+
if (!style) throw new Error("useMdStyle must be used inside <MdStyleProvider>");
|
|
250
|
+
return style;
|
|
40
251
|
}
|
|
41
252
|
//#endregion
|
|
42
253
|
//#region src/tui/components.tsx
|
|
@@ -69,9 +280,27 @@ const EventLine = memo(({ event, previous, depthOffset = 0 }) => /* @__PURE__ */
|
|
|
69
280
|
function onInputSubmit(handler) {
|
|
70
281
|
return handler;
|
|
71
282
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Footer status bar. Renders as a single row when the terminal is wide enough,
|
|
285
|
+
* otherwise stacks into two rows (agent + hints + provider on top, context
|
|
286
|
+
* bottom-right) and falls back to one segment per row at terminal widths
|
|
287
|
+
* where even that overflows. The width tiering is driven by plain-text
|
|
288
|
+
* length estimates of each segment — close enough since the segments are
|
|
289
|
+
* ASCII-heavy and worst case we under-estimate the breakpoint by a few cells.
|
|
290
|
+
*
|
|
291
|
+
* Agent badge is **always rendered first** so the active profile (Build /
|
|
292
|
+
* Plan / host-custom) is the anchor on the left edge of every layout tier.
|
|
293
|
+
*/
|
|
294
|
+
function Footer({ hints, picked, agent, context }) {
|
|
295
|
+
const { width } = useTerminalDimensions();
|
|
296
|
+
const inner = Math.max(0, width - 2);
|
|
297
|
+
const hW = hintsLength(hints);
|
|
298
|
+
const aW = agent ? agentBadgeLength(agent) : 0;
|
|
299
|
+
const pW = picked ? providerBadgeLength(picked) : 0;
|
|
300
|
+
const cW = context ? contextIndicatorLength(context) : 0;
|
|
301
|
+
const oneRowFits = aW + hW + pW + (cW > 0 ? cW + 1 : 0) <= inner;
|
|
302
|
+
const leftRowFits = aW + hW + pW <= inner;
|
|
303
|
+
if (oneRowFits) return /* @__PURE__ */ jsxs("box", {
|
|
75
304
|
style: {
|
|
76
305
|
flexDirection: "row",
|
|
77
306
|
height: 1,
|
|
@@ -79,36 +308,154 @@ function Footer({ hints, picked, context }) {
|
|
|
79
308
|
paddingRight: 1
|
|
80
309
|
},
|
|
81
310
|
children: [
|
|
82
|
-
/* @__PURE__ */ jsx(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
i > 0 && /* @__PURE__ */ jsx("span", {
|
|
86
|
-
fg: COLOR.mute,
|
|
87
|
-
children: " · "
|
|
88
|
-
}),
|
|
89
|
-
/* @__PURE__ */ jsx("span", {
|
|
90
|
-
fg: COLOR.warn,
|
|
91
|
-
children: h.key
|
|
92
|
-
}),
|
|
93
|
-
/* @__PURE__ */ jsx("span", {
|
|
94
|
-
fg: COLOR.dim,
|
|
95
|
-
children: ` ${h.label}`
|
|
96
|
-
})
|
|
97
|
-
] }, i))
|
|
311
|
+
agent && /* @__PURE__ */ jsx(AgentBadge, {
|
|
312
|
+
agent,
|
|
313
|
+
position: "leading"
|
|
98
314
|
}),
|
|
315
|
+
/* @__PURE__ */ jsx(HintsText, { hints }),
|
|
99
316
|
picked && /* @__PURE__ */ jsx(ProviderBadge, { picked }),
|
|
100
317
|
/* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }),
|
|
101
318
|
context && /* @__PURE__ */ jsx(ContextIndicator, { context })
|
|
102
319
|
]
|
|
103
320
|
});
|
|
321
|
+
if (leftRowFits) return /* @__PURE__ */ jsxs("box", {
|
|
322
|
+
style: {
|
|
323
|
+
flexDirection: "column",
|
|
324
|
+
paddingLeft: 1,
|
|
325
|
+
paddingRight: 1
|
|
326
|
+
},
|
|
327
|
+
children: [/* @__PURE__ */ jsxs("box", {
|
|
328
|
+
style: {
|
|
329
|
+
flexDirection: "row",
|
|
330
|
+
height: 1
|
|
331
|
+
},
|
|
332
|
+
children: [
|
|
333
|
+
agent && /* @__PURE__ */ jsx(AgentBadge, {
|
|
334
|
+
agent,
|
|
335
|
+
position: "leading"
|
|
336
|
+
}),
|
|
337
|
+
/* @__PURE__ */ jsx(HintsText, { hints }),
|
|
338
|
+
picked && /* @__PURE__ */ jsx(ProviderBadge, { picked })
|
|
339
|
+
]
|
|
340
|
+
}), context && /* @__PURE__ */ jsxs("box", {
|
|
341
|
+
style: {
|
|
342
|
+
flexDirection: "row",
|
|
343
|
+
height: 1
|
|
344
|
+
},
|
|
345
|
+
children: [/* @__PURE__ */ jsx("box", { style: { flexGrow: 1 } }), /* @__PURE__ */ jsx(ContextIndicator, { context })]
|
|
346
|
+
})]
|
|
347
|
+
});
|
|
348
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
349
|
+
style: {
|
|
350
|
+
flexDirection: "column",
|
|
351
|
+
paddingLeft: 1,
|
|
352
|
+
paddingRight: 1
|
|
353
|
+
},
|
|
354
|
+
children: [
|
|
355
|
+
agent && /* @__PURE__ */ jsx("box", {
|
|
356
|
+
style: {
|
|
357
|
+
flexDirection: "row",
|
|
358
|
+
height: 1
|
|
359
|
+
},
|
|
360
|
+
children: /* @__PURE__ */ jsx(AgentBadge, {
|
|
361
|
+
agent,
|
|
362
|
+
position: "standalone"
|
|
363
|
+
})
|
|
364
|
+
}),
|
|
365
|
+
/* @__PURE__ */ jsx("box", {
|
|
366
|
+
style: {
|
|
367
|
+
flexDirection: "row",
|
|
368
|
+
height: 1
|
|
369
|
+
},
|
|
370
|
+
children: /* @__PURE__ */ jsx(HintsText, { hints })
|
|
371
|
+
}),
|
|
372
|
+
picked && /* @__PURE__ */ jsx("box", {
|
|
373
|
+
style: {
|
|
374
|
+
flexDirection: "row",
|
|
375
|
+
height: 1
|
|
376
|
+
},
|
|
377
|
+
children: /* @__PURE__ */ jsx(ProviderBadge, {
|
|
378
|
+
picked,
|
|
379
|
+
standalone: true
|
|
380
|
+
})
|
|
381
|
+
}),
|
|
382
|
+
context && /* @__PURE__ */ jsx("box", {
|
|
383
|
+
style: {
|
|
384
|
+
flexDirection: "row",
|
|
385
|
+
height: 1
|
|
386
|
+
},
|
|
387
|
+
children: /* @__PURE__ */ jsx(ContextIndicator, { context })
|
|
388
|
+
})
|
|
389
|
+
]
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function HintsText({ hints }) {
|
|
393
|
+
const COLOR = useColors();
|
|
394
|
+
return /* @__PURE__ */ jsx("text", {
|
|
395
|
+
fg: COLOR.dim,
|
|
396
|
+
children: hints.map((h, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
397
|
+
i > 0 && /* @__PURE__ */ jsx("span", {
|
|
398
|
+
fg: COLOR.mute,
|
|
399
|
+
children: " · "
|
|
400
|
+
}),
|
|
401
|
+
/* @__PURE__ */ jsx("span", {
|
|
402
|
+
fg: COLOR.warn,
|
|
403
|
+
children: h.key
|
|
404
|
+
}),
|
|
405
|
+
/* @__PURE__ */ jsx("span", {
|
|
406
|
+
fg: COLOR.dim,
|
|
407
|
+
children: ` ${h.label}`
|
|
408
|
+
})
|
|
409
|
+
] }, i))
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Colored badge for the active {@link AgentProfile}. The badge anchors the
|
|
414
|
+
* left edge of the bottom bar so the current mode (Build / Plan / host
|
|
415
|
+
* custom) is the first thing the eye lands on. Accent color comes from
|
|
416
|
+
* the profile's `accent` token, resolved against the active theme via
|
|
417
|
+
* `accentColor()` so picker rows and footer tinting stay in sync.
|
|
418
|
+
*
|
|
419
|
+
* Two render positions:
|
|
420
|
+
*
|
|
421
|
+
* - `leading` (default) — in-row at the very start. Renders just the
|
|
422
|
+
* label and a trailing ` · ` separator, e.g. `Build · `. Tight,
|
|
423
|
+
* prominent, and visually distinct from the surrounding hint text.
|
|
424
|
+
* - `standalone` — one-row-per-segment fallback for narrow terminals.
|
|
425
|
+
* Renders `agent <label>` with no separators so the row reads
|
|
426
|
+
* correctly on its own.
|
|
427
|
+
*/
|
|
428
|
+
function AgentBadge({ agent, position = "leading" }) {
|
|
429
|
+
const COLOR = useColors();
|
|
430
|
+
const fg = accentColor(agent.accent, COLOR);
|
|
431
|
+
if (position === "standalone") return /* @__PURE__ */ jsxs("text", {
|
|
432
|
+
fg: COLOR.dim,
|
|
433
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
434
|
+
fg: COLOR.mute,
|
|
435
|
+
children: "agent "
|
|
436
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
437
|
+
fg,
|
|
438
|
+
children: agent.label
|
|
439
|
+
})]
|
|
440
|
+
});
|
|
441
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
442
|
+
fg: COLOR.dim,
|
|
443
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
444
|
+
fg,
|
|
445
|
+
children: agent.label
|
|
446
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
447
|
+
fg: COLOR.mute,
|
|
448
|
+
children: " · "
|
|
449
|
+
})]
|
|
450
|
+
});
|
|
104
451
|
}
|
|
105
|
-
function ProviderBadge({ picked }) {
|
|
452
|
+
function ProviderBadge({ picked, standalone = false }) {
|
|
106
453
|
const COLOR = useColors();
|
|
107
454
|
const source = picked.provider.methods[0].source;
|
|
108
455
|
return /* @__PURE__ */ jsxs("text", {
|
|
109
456
|
fg: COLOR.dim,
|
|
110
457
|
children: [
|
|
111
|
-
/* @__PURE__ */ jsx("span", {
|
|
458
|
+
!standalone && /* @__PURE__ */ jsx("span", {
|
|
112
459
|
fg: COLOR.mute,
|
|
113
460
|
children: " · "
|
|
114
461
|
}),
|
|
@@ -162,6 +509,22 @@ function ContextIndicator({ context }) {
|
|
|
162
509
|
]
|
|
163
510
|
});
|
|
164
511
|
}
|
|
512
|
+
function hintsLength(hints) {
|
|
513
|
+
if (hints.length === 0) return 0;
|
|
514
|
+
return hints.reduce((sum, h, i) => sum + h.key.length + 1 + h.label.length + (i > 0 ? 3 : 0), 0);
|
|
515
|
+
}
|
|
516
|
+
function providerBadgeLength(picked) {
|
|
517
|
+
const source = picked.provider.methods[0].source;
|
|
518
|
+
return 3 + picked.provider.label.length + 3 + picked.model.length + 3 + source.length;
|
|
519
|
+
}
|
|
520
|
+
function agentBadgeLength(agent) {
|
|
521
|
+
return agent.label.length + 3;
|
|
522
|
+
}
|
|
523
|
+
function contextIndicatorLength(context) {
|
|
524
|
+
const ratio = context.max > 0 ? context.used / context.max : 0;
|
|
525
|
+
const pct = Math.round(ratio * 100);
|
|
526
|
+
return 4 + fmtTokens(context.used).length + 3 + fmtTokens(context.max).length + 2 + String(pct).length + 2;
|
|
527
|
+
}
|
|
165
528
|
const SPINNER_FRAMES = [
|
|
166
529
|
"⠋",
|
|
167
530
|
"⠙",
|
|
@@ -575,96 +938,6 @@ function ToolResultBlock({ text, indent }) {
|
|
|
575
938
|
});
|
|
576
939
|
}
|
|
577
940
|
//#endregion
|
|
578
|
-
//#region src/tui/modal.tsx
|
|
579
|
-
const ModalContext = createContext(null);
|
|
580
|
-
function ModalRoot({ children }) {
|
|
581
|
-
const [active, setActive] = useState(null);
|
|
582
|
-
const api = useMemo(() => ({
|
|
583
|
-
open: (node) => setActive(node),
|
|
584
|
-
close: () => setActive(null),
|
|
585
|
-
get isOpen() {
|
|
586
|
-
return active !== null;
|
|
587
|
-
}
|
|
588
|
-
}), [active]);
|
|
589
|
-
return /* @__PURE__ */ jsxs(ModalContext.Provider, {
|
|
590
|
-
value: api,
|
|
591
|
-
children: [/* @__PURE__ */ jsx("box", {
|
|
592
|
-
style: {
|
|
593
|
-
flexDirection: "column",
|
|
594
|
-
flexGrow: 1
|
|
595
|
-
},
|
|
596
|
-
children
|
|
597
|
-
}), active && /* @__PURE__ */ jsx("box", {
|
|
598
|
-
style: {
|
|
599
|
-
position: "absolute",
|
|
600
|
-
top: 0,
|
|
601
|
-
left: 0,
|
|
602
|
-
right: 0,
|
|
603
|
-
bottom: 0,
|
|
604
|
-
alignItems: "center",
|
|
605
|
-
justifyContent: "center",
|
|
606
|
-
zIndex: 100
|
|
607
|
-
},
|
|
608
|
-
children: active
|
|
609
|
-
})]
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
function useModal() {
|
|
613
|
-
const ctx = useContext(ModalContext);
|
|
614
|
-
if (!ctx) throw new Error("useModal must be used inside <ModalRoot>");
|
|
615
|
-
return ctx;
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Focus computed against the modal layer.
|
|
619
|
-
*
|
|
620
|
-
* Pass a component's preferred focus state and this returns `false` whenever a
|
|
621
|
-
* modal is open — so focused inputs (textarea, selects) release their focus and
|
|
622
|
-
* stop intercepting keys behind the overlay. Pair with `focusable={false}` on
|
|
623
|
-
* "passive" focusables (scrollbox) so the renderer doesn't cycle focus into
|
|
624
|
-
* them when the primary input blurs.
|
|
625
|
-
*/
|
|
626
|
-
function useModalAwareFocus(preferred = true) {
|
|
627
|
-
const { isOpen } = useModal();
|
|
628
|
-
return preferred && !isOpen;
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Responsive modal — picks a width based on the live terminal size.
|
|
632
|
-
*
|
|
633
|
-
* - On a wide terminal, the modal grows to `maxWidth` so descriptions sit on
|
|
634
|
-
* one line and don't wrap.
|
|
635
|
-
* - On a narrow terminal, the modal shrinks down to `minWidth`, keeping a
|
|
636
|
-
* small horizontal margin from the screen edges. Text inside wraps naturally.
|
|
637
|
-
*
|
|
638
|
-
* Uses `useTerminalDimensions()` so it reflows on `SIGWINCH` without remount.
|
|
639
|
-
*/
|
|
640
|
-
function Modal({ title, onClose, children, maxWidth = 92, minWidth = 44, horizontalMargin = 4 }) {
|
|
641
|
-
const ctx = useContext(ModalContext);
|
|
642
|
-
const dismiss = onClose ?? ctx?.close;
|
|
643
|
-
const COLOR = useColors();
|
|
644
|
-
const SURFACE = useSurfaces();
|
|
645
|
-
useKeyboard((key) => {
|
|
646
|
-
if (key.name === "escape") dismiss?.();
|
|
647
|
-
});
|
|
648
|
-
const { width: termWidth } = useTerminalDimensions();
|
|
649
|
-
const width = Math.max(minWidth, Math.min(maxWidth, termWidth - horizontalMargin * 2));
|
|
650
|
-
return /* @__PURE__ */ jsx("box", {
|
|
651
|
-
title: title ? ` ${title} ` : void 0,
|
|
652
|
-
style: {
|
|
653
|
-
border: true,
|
|
654
|
-
borderColor: COLOR.borderActive,
|
|
655
|
-
backgroundColor: SURFACE.modal,
|
|
656
|
-
paddingTop: 1,
|
|
657
|
-
paddingBottom: 1,
|
|
658
|
-
paddingLeft: 2,
|
|
659
|
-
paddingRight: 2,
|
|
660
|
-
width,
|
|
661
|
-
flexDirection: "column",
|
|
662
|
-
gap: 1
|
|
663
|
-
},
|
|
664
|
-
children
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
//#endregion
|
|
668
941
|
//#region src/tui/model-picker.tsx
|
|
669
942
|
/** Cap the visible scroll window so a 30-model list doesn't push the modal off-screen. */
|
|
670
943
|
const VISIBLE_ROW_CAP = 12;
|
|
@@ -1722,7 +1995,7 @@ function ThemedShell() {
|
|
|
1722
1995
|
const { settings } = useSettings();
|
|
1723
1996
|
return /* @__PURE__ */ jsx(ThemeProvider, {
|
|
1724
1997
|
theme: useMemo(() => resolveTheme(settings.theme), [settings.theme]),
|
|
1725
|
-
children: /* @__PURE__ */ jsx(SafeModeProvider, { children: /* @__PURE__ */ jsx(ModalRoot, { children: /* @__PURE__ */ jsx(AppShell, {}) }) })
|
|
1998
|
+
children: /* @__PURE__ */ jsx(MdStyleProvider, { children: /* @__PURE__ */ jsx(SafeModeProvider, { children: /* @__PURE__ */ jsx(ModalRoot, { children: /* @__PURE__ */ jsx(AppShell, {}) }) }) })
|
|
1726
1999
|
});
|
|
1727
2000
|
}
|
|
1728
2001
|
function AppShell() {
|
|
@@ -1732,9 +2005,11 @@ function AppShell() {
|
|
|
1732
2005
|
const { settings } = useSettings();
|
|
1733
2006
|
const queue = useSafeModeQueue();
|
|
1734
2007
|
const { requestApproval, resolveHead, denyAll } = useSafeModeActions();
|
|
1735
|
-
const { providers: providerRegistry,
|
|
2008
|
+
const { providers: providerRegistry, agents: agentRegistry, initialAgentId, store, stateStore, modelsFor, resumeProvider, initialPicked, initialState } = config;
|
|
1736
2009
|
const lastResumedSessionId = initialState.lastSessionId;
|
|
1737
2010
|
const dataDir = config.paths.dir;
|
|
2011
|
+
const [pickedAgent, setPickedAgent] = useState(() => agentRegistry[initialAgentId] ?? Object.values(agentRegistry)[0]);
|
|
2012
|
+
const pickedAgentRef = useRef(pickedAgent);
|
|
1738
2013
|
const safeModeEnabledRef = useRef(settings.safeMode);
|
|
1739
2014
|
useEffect(() => {
|
|
1740
2015
|
safeModeEnabledRef.current = settings.safeMode;
|
|
@@ -1804,8 +2079,9 @@ function AppShell() {
|
|
|
1804
2079
|
const buildAgent = useCallback((session, key) => {
|
|
1805
2080
|
const descriptor = providerRegistry[key];
|
|
1806
2081
|
if (!descriptor) throw new Error(`No provider registered for key "${key}"`);
|
|
2082
|
+
const profile = pickedAgentRef.current;
|
|
1807
2083
|
const agent = createAgent({
|
|
1808
|
-
...preset,
|
|
2084
|
+
...profile.preset,
|
|
1809
2085
|
provider: descriptor.factory(),
|
|
1810
2086
|
session
|
|
1811
2087
|
});
|
|
@@ -1911,7 +2187,6 @@ function AppShell() {
|
|
|
1911
2187
|
return agent;
|
|
1912
2188
|
}, [
|
|
1913
2189
|
providerRegistry,
|
|
1914
|
-
preset,
|
|
1915
2190
|
stream,
|
|
1916
2191
|
gateDecision
|
|
1917
2192
|
]);
|
|
@@ -1921,11 +2196,21 @@ function AppShell() {
|
|
|
1921
2196
|
return list;
|
|
1922
2197
|
}, [store]);
|
|
1923
2198
|
const teardown = useCallback(async () => {
|
|
2199
|
+
try {
|
|
2200
|
+
denyAll();
|
|
2201
|
+
} catch (err) {
|
|
2202
|
+
debugLog("teardown: denyAll failed", err);
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
agentRef.current?.abort();
|
|
2206
|
+
} catch (err) {
|
|
2207
|
+
debugLog("teardown: agent.abort failed", err);
|
|
2208
|
+
}
|
|
1924
2209
|
stream.reset();
|
|
1925
2210
|
await agentRef.current?.destroy().catch((err) => debugLog("agent.destroy failed", err));
|
|
1926
2211
|
agentRef.current = null;
|
|
1927
2212
|
sessionRef.current = null;
|
|
1928
|
-
}, [stream]);
|
|
2213
|
+
}, [stream, denyAll]);
|
|
1929
2214
|
const activateSession = useCallback(async (id, key) => {
|
|
1930
2215
|
await teardown();
|
|
1931
2216
|
const session = (id ? await loadSession(store, id) : null) ?? await createSession({
|
|
@@ -1935,7 +2220,7 @@ function AppShell() {
|
|
|
1935
2220
|
sessionRef.current = session;
|
|
1936
2221
|
agentRef.current = buildAgent(session, key);
|
|
1937
2222
|
setEvents(eventsFromTurns(session.turns, session.runs));
|
|
1938
|
-
setLastInputTokens(lastContextSizeFromTurns(session.turns));
|
|
2223
|
+
setLastInputTokens(lastContextSizeFromTurns(session.turns, session.runs));
|
|
1939
2224
|
setCurrentSession({
|
|
1940
2225
|
id: session.id,
|
|
1941
2226
|
title: titleFromTurns(session.turns) ?? "untitled",
|
|
@@ -2029,11 +2314,39 @@ function AppShell() {
|
|
|
2029
2314
|
});
|
|
2030
2315
|
modal.close();
|
|
2031
2316
|
}, [modal, stateStore]);
|
|
2317
|
+
const onPickAgent = useCallback(async (id) => {
|
|
2318
|
+
const profile = agentRegistry[id];
|
|
2319
|
+
if (!profile) return;
|
|
2320
|
+
pickedAgentRef.current = profile;
|
|
2321
|
+
setPickedAgent(profile);
|
|
2322
|
+
stateStore.save({
|
|
2323
|
+
...stateStore.load(),
|
|
2324
|
+
lastAgent: id
|
|
2325
|
+
});
|
|
2326
|
+
modal.close();
|
|
2327
|
+
if (picked && currentSession && !busy) await activateSession(currentSession.id, picked.provider.key);
|
|
2328
|
+
}, [
|
|
2329
|
+
agentRegistry,
|
|
2330
|
+
picked,
|
|
2331
|
+
currentSession,
|
|
2332
|
+
busy,
|
|
2333
|
+
activateSession,
|
|
2334
|
+
stateStore,
|
|
2335
|
+
modal
|
|
2336
|
+
]);
|
|
2337
|
+
const onCycleAgent = useCallback(async () => {
|
|
2338
|
+
const ids = Object.keys(agentRegistry);
|
|
2339
|
+
if (ids.length <= 1) return;
|
|
2340
|
+
const nextId = ids[(ids.indexOf(pickedAgentRef.current.id) + 1) % ids.length];
|
|
2341
|
+
await onPickAgent(nextId);
|
|
2342
|
+
}, [agentRegistry, onPickAgent]);
|
|
2343
|
+
const eventsLengthRef = useRef(0);
|
|
2344
|
+
eventsLengthRef.current = events.length;
|
|
2032
2345
|
const onSubmitPrompt = useCallback(async (prompt) => {
|
|
2033
2346
|
const agent = agentRef.current;
|
|
2034
2347
|
const session = sessionRef.current;
|
|
2035
2348
|
if (!agent || !session || !picked || !prompt.trim()) return;
|
|
2036
|
-
if (
|
|
2349
|
+
if (eventsLengthRef.current > 0) stream.appendImmediate({
|
|
2037
2350
|
kind: "separator",
|
|
2038
2351
|
text: ""
|
|
2039
2352
|
});
|
|
@@ -2063,16 +2376,18 @@ function AppShell() {
|
|
|
2063
2376
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
2064
2377
|
setBusy(false);
|
|
2065
2378
|
}
|
|
2066
|
-
}, [
|
|
2067
|
-
|
|
2068
|
-
events.length,
|
|
2069
|
-
stream
|
|
2070
|
-
]);
|
|
2379
|
+
}, [picked, stream]);
|
|
2380
|
+
const pendingApproval = queue[0] ?? null;
|
|
2071
2381
|
const onReauth = useCallback(() => {
|
|
2382
|
+
if (busy || pendingApproval) return;
|
|
2072
2383
|
modal.close();
|
|
2073
2384
|
setScreen("auth");
|
|
2074
|
-
}, [
|
|
2075
|
-
|
|
2385
|
+
}, [
|
|
2386
|
+
modal,
|
|
2387
|
+
busy,
|
|
2388
|
+
pendingApproval
|
|
2389
|
+
]);
|
|
2390
|
+
const hasMultipleAgents = useMemo(() => Object.keys(agentRegistry).length > 1, [agentRegistry]);
|
|
2076
2391
|
useKeyboard((key) => {
|
|
2077
2392
|
if (modal.isOpen) return;
|
|
2078
2393
|
if (key.ctrl && key.name === "," && screen !== "auth") {
|
|
@@ -2087,6 +2402,18 @@ function AppShell() {
|
|
|
2087
2402
|
}));
|
|
2088
2403
|
return;
|
|
2089
2404
|
}
|
|
2405
|
+
if (key.ctrl && key.name === "a" && screen === "chat" && hasMultipleAgents && !busy) {
|
|
2406
|
+
modal.open(/* @__PURE__ */ jsx(AgentPickerModal, {
|
|
2407
|
+
agents: agentRegistry,
|
|
2408
|
+
currentAgentId: pickedAgent.id,
|
|
2409
|
+
onPick: onPickAgent
|
|
2410
|
+
}));
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
if (key.shift && key.name === "tab" && screen === "chat" && hasMultipleAgents && !busy) {
|
|
2414
|
+
onCycleAgent();
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2090
2417
|
if (key.name !== "escape") return;
|
|
2091
2418
|
if (busy || pendingApproval) return onAbort();
|
|
2092
2419
|
if (screen === "chat") return onOpenSessions();
|
|
@@ -2101,11 +2428,12 @@ function AppShell() {
|
|
|
2101
2428
|
}
|
|
2102
2429
|
renderer.destroy();
|
|
2103
2430
|
});
|
|
2104
|
-
const hints = useMemo(() => buildHints(screen, busy, !!pendingApproval, currentSession), [
|
|
2431
|
+
const hints = useMemo(() => buildHints(screen, busy, !!pendingApproval, currentSession, hasMultipleAgents), [
|
|
2105
2432
|
screen,
|
|
2106
2433
|
busy,
|
|
2107
2434
|
pendingApproval,
|
|
2108
|
-
currentSession
|
|
2435
|
+
currentSession,
|
|
2436
|
+
hasMultipleAgents
|
|
2109
2437
|
]);
|
|
2110
2438
|
const contextUsage = useMemo(() => {
|
|
2111
2439
|
if (screen !== "chat" || !picked) return null;
|
|
@@ -2158,11 +2486,12 @@ function AppShell() {
|
|
|
2158
2486
|
}), /* @__PURE__ */ jsx(Footer, {
|
|
2159
2487
|
hints,
|
|
2160
2488
|
picked,
|
|
2489
|
+
agent: screen === "chat" && hasMultipleAgents ? pickedAgent : null,
|
|
2161
2490
|
context: contextUsage
|
|
2162
2491
|
})]
|
|
2163
2492
|
});
|
|
2164
2493
|
}
|
|
2165
|
-
function buildHints(screen, busy, pending, currentSession) {
|
|
2494
|
+
function buildHints(screen, busy, pending, currentSession, hasMultipleAgents) {
|
|
2166
2495
|
if (pending) return [
|
|
2167
2496
|
{
|
|
2168
2497
|
key: "↑↓",
|
|
@@ -2218,6 +2547,10 @@ function buildHints(screen, busy, pending, currentSession) {
|
|
|
2218
2547
|
key: "↵",
|
|
2219
2548
|
label: "send"
|
|
2220
2549
|
},
|
|
2550
|
+
...hasMultipleAgents ? [{
|
|
2551
|
+
key: "shift+tab",
|
|
2552
|
+
label: "agent"
|
|
2553
|
+
}] : [],
|
|
2221
2554
|
{
|
|
2222
2555
|
key: "ctrl+m",
|
|
2223
2556
|
label: "model"
|
|
@@ -2363,7 +2696,7 @@ let runTuiInvoked = false;
|
|
|
2363
2696
|
* to `runTui({ storageDir, prefix })`.
|
|
2364
2697
|
*
|
|
2365
2698
|
* ```ts
|
|
2366
|
-
* import { BUILTIN_PROVIDERS } from 'zidane/chat'
|
|
2699
|
+
* import { BUILTIN_AGENTS, BUILTIN_PROVIDERS } from 'zidane/chat'
|
|
2367
2700
|
* import { runTui } from 'zidane/tui'
|
|
2368
2701
|
* import { createRemoteStore } from 'zidane/session' // for the `store` option
|
|
2369
2702
|
*
|
|
@@ -2372,6 +2705,7 @@ let runTuiInvoked = false;
|
|
|
2372
2705
|
* await runTui({ storageDir: '/data', prefix: 'myapp' })
|
|
2373
2706
|
* await runTui({ providers: { ...BUILTIN_PROVIDERS, mine: myDescriptor } })
|
|
2374
2707
|
* await runTui({ store: createRemoteStore({ url: '…' }) })
|
|
2708
|
+
* await runTui({ agents: { ...BUILTIN_AGENTS, debug: myDebugProfile } })
|
|
2375
2709
|
* ```
|
|
2376
2710
|
*/
|
|
2377
2711
|
async function runTui(options = {}) {
|
|
@@ -2381,19 +2715,23 @@ async function runTui(options = {}) {
|
|
|
2381
2715
|
const cause = err instanceof Error ? err.message : String(err);
|
|
2382
2716
|
process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${cause}\n`);
|
|
2383
2717
|
});
|
|
2384
|
-
const config = resolveConfig(
|
|
2718
|
+
const config = resolveConfig({
|
|
2719
|
+
...options,
|
|
2720
|
+
store: options.store ?? ((paths) => createTuiStore(paths.db))
|
|
2721
|
+
});
|
|
2385
2722
|
let done = () => {};
|
|
2386
2723
|
const exited = new Promise((resolve) => {
|
|
2387
2724
|
done = resolve;
|
|
2388
2725
|
});
|
|
2389
2726
|
createRoot(await createCliRenderer({
|
|
2390
2727
|
exitOnCtrlC: true,
|
|
2391
|
-
onDestroy: () => done()
|
|
2728
|
+
onDestroy: () => done(),
|
|
2729
|
+
debounceDelay: 0
|
|
2392
2730
|
})).render(/* @__PURE__ */ jsx(App, { config }));
|
|
2393
2731
|
await exited;
|
|
2394
2732
|
process.exit(0);
|
|
2395
2733
|
}
|
|
2396
2734
|
//#endregion
|
|
2397
|
-
export { App, AuthScreen, ChatScreen, Footer, Modal, ModalRoot, ModelPickerModal, SessionsScreen, SettingsModal, Spinner, Transcript, buildMdStyle, isVisible, marginTopFor, onInputSubmit, runTui, useMdStyle, useModal, useModalAwareFocus };
|
|
2735
|
+
export { AgentPickerModal, App, AuthScreen, ChatScreen, Footer, Modal, ModalRoot, ModelPickerModal, SessionsScreen, SettingsModal, Spinner, Transcript, accentColor, buildMdStyle, isVisible, marginTopFor, onInputSubmit, runTui, useMdStyle, useModal, useModalAwareFocus };
|
|
2398
2736
|
|
|
2399
2737
|
//# sourceMappingURL=tui.js.map
|