zidane 6.0.1 → 6.1.1
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-DK382vzL.js} +8 -8
- package/dist/{acp-Irby04Us.js.map → acp-DK382vzL.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-Bq32ZJSo.js} +392 -63
- package/dist/agent-Bq32ZJSo.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-CTHsdFG9.js → anthropic-CV_OSYVD.js} +10 -9
- package/dist/anthropic-CV_OSYVD.js.map +1 -0
- package/dist/atomic-write-B7zr1ddF.js.map +1 -1
- package/dist/{auth-BDSu_0t3.js → auth-D5FfJtLf.js} +7 -7
- package/dist/auth-D5FfJtLf.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-C6Il4kBD.d.ts} +388 -5
- package/dist/index-C6Il4kBD.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-Ce4yRjd3.js} +2 -2
- package/dist/{messages-CwujiS74.js.map → messages-Ce4yRjd3.js.map} +1 -1
- package/dist/oauth-8LqbeI_Q.js.map +1 -1
- package/dist/{openai-compat-BvgYGSR1.js → openai-compat-TA7aihJ-.js} +6 -4
- package/dist/openai-compat-TA7aihJ-.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-Dln69sQ_.js} +3 -3
- package/dist/{providers-z6LDMAr3.js.map → providers-Dln69sQ_.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-D7JSEMME.js} +3 -3
- package/dist/session-D7JSEMME.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-ETbcC9DM.d.ts} +2 -2
- package/dist/tool-formatters-ETbcC9DM.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-CBaghfwl.js} +364 -96
- package/dist/transcript-anchors-CBaghfwl.js.map +1 -0
- package/dist/{transcript-anchors-Cb-bDbNH.d.ts → transcript-anchors-e_30Kvk0.d.ts} +37 -3
- package/dist/transcript-anchors-e_30Kvk0.d.ts.map +1 -0
- package/dist/tui.d.ts +21 -6
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +505 -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-D-aaiUOo.d.ts} +27 -9
- package/dist/turn-operations-D-aaiUOo.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-kGdhWmds.js} +3 -3
- package/dist/{xai-B_e6TOA8.js.map → xai-kGdhWmds.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-CBaghfwl.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-Bq32ZJSo.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-D7JSEMME.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-D5FfJtLf.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" && inp.key !== "activeIcon" || 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,32 @@ 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 reflects the
|
|
13842
|
+
* agent's state, most-urgent first:
|
|
13843
|
+
* 1. `attentionIcon` (⚠️ when empty) while the agent is blocked on
|
|
13844
|
+
* user input (tool approval / `ask_user` / `present_plan`) — a
|
|
13845
|
+
* backgrounded tab flags "zidane needs you". Wins over `active`
|
|
13846
|
+
* because an approval mid-run blocks the stream anyway.
|
|
13847
|
+
* 2. `activeIcon` (⚡ when empty) while a run is streaming — the
|
|
13848
|
+
* tab reads "working" at a glance.
|
|
13849
|
+
* 3. `sessionIcon` (⚽ when empty) — idle.
|
|
13850
|
+
*
|
|
13851
|
+
* Callers still pass the result through {@link sanitizeTitle} — icons
|
|
13852
|
+
* and session titles are both free text.
|
|
13853
|
+
*/
|
|
13854
|
+
function composeTerminalTitle(opts) {
|
|
13855
|
+
const sessionTitle = opts.sessionTitle?.trim() || "zidane";
|
|
13856
|
+
if (opts.mode === "app") return "zidane";
|
|
13857
|
+
if (opts.mode === "session") return sessionTitle;
|
|
13858
|
+
return `${opts.awaitingInput ? opts.attentionIcon.trim() || "⚠️" : opts.active ? opts.activeIcon.trim() || "⚡" : opts.sessionIcon.trim() || "⚽"} · ${sessionTitle}`;
|
|
13859
|
+
}
|
|
13860
|
+
/**
|
|
13690
13861
|
* Push the current terminal title so {@link restoreTerminalTitle} can
|
|
13691
13862
|
* bring it back later. No-op when `out` isn't a TTY.
|
|
13692
13863
|
*/
|
|
@@ -14333,7 +14504,7 @@ function blockSummary(turn) {
|
|
|
14333
14504
|
}
|
|
14334
14505
|
//#endregion
|
|
14335
14506
|
//#region src/tui/app.tsx
|
|
14336
|
-
const BUILTIN_COMPLETION_PROVIDER_IDS = new Set([
|
|
14507
|
+
const BUILTIN_COMPLETION_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
14337
14508
|
"commands",
|
|
14338
14509
|
"skills",
|
|
14339
14510
|
"files",
|
|
@@ -14346,7 +14517,7 @@ const BUILTIN_COMPLETION_PROVIDER_IDS = new Set([
|
|
|
14346
14517
|
* section into the commands+skills popover. Extensions extend `/` via
|
|
14347
14518
|
* `ctx.registerCommand`; a raw completion provider must pick its own char.
|
|
14348
14519
|
*/
|
|
14349
|
-
const BUILTIN_COMPLETION_TRIGGERS = new Set([
|
|
14520
|
+
const BUILTIN_COMPLETION_TRIGGERS = /* @__PURE__ */ new Set([
|
|
14350
14521
|
"/",
|
|
14351
14522
|
"@",
|
|
14352
14523
|
"#"
|
|
@@ -14362,6 +14533,12 @@ const BUILTIN_COMPLETION_TRIGGERS = new Set([
|
|
|
14362
14533
|
*/
|
|
14363
14534
|
const SKILL_ACTIVATION_TIMEOUT_MS = 60 * 1e3;
|
|
14364
14535
|
/**
|
|
14536
|
+
* Poll cadence for out-of-band extension install detection (see the
|
|
14537
|
+
* auto-reload effect near `runningRef`). A scan is a handful of readdirs
|
|
14538
|
+
* and lstats — cheap enough that latency, not cost, picked the number.
|
|
14539
|
+
*/
|
|
14540
|
+
const EXTENSION_WATCH_POLL_MS = 3e3;
|
|
14541
|
+
/**
|
|
14365
14542
|
* Surface failures that are normally silenced (teardown / save) when the
|
|
14366
14543
|
* `ZIDANE_DEBUG` env var is set. Logging via `console.error` would otherwise
|
|
14367
14544
|
* trigger OpenTUI's error console overlay and clutter the UI for end users.
|
|
@@ -14619,6 +14796,10 @@ function AppShell() {
|
|
|
14619
14796
|
useEffect(() => {
|
|
14620
14797
|
userInstructionsScopeRef.current = settings.userInstructionsScope;
|
|
14621
14798
|
}, [settings.userInstructionsScope]);
|
|
14799
|
+
const subagentModelsRef = useRef(settings.subagentModels);
|
|
14800
|
+
useEffect(() => {
|
|
14801
|
+
subagentModelsRef.current = settings.subagentModels;
|
|
14802
|
+
}, [settings.subagentModels]);
|
|
14622
14803
|
const autoCompactRef = useRef(settings.autoCompact);
|
|
14623
14804
|
useEffect(() => {
|
|
14624
14805
|
autoCompactRef.current = settings.autoCompact;
|
|
@@ -14965,6 +15146,14 @@ function AppShell() {
|
|
|
14965
15146
|
const [events, setEvents] = useState([]);
|
|
14966
15147
|
const [busy, setBusy] = useState(false);
|
|
14967
15148
|
/**
|
|
15149
|
+
* The provider+model the run actually advanced to after a `model:fallback`
|
|
15150
|
+
* — `null` while the primary target is serving. Drives the footer's model
|
|
15151
|
+
* chip (actual model id, warn-colored to read as "temporary") and resets
|
|
15152
|
+
* at every run submission because fallback is run-sticky: each new run
|
|
15153
|
+
* starts from the primary again.
|
|
15154
|
+
*/
|
|
15155
|
+
const [activeFallback, setActiveFallback] = useState(null);
|
|
15156
|
+
/**
|
|
14968
15157
|
* Drives the compaction UI indicator — the small spinner painted over the
|
|
14969
15158
|
* session title. Toggled by the loop-native `compact:start` / `compact:end`
|
|
14970
15159
|
* hooks (and the manual `onCompactSession` flow).
|
|
@@ -15006,6 +15195,95 @@ function AppShell() {
|
|
|
15006
15195
|
*/
|
|
15007
15196
|
const runningRef = useRef(false);
|
|
15008
15197
|
/**
|
|
15198
|
+
* Auto-`/reload` on out-of-band extension installs. The Settings-panel
|
|
15199
|
+
* lifecycle (ctrl+g/u/x) already ends in a hot reload, but extensions
|
|
15200
|
+
* can also land on disk without the panel: the agent running an
|
|
15201
|
+
* installer through the shell tool mid-conversation, a second terminal,
|
|
15202
|
+
* a plain `git clone` into a scan root. Those used to sit invisible
|
|
15203
|
+
* until a manual `/reload` or restart.
|
|
15204
|
+
*
|
|
15205
|
+
* Detection is a low-frequency poll of `scanExtensionDirectories` — the
|
|
15206
|
+
* SAME entry enumeration discovery uses (trust boundaries, project/user
|
|
15207
|
+
* precedence, `ZIDANE_EXTENSION_DIRS`), so the watcher can't drift from
|
|
15208
|
+
* what a reload would actually see. The signature is the entry set
|
|
15209
|
+
* (name + resolved path), NOT file contents — in-place code edits don't
|
|
15210
|
+
* trip it; this reacts to extensions appearing/disappearing, exactly
|
|
15211
|
+
* the "installed a new extension" event. (fs.watch was rejected: scan
|
|
15212
|
+
* roots routinely don't exist yet, and watching parents-of-missing-dirs
|
|
15213
|
+
* across platforms costs more complexity than a 3s readdir poll.)
|
|
15214
|
+
*
|
|
15215
|
+
* Deliberately skipped while a run is in flight (reload mid-run would
|
|
15216
|
+
* dispose extension instances whose tools the agent may be holding) and
|
|
15217
|
+
* while a modal is open (the Settings install flow does its own reload;
|
|
15218
|
+
* polling under it would race a second discovery pass). Both gates
|
|
15219
|
+
* self-resolve — the next tick catches up. The modal gate is read
|
|
15220
|
+
* through a ref, NOT an effect dep: `useModal()`'s api object is
|
|
15221
|
+
* rebuilt on every open/close, and depending on it would re-seed the
|
|
15222
|
+
* baseline right as a modal closes — silently absorbing anything that
|
|
15223
|
+
* landed on disk while it was open.
|
|
15224
|
+
*
|
|
15225
|
+
* Trust posture matches `/reload` exactly: a newly appeared extension
|
|
15226
|
+
* still goes through the same enable/allowlist + project-trust rules —
|
|
15227
|
+
* auto-reload never auto-enables anything the manual path wouldn't.
|
|
15228
|
+
*
|
|
15229
|
+
* Known accepted gap: the baseline seeds from a fresh disk scan at
|
|
15230
|
+
* mount, so an install landing between boot discovery and shell mount
|
|
15231
|
+
* (seconds, e.g. while on the auth screen) is absorbed unloaded.
|
|
15232
|
+
* Seeding from the registry's own entry set would close it, but the
|
|
15233
|
+
* registry legitimately diverges from raw scan enumeration (trust
|
|
15234
|
+
* rejection, allowlists, selection dedupe) — any systematic divergence
|
|
15235
|
+
* would turn into a permanent reload-every-tick loop. Loop-proof beats
|
|
15236
|
+
* complete here: baseline and tick MUST come from the same function.
|
|
15237
|
+
*/
|
|
15238
|
+
const modalRef = useRef(modal);
|
|
15239
|
+
modalRef.current = modal;
|
|
15240
|
+
const extensionAutoReloadInFlightRef = useRef(false);
|
|
15241
|
+
useEffect(() => {
|
|
15242
|
+
const scanSignature = () => {
|
|
15243
|
+
try {
|
|
15244
|
+
return scanExtensionDirectories({
|
|
15245
|
+
cwd: cwdRef.current,
|
|
15246
|
+
prefix: config.prefix,
|
|
15247
|
+
...config.extensionScanPaths.length > 0 ? { extensionScanPaths: config.extensionScanPaths } : {}
|
|
15248
|
+
}).map((e) => `${e.name}\u0000${e.dir}`).sort().join("\n");
|
|
15249
|
+
} catch (err) {
|
|
15250
|
+
debugLog("extension watch: scan failed", err);
|
|
15251
|
+
return null;
|
|
15252
|
+
}
|
|
15253
|
+
};
|
|
15254
|
+
let lastSignature = scanSignature();
|
|
15255
|
+
const id = setInterval(() => {
|
|
15256
|
+
if (extensionAutoReloadInFlightRef.current || runningRef.current || modalRef.current.isOpen) return;
|
|
15257
|
+
const next = scanSignature();
|
|
15258
|
+
if (next === null) return;
|
|
15259
|
+
if (lastSignature === null) {
|
|
15260
|
+
lastSignature = next;
|
|
15261
|
+
return;
|
|
15262
|
+
}
|
|
15263
|
+
if (next === lastSignature) return;
|
|
15264
|
+
lastSignature = next;
|
|
15265
|
+
extensionAutoReloadInFlightRef.current = true;
|
|
15266
|
+
(async () => {
|
|
15267
|
+
try {
|
|
15268
|
+
const { active, inactive } = await reloadExtensionsRef.current();
|
|
15269
|
+
streamRef.current?.appendImmediate({
|
|
15270
|
+
kind: "info",
|
|
15271
|
+
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.`
|
|
15272
|
+
});
|
|
15273
|
+
} catch (err) {
|
|
15274
|
+
debugLog("extension watch: auto-reload failed", err);
|
|
15275
|
+
} finally {
|
|
15276
|
+
extensionAutoReloadInFlightRef.current = false;
|
|
15277
|
+
}
|
|
15278
|
+
})();
|
|
15279
|
+
}, EXTENSION_WATCH_POLL_MS);
|
|
15280
|
+
return () => clearInterval(id);
|
|
15281
|
+
}, [
|
|
15282
|
+
extensionRegistry,
|
|
15283
|
+
config.prefix,
|
|
15284
|
+
config.extensionScanPaths
|
|
15285
|
+
]);
|
|
15286
|
+
/**
|
|
15009
15287
|
* Auto-wake trigger for background-task notifications. When a
|
|
15010
15288
|
* backgrounded shell task exits while the agent is IDLE, nothing would
|
|
15011
15289
|
* otherwise deliver the queued `<task-notification>` until the user
|
|
@@ -15106,16 +15384,25 @@ function AppShell() {
|
|
|
15106
15384
|
saveTerminalTitle();
|
|
15107
15385
|
titleSavedRef.current = true;
|
|
15108
15386
|
}
|
|
15109
|
-
const
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15387
|
+
const title = composeTerminalTitle({
|
|
15388
|
+
mode: settings.terminalTitle,
|
|
15389
|
+
sessionTitle: currentSession?.title,
|
|
15390
|
+
sessionIcon: settings.sessionIcon,
|
|
15391
|
+
attentionIcon: settings.attentionIcon,
|
|
15392
|
+
activeIcon: settings.activeIcon,
|
|
15393
|
+
awaitingInput: pendingApproval !== null || pendingInteraction !== null,
|
|
15394
|
+
active: busy
|
|
15395
|
+
});
|
|
15114
15396
|
renderer.setTerminalTitle(sanitizeTitle(title));
|
|
15115
15397
|
}, [
|
|
15116
15398
|
renderer,
|
|
15117
15399
|
settings.terminalTitle,
|
|
15118
15400
|
settings.sessionIcon,
|
|
15401
|
+
settings.attentionIcon,
|
|
15402
|
+
settings.activeIcon,
|
|
15403
|
+
busy,
|
|
15404
|
+
pendingApproval,
|
|
15405
|
+
pendingInteraction,
|
|
15119
15406
|
currentSession?.title,
|
|
15120
15407
|
restoreTitle
|
|
15121
15408
|
]);
|
|
@@ -15327,9 +15614,14 @@ function AppShell() {
|
|
|
15327
15614
|
const model = modelId ?? remembered ?? descriptor.defaultModel ?? descriptor.factory().meta.defaultModel;
|
|
15328
15615
|
const effort = effortForModel(descriptor, model, initialState.lastEffortByModel);
|
|
15329
15616
|
const opts = restoreModelOptions(descriptor, model, initialState.lastModelOptionsByModel);
|
|
15617
|
+
const fallbacks = restoreChainFallbacks(initialState.lastModelChain, {
|
|
15618
|
+
provider: provider.key,
|
|
15619
|
+
model
|
|
15620
|
+
}, (k) => providerRegistry[k] !== void 0);
|
|
15330
15621
|
return {
|
|
15331
15622
|
provider,
|
|
15332
15623
|
model,
|
|
15624
|
+
...fallbacks.length > 0 ? { fallbacks } : {},
|
|
15333
15625
|
...effort ? { effort } : {},
|
|
15334
15626
|
...opts ? { modelOptions: opts } : {}
|
|
15335
15627
|
};
|
|
@@ -15343,9 +15635,66 @@ function AppShell() {
|
|
|
15343
15635
|
suppressWakeRef.current = true;
|
|
15344
15636
|
agentRef.current?.abort();
|
|
15345
15637
|
}, [denyAll, interactions]);
|
|
15638
|
+
/**
|
|
15639
|
+
* `ProviderKey` → runtime `Provider.name` for the providers constructed
|
|
15640
|
+
* into the CURRENT agent. Replaced wholesale on every `buildAgent` so a
|
|
15641
|
+
* run never pins a fallback entry to a provider the live agent doesn't
|
|
15642
|
+
* own (`buildRunModel` drops entries with no mapping). Chain entries
|
|
15643
|
+
* carry chat-level keys; the agent loop matches on instance names —
|
|
15644
|
+
* this map is the bridge.
|
|
15645
|
+
*/
|
|
15646
|
+
const providerNameByKeyRef = useRef(/* @__PURE__ */ new Map());
|
|
15647
|
+
/**
|
|
15648
|
+
* The `model` option for chat runs: the bare primary model id when no
|
|
15649
|
+
* fallbacks are configured (byte-identical to single-model behavior),
|
|
15650
|
+
* else the pinned `{ provider, model }` chain — validated against the
|
|
15651
|
+
* current agent's provider map so a stale chain degrades to fewer
|
|
15652
|
+
* targets instead of a run-time resolution error. Each target carries
|
|
15653
|
+
* its model's context window from the chat catalog so the loop re-bases
|
|
15654
|
+
* the auto-compact trigger when a fallback advances (the run-level
|
|
15655
|
+
* `autoCompact.contextWindow` is sized for the primary).
|
|
15656
|
+
*/
|
|
15657
|
+
const runModelFor = useCallback((p) => {
|
|
15658
|
+
const fallbacks = p.fallbacks ?? [];
|
|
15659
|
+
if (fallbacks.length === 0) return p.model;
|
|
15660
|
+
return buildRunModel([{
|
|
15661
|
+
provider: p.provider.key,
|
|
15662
|
+
model: p.model
|
|
15663
|
+
}, ...fallbacks], (k) => providerNameByKeyRef.current.get(k), (entry) => {
|
|
15664
|
+
const d = providerRegistry[entry.provider];
|
|
15665
|
+
return d ? getContextWindow(d, entry.model) ?? void 0 : void 0;
|
|
15666
|
+
});
|
|
15667
|
+
}, [providerRegistry]);
|
|
15346
15668
|
const buildAgent = useCallback((session, key) => {
|
|
15347
15669
|
const descriptor = providerRegistry[key];
|
|
15348
15670
|
if (!descriptor) throw new Error(`No provider registered for key "${key}"`);
|
|
15671
|
+
const primaryProvider = descriptor.factory();
|
|
15672
|
+
const nameByKey = /* @__PURE__ */ new Map([[key, primaryProvider.name]]);
|
|
15673
|
+
const extraProviders = [];
|
|
15674
|
+
const seenNames = /* @__PURE__ */ new Set([primaryProvider.name]);
|
|
15675
|
+
for (const entry of pickedRef.current?.fallbacks ?? []) {
|
|
15676
|
+
if (nameByKey.has(entry.provider)) continue;
|
|
15677
|
+
const d = providerRegistry[entry.provider];
|
|
15678
|
+
if (!d) {
|
|
15679
|
+
debugLog("buildAgent: fallback provider not registered, skipping", entry.provider);
|
|
15680
|
+
continue;
|
|
15681
|
+
}
|
|
15682
|
+
let instance;
|
|
15683
|
+
try {
|
|
15684
|
+
instance = d.factory();
|
|
15685
|
+
} catch (err) {
|
|
15686
|
+
debugLog(`buildAgent: fallback provider "${entry.provider}" factory threw, skipping`, err);
|
|
15687
|
+
continue;
|
|
15688
|
+
}
|
|
15689
|
+
if (seenNames.has(instance.name)) {
|
|
15690
|
+
debugLog(`buildAgent: duplicate provider name "${instance.name}", skipping fallback provider`, entry.provider);
|
|
15691
|
+
continue;
|
|
15692
|
+
}
|
|
15693
|
+
seenNames.add(instance.name);
|
|
15694
|
+
nameByKey.set(entry.provider, instance.name);
|
|
15695
|
+
extraProviders.push(instance);
|
|
15696
|
+
}
|
|
15697
|
+
providerNameByKeyRef.current = nameByKey;
|
|
15349
15698
|
const profile = pickedAgentRef.current;
|
|
15350
15699
|
const skillsScan = defaultSkillScanPaths({
|
|
15351
15700
|
cwd: projectDir,
|
|
@@ -15356,7 +15705,7 @@ function AppShell() {
|
|
|
15356
15705
|
const userEnabledSkills = enabledSkillsRef.current;
|
|
15357
15706
|
const skillsConfig = buildSkillsConfig({
|
|
15358
15707
|
scan: skillsScan,
|
|
15359
|
-
enabled: userEnabledSkills === void 0 ? void 0 : Array.from(new Set([...userEnabledSkills, ...extensionSkillNames]))
|
|
15708
|
+
enabled: userEnabledSkills === void 0 ? void 0 : Array.from(/* @__PURE__ */ new Set([...userEnabledSkills, ...extensionSkillNames]))
|
|
15360
15709
|
});
|
|
15361
15710
|
const projectMcps = buildMcpServers({
|
|
15362
15711
|
discovered: mcpsCatalogRef.current,
|
|
@@ -15391,6 +15740,20 @@ function AppShell() {
|
|
|
15391
15740
|
sessionId: session.id
|
|
15392
15741
|
});
|
|
15393
15742
|
const composedPreset = composeExtensionPreset(liveExtensionRegistry, profile.preset);
|
|
15743
|
+
const modelCatalogTools = {};
|
|
15744
|
+
if (subagentModelsRef.current) {
|
|
15745
|
+
const catalog = [];
|
|
15746
|
+
for (const [providerKey, providerName] of nameByKey) for (const info of modelsFor(providerKey)) catalog.push({
|
|
15747
|
+
provider: providerName,
|
|
15748
|
+
model: info
|
|
15749
|
+
});
|
|
15750
|
+
const presetSpawn = composedPreset.tools?.spawn;
|
|
15751
|
+
if (catalog.length > 0 && presetSpawn && isSpawnTool(presetSpawn)) {
|
|
15752
|
+
modelCatalogTools.spawn = presetSpawn.withModels(catalog);
|
|
15753
|
+
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");
|
|
15754
|
+
modelCatalogTools.model_search = createModelSearchTool(catalog);
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15394
15757
|
const execution = launch?.context === "docker" ? createDockerContext({
|
|
15395
15758
|
image: launch.image,
|
|
15396
15759
|
cwd: actualCwd
|
|
@@ -15413,7 +15776,8 @@ function AppShell() {
|
|
|
15413
15776
|
tools: {
|
|
15414
15777
|
...interactionTools,
|
|
15415
15778
|
chatTurnsRead: chatTurnsReadTool,
|
|
15416
|
-
...composedPreset.tools ?? {}
|
|
15779
|
+
...composedPreset.tools ?? {},
|
|
15780
|
+
...modelCatalogTools
|
|
15417
15781
|
},
|
|
15418
15782
|
contextBreakdownSections: [...liveExtensionRegistry.contextBreakdownSections, ...liveExtensionRegistry.contextPanelSections],
|
|
15419
15783
|
behavior: {
|
|
@@ -15422,7 +15786,7 @@ function AppShell() {
|
|
|
15422
15786
|
tasksDir
|
|
15423
15787
|
},
|
|
15424
15788
|
execution,
|
|
15425
|
-
provider:
|
|
15789
|
+
provider: extraProviders.length > 0 ? [primaryProvider, ...extraProviders] : primaryProvider,
|
|
15426
15790
|
session,
|
|
15427
15791
|
mcpConnector: (configs) => connectMcpServers(configs, void 0, agent.hooks, { buildAuthProvider: (cfg) => new McpOAuthProvider({
|
|
15428
15792
|
name: cfg.name,
|
|
@@ -15613,6 +15977,16 @@ function AppShell() {
|
|
|
15613
15977
|
turnId
|
|
15614
15978
|
});
|
|
15615
15979
|
});
|
|
15980
|
+
agent.hooks.hook("model:fallback", ({ from, to, err }) => {
|
|
15981
|
+
setActiveFallback({
|
|
15982
|
+
provider: to.provider,
|
|
15983
|
+
model: to.model
|
|
15984
|
+
});
|
|
15985
|
+
stream.appendImmediate({
|
|
15986
|
+
kind: "info",
|
|
15987
|
+
text: `⚠ model fallback: ${from.provider}/${from.model} failed (${errorMessage(err)}) — continuing on ${to.provider}/${to.model}`
|
|
15988
|
+
});
|
|
15989
|
+
});
|
|
15616
15990
|
agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
|
|
15617
15991
|
registerInFlightTool({
|
|
15618
15992
|
callId,
|
|
@@ -15736,6 +16110,10 @@ function AppShell() {
|
|
|
15736
16110
|
}
|
|
15737
16111
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
15738
16112
|
});
|
|
16113
|
+
agent.hooks.hook("child:turn:after", ({ usage }) => {
|
|
16114
|
+
const turnCost = usage.cost ?? 0;
|
|
16115
|
+
if (turnCost > 0) setSessionCost((prev) => prev + turnCost);
|
|
16116
|
+
});
|
|
15739
16117
|
agent.hooks.hook("agent:done", () => {
|
|
15740
16118
|
const reason = lastFinishReasonRef.current;
|
|
15741
16119
|
lastFinishReasonRef.current = void 0;
|
|
@@ -15781,13 +16159,14 @@ function AppShell() {
|
|
|
15781
16159
|
setLastInputTokens(ctx.effectiveTokens);
|
|
15782
16160
|
lastInputTokensRef.current = ctx.effectiveTokens;
|
|
15783
16161
|
});
|
|
15784
|
-
agent.hooks.hook("spawn:before", ({ id, task, depth }) => {
|
|
16162
|
+
agent.hooks.hook("spawn:before", ({ id, task, depth, model }) => {
|
|
15785
16163
|
const taskPreview = previewLine(task, 80);
|
|
15786
16164
|
stream.appendImmediate({
|
|
15787
16165
|
kind: "spawn-start",
|
|
15788
16166
|
text: taskPreview,
|
|
15789
16167
|
childId: id,
|
|
15790
|
-
depth: depth ?? 1
|
|
16168
|
+
depth: depth ?? 1,
|
|
16169
|
+
...model ? { model } : {}
|
|
15791
16170
|
});
|
|
15792
16171
|
});
|
|
15793
16172
|
agent.hooks.hook("spawn:complete", ({ id, depth, status, stats }) => {
|
|
@@ -15893,6 +16272,7 @@ function AppShell() {
|
|
|
15893
16272
|
return agent;
|
|
15894
16273
|
}, [
|
|
15895
16274
|
providerRegistry,
|
|
16275
|
+
modelsFor,
|
|
15896
16276
|
stream,
|
|
15897
16277
|
gateDecision,
|
|
15898
16278
|
cancelRunOnDenial,
|
|
@@ -15908,7 +16288,8 @@ function AppShell() {
|
|
|
15908
16288
|
extensionRegistry
|
|
15909
16289
|
]);
|
|
15910
16290
|
const refreshSessions = useCallback(async () => {
|
|
15911
|
-
const
|
|
16291
|
+
const filter = settings.showAllProjects ? void 0 : { projectRoot: projectDir };
|
|
16292
|
+
const list = await listSessionMeta(store, filter);
|
|
15912
16293
|
setSessions(list);
|
|
15913
16294
|
return list;
|
|
15914
16295
|
}, [
|
|
@@ -16015,10 +16396,11 @@ function AppShell() {
|
|
|
16015
16396
|
setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
16016
16397
|
runningRef.current = true;
|
|
16017
16398
|
setBusy(true);
|
|
16399
|
+
setActiveFallback(null);
|
|
16018
16400
|
try {
|
|
16019
16401
|
const runModelOptions = enabledModelOptions(currentPicked.modelOptions);
|
|
16020
16402
|
await agent.run({
|
|
16021
|
-
model: currentPicked
|
|
16403
|
+
model: runModelFor(currentPicked),
|
|
16022
16404
|
...currentPicked.effort ? { thinking: currentPicked.effort } : {},
|
|
16023
16405
|
...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {},
|
|
16024
16406
|
behavior: {
|
|
@@ -16060,8 +16442,10 @@ function AppShell() {
|
|
|
16060
16442
|
sessionRef.current = session;
|
|
16061
16443
|
agentRef.current = buildAgent(session, key);
|
|
16062
16444
|
if (extensionRegistrySwapKeyRef.current === null || extensionRegistrySwapKeyRef.current === extensionRegistry) pendingAgentRebuildRef.current = false;
|
|
16063
|
-
|
|
16445
|
+
const persistedPins = readPinnedSkills(session.metadata["zidane.activeSkills"]);
|
|
16446
|
+
setActiveSkillNames(persistedPins);
|
|
16064
16447
|
setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
16448
|
+
setActiveFallback(null);
|
|
16065
16449
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
16066
16450
|
setLastInputTokens(replayedTokens);
|
|
16067
16451
|
lastInputTokensRef.current = replayedTokens;
|
|
@@ -16149,7 +16533,8 @@ function AppShell() {
|
|
|
16149
16533
|
]);
|
|
16150
16534
|
const [availableProviders, setAvailableProviders] = useState([]);
|
|
16151
16535
|
const refreshAvailableProviders = useCallback(() => {
|
|
16152
|
-
|
|
16536
|
+
const detected = detectAuth(config.paths.userDir, providerRegistry);
|
|
16537
|
+
setAvailableProviders(detected.filter((p) => p.available));
|
|
16153
16538
|
}, [config.paths.userDir, providerRegistry]);
|
|
16154
16539
|
useEffect(() => {
|
|
16155
16540
|
refreshAvailableProviders();
|
|
@@ -16456,30 +16841,36 @@ function AppShell() {
|
|
|
16456
16841
|
else setQueueSelectionIndex(Math.min(pushIdx, messageQueueRef.current.length - 1));
|
|
16457
16842
|
}, []);
|
|
16458
16843
|
/**
|
|
16459
|
-
*
|
|
16460
|
-
*
|
|
16844
|
+
* Apply a model chain from the cross-provider picker's ordered
|
|
16845
|
+
* multi-select. Entry 0 is the PRIMARY (same role as the old
|
|
16846
|
+
* single-pick: provider swap, effort, options); entries 1+ become
|
|
16847
|
+
* run-level fallbacks (`agent.run({ model: [...] })`).
|
|
16461
16848
|
*
|
|
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.
|
|
16849
|
+
* Persistence mirrors the old single-pick path (`lastProvider` +
|
|
16850
|
+
* `lastModelByProvider` track the primary) plus the additive
|
|
16851
|
+
* `lastModelChain`. Fallback entries are re-validated against the
|
|
16852
|
+
* live auth list; stale entries drop with a debug log.
|
|
16471
16853
|
*
|
|
16472
|
-
*
|
|
16473
|
-
*
|
|
16474
|
-
*
|
|
16854
|
+
* The session re-activates (agent rebuild) when the primary provider
|
|
16855
|
+
* changed OR the chain's provider-key SET changed — the agent must
|
|
16856
|
+
* own an instance for every chain provider. A pure model / order
|
|
16857
|
+
* change within the same providers stays a per-run swap, exactly
|
|
16858
|
+
* like same-provider single picks always have.
|
|
16475
16859
|
*/
|
|
16476
|
-
const
|
|
16477
|
-
const
|
|
16860
|
+
const onApplyModelChain = useCallback(async (chain) => {
|
|
16861
|
+
const primary = chain[0];
|
|
16862
|
+
if (!primary) {
|
|
16863
|
+
modal.close();
|
|
16864
|
+
return;
|
|
16865
|
+
}
|
|
16866
|
+
const nextProvider = availableProviders.find((p) => p.key === primary.provider);
|
|
16478
16867
|
if (!nextProvider) {
|
|
16479
|
-
debugLog("
|
|
16868
|
+
debugLog("onApplyModelChain: unknown provider key", primary.provider);
|
|
16480
16869
|
modal.close();
|
|
16481
16870
|
return;
|
|
16482
16871
|
}
|
|
16872
|
+
const fallbacks = filterChainFallbacks(chain.slice(1), primary, (k) => availableProviders.some((p) => p.key === k) && providerRegistry[k] !== void 0);
|
|
16873
|
+
if (fallbacks.length < chain.length - 1) debugLog("onApplyModelChain: dropped unavailable/duplicate fallback entries", chain.length - 1 - fallbacks.length);
|
|
16483
16874
|
const descriptor = providerRegistry[nextProvider.key];
|
|
16484
16875
|
const prior = stateStore.load();
|
|
16485
16876
|
const providerChanged = picked?.provider.key !== nextProvider.key;
|
|
@@ -16488,19 +16879,34 @@ function AppShell() {
|
|
|
16488
16879
|
...providerChanged ? { lastProvider: nextProvider.key } : {},
|
|
16489
16880
|
lastModelByProvider: {
|
|
16490
16881
|
...prior.lastModelByProvider,
|
|
16491
|
-
[nextProvider.key]:
|
|
16492
|
-
}
|
|
16882
|
+
[nextProvider.key]: primary.model
|
|
16883
|
+
},
|
|
16884
|
+
lastModelChain: [{
|
|
16885
|
+
provider: primary.provider,
|
|
16886
|
+
model: primary.model
|
|
16887
|
+
}, ...fallbacks]
|
|
16493
16888
|
});
|
|
16494
|
-
const nextEffort = descriptor ? effortForModel(descriptor,
|
|
16495
|
-
const nextOptions = descriptor ? restoreModelOptions(descriptor,
|
|
16496
|
-
|
|
16889
|
+
const nextEffort = descriptor ? effortForModel(descriptor, primary.model, prior.lastEffortByModel) : void 0;
|
|
16890
|
+
const nextOptions = descriptor ? restoreModelOptions(descriptor, primary.model, prior.lastModelOptionsByModel) : void 0;
|
|
16891
|
+
const updated = {
|
|
16497
16892
|
provider: nextProvider,
|
|
16498
|
-
model:
|
|
16893
|
+
model: primary.model,
|
|
16894
|
+
...fallbacks.length > 0 ? { fallbacks } : {},
|
|
16499
16895
|
...nextEffort ? { effort: nextEffort } : {},
|
|
16500
16896
|
...nextOptions ? { modelOptions: nextOptions } : {}
|
|
16501
|
-
}
|
|
16897
|
+
};
|
|
16898
|
+
const prevChain = picked ? [{
|
|
16899
|
+
provider: picked.provider.key,
|
|
16900
|
+
model: picked.model
|
|
16901
|
+
}, ...picked.fallbacks ?? []] : [];
|
|
16902
|
+
setPicked(updated);
|
|
16903
|
+
setActiveFallback(null);
|
|
16904
|
+
pickedRef.current = updated;
|
|
16502
16905
|
modal.close();
|
|
16503
|
-
if (providerChanged
|
|
16906
|
+
if ((providerChanged || providerKeySetChanged(prevChain, [{
|
|
16907
|
+
provider: primary.provider,
|
|
16908
|
+
model: primary.model
|
|
16909
|
+
}, ...fallbacks])) && currentSession && !busy && !pendingApproval) await activateSession(currentSession.id, nextProvider.key);
|
|
16504
16910
|
}, [
|
|
16505
16911
|
availableProviders,
|
|
16506
16912
|
providerRegistry,
|
|
@@ -16535,7 +16941,8 @@ function AppShell() {
|
|
|
16535
16941
|
modelsFor,
|
|
16536
16942
|
current,
|
|
16537
16943
|
onPick: (next) => {
|
|
16538
|
-
|
|
16944
|
+
const isMain = next.providerKey === picked.provider.key && next.modelId === picked.model;
|
|
16945
|
+
setSetting("compactionModel", isMain ? "" : `${next.providerKey}/${next.modelId}`);
|
|
16539
16946
|
modal.close();
|
|
16540
16947
|
}
|
|
16541
16948
|
}));
|
|
@@ -16676,7 +17083,7 @@ function AppShell() {
|
|
|
16676
17083
|
});
|
|
16677
17084
|
}
|
|
16678
17085
|
const newRefs = uniqueSkillNamesFromReferences(references);
|
|
16679
|
-
const skillNames = Array.from(new Set([...activeSkillNamesRef.current, ...newRefs]));
|
|
17086
|
+
const skillNames = Array.from(/* @__PURE__ */ new Set([...activeSkillNamesRef.current, ...newRefs]));
|
|
16680
17087
|
for (const name of skillNames) try {
|
|
16681
17088
|
await withTimeout(agent.activateSkill(name), {
|
|
16682
17089
|
operation: `activateSkill(${name})`,
|
|
@@ -16713,8 +17120,9 @@ function AppShell() {
|
|
|
16713
17120
|
};
|
|
16714
17121
|
})];
|
|
16715
17122
|
const runModelOptions = enabledModelOptions(picked.modelOptions);
|
|
17123
|
+
setActiveFallback(null);
|
|
16716
17124
|
await agent.run({
|
|
16717
|
-
model: picked
|
|
17125
|
+
model: runModelFor(picked),
|
|
16718
17126
|
prompt: runPrompt,
|
|
16719
17127
|
...picked.effort ? { thinking: picked.effort } : {},
|
|
16720
17128
|
...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {},
|
|
@@ -16848,8 +17256,9 @@ function AppShell() {
|
|
|
16848
17256
|
const before = session.turns.length;
|
|
16849
17257
|
const beforeRuns = session.runs.length;
|
|
16850
17258
|
const runModelOptions = enabledModelOptions(picked.modelOptions);
|
|
17259
|
+
setActiveFallback(null);
|
|
16851
17260
|
await agent.run({
|
|
16852
|
-
model: picked
|
|
17261
|
+
model: runModelFor(picked),
|
|
16853
17262
|
prompt: agentPrompt,
|
|
16854
17263
|
...picked.effort ? { thinking: picked.effort } : {},
|
|
16855
17264
|
...Object.keys(runModelOptions).length > 0 ? { modelOptions: runModelOptions } : {},
|
|
@@ -17256,7 +17665,8 @@ function AppShell() {
|
|
|
17256
17665
|
if (!session || !p) return;
|
|
17257
17666
|
const stale = agentRef.current;
|
|
17258
17667
|
const staleWarnLog = warnLogRef.current;
|
|
17259
|
-
|
|
17668
|
+
const fresh = buildAgentRef.current(session, p.provider.key);
|
|
17669
|
+
agentRef.current = fresh;
|
|
17260
17670
|
if (stale) await stale.destroy().catch((err) => debugLog("rebuildActiveAgent: destroy failed", err));
|
|
17261
17671
|
if (staleWarnLog) await staleWarnLog.close().catch((err) => debugLog("rebuildActiveAgent: warnLog.close failed", err));
|
|
17262
17672
|
}, []);
|
|
@@ -17353,7 +17763,8 @@ function AppShell() {
|
|
|
17353
17763
|
]);
|
|
17354
17764
|
const onLogoutMcp = useCallback((name) => {
|
|
17355
17765
|
mcpCredentialStore.delete(name);
|
|
17356
|
-
|
|
17766
|
+
const entry = mcpsCatalogRef.current.find((d) => d.config.name === name);
|
|
17767
|
+
dispatchAuth(entry?.config.auth === "oauth" ? {
|
|
17357
17768
|
type: "auth-required",
|
|
17358
17769
|
name,
|
|
17359
17770
|
reason: "no-tokens"
|
|
@@ -17415,7 +17826,8 @@ function AppShell() {
|
|
|
17415
17826
|
const idx = turnIds.indexOf(prev);
|
|
17416
17827
|
if (idx === -1) return turnIds[turnIds.length - 1];
|
|
17417
17828
|
const len = turnIds.length;
|
|
17418
|
-
|
|
17829
|
+
const nextIdx = ((idx + direction) % len + len) % len;
|
|
17830
|
+
return turnIds[nextIdx];
|
|
17419
17831
|
});
|
|
17420
17832
|
}, [turnIds]);
|
|
17421
17833
|
const onForkTurn = useCallback(async (turnId) => {
|
|
@@ -17929,7 +18341,11 @@ function AppShell() {
|
|
|
17929
18341
|
providerKey: picked.provider.key,
|
|
17930
18342
|
modelId: picked.model
|
|
17931
18343
|
},
|
|
17932
|
-
|
|
18344
|
+
chain: [{
|
|
18345
|
+
provider: picked.provider.key,
|
|
18346
|
+
model: picked.model
|
|
18347
|
+
}, ...picked.fallbacks ?? []],
|
|
18348
|
+
onApplyChain: onApplyModelChain
|
|
17933
18349
|
}));
|
|
17934
18350
|
return;
|
|
17935
18351
|
}
|
|
@@ -17964,7 +18380,13 @@ function AppShell() {
|
|
|
17964
18380
|
autocompactBuffer: OUTPUT_RESERVE_TOKENS,
|
|
17965
18381
|
...settings.autoCompact && settings.autoCompactThreshold ? { compactThreshold: settings.autoCompactThreshold } : {}
|
|
17966
18382
|
}).then((breakdown) => {
|
|
17967
|
-
if (breakdown && agentRef.current === agent)
|
|
18383
|
+
if (breakdown && agentRef.current === agent) {
|
|
18384
|
+
const modelUsage = usageByModel(sessionRef.current?.runs ?? []);
|
|
18385
|
+
modal.open(/* @__PURE__ */ jsx$1(ContextPanelModal, {
|
|
18386
|
+
breakdown,
|
|
18387
|
+
modelUsage
|
|
18388
|
+
}));
|
|
18389
|
+
}
|
|
17968
18390
|
}).catch((err) => debugLog("getContextBreakdown failed", err));
|
|
17969
18391
|
return;
|
|
17970
18392
|
}
|
|
@@ -18135,8 +18557,8 @@ function AppShell() {
|
|
|
18135
18557
|
currentSession,
|
|
18136
18558
|
hasMultipleAgents,
|
|
18137
18559
|
uiMode: settings.uiMode,
|
|
18138
|
-
modelLabel: picked
|
|
18139
|
-
modelColor: COLOR.model,
|
|
18560
|
+
modelLabel: activeFallback ? activeFallback.model : picked ? `${picked.model}${picked.fallbacks && picked.fallbacks.length > 0 ? ` +${picked.fallbacks.length}` : ""}` : null,
|
|
18561
|
+
modelColor: activeFallback ? COLOR.warn : COLOR.model,
|
|
18140
18562
|
effortLabel: modelHasReasoning ? picked?.effort ?? "medium" : null,
|
|
18141
18563
|
effortColor: COLOR.warn,
|
|
18142
18564
|
modelOptionsLabel: (() => {
|