zidane 6.1.4 → 6.1.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.
Files changed (70) hide show
  1. package/dist/{acp-CO1EGSwh.js → acp-D-k-L0DX.js} +2 -2
  2. package/dist/{acp-CO1EGSwh.js.map → acp-D-k-L0DX.js.map} +1 -1
  3. package/dist/acp-cli.js +2 -2
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-CZKpBI1X.js → agent-BJ1NC1pA.js} +47 -8
  7. package/dist/agent-BJ1NC1pA.js.map +1 -0
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/chat/pure.d.ts +3 -3
  11. package/dist/chat.d.ts +10 -4
  12. package/dist/chat.d.ts.map +1 -1
  13. package/dist/chat.js +2 -2
  14. package/dist/contexts/daytona.d.ts +1 -1
  15. package/dist/contexts/e2b.d.ts +1 -1
  16. package/dist/eval.d.ts +1 -1
  17. package/dist/extensions.d.ts +2 -2
  18. package/dist/extensions.js +1 -1
  19. package/dist/generate-data-WL_yGVL6.js.map +1 -1
  20. package/dist/headless.d.ts +1 -1
  21. package/dist/headless.js +1 -1
  22. package/dist/{index-DC9Iy3Vn.d.ts → index-CR6uvJ3m.d.ts} +190 -7
  23. package/dist/index-CR6uvJ3m.d.ts.map +1 -0
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +1 -1
  26. package/dist/mcp.d.ts +1 -1
  27. package/dist/output/stream-json.d.ts +1 -1
  28. package/dist/output/terminal.d.ts +1 -1
  29. package/dist/presets.d.ts +1 -1
  30. package/dist/presets.js +1 -1
  31. package/dist/providers/anthropic.d.ts +1 -1
  32. package/dist/providers/arcee.d.ts +1 -1
  33. package/dist/providers/baseten.d.ts +1 -1
  34. package/dist/providers/cerebras.d.ts +1 -1
  35. package/dist/providers/local.d.ts +1 -1
  36. package/dist/providers/openai-compat.d.ts +1 -1
  37. package/dist/providers/openai.d.ts +1 -1
  38. package/dist/providers/openrouter.d.ts +1 -1
  39. package/dist/providers/xai.d.ts +1 -1
  40. package/dist/providers.d.ts +1 -1
  41. package/dist/restate.d.ts +1 -1
  42. package/dist/session/sqlite.d.ts +1 -1
  43. package/dist/session.d.ts +1 -1
  44. package/dist/skills.d.ts +1 -1
  45. package/dist/{tool-formatters-D7AQW80Z.d.ts → tool-formatters-D8w27pin.d.ts} +60 -3
  46. package/dist/tool-formatters-D8w27pin.d.ts.map +1 -0
  47. package/dist/tools/fetch-url.d.ts +1 -1
  48. package/dist/tools/web-search.d.ts +1 -1
  49. package/dist/tools.d.ts +2 -2
  50. package/dist/tools.js +1 -1
  51. package/dist/{transcript-anchors-hv97ejJn.js → transcript-anchors-C8l8zDzs.js} +20 -4
  52. package/dist/{transcript-anchors-hv97ejJn.js.map → transcript-anchors-C8l8zDzs.js.map} +1 -1
  53. package/dist/{transcript-anchors-ClIlrGxh.d.ts → transcript-anchors-DtV7fmmw.d.ts} +2 -2
  54. package/dist/{transcript-anchors-ClIlrGxh.d.ts.map → transcript-anchors-DtV7fmmw.d.ts.map} +1 -1
  55. package/dist/tui.d.ts +26 -5
  56. package/dist/tui.d.ts.map +1 -1
  57. package/dist/tui.js +658 -64
  58. package/dist/tui.js.map +1 -1
  59. package/dist/{turn-operations-mvM38tP-.d.ts → turn-operations-BsTP7T1w.d.ts} +4 -61
  60. package/dist/turn-operations-BsTP7T1w.d.ts.map +1 -0
  61. package/dist/types.d.ts +1 -1
  62. package/docs/CHAT.md +2 -0
  63. package/docs/EXTENSIONS.md +5 -2
  64. package/docs/INTERACTIONS.md +5 -1
  65. package/docs/TUI.md +3 -2
  66. package/package.json +1 -1
  67. package/dist/agent-CZKpBI1X.js.map +0 -1
  68. package/dist/index-DC9Iy3Vn.d.ts.map +0 -1
  69. package/dist/tool-formatters-D7AQW80Z.d.ts.map +0 -1
  70. package/dist/turn-operations-mvM38tP-.d.ts.map +0 -1
package/dist/tui.js CHANGED
@@ -1,5 +1,5 @@
1
- import { $ as useMcpAuthState, $n as buildPlanSystem, $t as restoreChainFallbacks, A as runOAuthLogin, Bt as createDiscoverySlot, Ct as SETTINGS_CHOICES, D as formatPathForCwd, Dt as clampFps, Et as SettingsProvider, G as buildVisibleMcpRows, Gt as useConfig, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as buildRunModel, K as indexOfServerRow, Kt as resolveConfig, L as loadMcpToolsCache, M as buildMcpServers, Mt as resolveTheme, O as fetchOAuthRedirect, Ot as useSettings, P as discoverProjectMcps, Q as useMcpAuthDispatch, Qn as buildBuildSystem, Qt as providerKeySetChanged, R as mcpToolsCachePath, St as SETTINGS_CATEGORIES, T as suggestSafelistEntries, Tn as accentColor, Tt as SETTINGS_TOGGLES, U as useMcpToolToggleMap, Ut as useDiscoveryOptional, V as subscribeMcpToolsCache, Vn as useActiveTodos, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as chainsEqual, Yt as chainPositionOf, Z as McpAuthProvider, Zt as filterChainFallbacks, _ as useSafeModeQueue, _n as discoverAgentsMd, _r as toolCallPreview, _t as buildHints, a as useSurfaces, an as bootTick, at as buildResumedToolResultsTurn, b as getSafelist, br as usageByModel, bt as useEnabledToggleSet, c as useStreamBuffer, cr as lastContextSizeFromTurns, ct as makeRequestInteraction, d as discoverProjectSkills, dr as marginTopFor, dt as useInteractionsActions, er as envSection, et as getMcpAuthStatus, ft as useInteractionsQueue, g as useSafeModeActions, gn as chatAutoCompactBehavior, gt as truncateTrailing, h as SafeModeProvider, hr as sumRunCosts, ht as hintsLength, i as useSelectStyle, ir as compactSummaryEvent, it as PRESENT_PLAN_TOOL, j as supportsOAuth, jn as TODO_STATUS_GLYPHS, jt as resolveChipColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, lr as listSessionMeta, lt as pendingInteractionsFromTurns, m as writeSessionExport, mr as stripSpawnTokensLine, mt as clipHintsToWidth, n as ThemeProvider, nn as useCompletion, nr as createChatTurnsReadTool, nt as ASK_USER_TOOL, on as buildUpdateHint, or as deriveSessionTitle, ot as createInteractionTools, pr as serverToolResultSummary, pt as EMPTY_HINTS, q as parentServerName, qt as resolveStoragePaths, r as useColors, rn as tryOpenBrowser, rt as InteractionsProvider, s as useTheme, sn as useUpdateCheck, sr as eventsFromTurns, t as computeTurnAnchors, tn as toggleChainEntry, tr as chatReferenceTokensForPrompt, u as defaultSkillScanPaths, ur as loadState, vr as toolResultText, wt as SETTINGS_INPUTS, x as isOnSafelist, xt as DEFAULT_SETTINGS, y as addToSafelist, yr as updateToolEventOutcomes, yt as listProjectFiles, z as refreshMcpToolsCatalog } from "./transcript-anchors-hv97ejJn.js";
2
- import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as effectiveContextWindow, nn as parseExtensionScanPathEnv, on as coerceExtensionConfigInput, pn as OUTPUT_RESERVE_TOKENS, q as updateInstalledExtension, rn as scanExtensionDirectories, rt as partitionExtensionCatalog, t as createAgent, tt as filterExtensionRegistry, un as resolveExtensionConfigValue, wt as looksBinary, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as KEYBINDING_KEY_COL_WIDTH } from "./agent-CZKpBI1X.js";
1
+ import { $ as useMcpAuthState, $n as buildPlanSystem, $t as restoreChainFallbacks, A as runOAuthLogin, Bt as createDiscoverySlot, Ct as SETTINGS_CHOICES, D as formatPathForCwd, Dt as clampFps, Et as SettingsProvider, G as buildVisibleMcpRows, Gt as useConfig, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as buildRunModel, K as indexOfServerRow, Kt as resolveConfig, L as loadMcpToolsCache, M as buildMcpServers, Mt as resolveTheme, O as fetchOAuthRedirect, Ot as useSettings, P as discoverProjectMcps, Q as useMcpAuthDispatch, Qn as buildBuildSystem, Qt as providerKeySetChanged, R as mcpToolsCachePath, St as SETTINGS_CATEGORIES, T as suggestSafelistEntries, Tn as accentColor, Tt as SETTINGS_TOGGLES, U as useMcpToolToggleMap, Ut as useDiscoveryOptional, V as subscribeMcpToolsCache, Vn as useActiveTodos, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as chainsEqual, Yt as chainPositionOf, Z as McpAuthProvider, Zt as filterChainFallbacks, _ as useSafeModeQueue, _n as discoverAgentsMd, _r as toolCallPreview, _t as buildHints, a as useSurfaces, an as bootTick, at as buildResumedToolResultsTurn, b as getSafelist, br as usageByModel, bt as useEnabledToggleSet, c as useStreamBuffer, cr as lastContextSizeFromTurns, ct as makeRequestInteraction, d as discoverProjectSkills, dr as marginTopFor, dt as useInteractionsActions, er as envSection, et as getMcpAuthStatus, ft as useInteractionsQueue, g as useSafeModeActions, gn as chatAutoCompactBehavior, gt as truncateTrailing, h as SafeModeProvider, hr as sumRunCosts, ht as hintsLength, i as useSelectStyle, ir as compactSummaryEvent, it as PRESENT_PLAN_TOOL, j as supportsOAuth, jn as TODO_STATUS_GLYPHS, jt as resolveChipColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, lr as listSessionMeta, lt as pendingInteractionsFromTurns, m as writeSessionExport, mr as stripSpawnTokensLine, mt as clipHintsToWidth, n as ThemeProvider, nn as useCompletion, nr as createChatTurnsReadTool, nt as ASK_USER_TOOL, on as buildUpdateHint, or as deriveSessionTitle, ot as createInteractionTools, pr as serverToolResultSummary, pt as EMPTY_HINTS, q as parentServerName, qt as resolveStoragePaths, r as useColors, rn as tryOpenBrowser, rt as InteractionsProvider, s as useTheme, sn as useUpdateCheck, sr as eventsFromTurns, t as computeTurnAnchors, tn as toggleChainEntry, tr as chatReferenceTokensForPrompt, u as defaultSkillScanPaths, ur as loadState, vr as toolResultText, wt as SETTINGS_INPUTS, x as isOnSafelist, xt as DEFAULT_SETTINGS, y as addToSafelist, yr as updateToolEventOutcomes, yt as listProjectFiles, z as refreshMcpToolsCatalog } from "./transcript-anchors-C8l8zDzs.js";
2
+ import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as effectiveContextWindow, nn as parseExtensionScanPathEnv, on as coerceExtensionConfigInput, pn as OUTPUT_RESERVE_TOKENS, q as updateInstalledExtension, rn as scanExtensionDirectories, rt as partitionExtensionCatalog, t as createAgent, tt as filterExtensionRegistry, un as resolveExtensionConfigValue, wt as looksBinary, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as KEYBINDING_KEY_COL_WIDTH } from "./agent-BJ1NC1pA.js";
3
3
  import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId, n as compactPath, o as formatTaskStatus, r as fmtCost, s as formatTaskSummary, t as ageString } from "./format-BF745pLL.js";
4
4
  import { n as appendCapped } from "./output-cap-kBUHBlnq.js";
5
5
  import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-A4ep-iJs.js";
@@ -2216,7 +2216,7 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
2216
2216
  renderDefault: () => /* @__PURE__ */ jsx$1(ToolResultBlock, {
2217
2217
  text: event.text,
2218
2218
  indent: row.paddingLeft,
2219
- maxLines: isUserBashEvent(event) ? BASH_RESULT_MAX_LINES : void 0
2219
+ maxLines: isUserBashEvent(event) ? settings.showFullShellOutput ? Number.MAX_SAFE_INTEGER : BASH_RESULT_MAX_LINES : void 0
2220
2220
  })
2221
2221
  });
2222
2222
  case "custom": return /* @__PURE__ */ jsx$1("box", {
@@ -5332,6 +5332,176 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
5332
5332
  });
5333
5333
  }
5334
5334
  /**
5335
+ * Inline, embeddable variant of the picker — the same interaction grammar
5336
+ * as {@link ModelPickerModal} (type-to-filter search, windowed list,
5337
+ * wrap-around ↑↓, ↵ pick / toggle, esc apply / cancel) without the
5338
+ * `<Modal>` chrome, so hosts can mount it inside another surface. Built
5339
+ * for `type: 'model'` extension-config rows in the Settings modal; reused
5340
+ * by anything that needs a model choice in place.
5341
+ *
5342
+ * Selection is by MODEL ID (the catalog is deduped by id — first provider
5343
+ * wins), unlike the modal's provider-aware chain entries: config values
5344
+ * store bare ids and the host routes ids to providers at spawn time.
5345
+ *
5346
+ * Commit semantics mirror the modal:
5347
+ * - single (`multi: false`): ↵ applies the focused row's id; esc cancels.
5348
+ * - multi: ↵ toggles the focused row (numbered marker = pick order); esc
5349
+ * applies when the selection is non-empty and actually changed,
5350
+ * otherwise cancels — an untouched or emptied picker never writes.
5351
+ */
5352
+ function ModelSelectList({ catalog, multi = false, initial, onApply, onCancel, visibleRows = 8 }) {
5353
+ const COLOR = useColors();
5354
+ const SURFACE = useSurfaces();
5355
+ const inputRef = useRef(null);
5356
+ const [query, setQuery] = useState("");
5357
+ useEffect(() => {
5358
+ inputRef.current?.focus();
5359
+ }, []);
5360
+ const deduped = useMemo(() => {
5361
+ const seen = /* @__PURE__ */ new Set();
5362
+ return catalog.filter((entry) => {
5363
+ if (seen.has(entry.model.id)) return false;
5364
+ seen.add(entry.model.id);
5365
+ return true;
5366
+ });
5367
+ }, [catalog]);
5368
+ const filtered = useMemo(() => filterModelCatalog(deduped, query), [deduped, query]);
5369
+ const [selectedIdx, setSelectedIdx] = useState(() => {
5370
+ const first = initial[0];
5371
+ const idx = first ? deduped.findIndex((e) => e.model.id === first) : -1;
5372
+ return Math.max(0, idx);
5373
+ });
5374
+ const handleQueryChange = useCallback((next) => {
5375
+ setQuery(next);
5376
+ setSelectedIdx(0);
5377
+ }, []);
5378
+ const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
5379
+ const [initialPicks] = useState(() => {
5380
+ const known = new Set(deduped.map((e) => e.model.id));
5381
+ return initial.filter((id) => known.has(id));
5382
+ });
5383
+ const [picks, setPicks] = useState(initialPicks);
5384
+ useKeyboard((key) => {
5385
+ if (key.name === "up" || key.name === "down") {
5386
+ setSelectedIdx((i) => {
5387
+ if (filtered.length === 0) return i;
5388
+ return ((i + (key.name === "up" ? -1 : 1)) % filtered.length + filtered.length) % filtered.length;
5389
+ });
5390
+ return;
5391
+ }
5392
+ if (key.name === "return") {
5393
+ const row = filtered[safeIndex];
5394
+ if (!row) return;
5395
+ if (!multi) {
5396
+ onApply([row.model.id]);
5397
+ return;
5398
+ }
5399
+ setPicks((prev) => prev.includes(row.model.id) ? prev.filter((id) => id !== row.model.id) : [...prev, row.model.id]);
5400
+ return;
5401
+ }
5402
+ if (key.name === "escape") {
5403
+ if (multi && picks.length > 0 && (picks.length !== initialPicks.length || picks.some((id, i) => id !== initialPicks[i]))) {
5404
+ onApply(picks);
5405
+ return;
5406
+ }
5407
+ onCancel();
5408
+ }
5409
+ });
5410
+ const viewport = useMemo(() => {
5411
+ if (filtered.length <= visibleRows) return {
5412
+ start: 0,
5413
+ slice: filtered
5414
+ };
5415
+ const half = Math.floor(visibleRows / 2);
5416
+ let start = Math.max(0, safeIndex - half);
5417
+ if (start + visibleRows > filtered.length) start = filtered.length - visibleRows;
5418
+ return {
5419
+ start,
5420
+ slice: filtered.slice(start, start + visibleRows)
5421
+ };
5422
+ }, [
5423
+ filtered,
5424
+ safeIndex,
5425
+ visibleRows
5426
+ ]);
5427
+ return /* @__PURE__ */ jsxs("box", {
5428
+ style: {
5429
+ flexDirection: "column",
5430
+ flexShrink: 0
5431
+ },
5432
+ children: [
5433
+ /* @__PURE__ */ jsx$1("box", {
5434
+ style: {
5435
+ border: true,
5436
+ borderColor: COLOR.borderActive,
5437
+ paddingLeft: 1,
5438
+ paddingRight: 1,
5439
+ height: 3
5440
+ },
5441
+ children: /* @__PURE__ */ jsx$1("input", {
5442
+ ref: inputRef,
5443
+ focused: true,
5444
+ placeholder: "search models — provider, name, capability…",
5445
+ onInput: handleQueryChange,
5446
+ onSubmit: () => {},
5447
+ style: { flexGrow: 1 }
5448
+ })
5449
+ }),
5450
+ /* @__PURE__ */ jsx$1("box", {
5451
+ style: {
5452
+ flexDirection: "column",
5453
+ height: Math.min(visibleRows, Math.max(1, filtered.length)),
5454
+ flexShrink: 0
5455
+ },
5456
+ children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
5457
+ fg: COLOR.dim,
5458
+ children: [/* @__PURE__ */ jsx$1("span", {
5459
+ fg: COLOR.mute,
5460
+ children: "no models match "
5461
+ }), /* @__PURE__ */ jsx$1("span", {
5462
+ fg: COLOR.warn,
5463
+ children: query.trim()
5464
+ })]
5465
+ }) : viewport.slice.map((entry, i) => /* @__PURE__ */ jsx$1(ModelRow, {
5466
+ entry,
5467
+ isCurrent: !multi && initialPicks.includes(entry.model.id),
5468
+ isFocused: viewport.start + i === safeIndex,
5469
+ highlightBg: SURFACE.selection,
5470
+ chainPosition: multi ? picks.includes(entry.model.id) ? picks.indexOf(entry.model.id) + 1 : null : null
5471
+ }, `${entry.providerKey}:${entry.model.id}`))
5472
+ }),
5473
+ /* @__PURE__ */ jsxs("text", {
5474
+ fg: COLOR.dim,
5475
+ children: [
5476
+ /* @__PURE__ */ jsx$1("span", {
5477
+ fg: COLOR.warn,
5478
+ children: "↑↓"
5479
+ }),
5480
+ " navigate · ",
5481
+ /* @__PURE__ */ jsx$1("span", {
5482
+ fg: COLOR.warn,
5483
+ children: "↵"
5484
+ }),
5485
+ multi ? " add/remove · " : " select · ",
5486
+ /* @__PURE__ */ jsx$1("span", {
5487
+ fg: COLOR.warn,
5488
+ children: "esc"
5489
+ }),
5490
+ multi ? " apply · " : " cancel · ",
5491
+ multi && /* @__PURE__ */ jsx$1("span", {
5492
+ fg: COLOR.brand,
5493
+ children: `${picks.length} selected · `
5494
+ }),
5495
+ /* @__PURE__ */ jsx$1("span", {
5496
+ fg: COLOR.mute,
5497
+ children: `${filtered.length} / ${deduped.length} model${deduped.length === 1 ? "" : "s"}`
5498
+ })
5499
+ ]
5500
+ })
5501
+ ]
5502
+ });
5503
+ }
5504
+ /**
5335
5505
  * Single row in the picker. Renders the model name in `brand`, the
5336
5506
  * provider tag in `dim`, and the capability suffix in `mute`. The
5337
5507
  * focused row gets a subtle selection background (same surface as
@@ -7145,7 +7315,7 @@ function estimateQuestionRowHeight(question, status) {
7145
7315
  height += 6;
7146
7316
  break;
7147
7317
  case "select":
7148
- height += question.choices.length;
7318
+ height += question.choices.length + (question.multi ? 1 : 0);
7149
7319
  break;
7150
7320
  case "confirm":
7151
7321
  height += 2;
@@ -7365,6 +7535,10 @@ function formatAnswerForDisplay(question, answer) {
7365
7535
  if (answer === void 0 || answer === "") return "(skipped)";
7366
7536
  if (question.type === "confirm") return typeof answer === "boolean" ? answer ? question.affirmLabel ?? "yes" : question.denyLabel ?? "no" : "(invalid)";
7367
7537
  if (question.type === "select") {
7538
+ if (Array.isArray(answer)) {
7539
+ const labels = answer.map((id) => question.choices.find((c) => c.id === id)?.label ?? String(id));
7540
+ return labels.length > 0 ? labels.join(", ") : "(skipped)";
7541
+ }
7368
7542
  const choice = question.choices.find((c) => c.id === answer);
7369
7543
  return choice ? choice.label : String(answer);
7370
7544
  }
@@ -7386,11 +7560,17 @@ function ActiveQuestionInput({ question, initialAnswer, onSubmit }) {
7386
7560
  onSubmit,
7387
7561
  multiLine: true
7388
7562
  });
7389
- case "select": return /* @__PURE__ */ jsx$1(SelectQuestionInput, {
7390
- question,
7391
- initialChoiceId: typeof initialAnswer === "string" ? initialAnswer : void 0,
7392
- onSubmit
7393
- });
7563
+ case "select":
7564
+ if (question.multi) return /* @__PURE__ */ jsx$1(MultiSelectQuestionInput, {
7565
+ question,
7566
+ initialChoiceIds: Array.isArray(initialAnswer) ? initialAnswer : void 0,
7567
+ onSubmit
7568
+ });
7569
+ return /* @__PURE__ */ jsx$1(SelectQuestionInput, {
7570
+ question,
7571
+ initialChoiceId: typeof initialAnswer === "string" ? initialAnswer : void 0,
7572
+ onSubmit
7573
+ });
7394
7574
  case "confirm": return /* @__PURE__ */ jsx$1(ConfirmQuestionInput, {
7395
7575
  question,
7396
7576
  initialValue: typeof initialAnswer === "boolean" ? initialAnswer : void 0,
@@ -7533,6 +7713,113 @@ function SelectQuestionInput({ question, initialChoiceId, onSubmit }) {
7533
7713
  onPick: (id) => onSubmit(id)
7534
7714
  });
7535
7715
  }
7716
+ /**
7717
+ * Multi-choice picker — space toggles the option under the cursor, ↵
7718
+ * commits the full selection as a `string[]` of choice ids. `required`
7719
+ * defaults to TRUE for selects (an empty pick is rarely meaningful);
7720
+ * explicit `required: false` lets ↵ submit an empty selection.
7721
+ *
7722
+ * Rendering mirrors {@link OptionList} (same indent + cursor glyphs)
7723
+ * with a checkbox column so toggled state survives cursor movement.
7724
+ */
7725
+ function MultiSelectQuestionInput({ question, initialChoiceIds, onSubmit }) {
7726
+ const focused = useModalAwareFocus();
7727
+ const COLOR = useColors();
7728
+ const required = question.required ?? true;
7729
+ const [cursor, setCursor] = useState(0);
7730
+ const [picked, setPicked] = useState(() => {
7731
+ const valid = new Set(question.choices.map((c) => c.id));
7732
+ return new Set((initialChoiceIds ?? []).filter((id) => valid.has(id)));
7733
+ });
7734
+ const moveCursor = useCallback((delta) => {
7735
+ setCursor((c) => {
7736
+ const len = question.choices.length;
7737
+ if (len === 0) return c;
7738
+ return ((c + delta) % len + len) % len;
7739
+ });
7740
+ }, [question.choices.length]);
7741
+ useKeyboard((key) => {
7742
+ if (!focused) return;
7743
+ if (key.ctrl || key.meta || key.shift || key.option) return;
7744
+ if (key.name === "up" || key.name === "k") {
7745
+ moveCursor(-1);
7746
+ return;
7747
+ }
7748
+ if (key.name === "down" || key.name === "j") {
7749
+ moveCursor(1);
7750
+ return;
7751
+ }
7752
+ if (key.name === "space") {
7753
+ const choice = question.choices[cursor];
7754
+ if (!choice) return;
7755
+ setPicked((prev) => {
7756
+ const next = new Set(prev);
7757
+ if (next.has(choice.id)) next.delete(choice.id);
7758
+ else next.add(choice.id);
7759
+ return next;
7760
+ });
7761
+ return;
7762
+ }
7763
+ if (key.name === "return") {
7764
+ if (required && picked.size === 0) return;
7765
+ onSubmit(question.choices.filter((c) => picked.has(c.id)).map((c) => c.id));
7766
+ }
7767
+ });
7768
+ return /* @__PURE__ */ jsxs("box", {
7769
+ style: {
7770
+ flexDirection: "column",
7771
+ flexShrink: 0
7772
+ },
7773
+ children: [question.choices.map((choice, i) => {
7774
+ const isCursor = i === cursor;
7775
+ const isPicked = picked.has(choice.id);
7776
+ return /* @__PURE__ */ jsxs("text", {
7777
+ wrapMode: "word",
7778
+ children: [
7779
+ /* @__PURE__ */ jsx$1("span", {
7780
+ fg: COLOR.mute,
7781
+ children: " "
7782
+ }),
7783
+ /* @__PURE__ */ jsx$1("span", {
7784
+ fg: isCursor ? COLOR.brand : COLOR.mute,
7785
+ children: isCursor ? "↳ " : "· "
7786
+ }),
7787
+ /* @__PURE__ */ jsx$1("span", {
7788
+ fg: isPicked ? COLOR.accent : COLOR.mute,
7789
+ children: isPicked ? "[x] " : "[ ] "
7790
+ }),
7791
+ /* @__PURE__ */ jsx$1("span", {
7792
+ fg: isCursor ? COLOR.brand : COLOR.dim,
7793
+ children: choice.label
7794
+ }),
7795
+ choice.description && /* @__PURE__ */ jsx$1("span", {
7796
+ fg: COLOR.mute,
7797
+ children: ` · ${choice.description}`
7798
+ })
7799
+ ]
7800
+ }, choice.id);
7801
+ }), /* @__PURE__ */ jsxs("text", {
7802
+ fg: COLOR.mute,
7803
+ children: [
7804
+ " ",
7805
+ /* @__PURE__ */ jsx$1("span", {
7806
+ fg: COLOR.warn,
7807
+ children: "space"
7808
+ }),
7809
+ " toggle · ",
7810
+ /* @__PURE__ */ jsx$1("span", {
7811
+ fg: COLOR.warn,
7812
+ children: "↵"
7813
+ }),
7814
+ ` submit${required ? " · " : ""}`,
7815
+ required && /* @__PURE__ */ jsx$1("span", {
7816
+ fg: COLOR.warn,
7817
+ children: "required"
7818
+ })
7819
+ ]
7820
+ })]
7821
+ });
7822
+ }
7536
7823
  /** Yes/no picker — returns a boolean. */
7537
7824
  function ConfirmQuestionInput({ question, initialValue, onSubmit }) {
7538
7825
  return /* @__PURE__ */ jsx$1(OptionList, {
@@ -8706,7 +8993,8 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
8706
8993
  * sessions list. Exposed so the AppShell can pass it through
8707
8994
  * `focusedSessionId` (or rather: the focused ROW id, which is either a
8708
8995
  * real session id or this sentinel) and gate global shortcuts that
8709
- * only make sense on real sessions — `ctrl+x` is the canonical example.
8996
+ * only make sense on real sessions — `openSessionDetails` (ctrl+shift+x)
8997
+ * is the canonical example.
8710
8998
  *
8711
8999
  * Stays a plain string (rather than a discriminated union) so the
8712
9000
  * parent's focus state remains a flat `string | null` — `null` means
@@ -8714,13 +9002,13 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
8714
9002
  * sentinel means "+ new is the active row".
8715
9003
  */
8716
9004
  const NEW_SESSION_ROW_ID = "__new__";
8717
- /** Guard for the `ctrl+x` handler: only a real session id should open the details modal. */
9005
+ /** Guard for the `openSessionDetails` handler: only a real session id should open the details modal. */
8718
9006
  function isSessionRowId(rowId) {
8719
9007
  return rowId !== null && rowId !== "__new__";
8720
9008
  }
8721
9009
  /** Page-up / page-down jump size — half a typical visible window. */
8722
9010
  const PAGE_JUMP = 6;
8723
- function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreate, onFocusChange, showAllProjects = false, currentProjectRoot }) {
9011
+ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreate, onFocusChange, showAllProjects = false, onToggleScope, currentProjectRoot }) {
8724
9012
  const focused = useModalAwareFocus();
8725
9013
  const COLOR = useColors();
8726
9014
  const inputRef = useRef(null);
@@ -8802,7 +9090,11 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
8802
9090
  moveCursor(cursorIndex + PAGE_JUMP);
8803
9091
  return;
8804
9092
  }
8805
- if (key.name === "return") commitCurrent();
9093
+ if (key.name === "return") {
9094
+ commitCurrent();
9095
+ return;
9096
+ }
9097
+ if (key.name === "tab" && !key.shift) onToggleScope?.();
8806
9098
  });
8807
9099
  const { width: termWidth } = useTerminalDimensions();
8808
9100
  const titleMeta = useMemo(() => {
@@ -9040,12 +9332,12 @@ function SessionRow({ row, focused, isCurrent, showProject = false, currentProje
9040
9332
  /**
9041
9333
  * Render the project label for the cross-project view. Three cases:
9042
9334
  *
9043
- * - The row belongs to the current project → render "this project"
9335
+ * - The row belongs to the current project → render "current project"
9044
9336
  * in `accent` so the user can quickly spot rows that match where
9045
9337
  * they are.
9046
9338
  * - The row is tagged but for a different project → render the
9047
- * basename of the project root in `dim` (full path would be too
9048
- * long for the row).
9339
+ * project root in `dim`, `~`-relative when it lives under the
9340
+ * user's home (verbatim otherwise — `compactPath` handles both).
9049
9341
  * - The row is untagged (legacy) → render "untagged" in `mute`.
9050
9342
  */
9051
9343
  function renderProjectLabel(rowProject, currentProject, COLOR) {
@@ -9055,15 +9347,14 @@ function renderProjectLabel(rowProject, currentProject, COLOR) {
9055
9347
  });
9056
9348
  if (currentProject && rowProject === currentProject) return /* @__PURE__ */ jsx$1("span", {
9057
9349
  fg: COLOR.accent,
9058
- children: "this project"
9350
+ children: "current project"
9059
9351
  });
9060
- const basename = rowProject.split("/").pop() ?? rowProject;
9061
9352
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
9062
9353
  fg: COLOR.mute,
9063
9354
  children: "project "
9064
9355
  }), /* @__PURE__ */ jsx$1("span", {
9065
9356
  fg: COLOR.dim,
9066
- children: basename
9357
+ children: compactPath(rowProject)
9067
9358
  })] });
9068
9359
  }
9069
9360
  /** Visible content lines: 1 minimum, 5 maximum (textarea scrolls past 5). */
@@ -9131,10 +9422,11 @@ const MIME_BY_EXT = {
9131
9422
  * default-value reference at the prop destructure.
9132
9423
  */
9133
9424
  const EMPTY_QUEUED_MESSAGES = [];
9134
- function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onCompletionTab, onPopupOpenChange, selectedTurnId, promptTriggerHints, attachImageBinding, editorBinding, liveSession = null }) {
9425
+ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onCompletionTab, onPopupOpenChange, selectedTurnId, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding, liveSession = null }) {
9135
9426
  const COLOR = useColors();
9136
9427
  const titleText = session?.title ?? "New session";
9137
- const showSessionShortcut = !!session && !busy && !pending && !pendingInteraction && settings.uiMode !== "minimal";
9428
+ const sessionShortcutKey = formatBindingForDisplay(sessionDetailsBinding);
9429
+ const showSessionShortcut = !!session && !!sessionShortcutKey && !busy && !pending && !pendingInteraction && settings.uiMode !== "minimal";
9138
9430
  const userMessageCount = useMemo(() => events.filter((e) => e.kind === "user-prompt").length, [events]);
9139
9431
  const { width: termWidth } = useTerminalDimensions();
9140
9432
  const hasStatusIcon = busy || compacting;
@@ -9162,7 +9454,7 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
9162
9454
  text: " · ",
9163
9455
  color: COLOR.mute
9164
9456
  }, {
9165
- text: "ctrl+x",
9457
+ text: sessionShortcutKey,
9166
9458
  color: COLOR.warn
9167
9459
  }, { text: " session" });
9168
9460
  const OVERLAY_RESERVED = 6;
@@ -11610,7 +11902,7 @@ function truncateToCols(s, maxCols) {
11610
11902
  if (maxCols === 1) return "…";
11611
11903
  return `${clean.slice(0, maxCols - 1)}…`;
11612
11904
  }
11613
- function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, keybindings, keybindingsPath, extensionKeybindings, extensionThemes: extensionThemesProp, extensionsCatalog: extensionsCatalogProp, extensionsInactive: extensionsInactiveProp, extensionConfigCategories: extensionConfigCategoriesProp, extensionConfigItems: extensionConfigItemsProp, logs, authentication, actions, version } = {}) {
11905
+ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, keybindings, keybindingsPath, extensionKeybindings, extensionThemes: extensionThemesProp, extensionsCatalog: extensionsCatalogProp, extensionsInactive: extensionsInactiveProp, extensionConfigCategories: extensionConfigCategoriesProp, extensionConfigItems: extensionConfigItemsProp, modelCatalog, logs, authentication, actions, version } = {}) {
11614
11906
  const COLOR = useColors();
11615
11907
  const SURFACE = useSurfaces();
11616
11908
  const { settings, toggle: toggleBoolean, setSetting } = useSettings();
@@ -11673,12 +11965,23 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
11673
11965
  if (next) writeConfigValue(extName, item.key, next.value);
11674
11966
  return;
11675
11967
  }
11968
+ const kind = item.type === "model" && modelCatalog && modelCatalog.length > 0 ? "model" : "input";
11676
11969
  setConfigEdit({
11677
11970
  extName,
11678
11971
  key: item.key,
11679
- initial: String(current)
11972
+ initial: String(current),
11973
+ kind
11680
11974
  });
11681
- }, [settings.extensionConfig, writeConfigValue]);
11975
+ }, [
11976
+ settings.extensionConfig,
11977
+ writeConfigValue,
11978
+ modelCatalog
11979
+ ]);
11980
+ const applyModelEdit = useCallback((ids) => {
11981
+ if (configEdit) writeConfigValue(configEdit.extName, configEdit.key, ids.join(", "));
11982
+ setConfigEdit(null);
11983
+ }, [configEdit, writeConfigValue]);
11984
+ const cancelConfigEdit = useCallback(() => setConfigEdit(null), []);
11682
11985
  const commitConfigEdit = useCallback(() => {
11683
11986
  if (!configEdit) return;
11684
11987
  const item = extensionConfigItems[configEdit.extName]?.[configEdit.key];
@@ -12108,7 +12411,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
12108
12411
  }, [editingInputKey]);
12109
12412
  useKeyboard((key) => {
12110
12413
  if (configEditActive) {
12111
- if (key.name === "escape") setConfigEdit(null);
12414
+ if (key.name === "escape" && configEdit?.kind !== "model") setConfigEdit(null);
12112
12415
  return;
12113
12416
  }
12114
12417
  if (extInstallActive || skillInstallActive) return;
@@ -12414,7 +12717,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
12414
12717
  contentCols,
12415
12718
  editing: configEdit,
12416
12719
  inputRef: configInputRef,
12417
- onCommitEdit: commitConfigEdit
12720
+ onCommitEdit: commitConfigEdit,
12721
+ modelCatalog,
12722
+ onApplyModelEdit: applyModelEdit,
12723
+ onCancelEdit: cancelConfigEdit
12418
12724
  }),
12419
12725
  activeTab === "mcps" && /* @__PURE__ */ jsx$1(McpsList, {
12420
12726
  rows: mcpVisibleRows,
@@ -12688,7 +12994,7 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
12688
12994
  });
12689
12995
  }
12690
12996
  const EXTENSION_CONFIG_INDENT = 0;
12691
- function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit }) {
12997
+ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit }) {
12692
12998
  const COLOR = useColors();
12693
12999
  const home = homedir();
12694
13000
  if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
@@ -12856,14 +13162,26 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
12856
13162
  focused: childFocused,
12857
13163
  bg: childBg
12858
13164
  }, item.key));
12859
- } else {
12860
- const isEditing = editing?.extName === group.ext.name && editing.key === item.key;
13165
+ } else if (item.type === "model" && editing?.extName === group.ext.name && editing.key === item.key && editing.kind === "model" && modelCatalog && modelCatalog.length > 0) childEls.push(/* @__PURE__ */ jsx$1(ConfigModelPickerRow, {
13166
+ id: anchorIdFor(index),
13167
+ indent: EXTENSION_CONFIG_INDENT,
13168
+ label: item.label,
13169
+ description: childDesc,
13170
+ catalog: modelCatalog,
13171
+ multi: item.multi === true,
13172
+ initial: parseStoredModelIds(String(value)),
13173
+ onApply: onApplyModelEdit,
13174
+ onCancel: onCancelEdit,
13175
+ bg: childBg
13176
+ }, item.key));
13177
+ else {
13178
+ const isEditing = editing?.extName === group.ext.name && editing.key === item.key && editing.kind === "input";
12861
13179
  childEls.push(/* @__PURE__ */ jsx$1(ConfigInputRow, {
12862
13180
  id: anchorIdFor(index),
12863
13181
  indent: EXTENSION_CONFIG_INDENT,
12864
13182
  label: item.label,
12865
13183
  description: childDesc,
12866
- value: String(value),
13184
+ value: item.type === "model" && String(value).trim() === "" ? "(unset)" : String(value),
12867
13185
  editing: isEditing,
12868
13186
  initial: isEditing ? editing.initial : "",
12869
13187
  inputRef,
@@ -13028,6 +13346,70 @@ function ConfigInputRow({ id, label, description, value, editing, initial, input
13028
13346
  ]
13029
13347
  });
13030
13348
  }
13349
+ /** Split a stored model-config value (comma-separated ids) into clean ids. */
13350
+ function parseStoredModelIds(value) {
13351
+ return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
13352
+ }
13353
+ /**
13354
+ * `type: 'model'` config row in EDIT mode — label line plus the inline
13355
+ * searchable picker ({@link ModelSelectList}). The picker owns its keys
13356
+ * (↑↓ filter ↵ esc); the parent only persists on apply / closes on cancel.
13357
+ */
13358
+ function ConfigModelPickerRow({ id, label, description, catalog, multi, initial, onApply, onCancel, bg, indent = 0 }) {
13359
+ const COLOR = useColors();
13360
+ return /* @__PURE__ */ jsxs("box", {
13361
+ id,
13362
+ style: {
13363
+ flexDirection: "column",
13364
+ flexShrink: 0,
13365
+ paddingLeft: 1 + indent,
13366
+ paddingRight: 1,
13367
+ backgroundColor: bg
13368
+ },
13369
+ children: [
13370
+ /* @__PURE__ */ jsxs("text", {
13371
+ wrapMode: "none",
13372
+ children: [
13373
+ /* @__PURE__ */ jsx$1("span", {
13374
+ fg: COLOR.brand,
13375
+ children: "▶ "
13376
+ }),
13377
+ /* @__PURE__ */ jsx$1("span", {
13378
+ fg: COLOR.mute,
13379
+ children: SPACER_CHECKBOX_WIDTH
13380
+ }),
13381
+ /* @__PURE__ */ jsx$1("span", {
13382
+ fg: COLOR.brand,
13383
+ children: label
13384
+ }),
13385
+ /* @__PURE__ */ jsx$1("span", {
13386
+ fg: COLOR.mute,
13387
+ children: ":"
13388
+ })
13389
+ ]
13390
+ }),
13391
+ /* @__PURE__ */ jsx$1("box", {
13392
+ style: {
13393
+ flexDirection: "column",
13394
+ flexShrink: 0,
13395
+ marginLeft: 4
13396
+ },
13397
+ children: /* @__PURE__ */ jsx$1(ModelSelectList, {
13398
+ catalog,
13399
+ multi,
13400
+ initial,
13401
+ onApply,
13402
+ onCancel
13403
+ })
13404
+ }),
13405
+ /* @__PURE__ */ jsx$1("text", {
13406
+ wrapMode: "none",
13407
+ fg: COLOR.mute,
13408
+ children: `${COL_TITLE}${description}`
13409
+ })
13410
+ ]
13411
+ });
13412
+ }
13031
13413
  function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query, contentCols }) {
13032
13414
  const COLOR = useColors();
13033
13415
  if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
@@ -15805,6 +16187,15 @@ function AppShell() {
15805
16187
  */
15806
16188
  const providerNameByKeyRef = useRef(/* @__PURE__ */ new Map());
15807
16189
  /**
16190
+ * Captured at `buildAgent` time for command-driven child spawns
16191
+ * (`ctx.spawnAgents`): the exact inputs the agent was built with, so a
16192
+ * hand-built ToolContext drives the spawn tool with the same tools /
16193
+ * providers / hook bus as a model-initiated spawn. Replaced wholesale on
16194
+ * every rebuild; consumers must check `seam.agent === agentRef.current`
16195
+ * before use so a stale seam can never spawn against a dead agent.
16196
+ */
16197
+ const commandSpawnSeamRef = useRef(null);
16198
+ /**
15808
16199
  * The `model` option for chat runs: the bare primary model id when no
15809
16200
  * fallbacks are configured (byte-identical to single-model behavior),
15810
16201
  * else the pinned `{ provider, model }` chain — validated against the
@@ -15901,12 +16292,12 @@ function AppShell() {
15901
16292
  });
15902
16293
  const composedPreset = composeExtensionPreset(liveExtensionRegistry, profile.preset);
15903
16294
  const modelCatalogTools = {};
16295
+ const catalog = [];
16296
+ for (const [providerKey, providerName] of nameByKey) for (const info of modelsFor(providerKey)) catalog.push({
16297
+ provider: providerName,
16298
+ model: info
16299
+ });
15904
16300
  if (subagentModelsRef.current) {
15905
- const catalog = [];
15906
- for (const [providerKey, providerName] of nameByKey) for (const info of modelsFor(providerKey)) catalog.push({
15907
- provider: providerName,
15908
- model: info
15909
- });
15910
16301
  const presetSpawn = composedPreset.tools?.spawn;
15911
16302
  if (catalog.length > 0 && presetSpawn && isSpawnTool(presetSpawn)) {
15912
16303
  modelCatalogTools.spawn = presetSpawn.withModels(catalog);
@@ -15923,28 +16314,32 @@ function AppShell() {
15923
16314
  ...composedPreset.skills ?? {}
15924
16315
  };
15925
16316
  const skills = !!session.metadata["zidane.extensionEdit"] || extensionDirForCwd(actualCwd) !== null ? withZidaneExtensionSkill(mergedSkills, { cwd: actualCwd }) : mergedSkills;
16317
+ const mergedMcpServers = dedupMcpServers([
16318
+ ...projectMcps,
16319
+ ...launch?.mcp ?? [],
16320
+ ...composedPreset.mcpServers ?? []
16321
+ ]);
16322
+ const mergedTools = {
16323
+ ...interactionTools,
16324
+ chatTurnsRead: chatTurnsReadTool,
16325
+ ...composedPreset.tools ?? {},
16326
+ ...modelCatalogTools
16327
+ };
16328
+ const mergedBehavior = {
16329
+ ...composedPreset.behavior ?? {},
16330
+ ...persistBehavior,
16331
+ tasksDir
16332
+ };
16333
+ const effectiveSystem = launch?.system !== void 0 ? launch.system : builtInSystem || composedPreset.system;
15926
16334
  const agent = createAgent({
15927
16335
  ...composedPreset,
15928
16336
  ...builtInSystem ? { system: builtInSystem } : {},
15929
16337
  ...launch?.system !== void 0 ? { system: launch.system } : {},
15930
16338
  skills,
15931
- mcpServers: dedupMcpServers([
15932
- ...projectMcps,
15933
- ...launch?.mcp ?? [],
15934
- ...composedPreset.mcpServers ?? []
15935
- ]),
15936
- tools: {
15937
- ...interactionTools,
15938
- chatTurnsRead: chatTurnsReadTool,
15939
- ...composedPreset.tools ?? {},
15940
- ...modelCatalogTools
15941
- },
16339
+ mcpServers: mergedMcpServers,
16340
+ tools: mergedTools,
15942
16341
  contextBreakdownSections: [...liveExtensionRegistry.contextBreakdownSections, ...liveExtensionRegistry.contextPanelSections],
15943
- behavior: {
15944
- ...composedPreset.behavior ?? {},
15945
- ...persistBehavior,
15946
- tasksDir
15947
- },
16342
+ behavior: mergedBehavior,
15948
16343
  execution,
15949
16344
  provider: extraProviders.length > 0 ? [primaryProvider, ...extraProviders] : primaryProvider,
15950
16345
  session,
@@ -15953,6 +16348,20 @@ function AppShell() {
15953
16348
  store: mcpCredentialStore
15954
16349
  }) })
15955
16350
  });
16351
+ const seamSpawnBase = mergedTools.spawn;
16352
+ commandSpawnSeamRef.current = seamSpawnBase && isSpawnTool(seamSpawnBase) ? {
16353
+ agent,
16354
+ provider: primaryProvider,
16355
+ providers: [primaryProvider, ...extraProviders],
16356
+ ...composedPreset.name ? { name: composedPreset.name } : {},
16357
+ tools: mergedTools,
16358
+ ...composedPreset.toolAliases ? { toolAliases: composedPreset.toolAliases } : {},
16359
+ ...effectiveSystem ? { system: effectiveSystem } : {},
16360
+ mcpServers: mergedMcpServers,
16361
+ skills,
16362
+ behavior: mergedBehavior,
16363
+ spawnTool: "spawn" in modelCatalogTools || catalog.length === 0 ? seamSpawnBase : seamSpawnBase.withModels(catalog)
16364
+ } : null;
15956
16365
  const tuiLogging = getTuiLogging();
15957
16366
  if (tuiLogging) createLoggingHooks({ logger: createLogger(tuiLogging.sink, { service: "tui" }) }).install(agent.hooks);
15958
16367
  const liveFragments = liveExtensionRegistry.systemFragments;
@@ -16447,14 +16856,18 @@ function AppShell() {
16447
16856
  unregisterInFlightTool,
16448
16857
  extensionRegistry
16449
16858
  ]);
16859
+ const [sessionsScopeAll, setSessionsScopeAll] = useState(null);
16860
+ const sessionsShowAll = sessionsScopeAll ?? settings.showAllProjects;
16861
+ const toggleSessionsScope = useCallback(() => {
16862
+ setSessionsScopeAll(!sessionsShowAll);
16863
+ }, [sessionsShowAll]);
16450
16864
  const refreshSessions = useCallback(async () => {
16451
- const filter = settings.showAllProjects ? void 0 : { projectRoot: projectDir };
16452
- const list = await listSessionMeta(store, filter);
16865
+ const list = await listSessionMeta(store, sessionsShowAll ? void 0 : { projectRoot: projectDir });
16453
16866
  setSessions(list);
16454
16867
  return list;
16455
16868
  }, [
16456
16869
  store,
16457
- settings.showAllProjects,
16870
+ sessionsShowAll,
16458
16871
  projectDir
16459
16872
  ]);
16460
16873
  useEffect(() => {
@@ -16699,6 +17112,25 @@ function AppShell() {
16699
17112
  useEffect(() => {
16700
17113
  refreshAvailableProviders();
16701
17114
  }, [refreshAvailableProviders]);
17115
+ const credentialedModelEntries = useCallback(() => {
17116
+ const entries = [];
17117
+ for (const auth of availableProviders) {
17118
+ const descriptor = providerRegistry[auth.key];
17119
+ if (!descriptor) continue;
17120
+ for (const info of modelsFor(auth.key)) entries.push({
17121
+ provider: {
17122
+ key: auth.key,
17123
+ name: descriptor.label
17124
+ },
17125
+ model: info
17126
+ });
17127
+ }
17128
+ return entries;
17129
+ }, [
17130
+ availableProviders,
17131
+ providerRegistry,
17132
+ modelsFor
17133
+ ]);
16702
17134
  const onPickProvider = useCallback(async (p) => {
16703
17135
  const next = makePicked(p);
16704
17136
  if (!next) return;
@@ -16721,9 +17153,45 @@ function AppShell() {
16721
17153
  const onCreateSession = useCallback(async () => {
16722
17154
  if (picked) await activateSession(null, picked.provider.key);
16723
17155
  }, [picked, activateSession]);
17156
+ const pendingSessionSwitchRef = useRef(null);
17157
+ const [sessionSwitchNonce, setSessionSwitchNonce] = useState(0);
16724
17158
  const onSwitchSession = useCallback(async (id) => {
16725
- if (picked) await activateSession(id, picked.provider.key);
16726
- }, [picked, activateSession]);
17159
+ if (!picked) return;
17160
+ const targetRoot = sessions.find((s) => s.id === id)?.projectRoot;
17161
+ if (targetRoot && targetRoot !== projectDir) {
17162
+ pendingSessionSwitchRef.current = {
17163
+ id,
17164
+ dir: targetRoot
17165
+ };
17166
+ setSessionSwitchNonce((n) => n + 1);
17167
+ try {
17168
+ setCwd(targetRoot);
17169
+ return;
17170
+ } catch (err) {
17171
+ pendingSessionSwitchRef.current = null;
17172
+ debugLog("switch session: chdir failed", err);
17173
+ }
17174
+ }
17175
+ await activateSession(id, picked.provider.key);
17176
+ }, [
17177
+ picked,
17178
+ sessions,
17179
+ projectDir,
17180
+ setCwd,
17181
+ activateSession
17182
+ ]);
17183
+ useEffect(() => {
17184
+ const pending = pendingSessionSwitchRef.current;
17185
+ if (!pending || cwd !== pending.dir || !picked || busy) return;
17186
+ pendingSessionSwitchRef.current = null;
17187
+ activateSession(pending.id, picked.provider.key).catch((err) => debugLog("switch session: activate failed", err));
17188
+ }, [
17189
+ cwd,
17190
+ picked,
17191
+ busy,
17192
+ sessionSwitchNonce,
17193
+ activateSession
17194
+ ]);
16727
17195
  const onOpenSessions = useCallback(async () => {
16728
17196
  await refreshSessions();
16729
17197
  setScreen("sessions");
@@ -17454,9 +17922,18 @@ function AppShell() {
17454
17922
  if (controller.signal.aborted) throw new Error("generateData: command aborted");
17455
17923
  const descriptor = providerRegistry[picked.provider.key];
17456
17924
  if (!descriptor) throw new Error(`generateData: provider "${picked.provider.key}" is not available`);
17925
+ let genDescriptor = descriptor;
17926
+ let genModel = picked.model;
17927
+ if (dataOpts.model && dataOpts.model !== picked.model) {
17928
+ const ownerKey = credentialedModelEntries().find((e) => e.model.id === dataOpts.model)?.provider.key;
17929
+ const owner = ownerKey ? providerRegistry[ownerKey] : void 0;
17930
+ if (!owner) throw new Error(`generateData: unknown model "${dataOpts.model}" — not offered by any credentialed provider`);
17931
+ genDescriptor = owner;
17932
+ genModel = dataOpts.model;
17933
+ }
17457
17934
  return generateStructuredData({
17458
- provider: descriptor.factory(),
17459
- model: picked.model,
17935
+ provider: genDescriptor.factory(),
17936
+ model: genModel,
17460
17937
  prompt: dataOpts.prompt,
17461
17938
  schema: dataOpts.schema,
17462
17939
  ...dataOpts.system ? { system: dataOpts.system } : {},
@@ -17524,6 +18001,103 @@ function AppShell() {
17524
18001
  if (response.kind !== "plan") throw new Error("presentPlan: unexpected interaction response kind");
17525
18002
  return response;
17526
18003
  }
18004
+ },
18005
+ spawnAgents: async (tasks, spawnOpts) => {
18006
+ const seam = commandSpawnSeamRef.current;
18007
+ if (!agent || !picked || !seam || seam.agent !== agent) throw new Error("spawnAgents: no active agent");
18008
+ if (controller.signal.aborted) throw new Error("spawnAgents: command aborted");
18009
+ if (tasks.length === 0) return [];
18010
+ await flushCommandTurn();
18011
+ const handle = await agent.ensureExecution();
18012
+ const results = tasks.map((task, i) => ({
18013
+ label: task.label ?? `task-${i + 1}`,
18014
+ ...task.model !== void 0 ? { model: task.model } : {},
18015
+ status: "aborted",
18016
+ text: "",
18017
+ message: "spawnAgents: batch aborted before this task ran"
18018
+ }));
18019
+ let cursor = 0;
18020
+ const worker = async () => {
18021
+ while (cursor < tasks.length && !controller.signal.aborted) {
18022
+ const index = cursor++;
18023
+ const spawnTask = tasks[index];
18024
+ try {
18025
+ const outcome = await seam.spawnTool.runTask({
18026
+ task: spawnTask.prompt,
18027
+ ...spawnTask.system !== void 0 ? { system: spawnTask.system } : {},
18028
+ ...spawnTask.model !== void 0 ? { model: spawnTask.model } : {}
18029
+ }, {
18030
+ provider: seam.provider,
18031
+ providers: seam.providers,
18032
+ ...seam.name !== void 0 ? { name: seam.name } : {},
18033
+ model: picked.model,
18034
+ signal: controller.signal,
18035
+ execution: agent.execution,
18036
+ handle,
18037
+ hooks: agent.hooks,
18038
+ ...seam.system !== void 0 ? { system: seam.system } : {},
18039
+ tools: seam.tools,
18040
+ ...seam.toolAliases !== void 0 ? { toolAliases: seam.toolAliases } : {},
18041
+ mcpServers: seam.mcpServers,
18042
+ skills: seam.skills,
18043
+ behavior: seam.behavior,
18044
+ turnId: `command_${invocation.name}`,
18045
+ callId: `command_spawn_${crypto.randomUUID()}`,
18046
+ session
18047
+ });
18048
+ results[index] = {
18049
+ label: spawnTask.label ?? outcome.id ?? `task-${index + 1}`,
18050
+ ...spawnTask.model !== void 0 ? { model: spawnTask.model } : {},
18051
+ status: outcome.status,
18052
+ text: outcome.text,
18053
+ message: outcome.message
18054
+ };
18055
+ } catch (err) {
18056
+ results[index] = {
18057
+ label: spawnTask.label ?? `task-${index + 1}`,
18058
+ ...spawnTask.model !== void 0 ? { model: spawnTask.model } : {},
18059
+ status: "error",
18060
+ text: "",
18061
+ message: `spawnAgents: ${errorMessage(err)}`
18062
+ };
18063
+ }
18064
+ }
18065
+ };
18066
+ const cap = seam.spawnTool.maxConcurrent;
18067
+ const width = Math.max(1, Math.min(spawnOpts?.maxConcurrent ?? cap, cap, tasks.length));
18068
+ await Promise.all(Array.from({ length: width }, () => worker()));
18069
+ await session.save().catch((err) => debugLog("command spawnAgents: save failed", err));
18070
+ separateNextEmit = true;
18071
+ return results;
18072
+ },
18073
+ appendContext: async (text, noteOpts) => {
18074
+ if (controller.signal.aborted) throw new Error("appendContext: command aborted");
18075
+ const trimmed = text.trim();
18076
+ if (!trimmed) return;
18077
+ await flushCommandTurn();
18078
+ stream.appendImmediate({
18079
+ kind: "separator",
18080
+ text: ""
18081
+ });
18082
+ stream.appendImmediate({
18083
+ kind: "user-prompt",
18084
+ text: noteOpts?.display ?? trimmed
18085
+ });
18086
+ if (noteOpts?.display) stream.appendImmediate({
18087
+ kind: "command-detail",
18088
+ label: "context",
18089
+ text: trimmed
18090
+ });
18091
+ await session.appendTurns([{
18092
+ id: await session.generateTurnId(),
18093
+ role: "user",
18094
+ content: [{
18095
+ type: "text",
18096
+ text: trimmed
18097
+ }],
18098
+ createdAt: Date.now()
18099
+ }]);
18100
+ separateNextEmit = true;
17527
18101
  }
17528
18102
  };
17529
18103
  try {
@@ -17561,7 +18135,8 @@ function AppShell() {
17561
18135
  picked,
17562
18136
  autoCompactBehaviorFor,
17563
18137
  providerRegistry,
17564
- interactions
18138
+ interactions,
18139
+ credentialedModelEntries
17565
18140
  ]);
17566
18141
  /**
17567
18142
  * `!` bash mode — run a user-typed shell command in the cwd, streaming
@@ -17800,6 +18375,7 @@ function AppShell() {
17800
18375
  if (!bridge) return;
17801
18376
  return bridge.attach({
17802
18377
  model: () => picked?.model ?? null,
18378
+ models: credentialedModelEntries,
17803
18379
  thinking: () => picked?.effort ?? null,
17804
18380
  setThinking: (level) => {
17805
18381
  if (!picked) return false;
@@ -17864,7 +18440,8 @@ function AppShell() {
17864
18440
  onPickEffort,
17865
18441
  refreshSessions,
17866
18442
  activateSession,
17867
- stream
18443
+ stream,
18444
+ credentialedModelEntries
17868
18445
  ]);
17869
18446
  /**
17870
18447
  * Idle-path auto-wake: deliver queued `<task-notification>` blocks by
@@ -18567,6 +19144,10 @@ function AppShell() {
18567
19144
  extensionThemes: extensionThemeChoices,
18568
19145
  extensionConfigCategories: extensionRegistry.configCategories,
18569
19146
  extensionConfigItems: extensionRegistry.configItems,
19147
+ modelCatalog: buildModelCatalog({
19148
+ providers: availableProviders,
19149
+ modelsFor
19150
+ }),
18570
19151
  ...tuiLogging ? { logs: {
18571
19152
  buffer: tuiLogging.buffer,
18572
19153
  filePath: tuiLogging.filePath
@@ -18724,6 +19305,15 @@ function AppShell() {
18724
19305
  }));
18725
19306
  return;
18726
19307
  }
19308
+ if (key.ctrl && !key.shift && !key.meta && (key.name === "c" || key.name === "x") && !showQuitConfirmRef.current) {
19309
+ const editor = renderer.currentFocusedEditor;
19310
+ if (editor?.hasSelection()) {
19311
+ const text = editor.getSelectedText();
19312
+ if (text) writeToClipboard(text);
19313
+ if (key.name === "x") editor.deleteSelection();
19314
+ return;
19315
+ }
19316
+ }
18727
19317
  if (showQuitConfirmRef.current) {
18728
19318
  if (key.name === "escape") {
18729
19319
  setShowQuitConfirm(false);
@@ -18855,7 +19445,8 @@ function AppShell() {
18855
19445
  inFlightToolCount: inFlightTools.reduce((n, entry) => entry.childId === void 0 ? n + 1 : n, 0) + backgroundTasks.reduce((n, entry) => entry.childId === void 0 ? n + 1 : n, 0),
18856
19446
  activeSkillCount: activeSkillNames.size,
18857
19447
  skillsChipColor: COLOR.brand,
18858
- updateHint
19448
+ updateHint,
19449
+ sessionsScopeAll: sessionsShowAll
18859
19450
  });
18860
19451
  if (screen !== "chat" || busy || pendingApproval || pendingInteraction || extensionRegistry.tuiContributions.footerHints.length === 0) return base;
18861
19452
  const extras = [];
@@ -18884,6 +19475,7 @@ function AppShell() {
18884
19475
  backgroundTasks,
18885
19476
  activeSkillNames,
18886
19477
  updateHint,
19478
+ sessionsShowAll,
18887
19479
  extensionRegistry,
18888
19480
  enabledExtensionsSet
18889
19481
  ]);
@@ -18964,7 +19556,8 @@ function AppShell() {
18964
19556
  onPick: onSwitchSession,
18965
19557
  onCreate: onCreateSession,
18966
19558
  onFocusChange: setFocusedSessionId,
18967
- showAllProjects: settings.showAllProjects,
19559
+ showAllProjects: sessionsShowAll,
19560
+ onToggleScope: toggleSessionsScope,
18968
19561
  currentProjectRoot: projectDir
18969
19562
  }),
18970
19563
  screen === "chat" && /* @__PURE__ */ jsx$1(ChatScreen, {
@@ -18994,7 +19587,8 @@ function AppShell() {
18994
19587
  selectedTurnId,
18995
19588
  promptTriggerHints,
18996
19589
  attachImageBinding: keybindings.attachClipboardImage,
18997
- editorBinding: settings.externalEditor ? keybindings.openExternalEditor : void 0
19590
+ editorBinding: settings.externalEditor ? keybindings.openExternalEditor : void 0,
19591
+ sessionDetailsBinding: keybindings.openSessionDetails
18998
19592
  })
18999
19593
  ]
19000
19594
  }),