zidane 6.6.0 → 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-A1qz0l55.d.ts → index-D0O-QO7Z.d.ts} +72 -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-OobhXAK1.d.ts → tool-formatters-NrzD6Fkk.d.ts} +20 -3
- package/dist/{tool-formatters-OobhXAK1.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-ChTtfDdM.js → transcript-anchors-BjH-68k2.js} +661 -37
- package/dist/transcript-anchors-BjH-68k2.js.map +1 -0
- package/dist/{transcript-anchors-0midxNxf.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 +864 -193
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-JtKQfSFC.d.ts → turn-operations-Cuc9pwiB.d.ts} +3 -3
- package/dist/{turn-operations-JtKQfSFC.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/INTERACTIONS.md +11 -0
- package/docs/TUI.md +22 -2
- package/package.json +1 -1
- package/dist/agent-ut6nQRnI.js.map +0 -1
- package/dist/index-A1qz0l55.d.ts.map +0 -1
- package/dist/transcript-anchors-0midxNxf.d.ts.map +0 -1
- package/dist/transcript-anchors-ChTtfDdM.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
|
})
|
|
@@ -9761,8 +9945,11 @@ function MultiSelectQuestionInput({ question, initialChoiceIds, onSubmit }) {
|
|
|
9761
9945
|
const required = question.required ?? true;
|
|
9762
9946
|
const [cursor, setCursor] = useState(0);
|
|
9763
9947
|
const [picked, setPicked] = useState(() => {
|
|
9764
|
-
|
|
9765
|
-
|
|
9948
|
+
if (initialChoiceIds) {
|
|
9949
|
+
const valid = new Set(question.choices.map((c) => c.id));
|
|
9950
|
+
return new Set(initialChoiceIds.filter((id) => valid.has(id)));
|
|
9951
|
+
}
|
|
9952
|
+
return new Set(question.choices.filter((c) => c.selected).map((c) => c.id));
|
|
9766
9953
|
});
|
|
9767
9954
|
const moveCursor = useCallback((delta) => {
|
|
9768
9955
|
setCursor((c) => {
|
|
@@ -15674,7 +15861,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15674
15861
|
category: "agent",
|
|
15675
15862
|
id: "compaction-model",
|
|
15676
15863
|
label: "Compaction model",
|
|
15677
|
-
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`,
|
|
15678
15865
|
onPick: actions.onPickCompactionModel
|
|
15679
15866
|
});
|
|
15680
15867
|
if (actions?.onImportClaudeCode) acts.push({
|
|
@@ -16064,14 +16251,14 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16064
16251
|
const { segment } = row;
|
|
16065
16252
|
if (segment.id === "system" && !segment.path && actions?.onCreateSystemPrompt) {
|
|
16066
16253
|
if (!instructionsCanOverride) {
|
|
16067
|
-
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.");
|
|
16068
16255
|
return;
|
|
16069
16256
|
}
|
|
16070
16257
|
Promise.resolve(actions.onCreateSystemPrompt()).catch((err) => setInstructionsNotice(`Could not create SYSTEM.md: ${errorMessage(err)}`));
|
|
16071
16258
|
return;
|
|
16072
16259
|
}
|
|
16073
16260
|
if (segment.path) Promise.resolve(actions?.onOpenInstructionsFile?.(segment.path)).catch((err) => setInstructionsNotice(`Could not open ${segment.label}: ${errorMessage(err)}`));
|
|
16074
|
-
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.`);
|
|
16075
16262
|
return;
|
|
16076
16263
|
}
|
|
16077
16264
|
if (activeTab === "keybindings") {
|
|
@@ -16276,7 +16463,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16276
16463
|
key.preventDefault();
|
|
16277
16464
|
const ext = focusedExtension;
|
|
16278
16465
|
if (ext?.dir) actions?.onEditExtension?.(ext.dir);
|
|
16279
|
-
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.`);
|
|
16280
16467
|
return;
|
|
16281
16468
|
}
|
|
16282
16469
|
if (key.ctrl && key.name === "g" && actions?.onInstallExtension) {
|
|
@@ -16398,7 +16585,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16398
16585
|
if (skill.baseDir) actions.onEditSkill(skill);
|
|
16399
16586
|
else setSkillOp({
|
|
16400
16587
|
kind: "notice",
|
|
16401
|
-
text: `"${skill.name}" is ${skill.source ?? "inline"}
|
|
16588
|
+
text: `"${skill.name}" is ${skill.source ?? "inline"}, no directory to edit.`
|
|
16402
16589
|
});
|
|
16403
16590
|
return;
|
|
16404
16591
|
}
|
|
@@ -16470,7 +16657,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16470
16657
|
setInstructionsNotice("reloading…");
|
|
16471
16658
|
actions.onRefreshInstructions().then((outcome) => {
|
|
16472
16659
|
setInstructionsRefreshKey((n) => n + 1);
|
|
16473
|
-
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");
|
|
16474
16661
|
}).catch((err) => setInstructionsNotice(`Refresh failed: ${errorMessage(err)}`));
|
|
16475
16662
|
return;
|
|
16476
16663
|
}
|
|
@@ -17888,7 +18075,7 @@ function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR, ref
|
|
|
17888
18075
|
children: focused ? TOOL_ROW_FOCUS_INDENT$1 : TOOL_ROW_INDENT$1
|
|
17889
18076
|
}), /* @__PURE__ */ jsx$1("span", {
|
|
17890
18077
|
fg: COLOR.mute,
|
|
17891
|
-
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."
|
|
17892
18079
|
})]
|
|
17893
18080
|
}, `tool-empty:${serverName}`);
|
|
17894
18081
|
}
|
|
@@ -17908,11 +18095,11 @@ function InstructionsList({ rows, totalCount, loading, error, live, shadowedSyst
|
|
|
17908
18095
|
children: [
|
|
17909
18096
|
!live && /* @__PURE__ */ jsx$1("text", {
|
|
17910
18097
|
fg: COLOR.mute,
|
|
17911
|
-
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."
|
|
17912
18099
|
}),
|
|
17913
18100
|
shadowedSystemPath !== void 0 && /* @__PURE__ */ jsx$1("text", {
|
|
17914
18101
|
fg: COLOR.mute,
|
|
17915
|
-
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.`
|
|
17916
18103
|
}),
|
|
17917
18104
|
rows.map(({ segment, expanded }, i) => {
|
|
17918
18105
|
const focused = i === cursor;
|
|
@@ -18080,7 +18267,7 @@ function AuthenticationList({ view, providers, rows, cursor, highlightBg, query
|
|
|
18080
18267
|
}),
|
|
18081
18268
|
/* @__PURE__ */ jsx$1("span", {
|
|
18082
18269
|
fg: view.currentProviderLabel ? COLOR.accent : COLOR.mute,
|
|
18083
|
-
children: view.currentProviderLabel ?? "
|
|
18270
|
+
children: view.currentProviderLabel ?? "not selected"
|
|
18084
18271
|
}),
|
|
18085
18272
|
view.currentProviderKey && view.currentProviderLabel && view.currentProviderKey !== view.currentProviderLabel.toLowerCase() && /* @__PURE__ */ jsx$1("span", {
|
|
18086
18273
|
fg: COLOR.dim,
|
|
@@ -18095,7 +18282,7 @@ function AuthenticationList({ view, providers, rows, cursor, highlightBg, query
|
|
|
18095
18282
|
children: " model "
|
|
18096
18283
|
}), /* @__PURE__ */ jsx$1("span", {
|
|
18097
18284
|
fg: view.currentModel ? COLOR.model : COLOR.mute,
|
|
18098
|
-
children: view.currentModel ?? "
|
|
18285
|
+
children: view.currentModel ?? "not selected"
|
|
18099
18286
|
})]
|
|
18100
18287
|
})
|
|
18101
18288
|
]
|
|
@@ -18752,12 +18939,12 @@ function canLogout$1(status) {
|
|
|
18752
18939
|
return status.kind === "authed" || status.kind === "error" || status.kind === "authorizing";
|
|
18753
18940
|
}
|
|
18754
18941
|
function searchPlaceholder(tab) {
|
|
18755
|
-
if (tab === "skills") return "search skills
|
|
18756
|
-
if (tab === "mcps") return "search MCP servers
|
|
18757
|
-
if (tab === "extensions") return "search extensions
|
|
18758
|
-
if (tab === "keybindings") return "search keybindings
|
|
18759
|
-
if (tab === "authentication") return "search providers
|
|
18760
|
-
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…`;
|
|
18761
18948
|
}
|
|
18762
18949
|
function displayPath$1(path, home) {
|
|
18763
18950
|
if (home && path.startsWith(`${home}/`)) return `~/${path.slice(home.length + 1)}`;
|
|
@@ -19849,6 +20036,322 @@ function blockSummary(turns) {
|
|
|
19849
20036
|
return parts.length === 0 ? "(empty)" : parts.join(" · ");
|
|
19850
20037
|
}
|
|
19851
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
|
|
19852
20355
|
//#region src/tui/app.tsx
|
|
19853
20356
|
const BUILTIN_COMPLETION_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
19854
20357
|
"commands",
|
|
@@ -21213,6 +21716,51 @@ function AppShell() {
|
|
|
21213
21716
|
getSmooth: useCallback(() => smoothStreamingRef.current, []),
|
|
21214
21717
|
maxTranscriptEvents: transcriptRevealHoldRef.current ? null : void 0
|
|
21215
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]);
|
|
21216
21764
|
streamRef.current = stream;
|
|
21217
21765
|
const makePicked = useCallback((provider, modelId) => {
|
|
21218
21766
|
const descriptor = providerRegistry[provider.key];
|
|
@@ -21782,10 +22330,23 @@ function AppShell() {
|
|
|
21782
22330
|
provider: to.provider,
|
|
21783
22331
|
model: to.model
|
|
21784
22332
|
});
|
|
21785
|
-
|
|
21786
|
-
|
|
21787
|
-
|
|
21788
|
-
});
|
|
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();
|
|
21789
22350
|
});
|
|
21790
22351
|
agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
|
|
21791
22352
|
registerInFlightTool({
|
|
@@ -21930,18 +22491,18 @@ function AppShell() {
|
|
|
21930
22491
|
const reason = lastFinishReasonRef.current;
|
|
21931
22492
|
lastFinishReasonRef.current = void 0;
|
|
21932
22493
|
if (sessionRef.current?.id !== session.id) return;
|
|
21933
|
-
if (reason === "length")
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
});
|
|
21937
|
-
else if (reason === "pause")
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
});
|
|
21941
|
-
else if (reason === "content-filter")
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
});
|
|
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.");
|
|
21945
22506
|
});
|
|
21946
22507
|
agent.hooks.hook("compact:start", ({ usedFraction }) => {
|
|
21947
22508
|
setCompacting(true);
|
|
@@ -21963,10 +22524,10 @@ function AppShell() {
|
|
|
21963
22524
|
if (!(sessionRef.current?.id === session.id)) return;
|
|
21964
22525
|
if (ctx.status === "error") {
|
|
21965
22526
|
const msg = errorMessage(ctx.error);
|
|
21966
|
-
if (!/abort/i.test(msg))
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
});
|
|
22527
|
+
if (!/abort/i.test(msg)) emitNotice({
|
|
22528
|
+
severity: "error",
|
|
22529
|
+
label: "auto-compaction"
|
|
22530
|
+
}, msg);
|
|
21970
22531
|
return;
|
|
21971
22532
|
}
|
|
21972
22533
|
const markerTurn = [...session.turns].reverse().find((t) => t.content.some((b) => b.type === "compact-summary"));
|
|
@@ -21978,10 +22539,10 @@ function AppShell() {
|
|
|
21978
22539
|
agent.hooks.hook("history:repair", (ctx) => {
|
|
21979
22540
|
if (sessionRef.current?.id !== session.id) return;
|
|
21980
22541
|
const blocks = `${ctx.removedBlocks} stale reasoning block${ctx.removedBlocks === 1 ? "" : "s"}`;
|
|
21981
|
-
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
});
|
|
22542
|
+
emitNotice({
|
|
22543
|
+
severity: "warn",
|
|
22544
|
+
label: "history repair"
|
|
22545
|
+
}, `removed ${blocks} the provider rejected${ctx.persisted ? "" : " (session not yet persisted)"}`);
|
|
21985
22546
|
});
|
|
21986
22547
|
const spawnStartedAt = /* @__PURE__ */ new Map();
|
|
21987
22548
|
agent.hooks.hook("spawn:before", ({ id, task, depth, model, resumesChildId }) => {
|
|
@@ -22023,11 +22584,13 @@ function AppShell() {
|
|
|
22023
22584
|
});
|
|
22024
22585
|
});
|
|
22025
22586
|
agent.hooks.hook("spawn:error", ({ id, depth, error }) => {
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22587
|
+
emitNotice({
|
|
22588
|
+
severity: "error",
|
|
22589
|
+
label: "subagent"
|
|
22590
|
+
}, `[${id}] ${error.message}`, {
|
|
22029
22591
|
childId: id,
|
|
22030
|
-
depth: depth ?? 1
|
|
22592
|
+
depth: depth ?? 1,
|
|
22593
|
+
session
|
|
22031
22594
|
});
|
|
22032
22595
|
});
|
|
22033
22596
|
agent.hooks.hook("child:stream:thinking", ({ delta, childId, depth, turnId }) => {
|
|
@@ -22438,10 +23001,10 @@ function AppShell() {
|
|
|
22438
23001
|
updatedAt: Date.now()
|
|
22439
23002
|
} : prev);
|
|
22440
23003
|
} catch (err) {
|
|
22441
|
-
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
});
|
|
23004
|
+
emitNotice({
|
|
23005
|
+
severity: "error",
|
|
23006
|
+
label: "run"
|
|
23007
|
+
}, errorMessage(err), { session });
|
|
22445
23008
|
} finally {
|
|
22446
23009
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
22447
23010
|
await drainFollowUpsRef.current().catch((err) => debugLog("resume-interaction: follow-up drain failed", err));
|
|
@@ -22562,6 +23125,7 @@ function AppShell() {
|
|
|
22562
23125
|
]);
|
|
22563
23126
|
const [availableProviders, setAvailableProviders] = useState([]);
|
|
22564
23127
|
const refreshAvailableProviders = useCallback(() => {
|
|
23128
|
+
clearProviderUsageCache();
|
|
22565
23129
|
const detected = detectAuth(config.paths.userDir, providerRegistry);
|
|
22566
23130
|
setAvailableProviders(detected.filter((p) => p.available));
|
|
22567
23131
|
}, [config.paths.userDir, providerRegistry]);
|
|
@@ -23363,10 +23927,10 @@ function AppShell() {
|
|
|
23363
23927
|
} : prev);
|
|
23364
23928
|
maybeAutoGenerateTitleRef.current(session);
|
|
23365
23929
|
} catch (err) {
|
|
23366
|
-
|
|
23367
|
-
|
|
23368
|
-
|
|
23369
|
-
});
|
|
23930
|
+
emitNotice({
|
|
23931
|
+
severity: "error",
|
|
23932
|
+
label: "run"
|
|
23933
|
+
}, errorMessage(err), { session });
|
|
23370
23934
|
} finally {
|
|
23371
23935
|
chatReferenceTokensRef.current = [];
|
|
23372
23936
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
@@ -23726,15 +24290,15 @@ function AppShell() {
|
|
|
23726
24290
|
});
|
|
23727
24291
|
emitBlock({ text: `\`❯ /${invocation.name}${invocation.args ? ` ${invocation.args}` : ""}\`` });
|
|
23728
24292
|
const result = await executeCommand(command, ctx);
|
|
23729
|
-
if (result.error)
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
});
|
|
24293
|
+
if (result.error) emitNotice({
|
|
24294
|
+
severity: "error",
|
|
24295
|
+
label: "command"
|
|
24296
|
+
}, `/${invocation.name}: ${errorMessage(result.error)}`, { session });
|
|
23733
24297
|
} catch (err) {
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
});
|
|
24298
|
+
emitNotice({
|
|
24299
|
+
severity: "error",
|
|
24300
|
+
label: "command"
|
|
24301
|
+
}, `/${invocation.name}: ${errorMessage(err)}`, { session });
|
|
23738
24302
|
} finally {
|
|
23739
24303
|
commandAbortRef.current = null;
|
|
23740
24304
|
await flushCommandTurn();
|
|
@@ -23847,11 +24411,10 @@ function AppShell() {
|
|
|
23847
24411
|
}]
|
|
23848
24412
|
}]).catch((err) => debugLog("bash: appendTurns failed", err));
|
|
23849
24413
|
} catch (err) {
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
|
|
23854
|
-
});
|
|
24414
|
+
emitNotice({
|
|
24415
|
+
severity: "error",
|
|
24416
|
+
label: "shell"
|
|
24417
|
+
}, `!${command}: ${errorMessage(err)}`, { session });
|
|
23855
24418
|
} finally {
|
|
23856
24419
|
commandAbortRef.current = null;
|
|
23857
24420
|
setCurrentSession((prev) => prev ? {
|
|
@@ -25248,6 +25811,15 @@ function AppShell() {
|
|
|
25248
25811
|
}).catch((err) => debugLog("getContextBreakdown failed", err));
|
|
25249
25812
|
return;
|
|
25250
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
|
+
}
|
|
25251
25823
|
if (matchesBinding(key, keybindings.openKeybindings) && screen !== "auth") {
|
|
25252
25824
|
modal.open(/* @__PURE__ */ jsx$1(KeybindingsModal, {
|
|
25253
25825
|
bindings: keybindings,
|
|
@@ -25525,6 +26097,22 @@ function AppShell() {
|
|
|
25525
26097
|
providerRegistry
|
|
25526
26098
|
]);
|
|
25527
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]);
|
|
25528
26116
|
useEffect(() => () => {
|
|
25529
26117
|
teardown();
|
|
25530
26118
|
}, [teardown]);
|
|
@@ -25614,7 +26202,9 @@ function AppShell() {
|
|
|
25614
26202
|
compactionTokens,
|
|
25615
26203
|
compactionStartedAt,
|
|
25616
26204
|
retry: retryInfo,
|
|
25617
|
-
contextKey: keybindings.openContextPanel
|
|
26205
|
+
contextKey: keybindings.openContextPanel,
|
|
26206
|
+
usage: footerUsage,
|
|
26207
|
+
usageKey: keybindings.openUsagePanel
|
|
25618
26208
|
}),
|
|
25619
26209
|
showQuitConfirm && /* @__PURE__ */ jsxs("box", {
|
|
25620
26210
|
position: "absolute",
|
|
@@ -25692,6 +26282,67 @@ function effortForModel(descriptor, modelId, remembered) {
|
|
|
25692
26282
|
return remembered?.[modelId] ?? "medium";
|
|
25693
26283
|
}
|
|
25694
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
|
|
25695
26346
|
//#region src/tui/tree-sitter.ts
|
|
25696
26347
|
/**
|
|
25697
26348
|
* Register Tree-sitter parsers for the languages we'd like highlighted
|
|
@@ -27096,65 +27747,85 @@ async function runTui(options = {}) {
|
|
|
27096
27747
|
const inlineExtensions = (options.extensions ?? []).map(inlineLoadedExtension);
|
|
27097
27748
|
const trustProjectExtensions = options.trustProjectExtensions ?? (preState.settings?.loadProjectExtensions === true || process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === "1");
|
|
27098
27749
|
const logging = initTuiLogging({ stateDir: paths.stateDir });
|
|
27099
|
-
|
|
27100
|
-
|
|
27101
|
-
|
|
27102
|
-
|
|
27103
|
-
|
|
27104
|
-
|
|
27105
|
-
|
|
27106
|
-
|
|
27107
|
-
|
|
27108
|
-
|
|
27109
|
-
|
|
27110
|
-
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
promptBridge,
|
|
27114
|
-
hostRuntime,
|
|
27115
|
-
readSettings: readLiveSettings
|
|
27116
|
-
});
|
|
27117
|
-
bootTick("runTui:after-buildExtensionRegistry");
|
|
27118
|
-
const config = resolveConfig({
|
|
27119
|
-
...options,
|
|
27120
|
-
extensionScanPaths,
|
|
27121
|
-
pluginScanPaths,
|
|
27122
|
-
extensionRegistry,
|
|
27123
|
-
trustProjectExtensions,
|
|
27124
|
-
...options.launch && !options.preset && !options.agents ? { preset: basic_default } : {},
|
|
27125
|
-
store: options.store ?? ((resolved) => createTuiStore(options.launch?.sessionDb ?? resolved.db))
|
|
27126
|
-
});
|
|
27127
|
-
bootTick("runTui:after-resolveConfig");
|
|
27128
|
-
let done = () => {};
|
|
27129
|
-
const exited = new Promise((resolve) => {
|
|
27130
|
-
done = resolve;
|
|
27131
|
-
});
|
|
27132
|
-
const bootFps = clampFps(config.initialSettings.targetFps ?? DEFAULT_SETTINGS.targetFps);
|
|
27133
|
-
const gatherStats = !!process.env.ZIDANE_DEBUG;
|
|
27134
|
-
const renderer = await createCliRenderer({
|
|
27135
|
-
exitOnCtrlC: false,
|
|
27136
|
-
onDestroy: () => done(),
|
|
27137
|
-
debounceDelay: 0,
|
|
27138
|
-
targetFps: bootFps,
|
|
27139
|
-
maxFps: bootFps,
|
|
27140
|
-
useMouse: config.initialSettings.mouseInteractions ?? DEFAULT_SETTINGS.mouseInteractions,
|
|
27141
|
-
gatherStats
|
|
27142
|
-
});
|
|
27143
|
-
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
|
+
};
|
|
27144
27764
|
try {
|
|
27145
|
-
|
|
27146
|
-
|
|
27147
|
-
|
|
27148
|
-
|
|
27149
|
-
|
|
27150
|
-
|
|
27151
|
-
|
|
27152
|
-
|
|
27153
|
-
|
|
27154
|
-
|
|
27155
|
-
|
|
27156
|
-
|
|
27157
|
-
|
|
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();
|
|
27158
27829
|
}
|
|
27159
27830
|
process.exit(0);
|
|
27160
27831
|
}
|