zidane 5.0.4 → 5.0.6
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/chat.d.ts +68 -12
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/tui.d.ts +13 -15
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +415 -135
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-BF3hMNgo.js → turn-operations-BfEh-GER.js} +133 -29
- package/dist/turn-operations-BfEh-GER.js.map +1 -0
- package/package.json +1 -1
- package/dist/turn-operations-BF3hMNgo.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -2,7 +2,7 @@ import { d as createAgent } from "./tools-CLazLRb4.js";
|
|
|
2
2
|
import { n as formatTokenUsage } from "./stats-DZIsGqzu.js";
|
|
3
3
|
import { n as loadSession, t as createSession } from "./session-791hhrFa.js";
|
|
4
4
|
import { createTuiStore } from "./session/sqlite.js";
|
|
5
|
-
import { $
|
|
5
|
+
import { $ as SETTINGS_TOGGLES, C as useSafeModeQueue, D as isOnSafelist, E as getSafelist, Et as findGitRoot, F as supportsOAuth, G as ageString, I as buildModelCatalog, J as shortId, K as compactPath, L as filterModelCatalog, Lt as useCompletion, N as splitPromptSegments, Ot as createSkillsCompletionProvider, P as runOAuthLogin, Q as SETTINGS_CHOICES, Qt as modelSupportsReasoning, R as indexOfEntry, Rt as detectAuth, S as useSafeModeActions, St as stripSpawnTokensLine, T as addToSafelist, Tt as toolResultText, V as discoverProjectMcps, W as generateSessionTitle, Wt as setProviderCredential, X as useEnabledToggleSet, Xt as getContextWindow, Y as listProjectFiles, Z as DEFAULT_SETTINGS, _ as discoverProjectSkills, _t as lastContextSizeFromTurns, a as ThemeProvider, at as resolveTheme, b as writeSessionExport, c as useSurfaces, d as finalizeStreamingMarkdown, dt as ConfigProvider, et as SettingsProvider, f as finalizeStreamingMarkdownForOwner, ft as useConfig, g as defaultSkillScanPaths, gt as eventsFromTurns, h as buildSkillsConfig, ht as deriveSessionTitle, i as turnAsText, it as resolveChipColor, j as suggestSafelistEntry, jt as createFilesCompletionProvider, kt as uniqueSkillNamesFromReferences, m as useStreamBuffer, n as deleteTurnSafely, nn as piIdOf, o as useColors, p as turnContextSize, pt as resolveConfig, q as fmtTokens, r as truncateTurnsAt, s as useSelectStyle, tt as useSettings, u as useTheme, vt as listSessionMeta, wt as toolCallPreview, x as SafeModeProvider, xt as selectableTurnIds, z as buildMcpServers } from "./turn-operations-BfEh-GER.js";
|
|
6
6
|
import { Buffer } from "node:buffer";
|
|
7
7
|
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { RGBA, SyntaxStyle, addDefaultParsers, createCliRenderer, defaultTextareaKeyBindings, getTreeSitterClient } from "@opentui/core";
|
|
@@ -108,7 +108,7 @@ function Modal({ title, bottomTitle, onClose, disableEscape = false, children, m
|
|
|
108
108
|
//#endregion
|
|
109
109
|
//#region src/tui/agent-picker.tsx
|
|
110
110
|
/** Cap the scroll window — a long custom registry shouldn't push the modal off-screen. */
|
|
111
|
-
const VISIBLE_ROW_CAP
|
|
111
|
+
const VISIBLE_ROW_CAP = 10;
|
|
112
112
|
/**
|
|
113
113
|
* Modal that lists the registered {@link AgentProfile}s and lets the user
|
|
114
114
|
* pick one. Rows show: `● selected · label description`.
|
|
@@ -130,8 +130,8 @@ function AgentPickerModal({ agents, currentAgentId, onPick }) {
|
|
|
130
130
|
description: p.description,
|
|
131
131
|
value: p.id
|
|
132
132
|
})), [profiles, currentAgentId]);
|
|
133
|
-
if (profiles.length === 0) return /* @__PURE__ */ jsx(EmptyState$
|
|
134
|
-
const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP
|
|
133
|
+
if (profiles.length === 0) return /* @__PURE__ */ jsx(EmptyState$1, {});
|
|
134
|
+
const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP);
|
|
135
135
|
const currentMissing = initialIndex < 0;
|
|
136
136
|
const safeIndex = currentMissing ? 0 : initialIndex;
|
|
137
137
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
@@ -176,7 +176,7 @@ function AgentPickerModal({ agents, currentAgentId, onPick }) {
|
|
|
176
176
|
]
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
|
-
function EmptyState$
|
|
179
|
+
function EmptyState$1() {
|
|
180
180
|
const COLOR = useColors();
|
|
181
181
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
182
182
|
title: "select agent",
|
|
@@ -733,7 +733,7 @@ function Transcript({ events, settings, selectedTurnId = null }) {
|
|
|
733
733
|
});
|
|
734
734
|
return () => cancelAnimationFrame(handle);
|
|
735
735
|
}, [selectedTurnId, anchors]);
|
|
736
|
-
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyState
|
|
736
|
+
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyState, {});
|
|
737
737
|
return /* @__PURE__ */ jsx("scrollbox", {
|
|
738
738
|
ref: scrollboxRef,
|
|
739
739
|
focusable: false,
|
|
@@ -905,7 +905,7 @@ function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds }) {
|
|
|
905
905
|
}, i))
|
|
906
906
|
});
|
|
907
907
|
}
|
|
908
|
-
function EmptyState
|
|
908
|
+
function EmptyState() {
|
|
909
909
|
return /* @__PURE__ */ jsx("box", {
|
|
910
910
|
style: {
|
|
911
911
|
flexGrow: 1,
|
|
@@ -1216,12 +1216,6 @@ function ToolResultBlock({ text, indent }) {
|
|
|
1216
1216
|
}
|
|
1217
1217
|
//#endregion
|
|
1218
1218
|
//#region src/tui/effort-picker.tsx
|
|
1219
|
-
/**
|
|
1220
|
-
* Reasoning effort options surfaced in the picker. Mirrors {@link ThinkingLevel}
|
|
1221
|
-
* minus `'adaptive'` (Anthropic-only — opt in via {@link EffortPickerModal}'s
|
|
1222
|
-
* `supportsAdaptive` flag rather than confusing OpenAI / OpenRouter users with
|
|
1223
|
-
* a level their model silently treats as `'off'`).
|
|
1224
|
-
*/
|
|
1225
1219
|
const BASE_LEVELS = [
|
|
1226
1220
|
{
|
|
1227
1221
|
id: "off",
|
|
@@ -1248,58 +1242,162 @@ const ADAPTIVE_LEVEL = {
|
|
|
1248
1242
|
id: "adaptive",
|
|
1249
1243
|
description: "model decides per-turn (Anthropic)"
|
|
1250
1244
|
};
|
|
1251
|
-
/**
|
|
1252
|
-
* Modal that lets the user pick a reasoning effort for the active model.
|
|
1253
|
-
* Only surfaced for models whose registry entry reports
|
|
1254
|
-
* `reasoning: true` — see `modelSupportsReasoning`.
|
|
1255
|
-
*
|
|
1256
|
-
* `'adaptive'` is Anthropic-only; pass `supportsAdaptive` to surface it.
|
|
1257
|
-
*/
|
|
1258
1245
|
function EffortPickerModal({ current, supportsAdaptive, onPick }) {
|
|
1259
1246
|
const COLOR = useColors();
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1247
|
+
const SURFACE = useSurfaces();
|
|
1248
|
+
const inputRef = useRef(null);
|
|
1249
|
+
const [query, setQuery] = useState("");
|
|
1250
|
+
const levels = useMemo(() => {
|
|
1251
|
+
return (supportsAdaptive ? [...BASE_LEVELS, ADAPTIVE_LEVEL] : BASE_LEVELS).map((l) => ({
|
|
1252
|
+
...l,
|
|
1253
|
+
searchCorpus: `${l.id} ${l.description}`.toLowerCase()
|
|
1254
|
+
}));
|
|
1255
|
+
}, [supportsAdaptive]);
|
|
1256
|
+
const filtered = useMemo(() => {
|
|
1257
|
+
const trimmed = query.trim().toLowerCase();
|
|
1258
|
+
if (!trimmed) return levels;
|
|
1259
|
+
const terms = trimmed.split(/\s+/);
|
|
1260
|
+
return levels.filter((l) => terms.every((t) => l.searchCorpus.includes(t)));
|
|
1261
|
+
}, [levels, query]);
|
|
1262
|
+
const [selectedIdx, setSelectedIdx] = useState(() => {
|
|
1268
1263
|
const idx = levels.findIndex((l) => l.id === current);
|
|
1269
|
-
|
|
1270
|
-
|
|
1264
|
+
if (idx >= 0) return idx;
|
|
1265
|
+
const fallback = levels.findIndex((l) => l.id === "medium");
|
|
1266
|
+
return fallback < 0 ? 0 : fallback;
|
|
1267
|
+
});
|
|
1268
|
+
const handleQueryChange = useCallback((next) => {
|
|
1269
|
+
setQuery(next);
|
|
1270
|
+
setSelectedIdx(0);
|
|
1271
|
+
}, []);
|
|
1272
|
+
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
1273
|
+
const commit = () => {
|
|
1274
|
+
const row = filtered[safeIndex];
|
|
1275
|
+
if (row) onPick(row.id);
|
|
1276
|
+
};
|
|
1277
|
+
useEffect(() => {
|
|
1278
|
+
inputRef.current?.focus();
|
|
1279
|
+
}, []);
|
|
1280
|
+
useKeyboard((key) => {
|
|
1281
|
+
if (key.name === "up") {
|
|
1282
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
if (key.name === "down") {
|
|
1286
|
+
setSelectedIdx((i) => Math.min(filtered.length - 1, i + 1));
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (key.name === "return") commit();
|
|
1290
|
+
});
|
|
1271
1291
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
1272
1292
|
title: "select reasoning effort",
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1293
|
+
maxWidth: 80,
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsx("box", {
|
|
1296
|
+
style: {
|
|
1297
|
+
border: true,
|
|
1298
|
+
borderColor: COLOR.borderActive,
|
|
1299
|
+
paddingLeft: 1,
|
|
1300
|
+
paddingRight: 1,
|
|
1301
|
+
height: 3
|
|
1302
|
+
},
|
|
1303
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1304
|
+
ref: inputRef,
|
|
1305
|
+
focused: true,
|
|
1306
|
+
placeholder: "search effort levels…",
|
|
1307
|
+
onInput: handleQueryChange,
|
|
1308
|
+
onSubmit: () => {},
|
|
1309
|
+
style: { flexGrow: 1 }
|
|
1310
|
+
})
|
|
1311
|
+
}),
|
|
1312
|
+
/* @__PURE__ */ jsx("box", {
|
|
1313
|
+
style: {
|
|
1314
|
+
flexDirection: "column",
|
|
1315
|
+
flexShrink: 0
|
|
1316
|
+
},
|
|
1317
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
|
|
1318
|
+
fg: COLOR.dim,
|
|
1319
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1320
|
+
fg: COLOR.mute,
|
|
1321
|
+
children: "no levels match "
|
|
1322
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1323
|
+
fg: COLOR.warn,
|
|
1324
|
+
children: query.trim()
|
|
1325
|
+
})]
|
|
1326
|
+
}) : filtered.map((level, i) => /* @__PURE__ */ jsx(EffortRow, {
|
|
1327
|
+
level,
|
|
1328
|
+
isCurrent: level.id === current,
|
|
1329
|
+
isFocused: i === safeIndex,
|
|
1330
|
+
highlightBg: SURFACE.selection
|
|
1331
|
+
}, level.id))
|
|
1332
|
+
}),
|
|
1333
|
+
/* @__PURE__ */ jsxs("text", {
|
|
1334
|
+
fg: COLOR.dim,
|
|
1335
|
+
children: [
|
|
1336
|
+
/* @__PURE__ */ jsx("span", {
|
|
1337
|
+
fg: COLOR.warn,
|
|
1338
|
+
children: "↑↓"
|
|
1339
|
+
}),
|
|
1340
|
+
" navigate · ",
|
|
1341
|
+
/* @__PURE__ */ jsx("span", {
|
|
1342
|
+
fg: COLOR.warn,
|
|
1343
|
+
children: "↵"
|
|
1344
|
+
}),
|
|
1345
|
+
" select · ",
|
|
1346
|
+
/* @__PURE__ */ jsx("span", {
|
|
1347
|
+
fg: COLOR.warn,
|
|
1348
|
+
children: "esc"
|
|
1349
|
+
}),
|
|
1350
|
+
" close · ",
|
|
1351
|
+
/* @__PURE__ */ jsx("span", {
|
|
1352
|
+
fg: COLOR.mute,
|
|
1353
|
+
children: `${filtered.length} / ${levels.length} level${levels.length === 1 ? "" : "s"}`
|
|
1354
|
+
})
|
|
1355
|
+
]
|
|
1356
|
+
})
|
|
1357
|
+
]
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Single row in the picker. Mirrors `ModelRow` in `model-picker.tsx`:
|
|
1362
|
+
* `●` marker for the current pick, single-space middle-dot separators,
|
|
1363
|
+
* focused row gets the `surfaces.selection` background lift.
|
|
1364
|
+
*/
|
|
1365
|
+
function EffortRow({ level, isCurrent, isFocused, highlightBg }) {
|
|
1366
|
+
const COLOR = useColors();
|
|
1367
|
+
const marker = isCurrent ? "●" : " ";
|
|
1368
|
+
return /* @__PURE__ */ jsx("box", {
|
|
1369
|
+
style: {
|
|
1370
|
+
height: 1,
|
|
1371
|
+
paddingLeft: 1,
|
|
1372
|
+
paddingRight: 1,
|
|
1373
|
+
flexShrink: 0,
|
|
1374
|
+
backgroundColor: isFocused ? highlightBg : void 0
|
|
1375
|
+
},
|
|
1376
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
1377
|
+
wrapMode: "none",
|
|
1285
1378
|
children: [
|
|
1286
1379
|
/* @__PURE__ */ jsx("span", {
|
|
1287
|
-
fg: COLOR.
|
|
1288
|
-
children:
|
|
1380
|
+
fg: isCurrent ? COLOR.brand : COLOR.mute,
|
|
1381
|
+
children: marker
|
|
1289
1382
|
}),
|
|
1290
|
-
" navigate · ",
|
|
1291
1383
|
/* @__PURE__ */ jsx("span", {
|
|
1292
|
-
fg: COLOR.
|
|
1293
|
-
children: "
|
|
1384
|
+
fg: COLOR.mute,
|
|
1385
|
+
children: " "
|
|
1294
1386
|
}),
|
|
1295
|
-
" select · ",
|
|
1296
1387
|
/* @__PURE__ */ jsx("span", {
|
|
1297
|
-
fg: COLOR.
|
|
1298
|
-
children:
|
|
1388
|
+
fg: isFocused ? COLOR.brand : COLOR.dim,
|
|
1389
|
+
children: level.id
|
|
1299
1390
|
}),
|
|
1300
|
-
"
|
|
1391
|
+
/* @__PURE__ */ jsx("span", {
|
|
1392
|
+
fg: COLOR.mute,
|
|
1393
|
+
children: " · "
|
|
1394
|
+
}),
|
|
1395
|
+
/* @__PURE__ */ jsx("span", {
|
|
1396
|
+
fg: COLOR.mute,
|
|
1397
|
+
children: level.description
|
|
1398
|
+
})
|
|
1301
1399
|
]
|
|
1302
|
-
})
|
|
1400
|
+
})
|
|
1303
1401
|
});
|
|
1304
1402
|
}
|
|
1305
1403
|
//#endregion
|
|
@@ -1465,50 +1563,134 @@ function McpsSettingsModal({ catalog }) {
|
|
|
1465
1563
|
}
|
|
1466
1564
|
//#endregion
|
|
1467
1565
|
//#region src/tui/model-picker.tsx
|
|
1468
|
-
/** Cap the visible scroll window so a 30-model list doesn't push the modal off-screen. */
|
|
1469
|
-
const VISIBLE_ROW_CAP = 12;
|
|
1470
1566
|
/**
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1567
|
+
* Cross-provider, searchable model picker.
|
|
1568
|
+
*
|
|
1569
|
+
* The picker unions every available provider's models into one flat
|
|
1570
|
+
* catalog, lets the user filter with a typed query, and returns both
|
|
1571
|
+
* the chosen provider and model id on commit. Pick a model from a
|
|
1572
|
+
* different provider than the current one and the caller is expected
|
|
1573
|
+
* to swap the active `ProviderAuth` + rebuild the agent — see
|
|
1574
|
+
* `app.tsx`'s `onPickModel` for the canonical wiring.
|
|
1475
1575
|
*
|
|
1476
|
-
*
|
|
1576
|
+
* Geometry:
|
|
1577
|
+
* - Top: single-row search input. The input owns focus so the user
|
|
1578
|
+
* can type immediately; ↑/↓/↵/⎋ are intercepted before the input's
|
|
1579
|
+
* text handler sees them so navigation works without losing focus.
|
|
1580
|
+
* - Middle: windowed list of catalog rows around the current
|
|
1581
|
+
* selection. Each row shows the model name (or id) in brand color,
|
|
1582
|
+
* the provider label in dim, and a compact capability suffix
|
|
1583
|
+
* (`ctx 200k · reasoning · vision`).
|
|
1584
|
+
* - Bottom: shortcut hint row.
|
|
1585
|
+
*
|
|
1586
|
+
* Empty states:
|
|
1587
|
+
* - No available providers → "no providers configured" notice.
|
|
1588
|
+
* - Query has no matches → "no matches" row, keep the search input
|
|
1589
|
+
* live so the user can backspace out.
|
|
1477
1590
|
*/
|
|
1478
|
-
|
|
1591
|
+
/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */
|
|
1592
|
+
const VISIBLE_ROWS = 10;
|
|
1593
|
+
function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
1479
1594
|
const COLOR = useColors();
|
|
1480
|
-
const
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1595
|
+
const SURFACE = useSurfaces();
|
|
1596
|
+
const inputRef = useRef(null);
|
|
1597
|
+
const [query, setQuery] = useState("");
|
|
1598
|
+
useEffect(() => {
|
|
1599
|
+
inputRef.current?.focus();
|
|
1600
|
+
}, []);
|
|
1601
|
+
const catalog = useMemo(() => buildModelCatalog({
|
|
1602
|
+
providers,
|
|
1603
|
+
modelsFor,
|
|
1604
|
+
current
|
|
1605
|
+
}), [
|
|
1606
|
+
providers,
|
|
1607
|
+
modelsFor,
|
|
1608
|
+
current
|
|
1609
|
+
]);
|
|
1610
|
+
const filtered = useMemo(() => filterModelCatalog(catalog, query), [catalog, query]);
|
|
1611
|
+
const [selectedIdx, setSelectedIdx] = useState(() => Math.max(0, indexOfEntry(catalog, current)));
|
|
1612
|
+
const handleQueryChange = useCallback((next) => {
|
|
1613
|
+
setQuery(next);
|
|
1614
|
+
setSelectedIdx(0);
|
|
1615
|
+
}, []);
|
|
1616
|
+
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
1617
|
+
const commit = () => {
|
|
1618
|
+
const row = filtered[safeIndex];
|
|
1619
|
+
if (row) onPick({
|
|
1620
|
+
providerKey: row.providerKey,
|
|
1621
|
+
modelId: row.model.id
|
|
1622
|
+
});
|
|
1623
|
+
};
|
|
1624
|
+
const viewport = useMemo(() => {
|
|
1625
|
+
if (filtered.length <= VISIBLE_ROWS) return {
|
|
1626
|
+
start: 0,
|
|
1627
|
+
slice: filtered
|
|
1628
|
+
};
|
|
1629
|
+
const half = Math.floor(VISIBLE_ROWS / 2);
|
|
1630
|
+
let start = Math.max(0, safeIndex - half);
|
|
1631
|
+
if (start + VISIBLE_ROWS > filtered.length) start = filtered.length - VISIBLE_ROWS;
|
|
1632
|
+
return {
|
|
1633
|
+
start,
|
|
1634
|
+
slice: filtered.slice(start, start + VISIBLE_ROWS)
|
|
1635
|
+
};
|
|
1636
|
+
}, [filtered, safeIndex]);
|
|
1637
|
+
useKeyboard((key) => {
|
|
1638
|
+
if (key.name === "up") {
|
|
1639
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (key.name === "down") {
|
|
1643
|
+
setSelectedIdx((i) => Math.min(filtered.length - 1, i + 1));
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (key.name === "return") commit();
|
|
1647
|
+
});
|
|
1648
|
+
if (providers.length === 0) return /* @__PURE__ */ jsx(EmptyProvidersState, {});
|
|
1491
1649
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
1492
1650
|
title: "select model",
|
|
1651
|
+
maxWidth: 100,
|
|
1493
1652
|
children: [
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1653
|
+
/* @__PURE__ */ jsx("box", {
|
|
1654
|
+
style: {
|
|
1655
|
+
border: true,
|
|
1656
|
+
borderColor: COLOR.borderActive,
|
|
1657
|
+
paddingLeft: 1,
|
|
1658
|
+
paddingRight: 1,
|
|
1659
|
+
height: 3
|
|
1660
|
+
},
|
|
1661
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1662
|
+
ref: inputRef,
|
|
1663
|
+
focused: true,
|
|
1664
|
+
placeholder: "search models — provider, name, capability…",
|
|
1665
|
+
onInput: handleQueryChange,
|
|
1666
|
+
onSubmit: () => {},
|
|
1667
|
+
style: { flexGrow: 1 }
|
|
1668
|
+
})
|
|
1497
1669
|
}),
|
|
1498
|
-
/* @__PURE__ */ jsx("
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1670
|
+
/* @__PURE__ */ jsx("box", {
|
|
1671
|
+
style: {
|
|
1672
|
+
flexDirection: "column",
|
|
1673
|
+
height: VISIBLE_ROWS,
|
|
1674
|
+
flexShrink: 0
|
|
1675
|
+
},
|
|
1676
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
|
|
1677
|
+
fg: COLOR.dim,
|
|
1678
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1679
|
+
fg: COLOR.mute,
|
|
1680
|
+
children: "no models match "
|
|
1681
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1682
|
+
fg: COLOR.warn,
|
|
1683
|
+
children: query.trim()
|
|
1684
|
+
})]
|
|
1685
|
+
}) : viewport.slice.map((entry, i) => /* @__PURE__ */ jsx(ModelRow, {
|
|
1686
|
+
entry,
|
|
1687
|
+
isCurrent: entry.providerKey === current.providerKey && entry.model.id === current.modelId,
|
|
1688
|
+
isFocused: viewport.start + i === safeIndex,
|
|
1689
|
+
highlightBg: SURFACE.selection
|
|
1690
|
+
}, `${entry.providerKey}:${entry.model.id}`))
|
|
1509
1691
|
}),
|
|
1510
1692
|
/* @__PURE__ */ jsxs("text", {
|
|
1511
|
-
fg: COLOR.
|
|
1693
|
+
fg: COLOR.dim,
|
|
1512
1694
|
children: [
|
|
1513
1695
|
/* @__PURE__ */ jsx("span", {
|
|
1514
1696
|
fg: COLOR.warn,
|
|
@@ -1524,38 +1706,94 @@ function ModelPickerModal({ models, currentModelId, onPick }) {
|
|
|
1524
1706
|
fg: COLOR.warn,
|
|
1525
1707
|
children: "esc"
|
|
1526
1708
|
}),
|
|
1527
|
-
" close"
|
|
1709
|
+
" close · ",
|
|
1710
|
+
/* @__PURE__ */ jsx("span", {
|
|
1711
|
+
fg: COLOR.mute,
|
|
1712
|
+
children: `${filtered.length} / ${catalog.length} model${catalog.length === 1 ? "" : "s"}`
|
|
1713
|
+
})
|
|
1528
1714
|
]
|
|
1529
1715
|
})
|
|
1530
1716
|
]
|
|
1531
1717
|
});
|
|
1532
1718
|
}
|
|
1533
|
-
|
|
1719
|
+
/**
|
|
1720
|
+
* Single row in the picker. Renders the model name in `brand`, the
|
|
1721
|
+
* provider tag in `dim`, and the capability suffix in `mute`. The
|
|
1722
|
+
* focused row gets a subtle selection background (same surface as
|
|
1723
|
+
* select-turn mode in the transcript) so it pops without a separate
|
|
1724
|
+
* marker glyph competing with the `●` "current" indicator.
|
|
1725
|
+
*/
|
|
1726
|
+
function ModelRow({ entry, isCurrent, isFocused, highlightBg }) {
|
|
1727
|
+
const COLOR = useColors();
|
|
1728
|
+
const marker = isCurrent ? "●" : " ";
|
|
1729
|
+
return /* @__PURE__ */ jsx("box", {
|
|
1730
|
+
style: {
|
|
1731
|
+
height: 1,
|
|
1732
|
+
paddingLeft: 1,
|
|
1733
|
+
paddingRight: 1,
|
|
1734
|
+
flexShrink: 0,
|
|
1735
|
+
backgroundColor: isFocused ? highlightBg : void 0
|
|
1736
|
+
},
|
|
1737
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
1738
|
+
wrapMode: "none",
|
|
1739
|
+
children: [
|
|
1740
|
+
/* @__PURE__ */ jsx("span", {
|
|
1741
|
+
fg: isCurrent ? COLOR.brand : COLOR.mute,
|
|
1742
|
+
children: marker
|
|
1743
|
+
}),
|
|
1744
|
+
/* @__PURE__ */ jsx("span", {
|
|
1745
|
+
fg: COLOR.mute,
|
|
1746
|
+
children: " "
|
|
1747
|
+
}),
|
|
1748
|
+
/* @__PURE__ */ jsx("span", {
|
|
1749
|
+
fg: isFocused ? COLOR.brand : COLOR.dim,
|
|
1750
|
+
children: entry.model.name ?? entry.model.id
|
|
1751
|
+
}),
|
|
1752
|
+
/* @__PURE__ */ jsx("span", {
|
|
1753
|
+
fg: COLOR.mute,
|
|
1754
|
+
children: " · "
|
|
1755
|
+
}),
|
|
1756
|
+
/* @__PURE__ */ jsx("span", {
|
|
1757
|
+
fg: COLOR.model,
|
|
1758
|
+
children: entry.providerLabel
|
|
1759
|
+
}),
|
|
1760
|
+
/* @__PURE__ */ jsx("span", {
|
|
1761
|
+
fg: COLOR.mute,
|
|
1762
|
+
children: " · "
|
|
1763
|
+
}),
|
|
1764
|
+
/* @__PURE__ */ jsx("span", {
|
|
1765
|
+
fg: COLOR.mute,
|
|
1766
|
+
children: describeModel(entry.model)
|
|
1767
|
+
})
|
|
1768
|
+
]
|
|
1769
|
+
})
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
function EmptyProvidersState() {
|
|
1534
1773
|
const COLOR = useColors();
|
|
1535
1774
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
1536
1775
|
title: "select model",
|
|
1537
1776
|
children: [/* @__PURE__ */ jsx("text", {
|
|
1538
1777
|
fg: COLOR.dim,
|
|
1539
|
-
children: "No
|
|
1778
|
+
children: "No authed providers — configure one via"
|
|
1540
1779
|
}), /* @__PURE__ */ jsxs("text", {
|
|
1541
|
-
fg: COLOR.
|
|
1780
|
+
fg: COLOR.dim,
|
|
1542
1781
|
children: [
|
|
1543
|
-
"Set",
|
|
1544
1782
|
/* @__PURE__ */ jsx("span", {
|
|
1545
1783
|
fg: COLOR.model,
|
|
1546
|
-
children: "
|
|
1784
|
+
children: " settings → re-configure providers"
|
|
1547
1785
|
}),
|
|
1548
|
-
"
|
|
1786
|
+
" or ",
|
|
1549
1787
|
/* @__PURE__ */ jsx("span", {
|
|
1550
1788
|
fg: COLOR.model,
|
|
1551
|
-
children: "
|
|
1789
|
+
children: "esc → sessions → settings"
|
|
1552
1790
|
}),
|
|
1553
|
-
"
|
|
1791
|
+
" first."
|
|
1554
1792
|
]
|
|
1555
1793
|
})]
|
|
1556
1794
|
});
|
|
1557
1795
|
}
|
|
1558
|
-
/** "ctx 200k · reasoning · vision" — compact
|
|
1796
|
+
/** "ctx 200k · reasoning · vision" — compact capability blurb. */
|
|
1559
1797
|
function describeModel(m) {
|
|
1560
1798
|
const parts = [`ctx ${fmtTokens(m.contextWindow)}`];
|
|
1561
1799
|
if (m.reasoning) parts.push("reasoning");
|
|
@@ -4131,6 +4369,7 @@ function AppShell() {
|
|
|
4131
4369
|
const SURFACE = useSurfaces();
|
|
4132
4370
|
const queue = useSafeModeQueue();
|
|
4133
4371
|
const { requestApproval, resolveHead, denyAll } = useSafeModeActions();
|
|
4372
|
+
const pendingApproval = queue[0] ?? null;
|
|
4134
4373
|
const { providers: providerRegistry, agents: agentRegistry, initialAgentId, store, stateStore, modelsFor, resumeProvider, initialPicked, initialState } = config;
|
|
4135
4374
|
const lastResumedSessionId = initialState.lastSessionId;
|
|
4136
4375
|
const dataDir = config.paths.userDir;
|
|
@@ -4140,8 +4379,7 @@ function AppShell() {
|
|
|
4140
4379
|
useEffect(() => {
|
|
4141
4380
|
safeModeEnabledRef.current = settings.safeMode;
|
|
4142
4381
|
}, [settings.safeMode]);
|
|
4143
|
-
const [projectDir] = useState(() => process.cwd());
|
|
4144
|
-
const [sessionProjectRoot] = useState(() => findGitRoot(process.cwd()) ?? process.cwd());
|
|
4382
|
+
const [projectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd());
|
|
4145
4383
|
const safelistRef = useRef(null);
|
|
4146
4384
|
const readSafelist = useCallback(() => {
|
|
4147
4385
|
if (safelistRef.current === null) safelistRef.current = getSafelist(dataDir, projectDir);
|
|
@@ -4411,13 +4649,13 @@ function AppShell() {
|
|
|
4411
4649
|
config.prefix
|
|
4412
4650
|
]);
|
|
4413
4651
|
const refreshSessions = useCallback(async () => {
|
|
4414
|
-
const list = await listSessionMeta(store, settings.showAllProjects ? void 0 : { projectRoot:
|
|
4652
|
+
const list = await listSessionMeta(store, settings.showAllProjects ? void 0 : { projectRoot: projectDir });
|
|
4415
4653
|
setSessions(list);
|
|
4416
4654
|
return list;
|
|
4417
4655
|
}, [
|
|
4418
4656
|
store,
|
|
4419
4657
|
settings.showAllProjects,
|
|
4420
|
-
|
|
4658
|
+
projectDir
|
|
4421
4659
|
]);
|
|
4422
4660
|
const teardown = useCallback(async () => {
|
|
4423
4661
|
try {
|
|
@@ -4439,7 +4677,7 @@ function AppShell() {
|
|
|
4439
4677
|
await teardown();
|
|
4440
4678
|
const session = (id ? await loadSession(store, id) : null) ?? await createSession({
|
|
4441
4679
|
store,
|
|
4442
|
-
projectRoot:
|
|
4680
|
+
projectRoot: projectDir,
|
|
4443
4681
|
...id ? { id } : {}
|
|
4444
4682
|
});
|
|
4445
4683
|
sessionRef.current = session;
|
|
@@ -4465,7 +4703,7 @@ function AppShell() {
|
|
|
4465
4703
|
buildAgent,
|
|
4466
4704
|
store,
|
|
4467
4705
|
stateStore,
|
|
4468
|
-
|
|
4706
|
+
projectDir
|
|
4469
4707
|
]);
|
|
4470
4708
|
useEffect(() => {
|
|
4471
4709
|
if (!resumeProvider) return;
|
|
@@ -4474,7 +4712,7 @@ function AppShell() {
|
|
|
4474
4712
|
if (lastResumedSessionId) {
|
|
4475
4713
|
const data = await store.load(lastResumedSessionId);
|
|
4476
4714
|
if (cancelled) return;
|
|
4477
|
-
const sessionMatchesProject = settings.showAllProjects || data?.projectRoot != null && data.projectRoot ===
|
|
4715
|
+
const sessionMatchesProject = settings.showAllProjects || data?.projectRoot != null && data.projectRoot === projectDir;
|
|
4478
4716
|
if (data && sessionMatchesProject) {
|
|
4479
4717
|
await activateSession(lastResumedSessionId, resumeProvider.key);
|
|
4480
4718
|
return;
|
|
@@ -4495,8 +4733,15 @@ function AppShell() {
|
|
|
4495
4733
|
lastResumedSessionId,
|
|
4496
4734
|
store,
|
|
4497
4735
|
settings.showAllProjects,
|
|
4498
|
-
|
|
4736
|
+
projectDir
|
|
4499
4737
|
]);
|
|
4738
|
+
const [availableProviders, setAvailableProviders] = useState([]);
|
|
4739
|
+
const refreshAvailableProviders = useCallback(() => {
|
|
4740
|
+
setAvailableProviders(detectAuth(config.paths.userDir, providerRegistry).filter((p) => p.available));
|
|
4741
|
+
}, [config.paths.userDir, providerRegistry]);
|
|
4742
|
+
useEffect(() => {
|
|
4743
|
+
refreshAvailableProviders();
|
|
4744
|
+
}, [refreshAvailableProviders]);
|
|
4500
4745
|
const onPickProvider = useCallback(async (p) => {
|
|
4501
4746
|
const next = makePicked(p);
|
|
4502
4747
|
if (!next) return;
|
|
@@ -4505,13 +4750,15 @@ function AppShell() {
|
|
|
4505
4750
|
...stateStore.load(),
|
|
4506
4751
|
lastProvider: p.key
|
|
4507
4752
|
});
|
|
4753
|
+
refreshAvailableProviders();
|
|
4508
4754
|
if ((await refreshSessions()).length === 0) await activateSession(null, p.key);
|
|
4509
4755
|
else setScreen("sessions");
|
|
4510
4756
|
}, [
|
|
4511
4757
|
refreshSessions,
|
|
4512
4758
|
activateSession,
|
|
4513
4759
|
makePicked,
|
|
4514
|
-
stateStore
|
|
4760
|
+
stateStore,
|
|
4761
|
+
refreshAvailableProviders
|
|
4515
4762
|
]);
|
|
4516
4763
|
const onCreateSession = useCallback(async () => {
|
|
4517
4764
|
if (picked) await activateSession(null, picked.provider.key);
|
|
@@ -4532,33 +4779,63 @@ function AppShell() {
|
|
|
4532
4779
|
denyAll();
|
|
4533
4780
|
agentRef.current?.abort();
|
|
4534
4781
|
}, [denyAll]);
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4782
|
+
/**
|
|
4783
|
+
* Pick a `{ providerKey, modelId }` tuple from the cross-provider
|
|
4784
|
+
* model picker. Two branches:
|
|
4785
|
+
*
|
|
4786
|
+
* - **Same provider, different model** — update `picked.model` +
|
|
4787
|
+
* remember it in `lastModelByProvider`. The active agent keeps
|
|
4788
|
+
* running; `agent.run({ model })` accepts the model per-call so
|
|
4789
|
+
* no session rebuild is needed.
|
|
4790
|
+
* - **Different provider** — swap the active `ProviderAuth`,
|
|
4791
|
+
* persist BOTH `lastProvider` + `lastModelByProvider`, then
|
|
4792
|
+
* re-activate the current session against the new provider's
|
|
4793
|
+
* factory. The session id (and conversation history) is
|
|
4794
|
+
* preserved; only the bound agent + provider instance change.
|
|
4795
|
+
*
|
|
4796
|
+
* Either branch re-resolves the reasoning effort: a non-reasoning
|
|
4797
|
+
* model loses its `effort`, a reasoning model gets the remembered
|
|
4798
|
+
* per-model value (falling back to a sensible default).
|
|
4799
|
+
*/
|
|
4800
|
+
const onPickModel = useCallback(async (next) => {
|
|
4801
|
+
const nextProvider = availableProviders.find((p) => p.key === next.providerKey);
|
|
4802
|
+
if (!nextProvider) {
|
|
4803
|
+
debugLog("onPickModel: unknown provider key", next.providerKey);
|
|
4804
|
+
modal.close();
|
|
4805
|
+
return;
|
|
4806
|
+
}
|
|
4807
|
+
const descriptor = providerRegistry[nextProvider.key];
|
|
4808
|
+
const prior = stateStore.load();
|
|
4809
|
+
const providerChanged = picked?.provider.key !== nextProvider.key;
|
|
4810
|
+
stateStore.save({
|
|
4811
|
+
...prior,
|
|
4812
|
+
...providerChanged ? { lastProvider: nextProvider.key } : {},
|
|
4813
|
+
lastModelByProvider: {
|
|
4814
|
+
...prior.lastModelByProvider,
|
|
4815
|
+
[nextProvider.key]: next.modelId
|
|
4816
|
+
}
|
|
4817
|
+
});
|
|
4818
|
+
const nextEffort = descriptor ? effortForModel(descriptor, next.modelId, prior.lastEffortByModel) : void 0;
|
|
4819
|
+
setPicked(nextEffort ? {
|
|
4820
|
+
provider: nextProvider,
|
|
4821
|
+
model: next.modelId,
|
|
4822
|
+
effort: nextEffort
|
|
4823
|
+
} : {
|
|
4824
|
+
provider: nextProvider,
|
|
4825
|
+
model: next.modelId
|
|
4556
4826
|
});
|
|
4557
4827
|
modal.close();
|
|
4828
|
+
if (providerChanged && currentSession && !busy && !pendingApproval) await activateSession(currentSession.id, nextProvider.key);
|
|
4558
4829
|
}, [
|
|
4559
|
-
|
|
4830
|
+
availableProviders,
|
|
4831
|
+
providerRegistry,
|
|
4560
4832
|
stateStore,
|
|
4561
|
-
|
|
4833
|
+
modal,
|
|
4834
|
+
picked,
|
|
4835
|
+
currentSession,
|
|
4836
|
+
busy,
|
|
4837
|
+
pendingApproval,
|
|
4838
|
+
activateSession
|
|
4562
4839
|
]);
|
|
4563
4840
|
const onPickEffort = useCallback((effort) => {
|
|
4564
4841
|
setPicked((prev) => {
|
|
@@ -4661,7 +4938,6 @@ function AppShell() {
|
|
|
4661
4938
|
setBusy(false);
|
|
4662
4939
|
}
|
|
4663
4940
|
}, [picked, stream]);
|
|
4664
|
-
const pendingApproval = queue[0] ?? null;
|
|
4665
4941
|
const onReauth = useMemo(() => {
|
|
4666
4942
|
if (busy || pendingApproval) return void 0;
|
|
4667
4943
|
return () => {
|
|
@@ -4932,7 +5208,7 @@ function AppShell() {
|
|
|
4932
5208
|
}
|
|
4933
5209
|
return;
|
|
4934
5210
|
}
|
|
4935
|
-
if (key.ctrl && key.name === "
|
|
5211
|
+
if (key.ctrl && key.name === "o" && screen !== "auth") {
|
|
4936
5212
|
modal.open(/* @__PURE__ */ jsx(SettingsModal, { actions: {
|
|
4937
5213
|
onReauth,
|
|
4938
5214
|
onOpenSkills: onOpenSkillsSettings,
|
|
@@ -4952,8 +5228,12 @@ function AppShell() {
|
|
|
4952
5228
|
}
|
|
4953
5229
|
if (key.ctrl && key.name === "m" && screen === "chat" && picked && !busy) {
|
|
4954
5230
|
modal.open(/* @__PURE__ */ jsx(ModelPickerModal, {
|
|
4955
|
-
|
|
4956
|
-
|
|
5231
|
+
providers: availableProviders,
|
|
5232
|
+
modelsFor,
|
|
5233
|
+
current: {
|
|
5234
|
+
providerKey: picked.provider.key,
|
|
5235
|
+
modelId: picked.model
|
|
5236
|
+
},
|
|
4957
5237
|
onPick: onPickModel
|
|
4958
5238
|
}));
|
|
4959
5239
|
return;
|
|
@@ -5071,7 +5351,7 @@ function AppShell() {
|
|
|
5071
5351
|
onCreate: onCreateSession,
|
|
5072
5352
|
onFocusChange: setFocusedSessionId,
|
|
5073
5353
|
showAllProjects: settings.showAllProjects,
|
|
5074
|
-
currentProjectRoot:
|
|
5354
|
+
currentProjectRoot: projectDir
|
|
5075
5355
|
}),
|
|
5076
5356
|
screen === "chat" && /* @__PURE__ */ jsx(ChatScreen, {
|
|
5077
5357
|
events,
|
|
@@ -5159,7 +5439,7 @@ function buildHints({ screen, busy, pending, currentSession, hasMultipleAgents,
|
|
|
5159
5439
|
label: "session"
|
|
5160
5440
|
},
|
|
5161
5441
|
{
|
|
5162
|
-
key: "ctrl
|
|
5442
|
+
key: "ctrl+o",
|
|
5163
5443
|
label: "settings"
|
|
5164
5444
|
},
|
|
5165
5445
|
{
|
|
@@ -5190,7 +5470,7 @@ function buildHints({ screen, busy, pending, currentSession, hasMultipleAgents,
|
|
|
5190
5470
|
label: "session"
|
|
5191
5471
|
}] : [],
|
|
5192
5472
|
{
|
|
5193
|
-
key: "ctrl
|
|
5473
|
+
key: "ctrl+o",
|
|
5194
5474
|
label: "settings"
|
|
5195
5475
|
},
|
|
5196
5476
|
{
|