zidane 6.6.1 → 6.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-EFzMJPxa.js → acp-BQqhozmt.js} +2 -2
- package/dist/{acp-EFzMJPxa.js.map → acp-BQqhozmt.js.map} +1 -1
- package/dist/acp-cli.js +3 -3
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-ut6nQRnI.js → agent-DTxH7RHW.js} +40 -7
- package/dist/agent-DTxH7RHW.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{auth-BaLBiz0r.js → auth-7rv6ZfWk.js} +2 -2
- package/dist/{auth-BaLBiz0r.js.map → auth-7rv6ZfWk.js.map} +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +1 -1
- package/dist/chat.d.ts +88 -5
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +5 -5
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-BGiCne_W.d.ts → index-D0O-QO7Z.d.ts} +60 -1
- package/dist/index-D0O-QO7Z.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins.d.ts +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/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/restate.d.ts +1 -1
- package/dist/restate.js +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/{tool-formatters-5zq0YCK0.d.ts → tool-formatters-NrzD6Fkk.d.ts} +20 -3
- package/dist/{tool-formatters-5zq0YCK0.d.ts.map → tool-formatters-NrzD6Fkk.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-BfV8T2us.js → transcript-anchors-BjH-68k2.js} +654 -35
- package/dist/transcript-anchors-BjH-68k2.js.map +1 -0
- package/dist/{transcript-anchors-BaAzmW1d.d.ts → transcript-anchors-UhC58Tmj.d.ts} +3 -3
- package/dist/transcript-anchors-UhC58Tmj.d.ts.map +1 -0
- package/dist/tui.d.ts +33 -5
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +859 -191
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-DXiTV38z.d.ts → turn-operations-Cuc9pwiB.d.ts} +3 -3
- package/dist/{turn-operations-DXiTV38z.d.ts.map → turn-operations-Cuc9pwiB.d.ts.map} +1 -1
- package/dist/{turn-operations-7_Ff7INF.js → turn-operations-mMxDRgLY.js} +65 -3
- package/dist/{turn-operations-7_Ff7INF.js.map → turn-operations-mMxDRgLY.js.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CHAT.md +30 -5
- package/docs/TUI.md +22 -2
- package/package.json +1 -1
- package/dist/agent-ut6nQRnI.js.map +0 -1
- package/dist/index-BGiCne_W.d.ts.map +0 -1
- package/dist/transcript-anchors-BaAzmW1d.d.ts.map +0 -1
- package/dist/transcript-anchors-BfV8T2us.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as estimateRebilledCostUsd } from "./openai-compat-BoTi-9fd.js";
|
|
2
2
|
import { t as buildContextSegments } from "./context-segments-CJCtJ9sU.js";
|
|
3
|
-
import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession,
|
|
3
|
+
import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession, C as resolveLegacySessionDir, Cn as coerceExtensionConfigInput, D as resolveTasksDir, Dn as resolveExtensionConfigValue, E as resolveSessionDir, Et as isSpawnTool, G as summaryToTurn, It as looksBinary, M as compactConversation, Mn as effectiveContextWindow, O as buildPostCompactAttachments, On as sanitizeExtensionConfigActionPatch, Pt as tailTruncate, Qt as formatBindingForDisplay, Rt as createModelSearchTool, S as resolveLegacyPersistDir, T as resolvePersistDir, X as createExtensionPromptBridge, Xt as KEYBINDING_KEY_COL_WIDTH, Z as ExtensionInstallError, Zt as ensureKeybindingsFile, _n as parseExtensionScanPathEnv, at as createExtensionHud, bn as setDebugLogSink, bt as basic_default, dn as registerExtensionHostModules, dt as filterExtensionRegistry, en as keybindingsPath, f as applyCompactSummaryCutoff, ft as filterRegistryPlugins, hn as inlineLoadedExtension, ht as createExtensionHostRuntimeBridge, it as updateInstalledExtension, jn as OUTPUT_RESERVE_TOKENS, lt as composeExtensionPreset, mt as partitionExtensionCatalog, on as resolveExtensionKeybindings, ot as discoverAndBuildExtensionRegistry, rt as removeInstalledExtension, t as createAgent, tn as matchesBinding, ut as disposeExtensionRegistry, v as cleanupPersistedSession, vn as scanExtensionDirectories, w as resolveMcpWarningsDir, x as rehomePersistedStubs, yn as createDebugLog } from "./agent-DTxH7RHW.js";
|
|
4
4
|
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";
|
|
5
5
|
import { n as appendCapped } from "./output-cap-C9zukcCj.js";
|
|
6
6
|
import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-BSFQVclW.js";
|
|
@@ -18,12 +18,12 @@ import { i as installCacheBreakLogger } from "./cache-telemetry-ehVdWsg2.js";
|
|
|
18
18
|
import { a as generateStructuredData, c as shouldAutoGenerateTitle, f as commandBlockToEvent, g as parseCommandInvocation, h as executeCommand, n as patchInlineGhost, p as commandBlocksToContent, r as resolveInlineSuggestion, s as generateSessionTitle, t as createPathAutocompleteProvider } from "./inline-autocomplete-D0MPvBaS.js";
|
|
19
19
|
import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
|
|
20
20
|
import { a as ZIDANE_EXTENSION_SKILL_NAME, d as extensionScaffold, f as readSkillEditMarker, h as withZidaneSkillEditSkill, i as SKILL_EDIT_METADATA_KEY, l as editSessionMetadata, m as withZidaneExtensionSkill, o as ZIDANE_SKILL_EDIT_SKILL_NAME, t as loginMcpServer, u as extensionDirForCwd } from "./login-CB23LyI1.js";
|
|
21
|
-
import { E as renderDefaultCallbackPage, T as restoreModelOptions, b as modelSupportsReasoning, c as setPreferredCredential, g as getContextWindow, h as enabledModelOptions, l as setProviderCredential, n as applyApiKeyEnv, t as detectAuth, w as piIdOf, y as modelOptionsFor } from "./auth-
|
|
21
|
+
import { E as renderDefaultCallbackPage, T as restoreModelOptions, b as modelSupportsReasoning, c as setPreferredCredential, g as getContextWindow, h as enabledModelOptions, l as setProviderCredential, n as applyApiKeyEnv, t as detectAuth, w as piIdOf, y as modelOptionsFor } from "./auth-7rv6ZfWk.js";
|
|
22
22
|
import { createDockerContext } from "./contexts/docker.js";
|
|
23
23
|
import { createTuiStore } from "./session/sqlite.js";
|
|
24
|
-
import {
|
|
24
|
+
import { B as BASH_CALL_ID_PREFIX, D as normalizeMarkdownCodeFences, E as codeTokenHasClosingFence, G as isVisible, H as isEditErrorResult, J as retagUserPromptEvents, K as isVisibleDuringSearch, L as createCommandsCompletionProvider, M as uniqueSkillNamesFromReferences, O as splitMarkdownCodeBlocks, P as createFilesCompletionProvider, Q as turnSelectionOwnership, R as blendHsl, S as parseCustomModelQuery, T as closeDanglingCodeFence, U as isTurnHighlighted, V as EDIT_TOOL_NAMES, W as isUserBashEvent, X as selectionAfterTurnRemoval, Y as selectableTurnIds, Z as streamEventSearchText, _ as turnContextSize, _t as stripEditOutcomesAnnotation, a as TOOL_DISPLAY, at as extractEditPayload, b as filterModelCatalog, d as finalizeStreamingMarkdown, dt as buildEditOutcomesAnnotation, et as buildContextualDiff, f as finalizeStreamingMarkdownForOwner, g as revealEarlierTranscriptEvents, gt as rewriteMultiEditHeader, h as replaceTranscriptTurnEvents, ht as resolveApprovalForPayload, i as turnAsText, j as createSkillsCompletionProvider, k as stripHtmlCommentsOutsideCode, lt as summarizeEditPayload, mt as parseEditOutcomesFromResult, n as deleteTurnSafely, nt as clipDiffForRender, o as displayNameFor, ot as filetypeFromPath, p as hiddenEarlierTranscriptCount, pt as mergeApprovalAndBodyOutcomes, q as promptHistoryFromEvents, r as truncateTurnsAt, s as formatToolCall, st as previewEditPayload, tt as buildUnifiedDiff, u as capTranscriptEvents, v as splitPromptSegments, vt as summarizeOutcomes, w as chunkMarkdownForRender, xt as createChatsCompletionProvider, y as buildModelCatalog, z as buildLinearRamp } from "./turn-operations-mMxDRgLY.js";
|
|
25
25
|
import { a as searchHighlightRanges, c as searchSessionHistoryIndexAsync, i as highlightSegments, n as buildSessionSearchIndexAsync, r as filterSessionSearchCatalog, u as searchSessionIndexAsync } from "./history-search-C4bk1Wz_.js";
|
|
26
|
-
import {
|
|
26
|
+
import { At as hintsLength, B as formatPathForCwd, Bt as SETTINGS_TOGGLES, C as addToSafelist, Cn as upsertMcpServer, Ct as makeRequestInteraction, Dn as discoverProjectMcps, Dr as envSection, Dt as useInteractionsQueue, Er as buildPlanSystem, Et as useInteractionsActions, F as formatUsagePercent, Fr as lastContextSizeFromTurns, Ft as useEnabledToggleSet, G as runOAuthLogin, Gr as toolCallPreview, Hn as chatAutoCompactBehavior, Hr as stripSpawnTokensLine, Ht as clampFps, I as mostConstrainedWindow, Ir as listSessionMeta, It as DEFAULT_SETTINGS, J as loadMcpToolsCache, Jr as usageByModel, K as supportsOAuth, Kr as toolResultText, Kt as resolveChipColor, L as providerUsageSupported, Lr as loadState, Lt as SETTINGS_CATEGORIES, M as clearProviderUsageCache, Mn as buildUpdateHint, Mt as buildHints, N as fetchAllProviderUsage, Nn as useUpdateCheck, Nr as deriveSessionTitle, Or as chatReferenceTokensForPrompt, Ot as EMPTY_HINTS, P as fetchProviderUsage, Pr as eventsFromTurns, Pt as listProjectFiles, Q as subscribeMcpToolsCache, R as usageChecksOptedOut, Rr as marginTopFor, Rt as SETTINGS_CHOICES, Sn as removeMcpServer, St as isModelInteraction, T as isOnSafelist, Tn as dedupMcpServersByName, Tr as buildBuildSystem, U as redirectUriFromAuthUrl, Un as discoverAgentsMd, Ur as sumRunCosts, Ut as useSettings, V as fetchOAuthRedirect, Vr as serverToolResultSummary, Vt as SettingsProvider, W as oauthUsesManualCodePaste, X as refreshMcpToolsCatalog, Y as mcpToolsCachePath, Zn as accentColor, _n as useCompletion, _t as PRESENT_PLAN_TOOL, a as useSurfaces, an as ConfigProvider, at as createFileMcpCredentialStore, b as useSafeModeActions, bn as importFromClaudeCode, bt as createInteractionTools, c as discoverSystemMd, cn as resolveStoragePaths, ct as McpAuthProvider, d as userSystemMdPath, dn as chainsEqual, dt as getMcpAuthStatus, et as useMcpToolToggleMap, f as useStreamBuffer, fn as filterChainFallbacks, fr as setTodosForRun, gn as toggleChainEntry, gt as InteractionsProvider, h as discoverProjectSkills, ht as HOST_INTERACTION_ID_PREFIX, i as useSelectStyle, in as useDiscoveryOptional, it as parentServerName, jn as bootTick, jr as compactSummaryEvent, jt as truncateTrailing, k as suggestSafelistEntries, kn as tryOpenBrowser, kr as createChatTurnsReadTool, kt as clipHintsToWidth, l as renderSystemMdSeed, ln as buildRunModel, lt as useMcpAuthDispatch, m as defaultSkillScanPaths, mn as restoreChainFallbacks, mr as useActiveTodos, n as ThemeProvider, nn as DiscoveryProvider, nt as buildVisibleMcpRows, on as useConfig, p as buildSkillsConfig, pn as providerKeySetChanged, pr as todoProgressLabel, pt as ASK_USER_TOOL, qr as updateToolEventOutcomes, qt as resolveTheme, r as useColors, rn as useDiscovery, rr as TODO_STATUS_GLYPHS, rt as indexOfServerRow, s as useTheme, sn as resolveConfig, t as computeTurnAnchors, tn as createDiscoverySlot, u as userScopeOverridesLoad, un as chainPositionOf, ur as pruneTodosByRun, ut as useMcpAuthState, v as writeSessionExport, vn as countClaudeSessionFiles, vt as buildResumedToolResultsTurn, w as getSafelist, wn as buildMcpServers, wt as pendingInteractionsFromTurns, x as useSafeModeQueue, xn as mcpsWritePath, y as SafeModeProvider, yn as hasClaudeCodeSetup, yt as createAutomaticInteractionResponse, zr as noticeTurnContent, zt as SETTINGS_INPUTS } from "./transcript-anchors-BjH-68k2.js";
|
|
27
27
|
import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
|
|
28
28
|
import { Buffer } from "node:buffer";
|
|
29
29
|
import * as fs from "node:fs";
|
|
@@ -2037,7 +2037,7 @@ function onInputSubmit(handler) {
|
|
|
2037
2037
|
* Width tiering is driven by plain-text length estimates — close enough
|
|
2038
2038
|
* since the segments are ASCII-heavy.
|
|
2039
2039
|
*/
|
|
2040
|
-
function Footer({ hints, context, cost = null, status = null, compactionTokens = null, compactionStartedAt = null, retry = null, contextKey = null }) {
|
|
2040
|
+
function Footer({ hints, context, cost = null, status = null, compactionTokens = null, compactionStartedAt = null, retry = null, contextKey = null, usage = null, usageKey = null }) {
|
|
2041
2041
|
const { width } = useTerminalDimensions();
|
|
2042
2042
|
const SURFACE = useSurfaces();
|
|
2043
2043
|
const showCost = typeof cost === "number" && cost > 0;
|
|
@@ -2045,7 +2045,9 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
|
|
|
2045
2045
|
const hW = hintsLength(hints);
|
|
2046
2046
|
const ctxW = context ? contextIndicatorLength(context) : 0;
|
|
2047
2047
|
const costW = showCost ? costIndicatorLength(cost) : 0;
|
|
2048
|
-
const
|
|
2048
|
+
const usageW = usage ? usageIndicatorLength(usage) : 0;
|
|
2049
|
+
const costCtxW = costW + ctxW + (costW > 0 && ctxW > 0 ? 3 : 0);
|
|
2050
|
+
const rightW = usageW + costCtxW + (usageW > 0 && costCtxW > 0 ? 3 : 0);
|
|
2049
2051
|
const [, setCompactTick] = useState(0);
|
|
2050
2052
|
useEffect(() => {
|
|
2051
2053
|
if (status !== "compacting") return;
|
|
@@ -2072,7 +2074,9 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
|
|
|
2072
2074
|
/* @__PURE__ */ jsx$1(RightStatus, {
|
|
2073
2075
|
cost: showCost ? cost : null,
|
|
2074
2076
|
context,
|
|
2075
|
-
contextKey: contextKey ?? null
|
|
2077
|
+
contextKey: contextKey ?? null,
|
|
2078
|
+
usage,
|
|
2079
|
+
usageKey: usageKey ?? null
|
|
2076
2080
|
})
|
|
2077
2081
|
]
|
|
2078
2082
|
});
|
|
@@ -2101,41 +2105,77 @@ function Footer({ hints, context, cost = null, status = null, compactionTokens =
|
|
|
2101
2105
|
children: [/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }), /* @__PURE__ */ jsx$1(RightStatus, {
|
|
2102
2106
|
cost: showCost ? cost : null,
|
|
2103
2107
|
context,
|
|
2104
|
-
contextKey: contextKey ?? null
|
|
2108
|
+
contextKey: contextKey ?? null,
|
|
2109
|
+
usage,
|
|
2110
|
+
usageKey: usageKey ?? null
|
|
2105
2111
|
})]
|
|
2106
2112
|
})]
|
|
2107
2113
|
});
|
|
2108
2114
|
}
|
|
2109
|
-
function RightStatus({ cost, context, contextKey }) {
|
|
2115
|
+
function RightStatus({ cost, context, contextKey, usage = null, usageKey = null }) {
|
|
2110
2116
|
const COLOR = useColors();
|
|
2117
|
+
const showContextGroup = cost != null || context !== null;
|
|
2118
|
+
if (!usage && !showContextGroup) return null;
|
|
2119
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
2120
|
+
style: {
|
|
2121
|
+
flexDirection: "row",
|
|
2122
|
+
height: 1
|
|
2123
|
+
},
|
|
2124
|
+
children: [
|
|
2125
|
+
usage && /* @__PURE__ */ jsx$1(ClickableStatus, {
|
|
2126
|
+
keySpec: usageKey,
|
|
2127
|
+
render: (accent) => /* @__PURE__ */ jsx$1(UsageIndicator, {
|
|
2128
|
+
usage,
|
|
2129
|
+
accent
|
|
2130
|
+
})
|
|
2131
|
+
}),
|
|
2132
|
+
usage && showContextGroup && /* @__PURE__ */ jsx$1("text", { children: /* @__PURE__ */ jsx$1("span", {
|
|
2133
|
+
fg: COLOR.mute,
|
|
2134
|
+
children: " · "
|
|
2135
|
+
}) }),
|
|
2136
|
+
showContextGroup && /* @__PURE__ */ jsx$1(ClickableStatus, {
|
|
2137
|
+
keySpec: contextKey,
|
|
2138
|
+
render: (accent) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2139
|
+
cost != null && /* @__PURE__ */ jsx$1(CostIndicator, {
|
|
2140
|
+
cost,
|
|
2141
|
+
accent
|
|
2142
|
+
}),
|
|
2143
|
+
cost != null && context && /* @__PURE__ */ jsx$1("span", {
|
|
2144
|
+
fg: COLOR.mute,
|
|
2145
|
+
children: " · "
|
|
2146
|
+
}),
|
|
2147
|
+
context && /* @__PURE__ */ jsx$1(ContextIndicator, {
|
|
2148
|
+
context,
|
|
2149
|
+
accent
|
|
2150
|
+
})
|
|
2151
|
+
] })
|
|
2152
|
+
})
|
|
2153
|
+
]
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* One right-edge status segment. Click == the advertised keypress (see
|
|
2158
|
+
* `clickable.tsx`): the panel opens through the exact keyboard handler, so its
|
|
2159
|
+
* gating (chat screen only, agent picked) can't be bypassed by the mouse. A
|
|
2160
|
+
* missing or unparseable spec degrades to inert text.
|
|
2161
|
+
*
|
|
2162
|
+
* Takes `render(accent)` rather than children so the hover highlight reaches
|
|
2163
|
+
* the nested spans without each indicator subscribing to mouse events itself.
|
|
2164
|
+
*/
|
|
2165
|
+
function ClickableStatus({ keySpec, render }) {
|
|
2111
2166
|
const renderer = useRenderer();
|
|
2112
2167
|
const [hovered, setHovered] = useState(false);
|
|
2113
|
-
if (
|
|
2114
|
-
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2115
|
-
cost != null && /* @__PURE__ */ jsx$1(CostIndicator, {
|
|
2116
|
-
cost,
|
|
2117
|
-
accent: hovered
|
|
2118
|
-
}),
|
|
2119
|
-
cost != null && context && /* @__PURE__ */ jsx$1("span", {
|
|
2120
|
-
fg: COLOR.mute,
|
|
2121
|
-
children: " · "
|
|
2122
|
-
}),
|
|
2123
|
-
context && /* @__PURE__ */ jsx$1(ContextIndicator, {
|
|
2124
|
-
context,
|
|
2125
|
-
accent: hovered
|
|
2126
|
-
})
|
|
2127
|
-
] });
|
|
2128
|
-
if (!(contextKey !== null && parseHintKeySpec(contextKey) !== null)) return /* @__PURE__ */ jsx$1("text", { children: body });
|
|
2168
|
+
if (!(keySpec !== null && parseHintKeySpec(keySpec) !== null)) return /* @__PURE__ */ jsx$1("text", { children: render(false) });
|
|
2129
2169
|
return /* @__PURE__ */ jsx$1("text", {
|
|
2130
2170
|
selectable: false,
|
|
2131
2171
|
onMouseDown: (event) => {
|
|
2132
2172
|
event.stopPropagation();
|
|
2133
2173
|
event.preventDefault();
|
|
2134
|
-
dispatchHintKey(renderer,
|
|
2174
|
+
dispatchHintKey(renderer, keySpec);
|
|
2135
2175
|
},
|
|
2136
2176
|
onMouseOver: () => setHovered(true),
|
|
2137
2177
|
onMouseOut: () => setHovered(false),
|
|
2138
|
-
children:
|
|
2178
|
+
children: render(hovered)
|
|
2139
2179
|
});
|
|
2140
2180
|
}
|
|
2141
2181
|
/**
|
|
@@ -2243,6 +2283,31 @@ function ContextIndicator({ context, accent = false }) {
|
|
|
2243
2283
|
})
|
|
2244
2284
|
] });
|
|
2245
2285
|
}
|
|
2286
|
+
/** Gauge marker for the footer's usage segment. */
|
|
2287
|
+
const USAGE_GLYPH = "▰";
|
|
2288
|
+
/**
|
|
2289
|
+
* `▰ 5h 43%`: the active provider's tightest usage window. The ▰ marker is the
|
|
2290
|
+
* same glyph the panel's bars and the footer spinner use, so it reads as a
|
|
2291
|
+
* gauge without spending cells on one.
|
|
2292
|
+
*/
|
|
2293
|
+
function UsageIndicator({ usage, accent = false }) {
|
|
2294
|
+
const COLOR = useColors();
|
|
2295
|
+
const color = accent ? COLOR.accent : usage.usedPercent >= 85 ? COLOR.error : usage.usedPercent >= 60 ? COLOR.warn : COLOR.dim;
|
|
2296
|
+
return /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
|
|
2297
|
+
fg: accent ? COLOR.accent : COLOR.mute,
|
|
2298
|
+
children: `${USAGE_GLYPH} ${usage.label} `
|
|
2299
|
+
}), /* @__PURE__ */ jsx$1("span", {
|
|
2300
|
+
fg: color,
|
|
2301
|
+
children: `${footerUsagePercent(usage.usedPercent)}%`
|
|
2302
|
+
})] });
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Whole percent for the status bar, floored so it never rounds 99.9 up to a
|
|
2306
|
+
* ceiling the user hasn't hit. The panel keeps the decimal.
|
|
2307
|
+
*/
|
|
2308
|
+
function footerUsagePercent(usedPercent) {
|
|
2309
|
+
return String(Math.floor(usedPercent));
|
|
2310
|
+
}
|
|
2246
2311
|
function CostIndicator({ cost, accent = false }) {
|
|
2247
2312
|
const COLOR = useColors();
|
|
2248
2313
|
const fg = accent ? COLOR.accent : COLOR.money ?? COLOR.warn;
|
|
@@ -2448,6 +2513,9 @@ function formatCost(cost) {
|
|
|
2448
2513
|
function costIndicatorLength(cost) {
|
|
2449
2514
|
return 1 + formatCost(cost).length;
|
|
2450
2515
|
}
|
|
2516
|
+
function usageIndicatorLength(usage) {
|
|
2517
|
+
return 2 + usage.label.length + 1 + footerUsagePercent(usage.usedPercent).length + 1;
|
|
2518
|
+
}
|
|
2451
2519
|
const SPINNER_FRAMES = [
|
|
2452
2520
|
"▰▱▱▱▱",
|
|
2453
2521
|
"▰▰▱▱▱",
|
|
@@ -3230,6 +3298,22 @@ function EmptyState() {
|
|
|
3230
3298
|
/** Left-pad applied per depth level (in columns). */
|
|
3231
3299
|
const INDENT_PER_DEPTH = 2;
|
|
3232
3300
|
const THINKING_GUTTER_WIDTH = 4;
|
|
3301
|
+
/**
|
|
3302
|
+
* Notice rows: `<glyph> <label padded> <message>`. The label column is padded
|
|
3303
|
+
* so a stack of notices lines up down the transcript; 15 fits the longest
|
|
3304
|
+
* label in use (`auto-compaction`) without pushing short ones far from their
|
|
3305
|
+
* text.
|
|
3306
|
+
*
|
|
3307
|
+
* Single-width glyphs only. Emoji (the `🔌` / `🗜` / `🩹` these rows replaced)
|
|
3308
|
+
* render two cells wide in most terminals and one in some, which is exactly
|
|
3309
|
+
* the ambiguity an aligned gutter cannot absorb.
|
|
3310
|
+
*/
|
|
3311
|
+
const NOTICE_LABEL_WIDTH = 15;
|
|
3312
|
+
const NOTICE_GLYPH = {
|
|
3313
|
+
info: "·",
|
|
3314
|
+
warn: "!",
|
|
3315
|
+
error: "✗"
|
|
3316
|
+
};
|
|
3233
3317
|
function indentFor(depth) {
|
|
3234
3318
|
return depth && depth > 0 ? depth * INDENT_PER_DEPTH : 0;
|
|
3235
3319
|
}
|
|
@@ -3253,6 +3337,46 @@ function rowStyle(paddingLeft) {
|
|
|
3253
3337
|
alignSelf: "stretch"
|
|
3254
3338
|
};
|
|
3255
3339
|
}
|
|
3340
|
+
/**
|
|
3341
|
+
* One-line row for a harness event: model fallback, repeat-guard trip, history
|
|
3342
|
+
* repair, run error. Glyph and label carry the severity color, the message is
|
|
3343
|
+
* dim, and the whole row is clipped rather than wrapped so a long provider
|
|
3344
|
+
* error can't push a wall of text through the transcript.
|
|
3345
|
+
*
|
|
3346
|
+
* Multi-line messages collapse to their first non-empty line plus a `+N lines`
|
|
3347
|
+
* counter. The full text is what gets persisted, so nothing is lost.
|
|
3348
|
+
*/
|
|
3349
|
+
function NoticeRow({ row, notice, text }) {
|
|
3350
|
+
const COLOR = useColors();
|
|
3351
|
+
const accent = notice.severity === "error" ? COLOR.error : notice.severity === "warn" ? COLOR.warn : COLOR.mute;
|
|
3352
|
+
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
3353
|
+
const head = lines[0] ?? "";
|
|
3354
|
+
const extra = lines.length - 1;
|
|
3355
|
+
return /* @__PURE__ */ jsx$1("box", {
|
|
3356
|
+
style: row,
|
|
3357
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
3358
|
+
wrapMode: "none",
|
|
3359
|
+
children: [
|
|
3360
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3361
|
+
fg: accent,
|
|
3362
|
+
children: `${NOTICE_GLYPH[notice.severity]} `
|
|
3363
|
+
}),
|
|
3364
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3365
|
+
fg: accent,
|
|
3366
|
+
children: `${notice.label.padEnd(NOTICE_LABEL_WIDTH)} `
|
|
3367
|
+
}),
|
|
3368
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
3369
|
+
fg: COLOR.dim,
|
|
3370
|
+
children: head
|
|
3371
|
+
}),
|
|
3372
|
+
extra > 0 ? /* @__PURE__ */ jsx$1("span", {
|
|
3373
|
+
fg: COLOR.mute,
|
|
3374
|
+
children: ` +${extra} line${extra === 1 ? "" : "s"}`
|
|
3375
|
+
}) : null
|
|
3376
|
+
]
|
|
3377
|
+
})
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3256
3380
|
function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = false, selected = false, settleCodeEpoch = 0, liveSpawn, searchQuery, searchTitleMatch = false }) {
|
|
3257
3381
|
const COLOR = useColors();
|
|
3258
3382
|
const { settings } = useSettings();
|
|
@@ -3269,13 +3393,19 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
3269
3393
|
attachments: event.attachments,
|
|
3270
3394
|
searchQuery
|
|
3271
3395
|
});
|
|
3272
|
-
case "info":
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
})
|
|
3278
|
-
|
|
3396
|
+
case "info":
|
|
3397
|
+
if (event.notice) return /* @__PURE__ */ jsx$1(NoticeRow, {
|
|
3398
|
+
row,
|
|
3399
|
+
notice: event.notice,
|
|
3400
|
+
text: safeText
|
|
3401
|
+
});
|
|
3402
|
+
return /* @__PURE__ */ jsx$1("box", {
|
|
3403
|
+
style: row,
|
|
3404
|
+
children: /* @__PURE__ */ jsx$1("text", {
|
|
3405
|
+
fg: COLOR.dim,
|
|
3406
|
+
children: safeText
|
|
3407
|
+
})
|
|
3408
|
+
});
|
|
3279
3409
|
case "thinking": {
|
|
3280
3410
|
const startsRun = !(previous?.kind === "thinking" && previous.childId === event.childId);
|
|
3281
3411
|
const thinkingText = safeText.replace(/<!--[\s\S]*?-->/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").trimEnd();
|
|
@@ -3360,16 +3490,22 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
3360
3490
|
indent: row.paddingLeft,
|
|
3361
3491
|
diff: event.syntax === "diff"
|
|
3362
3492
|
});
|
|
3363
|
-
case "error":
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3493
|
+
case "error":
|
|
3494
|
+
if (event.notice) return /* @__PURE__ */ jsx$1(NoticeRow, {
|
|
3495
|
+
row,
|
|
3496
|
+
notice: event.notice,
|
|
3497
|
+
text: safeText
|
|
3498
|
+
});
|
|
3499
|
+
return /* @__PURE__ */ jsx$1("box", {
|
|
3500
|
+
style: row,
|
|
3501
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
3368
3502
|
fg: COLOR.error,
|
|
3369
|
-
children: "
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3503
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
3504
|
+
fg: COLOR.error,
|
|
3505
|
+
children: "✗ "
|
|
3506
|
+
}), safeText]
|
|
3507
|
+
})
|
|
3508
|
+
});
|
|
3373
3509
|
case "markdown": return /* @__PURE__ */ jsx$1("box", {
|
|
3374
3510
|
style: row,
|
|
3375
3511
|
children: /* @__PURE__ */ jsx$1(MarkdownBlock, {
|
|
@@ -6876,8 +7012,14 @@ function OptionRow({ option, isEnabled, isFocused, highlightBg }) {
|
|
|
6876
7012
|
*
|
|
6877
7013
|
* Empty states:
|
|
6878
7014
|
* - No available providers → "no providers configured" notice.
|
|
6879
|
-
* - Query has no matches → "
|
|
6880
|
-
*
|
|
7015
|
+
* - Query has no matches → a selectable "use anyway" row carrying the
|
|
7016
|
+
* typed id (see `parseCustomModelQuery`), so a model the local catalog
|
|
7017
|
+
* doesn't know can still be picked. The search input stays live.
|
|
7018
|
+
*
|
|
7019
|
+
* Off-catalog models: the active pick and every chain entry are passed to
|
|
7020
|
+
* `buildModelCatalog` as `include`, so a model the provider has since
|
|
7021
|
+
* retired still gets a row (marked `not in catalog`) and can be selected
|
|
7022
|
+
* away from. Without it such an entry is invisible and re-persists forever.
|
|
6881
7023
|
*/
|
|
6882
7024
|
/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */
|
|
6883
7025
|
const VISIBLE_ROWS = 10;
|
|
@@ -6921,22 +7063,36 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
6921
7063
|
useEffect(() => {
|
|
6922
7064
|
inputRef.current?.focus();
|
|
6923
7065
|
}, []);
|
|
6924
|
-
const catalog = useMemo(() => buildModelCatalog({
|
|
6925
|
-
providers,
|
|
6926
|
-
modelsFor,
|
|
6927
|
-
current
|
|
6928
|
-
}), [
|
|
6929
|
-
providers,
|
|
6930
|
-
modelsFor,
|
|
6931
|
-
current
|
|
6932
|
-
]);
|
|
6933
7066
|
const chainMode = onApplyChain !== void 0;
|
|
6934
7067
|
const [initialChain] = useState(() => chain && chain.length > 0 ? [...chain] : [{
|
|
6935
7068
|
provider: current.providerKey,
|
|
6936
7069
|
model: current.modelId
|
|
6937
7070
|
}]);
|
|
6938
7071
|
const [chainState, setChainState] = useState(initialChain);
|
|
7072
|
+
const [typedEntries, setTypedEntries] = useState([]);
|
|
6939
7073
|
const modalApi = useModalState();
|
|
7074
|
+
const [includeSeed] = useState(() => [{
|
|
7075
|
+
providerKey: current.providerKey,
|
|
7076
|
+
modelId: current.modelId
|
|
7077
|
+
}, ...(chain ?? []).map((entry) => ({
|
|
7078
|
+
providerKey: entry.provider,
|
|
7079
|
+
modelId: entry.model
|
|
7080
|
+
}))]);
|
|
7081
|
+
const catalog = useMemo(() => buildModelCatalog({
|
|
7082
|
+
providers,
|
|
7083
|
+
modelsFor,
|
|
7084
|
+
current,
|
|
7085
|
+
include: [...includeSeed, ...typedEntries.map((entry) => ({
|
|
7086
|
+
providerKey: entry.provider,
|
|
7087
|
+
modelId: entry.model
|
|
7088
|
+
}))]
|
|
7089
|
+
}), [
|
|
7090
|
+
providers,
|
|
7091
|
+
modelsFor,
|
|
7092
|
+
current,
|
|
7093
|
+
includeSeed,
|
|
7094
|
+
typedEntries
|
|
7095
|
+
]);
|
|
6940
7096
|
const filtered = useMemo(() => {
|
|
6941
7097
|
const matches = filterModelCatalog(catalog, query);
|
|
6942
7098
|
if (query.trim()) return matches;
|
|
@@ -6952,19 +7108,40 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
6952
7108
|
setSelectedIdx(0);
|
|
6953
7109
|
}, []);
|
|
6954
7110
|
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
7111
|
+
const custom = useMemo(() => filtered.length === 0 ? parseCustomModelQuery(query, providers, current.providerKey) : null, [
|
|
7112
|
+
filtered.length,
|
|
7113
|
+
query,
|
|
7114
|
+
providers,
|
|
7115
|
+
current.providerKey
|
|
7116
|
+
]);
|
|
7117
|
+
const customEntry = custom ? {
|
|
7118
|
+
providerKey: custom.providerKey,
|
|
7119
|
+
providerLabel: custom.providerLabel,
|
|
7120
|
+
model: {
|
|
7121
|
+
id: custom.modelId,
|
|
7122
|
+
contextWindow: 0
|
|
7123
|
+
},
|
|
7124
|
+
offCatalog: true,
|
|
7125
|
+
searchCorpus: ""
|
|
7126
|
+
} : null;
|
|
6955
7127
|
const commit = () => {
|
|
6956
7128
|
const row = filtered[safeIndex];
|
|
6957
|
-
|
|
7129
|
+
const target = row ? {
|
|
7130
|
+
provider: row.providerKey,
|
|
7131
|
+
model: row.model.id
|
|
7132
|
+
} : custom ? {
|
|
7133
|
+
provider: custom.providerKey,
|
|
7134
|
+
model: custom.modelId
|
|
7135
|
+
} : null;
|
|
7136
|
+
if (!target) return;
|
|
6958
7137
|
if (chainMode) {
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
model: row.model.id
|
|
6962
|
-
}));
|
|
7138
|
+
if (!row) setTypedEntries((prev) => prev.some((e) => e.provider === target.provider && e.model === target.model) ? prev : [...prev, target]);
|
|
7139
|
+
setChainState((prev) => toggleChainEntry(prev, target));
|
|
6963
7140
|
return;
|
|
6964
7141
|
}
|
|
6965
7142
|
onPick?.({
|
|
6966
|
-
providerKey:
|
|
6967
|
-
modelId:
|
|
7143
|
+
providerKey: target.provider,
|
|
7144
|
+
modelId: target.model
|
|
6968
7145
|
});
|
|
6969
7146
|
};
|
|
6970
7147
|
const handleClose = () => {
|
|
@@ -7052,7 +7229,7 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
7052
7229
|
height: VISIBLE_ROWS,
|
|
7053
7230
|
flexShrink: 0
|
|
7054
7231
|
},
|
|
7055
|
-
children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
|
|
7232
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("text", {
|
|
7056
7233
|
fg: COLOR.dim,
|
|
7057
7234
|
children: [/* @__PURE__ */ jsx$1("span", {
|
|
7058
7235
|
fg: COLOR.mute,
|
|
@@ -7061,7 +7238,13 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
7061
7238
|
fg: COLOR.warn,
|
|
7062
7239
|
children: query.trim()
|
|
7063
7240
|
})]
|
|
7064
|
-
})
|
|
7241
|
+
}), customEntry && /* @__PURE__ */ jsx$1(ModelRow, {
|
|
7242
|
+
entry: customEntry,
|
|
7243
|
+
isCurrent: false,
|
|
7244
|
+
isFocused: true,
|
|
7245
|
+
highlightBg: SURFACE.selection,
|
|
7246
|
+
chainPosition: chainMode ? chainPositionOf(chainState, customEntry.providerKey, customEntry.model.id) : null
|
|
7247
|
+
})] }) : viewport.slice.map((entry, i) => /* @__PURE__ */ jsx$1(ModelRow, {
|
|
7065
7248
|
entry,
|
|
7066
7249
|
isCurrent: entry.providerKey === current.providerKey && entry.model.id === current.modelId,
|
|
7067
7250
|
isFocused: viewport.start + i === safeIndex,
|
|
@@ -7077,7 +7260,7 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
|
|
|
7077
7260
|
},
|
|
7078
7261
|
{
|
|
7079
7262
|
key: "↵",
|
|
7080
|
-
label: chainMode ? "add/remove" : "select",
|
|
7263
|
+
label: customEntry ? "use anyway" : chainMode ? "add/remove" : "select",
|
|
7081
7264
|
labelColor: COLOR.dim
|
|
7082
7265
|
},
|
|
7083
7266
|
chainMode && {
|
|
@@ -7305,6 +7488,7 @@ function ModelRow({ entry, isCurrent, isFocused, highlightBg, chainPosition = nu
|
|
|
7305
7488
|
const COLOR = useColors();
|
|
7306
7489
|
const marker = chainPosition !== null ? chainPosition > 9 ? "+" : String(chainPosition) : isCurrent ? "●" : " ";
|
|
7307
7490
|
const markerColor = chainPosition !== null || isCurrent ? COLOR.brand : COLOR.mute;
|
|
7491
|
+
const detail = entry.offCatalog ? "not in catalog" : describeModel(entry.model);
|
|
7308
7492
|
return /* @__PURE__ */ jsx$1("box", {
|
|
7309
7493
|
style: {
|
|
7310
7494
|
height: 1,
|
|
@@ -7341,8 +7525,8 @@ function ModelRow({ entry, isCurrent, isFocused, highlightBg, chainPosition = nu
|
|
|
7341
7525
|
children: " · "
|
|
7342
7526
|
}),
|
|
7343
7527
|
/* @__PURE__ */ jsx$1("span", {
|
|
7344
|
-
fg: COLOR.mute,
|
|
7345
|
-
children:
|
|
7528
|
+
fg: entry.offCatalog ? COLOR.warn : COLOR.mute,
|
|
7529
|
+
children: detail
|
|
7346
7530
|
})
|
|
7347
7531
|
]
|
|
7348
7532
|
})
|
|
@@ -15677,7 +15861,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15677
15861
|
category: "agent",
|
|
15678
15862
|
id: "compaction-model",
|
|
15679
15863
|
label: "Compaction model",
|
|
15680
|
-
description: `current: ${settings.compactionModel || "main model"}
|
|
15864
|
+
description: `current: ${settings.compactionModel || "main model"}. Pick any provider's model for compaction summaries, or the main model to clear`,
|
|
15681
15865
|
onPick: actions.onPickCompactionModel
|
|
15682
15866
|
});
|
|
15683
15867
|
if (actions?.onImportClaudeCode) acts.push({
|
|
@@ -16067,14 +16251,14 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16067
16251
|
const { segment } = row;
|
|
16068
16252
|
if (segment.id === "system" && !segment.path && actions?.onCreateSystemPrompt) {
|
|
16069
16253
|
if (!instructionsCanOverride) {
|
|
16070
|
-
setInstructionsNotice(instructionsOverrideBlockedBy === "scope" ? "Your instruction scope excludes user-scope files, so a SYSTEM.md in your home directory would not be loaded. Change it on the Agent tab. Press tab to read the prompt inline." : "This profile supplies its own system prompt
|
|
16254
|
+
setInstructionsNotice(instructionsOverrideBlockedBy === "scope" ? "Your instruction scope excludes user-scope files, so a SYSTEM.md in your home directory would not be loaded. Change it on the Agent tab. Press tab to read the prompt inline." : "This profile supplies its own system prompt, so a SYSTEM.md override would not apply. Press tab to read it inline.");
|
|
16071
16255
|
return;
|
|
16072
16256
|
}
|
|
16073
16257
|
Promise.resolve(actions.onCreateSystemPrompt()).catch((err) => setInstructionsNotice(`Could not create SYSTEM.md: ${errorMessage(err)}`));
|
|
16074
16258
|
return;
|
|
16075
16259
|
}
|
|
16076
16260
|
if (segment.path) Promise.resolve(actions?.onOpenInstructionsFile?.(segment.path)).catch((err) => setInstructionsNotice(`Could not open ${segment.label}: ${errorMessage(err)}`));
|
|
16077
|
-
else setInstructionsNotice(`"${segment.label}" is ${instructionOriginBadge(segment)}
|
|
16261
|
+
else setInstructionsNotice(`"${segment.label}" is ${instructionOriginBadge(segment)}, no file to open. Press tab to read it inline.`);
|
|
16078
16262
|
return;
|
|
16079
16263
|
}
|
|
16080
16264
|
if (activeTab === "keybindings") {
|
|
@@ -16279,7 +16463,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16279
16463
|
key.preventDefault();
|
|
16280
16464
|
const ext = focusedExtension;
|
|
16281
16465
|
if (ext?.dir) actions?.onEditExtension?.(ext.dir);
|
|
16282
|
-
else if (ext) setExtEditNotice(`"${ext.name}" is ${ext.source}
|
|
16466
|
+
else if (ext) setExtEditNotice(`"${ext.name}" is ${ext.source}, no directory to edit. Press ctrl+a to scaffold an editable extension.`);
|
|
16283
16467
|
return;
|
|
16284
16468
|
}
|
|
16285
16469
|
if (key.ctrl && key.name === "g" && actions?.onInstallExtension) {
|
|
@@ -16401,7 +16585,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16401
16585
|
if (skill.baseDir) actions.onEditSkill(skill);
|
|
16402
16586
|
else setSkillOp({
|
|
16403
16587
|
kind: "notice",
|
|
16404
|
-
text: `"${skill.name}" is ${skill.source ?? "inline"}
|
|
16588
|
+
text: `"${skill.name}" is ${skill.source ?? "inline"}, no directory to edit.`
|
|
16405
16589
|
});
|
|
16406
16590
|
return;
|
|
16407
16591
|
}
|
|
@@ -16473,7 +16657,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16473
16657
|
setInstructionsNotice("reloading…");
|
|
16474
16658
|
actions.onRefreshInstructions().then((outcome) => {
|
|
16475
16659
|
setInstructionsRefreshKey((n) => n + 1);
|
|
16476
|
-
setInstructionsNotice(outcome === "rebuilt" ? "reloaded
|
|
16660
|
+
setInstructionsNotice(outcome === "rebuilt" ? "reloaded, agent rebuilt. MCP servers reconnect on the next turn" : outcome === "queued" ? "reloaded, agent rebuild queued until the current run finishes" : "reloaded from disk");
|
|
16477
16661
|
}).catch((err) => setInstructionsNotice(`Refresh failed: ${errorMessage(err)}`));
|
|
16478
16662
|
return;
|
|
16479
16663
|
}
|
|
@@ -17891,7 +18075,7 @@ function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR, ref
|
|
|
17891
18075
|
children: focused ? TOOL_ROW_FOCUS_INDENT$1 : TOOL_ROW_INDENT$1
|
|
17892
18076
|
}), /* @__PURE__ */ jsx$1("span", {
|
|
17893
18077
|
fg: COLOR.mute,
|
|
17894
|
-
children: refreshing ? "⟲ Fetching tool list…" : "⟲ No tools reported
|
|
18078
|
+
children: refreshing ? "⟲ Fetching tool list…" : "⟲ No tools reported. Check the server command or login, ctrl+r retries."
|
|
17895
18079
|
})]
|
|
17896
18080
|
}, `tool-empty:${serverName}`);
|
|
17897
18081
|
}
|
|
@@ -17911,11 +18095,11 @@ function InstructionsList({ rows, totalCount, loading, error, live, shadowedSyst
|
|
|
17911
18095
|
children: [
|
|
17912
18096
|
!live && /* @__PURE__ */ jsx$1("text", {
|
|
17913
18097
|
fg: COLOR.mute,
|
|
17914
|
-
children: " No active session
|
|
18098
|
+
children: " No active session, showing what your next one will load. MCP and extension context is added at run time."
|
|
17915
18099
|
}),
|
|
17916
18100
|
shadowedSystemPath !== void 0 && /* @__PURE__ */ jsx$1("text", {
|
|
17917
18101
|
fg: COLOR.mute,
|
|
17918
|
-
children: canOverrideSystem ? ` ${displayPath$1(shadowedSystemPath, homedir())} is shadowed by the project SYSTEM.md and is not loaded.` : overrideBlockedBy === "scope" ? ` ${displayPath$1(shadowedSystemPath, homedir())} is not loaded
|
|
18102
|
+
children: canOverrideSystem ? ` ${displayPath$1(shadowedSystemPath, homedir())} is shadowed by the project SYSTEM.md and is not loaded.` : overrideBlockedBy === "scope" ? ` ${displayPath$1(shadowedSystemPath, homedir())} is not loaded, your instruction scope excludes it.` : ` ${displayPath$1(shadowedSystemPath, homedir())} is not loaded, this profile supplies its own system prompt.`
|
|
17919
18103
|
}),
|
|
17920
18104
|
rows.map(({ segment, expanded }, i) => {
|
|
17921
18105
|
const focused = i === cursor;
|
|
@@ -18083,7 +18267,7 @@ function AuthenticationList({ view, providers, rows, cursor, highlightBg, query
|
|
|
18083
18267
|
}),
|
|
18084
18268
|
/* @__PURE__ */ jsx$1("span", {
|
|
18085
18269
|
fg: view.currentProviderLabel ? COLOR.accent : COLOR.mute,
|
|
18086
|
-
children: view.currentProviderLabel ?? "
|
|
18270
|
+
children: view.currentProviderLabel ?? "not selected"
|
|
18087
18271
|
}),
|
|
18088
18272
|
view.currentProviderKey && view.currentProviderLabel && view.currentProviderKey !== view.currentProviderLabel.toLowerCase() && /* @__PURE__ */ jsx$1("span", {
|
|
18089
18273
|
fg: COLOR.dim,
|
|
@@ -18098,7 +18282,7 @@ function AuthenticationList({ view, providers, rows, cursor, highlightBg, query
|
|
|
18098
18282
|
children: " model "
|
|
18099
18283
|
}), /* @__PURE__ */ jsx$1("span", {
|
|
18100
18284
|
fg: view.currentModel ? COLOR.model : COLOR.mute,
|
|
18101
|
-
children: view.currentModel ?? "
|
|
18285
|
+
children: view.currentModel ?? "not selected"
|
|
18102
18286
|
})]
|
|
18103
18287
|
})
|
|
18104
18288
|
]
|
|
@@ -18755,12 +18939,12 @@ function canLogout$1(status) {
|
|
|
18755
18939
|
return status.kind === "authed" || status.kind === "error" || status.kind === "authorizing";
|
|
18756
18940
|
}
|
|
18757
18941
|
function searchPlaceholder(tab) {
|
|
18758
|
-
if (tab === "skills") return "search skills
|
|
18759
|
-
if (tab === "mcps") return "search MCP servers
|
|
18760
|
-
if (tab === "extensions") return "search extensions
|
|
18761
|
-
if (tab === "keybindings") return "search keybindings
|
|
18762
|
-
if (tab === "authentication") return "search providers
|
|
18763
|
-
return `search ${TAB_LABELS[tab].toLowerCase()} settings
|
|
18942
|
+
if (tab === "skills") return "search skills by name or description…";
|
|
18943
|
+
if (tab === "mcps") return "search MCP servers by name, transport or command…";
|
|
18944
|
+
if (tab === "extensions") return "search extensions by name, version or description…";
|
|
18945
|
+
if (tab === "keybindings") return "search keybindings by action, key or description…";
|
|
18946
|
+
if (tab === "authentication") return "search providers by name or method…";
|
|
18947
|
+
return `search ${TAB_LABELS[tab].toLowerCase()} settings by name or description…`;
|
|
18764
18948
|
}
|
|
18765
18949
|
function displayPath$1(path, home) {
|
|
18766
18950
|
if (home && path.startsWith(`${home}/`)) return `~/${path.slice(home.length + 1)}`;
|
|
@@ -19852,6 +20036,322 @@ function blockSummary(turns) {
|
|
|
19852
20036
|
return parts.length === 0 ? "(empty)" : parts.join(" · ");
|
|
19853
20037
|
}
|
|
19854
20038
|
//#endregion
|
|
20039
|
+
//#region src/tui/use-provider-usage.ts
|
|
20040
|
+
/**
|
|
20041
|
+
* Floor on run-completion refetches. Back-to-back short turns shouldn't each
|
|
20042
|
+
* cost a request, and usage windows don't move meaningfully inside 15s.
|
|
20043
|
+
*/
|
|
20044
|
+
const RUN_REFRESH_MIN_AGE_MS = 15e3;
|
|
20045
|
+
/**
|
|
20046
|
+
* Usage for the active provider, refetched when the provider changes and when
|
|
20047
|
+
* a run finishes. Stays `null` for providers with no usage surface or no stored
|
|
20048
|
+
* credential, which is what hides the footer indicator.
|
|
20049
|
+
*/
|
|
20050
|
+
function useActiveProviderUsage({ dataDir, descriptor, enabled, busy }) {
|
|
20051
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
20052
|
+
const generationRef = useRef(0);
|
|
20053
|
+
const mountedRef = useRef(true);
|
|
20054
|
+
useEffect(() => () => {
|
|
20055
|
+
mountedRef.current = false;
|
|
20056
|
+
}, []);
|
|
20057
|
+
const snapshotRef = useRef(snapshot);
|
|
20058
|
+
snapshotRef.current = snapshot;
|
|
20059
|
+
const providerKey = descriptor?.key ?? null;
|
|
20060
|
+
const active = enabled && descriptor !== null && providerUsageSupported(descriptor);
|
|
20061
|
+
const load = useCallback((refresh) => {
|
|
20062
|
+
if (!enabled || !descriptor || !providerUsageSupported(descriptor)) return;
|
|
20063
|
+
const generation = ++generationRef.current;
|
|
20064
|
+
fetchProviderUsage(dataDir, descriptor, { refresh }).then((next) => {
|
|
20065
|
+
if (mountedRef.current && generationRef.current === generation) setSnapshot(next);
|
|
20066
|
+
}).catch(() => {});
|
|
20067
|
+
}, [
|
|
20068
|
+
dataDir,
|
|
20069
|
+
descriptor,
|
|
20070
|
+
enabled
|
|
20071
|
+
]);
|
|
20072
|
+
useEffect(() => {
|
|
20073
|
+
generationRef.current++;
|
|
20074
|
+
setSnapshot(null);
|
|
20075
|
+
if (active) load(false);
|
|
20076
|
+
}, [providerKey, active]);
|
|
20077
|
+
const prevBusyRef = useRef(busy);
|
|
20078
|
+
useEffect(() => {
|
|
20079
|
+
const finished = prevBusyRef.current && !busy;
|
|
20080
|
+
prevBusyRef.current = busy;
|
|
20081
|
+
if (!finished || !active) return;
|
|
20082
|
+
const current = snapshotRef.current;
|
|
20083
|
+
if (current && Date.now() - current.fetchedAt < RUN_REFRESH_MIN_AGE_MS) return;
|
|
20084
|
+
load(true);
|
|
20085
|
+
}, [busy, active]);
|
|
20086
|
+
return snapshot;
|
|
20087
|
+
}
|
|
20088
|
+
/**
|
|
20089
|
+
* Usage for every registered provider that has a usage surface and a stored
|
|
20090
|
+
* credential. Loads once on mount unless `autoLoad` is off; `refresh`
|
|
20091
|
+
* re-probes them all either way.
|
|
20092
|
+
*/
|
|
20093
|
+
function useAllProviderUsage({ dataDir, registry, autoLoad = true }) {
|
|
20094
|
+
const [snapshots, setSnapshots] = useState([]);
|
|
20095
|
+
const [loading, setLoading] = useState(autoLoad);
|
|
20096
|
+
const [loaded, setLoaded] = useState(false);
|
|
20097
|
+
const generationRef = useRef(0);
|
|
20098
|
+
const mountedRef = useRef(true);
|
|
20099
|
+
useEffect(() => () => {
|
|
20100
|
+
mountedRef.current = false;
|
|
20101
|
+
}, []);
|
|
20102
|
+
const registryRef = useRef(registry);
|
|
20103
|
+
registryRef.current = registry;
|
|
20104
|
+
const load = useCallback((refresh) => {
|
|
20105
|
+
const generation = ++generationRef.current;
|
|
20106
|
+
setLoading(true);
|
|
20107
|
+
fetchAllProviderUsage(dataDir, registryRef.current, { refresh }).then((next) => {
|
|
20108
|
+
if (!mountedRef.current || generationRef.current !== generation) return;
|
|
20109
|
+
setSnapshots(next);
|
|
20110
|
+
setLoading(false);
|
|
20111
|
+
setLoaded(true);
|
|
20112
|
+
}).catch(() => {
|
|
20113
|
+
if (mountedRef.current && generationRef.current === generation) setLoading(false);
|
|
20114
|
+
});
|
|
20115
|
+
}, [dataDir]);
|
|
20116
|
+
useEffect(() => {
|
|
20117
|
+
if (autoLoad) load(false);
|
|
20118
|
+
}, [load, autoLoad]);
|
|
20119
|
+
return {
|
|
20120
|
+
snapshots,
|
|
20121
|
+
loading,
|
|
20122
|
+
loaded,
|
|
20123
|
+
refresh: useCallback(() => load(true), [load])
|
|
20124
|
+
};
|
|
20125
|
+
}
|
|
20126
|
+
//#endregion
|
|
20127
|
+
//#region src/tui/usage-panel.tsx
|
|
20128
|
+
/** Cells in a window's bar. Fixed, so every row's numbers line up. */
|
|
20129
|
+
const BAR_WIDTH = 12;
|
|
20130
|
+
/** Right-hand columns, padded so percents and resets align down the panel. */
|
|
20131
|
+
const PERCENT_COL = 6;
|
|
20132
|
+
const TRAILING_COL = 16;
|
|
20133
|
+
/** Percent thresholds, shared with the footer indicator. */
|
|
20134
|
+
function usageColor(usedPercent, COLOR) {
|
|
20135
|
+
if (usedPercent >= 85) return COLOR.error;
|
|
20136
|
+
if (usedPercent >= 60) return COLOR.warn;
|
|
20137
|
+
return COLOR.brand;
|
|
20138
|
+
}
|
|
20139
|
+
/**
|
|
20140
|
+
* Time to reset, coarsened to whole days past 24h. `null` when the provider
|
|
20141
|
+
* reported no reset, `resetting` once it is due.
|
|
20142
|
+
*
|
|
20143
|
+
* Every step rounds DOWN, like the footer's percent: 36h reads as `1d`, not
|
|
20144
|
+
* `2d`. Overstating the headroom left on a window is the wrong direction to
|
|
20145
|
+
* be wrong in.
|
|
20146
|
+
*/
|
|
20147
|
+
function resetLabel(resetsAt, now = Date.now()) {
|
|
20148
|
+
if (resetsAt === null) return null;
|
|
20149
|
+
const remaining = resetsAt - now;
|
|
20150
|
+
if (remaining <= 0) return "resetting";
|
|
20151
|
+
const minutes = Math.floor(remaining / 6e4);
|
|
20152
|
+
if (minutes < 60) return `resets in ${Math.max(1, minutes)}m`;
|
|
20153
|
+
const hours = Math.floor(minutes / 60);
|
|
20154
|
+
if (hours < 24) {
|
|
20155
|
+
const remMinutes = minutes % 60;
|
|
20156
|
+
return `resets in ${hours}h${remMinutes > 0 ? `${remMinutes}m` : ""}`;
|
|
20157
|
+
}
|
|
20158
|
+
return `resets in ${Math.max(1, Math.floor(hours / 24))}d`;
|
|
20159
|
+
}
|
|
20160
|
+
function UsagePanelModal({ dataDir, registry, activeProviderKey, autoLoad = true }) {
|
|
20161
|
+
const COLOR = useColors();
|
|
20162
|
+
const envOptedOut = usageChecksOptedOut();
|
|
20163
|
+
const { snapshots, loading, loaded, refresh } = useAllProviderUsage({
|
|
20164
|
+
dataDir,
|
|
20165
|
+
registry,
|
|
20166
|
+
autoLoad: autoLoad && !envOptedOut
|
|
20167
|
+
});
|
|
20168
|
+
useKeyboard((key) => {
|
|
20169
|
+
if (key.ctrl && key.name === "r" && !envOptedOut && !loading) refresh();
|
|
20170
|
+
});
|
|
20171
|
+
const ordered = [...snapshots].sort((a, b) => {
|
|
20172
|
+
return (a.providerKey === activeProviderKey ? 0 : 1) - (b.providerKey === activeProviderKey ? 0 : 1);
|
|
20173
|
+
});
|
|
20174
|
+
const { height: termHeight } = useTerminalDimensions();
|
|
20175
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
20176
|
+
title: "subscription usage",
|
|
20177
|
+
maxWidth: 72,
|
|
20178
|
+
children: [/* @__PURE__ */ jsx$1("box", {
|
|
20179
|
+
style: {
|
|
20180
|
+
flexDirection: "column",
|
|
20181
|
+
maxHeight: Math.max(6, termHeight - 11),
|
|
20182
|
+
flexShrink: 1,
|
|
20183
|
+
overflow: "hidden"
|
|
20184
|
+
},
|
|
20185
|
+
children: /* @__PURE__ */ jsx$1("scrollbox", {
|
|
20186
|
+
focusable: false,
|
|
20187
|
+
stickyScroll: false,
|
|
20188
|
+
style: {
|
|
20189
|
+
flexGrow: 1,
|
|
20190
|
+
flexShrink: 1
|
|
20191
|
+
},
|
|
20192
|
+
children: ordered.length === 0 ? /* @__PURE__ */ jsx$1("text", {
|
|
20193
|
+
wrapMode: "word",
|
|
20194
|
+
fg: COLOR.mute,
|
|
20195
|
+
children: emptyStateLine({
|
|
20196
|
+
loading,
|
|
20197
|
+
loaded,
|
|
20198
|
+
envOptedOut,
|
|
20199
|
+
autoLoad
|
|
20200
|
+
})
|
|
20201
|
+
}) : ordered.map((snapshot, index) => /* @__PURE__ */ jsx$1(ProviderSection, {
|
|
20202
|
+
snapshot,
|
|
20203
|
+
isActive: snapshot.providerKey === activeProviderKey,
|
|
20204
|
+
first: index === 0,
|
|
20205
|
+
COLOR
|
|
20206
|
+
}, snapshot.providerKey))
|
|
20207
|
+
})
|
|
20208
|
+
}), /* @__PURE__ */ jsx$1(ModalHints, { hints: [...envOptedOut ? [] : [{
|
|
20209
|
+
key: "ctrl+r",
|
|
20210
|
+
label: loading ? "refreshing" : "refresh",
|
|
20211
|
+
labelColor: COLOR.dim
|
|
20212
|
+
}], {
|
|
20213
|
+
key: "esc",
|
|
20214
|
+
label: "close",
|
|
20215
|
+
labelColor: COLOR.dim
|
|
20216
|
+
}] })]
|
|
20217
|
+
});
|
|
20218
|
+
}
|
|
20219
|
+
function ProviderSection({ snapshot, isActive, first, COLOR }) {
|
|
20220
|
+
const SURFACE = useSurfaces();
|
|
20221
|
+
const looseCredits = snapshot.windows.some((win) => win.id === "credits") ? null : snapshot.credits;
|
|
20222
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
20223
|
+
style: {
|
|
20224
|
+
flexDirection: "column",
|
|
20225
|
+
flexShrink: 0
|
|
20226
|
+
},
|
|
20227
|
+
children: [
|
|
20228
|
+
!first && /* @__PURE__ */ jsx$1("box", { style: {
|
|
20229
|
+
height: 1,
|
|
20230
|
+
flexShrink: 0
|
|
20231
|
+
} }),
|
|
20232
|
+
/* @__PURE__ */ jsxs("box", {
|
|
20233
|
+
style: {
|
|
20234
|
+
flexDirection: "row",
|
|
20235
|
+
height: 1,
|
|
20236
|
+
flexShrink: 0,
|
|
20237
|
+
backgroundColor: isActive ? SURFACE.selection : void 0
|
|
20238
|
+
},
|
|
20239
|
+
children: [
|
|
20240
|
+
/* @__PURE__ */ jsxs("text", {
|
|
20241
|
+
wrapMode: "none",
|
|
20242
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
20243
|
+
fg: isActive ? COLOR.brand : COLOR.model,
|
|
20244
|
+
children: snapshot.label
|
|
20245
|
+
}), snapshot.plan ? /* @__PURE__ */ jsx$1("span", {
|
|
20246
|
+
fg: COLOR.mute,
|
|
20247
|
+
children: ` · ${snapshot.plan}`
|
|
20248
|
+
}) : null]
|
|
20249
|
+
}),
|
|
20250
|
+
/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
|
|
20251
|
+
isActive && /* @__PURE__ */ jsx$1("text", {
|
|
20252
|
+
wrapMode: "none",
|
|
20253
|
+
fg: COLOR.brand,
|
|
20254
|
+
children: "active"
|
|
20255
|
+
})
|
|
20256
|
+
]
|
|
20257
|
+
}),
|
|
20258
|
+
snapshot.status === "ok" ? /* @__PURE__ */ jsxs(Fragment, { children: [snapshot.windows.map((win) => /* @__PURE__ */ jsx$1(WindowRow, {
|
|
20259
|
+
win,
|
|
20260
|
+
trailing: win.id === "credits" && snapshot.credits ? formatBalance(snapshot.credits) : resetLabel(win.resetsAt),
|
|
20261
|
+
COLOR
|
|
20262
|
+
}, win.id)), looseCredits && /* @__PURE__ */ jsxs("box", {
|
|
20263
|
+
style: {
|
|
20264
|
+
flexDirection: "row",
|
|
20265
|
+
height: 1,
|
|
20266
|
+
flexShrink: 0
|
|
20267
|
+
},
|
|
20268
|
+
children: [
|
|
20269
|
+
/* @__PURE__ */ jsx$1("text", {
|
|
20270
|
+
wrapMode: "none",
|
|
20271
|
+
fg: COLOR.dim,
|
|
20272
|
+
children: "credits"
|
|
20273
|
+
}),
|
|
20274
|
+
/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
|
|
20275
|
+
/* @__PURE__ */ jsx$1("text", {
|
|
20276
|
+
wrapMode: "none",
|
|
20277
|
+
fg: COLOR.mute,
|
|
20278
|
+
children: formatBalance(looseCredits).padStart(22)
|
|
20279
|
+
})
|
|
20280
|
+
]
|
|
20281
|
+
})] }) : /* @__PURE__ */ jsx$1("box", {
|
|
20282
|
+
style: {
|
|
20283
|
+
flexDirection: "row",
|
|
20284
|
+
height: 1,
|
|
20285
|
+
flexShrink: 0
|
|
20286
|
+
},
|
|
20287
|
+
children: /* @__PURE__ */ jsx$1("text", {
|
|
20288
|
+
wrapMode: "none",
|
|
20289
|
+
fg: snapshot.status === "unauthorized" ? COLOR.warn : COLOR.mute,
|
|
20290
|
+
children: `${snapshot.status === "unauthorized" ? "not authorized" : "unavailable"} · ${snapshot.error ?? "no data"}`
|
|
20291
|
+
})
|
|
20292
|
+
})
|
|
20293
|
+
]
|
|
20294
|
+
});
|
|
20295
|
+
}
|
|
20296
|
+
/** One window: `▰▰▰▱▱ 5h 43% resets in 2h14m`. */
|
|
20297
|
+
function WindowRow({ win, trailing, COLOR }) {
|
|
20298
|
+
const filled = Math.min(BAR_WIDTH, Math.max(win.usedPercent > 0 ? 1 : 0, Math.round(win.usedPercent / 100 * BAR_WIDTH)));
|
|
20299
|
+
const color = usageColor(win.usedPercent, COLOR);
|
|
20300
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
20301
|
+
style: {
|
|
20302
|
+
flexDirection: "row",
|
|
20303
|
+
height: 1,
|
|
20304
|
+
flexShrink: 0
|
|
20305
|
+
},
|
|
20306
|
+
children: [
|
|
20307
|
+
/* @__PURE__ */ jsxs("text", {
|
|
20308
|
+
wrapMode: "none",
|
|
20309
|
+
children: [
|
|
20310
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
20311
|
+
fg: color,
|
|
20312
|
+
children: "▰".repeat(filled)
|
|
20313
|
+
}),
|
|
20314
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
20315
|
+
fg: COLOR.mute,
|
|
20316
|
+
children: "▱".repeat(BAR_WIDTH - filled)
|
|
20317
|
+
}),
|
|
20318
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
20319
|
+
fg: COLOR.dim,
|
|
20320
|
+
children: ` ${win.label}`
|
|
20321
|
+
})
|
|
20322
|
+
]
|
|
20323
|
+
}),
|
|
20324
|
+
/* @__PURE__ */ jsx$1("box", { style: { flexGrow: 1 } }),
|
|
20325
|
+
/* @__PURE__ */ jsxs("text", {
|
|
20326
|
+
wrapMode: "none",
|
|
20327
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
20328
|
+
fg: color,
|
|
20329
|
+
children: `${formatUsagePercent(win.usedPercent)}%`.padStart(PERCENT_COL)
|
|
20330
|
+
}), /* @__PURE__ */ jsx$1("span", {
|
|
20331
|
+
fg: COLOR.mute,
|
|
20332
|
+
children: (trailing ?? "").padStart(TRAILING_COL)
|
|
20333
|
+
})]
|
|
20334
|
+
})
|
|
20335
|
+
]
|
|
20336
|
+
});
|
|
20337
|
+
}
|
|
20338
|
+
/** What an empty body says, in precedence order. */
|
|
20339
|
+
function emptyStateLine({ loading, loaded, envOptedOut, autoLoad }) {
|
|
20340
|
+
if (envOptedOut) return "usage checks are disabled in this environment (ZIDANE_NO_USAGE_CHECK / CI)";
|
|
20341
|
+
if (loading) return "loading…";
|
|
20342
|
+
if (!autoLoad && !loaded) return "usage checks are off · ctrl+r fetches once";
|
|
20343
|
+
return "no signed-in provider reports usage";
|
|
20344
|
+
}
|
|
20345
|
+
/** `20.00 / 25.00`, or just the remainder when no total is reported. */
|
|
20346
|
+
function formatBalance(credits) {
|
|
20347
|
+
const remaining = formatCredits(credits.remaining);
|
|
20348
|
+
return credits.total === null ? remaining : `${remaining} / ${formatCredits(credits.total)}`;
|
|
20349
|
+
}
|
|
20350
|
+
/** Balances are dollars for OpenRouter and opaque units elsewhere. */
|
|
20351
|
+
function formatCredits(value) {
|
|
20352
|
+
return value >= 100 ? value.toFixed(0) : value.toFixed(2);
|
|
20353
|
+
}
|
|
20354
|
+
//#endregion
|
|
19855
20355
|
//#region src/tui/app.tsx
|
|
19856
20356
|
const BUILTIN_COMPLETION_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
19857
20357
|
"commands",
|
|
@@ -21216,6 +21716,51 @@ function AppShell() {
|
|
|
21216
21716
|
getSmooth: useCallback(() => smoothStreamingRef.current, []),
|
|
21217
21717
|
maxTranscriptEvents: transcriptRevealHoldRef.current ? null : void 0
|
|
21218
21718
|
});
|
|
21719
|
+
/**
|
|
21720
|
+
* Append a harness notice: the aligned one-line rows for things the harness
|
|
21721
|
+
* did rather than things the model said (run error, model fallback,
|
|
21722
|
+
* repeat-guard trip, history repair).
|
|
21723
|
+
*
|
|
21724
|
+
* Persists by default as a display-only `role: 'command'` turn, sharing the
|
|
21725
|
+
* pre-minted `turnId` with the live event so the reloaded row IS the live
|
|
21726
|
+
* row. Pass `persist: false` for notices a reload makes meaningless or
|
|
21727
|
+
* duplicative: progress banners, hot-reload chatter, anything already
|
|
21728
|
+
* superseded by a persisted boundary card.
|
|
21729
|
+
*
|
|
21730
|
+
* Callers whose work outlives a session switch (run hooks, awaited command
|
|
21731
|
+
* handlers) pass their own `session`, captured when the work started. That
|
|
21732
|
+
* splits the two halves rather than dropping both:
|
|
21733
|
+
*
|
|
21734
|
+
* - the live row is skipped when that session is no longer the open one,
|
|
21735
|
+
* so a background run can't paint into the conversation the user just
|
|
21736
|
+
* opened;
|
|
21737
|
+
* - the turn is still persisted to the run's OWN session, because a run
|
|
21738
|
+
* that errors while you are looking elsewhere is exactly when a durable
|
|
21739
|
+
* record is worth the most. Reopening it replays the row in place.
|
|
21740
|
+
*/
|
|
21741
|
+
const emitNotice = useCallback((notice, text, opts = {}) => {
|
|
21742
|
+
const open = sessionRef.current;
|
|
21743
|
+
const turnId = crypto.randomUUID();
|
|
21744
|
+
if (!opts.session || open?.id === opts.session.id) stream.appendImmediate({
|
|
21745
|
+
kind: notice.severity === "error" ? "error" : "info",
|
|
21746
|
+
text,
|
|
21747
|
+
notice,
|
|
21748
|
+
turnId,
|
|
21749
|
+
...opts.childId ? { childId: opts.childId } : {},
|
|
21750
|
+
...opts.depth !== void 0 ? { depth: opts.depth } : {}
|
|
21751
|
+
});
|
|
21752
|
+
const target = opts.session ?? open;
|
|
21753
|
+
if (opts.persist === false || !target) return;
|
|
21754
|
+
target.appendTurns([{
|
|
21755
|
+
id: turnId,
|
|
21756
|
+
role: "command",
|
|
21757
|
+
createdAt: Date.now(),
|
|
21758
|
+
content: noticeTurnContent(notice, text, {
|
|
21759
|
+
...opts.childId ? { childId: opts.childId } : {},
|
|
21760
|
+
...opts.depth !== void 0 ? { depth: opts.depth } : {}
|
|
21761
|
+
})
|
|
21762
|
+
}]).catch((err) => debugLog("emitNotice: appendTurns failed", err));
|
|
21763
|
+
}, [stream]);
|
|
21219
21764
|
streamRef.current = stream;
|
|
21220
21765
|
const makePicked = useCallback((provider, modelId) => {
|
|
21221
21766
|
const descriptor = providerRegistry[provider.key];
|
|
@@ -21785,10 +22330,23 @@ function AppShell() {
|
|
|
21785
22330
|
provider: to.provider,
|
|
21786
22331
|
model: to.model
|
|
21787
22332
|
});
|
|
21788
|
-
|
|
21789
|
-
|
|
21790
|
-
|
|
21791
|
-
});
|
|
22333
|
+
emitNotice({
|
|
22334
|
+
severity: "warn",
|
|
22335
|
+
label: "model fallback"
|
|
22336
|
+
}, `${from.provider}/${from.model} failed (${errorMessage(err)}), continuing on ${to.provider}/${to.model}`, { session });
|
|
22337
|
+
});
|
|
22338
|
+
const guardNoticed = /* @__PURE__ */ new Set();
|
|
22339
|
+
agent.hooks.hook("repeat-guard:exceeded", ({ tool, count, action }) => {
|
|
22340
|
+
const key = `${tool}\u0000${action}`;
|
|
22341
|
+
if (guardNoticed.has(key)) return;
|
|
22342
|
+
guardNoticed.add(key);
|
|
22343
|
+
emitNotice({
|
|
22344
|
+
severity: action === "abort" ? "error" : "warn",
|
|
22345
|
+
label: "repeat guard"
|
|
22346
|
+
}, action === "abort" ? `${tool} repeated ${count} times with the same input, run stopped` : `${tool} repeated ${count} times with the same input, asked the model to change approach`, { session });
|
|
22347
|
+
});
|
|
22348
|
+
agent.hooks.hook("agent:done", () => {
|
|
22349
|
+
guardNoticed.clear();
|
|
21792
22350
|
});
|
|
21793
22351
|
agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
|
|
21794
22352
|
registerInFlightTool({
|
|
@@ -21933,18 +22491,18 @@ function AppShell() {
|
|
|
21933
22491
|
const reason = lastFinishReasonRef.current;
|
|
21934
22492
|
lastFinishReasonRef.current = void 0;
|
|
21935
22493
|
if (sessionRef.current?.id !== session.id) return;
|
|
21936
|
-
if (reason === "length")
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
});
|
|
21940
|
-
else if (reason === "pause")
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
});
|
|
21944
|
-
else if (reason === "content-filter")
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
});
|
|
22494
|
+
if (reason === "length") emitNotice({
|
|
22495
|
+
severity: "warn",
|
|
22496
|
+
label: "output limit"
|
|
22497
|
+
}, "stopped at the model output limit (max_tokens). Turn on \"Auto-resume on output limit\" or raise \"Max output tokens\" in Settings to continue past it.");
|
|
22498
|
+
else if (reason === "pause") emitNotice({
|
|
22499
|
+
severity: "warn",
|
|
22500
|
+
label: "pause cap"
|
|
22501
|
+
}, "stopped after repeated empty pause turns from the provider (maxConsecutivePauseTurns cap is armed).");
|
|
22502
|
+
else if (reason === "content-filter") emitNotice({
|
|
22503
|
+
severity: "warn",
|
|
22504
|
+
label: "refusal"
|
|
22505
|
+
}, "the provider refused to continue (safety filter / refusal stop) despite automatic retries. Rephrase the request, or start a fresh session if it keeps happening.");
|
|
21948
22506
|
});
|
|
21949
22507
|
agent.hooks.hook("compact:start", ({ usedFraction }) => {
|
|
21950
22508
|
setCompacting(true);
|
|
@@ -21966,10 +22524,10 @@ function AppShell() {
|
|
|
21966
22524
|
if (!(sessionRef.current?.id === session.id)) return;
|
|
21967
22525
|
if (ctx.status === "error") {
|
|
21968
22526
|
const msg = errorMessage(ctx.error);
|
|
21969
|
-
if (!/abort/i.test(msg))
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
});
|
|
22527
|
+
if (!/abort/i.test(msg)) emitNotice({
|
|
22528
|
+
severity: "error",
|
|
22529
|
+
label: "auto-compaction"
|
|
22530
|
+
}, msg);
|
|
21973
22531
|
return;
|
|
21974
22532
|
}
|
|
21975
22533
|
const markerTurn = [...session.turns].reverse().find((t) => t.content.some((b) => b.type === "compact-summary"));
|
|
@@ -21981,10 +22539,10 @@ function AppShell() {
|
|
|
21981
22539
|
agent.hooks.hook("history:repair", (ctx) => {
|
|
21982
22540
|
if (sessionRef.current?.id !== session.id) return;
|
|
21983
22541
|
const blocks = `${ctx.removedBlocks} stale reasoning block${ctx.removedBlocks === 1 ? "" : "s"}`;
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
|
|
21987
|
-
});
|
|
22542
|
+
emitNotice({
|
|
22543
|
+
severity: "warn",
|
|
22544
|
+
label: "history repair"
|
|
22545
|
+
}, `removed ${blocks} the provider rejected${ctx.persisted ? "" : " (session not yet persisted)"}`);
|
|
21988
22546
|
});
|
|
21989
22547
|
const spawnStartedAt = /* @__PURE__ */ new Map();
|
|
21990
22548
|
agent.hooks.hook("spawn:before", ({ id, task, depth, model, resumesChildId }) => {
|
|
@@ -22026,11 +22584,13 @@ function AppShell() {
|
|
|
22026
22584
|
});
|
|
22027
22585
|
});
|
|
22028
22586
|
agent.hooks.hook("spawn:error", ({ id, depth, error }) => {
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22587
|
+
emitNotice({
|
|
22588
|
+
severity: "error",
|
|
22589
|
+
label: "subagent"
|
|
22590
|
+
}, `[${id}] ${error.message}`, {
|
|
22032
22591
|
childId: id,
|
|
22033
|
-
depth: depth ?? 1
|
|
22592
|
+
depth: depth ?? 1,
|
|
22593
|
+
session
|
|
22034
22594
|
});
|
|
22035
22595
|
});
|
|
22036
22596
|
agent.hooks.hook("child:stream:thinking", ({ delta, childId, depth, turnId }) => {
|
|
@@ -22441,10 +23001,10 @@ function AppShell() {
|
|
|
22441
23001
|
updatedAt: Date.now()
|
|
22442
23002
|
} : prev);
|
|
22443
23003
|
} catch (err) {
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
});
|
|
23004
|
+
emitNotice({
|
|
23005
|
+
severity: "error",
|
|
23006
|
+
label: "run"
|
|
23007
|
+
}, errorMessage(err), { session });
|
|
22448
23008
|
} finally {
|
|
22449
23009
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
22450
23010
|
await drainFollowUpsRef.current().catch((err) => debugLog("resume-interaction: follow-up drain failed", err));
|
|
@@ -22565,6 +23125,7 @@ function AppShell() {
|
|
|
22565
23125
|
]);
|
|
22566
23126
|
const [availableProviders, setAvailableProviders] = useState([]);
|
|
22567
23127
|
const refreshAvailableProviders = useCallback(() => {
|
|
23128
|
+
clearProviderUsageCache();
|
|
22568
23129
|
const detected = detectAuth(config.paths.userDir, providerRegistry);
|
|
22569
23130
|
setAvailableProviders(detected.filter((p) => p.available));
|
|
22570
23131
|
}, [config.paths.userDir, providerRegistry]);
|
|
@@ -23366,10 +23927,10 @@ function AppShell() {
|
|
|
23366
23927
|
} : prev);
|
|
23367
23928
|
maybeAutoGenerateTitleRef.current(session);
|
|
23368
23929
|
} catch (err) {
|
|
23369
|
-
|
|
23370
|
-
|
|
23371
|
-
|
|
23372
|
-
});
|
|
23930
|
+
emitNotice({
|
|
23931
|
+
severity: "error",
|
|
23932
|
+
label: "run"
|
|
23933
|
+
}, errorMessage(err), { session });
|
|
23373
23934
|
} finally {
|
|
23374
23935
|
chatReferenceTokensRef.current = [];
|
|
23375
23936
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
@@ -23729,15 +24290,15 @@ function AppShell() {
|
|
|
23729
24290
|
});
|
|
23730
24291
|
emitBlock({ text: `\`❯ /${invocation.name}${invocation.args ? ` ${invocation.args}` : ""}\`` });
|
|
23731
24292
|
const result = await executeCommand(command, ctx);
|
|
23732
|
-
if (result.error)
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
});
|
|
24293
|
+
if (result.error) emitNotice({
|
|
24294
|
+
severity: "error",
|
|
24295
|
+
label: "command"
|
|
24296
|
+
}, `/${invocation.name}: ${errorMessage(result.error)}`, { session });
|
|
23736
24297
|
} catch (err) {
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
});
|
|
24298
|
+
emitNotice({
|
|
24299
|
+
severity: "error",
|
|
24300
|
+
label: "command"
|
|
24301
|
+
}, `/${invocation.name}: ${errorMessage(err)}`, { session });
|
|
23741
24302
|
} finally {
|
|
23742
24303
|
commandAbortRef.current = null;
|
|
23743
24304
|
await flushCommandTurn();
|
|
@@ -23850,11 +24411,10 @@ function AppShell() {
|
|
|
23850
24411
|
}]
|
|
23851
24412
|
}]).catch((err) => debugLog("bash: appendTurns failed", err));
|
|
23852
24413
|
} catch (err) {
|
|
23853
|
-
|
|
23854
|
-
|
|
23855
|
-
|
|
23856
|
-
|
|
23857
|
-
});
|
|
24414
|
+
emitNotice({
|
|
24415
|
+
severity: "error",
|
|
24416
|
+
label: "shell"
|
|
24417
|
+
}, `!${command}: ${errorMessage(err)}`, { session });
|
|
23858
24418
|
} finally {
|
|
23859
24419
|
commandAbortRef.current = null;
|
|
23860
24420
|
setCurrentSession((prev) => prev ? {
|
|
@@ -25251,6 +25811,15 @@ function AppShell() {
|
|
|
25251
25811
|
}).catch((err) => debugLog("getContextBreakdown failed", err));
|
|
25252
25812
|
return;
|
|
25253
25813
|
}
|
|
25814
|
+
if (matchesBinding(key, keybindings.openUsagePanel) && screen === "chat") {
|
|
25815
|
+
modal.open(/* @__PURE__ */ jsx$1(UsagePanelModal, {
|
|
25816
|
+
dataDir: config.paths.userDir,
|
|
25817
|
+
registry: providerRegistry,
|
|
25818
|
+
activeProviderKey: picked?.provider.key ?? null,
|
|
25819
|
+
autoLoad: settings.subscriptionUsage
|
|
25820
|
+
}));
|
|
25821
|
+
return;
|
|
25822
|
+
}
|
|
25254
25823
|
if (matchesBinding(key, keybindings.openKeybindings) && screen !== "auth") {
|
|
25255
25824
|
modal.open(/* @__PURE__ */ jsx$1(KeybindingsModal, {
|
|
25256
25825
|
bindings: keybindings,
|
|
@@ -25528,6 +26097,22 @@ function AppShell() {
|
|
|
25528
26097
|
providerRegistry
|
|
25529
26098
|
]);
|
|
25530
26099
|
const footerCost = screen === "chat" ? sessionCost : null;
|
|
26100
|
+
const activeUsageDescriptor = useMemo(() => picked ? providerRegistry[picked.provider.key] ?? null : null, [picked, providerRegistry]);
|
|
26101
|
+
const providerUsage = useActiveProviderUsage({
|
|
26102
|
+
dataDir: config.paths.userDir,
|
|
26103
|
+
descriptor: activeUsageDescriptor,
|
|
26104
|
+
enabled: screen === "chat" && settings.subscriptionUsage && settings.showUsageIndicator,
|
|
26105
|
+
busy
|
|
26106
|
+
});
|
|
26107
|
+
const footerUsage = useMemo(() => {
|
|
26108
|
+
if (!settings.showUsageIndicator) return null;
|
|
26109
|
+
if (!providerUsage || providerUsage.status !== "ok") return null;
|
|
26110
|
+
const window = mostConstrainedWindow(providerUsage);
|
|
26111
|
+
return window ? {
|
|
26112
|
+
label: window.label,
|
|
26113
|
+
usedPercent: window.usedPercent
|
|
26114
|
+
} : null;
|
|
26115
|
+
}, [providerUsage, settings.showUsageIndicator]);
|
|
25531
26116
|
useEffect(() => () => {
|
|
25532
26117
|
teardown();
|
|
25533
26118
|
}, [teardown]);
|
|
@@ -25617,7 +26202,9 @@ function AppShell() {
|
|
|
25617
26202
|
compactionTokens,
|
|
25618
26203
|
compactionStartedAt,
|
|
25619
26204
|
retry: retryInfo,
|
|
25620
|
-
contextKey: keybindings.openContextPanel
|
|
26205
|
+
contextKey: keybindings.openContextPanel,
|
|
26206
|
+
usage: footerUsage,
|
|
26207
|
+
usageKey: keybindings.openUsagePanel
|
|
25621
26208
|
}),
|
|
25622
26209
|
showQuitConfirm && /* @__PURE__ */ jsxs("box", {
|
|
25623
26210
|
position: "absolute",
|
|
@@ -25695,6 +26282,67 @@ function effortForModel(descriptor, modelId, remembered) {
|
|
|
25695
26282
|
return remembered?.[modelId] ?? "medium";
|
|
25696
26283
|
}
|
|
25697
26284
|
//#endregion
|
|
26285
|
+
//#region src/tui/stderr-guard.ts
|
|
26286
|
+
/** Lines kept for the abnormal-exit replay. Bounded so a chatty loop can't grow unbounded. */
|
|
26287
|
+
const REPLAY_CAPACITY = 200;
|
|
26288
|
+
/**
|
|
26289
|
+
* Redirect `process.stderr` into `sink` until the returned function is called.
|
|
26290
|
+
* Idempotent per install; call the returned uninstall before the renderer is
|
|
26291
|
+
* torn down so post-teardown output (render stats, fatal rethrows) reaches the
|
|
26292
|
+
* terminal normally.
|
|
26293
|
+
*/
|
|
26294
|
+
function installStderrGuard({ sink, level = "warn" }) {
|
|
26295
|
+
const original = process.stderr.write.bind(process.stderr);
|
|
26296
|
+
const replay = [];
|
|
26297
|
+
let pending = "";
|
|
26298
|
+
let emitting = false;
|
|
26299
|
+
const emit = (line) => {
|
|
26300
|
+
if (line.length === 0 || emitting) return;
|
|
26301
|
+
replay.push(line);
|
|
26302
|
+
if (replay.length > REPLAY_CAPACITY) replay.splice(0, replay.length - REPLAY_CAPACITY);
|
|
26303
|
+
emitting = true;
|
|
26304
|
+
try {
|
|
26305
|
+
sink.emit({
|
|
26306
|
+
level,
|
|
26307
|
+
timestamp: Date.now(),
|
|
26308
|
+
message: line,
|
|
26309
|
+
attrs: { source: "stderr" }
|
|
26310
|
+
});
|
|
26311
|
+
} catch {} finally {
|
|
26312
|
+
emitting = false;
|
|
26313
|
+
}
|
|
26314
|
+
};
|
|
26315
|
+
const flushPending = () => {
|
|
26316
|
+
if (pending.length > 0) {
|
|
26317
|
+
const line = pending;
|
|
26318
|
+
pending = "";
|
|
26319
|
+
emit(line);
|
|
26320
|
+
}
|
|
26321
|
+
};
|
|
26322
|
+
const capture = ((chunk, ...rest) => {
|
|
26323
|
+
const done = rest.find((arg) => typeof arg === "function");
|
|
26324
|
+
const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? new TextDecoder().decode(chunk) : String(chunk);
|
|
26325
|
+
pending += text;
|
|
26326
|
+
const lines = pending.split("\n");
|
|
26327
|
+
pending = lines.pop() ?? "";
|
|
26328
|
+
for (const line of lines) emit(line.replace(/\r$/, ""));
|
|
26329
|
+
done?.(null);
|
|
26330
|
+
return true;
|
|
26331
|
+
});
|
|
26332
|
+
const onExit = (code) => {
|
|
26333
|
+
process.stderr.write = original;
|
|
26334
|
+
if (code === 0 || replay.length === 0) return;
|
|
26335
|
+
original(`${replay.join("\n")}\n`);
|
|
26336
|
+
};
|
|
26337
|
+
process.stderr.write = capture;
|
|
26338
|
+
process.on("exit", onExit);
|
|
26339
|
+
return () => {
|
|
26340
|
+
process.off("exit", onExit);
|
|
26341
|
+
flushPending();
|
|
26342
|
+
process.stderr.write = original;
|
|
26343
|
+
};
|
|
26344
|
+
}
|
|
26345
|
+
//#endregion
|
|
25698
26346
|
//#region src/tui/tree-sitter.ts
|
|
25699
26347
|
/**
|
|
25700
26348
|
* Register Tree-sitter parsers for the languages we'd like highlighted
|
|
@@ -27099,65 +27747,85 @@ async function runTui(options = {}) {
|
|
|
27099
27747
|
const inlineExtensions = (options.extensions ?? []).map(inlineLoadedExtension);
|
|
27100
27748
|
const trustProjectExtensions = options.trustProjectExtensions ?? (preState.settings?.loadProjectExtensions === true || process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === "1");
|
|
27101
27749
|
const logging = initTuiLogging({ stateDir: paths.stateDir });
|
|
27102
|
-
|
|
27103
|
-
|
|
27104
|
-
|
|
27105
|
-
|
|
27106
|
-
|
|
27107
|
-
|
|
27108
|
-
|
|
27109
|
-
|
|
27110
|
-
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
promptBridge,
|
|
27117
|
-
hostRuntime,
|
|
27118
|
-
readSettings: readLiveSettings
|
|
27119
|
-
});
|
|
27120
|
-
bootTick("runTui:after-buildExtensionRegistry");
|
|
27121
|
-
const config = resolveConfig({
|
|
27122
|
-
...options,
|
|
27123
|
-
extensionScanPaths,
|
|
27124
|
-
pluginScanPaths,
|
|
27125
|
-
extensionRegistry,
|
|
27126
|
-
trustProjectExtensions,
|
|
27127
|
-
...options.launch && !options.preset && !options.agents ? { preset: basic_default } : {},
|
|
27128
|
-
store: options.store ?? ((resolved) => createTuiStore(options.launch?.sessionDb ?? resolved.db))
|
|
27129
|
-
});
|
|
27130
|
-
bootTick("runTui:after-resolveConfig");
|
|
27131
|
-
let done = () => {};
|
|
27132
|
-
const exited = new Promise((resolve) => {
|
|
27133
|
-
done = resolve;
|
|
27134
|
-
});
|
|
27135
|
-
const bootFps = clampFps(config.initialSettings.targetFps ?? DEFAULT_SETTINGS.targetFps);
|
|
27136
|
-
const gatherStats = !!process.env.ZIDANE_DEBUG;
|
|
27137
|
-
const renderer = await createCliRenderer({
|
|
27138
|
-
exitOnCtrlC: false,
|
|
27139
|
-
onDestroy: () => done(),
|
|
27140
|
-
debounceDelay: 0,
|
|
27141
|
-
targetFps: bootFps,
|
|
27142
|
-
maxFps: bootFps,
|
|
27143
|
-
useMouse: config.initialSettings.mouseInteractions ?? DEFAULT_SETTINGS.mouseInteractions,
|
|
27144
|
-
gatherStats
|
|
27145
|
-
});
|
|
27146
|
-
bootTick("runTui:after-createCliRenderer");
|
|
27750
|
+
setDebugLogSink((scope, line) => logging.sink.emit({
|
|
27751
|
+
level: "debug",
|
|
27752
|
+
timestamp: Date.now(),
|
|
27753
|
+
message: line,
|
|
27754
|
+
attrs: { scope }
|
|
27755
|
+
}));
|
|
27756
|
+
const releaseStderr = installStderrGuard({ sink: logging.sink });
|
|
27757
|
+
let logRedirectsReleased = false;
|
|
27758
|
+
const releaseLogRedirects = () => {
|
|
27759
|
+
if (logRedirectsReleased) return;
|
|
27760
|
+
logRedirectsReleased = true;
|
|
27761
|
+
releaseStderr();
|
|
27762
|
+
setDebugLogSink(null);
|
|
27763
|
+
};
|
|
27147
27764
|
try {
|
|
27148
|
-
|
|
27149
|
-
|
|
27150
|
-
|
|
27151
|
-
|
|
27152
|
-
|
|
27153
|
-
|
|
27154
|
-
|
|
27155
|
-
|
|
27156
|
-
|
|
27157
|
-
|
|
27158
|
-
|
|
27159
|
-
|
|
27160
|
-
|
|
27765
|
+
const extensionSink = logging.sink;
|
|
27766
|
+
registerTuiHostModules();
|
|
27767
|
+
const hud = initTuiHud();
|
|
27768
|
+
const promptBridge = initTuiPromptBridge();
|
|
27769
|
+
const hostRuntime = initTuiHostRuntime();
|
|
27770
|
+
const extensionRegistry = await discoverAndBuildExtensionRegistry({
|
|
27771
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
27772
|
+
prefix,
|
|
27773
|
+
...extensionScanPaths.length > 0 ? { extensionScanPaths } : {},
|
|
27774
|
+
...enabledAllowlist !== void 0 ? { enabledAllowlist } : {},
|
|
27775
|
+
trustProjectExtensions,
|
|
27776
|
+
inlineExtensions,
|
|
27777
|
+
sink: extensionSink,
|
|
27778
|
+
hud,
|
|
27779
|
+
promptBridge,
|
|
27780
|
+
hostRuntime,
|
|
27781
|
+
readSettings: readLiveSettings
|
|
27782
|
+
});
|
|
27783
|
+
bootTick("runTui:after-buildExtensionRegistry");
|
|
27784
|
+
const config = resolveConfig({
|
|
27785
|
+
...options,
|
|
27786
|
+
extensionScanPaths,
|
|
27787
|
+
pluginScanPaths,
|
|
27788
|
+
extensionRegistry,
|
|
27789
|
+
trustProjectExtensions,
|
|
27790
|
+
...options.launch && !options.preset && !options.agents ? { preset: basic_default } : {},
|
|
27791
|
+
store: options.store ?? ((resolved) => createTuiStore(options.launch?.sessionDb ?? resolved.db))
|
|
27792
|
+
});
|
|
27793
|
+
bootTick("runTui:after-resolveConfig");
|
|
27794
|
+
let done = () => {};
|
|
27795
|
+
const exited = new Promise((resolve) => {
|
|
27796
|
+
done = resolve;
|
|
27797
|
+
});
|
|
27798
|
+
const bootFps = clampFps(config.initialSettings.targetFps ?? DEFAULT_SETTINGS.targetFps);
|
|
27799
|
+
const gatherStats = !!process.env.ZIDANE_DEBUG;
|
|
27800
|
+
const renderer = await createCliRenderer({
|
|
27801
|
+
exitOnCtrlC: false,
|
|
27802
|
+
onDestroy: () => done(),
|
|
27803
|
+
debounceDelay: 0,
|
|
27804
|
+
targetFps: bootFps,
|
|
27805
|
+
maxFps: bootFps,
|
|
27806
|
+
useMouse: config.initialSettings.mouseInteractions ?? DEFAULT_SETTINGS.mouseInteractions,
|
|
27807
|
+
gatherStats
|
|
27808
|
+
});
|
|
27809
|
+
bootTick("runTui:after-createCliRenderer");
|
|
27810
|
+
try {
|
|
27811
|
+
createRoot(renderer).render(/* @__PURE__ */ jsx(App, { config }));
|
|
27812
|
+
} catch (err) {
|
|
27813
|
+
renderer.destroy();
|
|
27814
|
+
throw err;
|
|
27815
|
+
}
|
|
27816
|
+
bootTick("runTui:after-mount");
|
|
27817
|
+
await exited;
|
|
27818
|
+
releaseLogRedirects();
|
|
27819
|
+
logging.close();
|
|
27820
|
+
if (gatherStats) try {
|
|
27821
|
+
const s = renderer.getStats();
|
|
27822
|
+
process.stderr.write(`[zidane/tui] render stats: fps=${s.fps.toFixed(1)} avgFrame=${s.averageFrameTime.toFixed(2)}ms min=${s.minFrameTime.toFixed(2)}ms max=${s.maxFrameTime.toFixed(2)}ms frames=${s.frameCount}\n`);
|
|
27823
|
+
} catch (err) {
|
|
27824
|
+
process.stderr.write(`[zidane/tui] render stats unavailable: ${errorMessage(err)}\n`);
|
|
27825
|
+
}
|
|
27826
|
+
} finally {
|
|
27827
|
+
releaseLogRedirects();
|
|
27828
|
+
logging.close();
|
|
27161
27829
|
}
|
|
27162
27830
|
process.exit(0);
|
|
27163
27831
|
}
|