zidane 6.1.13 → 6.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-BqfKqQ9x.js → acp-CYJvnqPa.js} +2 -2
- package/dist/{acp-BqfKqQ9x.js.map → acp-CYJvnqPa.js.map} +1 -1
- package/dist/acp-cli.js +4 -4
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-CZ3WSWqR.js → agent-D11SxCvA.js} +155 -75
- package/dist/agent-D11SxCvA.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/{anthropic-CV_OSYVD.js → anthropic-DdlSgT6K.js} +47 -25
- package/dist/anthropic-DdlSgT6K.js.map +1 -0
- package/dist/{auth-D5FfJtLf.js → auth-Ys_7TmRj.js} +2 -2
- package/dist/{auth-D5FfJtLf.js.map → auth-Ys_7TmRj.js.map} +1 -1
- package/dist/{cache-telemetry-BKAwsNxm.js → cache-telemetry-KJ7B-_Gh.js} +8 -3
- package/dist/cache-telemetry-KJ7B-_Gh.js.map +1 -0
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +2 -2
- package/dist/chat.d.ts +12 -5
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +7 -7
- package/dist/{completion-core-CXYSVhZo.js → completion-core-BRo86R_L.js} +23 -10
- package/dist/completion-core-BRo86R_L.js.map +1 -0
- 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 +2 -2
- package/dist/extensions.js +2 -2
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-X02SI0nt.d.ts → index-CNyYEOy0.d.ts} +351 -42
- package/dist/index-CNyYEOy0.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/{generate-data-WL_yGVL6.js → inline-autocomplete-BAwBcZtD.js} +191 -6
- package/dist/inline-autocomplete-BAwBcZtD.js.map +1 -0
- 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/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/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/providers.js +1 -1
- package/dist/restate.d.ts +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-kg8O6wHZ.d.ts → tool-formatters-B7oWiy5m.d.ts} +2 -2
- package/dist/tool-formatters-B7oWiy5m.d.ts.map +1 -0
- 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-DAF4RbyU.d.ts → transcript-anchors-BUZ915pM.d.ts} +2 -2
- package/dist/{transcript-anchors-DAF4RbyU.d.ts.map → transcript-anchors-BUZ915pM.d.ts.map} +1 -1
- package/dist/{transcript-anchors-DAf0Mr_9.js → transcript-anchors-cc4gsU-j.js} +23 -8
- package/dist/transcript-anchors-cc4gsU-j.js.map +1 -0
- package/dist/tui.d.ts +25 -15
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +539 -57
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-zkZ1L0eb.d.ts → turn-operations-2Ou_DkbC.d.ts} +3 -3
- package/dist/{turn-operations-zkZ1L0eb.d.ts.map → turn-operations-2Ou_DkbC.d.ts.map} +1 -1
- package/dist/{turn-operations-D9p-y9EC.js → turn-operations-CXjRaltC.js} +72 -2
- package/dist/{turn-operations-D9p-y9EC.js.map → turn-operations-CXjRaltC.js.map} +1 -1
- package/dist/types-BIarq1qE.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/EXTENSIONS.md +43 -6
- package/package.json +2 -1
- package/scripts/eval.ts +21 -3
- package/dist/agent-CZ3WSWqR.js.map +0 -1
- package/dist/anthropic-CV_OSYVD.js.map +0 -1
- package/dist/cache-telemetry-BKAwsNxm.js.map +0 -1
- package/dist/completion-core-CXYSVhZo.js.map +0 -1
- package/dist/generate-data-WL_yGVL6.js.map +0 -1
- package/dist/index-X02SI0nt.d.ts.map +0 -1
- package/dist/tool-formatters-kg8O6wHZ.d.ts.map +0 -1
- package/dist/transcript-anchors-DAf0Mr_9.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as useMcpAuthState, $n as buildPlanSystem, $t as restoreChainFallbacks, A as runOAuthLogin, Bt as createDiscoverySlot, Ct as SETTINGS_CHOICES, D as formatPathForCwd, Dt as clampFps, Et as SettingsProvider, G as buildVisibleMcpRows, Gt as useConfig, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as buildRunModel, K as indexOfServerRow, Kt as resolveConfig, L as loadMcpToolsCache, M as buildMcpServers, Mt as resolveTheme, O as fetchOAuthRedirect, Ot as useSettings, P as discoverProjectMcps, Q as useMcpAuthDispatch, Qn as buildBuildSystem, Qt as providerKeySetChanged, R as mcpToolsCachePath, St as SETTINGS_CATEGORIES, T as suggestSafelistEntries, Tn as accentColor, Tt as SETTINGS_TOGGLES, U as useMcpToolToggleMap, Ut as useDiscoveryOptional, V as subscribeMcpToolsCache, Vn as useActiveTodos, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as chainsEqual, Yt as chainPositionOf, Z as McpAuthProvider, Zt as filterChainFallbacks, _ as useSafeModeQueue, _n as discoverAgentsMd, _r as toolCallPreview, _t as buildHints, a as useSurfaces, an as bootTick, at as buildResumedToolResultsTurn, b as getSafelist, br as usageByModel, bt as useEnabledToggleSet, c as useStreamBuffer, cr as lastContextSizeFromTurns, ct as makeRequestInteraction, d as discoverProjectSkills, dr as marginTopFor, dt as useInteractionsActions, er as envSection, et as getMcpAuthStatus, ft as useInteractionsQueue, g as useSafeModeActions, gn as chatAutoCompactBehavior, gt as truncateTrailing, h as SafeModeProvider, hr as sumRunCosts, ht as hintsLength, i as useSelectStyle, ir as compactSummaryEvent, it as PRESENT_PLAN_TOOL, j as supportsOAuth, jn as TODO_STATUS_GLYPHS, jt as resolveChipColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, lr as listSessionMeta, lt as pendingInteractionsFromTurns, m as writeSessionExport, mr as stripSpawnTokensLine, mt as clipHintsToWidth, n as ThemeProvider, nn as useCompletion, nr as createChatTurnsReadTool, nt as ASK_USER_TOOL, on as buildUpdateHint, or as deriveSessionTitle, ot as createInteractionTools, pr as serverToolResultSummary, pt as EMPTY_HINTS, q as parentServerName, qt as resolveStoragePaths, r as useColors, rn as tryOpenBrowser, rt as InteractionsProvider, s as useTheme, sn as useUpdateCheck, sr as eventsFromTurns, t as computeTurnAnchors, tn as toggleChainEntry, tr as chatReferenceTokensForPrompt, u as defaultSkillScanPaths, ur as loadState, vr as toolResultText, wt as SETTINGS_INPUTS, x as isOnSafelist, xt as DEFAULT_SETTINGS, y as addToSafelist, yr as updateToolEventOutcomes, yt as listProjectFiles, z as refreshMcpToolsCatalog } from "./transcript-anchors-
|
|
2
|
-
import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as
|
|
1
|
+
import { $ as useMcpAuthState, $n as buildPlanSystem, $t as restoreChainFallbacks, A as runOAuthLogin, Bt as createDiscoverySlot, Ct as SETTINGS_CHOICES, D as formatPathForCwd, Dt as clampFps, Et as SettingsProvider, G as buildVisibleMcpRows, Gt as useConfig, Ht as useDiscovery, J as createFileMcpCredentialStore, Jt as buildRunModel, K as indexOfServerRow, Kt as resolveConfig, L as loadMcpToolsCache, M as buildMcpServers, Mt as resolveTheme, O as fetchOAuthRedirect, Ot as useSettings, P as discoverProjectMcps, Q as useMcpAuthDispatch, Qn as buildBuildSystem, Qt as providerKeySetChanged, R as mcpToolsCachePath, St as SETTINGS_CATEGORIES, T as suggestSafelistEntries, Tn as accentColor, Tt as SETTINGS_TOGGLES, U as useMcpToolToggleMap, Ut as useDiscoveryOptional, V as subscribeMcpToolsCache, Vn as useActiveTodos, Vt as DiscoveryProvider, Wt as ConfigProvider, Xt as chainsEqual, Yt as chainPositionOf, Z as McpAuthProvider, Zt as filterChainFallbacks, _ as useSafeModeQueue, _n as discoverAgentsMd, _r as toolCallPreview, _t as buildHints, a as useSurfaces, an as bootTick, at as buildResumedToolResultsTurn, b as getSafelist, br as usageByModel, bt as useEnabledToggleSet, c as useStreamBuffer, cr as lastContextSizeFromTurns, ct as makeRequestInteraction, d as discoverProjectSkills, dr as marginTopFor, dt as useInteractionsActions, er as envSection, et as getMcpAuthStatus, ft as useInteractionsQueue, g as useSafeModeActions, gn as chatAutoCompactBehavior, gt as truncateTrailing, h as SafeModeProvider, hr as sumRunCosts, ht as hintsLength, i as useSelectStyle, ir as compactSummaryEvent, it as PRESENT_PLAN_TOOL, j as supportsOAuth, jn as TODO_STATUS_GLYPHS, jt as resolveChipColor, k as oauthUsesManualCodePaste, l as buildSkillsConfig, lr as listSessionMeta, lt as pendingInteractionsFromTurns, m as writeSessionExport, mr as stripSpawnTokensLine, mt as clipHintsToWidth, n as ThemeProvider, nn as useCompletion, nr as createChatTurnsReadTool, nt as ASK_USER_TOOL, on as buildUpdateHint, or as deriveSessionTitle, ot as createInteractionTools, pr as serverToolResultSummary, pt as EMPTY_HINTS, q as parentServerName, qt as resolveStoragePaths, r as useColors, rn as tryOpenBrowser, rt as InteractionsProvider, s as useTheme, sn as useUpdateCheck, sr as eventsFromTurns, t as computeTurnAnchors, tn as toggleChainEntry, tr as chatReferenceTokensForPrompt, u as defaultSkillScanPaths, ur as loadState, vr as toolResultText, wt as SETTINGS_INPUTS, x as isOnSafelist, xt as DEFAULT_SETTINGS, y as addToSafelist, yr as updateToolEventOutcomes, yt as listProjectFiles, z as refreshMcpToolsCatalog } from "./transcript-anchors-cc4gsU-j.js";
|
|
2
|
+
import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, dn as sanitizeExtensionConfigActionPatch, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, hn as effectiveContextWindow, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as OUTPUT_RESERVE_TOKENS, nn as parseExtensionScanPathEnv, on as coerceExtensionConfigInput, q as updateInstalledExtension, rn as scanExtensionDirectories, rt as partitionExtensionCatalog, t as createAgent, tt as filterExtensionRegistry, un as resolveExtensionConfigValue, wt as looksBinary, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as KEYBINDING_KEY_COL_WIDTH } from "./agent-D11SxCvA.js";
|
|
3
3
|
import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId, n as compactPath, o as formatTaskStatus, r as fmtCost, s as formatTaskSummary, t as ageString } from "./format-BF745pLL.js";
|
|
4
4
|
import { n as appendCapped } from "./output-cap-kBUHBlnq.js";
|
|
5
5
|
import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-A4ep-iJs.js";
|
|
@@ -12,14 +12,14 @@ import { replaceDynamicSection } from "./prompt.js";
|
|
|
12
12
|
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-D7JSEMME.js";
|
|
13
13
|
import { o as withTimeout } from "./timeout-BDetbuN6.js";
|
|
14
14
|
import { n as connectMcpServers, u as McpOAuthProvider } from "./mcp-CkHUripi.js";
|
|
15
|
-
import {
|
|
16
|
-
import { s as resolveCompletionSubmit } from "./completion-core-
|
|
15
|
+
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-BAwBcZtD.js";
|
|
16
|
+
import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
|
|
17
17
|
import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-BlmY7QhV.js";
|
|
18
18
|
import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-Bp_tRj_e.js";
|
|
19
|
-
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning } from "./auth-
|
|
19
|
+
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning } from "./auth-Ys_7TmRj.js";
|
|
20
20
|
import { createDockerContext } from "./contexts/docker.js";
|
|
21
21
|
import { createTuiStore } from "./session/sqlite.js";
|
|
22
|
-
import { A as
|
|
22
|
+
import { $ as buildEditOutcomesAnnotation, A as blendHsl, B as turnSelectionOwnership, C as stripHtmlCommentsOutsideCode, E as uniqueSkillNamesFromReferences, F as isTurnHighlighted, H as buildContextualDiff, I as isUserBashEvent, J as filetypeFromPath, L as isVisible, M as BASH_CALL_ID_PREFIX, N as EDIT_TOOL_NAMES, O as createFilesCompletionProvider, P as isEditErrorResult, R as promptHistoryFromEvents, S as splitMarkdownCodeBlocks, T as createSkillsCompletionProvider, U as buildUnifiedDiff, W as clipDiffForRender, Y as previewEditPayload, Z as summarizeEditPayload, _ as filterModelCatalog, a as TOOL_DISPLAY, at as stripEditOutcomesAnnotation, b as chunkMarkdownForRender, d as finalizeStreamingMarkdown, f as finalizeStreamingMarkdownForOwner, g as buildModelCatalog, h as splitPromptSegments, i as turnAsText, it as rewriteMultiEditHeader, j as buildLinearRamp, lt as createChatsCompletionProvider, m as turnContextSize, n as deleteTurnSafely, nt as parseEditOutcomesFromResult, o as displayNameFor, ot as summarizeOutcomes, q as extractEditPayload, r as truncateTurnsAt, rt as resolveApprovalForPayload, s as formatToolCall, tt as mergeApprovalAndBodyOutcomes, u as capTranscriptEvents, v as indexOfEntry, x as normalizeMarkdownCodeFences, z as selectableTurnIds } from "./turn-operations-CXjRaltC.js";
|
|
23
23
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
24
24
|
import { Buffer } from "node:buffer";
|
|
25
25
|
import * as fs from "node:fs";
|
|
@@ -50,16 +50,18 @@ function scoreCommands(catalog, query) {
|
|
|
50
50
|
const scored = [];
|
|
51
51
|
for (const command of catalog) {
|
|
52
52
|
const name = command.name.toLowerCase();
|
|
53
|
+
const aliases = (command.aliases ?? []).map((a) => a.toLowerCase());
|
|
54
|
+
const group = command.group?.toLowerCase();
|
|
53
55
|
const desc = command.description.toLowerCase();
|
|
54
56
|
if (q.length === 0) scored.push({
|
|
55
57
|
command,
|
|
56
58
|
rank: 4
|
|
57
59
|
});
|
|
58
|
-
else if (name.startsWith(q)) scored.push({
|
|
60
|
+
else if (name.startsWith(q) || aliases.some((a) => a.startsWith(q))) scored.push({
|
|
59
61
|
command,
|
|
60
62
|
rank: 0
|
|
61
63
|
});
|
|
62
|
-
else if (name.includes(q)) scored.push({
|
|
64
|
+
else if (name.includes(q) || aliases.some((a) => a.includes(q)) || group !== void 0 && q.length >= 2 && group.startsWith(q)) scored.push({
|
|
63
65
|
command,
|
|
64
66
|
rank: 1
|
|
65
67
|
});
|
|
@@ -76,7 +78,7 @@ function scoreCommands(catalog, query) {
|
|
|
76
78
|
return scored.map(({ command }) => ({
|
|
77
79
|
id: command.name,
|
|
78
80
|
label: command.name,
|
|
79
|
-
description: command.description,
|
|
81
|
+
description: command.aliases?.length ? `${command.description} (alias: ${command.aliases.map((a) => `/${a}`).join(", ")})` : command.description,
|
|
80
82
|
insertText: `/${command.name} `,
|
|
81
83
|
data: command
|
|
82
84
|
}));
|
|
@@ -97,7 +99,7 @@ function createCommandsCompletionProvider(opts) {
|
|
|
97
99
|
if (spaceIdx !== -1) {
|
|
98
100
|
if (ctx.text.trimStart()[0] !== "/") return [];
|
|
99
101
|
const typed = query.slice(0, spaceIdx).toLowerCase();
|
|
100
|
-
const command = opts.getCommands().find((c) => c.name.toLowerCase() === typed);
|
|
102
|
+
const command = opts.getCommands().find((c) => c.name.toLowerCase() === typed || c.aliases?.some((a) => a.toLowerCase() === typed));
|
|
101
103
|
if (!command?.completeArgs) return [];
|
|
102
104
|
const argsPrefix = query.slice(spaceIdx + 1).replace(/^\s+/, "");
|
|
103
105
|
return Promise.resolve(command.completeArgs(argsPrefix)).then((suggestions) => (suggestions ?? []).map((s) => ({
|
|
@@ -1254,6 +1256,36 @@ function RenderCall({ render }) {
|
|
|
1254
1256
|
return /* @__PURE__ */ jsx$1(Fragment, { children: render() });
|
|
1255
1257
|
}
|
|
1256
1258
|
//#endregion
|
|
1259
|
+
//#region src/tui/terminal-sanitize.ts
|
|
1260
|
+
/**
|
|
1261
|
+
* Terminal-display sanitation.
|
|
1262
|
+
*
|
|
1263
|
+
* Provider and model text is written into a terminal cell buffer that does
|
|
1264
|
+
* NOT neutralize control bytes: a raw ESC sequence in model output would be
|
|
1265
|
+
* forwarded to the user's terminal at paint time (recolor, cursor moves,
|
|
1266
|
+
* window retitling — classic escape injection). Applied once at the
|
|
1267
|
+
* rendering boundary — persistence and provider round-trips keep raw text.
|
|
1268
|
+
*
|
|
1269
|
+
* Lives in `src/tui` (not `src/chat`): the chat layer is renderer-agnostic
|
|
1270
|
+
* by invariant and must not carry escape-sequence literals.
|
|
1271
|
+
*/
|
|
1272
|
+
/** CSI sequences: ESC [ params intermediates final. */
|
|
1273
|
+
const ANSI_CSI = /\u001B\[[0-9:;<=>?]*[ -/]*[@-~]/g;
|
|
1274
|
+
/** OSC sequences (window title, hyperlinks, …), terminated by BEL or ST. */
|
|
1275
|
+
const ANSI_OSC = /\u001B\][\s\S]*?(?:\u0007|\u001B\\)/g;
|
|
1276
|
+
/** Any remaining ESC + one char (SS2/SS3, unterminated introducers, …). */
|
|
1277
|
+
const ANSI_ESC_OTHER = /\u001B[\s\S]?/g;
|
|
1278
|
+
/** C0 controls except \n and \t (\r is normalized to \n beforehand), plus DEL. */
|
|
1279
|
+
const CONTROL_CHARS = /[\u0000-\u0008\v\f\u000E-\u001F\u007F]/g;
|
|
1280
|
+
/**
|
|
1281
|
+
* Make arbitrary provider/model/tool text safe to paint into a terminal
|
|
1282
|
+
* cell buffer: normalize \r to \n, drop ANSI escape sequences, drop the
|
|
1283
|
+
* remaining C0 control characters (keeping \n and \t).
|
|
1284
|
+
*/
|
|
1285
|
+
function stripTerminalControlChars(text) {
|
|
1286
|
+
return text.replace(/\r\n?/g, "\n").replace(ANSI_OSC, "").replace(ANSI_CSI, "").replace(ANSI_ESC_OTHER, "").replace(CONTROL_CHARS, "");
|
|
1287
|
+
}
|
|
1288
|
+
//#endregion
|
|
1257
1289
|
//#region src/tui/theme.ts
|
|
1258
1290
|
/**
|
|
1259
1291
|
* Resolve a theme color string to an OpenTUI `RGBA`.
|
|
@@ -2138,7 +2170,8 @@ function rowStyle(paddingLeft) {
|
|
|
2138
2170
|
function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = false, selected = false, settleCodeEpoch = 0 }) {
|
|
2139
2171
|
const COLOR = useColors();
|
|
2140
2172
|
const { settings } = useSettings();
|
|
2141
|
-
const
|
|
2173
|
+
const cleanText = stripTerminalControlChars(event.text);
|
|
2174
|
+
const safeText = cleanText === "" ? " " : cleanText;
|
|
2142
2175
|
const effectiveDepth = Math.max(0, (event.depth ?? 0) - depthOffset);
|
|
2143
2176
|
const row = rowStyle(indentFor(settings.hideSubagentOutput && (event.kind === "spawn-start" || event.kind === "spawn-end") ? 0 : effectiveDepth));
|
|
2144
2177
|
const child = isChild(event);
|
|
@@ -2158,7 +2191,8 @@ function EventLineImpl({ event, previous, depthOffset = 0, hideChildLabel = fals
|
|
|
2158
2191
|
});
|
|
2159
2192
|
case "thinking": {
|
|
2160
2193
|
const startsRun = !(previous?.kind === "thinking" && previous.childId === event.childId);
|
|
2161
|
-
const thinkingText = safeText.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
2194
|
+
const thinkingText = safeText.replace(/<!--[\s\S]*?-->/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").trimEnd();
|
|
2195
|
+
if (thinkingText.trim() === "") return null;
|
|
2162
2196
|
return /* @__PURE__ */ jsx$1("box", {
|
|
2163
2197
|
style: row,
|
|
2164
2198
|
children: /* @__PURE__ */ jsxs("box", {
|
|
@@ -2900,7 +2934,7 @@ const MarkdownBlock = memo(({ text, dim, selected = false, settleCodeEpoch = 0 }
|
|
|
2900
2934
|
dimmed: dim
|
|
2901
2935
|
});
|
|
2902
2936
|
const renderer = useRenderer();
|
|
2903
|
-
const content = normalizeMarkdownCodeFences(text.replace(/^\n+|\n+$/g, ""));
|
|
2937
|
+
const content = normalizeMarkdownCodeFences(stripHtmlCommentsOutsideCode(stripTerminalControlChars(text)).replace(/^\n+|\n+$/g, ""));
|
|
2904
2938
|
const chunks = useMemo(() => chunkMarkdownForRender(content), [content]);
|
|
2905
2939
|
const bag = useRef({
|
|
2906
2940
|
ctx: renderer,
|
|
@@ -5998,17 +6032,48 @@ function CompletionPopup({ state, visibleRows = 6 }) {
|
|
|
5998
6032
|
});
|
|
5999
6033
|
height = 3;
|
|
6000
6034
|
} else {
|
|
6001
|
-
const
|
|
6035
|
+
const showSections = state.items.some((it) => it.section);
|
|
6036
|
+
const displayRows = [];
|
|
6037
|
+
let prevSection;
|
|
6038
|
+
for (const [index, item] of state.items.entries()) {
|
|
6039
|
+
if (showSections && item.section && item.section !== prevSection) {
|
|
6040
|
+
displayRows.push({
|
|
6041
|
+
kind: "header",
|
|
6042
|
+
section: item.section
|
|
6043
|
+
});
|
|
6044
|
+
prevSection = item.section;
|
|
6045
|
+
}
|
|
6046
|
+
displayRows.push({
|
|
6047
|
+
kind: "item",
|
|
6048
|
+
item,
|
|
6049
|
+
index
|
|
6050
|
+
});
|
|
6051
|
+
}
|
|
6052
|
+
const rows = Math.min(displayRows.length, visibleRows);
|
|
6053
|
+
const selectedRow = displayRows.findIndex((r) => r.kind === "item" && r.index === state.selectedIndex);
|
|
6002
6054
|
const half = Math.floor(rows / 2);
|
|
6003
|
-
let start = Math.max(0,
|
|
6004
|
-
if (start + rows >
|
|
6005
|
-
const slice =
|
|
6006
|
-
const showSections = new Set(state.items.map((it) => it.section).filter(Boolean)).size > 1;
|
|
6055
|
+
let start = Math.max(0, selectedRow - half);
|
|
6056
|
+
if (start + rows > displayRows.length) start = displayRows.length - rows;
|
|
6057
|
+
const slice = displayRows.slice(start, start + rows);
|
|
6007
6058
|
height = 2 + rows + 1;
|
|
6008
6059
|
body = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("box", {
|
|
6009
6060
|
style: { flexDirection: "column" },
|
|
6010
|
-
children: slice.map((
|
|
6011
|
-
|
|
6061
|
+
children: slice.map((row, i) => {
|
|
6062
|
+
if (row.kind === "header") return /* @__PURE__ */ jsx$1("box", {
|
|
6063
|
+
style: {
|
|
6064
|
+
height: 1,
|
|
6065
|
+
overflow: "hidden",
|
|
6066
|
+
flexShrink: 0
|
|
6067
|
+
},
|
|
6068
|
+
children: /* @__PURE__ */ jsx$1("text", {
|
|
6069
|
+
fg: COLOR.mute,
|
|
6070
|
+
wrapMode: "none",
|
|
6071
|
+
children: `── ${row.section} ${"─".repeat(200)}`
|
|
6072
|
+
})
|
|
6073
|
+
}, `header:${row.section}:${start + i}`);
|
|
6074
|
+
const { item, index } = row;
|
|
6075
|
+
const focused = index === state.selectedIndex;
|
|
6076
|
+
const titleFg = (item.providerId !== void 0 ? SURFACE.chips[item.providerId] : void 0)?.bg ?? (item.providerId === "commands" ? COLOR.accent : SELECT.textColor);
|
|
6012
6077
|
return /* @__PURE__ */ jsx$1("box", {
|
|
6013
6078
|
style: {
|
|
6014
6079
|
height: 1,
|
|
@@ -6024,12 +6089,8 @@ function CompletionPopup({ state, visibleRows = 6 }) {
|
|
|
6024
6089
|
fg: focused ? COLOR.brand : COLOR.mute,
|
|
6025
6090
|
children: focused ? "▶ " : " "
|
|
6026
6091
|
}),
|
|
6027
|
-
showSections && item.section && /* @__PURE__ */ jsxs("span", {
|
|
6028
|
-
fg: COLOR.mute,
|
|
6029
|
-
children: [item.section, " "]
|
|
6030
|
-
}),
|
|
6031
6092
|
/* @__PURE__ */ jsx$1("span", {
|
|
6032
|
-
fg: focused ? COLOR.brand :
|
|
6093
|
+
fg: focused ? COLOR.brand : titleFg,
|
|
6033
6094
|
children: item.label
|
|
6034
6095
|
}),
|
|
6035
6096
|
item.description && /* @__PURE__ */ jsxs("span", {
|
|
@@ -9463,7 +9524,7 @@ const MIME_BY_EXT = {
|
|
|
9463
9524
|
* default-value reference at the prop destructure.
|
|
9464
9525
|
*/
|
|
9465
9526
|
const EMPTY_QUEUED_MESSAGES = [];
|
|
9466
|
-
function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectedTurnId, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding, liveSession = null }) {
|
|
9527
|
+
function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectedTurnId, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding, liveSession = null }) {
|
|
9467
9528
|
const COLOR = useColors();
|
|
9468
9529
|
const titleText = session?.title ?? "New session";
|
|
9469
9530
|
const sessionShortcutKey = formatBindingForDisplay(sessionDetailsBinding);
|
|
@@ -9577,6 +9638,7 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
9577
9638
|
userPrompts,
|
|
9578
9639
|
onSubmit,
|
|
9579
9640
|
completionProviders,
|
|
9641
|
+
inlineAutocompleteProviders,
|
|
9580
9642
|
onCompletionTab,
|
|
9581
9643
|
enterSelectsCompletion,
|
|
9582
9644
|
onPopupOpenChange: handlePopupOpenChange,
|
|
@@ -9898,7 +9960,15 @@ function previewText(text) {
|
|
|
9898
9960
|
}
|
|
9899
9961
|
/** Stable empty providers reference — avoids `useCompletion` rerun on every render when no providers are wired. */
|
|
9900
9962
|
const EMPTY_PROVIDERS = [];
|
|
9901
|
-
|
|
9963
|
+
/** Stable empty reference for the inline-autocomplete effect's dep array. */
|
|
9964
|
+
const EMPTY_INLINE_PROVIDERS = [];
|
|
9965
|
+
/**
|
|
9966
|
+
* Debounce before querying inline-autocomplete providers. Long enough to
|
|
9967
|
+
* skip intermediate keystrokes while typing a path, short enough that the
|
|
9968
|
+
* ghost feels immediate on a pause.
|
|
9969
|
+
*/
|
|
9970
|
+
const INLINE_GHOST_DEBOUNCE_MS = 120;
|
|
9971
|
+
function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty, attachImageBinding, editorBinding }) {
|
|
9902
9972
|
const focused = useModalAwareFocus();
|
|
9903
9973
|
const renderer = useRenderer();
|
|
9904
9974
|
const COLOR = useColors();
|
|
@@ -9984,6 +10054,9 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
9984
10054
|
shellModeRef.current = on;
|
|
9985
10055
|
setShellMode(on);
|
|
9986
10056
|
}, []);
|
|
10057
|
+
const [ghost, setGhost] = useState(null);
|
|
10058
|
+
const ghostRef = useRef(null);
|
|
10059
|
+
ghostRef.current = ghost;
|
|
9987
10060
|
/**
|
|
9988
10061
|
* Pull the latest buffer state from the OpenTUI textarea ref. Called from
|
|
9989
10062
|
* `onContentChange` + `onKeyDown` so cursor moves (without text changes)
|
|
@@ -10001,9 +10074,49 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
10001
10074
|
text: ta.plainText,
|
|
10002
10075
|
cursor: ta.cursorOffset
|
|
10003
10076
|
});
|
|
10077
|
+
setGhost((prev) => patchInlineGhost(prev, ta.plainText, ta.cursorOffset));
|
|
10004
10078
|
const total = ta.editorView.getTotalVirtualLineCount();
|
|
10005
10079
|
setContentLines(Math.max(MIN_CONTENT_LINES, total));
|
|
10006
10080
|
}, []);
|
|
10081
|
+
const inlineProviders = inlineAutocompleteProviders ?? EMPTY_INLINE_PROVIDERS;
|
|
10082
|
+
const inlineEnabled = settings.inlineAutocomplete && inlineProviders.length > 0;
|
|
10083
|
+
useEffect(() => {
|
|
10084
|
+
const atWordBoundary = bufferState.cursor === bufferState.text.length || /\s/.test(bufferState.text[bufferState.cursor]);
|
|
10085
|
+
if (!inlineEnabled || popupOpen || selectMode !== null || shellMode || bufferState.text.length === 0 || !atWordBoundary) {
|
|
10086
|
+
setGhost(null);
|
|
10087
|
+
return;
|
|
10088
|
+
}
|
|
10089
|
+
const controller = new AbortController();
|
|
10090
|
+
const { text, cursor } = bufferState;
|
|
10091
|
+
const timer = setTimeout(() => {
|
|
10092
|
+
resolveInlineSuggestion(inlineProviders, {
|
|
10093
|
+
text,
|
|
10094
|
+
cursor,
|
|
10095
|
+
cwd: process.cwd(),
|
|
10096
|
+
signal: controller.signal
|
|
10097
|
+
}).then((res) => {
|
|
10098
|
+
if (controller.signal.aborted) return;
|
|
10099
|
+
const suffixes = (res?.suffixes ?? []).map((s) => s.split("\n")[0]).filter((s) => s.length > 0);
|
|
10100
|
+
setGhost(suffixes.length > 0 ? {
|
|
10101
|
+
suffixes,
|
|
10102
|
+
index: 0,
|
|
10103
|
+
text,
|
|
10104
|
+
cursor
|
|
10105
|
+
} : null);
|
|
10106
|
+
});
|
|
10107
|
+
}, INLINE_GHOST_DEBOUNCE_MS);
|
|
10108
|
+
return () => {
|
|
10109
|
+
controller.abort();
|
|
10110
|
+
clearTimeout(timer);
|
|
10111
|
+
};
|
|
10112
|
+
}, [
|
|
10113
|
+
bufferState,
|
|
10114
|
+
inlineEnabled,
|
|
10115
|
+
popupOpen,
|
|
10116
|
+
selectMode,
|
|
10117
|
+
shellMode,
|
|
10118
|
+
inlineProviders
|
|
10119
|
+
]);
|
|
10007
10120
|
/**
|
|
10008
10121
|
* Open the current prompt in `$EDITOR` (git-style round-trip). The
|
|
10009
10122
|
* external-editor module is pure; the TUI handoff is owned here — suspend
|
|
@@ -10255,6 +10368,30 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
10255
10368
|
return;
|
|
10256
10369
|
}
|
|
10257
10370
|
if (event.ctrl || event.shift || event.meta) return;
|
|
10371
|
+
if (ghostRef.current) {
|
|
10372
|
+
const g = ghostRef.current;
|
|
10373
|
+
if (event.name === "tab") {
|
|
10374
|
+
const ta = textareaRef.current;
|
|
10375
|
+
if (ta && ta.plainText === g.text && ta.cursorOffset === g.cursor) {
|
|
10376
|
+
event.preventDefault();
|
|
10377
|
+
ta.insertText(g.suffixes[g.index]);
|
|
10378
|
+
setGhost(null);
|
|
10379
|
+
syncBuffer();
|
|
10380
|
+
return;
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
if ((event.name === "up" || event.name === "down") && g.suffixes.length > 1) {
|
|
10384
|
+
event.preventDefault();
|
|
10385
|
+
const delta = event.name === "down" ? 1 : -1;
|
|
10386
|
+
setGhost({
|
|
10387
|
+
...g,
|
|
10388
|
+
index: (g.index + delta + g.suffixes.length) % g.suffixes.length
|
|
10389
|
+
});
|
|
10390
|
+
return;
|
|
10391
|
+
}
|
|
10392
|
+
if (event.name === "escape") setGhost(null);
|
|
10393
|
+
else if (event.name === "left" || event.name === "right" || event.name === "home" || event.name === "end" || event.name === "pageup" || event.name === "pagedown" || event.name === "up" || event.name === "down") setGhost(null);
|
|
10394
|
+
}
|
|
10258
10395
|
if (event.name !== "up" && event.name !== "down") return;
|
|
10259
10396
|
const buffer = textareaRef.current;
|
|
10260
10397
|
if (!buffer) return;
|
|
@@ -10292,6 +10429,27 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
10292
10429
|
attachments,
|
|
10293
10430
|
showNotice
|
|
10294
10431
|
]);
|
|
10432
|
+
const ghostPosition = useMemo(() => {
|
|
10433
|
+
if (!ghost) return null;
|
|
10434
|
+
const ta = textareaRef.current;
|
|
10435
|
+
if (!ta || ta.plainText !== ghost.text || ta.cursorOffset !== ghost.cursor) return null;
|
|
10436
|
+
const cursor = ta.visualCursor;
|
|
10437
|
+
if (cursor.visualRow < 0 || cursor.visualRow >= Math.min(MAX_CONTENT_LINES, contentLines)) return null;
|
|
10438
|
+
return {
|
|
10439
|
+
row: cursor.visualRow,
|
|
10440
|
+
col: cursor.visualCol
|
|
10441
|
+
};
|
|
10442
|
+
}, [
|
|
10443
|
+
ghost,
|
|
10444
|
+
contentLines,
|
|
10445
|
+
bufferState
|
|
10446
|
+
]);
|
|
10447
|
+
const ghostTail = useMemo(() => {
|
|
10448
|
+
if (!ghost) return "";
|
|
10449
|
+
const rest = ghost.text.slice(ghost.cursor);
|
|
10450
|
+
const nl = rest.indexOf("\n");
|
|
10451
|
+
return nl === -1 ? rest : rest.slice(0, nl);
|
|
10452
|
+
}, [ghost]);
|
|
10295
10453
|
const boxHeight = Math.min(MAX_CONTENT_LINES, contentLines) + 2;
|
|
10296
10454
|
return /* @__PURE__ */ jsxs("box", {
|
|
10297
10455
|
style: {
|
|
@@ -10347,7 +10505,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
10347
10505
|
flexDirection: "column",
|
|
10348
10506
|
flexShrink: 0
|
|
10349
10507
|
},
|
|
10350
|
-
children: [/* @__PURE__ */
|
|
10508
|
+
children: [/* @__PURE__ */ jsxs("box", {
|
|
10351
10509
|
style: {
|
|
10352
10510
|
border: true,
|
|
10353
10511
|
borderColor: selectMode ? COLOR.warn : shellMode ? COLOR.accent : COLOR.borderActive,
|
|
@@ -10356,7 +10514,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
10356
10514
|
height: boxHeight,
|
|
10357
10515
|
flexDirection: "column"
|
|
10358
10516
|
},
|
|
10359
|
-
children: /* @__PURE__ */ jsx$1("textarea", {
|
|
10517
|
+
children: [/* @__PURE__ */ jsx$1("textarea", {
|
|
10360
10518
|
ref: textareaRef,
|
|
10361
10519
|
focused: focused && !selectMode,
|
|
10362
10520
|
keyBindings: TEXTAREA_BINDINGS,
|
|
@@ -10371,7 +10529,24 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, onCompletionT
|
|
|
10371
10529
|
onContentChange: syncBuffer,
|
|
10372
10530
|
onKeyDown,
|
|
10373
10531
|
onPaste
|
|
10374
|
-
})
|
|
10532
|
+
}), ghostPosition !== null && ghost !== null && /* @__PURE__ */ jsx$1("box", {
|
|
10533
|
+
style: {
|
|
10534
|
+
position: "absolute",
|
|
10535
|
+
top: ghostPosition.row,
|
|
10536
|
+
left: 1 + ghostPosition.col,
|
|
10537
|
+
right: 1,
|
|
10538
|
+
height: 1,
|
|
10539
|
+
overflow: "hidden",
|
|
10540
|
+
backgroundColor: SURFACE.background
|
|
10541
|
+
},
|
|
10542
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
10543
|
+
wrapMode: "none",
|
|
10544
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
10545
|
+
fg: COLOR.dim,
|
|
10546
|
+
children: ghost.suffixes[ghost.index]
|
|
10547
|
+
}), ghostTail.length > 0 && /* @__PURE__ */ jsx$1("span", { children: ghostTail })]
|
|
10548
|
+
})
|
|
10549
|
+
})]
|
|
10375
10550
|
}), /* @__PURE__ */ jsx$1(PromptHints, {
|
|
10376
10551
|
selectMode,
|
|
10377
10552
|
triggerHints,
|
|
@@ -11909,6 +12084,10 @@ const EMPTY_TOOLS = Object.freeze({});
|
|
|
11909
12084
|
/** Stable empty defaults for the extension-config props — keep memo deps from churning. */
|
|
11910
12085
|
const EMPTY_CONFIG_CATEGORIES = Object.freeze({});
|
|
11911
12086
|
const EMPTY_CONFIG_ITEMS = Object.freeze({});
|
|
12087
|
+
/** How long a config action's run-result message stays before yielding to the live probe. */
|
|
12088
|
+
const CONFIG_ACTION_DONE_TTL_MS = 8e3;
|
|
12089
|
+
/** Errors linger longer — the user may need to read them — but expire too. */
|
|
12090
|
+
const CONFIG_ACTION_ERROR_TTL_MS = 2e4;
|
|
11912
12091
|
const TAB_LABELS = {
|
|
11913
12092
|
...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, c.label])),
|
|
11914
12093
|
keybindings: "Keybindings",
|
|
@@ -12009,7 +12188,77 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12009
12188
|
}
|
|
12010
12189
|
});
|
|
12011
12190
|
}, [settings.extensionConfig, setSetting]);
|
|
12191
|
+
const writeConfigValues = useCallback((extName, values) => {
|
|
12192
|
+
const prev = settings.extensionConfig ?? {};
|
|
12193
|
+
const prevExt = prev[extName] ?? {};
|
|
12194
|
+
setSetting("extensionConfig", {
|
|
12195
|
+
...prev,
|
|
12196
|
+
[extName]: {
|
|
12197
|
+
...prevExt,
|
|
12198
|
+
...values
|
|
12199
|
+
}
|
|
12200
|
+
});
|
|
12201
|
+
}, [settings.extensionConfig, setSetting]);
|
|
12202
|
+
const [configAction, setConfigAction] = useState(null);
|
|
12203
|
+
const [actionProbes, setActionProbes] = useState({});
|
|
12204
|
+
const [actionProbeNonce, setActionProbeNonce] = useState(0);
|
|
12205
|
+
useEffect(() => {
|
|
12206
|
+
if (!configAction || configAction.state === "busy") return;
|
|
12207
|
+
const ttl = configAction.state === "error" ? CONFIG_ACTION_ERROR_TTL_MS : CONFIG_ACTION_DONE_TTL_MS;
|
|
12208
|
+
const id = setTimeout(() => {
|
|
12209
|
+
setConfigAction((current) => current === configAction ? null : current);
|
|
12210
|
+
setActionProbeNonce((n) => n + 1);
|
|
12211
|
+
}, ttl);
|
|
12212
|
+
return () => clearTimeout(id);
|
|
12213
|
+
}, [configAction]);
|
|
12214
|
+
const writeConfigValuesRef = useRef(writeConfigValues);
|
|
12215
|
+
writeConfigValuesRef.current = writeConfigValues;
|
|
12216
|
+
const extensionConfigItemsRef = useRef(extensionConfigItems);
|
|
12217
|
+
extensionConfigItemsRef.current = extensionConfigItems;
|
|
12218
|
+
const configActionBusyRef = useRef(false);
|
|
12219
|
+
const runConfigAction = useCallback((extName, item) => {
|
|
12220
|
+
if (configActionBusyRef.current) return;
|
|
12221
|
+
configActionBusyRef.current = true;
|
|
12222
|
+
setConfigAction({
|
|
12223
|
+
extName,
|
|
12224
|
+
key: item.key,
|
|
12225
|
+
state: "busy"
|
|
12226
|
+
});
|
|
12227
|
+
(async () => {
|
|
12228
|
+
try {
|
|
12229
|
+
const result = await item.run() ?? void 0;
|
|
12230
|
+
const { accepted, rejected } = sanitizeExtensionConfigActionPatch(extensionConfigItemsRef.current[extName], result?.config);
|
|
12231
|
+
const acceptedKeys = Object.keys(accepted);
|
|
12232
|
+
if (acceptedKeys.length > 0) writeConfigValuesRef.current(extName, accepted);
|
|
12233
|
+
const parts = [];
|
|
12234
|
+
if (result?.message) parts.push(result.message);
|
|
12235
|
+
else if (acceptedKeys.length > 0) parts.push(`updated ${acceptedKeys.join(", ")}`);
|
|
12236
|
+
else parts.push("done");
|
|
12237
|
+
if (rejected.length > 0) parts.push(`(ignored invalid: ${rejected.join(", ")})`);
|
|
12238
|
+
setConfigAction({
|
|
12239
|
+
extName,
|
|
12240
|
+
key: item.key,
|
|
12241
|
+
state: "done",
|
|
12242
|
+
text: parts.join(" ")
|
|
12243
|
+
});
|
|
12244
|
+
} catch (err) {
|
|
12245
|
+
setConfigAction({
|
|
12246
|
+
extName,
|
|
12247
|
+
key: item.key,
|
|
12248
|
+
state: "error",
|
|
12249
|
+
text: errorMessage(err)
|
|
12250
|
+
});
|
|
12251
|
+
} finally {
|
|
12252
|
+
configActionBusyRef.current = false;
|
|
12253
|
+
}
|
|
12254
|
+
setActionProbeNonce((n) => n + 1);
|
|
12255
|
+
})();
|
|
12256
|
+
}, []);
|
|
12012
12257
|
const activateConfigRow = useCallback((extName, item) => {
|
|
12258
|
+
if (item.type === "action") {
|
|
12259
|
+
runConfigAction(extName, item);
|
|
12260
|
+
return;
|
|
12261
|
+
}
|
|
12013
12262
|
const current = resolveExtensionConfigValue(item, settings.extensionConfig?.[extName]?.[item.key]);
|
|
12014
12263
|
if (item.type === "boolean") {
|
|
12015
12264
|
writeConfigValue(extName, item.key, !current);
|
|
@@ -12031,7 +12280,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12031
12280
|
}, [
|
|
12032
12281
|
settings.extensionConfig,
|
|
12033
12282
|
writeConfigValue,
|
|
12034
|
-
modelCatalog
|
|
12283
|
+
modelCatalog,
|
|
12284
|
+
runConfigAction
|
|
12035
12285
|
]);
|
|
12036
12286
|
const applyModelEdit = useCallback((ids) => {
|
|
12037
12287
|
if (configEdit) writeConfigValue(configEdit.extName, configEdit.key, ids.join(", "));
|
|
@@ -12057,6 +12307,16 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12057
12307
|
const [expandedExtensions, setExpandedExtensions] = useState(() => /* @__PURE__ */ new Set());
|
|
12058
12308
|
const onRefreshMcpToolsRef = useRef(actions?.onRefreshMcpTools);
|
|
12059
12309
|
onRefreshMcpToolsRef.current = actions?.onRefreshMcpTools;
|
|
12310
|
+
const [mcpToolsRefreshing, setMcpToolsRefreshing] = useState(false);
|
|
12311
|
+
const fireMcpToolsRefresh = useCallback(() => {
|
|
12312
|
+
const run = onRefreshMcpToolsRef.current;
|
|
12313
|
+
if (!run) return;
|
|
12314
|
+
setMcpToolsRefreshing(true);
|
|
12315
|
+
Promise.resolve(run()).catch(() => {}).finally(() => setMcpToolsRefreshing(false));
|
|
12316
|
+
}, []);
|
|
12317
|
+
const mcpsCatalogLiveRef = useRef([]);
|
|
12318
|
+
mcpsCatalogLiveRef.current = mcpsCatalog;
|
|
12319
|
+
const mcpColdProbedRef = useRef(/* @__PURE__ */ new Set());
|
|
12060
12320
|
const tabOrder = useMemo(() => {
|
|
12061
12321
|
const tabs = [...SETTINGS_CATEGORIES.map((c) => c.id)];
|
|
12062
12322
|
if (keybindings) tabs.push("keybindings");
|
|
@@ -12073,6 +12333,40 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12073
12333
|
logs
|
|
12074
12334
|
]);
|
|
12075
12335
|
const [activeTab, setActiveTab] = useState(tabOrder[0]);
|
|
12336
|
+
useEffect(() => {
|
|
12337
|
+
if (activeTab !== "extensions") return;
|
|
12338
|
+
let cancelled = false;
|
|
12339
|
+
for (const [extName, byKey] of Object.entries(extensionConfigItems)) for (const item of Object.values(byKey)) {
|
|
12340
|
+
if (item.type !== "action" || typeof item.status !== "function") continue;
|
|
12341
|
+
(async () => {
|
|
12342
|
+
let status;
|
|
12343
|
+
try {
|
|
12344
|
+
status = await Promise.race([Promise.resolve(item.status()), new Promise((resolve) => setTimeout(resolve, 3e3))]);
|
|
12345
|
+
} catch {
|
|
12346
|
+
status = void 0;
|
|
12347
|
+
}
|
|
12348
|
+
if (cancelled) return;
|
|
12349
|
+
const mapKey = `${extName}:${item.key}`;
|
|
12350
|
+
setActionProbes((prev) => {
|
|
12351
|
+
if (status && typeof status.message === "string") return {
|
|
12352
|
+
...prev,
|
|
12353
|
+
[mapKey]: status
|
|
12354
|
+
};
|
|
12355
|
+
if (!(mapKey in prev)) return prev;
|
|
12356
|
+
const next = { ...prev };
|
|
12357
|
+
delete next[mapKey];
|
|
12358
|
+
return next;
|
|
12359
|
+
});
|
|
12360
|
+
})();
|
|
12361
|
+
}
|
|
12362
|
+
return () => {
|
|
12363
|
+
cancelled = true;
|
|
12364
|
+
};
|
|
12365
|
+
}, [
|
|
12366
|
+
activeTab,
|
|
12367
|
+
extensionConfigItems,
|
|
12368
|
+
actionProbeNonce
|
|
12369
|
+
]);
|
|
12076
12370
|
const [extEditNotice, setExtEditNotice] = useState(null);
|
|
12077
12371
|
const [extOp, setExtOp] = useState(null);
|
|
12078
12372
|
const [removeArmed, setRemoveArmed] = useState(null);
|
|
@@ -12183,8 +12477,23 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12183
12477
|
}, [tabOrder, activeTab]);
|
|
12184
12478
|
useEffect(() => {
|
|
12185
12479
|
if (activeTab !== "mcps") return;
|
|
12186
|
-
onRefreshMcpToolsRef.current
|
|
12187
|
-
|
|
12480
|
+
if (!onRefreshMcpToolsRef.current) return;
|
|
12481
|
+
for (const d of mcpsCatalogLiveRef.current) mcpColdProbedRef.current.add(d.config.name);
|
|
12482
|
+
fireMcpToolsRefresh();
|
|
12483
|
+
}, [activeTab, fireMcpToolsRefresh]);
|
|
12484
|
+
useEffect(() => {
|
|
12485
|
+
if (activeTab !== "mcps") return;
|
|
12486
|
+
const cold = mcpsCatalog.filter((d) => mcpsToggle.enabledSet.has(d.config.name) && (mcpToolsByServer[d.config.name]?.length ?? 0) === 0 && !mcpColdProbedRef.current.has(d.config.name));
|
|
12487
|
+
if (cold.length === 0) return;
|
|
12488
|
+
for (const d of cold) mcpColdProbedRef.current.add(d.config.name);
|
|
12489
|
+
fireMcpToolsRefresh();
|
|
12490
|
+
}, [
|
|
12491
|
+
activeTab,
|
|
12492
|
+
mcpsCatalog,
|
|
12493
|
+
mcpsToggle.enabledSet,
|
|
12494
|
+
mcpToolsByServer,
|
|
12495
|
+
fireMcpToolsRefresh
|
|
12496
|
+
]);
|
|
12188
12497
|
const extensionThemes = extensionThemesProp ?? [];
|
|
12189
12498
|
const generalItems = useMemo(() => {
|
|
12190
12499
|
const toggles = SETTINGS_TOGGLES.map((t) => ({
|
|
@@ -12322,13 +12631,19 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12322
12631
|
const row = mcpVisibleRows[cursor];
|
|
12323
12632
|
if (!row || row.kind !== "server") return;
|
|
12324
12633
|
const name = row.entry.config.name;
|
|
12634
|
+
if ((mcpToolsByServer[name]?.length ?? 0) === 0) fireMcpToolsRefresh();
|
|
12325
12635
|
setExpandedMcps((prev) => {
|
|
12326
12636
|
if (prev.has(name)) return prev;
|
|
12327
12637
|
const next = new Set(prev);
|
|
12328
12638
|
next.add(name);
|
|
12329
12639
|
return next;
|
|
12330
12640
|
});
|
|
12331
|
-
}, [
|
|
12641
|
+
}, [
|
|
12642
|
+
mcpVisibleRows,
|
|
12643
|
+
cursor,
|
|
12644
|
+
mcpToolsByServer,
|
|
12645
|
+
fireMcpToolsRefresh
|
|
12646
|
+
]);
|
|
12332
12647
|
const collapseFocusedMcpRow = useCallback(() => {
|
|
12333
12648
|
const row = mcpVisibleRows[cursor];
|
|
12334
12649
|
if (!row) return;
|
|
@@ -12558,8 +12873,12 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12558
12873
|
if (activeTab === "mcps") {
|
|
12559
12874
|
const row = mcpVisibleRows[cursor];
|
|
12560
12875
|
if (!row) return;
|
|
12561
|
-
if (row.kind === "server")
|
|
12562
|
-
|
|
12876
|
+
if (row.kind === "server") {
|
|
12877
|
+
const name = row.entry.config.name;
|
|
12878
|
+
const enabling = !mcpsToggle.enabledSet.has(name);
|
|
12879
|
+
mcpsToggle.toggle(name);
|
|
12880
|
+
if (enabling && (mcpToolsByServer[name]?.length ?? 0) === 0) fireMcpToolsRefresh();
|
|
12881
|
+
} else if (row.kind === "tool") mcpToolToggleMap[row.serverName]?.toggle(row.tool.name);
|
|
12563
12882
|
return;
|
|
12564
12883
|
}
|
|
12565
12884
|
if (activeTab === "keybindings") {
|
|
@@ -12680,7 +12999,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12680
12999
|
}
|
|
12681
13000
|
if (activeTab === "mcps") {
|
|
12682
13001
|
if (actions?.onRefreshMcps) actions.onRefreshMcps();
|
|
12683
|
-
|
|
13002
|
+
fireMcpToolsRefresh();
|
|
12684
13003
|
}
|
|
12685
13004
|
}
|
|
12686
13005
|
});
|
|
@@ -12785,7 +13104,9 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12785
13104
|
onCommitEdit: commitConfigEdit,
|
|
12786
13105
|
modelCatalog,
|
|
12787
13106
|
onApplyModelEdit: applyModelEdit,
|
|
12788
|
-
onCancelEdit: cancelConfigEdit
|
|
13107
|
+
onCancelEdit: cancelConfigEdit,
|
|
13108
|
+
actionStatus: configAction,
|
|
13109
|
+
actionProbes
|
|
12789
13110
|
}),
|
|
12790
13111
|
activeTab === "mcps" && /* @__PURE__ */ jsx$1(McpsList, {
|
|
12791
13112
|
rows: mcpVisibleRows,
|
|
@@ -12793,6 +13114,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
12793
13114
|
enabledSet: mcpsToggle.enabledSet,
|
|
12794
13115
|
toolToggleMap: mcpToolToggleMap,
|
|
12795
13116
|
toolsByServer: mcpToolsByServer,
|
|
13117
|
+
toolsRefreshing: mcpToolsRefreshing,
|
|
12796
13118
|
expanded: expandedMcps,
|
|
12797
13119
|
totalCount: mcpsCatalog.length,
|
|
12798
13120
|
cursor,
|
|
@@ -13058,8 +13380,8 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
|
|
|
13058
13380
|
})
|
|
13059
13381
|
});
|
|
13060
13382
|
}
|
|
13061
|
-
const EXTENSION_CONFIG_INDENT =
|
|
13062
|
-
function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit }) {
|
|
13383
|
+
const EXTENSION_CONFIG_INDENT = 3;
|
|
13384
|
+
function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit, actionStatus, actionProbes }) {
|
|
13063
13385
|
const COLOR = useColors();
|
|
13064
13386
|
const home = homedir();
|
|
13065
13387
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
@@ -13206,7 +13528,19 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
13206
13528
|
const childBg = childFocused ? highlightBg : void 0;
|
|
13207
13529
|
const value = resolveExtensionConfigValue(item, extensionConfig?.[group.ext.name]?.[item.key]);
|
|
13208
13530
|
const childDesc = truncateToCols(item.description ?? "", descCols);
|
|
13209
|
-
if (item.type === "
|
|
13531
|
+
if (item.type === "action") {
|
|
13532
|
+
const status = actionStatus && actionStatus.extName === group.ext.name && actionStatus.key === item.key ? actionStatus : null;
|
|
13533
|
+
childEls.push(/* @__PURE__ */ jsx$1(ConfigActionRow, {
|
|
13534
|
+
id: anchorIdFor(index),
|
|
13535
|
+
indent: EXTENSION_CONFIG_INDENT,
|
|
13536
|
+
label: item.label,
|
|
13537
|
+
description: childDesc,
|
|
13538
|
+
status,
|
|
13539
|
+
probe: actionProbes[`${group.ext.name}:${item.key}`] ?? null,
|
|
13540
|
+
focused: childFocused,
|
|
13541
|
+
bg: childBg
|
|
13542
|
+
}, item.key));
|
|
13543
|
+
} else if (item.type === "boolean") childEls.push(/* @__PURE__ */ jsx$1(ToggleRow, {
|
|
13210
13544
|
id: anchorIdFor(index),
|
|
13211
13545
|
indent: EXTENSION_CONFIG_INDENT,
|
|
13212
13546
|
label: item.label,
|
|
@@ -13345,6 +13679,53 @@ function CreateExtensionButton() {
|
|
|
13345
13679
|
})]
|
|
13346
13680
|
});
|
|
13347
13681
|
}
|
|
13682
|
+
function ConfigActionRow({ id, label, description, status, probe, focused, bg, indent = 0 }) {
|
|
13683
|
+
const COLOR = useColors();
|
|
13684
|
+
const statusText = (status?.state === "busy" ? "running…" : status?.text) ?? probe?.message;
|
|
13685
|
+
const statusColor = status ? status.state === "error" ? COLOR.error : status.state === "busy" ? COLOR.mute : COLOR.accent : probe ? probe.state === "error" ? COLOR.error : probe.state === "off" ? COLOR.mute : COLOR.accent : COLOR.mute;
|
|
13686
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
13687
|
+
id,
|
|
13688
|
+
style: {
|
|
13689
|
+
flexDirection: "column",
|
|
13690
|
+
flexShrink: 0,
|
|
13691
|
+
paddingLeft: 1 + indent,
|
|
13692
|
+
paddingRight: 1,
|
|
13693
|
+
backgroundColor: bg
|
|
13694
|
+
},
|
|
13695
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
13696
|
+
wrapMode: "none",
|
|
13697
|
+
children: [
|
|
13698
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
13699
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
13700
|
+
children: focused ? "▶ " : " "
|
|
13701
|
+
}),
|
|
13702
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
13703
|
+
fg: COLOR.mute,
|
|
13704
|
+
children: SPACER_CHECKBOX_WIDTH
|
|
13705
|
+
}),
|
|
13706
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
13707
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
13708
|
+
children: label
|
|
13709
|
+
}),
|
|
13710
|
+
statusText && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
13711
|
+
fg: COLOR.mute,
|
|
13712
|
+
children: ": "
|
|
13713
|
+
}), /* @__PURE__ */ jsx$1("span", {
|
|
13714
|
+
fg: statusColor,
|
|
13715
|
+
children: statusText
|
|
13716
|
+
})] }),
|
|
13717
|
+
focused && status?.state !== "busy" && /* @__PURE__ */ jsx$1("span", {
|
|
13718
|
+
fg: COLOR.brand,
|
|
13719
|
+
children: " ↵ run"
|
|
13720
|
+
})
|
|
13721
|
+
]
|
|
13722
|
+
}), description && /* @__PURE__ */ jsx$1("text", {
|
|
13723
|
+
wrapMode: "none",
|
|
13724
|
+
fg: COLOR.mute,
|
|
13725
|
+
children: `${COL_TITLE}${description}`
|
|
13726
|
+
})]
|
|
13727
|
+
});
|
|
13728
|
+
}
|
|
13348
13729
|
function ConfigInputRow({ id, label, description, value, editing, initial, inputRef, onCommit, focused, bg, indent = 0 }) {
|
|
13349
13730
|
const COLOR = useColors();
|
|
13350
13731
|
return /* @__PURE__ */ jsxs("box", {
|
|
@@ -13552,7 +13933,7 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
13552
13933
|
})
|
|
13553
13934
|
});
|
|
13554
13935
|
}
|
|
13555
|
-
function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServer, expanded, totalCount, cursor, highlightBg, query, errors, authState, contentCols }) {
|
|
13936
|
+
function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServer, toolsRefreshing, expanded, totalCount, cursor, highlightBg, query, errors, authState, contentCols }) {
|
|
13556
13937
|
const COLOR = useColors();
|
|
13557
13938
|
const home = homedir();
|
|
13558
13939
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
@@ -13651,7 +14032,8 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
|
|
|
13651
14032
|
anchorId: anchorIdFor(index),
|
|
13652
14033
|
focused,
|
|
13653
14034
|
bg,
|
|
13654
|
-
COLOR
|
|
14035
|
+
COLOR,
|
|
14036
|
+
refreshing: toolsRefreshing
|
|
13655
14037
|
});
|
|
13656
14038
|
})]
|
|
13657
14039
|
}, `group:${group.server.entry.config.name}`))]
|
|
@@ -13726,7 +14108,7 @@ function renderToolListRow({ serverName, tool, focused, bg, enabled, anchorId, C
|
|
|
13726
14108
|
]
|
|
13727
14109
|
}, `tool:${serverName}/${tool.name}`);
|
|
13728
14110
|
}
|
|
13729
|
-
function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR }) {
|
|
14111
|
+
function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR, refreshing }) {
|
|
13730
14112
|
return /* @__PURE__ */ jsxs("text", {
|
|
13731
14113
|
id: anchorId,
|
|
13732
14114
|
wrapMode: "none",
|
|
@@ -13736,7 +14118,7 @@ function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR }) {
|
|
|
13736
14118
|
children: focused ? TOOL_ROW_FOCUS_INDENT$1 : TOOL_ROW_INDENT$1
|
|
13737
14119
|
}), /* @__PURE__ */ jsx$1("span", {
|
|
13738
14120
|
fg: COLOR.mute,
|
|
13739
|
-
children: "⟲
|
|
14121
|
+
children: refreshing ? "⟲ Fetching tool list…" : "⟲ No tools reported — check the server command or login; ctrl+r retries."
|
|
13740
14122
|
})]
|
|
13741
14123
|
}, `tool-empty:${serverName}`);
|
|
13742
14124
|
}
|
|
@@ -14157,6 +14539,37 @@ function renderMcpDetailPanel(entry, status, COLOR) {
|
|
|
14157
14539
|
})
|
|
14158
14540
|
]
|
|
14159
14541
|
});
|
|
14542
|
+
if (status.kind === "bootstrap-error") return /* @__PURE__ */ jsxs("box", {
|
|
14543
|
+
style: {
|
|
14544
|
+
flexDirection: "column",
|
|
14545
|
+
border: ["top"],
|
|
14546
|
+
borderColor: COLOR.border,
|
|
14547
|
+
paddingTop: 1
|
|
14548
|
+
},
|
|
14549
|
+
children: [
|
|
14550
|
+
/* @__PURE__ */ jsx$1("text", {
|
|
14551
|
+
fg: COLOR.error,
|
|
14552
|
+
children: `Server failed to start: ${entry.config.name}`
|
|
14553
|
+
}),
|
|
14554
|
+
/* @__PURE__ */ jsx$1("text", {
|
|
14555
|
+
fg: COLOR.dim,
|
|
14556
|
+
children: status.error
|
|
14557
|
+
}),
|
|
14558
|
+
/* @__PURE__ */ jsxs("text", {
|
|
14559
|
+
fg: COLOR.mute,
|
|
14560
|
+
children: [
|
|
14561
|
+
"Fix the command / URL in mcps.json, then",
|
|
14562
|
+
" ",
|
|
14563
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
14564
|
+
fg: COLOR.warn,
|
|
14565
|
+
children: "ctrl+r"
|
|
14566
|
+
}),
|
|
14567
|
+
" ",
|
|
14568
|
+
"to re-probe."
|
|
14569
|
+
]
|
|
14570
|
+
})
|
|
14571
|
+
]
|
|
14572
|
+
});
|
|
14160
14573
|
return null;
|
|
14161
14574
|
}
|
|
14162
14575
|
function renderInlineMcpBadge(status, COLOR) {
|
|
@@ -14178,6 +14591,10 @@ function renderInlineMcpBadge(status, COLOR) {
|
|
|
14178
14591
|
fg: COLOR.error,
|
|
14179
14592
|
children: " · ✗ login failed"
|
|
14180
14593
|
});
|
|
14594
|
+
case "bootstrap-error": return /* @__PURE__ */ jsx$1("span", {
|
|
14595
|
+
fg: COLOR.error,
|
|
14596
|
+
children: " · ✗ failed to start"
|
|
14597
|
+
});
|
|
14181
14598
|
}
|
|
14182
14599
|
}
|
|
14183
14600
|
function renderMcpErrors(errors, home, warnColor) {
|
|
@@ -14309,7 +14726,7 @@ function Hints({ activeTab, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefr
|
|
|
14309
14726
|
" · ",
|
|
14310
14727
|
/* @__PURE__ */ jsx$1("span", {
|
|
14311
14728
|
fg: COLOR.warn,
|
|
14312
|
-
children: "ctrl+
|
|
14729
|
+
children: "ctrl+r"
|
|
14313
14730
|
}),
|
|
14314
14731
|
" refresh"
|
|
14315
14732
|
] }),
|
|
@@ -15894,6 +16311,58 @@ function AppShell() {
|
|
|
15894
16311
|
config.extensionScanPaths
|
|
15895
16312
|
]);
|
|
15896
16313
|
/**
|
|
16314
|
+
* Hot-load on enable: toggling an extension ON in Settings → Extensions
|
|
16315
|
+
* used to take effect only at the next launch — live filtering can remove
|
|
16316
|
+
* an already-loaded extension's contributions, but it can't import a
|
|
16317
|
+
* module that boot skipped, so the row stayed blank (no version, no
|
|
16318
|
+
* settings) until a reboot. This effect closes that asymmetry: when a
|
|
16319
|
+
* newly-enabled name matches a registry entry whose `setup()` never ran,
|
|
16320
|
+
* it rides the same rebuild path as `/reload` and the disk watcher —
|
|
16321
|
+
* `registryBuildOptions` reads the allowlist through the eagerly-written
|
|
16322
|
+
* `enabledExtensionsRef`, so the rebuild sees the fresh toggle. The
|
|
16323
|
+
* settings modal renders registry state as live props, so the extension's
|
|
16324
|
+
* config items appear immediately, modal still open.
|
|
16325
|
+
*
|
|
16326
|
+
* Toggling OFF needs nothing here (`filterExtensionRegistry` handles it
|
|
16327
|
+
* reactively), and re-enabling an extension that is still in `loaded`
|
|
16328
|
+
* (same-session toggle round-trip) is also pure filtering — only
|
|
16329
|
+
* never-set-up entries trigger the reload. Errored entries are excluded:
|
|
16330
|
+
* a rebuild wouldn't fix them, and the modal already surfaces the error.
|
|
16331
|
+
*/
|
|
16332
|
+
const handledEnabledExtensionsRef = useRef(settings.enabledExtensions);
|
|
16333
|
+
const [enableRetryNonce, setEnableRetryNonce] = useState(0);
|
|
16334
|
+
useEffect(() => {
|
|
16335
|
+
const prev = handledEnabledExtensionsRef.current;
|
|
16336
|
+
const next = settings.enabledExtensions;
|
|
16337
|
+
if (next === void 0 || next === prev) return;
|
|
16338
|
+
const prevSet = new Set(prev ?? []);
|
|
16339
|
+
const registry = extensionRegistryLiveRef.current;
|
|
16340
|
+
const needsLoad = next.filter((name) => !prevSet.has(name) && (registry.inactive.some((e) => e.name === name && !e.error) || registry.loaded.some((e) => e.name === name && !e.setupRan && !e.error)));
|
|
16341
|
+
if (needsLoad.length === 0) {
|
|
16342
|
+
handledEnabledExtensionsRef.current = next;
|
|
16343
|
+
return;
|
|
16344
|
+
}
|
|
16345
|
+
if (extensionAutoReloadInFlightRef.current || runningRef.current) {
|
|
16346
|
+
const id = setTimeout(() => setEnableRetryNonce((n) => n + 1), EXTENSION_WATCH_POLL_MS);
|
|
16347
|
+
return () => clearTimeout(id);
|
|
16348
|
+
}
|
|
16349
|
+
handledEnabledExtensionsRef.current = next;
|
|
16350
|
+
extensionAutoReloadInFlightRef.current = true;
|
|
16351
|
+
(async () => {
|
|
16352
|
+
try {
|
|
16353
|
+
await reloadExtensionsRef.current();
|
|
16354
|
+
streamRef.current?.appendImmediate({
|
|
16355
|
+
kind: "info",
|
|
16356
|
+
text: `\u{1F50C} Enabled ${needsLoad.join(", ")} — settings and UI are live; agent tools apply next run.`
|
|
16357
|
+
});
|
|
16358
|
+
} catch (err) {
|
|
16359
|
+
debugLog("extension enable: hot-load failed", err);
|
|
16360
|
+
} finally {
|
|
16361
|
+
extensionAutoReloadInFlightRef.current = false;
|
|
16362
|
+
}
|
|
16363
|
+
})();
|
|
16364
|
+
}, [settings.enabledExtensions, enableRetryNonce]);
|
|
16365
|
+
/**
|
|
15897
16366
|
* Auto-wake trigger for background-task notifications. When a
|
|
15898
16367
|
* backgrounded shell task exits while the agent is IDLE, nothing would
|
|
15899
16368
|
* otherwise deliver the queued `<task-notification>` until the user
|
|
@@ -15917,6 +16386,11 @@ function AppShell() {
|
|
|
15917
16386
|
const [showQuitConfirm, setShowQuitConfirm] = useState(false);
|
|
15918
16387
|
const showQuitConfirmRef = useRef(false);
|
|
15919
16388
|
showQuitConfirmRef.current = showQuitConfirm;
|
|
16389
|
+
useEffect(() => {
|
|
16390
|
+
if (!showQuitConfirm) return;
|
|
16391
|
+
modal.lock();
|
|
16392
|
+
return () => modal.unlock();
|
|
16393
|
+
}, [showQuitConfirm, modal]);
|
|
15920
16394
|
const [quitConfirmSelected, setQuitConfirmSelected] = useState("cancel");
|
|
15921
16395
|
const quitConfirmSelectedRef = useRef("cancel");
|
|
15922
16396
|
quitConfirmSelectedRef.current = quitConfirmSelected;
|
|
@@ -16059,6 +16533,7 @@ function AppShell() {
|
|
|
16059
16533
|
const key = command.name.toLowerCase();
|
|
16060
16534
|
if (seen.has(key)) continue;
|
|
16061
16535
|
seen.add(key);
|
|
16536
|
+
for (const alias of command.aliases ?? []) seen.add(alias.toLowerCase());
|
|
16062
16537
|
merged.push(command);
|
|
16063
16538
|
}
|
|
16064
16539
|
return merged;
|
|
@@ -16093,6 +16568,7 @@ function AppShell() {
|
|
|
16093
16568
|
visibleChatCatalog,
|
|
16094
16569
|
liveExtensionCompletionProviders
|
|
16095
16570
|
]);
|
|
16571
|
+
const inlineAutocompleteProviders = useMemo(() => [createPathAutocompleteProvider({ cwd: () => process.cwd() }), ...filterExtensionRegistry(extensionRegistry, settings.enabledExtensions).inlineAutocompleteProviders], [extensionRegistry, settings.enabledExtensions]);
|
|
16096
16572
|
useEffect(() => {
|
|
16097
16573
|
const handle = agentRef.current?.handle;
|
|
16098
16574
|
if (handle) handle.cwd = cwd;
|
|
@@ -16575,12 +17051,16 @@ function AppShell() {
|
|
|
16575
17051
|
});
|
|
16576
17052
|
agent.hooks.hook("mcp:bootstrap:end", (ctx) => {
|
|
16577
17053
|
if (ctx.ok) return;
|
|
16578
|
-
if (mcpsCatalogRef.current.find((d) => d.config.name === ctx.name)?.config.auth
|
|
16579
|
-
dispatchAuthRef.current({
|
|
17054
|
+
if (mcpsCatalogRef.current.find((d) => d.config.name === ctx.name)?.config.auth === "oauth") dispatchAuthRef.current({
|
|
16580
17055
|
type: "auth-error",
|
|
16581
17056
|
name: ctx.name,
|
|
16582
17057
|
error: ctx.error.message
|
|
16583
17058
|
});
|
|
17059
|
+
else dispatchAuthRef.current({
|
|
17060
|
+
type: "bootstrap-error",
|
|
17061
|
+
name: ctx.name,
|
|
17062
|
+
error: ctx.error.message
|
|
17063
|
+
});
|
|
16584
17064
|
});
|
|
16585
17065
|
agent.hooks.hook("mcp:auth:url", ({ name, url }) => {
|
|
16586
17066
|
dispatchAuthRef.current({
|
|
@@ -18330,7 +18810,7 @@ function AppShell() {
|
|
|
18330
18810
|
/** Route a submitted prompt to the command executor when it's a `/cmd`, otherwise to the model. */
|
|
18331
18811
|
const runPromptOrCommand = useCallback(async (prompt, references, attachments, opts) => {
|
|
18332
18812
|
if (!opts?.wake) {
|
|
18333
|
-
const invocation = parseCommandInvocation(prompt, commandsRef.current
|
|
18813
|
+
const invocation = parseCommandInvocation(prompt, commandsRef.current);
|
|
18334
18814
|
if (invocation) {
|
|
18335
18815
|
await runCommand(invocation);
|
|
18336
18816
|
return;
|
|
@@ -18401,7 +18881,7 @@ function AppShell() {
|
|
|
18401
18881
|
suppressWakeRef.current = false;
|
|
18402
18882
|
(async () => {
|
|
18403
18883
|
let effectivePrompt = prompt;
|
|
18404
|
-
if (parseCommandInvocation(prompt, commandsRef.current
|
|
18884
|
+
if (parseCommandInvocation(prompt, commandsRef.current) === null && parseBashInvocation(prompt) === null) for (const contribution of extensionRegistry.promptTransforms) try {
|
|
18405
18885
|
const out = await contribution.value(effectivePrompt);
|
|
18406
18886
|
if (out === void 0) continue;
|
|
18407
18887
|
if (typeof out === "string") {
|
|
@@ -19667,6 +20147,7 @@ function AppShell() {
|
|
|
19667
20147
|
pendingInteraction,
|
|
19668
20148
|
onInteraction: onInteractionResolve,
|
|
19669
20149
|
completionProviders,
|
|
20150
|
+
inlineAutocompleteProviders,
|
|
19670
20151
|
onCompletionTab: (providerId) => {
|
|
19671
20152
|
if (providerId !== "chats") return false;
|
|
19672
20153
|
toggleChatReferenceScope();
|
|
@@ -20257,7 +20738,7 @@ function ToggleListModal({ catalog, keyOf, settingKey, gcKeys, title, renderDeta
|
|
|
20257
20738
|
children: [
|
|
20258
20739
|
onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
|
|
20259
20740
|
fg: COLOR.warn,
|
|
20260
|
-
children: "ctrl+
|
|
20741
|
+
children: "ctrl+r"
|
|
20261
20742
|
}), " refresh · "] }),
|
|
20262
20743
|
/* @__PURE__ */ jsx$1("span", {
|
|
20263
20744
|
fg: COLOR.warn,
|
|
@@ -20316,7 +20797,7 @@ function ToggleListModal({ catalog, keyOf, settingKey, gcKeys, title, renderDeta
|
|
|
20316
20797
|
" toggle · ",
|
|
20317
20798
|
onRefresh && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx$1("span", {
|
|
20318
20799
|
fg: COLOR.warn,
|
|
20319
|
-
children: "ctrl+
|
|
20800
|
+
children: "ctrl+r"
|
|
20320
20801
|
}), " refresh · "] }),
|
|
20321
20802
|
/* @__PURE__ */ jsx$1("span", {
|
|
20322
20803
|
fg: COLOR.warn,
|
|
@@ -20343,10 +20824,11 @@ function ToggleListModal({ catalog, keyOf, settingKey, gcKeys, title, renderDeta
|
|
|
20343
20824
|
* folder without leaving the TUI.
|
|
20344
20825
|
*
|
|
20345
20826
|
* Enabled state lives in `settings.enabledExtensions` (mirrors
|
|
20346
|
-
* `enabledSkills` / `enabledMcps`). Toggling
|
|
20347
|
-
*
|
|
20348
|
-
*
|
|
20349
|
-
*
|
|
20827
|
+
* `enabledSkills` / `enabledMcps`). Toggling OFF filters the live
|
|
20828
|
+
* registry immediately; toggling ON an extension that boot skipped
|
|
20829
|
+
* hot-reloads the registry (see "Hot-load on enable" in app.tsx), so
|
|
20830
|
+
* its `setup()` runs and its settings appear without a restart.
|
|
20831
|
+
* Agent-side contributions (tools / hooks) apply on the next run.
|
|
20350
20832
|
*/
|
|
20351
20833
|
function ExtensionsSettingsModal({ catalog, inactive }) {
|
|
20352
20834
|
const COLOR = useColors();
|
|
@@ -21034,7 +21516,7 @@ function displayPath(path, home) {
|
|
|
21034
21516
|
* row geometry live in `<ToggleListModal>`; this file just supplies the
|
|
21035
21517
|
* skill-specific column (description) and the empty-state hint.
|
|
21036
21518
|
*
|
|
21037
|
-
* Pass `onRefresh` to expose `ctrl+
|
|
21519
|
+
* Pass `onRefresh` to expose `ctrl+r` (force re-scan) on the list. The
|
|
21038
21520
|
* standalone modal is for embedders — the in-app flow goes through
|
|
21039
21521
|
* `<SettingsModal>` which exposes the same affordance via its own
|
|
21040
21522
|
* `SettingsActions.onRefreshSkills` plumbing.
|