zidane 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/{acp-Irby04Us.js → acp-cfR8T19u.js} +8 -8
- package/dist/{acp-Irby04Us.js.map → acp-cfR8T19u.js.map} +1 -1
- package/dist/acp-cli.js +7 -7
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-DkSmGkDM.js → agent-DZRA7wip.js} +377 -61
- package/dist/agent-DZRA7wip.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-CTHsdFG9.js → anthropic-DNJbRMUK.js} +7 -7
- package/dist/anthropic-DNJbRMUK.js.map +1 -0
- package/dist/atomic-write-B7zr1ddF.js.map +1 -1
- package/dist/{auth-BDSu_0t3.js → auth-B3cB5K6d.js} +7 -7
- package/dist/auth-B3cB5K6d.js.map +1 -0
- package/dist/cache-telemetry-BKAwsNxm.js.map +1 -1
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +3 -3
- package/dist/chat.d.ts +44 -21
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +7 -7
- package/dist/chat.js.map +1 -1
- package/dist/completion-core-CXYSVhZo.js.map +1 -1
- package/dist/context-breakdown-Dzo25N45.js.map +1 -1
- package/dist/contexts/daytona.d.ts +3 -1
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +2 -2
- package/dist/contexts/daytona.js.map +1 -1
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +2 -2
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +3 -1
- package/dist/contexts/e2b.d.ts.map +1 -1
- package/dist/contexts/e2b.js +1 -1
- package/dist/contexts/e2b.js.map +1 -1
- package/dist/{contexts-Vone3qQQ.js → contexts-BIbcfBFH.js} +2 -2
- package/dist/contexts-BIbcfBFH.js.map +1 -0
- package/dist/contexts.js +1 -1
- package/dist/edit-utils-EGosADZq.js.map +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +4 -3
- package/dist/eval.js.map +1 -1
- package/dist/exec-file-size-PXkr-MGA.js.map +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/{fetch-url-CWE8X5OD.js → fetch-url-_5WKWpbQ.js} +2 -2
- package/dist/{fetch-url-CWE8X5OD.js.map → fetch-url-_5WKWpbQ.js.map} +1 -1
- package/dist/{format-BNOXpl-1.js → format-BF745pLL.js} +9 -2
- package/dist/format-BF745pLL.js.map +1 -0
- package/dist/generate-data-WL_yGVL6.js.map +1 -1
- package/dist/hash-DJRzmcPI.js.map +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +4 -4
- package/dist/headless.js.map +1 -1
- package/dist/{index-B8UtiZy7.d.ts → index-BGpM2YaU.d.ts} +378 -5
- package/dist/index-BGpM2YaU.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/logger-DBX9uYOw.js.map +1 -1
- package/dist/{login-DOF8pMVM.js → login-DZLG__2f.js} +4 -3
- package/dist/{login-DOF8pMVM.js.map → login-DZLG__2f.js.map} +1 -1
- package/dist/mcp-CcvuVXB9.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/media-sniff-Bn76JxAu.js.map +1 -1
- package/dist/{messages-CwujiS74.js → messages-C5BDOjUG.js} +2 -2
- package/dist/{messages-CwujiS74.js.map → messages-C5BDOjUG.js.map} +1 -1
- package/dist/oauth-8LqbeI_Q.js.map +1 -1
- package/dist/{openai-compat-BvgYGSR1.js → openai-compat-B0kqvZqN.js} +2 -2
- package/dist/openai-compat-B0kqvZqN.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/output/terminal.js +1 -1
- package/dist/output/terminal.js.map +1 -1
- package/dist/output-cap-kBUHBlnq.js.map +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +2 -2
- package/dist/providers/baseten.js.map +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +8 -8
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-z6LDMAr3.js → providers-BMlrTr6a.js} +3 -3
- package/dist/{providers-z6LDMAr3.js.map → providers-BMlrTr6a.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/read-state-Dq_TXUX1.js.map +1 -1
- package/dist/{resolve-Cr8JSL_O.js → resolve-CC8Lmwcj.js} +3 -3
- package/dist/{resolve-Cr8JSL_O.js.map → resolve-CC8Lmwcj.js.map} +1 -1
- package/dist/restate.d.ts +1 -1
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js.map +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +75 -20
- package/dist/session/sqlite.js.map +1 -1
- package/dist/{session-CsdDIPY8.js → session-CsKzK8-P.js} +3 -3
- package/dist/session-CsKzK8-P.js.map +1 -0
- package/dist/session.d.ts +2 -2
- package/dist/session.js +2 -2
- package/dist/{skills-Bsb0rvWI.js → skills-BjeXSRlQ.js} +2 -2
- package/dist/{skills-Bsb0rvWI.js.map → skills-BjeXSRlQ.js.map} +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -2
- package/dist/stdio-loader-Ce68wUmM.js.map +1 -1
- package/dist/{tool-formatters-DY0TDb1r.d.ts → tool-formatters-CMwjDvkp.d.ts} +2 -2
- package/dist/tool-formatters-CMwjDvkp.d.ts.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools/web-search.js +1 -1
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-DSRPWjiH.js → transcript-anchors-BTNe1OJi.js} +346 -89
- package/dist/transcript-anchors-BTNe1OJi.js.map +1 -0
- package/dist/{transcript-anchors-Cb-bDbNH.d.ts → transcript-anchors-D5wvdKQQ.d.ts} +37 -3
- package/dist/transcript-anchors-D5wvdKQQ.d.ts.map +1 -0
- package/dist/tui.d.ts +21 -6
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +497 -83
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-Dq9Kx7m2.js → turn-operations-CEt_l3fy.js} +23 -8
- package/dist/turn-operations-CEt_l3fy.js.map +1 -0
- package/dist/{turn-operations-CgMROLsr.d.ts → turn-operations-JO5MYM9_.d.ts} +27 -9
- package/dist/turn-operations-JO5MYM9_.d.ts.map +1 -0
- package/dist/types-BIarq1qE.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/{xai-B_e6TOA8.js → xai-C_aq671K.js} +3 -3
- package/dist/{xai-B_e6TOA8.js.map → xai-C_aq671K.js.map} +1 -1
- package/docs/ARCHITECTURE.md +4 -0
- package/docs/CHAT.md +12 -10
- package/docs/SKILL.md +1 -1
- package/docs/TUI.md +3 -3
- package/package.json +2 -2
- package/dist/agent-DkSmGkDM.js.map +0 -1
- package/dist/anthropic-CTHsdFG9.js.map +0 -1
- package/dist/auth-BDSu_0t3.js.map +0 -1
- package/dist/contexts-Vone3qQQ.js.map +0 -1
- package/dist/format-BNOXpl-1.js.map +0 -1
- package/dist/index-B8UtiZy7.d.ts.map +0 -1
- package/dist/openai-compat-BvgYGSR1.js.map +0 -1
- package/dist/session-CsdDIPY8.js.map +0 -1
- package/dist/tool-formatters-DY0TDb1r.d.ts.map +0 -1
- package/dist/transcript-anchors-Cb-bDbNH.d.ts.map +0 -1
- package/dist/transcript-anchors-DSRPWjiH.js.map +0 -1
- package/dist/turn-operations-CgMROLsr.d.ts.map +0 -1
- package/dist/turn-operations-Dq9Kx7m2.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { $ as useMcpAuthState, $n as
|
|
2
|
-
import { $ as
|
|
3
|
-
import { a as
|
|
4
|
-
import { n as createProcessContext } from "./contexts-
|
|
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-BTNe1OJi.js";
|
|
2
|
+
import { $ as composeExtensionPreset, $t as inlineLoadedExtension, B as ExtensionInstallError, Bt as formatBindingForDisplay, H as installExtensionFromSpec, Ht as keybindingsPath, J as createExtensionHud, Jt as resolveExtensionKeybindings, K as removeInstalledExtension, P as summaryToTurn, Rt as KEYBINDING_KEY_COL_WIDTH, Tt as createModelSearchTool, Ut as matchesBinding, Vt as groupBindings, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, _ as buildPostCompactAttachments, an as coerceExtensionConfigInput, c as applyCompactSummaryCutoff, et as disposeExtensionRegistry, f as cleanupPersistedSession, fn as OUTPUT_RESERVE_TOKENS, g as resolveTasksDir, h as resolvePersistDir, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, ln as resolveExtensionConfigValue, lt as basic_default, m as resolveMcpWarningsDir, nn as scanExtensionDirectories, pn as effectiveContextWindow, q as updateInstalledExtension, rt as partitionExtensionCatalog, t as createAgent, tn as parseExtensionScanPathEnv, tt as filterExtensionRegistry, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as ensureKeybindingsFile } from "./agent-DZRA7wip.js";
|
|
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
|
+
import { n as createProcessContext } from "./contexts-BIbcfBFH.js";
|
|
5
5
|
import { l as errorMessage } from "./errors-DJUxZg9b.js";
|
|
6
|
-
import { M as findGitRoot, j as projectUserPaths } from "./resolve-
|
|
6
|
+
import { M as findGitRoot, j as projectUserPaths } from "./resolve-CC8Lmwcj.js";
|
|
7
7
|
import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimestamp, n as createFileSink, o as fanoutSink, r as createLogger, s as formatLogLine, t as consoleSink } from "./logger-DBX9uYOw.js";
|
|
8
8
|
import { i as sniffImageMediaType } from "./media-sniff-Bn76JxAu.js";
|
|
9
9
|
import { r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
|
|
10
10
|
import { replaceDynamicSection } from "./prompt.js";
|
|
11
|
-
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-
|
|
11
|
+
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-CsKzK8-P.js";
|
|
12
12
|
import { o as withTimeout } from "./timeout-BDetbuN6.js";
|
|
13
13
|
import { n as connectMcpServers, u as McpOAuthProvider } from "./mcp-CcvuVXB9.js";
|
|
14
14
|
import { c as commandBlockToEvent, d as executeCommand, f as parseCommandInvocation, i as shouldAutoGenerateTitle, l as commandBlocksToContent, r as generateSessionTitle, t as generateStructuredData } from "./generate-data-WL_yGVL6.js";
|
|
15
15
|
import { s as resolveCompletionSubmit } from "./completion-core-CXYSVhZo.js";
|
|
16
|
-
import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-
|
|
17
|
-
import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-
|
|
18
|
-
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning } from "./auth-
|
|
16
|
+
import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-BjeXSRlQ.js";
|
|
17
|
+
import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-DZLG__2f.js";
|
|
18
|
+
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning } from "./auth-B3cB5K6d.js";
|
|
19
19
|
import { createDockerContext } from "./contexts/docker.js";
|
|
20
20
|
import { createTuiStore } from "./session/sqlite.js";
|
|
21
|
-
import { $ as rewriteMultiEditHeader, A as buildLinearRamp, D as createFilesCompletionProvider, F as selectableTurnIds, H as extractEditPayload, I as turnSelectionOwnership, J as buildEditOutcomesAnnotation, K as summarizeEditPayload, M as isEditErrorResult, N as isTurnHighlighted, P as isVisible, Q as resolveApprovalForPayload, R as buildContextualDiff, S as splitMarkdownCodeBlocks, T as uniqueSkillNamesFromReferences, U as filetypeFromPath, W as previewEditPayload, X as mergeApprovalAndBodyOutcomes, Z as parseEditOutcomesFromResult, _ as filterModelCatalog, a as TOOL_DISPLAY, b as chunkMarkdownForRender, d as finalizeStreamingMarkdown, et as stripEditOutcomesAnnotation, f as finalizeStreamingMarkdownForOwner, g as buildModelCatalog, h as splitPromptSegments, i as turnAsText, it as createChatsCompletionProvider, j as EDIT_TOOL_NAMES, k as blendHsl, m as turnContextSize, n as deleteTurnSafely, o as displayNameFor, r as truncateTurnsAt, s as formatToolCall, tt as summarizeOutcomes, u as capTranscriptEvents, v as indexOfEntry, w as createSkillsCompletionProvider, x as normalizeMarkdownCodeFences, z as buildUnifiedDiff } from "./turn-operations-
|
|
21
|
+
import { $ as rewriteMultiEditHeader, A as buildLinearRamp, D as createFilesCompletionProvider, F as selectableTurnIds, H as extractEditPayload, I as turnSelectionOwnership, J as buildEditOutcomesAnnotation, K as summarizeEditPayload, M as isEditErrorResult, N as isTurnHighlighted, P as isVisible, Q as resolveApprovalForPayload, R as buildContextualDiff, S as splitMarkdownCodeBlocks, T as uniqueSkillNamesFromReferences, U as filetypeFromPath, W as previewEditPayload, X as mergeApprovalAndBodyOutcomes, Z as parseEditOutcomesFromResult, _ as filterModelCatalog, a as TOOL_DISPLAY, b as chunkMarkdownForRender, d as finalizeStreamingMarkdown, et as stripEditOutcomesAnnotation, f as finalizeStreamingMarkdownForOwner, g as buildModelCatalog, h as splitPromptSegments, i as turnAsText, it as createChatsCompletionProvider, j as EDIT_TOOL_NAMES, k as blendHsl, m as turnContextSize, n as deleteTurnSafely, o as displayNameFor, r as truncateTurnsAt, s as formatToolCall, tt as summarizeOutcomes, u as capTranscriptEvents, v as indexOfEntry, w as createSkillsCompletionProvider, x as normalizeMarkdownCodeFences, z as buildUnifiedDiff } from "./turn-operations-CEt_l3fy.js";
|
|
22
22
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
23
23
|
import { Buffer } from "node:buffer";
|
|
24
24
|
import * as fs from "node:fs";
|
|
@@ -2183,6 +2183,13 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
2183
2183
|
fg: COLOR.mute,
|
|
2184
2184
|
children: " · "
|
|
2185
2185
|
})] }),
|
|
2186
|
+
event.model !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
2187
|
+
fg: COLOR.model,
|
|
2188
|
+
children: event.model
|
|
2189
|
+
}), /* @__PURE__ */ jsx$1("span", {
|
|
2190
|
+
fg: COLOR.mute,
|
|
2191
|
+
children: " · "
|
|
2192
|
+
})] }),
|
|
2186
2193
|
/* @__PURE__ */ jsx$1("span", {
|
|
2187
2194
|
fg: COLOR.dim,
|
|
2188
2195
|
children: safeText
|
|
@@ -3378,7 +3385,7 @@ function pctLabel(tokens, denom) {
|
|
|
3378
3385
|
const raw = tokens / denom * 100;
|
|
3379
3386
|
return raw < 1 ? "<1%" : `${Math.round(raw)}%`;
|
|
3380
3387
|
}
|
|
3381
|
-
function ContextPanelModal({ breakdown }) {
|
|
3388
|
+
function ContextPanelModal({ breakdown, modelUsage }) {
|
|
3382
3389
|
const COLOR = useColors();
|
|
3383
3390
|
const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
|
|
3384
3391
|
const expandableIds = useMemo(() => breakdown.categories.filter((c) => c.items && c.items.length > 0).map((c) => c.id), [breakdown]);
|
|
@@ -3492,6 +3499,10 @@ function ContextPanelModal({ breakdown }) {
|
|
|
3492
3499
|
usage: breakdown.usage,
|
|
3493
3500
|
COLOR
|
|
3494
3501
|
}),
|
|
3502
|
+
modelUsage && modelUsage.length > 0 && /* @__PURE__ */ jsx$1(ModelUsageRows, {
|
|
3503
|
+
rows: modelUsage,
|
|
3504
|
+
COLOR
|
|
3505
|
+
}),
|
|
3495
3506
|
breakdown.extensionSections?.map((section) => /* @__PURE__ */ jsx$1(ExtensionSectionRows, {
|
|
3496
3507
|
section,
|
|
3497
3508
|
COLOR
|
|
@@ -3683,6 +3694,82 @@ function UsageRows({ usage, COLOR }) {
|
|
|
3683
3694
|
}, r.label))
|
|
3684
3695
|
] });
|
|
3685
3696
|
}
|
|
3697
|
+
/**
|
|
3698
|
+
* Session-wide token + spend totals per model, one row each — the surface
|
|
3699
|
+
* that makes cost-tiered subagent delegation legible (cheap workers show
|
|
3700
|
+
* as their own rows at their own rates). Costs come from per-turn usage:
|
|
3701
|
+
* provider-reported when available, locally estimated otherwise; `≥`
|
|
3702
|
+
* marks models where some turns had no rate (the figure is a floor).
|
|
3703
|
+
*/
|
|
3704
|
+
function ModelUsageRows({ rows, COLOR }) {
|
|
3705
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3706
|
+
/* @__PURE__ */ jsx$1("box", { style: {
|
|
3707
|
+
height: 1,
|
|
3708
|
+
flexShrink: 0
|
|
3709
|
+
} }),
|
|
3710
|
+
/* @__PURE__ */ jsx$1("box", {
|
|
3711
|
+
style: {
|
|
3712
|
+
height: 1,
|
|
3713
|
+
flexShrink: 0
|
|
3714
|
+
},
|
|
3715
|
+
children: /* @__PURE__ */ jsx$1("text", {
|
|
3716
|
+
wrapMode: "none",
|
|
3717
|
+
fg: COLOR.mute,
|
|
3718
|
+
children: "usage by model"
|
|
3719
|
+
})
|
|
3720
|
+
}),
|
|
3721
|
+
rows.map((r) => {
|
|
3722
|
+
const sent = r.input + r.cacheRead + r.cacheCreation;
|
|
3723
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
3724
|
+
style: {
|
|
3725
|
+
flexDirection: "row",
|
|
3726
|
+
height: 1,
|
|
3727
|
+
flexShrink: 0
|
|
3728
|
+
},
|
|
3729
|
+
children: [
|
|
3730
|
+
/* @__PURE__ */ jsx$1("text", {
|
|
3731
|
+
wrapMode: "none",
|
|
3732
|
+
fg: COLOR.model,
|
|
3733
|
+
children: r.modelId
|
|
3734
|
+
}),
|
|
3735
|
+
/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
|
|
3736
|
+
/* @__PURE__ */ jsxs("text", {
|
|
3737
|
+
wrapMode: "none",
|
|
3738
|
+
children: [
|
|
3739
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3740
|
+
fg: COLOR.mute,
|
|
3741
|
+
children: `${r.turns} turn${r.turns === 1 ? "" : "s"} · `
|
|
3742
|
+
}),
|
|
3743
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3744
|
+
fg: COLOR.dim,
|
|
3745
|
+
children: fmtTokens(sent)
|
|
3746
|
+
}),
|
|
3747
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3748
|
+
fg: COLOR.mute,
|
|
3749
|
+
children: " in · "
|
|
3750
|
+
}),
|
|
3751
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3752
|
+
fg: COLOR.dim,
|
|
3753
|
+
children: fmtTokens(r.output)
|
|
3754
|
+
}),
|
|
3755
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3756
|
+
fg: COLOR.mute,
|
|
3757
|
+
children: " out"
|
|
3758
|
+
}),
|
|
3759
|
+
r.cost > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
3760
|
+
fg: COLOR.mute,
|
|
3761
|
+
children: ` · ${r.costComplete ? "" : "≥"}`
|
|
3762
|
+
}), /* @__PURE__ */ jsx$1("span", {
|
|
3763
|
+
fg: COLOR.money ?? COLOR.warn,
|
|
3764
|
+
children: fmtCost(r.cost)
|
|
3765
|
+
})] })
|
|
3766
|
+
]
|
|
3767
|
+
})
|
|
3768
|
+
]
|
|
3769
|
+
}, r.modelId);
|
|
3770
|
+
})
|
|
3771
|
+
] });
|
|
3772
|
+
}
|
|
3686
3773
|
function ExtensionSectionRows({ section, COLOR }) {
|
|
3687
3774
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3688
3775
|
/* @__PURE__ */ jsx$1("box", { style: {
|
|
@@ -3990,7 +4077,7 @@ const HOME = homedir();
|
|
|
3990
4077
|
const MAX_ENTRIES = 5e4;
|
|
3991
4078
|
const MAX_DEPTH = 5;
|
|
3992
4079
|
const WALK_BATCH_SIZE = 64;
|
|
3993
|
-
const SKIP_DIRS = new Set([
|
|
4080
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3994
4081
|
"node_modules",
|
|
3995
4082
|
".git",
|
|
3996
4083
|
".hg",
|
|
@@ -4453,8 +4540,12 @@ function DiscoveryShell({ children }) {
|
|
|
4453
4540
|
mcpCredentialStore
|
|
4454
4541
|
]);
|
|
4455
4542
|
useEffect(() => {
|
|
4543
|
+
let lastSignature = cacheFileSignature(dataDir);
|
|
4456
4544
|
const id = setInterval(() => {
|
|
4457
4545
|
try {
|
|
4546
|
+
const signature = cacheFileSignature(dataDir);
|
|
4547
|
+
if (signature === lastSignature) return;
|
|
4548
|
+
lastSignature = signature;
|
|
4458
4549
|
setMcpToolsByServer(readToolsByServer(dataDir));
|
|
4459
4550
|
} catch (err) {
|
|
4460
4551
|
debugLog$1("mcp-tools-cache refresh failed", err);
|
|
@@ -4496,6 +4587,15 @@ function DiscoveryShell({ children }) {
|
|
|
4496
4587
|
* only need the schemas. Best-effort — a corrupt cache produces an
|
|
4497
4588
|
* empty map and the next bootstrap re-populates.
|
|
4498
4589
|
*/
|
|
4590
|
+
/**
|
|
4591
|
+
* Cheap change signature for the MCP tools cache file — `null` when the
|
|
4592
|
+
* file doesn't exist yet. mtime+size catches every write the atomic
|
|
4593
|
+
* save path produces (fresh tmp file + rename ⇒ new inode/mtime).
|
|
4594
|
+
*/
|
|
4595
|
+
function cacheFileSignature(dataDir) {
|
|
4596
|
+
const stat = statSync(mcpToolsCachePath(dataDir), { throwIfNoEntry: false });
|
|
4597
|
+
return stat ? `${stat.mtimeMs}:${stat.size}` : null;
|
|
4598
|
+
}
|
|
4499
4599
|
function readToolsByServer(dataDir) {
|
|
4500
4600
|
const cache = loadMcpToolsCache({ dataDir });
|
|
4501
4601
|
const out = {};
|
|
@@ -4954,7 +5054,17 @@ function OptionRow({ option, isEnabled, isFocused, highlightBg }) {
|
|
|
4954
5054
|
* the chosen provider and model id on commit. Pick a model from a
|
|
4955
5055
|
* different provider than the current one and the caller is expected
|
|
4956
5056
|
* to swap the active `ProviderAuth` + rebuild the agent — see
|
|
4957
|
-
* `app.tsx`'s `
|
|
5057
|
+
* `app.tsx`'s `onApplyModelChain` for the canonical wiring.
|
|
5058
|
+
*
|
|
5059
|
+
* Two commit modes:
|
|
5060
|
+
* - **Single pick** (`onPick`, default): ↵ commits the highlighted row
|
|
5061
|
+
* and the caller closes the modal — the original behavior, still
|
|
5062
|
+
* used by the compaction-model picker and external hosts.
|
|
5063
|
+
* - **Ordered multi-select** (`onApplyChain`): ↵ toggles the highlighted
|
|
5064
|
+
* row in a numbered fallback chain (the number is the row's 1-based
|
|
5065
|
+
* position — the order rows were picked is the order the agent falls
|
|
5066
|
+
* back in), esc applies the chain and closes. Esc with an unchanged
|
|
5067
|
+
* (or emptied) chain is a plain cancel.
|
|
4958
5068
|
*
|
|
4959
5069
|
* Geometry:
|
|
4960
5070
|
* - Top: single-row search input. The input owns focus so the user
|
|
@@ -4973,7 +5083,7 @@ function OptionRow({ option, isEnabled, isFocused, highlightBg }) {
|
|
|
4973
5083
|
*/
|
|
4974
5084
|
/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */
|
|
4975
5085
|
const VISIBLE_ROWS = 10;
|
|
4976
|
-
function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
5086
|
+
function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onApplyChain }) {
|
|
4977
5087
|
const COLOR = useColors();
|
|
4978
5088
|
const SURFACE = useSurfaces();
|
|
4979
5089
|
const inputRef = useRef(null);
|
|
@@ -4997,13 +5107,35 @@ function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
|
4997
5107
|
setSelectedIdx(0);
|
|
4998
5108
|
}, []);
|
|
4999
5109
|
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
5110
|
+
const chainMode = onApplyChain !== void 0;
|
|
5111
|
+
const [initialChain] = useState(() => chain && chain.length > 0 ? [...chain] : [{
|
|
5112
|
+
provider: current.providerKey,
|
|
5113
|
+
model: current.modelId
|
|
5114
|
+
}]);
|
|
5115
|
+
const [chainState, setChainState] = useState(initialChain);
|
|
5116
|
+
const modalApi = useModalState();
|
|
5000
5117
|
const commit = () => {
|
|
5001
5118
|
const row = filtered[safeIndex];
|
|
5002
|
-
if (row)
|
|
5119
|
+
if (!row) return;
|
|
5120
|
+
if (chainMode) {
|
|
5121
|
+
setChainState((prev) => toggleChainEntry(prev, {
|
|
5122
|
+
provider: row.providerKey,
|
|
5123
|
+
model: row.model.id
|
|
5124
|
+
}));
|
|
5125
|
+
return;
|
|
5126
|
+
}
|
|
5127
|
+
onPick?.({
|
|
5003
5128
|
providerKey: row.providerKey,
|
|
5004
5129
|
modelId: row.model.id
|
|
5005
5130
|
});
|
|
5006
5131
|
};
|
|
5132
|
+
const handleClose = () => {
|
|
5133
|
+
if (chainMode && chainState.length > 0 && !chainsEqual(chainState, initialChain)) {
|
|
5134
|
+
onApplyChain?.(chainState);
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
modalApi?.close();
|
|
5138
|
+
};
|
|
5007
5139
|
const viewport = useMemo(() => {
|
|
5008
5140
|
if (filtered.length <= VISIBLE_ROWS) return {
|
|
5009
5141
|
start: 0,
|
|
@@ -5035,8 +5167,9 @@ function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
|
5035
5167
|
});
|
|
5036
5168
|
if (providers.length === 0) return /* @__PURE__ */ jsx$1(EmptyProvidersState, {});
|
|
5037
5169
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
5038
|
-
title: "select model",
|
|
5170
|
+
title: chainMode ? "select models — order = fallback priority" : "select model",
|
|
5039
5171
|
maxWidth: 100,
|
|
5172
|
+
onClose: handleClose,
|
|
5040
5173
|
children: [
|
|
5041
5174
|
/* @__PURE__ */ jsx$1("box", {
|
|
5042
5175
|
style: {
|
|
@@ -5074,7 +5207,8 @@ function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
|
5074
5207
|
entry,
|
|
5075
5208
|
isCurrent: entry.providerKey === current.providerKey && entry.model.id === current.modelId,
|
|
5076
5209
|
isFocused: viewport.start + i === safeIndex,
|
|
5077
|
-
highlightBg: SURFACE.selection
|
|
5210
|
+
highlightBg: SURFACE.selection,
|
|
5211
|
+
chainPosition: chainMode ? chainPositionOf(chainState, entry.providerKey, entry.model.id) : null
|
|
5078
5212
|
}, `${entry.providerKey}:${entry.model.id}`))
|
|
5079
5213
|
}),
|
|
5080
5214
|
/* @__PURE__ */ jsxs("text", {
|
|
@@ -5089,12 +5223,16 @@ function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
|
5089
5223
|
fg: COLOR.warn,
|
|
5090
5224
|
children: "↵"
|
|
5091
5225
|
}),
|
|
5092
|
-
" select · ",
|
|
5226
|
+
chainMode ? " add/remove · " : " select · ",
|
|
5093
5227
|
/* @__PURE__ */ jsx$1("span", {
|
|
5094
5228
|
fg: COLOR.warn,
|
|
5095
5229
|
children: "esc"
|
|
5096
5230
|
}),
|
|
5097
|
-
" close · ",
|
|
5231
|
+
chainMode ? " apply · " : " close · ",
|
|
5232
|
+
chainMode && /* @__PURE__ */ jsx$1("span", {
|
|
5233
|
+
fg: COLOR.brand,
|
|
5234
|
+
children: `${chainState.length} selected · `
|
|
5235
|
+
}),
|
|
5098
5236
|
/* @__PURE__ */ jsx$1("span", {
|
|
5099
5237
|
fg: COLOR.mute,
|
|
5100
5238
|
children: `${filtered.length} / ${catalog.length} model${catalog.length === 1 ? "" : "s"}`
|
|
@@ -5111,9 +5249,10 @@ function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
|
5111
5249
|
* select-turn mode in the transcript) so it pops without a separate
|
|
5112
5250
|
* marker glyph competing with the `●` "current" indicator.
|
|
5113
5251
|
*/
|
|
5114
|
-
function ModelRow({ entry, isCurrent, isFocused, highlightBg }) {
|
|
5252
|
+
function ModelRow({ entry, isCurrent, isFocused, highlightBg, chainPosition = null }) {
|
|
5115
5253
|
const COLOR = useColors();
|
|
5116
|
-
const marker = isCurrent ? "●" : " ";
|
|
5254
|
+
const marker = chainPosition !== null ? chainPosition > 9 ? "+" : String(chainPosition) : isCurrent ? "●" : " ";
|
|
5255
|
+
const markerColor = chainPosition !== null || isCurrent ? COLOR.brand : COLOR.mute;
|
|
5117
5256
|
return /* @__PURE__ */ jsx$1("box", {
|
|
5118
5257
|
style: {
|
|
5119
5258
|
height: 1,
|
|
@@ -5126,7 +5265,7 @@ function ModelRow({ entry, isCurrent, isFocused, highlightBg }) {
|
|
|
5126
5265
|
wrapMode: "none",
|
|
5127
5266
|
children: [
|
|
5128
5267
|
/* @__PURE__ */ jsx$1("span", {
|
|
5129
|
-
fg:
|
|
5268
|
+
fg: markerColor,
|
|
5130
5269
|
children: marker
|
|
5131
5270
|
}),
|
|
5132
5271
|
/* @__PURE__ */ jsx$1("span", {
|
|
@@ -5818,7 +5957,7 @@ function ExtensionBanners() {
|
|
|
5818
5957
|
}
|
|
5819
5958
|
//#endregion
|
|
5820
5959
|
//#region src/tui/file-edit-approval-modal.tsx
|
|
5821
|
-
const FILE_EDIT_TOOLS = new Set([
|
|
5960
|
+
const FILE_EDIT_TOOLS = /* @__PURE__ */ new Set([
|
|
5822
5961
|
"edit",
|
|
5823
5962
|
"multi_edit",
|
|
5824
5963
|
"write_file"
|
|
@@ -7521,7 +7660,8 @@ function OpenBrowserButton({ authUrl }) {
|
|
|
7521
7660
|
const feedbackTimer = useRef(null);
|
|
7522
7661
|
const trigger = useCallback(() => {
|
|
7523
7662
|
tryOpenBrowser(authUrl);
|
|
7524
|
-
|
|
7663
|
+
const copied = writeToClipboard(authUrl);
|
|
7664
|
+
setFeedback(copied ? "opened in browser · URL copied to clipboard" : "opened in browser");
|
|
7525
7665
|
if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
|
|
7526
7666
|
feedbackTimer.current = setTimeout(setFeedback, 4e3, null);
|
|
7527
7667
|
}, [authUrl]);
|
|
@@ -8540,7 +8680,8 @@ function SessionsScreen({ sessions, currentId, focusedSessionId, onPick, onCreat
|
|
|
8540
8680
|
]);
|
|
8541
8681
|
const moveCursor = useCallback((nextIndex) => {
|
|
8542
8682
|
if (!onFocusChange || rows.length === 0) return;
|
|
8543
|
-
const
|
|
8683
|
+
const clamped = (nextIndex % rows.length + rows.length) % rows.length;
|
|
8684
|
+
const row = rows[clamped];
|
|
8544
8685
|
onFocusChange(row?.rowId ?? null);
|
|
8545
8686
|
}, [rows, onFocusChange]);
|
|
8546
8687
|
const commitCurrent = useCallback(() => {
|
|
@@ -10642,7 +10783,8 @@ const LOG_LEVEL_CYCLE = [
|
|
|
10642
10783
|
];
|
|
10643
10784
|
/** Next min-level in {@link LOG_LEVEL_CYCLE}, wrapping around. */
|
|
10644
10785
|
function nextLogLevel(level) {
|
|
10645
|
-
|
|
10786
|
+
const i = LOG_LEVEL_CYCLE.indexOf(level);
|
|
10787
|
+
return LOG_LEVEL_CYCLE[(i + 1) % LOG_LEVEL_CYCLE.length];
|
|
10646
10788
|
}
|
|
10647
10789
|
/** Retain only records at or above `minLevel`. Preserves order. */
|
|
10648
10790
|
function filterLogRecords(records, minLevel) {
|
|
@@ -11175,7 +11317,8 @@ function McpAddDialog({ active, existingNames, onSubmit, onCancel }) {
|
|
|
11175
11317
|
}
|
|
11176
11318
|
const cycles = key.name === "left" ? -1 : key.name === "right" || key.name === "space" ? 1 : 0;
|
|
11177
11319
|
if (cycles !== 0 && row === "transport") {
|
|
11178
|
-
|
|
11320
|
+
const t = MCP_TRANSPORTS.indexOf(transport);
|
|
11321
|
+
setTransport(MCP_TRANSPORTS[(t + cycles + MCP_TRANSPORTS.length) % MCP_TRANSPORTS.length]);
|
|
11179
11322
|
return;
|
|
11180
11323
|
}
|
|
11181
11324
|
if (cycles !== 0 && row === "scope") setScope((s) => s === "user" ? "project" : "user");
|
|
@@ -11582,7 +11725,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
11582
11725
|
description: `current: ${settings.compactionModel || "main model"} — pick any provider's model for compaction summaries (pick the main model to clear)`,
|
|
11583
11726
|
onPick: actions.onPickCompactionModel
|
|
11584
11727
|
});
|
|
11585
|
-
const inputs = SETTINGS_INPUTS.filter((inp) => inp.key !== "sessionIcon" || settings.terminalTitle === "icon-session").map((inp) => ({
|
|
11728
|
+
const inputs = SETTINGS_INPUTS.filter((inp) => inp.key !== "sessionIcon" && inp.key !== "attentionIcon" || settings.terminalTitle === "icon-session").map((inp) => ({
|
|
11586
11729
|
kind: "input",
|
|
11587
11730
|
...inp
|
|
11588
11731
|
}));
|
|
@@ -11837,7 +11980,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
11837
11980
|
if (!key.ctrl && !key.meta && !key.shift && (key.name === "left" || key.name === "right")) {
|
|
11838
11981
|
key.preventDefault();
|
|
11839
11982
|
const idx = tabOrder.indexOf(activeTab);
|
|
11840
|
-
|
|
11983
|
+
const next = key.name === "left" ? tabOrder[(idx - 1 + tabOrder.length) % tabOrder.length] : tabOrder[(idx + 1) % tabOrder.length];
|
|
11984
|
+
setActiveTab(next);
|
|
11841
11985
|
return;
|
|
11842
11986
|
}
|
|
11843
11987
|
if (key.name === "up" || key.ctrl && key.name === "p") {
|
|
@@ -13677,8 +13821,9 @@ const TITLE_STACK_PUSH = "\x1B[22;0t";
|
|
|
13677
13821
|
const TITLE_STACK_RESTORE = "\x1B]0;\x07\x1B[23;0t";
|
|
13678
13822
|
/**
|
|
13679
13823
|
* Make a string safe to hand to the terminal as a window/icon title.
|
|
13680
|
-
* Session titles come from user content and
|
|
13681
|
-
*
|
|
13824
|
+
* Session titles come from user content and the icon settings
|
|
13825
|
+
* (`sessionIcon` / `attentionIcon`) are free text, so a stray control
|
|
13826
|
+
* byte (newline, BEL, ESC, …) could terminate the OSC
|
|
13682
13827
|
* title sequence early or smuggle escape sequences through it. Strip
|
|
13683
13828
|
* control characters, collapse whitespace to single spaces, trim, and
|
|
13684
13829
|
* clamp to a sane length — a title is a one-line label, not a payload.
|
|
@@ -13687,6 +13832,28 @@ function sanitizeTitle(raw) {
|
|
|
13687
13832
|
return [...raw.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ").replace(/\s+/g, " ").trim()].slice(0, 256).join("");
|
|
13688
13833
|
}
|
|
13689
13834
|
/**
|
|
13835
|
+
* Compose the window title for the active (non-`'off'`) `terminalTitle`
|
|
13836
|
+
* modes. Pure — the TUI's title effect owns WHEN to apply it (and the
|
|
13837
|
+
* save/restore dance for `'off'`); this owns WHAT it says:
|
|
13838
|
+
*
|
|
13839
|
+
* - `'app'` — always `zidane`.
|
|
13840
|
+
* - `'session'` — the session title, falling back to `zidane`.
|
|
13841
|
+
* - `'icon-session'` — `{icon} · {title}`, where the icon is
|
|
13842
|
+
* `sessionIcon` (⚽ when empty) — or `attentionIcon` (⚠️ when empty)
|
|
13843
|
+
* while the agent is blocked on user input (tool approval /
|
|
13844
|
+
* `ask_user` / `present_plan`), so a backgrounded tab can flag
|
|
13845
|
+
* "zidane needs you".
|
|
13846
|
+
*
|
|
13847
|
+
* Callers still pass the result through {@link sanitizeTitle} — icons
|
|
13848
|
+
* and session titles are both free text.
|
|
13849
|
+
*/
|
|
13850
|
+
function composeTerminalTitle(opts) {
|
|
13851
|
+
const sessionTitle = opts.sessionTitle?.trim() || "zidane";
|
|
13852
|
+
if (opts.mode === "app") return "zidane";
|
|
13853
|
+
if (opts.mode === "session") return sessionTitle;
|
|
13854
|
+
return `${opts.awaitingInput ? opts.attentionIcon.trim() || "⚠️" : opts.sessionIcon.trim() || "⚽"} · ${sessionTitle}`;
|
|
13855
|
+
}
|
|
13856
|
+
/**
|
|
13690
13857
|
* Push the current terminal title so {@link restoreTerminalTitle} can
|
|
13691
13858
|
* bring it back later. No-op when `out` isn't a TTY.
|
|
13692
13859
|
*/
|
|
@@ -14333,7 +14500,7 @@ function blockSummary(turn) {
|
|
|
14333
14500
|
}
|
|
14334
14501
|
//#endregion
|
|
14335
14502
|
//#region src/tui/app.tsx
|
|
14336
|
-
const BUILTIN_COMPLETION_PROVIDER_IDS = new Set([
|
|
14503
|
+
const BUILTIN_COMPLETION_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
14337
14504
|
"commands",
|
|
14338
14505
|
"skills",
|
|
14339
14506
|
"files",
|
|
@@ -14346,7 +14513,7 @@ const BUILTIN_COMPLETION_PROVIDER_IDS = new Set([
|
|
|
14346
14513
|
* section into the commands+skills popover. Extensions extend `/` via
|
|
14347
14514
|
* `ctx.registerCommand`; a raw completion provider must pick its own char.
|
|
14348
14515
|
*/
|
|
14349
|
-
const BUILTIN_COMPLETION_TRIGGERS = new Set([
|
|
14516
|
+
const BUILTIN_COMPLETION_TRIGGERS = /* @__PURE__ */ new Set([
|
|
14350
14517
|
"/",
|
|
14351
14518
|
"@",
|
|
14352
14519
|
"#"
|
|
@@ -14362,6 +14529,12 @@ const BUILTIN_COMPLETION_TRIGGERS = new Set([
|
|
|
14362
14529
|
*/
|
|
14363
14530
|
const SKILL_ACTIVATION_TIMEOUT_MS = 60 * 1e3;
|
|
14364
14531
|
/**
|
|
14532
|
+
* Poll cadence for out-of-band extension install detection (see the
|
|
14533
|
+
* auto-reload effect near `runningRef`). A scan is a handful of readdirs
|
|
14534
|
+
* and lstats — cheap enough that latency, not cost, picked the number.
|
|
14535
|
+
*/
|
|
14536
|
+
const EXTENSION_WATCH_POLL_MS = 3e3;
|
|
14537
|
+
/**
|
|
14365
14538
|
* Surface failures that are normally silenced (teardown / save) when the
|
|
14366
14539
|
* `ZIDANE_DEBUG` env var is set. Logging via `console.error` would otherwise
|
|
14367
14540
|
* trigger OpenTUI's error console overlay and clutter the UI for end users.
|
|
@@ -14619,6 +14792,10 @@ function AppShell() {
|
|
|
14619
14792
|
useEffect(() => {
|
|
14620
14793
|
userInstructionsScopeRef.current = settings.userInstructionsScope;
|
|
14621
14794
|
}, [settings.userInstructionsScope]);
|
|
14795
|
+
const subagentModelsRef = useRef(settings.subagentModels);
|
|
14796
|
+
useEffect(() => {
|
|
14797
|
+
subagentModelsRef.current = settings.subagentModels;
|
|
14798
|
+
}, [settings.subagentModels]);
|
|
14622
14799
|
const autoCompactRef = useRef(settings.autoCompact);
|
|
14623
14800
|
useEffect(() => {
|
|
14624
14801
|
autoCompactRef.current = settings.autoCompact;
|
|
@@ -14965,6 +15142,14 @@ function AppShell() {
|
|
|
14965
15142
|
const [events, setEvents] = useState([]);
|
|
14966
15143
|
const [busy, setBusy] = useState(false);
|
|
14967
15144
|
/**
|
|
15145
|
+
* The provider+model the run actually advanced to after a `model:fallback`
|
|
15146
|
+
* — `null` while the primary target is serving. Drives the footer's model
|
|
15147
|
+
* chip (actual model id, warn-colored to read as "temporary") and resets
|
|
15148
|
+
* at every run submission because fallback is run-sticky: each new run
|
|
15149
|
+
* starts from the primary again.
|
|
15150
|
+
*/
|
|
15151
|
+
const [activeFallback, setActiveFallback] = useState(null);
|
|
15152
|
+
/**
|
|
14968
15153
|
* Drives the compaction UI indicator — the small spinner painted over the
|
|
14969
15154
|
* session title. Toggled by the loop-native `compact:start` / `compact:end`
|
|
14970
15155
|
* hooks (and the manual `onCompactSession` flow).
|
|
@@ -15006,6 +15191,95 @@ function AppShell() {
|
|
|
15006
15191
|
*/
|
|
15007
15192
|
const runningRef = useRef(false);
|
|
15008
15193
|
/**
|
|
15194
|
+
* Auto-`/reload` on out-of-band extension installs. The Settings-panel
|
|
15195
|
+
* lifecycle (ctrl+g/u/x) already ends in a hot reload, but extensions
|
|
15196
|
+
* can also land on disk without the panel: the agent running an
|
|
15197
|
+
* installer through the shell tool mid-conversation, a second terminal,
|
|
15198
|
+
* a plain `git clone` into a scan root. Those used to sit invisible
|
|
15199
|
+
* until a manual `/reload` or restart.
|
|
15200
|
+
*
|
|
15201
|
+
* Detection is a low-frequency poll of `scanExtensionDirectories` — the
|
|
15202
|
+
* SAME entry enumeration discovery uses (trust boundaries, project/user
|
|
15203
|
+
* precedence, `ZIDANE_EXTENSION_DIRS`), so the watcher can't drift from
|
|
15204
|
+
* what a reload would actually see. The signature is the entry set
|
|
15205
|
+
* (name + resolved path), NOT file contents — in-place code edits don't
|
|
15206
|
+
* trip it; this reacts to extensions appearing/disappearing, exactly
|
|
15207
|
+
* the "installed a new extension" event. (fs.watch was rejected: scan
|
|
15208
|
+
* roots routinely don't exist yet, and watching parents-of-missing-dirs
|
|
15209
|
+
* across platforms costs more complexity than a 3s readdir poll.)
|
|
15210
|
+
*
|
|
15211
|
+
* Deliberately skipped while a run is in flight (reload mid-run would
|
|
15212
|
+
* dispose extension instances whose tools the agent may be holding) and
|
|
15213
|
+
* while a modal is open (the Settings install flow does its own reload;
|
|
15214
|
+
* polling under it would race a second discovery pass). Both gates
|
|
15215
|
+
* self-resolve — the next tick catches up. The modal gate is read
|
|
15216
|
+
* through a ref, NOT an effect dep: `useModal()`'s api object is
|
|
15217
|
+
* rebuilt on every open/close, and depending on it would re-seed the
|
|
15218
|
+
* baseline right as a modal closes — silently absorbing anything that
|
|
15219
|
+
* landed on disk while it was open.
|
|
15220
|
+
*
|
|
15221
|
+
* Trust posture matches `/reload` exactly: a newly appeared extension
|
|
15222
|
+
* still goes through the same enable/allowlist + project-trust rules —
|
|
15223
|
+
* auto-reload never auto-enables anything the manual path wouldn't.
|
|
15224
|
+
*
|
|
15225
|
+
* Known accepted gap: the baseline seeds from a fresh disk scan at
|
|
15226
|
+
* mount, so an install landing between boot discovery and shell mount
|
|
15227
|
+
* (seconds, e.g. while on the auth screen) is absorbed unloaded.
|
|
15228
|
+
* Seeding from the registry's own entry set would close it, but the
|
|
15229
|
+
* registry legitimately diverges from raw scan enumeration (trust
|
|
15230
|
+
* rejection, allowlists, selection dedupe) — any systematic divergence
|
|
15231
|
+
* would turn into a permanent reload-every-tick loop. Loop-proof beats
|
|
15232
|
+
* complete here: baseline and tick MUST come from the same function.
|
|
15233
|
+
*/
|
|
15234
|
+
const modalRef = useRef(modal);
|
|
15235
|
+
modalRef.current = modal;
|
|
15236
|
+
const extensionAutoReloadInFlightRef = useRef(false);
|
|
15237
|
+
useEffect(() => {
|
|
15238
|
+
const scanSignature = () => {
|
|
15239
|
+
try {
|
|
15240
|
+
return scanExtensionDirectories({
|
|
15241
|
+
cwd: cwdRef.current,
|
|
15242
|
+
prefix: config.prefix,
|
|
15243
|
+
...config.extensionScanPaths.length > 0 ? { extensionScanPaths: config.extensionScanPaths } : {}
|
|
15244
|
+
}).map((e) => `${e.name}\u0000${e.dir}`).sort().join("\n");
|
|
15245
|
+
} catch (err) {
|
|
15246
|
+
debugLog("extension watch: scan failed", err);
|
|
15247
|
+
return null;
|
|
15248
|
+
}
|
|
15249
|
+
};
|
|
15250
|
+
let lastSignature = scanSignature();
|
|
15251
|
+
const id = setInterval(() => {
|
|
15252
|
+
if (extensionAutoReloadInFlightRef.current || runningRef.current || modalRef.current.isOpen) return;
|
|
15253
|
+
const next = scanSignature();
|
|
15254
|
+
if (next === null) return;
|
|
15255
|
+
if (lastSignature === null) {
|
|
15256
|
+
lastSignature = next;
|
|
15257
|
+
return;
|
|
15258
|
+
}
|
|
15259
|
+
if (next === lastSignature) return;
|
|
15260
|
+
lastSignature = next;
|
|
15261
|
+
extensionAutoReloadInFlightRef.current = true;
|
|
15262
|
+
(async () => {
|
|
15263
|
+
try {
|
|
15264
|
+
const { active, inactive } = await reloadExtensionsRef.current();
|
|
15265
|
+
streamRef.current?.appendImmediate({
|
|
15266
|
+
kind: "info",
|
|
15267
|
+
text: `\u{1F50C} Extensions changed on disk — reloaded: ${active} active${inactive > 0 ? `, ${inactive} inactive (enable in Settings → Extensions)` : ""}. Agent-side contributions apply on the next run.`
|
|
15268
|
+
});
|
|
15269
|
+
} catch (err) {
|
|
15270
|
+
debugLog("extension watch: auto-reload failed", err);
|
|
15271
|
+
} finally {
|
|
15272
|
+
extensionAutoReloadInFlightRef.current = false;
|
|
15273
|
+
}
|
|
15274
|
+
})();
|
|
15275
|
+
}, EXTENSION_WATCH_POLL_MS);
|
|
15276
|
+
return () => clearInterval(id);
|
|
15277
|
+
}, [
|
|
15278
|
+
extensionRegistry,
|
|
15279
|
+
config.prefix,
|
|
15280
|
+
config.extensionScanPaths
|
|
15281
|
+
]);
|
|
15282
|
+
/**
|
|
15009
15283
|
* Auto-wake trigger for background-task notifications. When a
|
|
15010
15284
|
* backgrounded shell task exits while the agent is IDLE, nothing would
|
|
15011
15285
|
* otherwise deliver the queued `<task-notification>` until the user
|
|
@@ -15106,16 +15380,21 @@ function AppShell() {
|
|
|
15106
15380
|
saveTerminalTitle();
|
|
15107
15381
|
titleSavedRef.current = true;
|
|
15108
15382
|
}
|
|
15109
|
-
const
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15383
|
+
const title = composeTerminalTitle({
|
|
15384
|
+
mode: settings.terminalTitle,
|
|
15385
|
+
sessionTitle: currentSession?.title,
|
|
15386
|
+
sessionIcon: settings.sessionIcon,
|
|
15387
|
+
attentionIcon: settings.attentionIcon,
|
|
15388
|
+
awaitingInput: pendingApproval !== null || pendingInteraction !== null
|
|
15389
|
+
});
|
|
15114
15390
|
renderer.setTerminalTitle(sanitizeTitle(title));
|
|
15115
15391
|
}, [
|
|
15116
15392
|
renderer,
|
|
15117
15393
|
settings.terminalTitle,
|
|
15118
15394
|
settings.sessionIcon,
|
|
15395
|
+
settings.attentionIcon,
|
|
15396
|
+
pendingApproval,
|
|
15397
|
+
pendingInteraction,
|
|
15119
15398
|
currentSession?.title,
|
|
15120
15399
|
restoreTitle
|
|
15121
15400
|
]);
|
|
@@ -15327,9 +15606,14 @@ function AppShell() {
|
|
|
15327
15606
|
const model = modelId ?? remembered ?? descriptor.defaultModel ?? descriptor.factory().meta.defaultModel;
|
|
15328
15607
|
const effort = effortForModel(descriptor, model, initialState.lastEffortByModel);
|
|
15329
15608
|
const opts = restoreModelOptions(descriptor, model, initialState.lastModelOptionsByModel);
|
|
15609
|
+
const fallbacks = restoreChainFallbacks(initialState.lastModelChain, {
|
|
15610
|
+
provider: provider.key,
|
|
15611
|
+
model
|
|
15612
|
+
}, (k) => providerRegistry[k] !== void 0);
|
|
15330
15613
|
return {
|
|
15331
15614
|
provider,
|
|
15332
15615
|
model,
|
|
15616
|
+
...fallbacks.length > 0 ? { fallbacks } : {},
|
|
15333
15617
|
...effort ? { effort } : {},
|
|
15334
15618
|
...opts ? { modelOptions: opts } : {}
|
|
15335
15619
|
};
|
|
@@ -15343,9 +15627,66 @@ function AppShell() {
|
|
|
15343
15627
|
suppressWakeRef.current = true;
|
|
15344
15628
|
agentRef.current?.abort();
|
|
15345
15629
|
}, [denyAll, interactions]);
|
|
15630
|
+
/**
|
|
15631
|
+
* `ProviderKey` → runtime `Provider.name` for the providers constructed
|
|
15632
|
+
* into the CURRENT agent. Replaced wholesale on every `buildAgent` so a
|
|
15633
|
+
* run never pins a fallback entry to a provider the live agent doesn't
|
|
15634
|
+
* own (`buildRunModel` drops entries with no mapping). Chain entries
|
|
15635
|
+
* carry chat-level keys; the agent loop matches on instance names —
|
|
15636
|
+
* this map is the bridge.
|
|
15637
|
+
*/
|
|
15638
|
+
const providerNameByKeyRef = useRef(/* @__PURE__ */ new Map());
|
|
15639
|
+
/**
|
|
15640
|
+
* The `model` option for chat runs: the bare primary model id when no
|
|
15641
|
+
* fallbacks are configured (byte-identical to single-model behavior),
|
|
15642
|
+
* else the pinned `{ provider, model }` chain — validated against the
|
|
15643
|
+
* current agent's provider map so a stale chain degrades to fewer
|
|
15644
|
+
* targets instead of a run-time resolution error. Each target carries
|
|
15645
|
+
* its model's context window from the chat catalog so the loop re-bases
|
|
15646
|
+
* the auto-compact trigger when a fallback advances (the run-level
|
|
15647
|
+
* `autoCompact.contextWindow` is sized for the primary).
|
|
15648
|
+
*/
|
|
15649
|
+
const runModelFor = useCallback((p) => {
|
|
15650
|
+
const fallbacks = p.fallbacks ?? [];
|
|
15651
|
+
if (fallbacks.length === 0) return p.model;
|
|
15652
|
+
return buildRunModel([{
|
|
15653
|
+
provider: p.provider.key,
|
|
15654
|
+
model: p.model
|
|
15655
|
+
}, ...fallbacks], (k) => providerNameByKeyRef.current.get(k), (entry) => {
|
|
15656
|
+
const d = providerRegistry[entry.provider];
|
|
15657
|
+
return d ? getContextWindow(d, entry.model) ?? void 0 : void 0;
|
|
15658
|
+
});
|
|
15659
|
+
}, [providerRegistry]);
|
|
15346
15660
|
const buildAgent = useCallback((session, key) => {
|
|
15347
15661
|
const descriptor = providerRegistry[key];
|
|
15348
15662
|
if (!descriptor) throw new Error(`No provider registered for key "${key}"`);
|
|
15663
|
+
const primaryProvider = descriptor.factory();
|
|
15664
|
+
const nameByKey = /* @__PURE__ */ new Map([[key, primaryProvider.name]]);
|
|
15665
|
+
const extraProviders = [];
|
|
15666
|
+
const seenNames = /* @__PURE__ */ new Set([primaryProvider.name]);
|
|
15667
|
+
for (const entry of pickedRef.current?.fallbacks ?? []) {
|
|
15668
|
+
if (nameByKey.has(entry.provider)) continue;
|
|
15669
|
+
const d = providerRegistry[entry.provider];
|
|
15670
|
+
if (!d) {
|
|
15671
|
+
debugLog("buildAgent: fallback provider not registered, skipping", entry.provider);
|
|
15672
|
+
continue;
|
|
15673
|
+
}
|
|
15674
|
+
let instance;
|
|
15675
|
+
try {
|
|
15676
|
+
instance = d.factory();
|
|
15677
|
+
} catch (err) {
|
|
15678
|
+
debugLog(`buildAgent: fallback provider "${entry.provider}" factory threw, skipping`, err);
|
|
15679
|
+
continue;
|
|
15680
|
+
}
|
|
15681
|
+
if (seenNames.has(instance.name)) {
|
|
15682
|
+
debugLog(`buildAgent: duplicate provider name "${instance.name}", skipping fallback provider`, entry.provider);
|
|
15683
|
+
continue;
|
|
15684
|
+
}
|
|
15685
|
+
seenNames.add(instance.name);
|
|
15686
|
+
nameByKey.set(entry.provider, instance.name);
|
|
15687
|
+
extraProviders.push(instance);
|
|
15688
|
+
}
|
|
15689
|
+
providerNameByKeyRef.current = nameByKey;
|
|
15349
15690
|
const profile = pickedAgentRef.current;
|
|
15350
15691
|
const skillsScan = defaultSkillScanPaths({
|
|
15351
15692
|
cwd: projectDir,
|
|
@@ -15356,7 +15697,7 @@ function AppShell() {
|
|
|
15356
15697
|
const userEnabledSkills = enabledSkillsRef.current;
|
|
15357
15698
|
const skillsConfig = buildSkillsConfig({
|
|
15358
15699
|
scan: skillsScan,
|
|
15359
|
-
enabled: userEnabledSkills === void 0 ? void 0 : Array.from(new Set([...userEnabledSkills, ...extensionSkillNames]))
|
|
15700
|
+
enabled: userEnabledSkills === void 0 ? void 0 : Array.from(/* @__PURE__ */ new Set([...userEnabledSkills, ...extensionSkillNames]))
|
|
15360
15701
|
});
|
|
15361
15702
|
const projectMcps = buildMcpServers({
|
|
15362
15703
|
discovered: mcpsCatalogRef.current,
|
|
@@ -15391,6 +15732,20 @@ function AppShell() {
|
|
|
15391
15732
|
sessionId: session.id
|
|
15392
15733
|
});
|
|
15393
15734
|
const composedPreset = composeExtensionPreset(liveExtensionRegistry, profile.preset);
|
|
15735
|
+
const modelCatalogTools = {};
|
|
15736
|
+
if (subagentModelsRef.current) {
|
|
15737
|
+
const catalog = [];
|
|
15738
|
+
for (const [providerKey, providerName] of nameByKey) for (const info of modelsFor(providerKey)) catalog.push({
|
|
15739
|
+
provider: providerName,
|
|
15740
|
+
model: info
|
|
15741
|
+
});
|
|
15742
|
+
const presetSpawn = composedPreset.tools?.spawn;
|
|
15743
|
+
if (catalog.length > 0 && presetSpawn && isSpawnTool(presetSpawn)) {
|
|
15744
|
+
modelCatalogTools.spawn = presetSpawn.withModels(catalog);
|
|
15745
|
+
if (composedPreset.tools && "model_search" in composedPreset.tools) debugLog("buildAgent: preset already registers a \"model_search\" tool — shadowing it with the spawn model catalog", "model_search");
|
|
15746
|
+
modelCatalogTools.model_search = createModelSearchTool(catalog);
|
|
15747
|
+
}
|
|
15748
|
+
}
|
|
15394
15749
|
const execution = launch?.context === "docker" ? createDockerContext({
|
|
15395
15750
|
image: launch.image,
|
|
15396
15751
|
cwd: actualCwd
|
|
@@ -15413,7 +15768,8 @@ function AppShell() {
|
|
|
15413
15768
|
tools: {
|
|
15414
15769
|
...interactionTools,
|
|
15415
15770
|
chatTurnsRead: chatTurnsReadTool,
|
|
15416
|
-
...composedPreset.tools ?? {}
|
|
15771
|
+
...composedPreset.tools ?? {},
|
|
15772
|
+
...modelCatalogTools
|
|
15417
15773
|
},
|
|
15418
15774
|
contextBreakdownSections: [...liveExtensionRegistry.contextBreakdownSections, ...liveExtensionRegistry.contextPanelSections],
|
|
15419
15775
|
behavior: {
|
|
@@ -15422,7 +15778,7 @@ function AppShell() {
|
|
|
15422
15778
|
tasksDir
|
|
15423
15779
|
},
|
|
15424
15780
|
execution,
|
|
15425
|
-
provider:
|
|
15781
|
+
provider: extraProviders.length > 0 ? [primaryProvider, ...extraProviders] : primaryProvider,
|
|
15426
15782
|
session,
|
|
15427
15783
|
mcpConnector: (configs) => connectMcpServers(configs, void 0, agent.hooks, { buildAuthProvider: (cfg) => new McpOAuthProvider({
|
|
15428
15784
|
name: cfg.name,
|
|
@@ -15613,6 +15969,16 @@ function AppShell() {
|
|
|
15613
15969
|
turnId
|
|
15614
15970
|
});
|
|
15615
15971
|
});
|
|
15972
|
+
agent.hooks.hook("model:fallback", ({ from, to, err }) => {
|
|
15973
|
+
setActiveFallback({
|
|
15974
|
+
provider: to.provider,
|
|
15975
|
+
model: to.model
|
|
15976
|
+
});
|
|
15977
|
+
stream.appendImmediate({
|
|
15978
|
+
kind: "info",
|
|
15979
|
+
text: `⚠ model fallback: ${from.provider}/${from.model} failed (${errorMessage(err)}) — continuing on ${to.provider}/${to.model}`
|
|
15980
|
+
});
|
|
15981
|
+
});
|
|
15616
15982
|
agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
|
|
15617
15983
|
registerInFlightTool({
|
|
15618
15984
|
callId,
|
|
@@ -15736,6 +16102,10 @@ function AppShell() {
|
|
|
15736
16102
|
}
|
|
15737
16103
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
15738
16104
|
});
|
|
16105
|
+
agent.hooks.hook("child:turn:after", ({ usage }) => {
|
|
16106
|
+
const turnCost = usage.cost ?? 0;
|
|
16107
|
+
if (turnCost > 0) setSessionCost((prev) => prev + turnCost);
|
|
16108
|
+
});
|
|
15739
16109
|
agent.hooks.hook("agent:done", () => {
|
|
15740
16110
|
const reason = lastFinishReasonRef.current;
|
|
15741
16111
|
lastFinishReasonRef.current = void 0;
|
|
@@ -15781,13 +16151,14 @@ function AppShell() {
|
|
|
15781
16151
|
setLastInputTokens(ctx.effectiveTokens);
|
|
15782
16152
|
lastInputTokensRef.current = ctx.effectiveTokens;
|
|
15783
16153
|
});
|
|
15784
|
-
agent.hooks.hook("spawn:before", ({ id, task, depth }) => {
|
|
16154
|
+
agent.hooks.hook("spawn:before", ({ id, task, depth, model }) => {
|
|
15785
16155
|
const taskPreview = previewLine(task, 80);
|
|
15786
16156
|
stream.appendImmediate({
|
|
15787
16157
|
kind: "spawn-start",
|
|
15788
16158
|
text: taskPreview,
|
|
15789
16159
|
childId: id,
|
|
15790
|
-
depth: depth ?? 1
|
|
16160
|
+
depth: depth ?? 1,
|
|
16161
|
+
...model ? { model } : {}
|
|
15791
16162
|
});
|
|
15792
16163
|
});
|
|
15793
16164
|
agent.hooks.hook("spawn:complete", ({ id, depth, status, stats }) => {
|
|
@@ -15893,6 +16264,7 @@ function AppShell() {
|
|
|
15893
16264
|
return agent;
|
|
15894
16265
|
}, [
|
|
15895
16266
|
providerRegistry,
|
|
16267
|
+
modelsFor,
|
|
15896
16268
|
stream,
|
|
15897
16269
|
gateDecision,
|
|
15898
16270
|
cancelRunOnDenial,
|
|
@@ -15908,7 +16280,8 @@ function AppShell() {
|
|
|
15908
16280
|
extensionRegistry
|
|
15909
16281
|
]);
|
|
15910
16282
|
const refreshSessions = useCallback(async () => {
|
|
15911
|
-
const
|
|
16283
|
+
const filter = settings.showAllProjects ? void 0 : { projectRoot: projectDir };
|
|
16284
|
+
const list = await listSessionMeta(store, filter);
|
|
15912
16285
|
setSessions(list);
|
|
15913
16286
|
return list;
|
|
15914
16287
|
}, [
|
|
@@ -16015,10 +16388,11 @@ function AppShell() {
|
|
|
16015
16388
|
setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
16016
16389
|
runningRef.current = true;
|
|
16017
16390
|
setBusy(true);
|
|
16391
|
+
setActiveFallback(null);
|
|
16018
16392
|
try {
|
|
16019
16393
|
const runModelOptions = enabledModelOptions(currentPicked.modelOptions);
|
|
16020
16394
|
await agent.run({
|
|
16021
|
-
model: currentPicked
|
|
16395
|
+
model: runModelFor(currentPicked),
|
|
16022
16396
|
...currentPicked.effort ? { thinking: currentPicked.effort } : {},
|
|
16023
16397
|
...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {},
|
|
16024
16398
|
behavior: {
|
|
@@ -16060,8 +16434,10 @@ function AppShell() {
|
|
|
16060
16434
|
sessionRef.current = session;
|
|
16061
16435
|
agentRef.current = buildAgent(session, key);
|
|
16062
16436
|
if (extensionRegistrySwapKeyRef.current === null || extensionRegistrySwapKeyRef.current === extensionRegistry) pendingAgentRebuildRef.current = false;
|
|
16063
|
-
|
|
16437
|
+
const persistedPins = readPinnedSkills(session.metadata["zidane.activeSkills"]);
|
|
16438
|
+
setActiveSkillNames(persistedPins);
|
|
16064
16439
|
setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
16440
|
+
setActiveFallback(null);
|
|
16065
16441
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
16066
16442
|
setLastInputTokens(replayedTokens);
|
|
16067
16443
|
lastInputTokensRef.current = replayedTokens;
|
|
@@ -16149,7 +16525,8 @@ function AppShell() {
|
|
|
16149
16525
|
]);
|
|
16150
16526
|
const [availableProviders, setAvailableProviders] = useState([]);
|
|
16151
16527
|
const refreshAvailableProviders = useCallback(() => {
|
|
16152
|
-
|
|
16528
|
+
const detected = detectAuth(config.paths.userDir, providerRegistry);
|
|
16529
|
+
setAvailableProviders(detected.filter((p) => p.available));
|
|
16153
16530
|
}, [config.paths.userDir, providerRegistry]);
|
|
16154
16531
|
useEffect(() => {
|
|
16155
16532
|
refreshAvailableProviders();
|
|
@@ -16456,30 +16833,36 @@ function AppShell() {
|
|
|
16456
16833
|
else setQueueSelectionIndex(Math.min(pushIdx, messageQueueRef.current.length - 1));
|
|
16457
16834
|
}, []);
|
|
16458
16835
|
/**
|
|
16459
|
-
*
|
|
16460
|
-
*
|
|
16836
|
+
* Apply a model chain from the cross-provider picker's ordered
|
|
16837
|
+
* multi-select. Entry 0 is the PRIMARY (same role as the old
|
|
16838
|
+
* single-pick: provider swap, effort, options); entries 1+ become
|
|
16839
|
+
* run-level fallbacks (`agent.run({ model: [...] })`).
|
|
16461
16840
|
*
|
|
16462
|
-
*
|
|
16463
|
-
*
|
|
16464
|
-
*
|
|
16465
|
-
*
|
|
16466
|
-
* - **Different provider** — swap the active `ProviderAuth`,
|
|
16467
|
-
* persist BOTH `lastProvider` + `lastModelByProvider`, then
|
|
16468
|
-
* re-activate the current session against the new provider's
|
|
16469
|
-
* factory. The session id (and conversation history) is
|
|
16470
|
-
* preserved; only the bound agent + provider instance change.
|
|
16841
|
+
* Persistence mirrors the old single-pick path (`lastProvider` +
|
|
16842
|
+
* `lastModelByProvider` track the primary) plus the additive
|
|
16843
|
+
* `lastModelChain`. Fallback entries are re-validated against the
|
|
16844
|
+
* live auth list; stale entries drop with a debug log.
|
|
16471
16845
|
*
|
|
16472
|
-
*
|
|
16473
|
-
*
|
|
16474
|
-
*
|
|
16846
|
+
* The session re-activates (agent rebuild) when the primary provider
|
|
16847
|
+
* changed OR the chain's provider-key SET changed — the agent must
|
|
16848
|
+
* own an instance for every chain provider. A pure model / order
|
|
16849
|
+
* change within the same providers stays a per-run swap, exactly
|
|
16850
|
+
* like same-provider single picks always have.
|
|
16475
16851
|
*/
|
|
16476
|
-
const
|
|
16477
|
-
const
|
|
16852
|
+
const onApplyModelChain = useCallback(async (chain) => {
|
|
16853
|
+
const primary = chain[0];
|
|
16854
|
+
if (!primary) {
|
|
16855
|
+
modal.close();
|
|
16856
|
+
return;
|
|
16857
|
+
}
|
|
16858
|
+
const nextProvider = availableProviders.find((p) => p.key === primary.provider);
|
|
16478
16859
|
if (!nextProvider) {
|
|
16479
|
-
debugLog("
|
|
16860
|
+
debugLog("onApplyModelChain: unknown provider key", primary.provider);
|
|
16480
16861
|
modal.close();
|
|
16481
16862
|
return;
|
|
16482
16863
|
}
|
|
16864
|
+
const fallbacks = filterChainFallbacks(chain.slice(1), primary, (k) => availableProviders.some((p) => p.key === k) && providerRegistry[k] !== void 0);
|
|
16865
|
+
if (fallbacks.length < chain.length - 1) debugLog("onApplyModelChain: dropped unavailable/duplicate fallback entries", chain.length - 1 - fallbacks.length);
|
|
16483
16866
|
const descriptor = providerRegistry[nextProvider.key];
|
|
16484
16867
|
const prior = stateStore.load();
|
|
16485
16868
|
const providerChanged = picked?.provider.key !== nextProvider.key;
|
|
@@ -16488,19 +16871,34 @@ function AppShell() {
|
|
|
16488
16871
|
...providerChanged ? { lastProvider: nextProvider.key } : {},
|
|
16489
16872
|
lastModelByProvider: {
|
|
16490
16873
|
...prior.lastModelByProvider,
|
|
16491
|
-
[nextProvider.key]:
|
|
16492
|
-
}
|
|
16874
|
+
[nextProvider.key]: primary.model
|
|
16875
|
+
},
|
|
16876
|
+
lastModelChain: [{
|
|
16877
|
+
provider: primary.provider,
|
|
16878
|
+
model: primary.model
|
|
16879
|
+
}, ...fallbacks]
|
|
16493
16880
|
});
|
|
16494
|
-
const nextEffort = descriptor ? effortForModel(descriptor,
|
|
16495
|
-
const nextOptions = descriptor ? restoreModelOptions(descriptor,
|
|
16496
|
-
|
|
16881
|
+
const nextEffort = descriptor ? effortForModel(descriptor, primary.model, prior.lastEffortByModel) : void 0;
|
|
16882
|
+
const nextOptions = descriptor ? restoreModelOptions(descriptor, primary.model, prior.lastModelOptionsByModel) : void 0;
|
|
16883
|
+
const updated = {
|
|
16497
16884
|
provider: nextProvider,
|
|
16498
|
-
model:
|
|
16885
|
+
model: primary.model,
|
|
16886
|
+
...fallbacks.length > 0 ? { fallbacks } : {},
|
|
16499
16887
|
...nextEffort ? { effort: nextEffort } : {},
|
|
16500
16888
|
...nextOptions ? { modelOptions: nextOptions } : {}
|
|
16501
|
-
}
|
|
16889
|
+
};
|
|
16890
|
+
const prevChain = picked ? [{
|
|
16891
|
+
provider: picked.provider.key,
|
|
16892
|
+
model: picked.model
|
|
16893
|
+
}, ...picked.fallbacks ?? []] : [];
|
|
16894
|
+
setPicked(updated);
|
|
16895
|
+
setActiveFallback(null);
|
|
16896
|
+
pickedRef.current = updated;
|
|
16502
16897
|
modal.close();
|
|
16503
|
-
if (providerChanged
|
|
16898
|
+
if ((providerChanged || providerKeySetChanged(prevChain, [{
|
|
16899
|
+
provider: primary.provider,
|
|
16900
|
+
model: primary.model
|
|
16901
|
+
}, ...fallbacks])) && currentSession && !busy && !pendingApproval) await activateSession(currentSession.id, nextProvider.key);
|
|
16504
16902
|
}, [
|
|
16505
16903
|
availableProviders,
|
|
16506
16904
|
providerRegistry,
|
|
@@ -16535,7 +16933,8 @@ function AppShell() {
|
|
|
16535
16933
|
modelsFor,
|
|
16536
16934
|
current,
|
|
16537
16935
|
onPick: (next) => {
|
|
16538
|
-
|
|
16936
|
+
const isMain = next.providerKey === picked.provider.key && next.modelId === picked.model;
|
|
16937
|
+
setSetting("compactionModel", isMain ? "" : `${next.providerKey}/${next.modelId}`);
|
|
16539
16938
|
modal.close();
|
|
16540
16939
|
}
|
|
16541
16940
|
}));
|
|
@@ -16676,7 +17075,7 @@ function AppShell() {
|
|
|
16676
17075
|
});
|
|
16677
17076
|
}
|
|
16678
17077
|
const newRefs = uniqueSkillNamesFromReferences(references);
|
|
16679
|
-
const skillNames = Array.from(new Set([...activeSkillNamesRef.current, ...newRefs]));
|
|
17078
|
+
const skillNames = Array.from(/* @__PURE__ */ new Set([...activeSkillNamesRef.current, ...newRefs]));
|
|
16680
17079
|
for (const name of skillNames) try {
|
|
16681
17080
|
await withTimeout(agent.activateSkill(name), {
|
|
16682
17081
|
operation: `activateSkill(${name})`,
|
|
@@ -16713,8 +17112,9 @@ function AppShell() {
|
|
|
16713
17112
|
};
|
|
16714
17113
|
})];
|
|
16715
17114
|
const runModelOptions = enabledModelOptions(picked.modelOptions);
|
|
17115
|
+
setActiveFallback(null);
|
|
16716
17116
|
await agent.run({
|
|
16717
|
-
model: picked
|
|
17117
|
+
model: runModelFor(picked),
|
|
16718
17118
|
prompt: runPrompt,
|
|
16719
17119
|
...picked.effort ? { thinking: picked.effort } : {},
|
|
16720
17120
|
...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {},
|
|
@@ -16848,8 +17248,9 @@ function AppShell() {
|
|
|
16848
17248
|
const before = session.turns.length;
|
|
16849
17249
|
const beforeRuns = session.runs.length;
|
|
16850
17250
|
const runModelOptions = enabledModelOptions(picked.modelOptions);
|
|
17251
|
+
setActiveFallback(null);
|
|
16851
17252
|
await agent.run({
|
|
16852
|
-
model: picked
|
|
17253
|
+
model: runModelFor(picked),
|
|
16853
17254
|
prompt: agentPrompt,
|
|
16854
17255
|
...picked.effort ? { thinking: picked.effort } : {},
|
|
16855
17256
|
...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {},
|
|
@@ -17256,7 +17657,8 @@ function AppShell() {
|
|
|
17256
17657
|
if (!session || !p) return;
|
|
17257
17658
|
const stale = agentRef.current;
|
|
17258
17659
|
const staleWarnLog = warnLogRef.current;
|
|
17259
|
-
|
|
17660
|
+
const fresh = buildAgentRef.current(session, p.provider.key);
|
|
17661
|
+
agentRef.current = fresh;
|
|
17260
17662
|
if (stale) await stale.destroy().catch((err) => debugLog("rebuildActiveAgent: destroy failed", err));
|
|
17261
17663
|
if (staleWarnLog) await staleWarnLog.close().catch((err) => debugLog("rebuildActiveAgent: warnLog.close failed", err));
|
|
17262
17664
|
}, []);
|
|
@@ -17353,7 +17755,8 @@ function AppShell() {
|
|
|
17353
17755
|
]);
|
|
17354
17756
|
const onLogoutMcp = useCallback((name) => {
|
|
17355
17757
|
mcpCredentialStore.delete(name);
|
|
17356
|
-
|
|
17758
|
+
const entry = mcpsCatalogRef.current.find((d) => d.config.name === name);
|
|
17759
|
+
dispatchAuth(entry?.config.auth === "oauth" ? {
|
|
17357
17760
|
type: "auth-required",
|
|
17358
17761
|
name,
|
|
17359
17762
|
reason: "no-tokens"
|
|
@@ -17415,7 +17818,8 @@ function AppShell() {
|
|
|
17415
17818
|
const idx = turnIds.indexOf(prev);
|
|
17416
17819
|
if (idx === -1) return turnIds[turnIds.length - 1];
|
|
17417
17820
|
const len = turnIds.length;
|
|
17418
|
-
|
|
17821
|
+
const nextIdx = ((idx + direction) % len + len) % len;
|
|
17822
|
+
return turnIds[nextIdx];
|
|
17419
17823
|
});
|
|
17420
17824
|
}, [turnIds]);
|
|
17421
17825
|
const onForkTurn = useCallback(async (turnId) => {
|
|
@@ -17929,7 +18333,11 @@ function AppShell() {
|
|
|
17929
18333
|
providerKey: picked.provider.key,
|
|
17930
18334
|
modelId: picked.model
|
|
17931
18335
|
},
|
|
17932
|
-
|
|
18336
|
+
chain: [{
|
|
18337
|
+
provider: picked.provider.key,
|
|
18338
|
+
model: picked.model
|
|
18339
|
+
}, ...picked.fallbacks ?? []],
|
|
18340
|
+
onApplyChain: onApplyModelChain
|
|
17933
18341
|
}));
|
|
17934
18342
|
return;
|
|
17935
18343
|
}
|
|
@@ -17964,7 +18372,13 @@ function AppShell() {
|
|
|
17964
18372
|
autocompactBuffer: OUTPUT_RESERVE_TOKENS,
|
|
17965
18373
|
...settings.autoCompact && settings.autoCompactThreshold ? { compactThreshold: settings.autoCompactThreshold } : {}
|
|
17966
18374
|
}).then((breakdown) => {
|
|
17967
|
-
if (breakdown && agentRef.current === agent)
|
|
18375
|
+
if (breakdown && agentRef.current === agent) {
|
|
18376
|
+
const modelUsage = usageByModel(sessionRef.current?.runs ?? []);
|
|
18377
|
+
modal.open(/* @__PURE__ */ jsx$1(ContextPanelModal, {
|
|
18378
|
+
breakdown,
|
|
18379
|
+
modelUsage
|
|
18380
|
+
}));
|
|
18381
|
+
}
|
|
17968
18382
|
}).catch((err) => debugLog("getContextBreakdown failed", err));
|
|
17969
18383
|
return;
|
|
17970
18384
|
}
|
|
@@ -18135,8 +18549,8 @@ function AppShell() {
|
|
|
18135
18549
|
currentSession,
|
|
18136
18550
|
hasMultipleAgents,
|
|
18137
18551
|
uiMode: settings.uiMode,
|
|
18138
|
-
modelLabel: picked
|
|
18139
|
-
modelColor: COLOR.model,
|
|
18552
|
+
modelLabel: activeFallback ? activeFallback.model : picked ? `${picked.model}${picked.fallbacks && picked.fallbacks.length > 0 ? ` +${picked.fallbacks.length}` : ""}` : null,
|
|
18553
|
+
modelColor: activeFallback ? COLOR.warn : COLOR.model,
|
|
18140
18554
|
effortLabel: modelHasReasoning ? picked?.effort ?? "medium" : null,
|
|
18141
18555
|
effortColor: COLOR.warn,
|
|
18142
18556
|
modelOptionsLabel: (() => {
|