zidane 6.4.4 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/{acp-BLO58K3F.js → acp-B-AOovHz.js} +2 -2
- package/dist/{acp-BLO58K3F.js.map → acp-B-AOovHz.js.map} +1 -1
- package/dist/acp-cli.js +3 -2
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-8sUadp5M.js → agent-SQ6rxfpT.js} +231 -106
- package/dist/agent-SQ6rxfpT.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat.d.ts +13 -44
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +5 -4
- package/dist/chat.js.map +1 -1
- package/dist/{context-transport-D29WvHcm.js → context-transport-CDBatvQc.js} +3 -3
- package/dist/{context-transport-D29WvHcm.js.map → context-transport-CDBatvQc.js.map} +1 -1
- 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-C-Nd2ZbV.d.ts → index-CWBSrqI6.d.ts} +676 -3
- package/dist/index-CWBSrqI6.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/{login-CMVVnAHS.js → login-XZDS4K2o.js} +3 -3
- package/dist/{login-CMVVnAHS.js.map → login-XZDS4K2o.js.map} +1 -1
- package/dist/{mcp-3GXUBwur.js → mcp-D27rP47I.js} +87 -6
- package/dist/mcp-D27rP47I.js.map +1 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +3 -2
- package/dist/origin-pinned-fetch-CqG8VZIq.js +159 -0
- package/dist/origin-pinned-fetch-CqG8VZIq.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins-2PMTVcp-.js +1174 -0
- package/dist/plugins-2PMTVcp-.js.map +1 -0
- package/dist/plugins.d.ts +2 -0
- package/dist/plugins.js +2 -0
- 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 +5 -3
- package/dist/restate.js.map +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/{resolve-CZJxds85.js → skills-Cp9vnBwd.js} +1430 -1006
- package/dist/skills-Cp9vnBwd.js.map +1 -0
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -2
- package/dist/{stdio-BvL8HGYW.js → stdio-O-m9WVkE.js} +2 -2
- package/dist/{stdio-BvL8HGYW.js.map → stdio-O-m9WVkE.js.map} +1 -1
- package/dist/{stdio-loader-CjfoqBle.js → stdio-loader-BSGQmG4L.js} +2 -2
- package/dist/{stdio-loader-CjfoqBle.js.map → stdio-loader-BSGQmG4L.js.map} +1 -1
- package/dist/{tool-formatters-DPQjso_C.d.ts → tool-formatters-gFqQbzPY.d.ts} +2 -2
- package/dist/{tool-formatters-DPQjso_C.d.ts.map → tool-formatters-gFqQbzPY.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-CgDzYin1.js → transcript-anchors-D4IxJifV.js} +42 -5
- package/dist/transcript-anchors-D4IxJifV.js.map +1 -0
- package/dist/{transcript-anchors-BCUpbNxc.d.ts → transcript-anchors-qCs3wZpu.d.ts} +98 -92
- package/dist/transcript-anchors-qCs3wZpu.d.ts.map +1 -0
- package/dist/tui.d.ts +38 -4
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +839 -105
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-Bu2Hkinw.d.ts → turn-operations-M9-jUyDd.d.ts} +3 -3
- package/dist/{turn-operations-Bu2Hkinw.d.ts.map → turn-operations-M9-jUyDd.d.ts.map} +1 -1
- package/dist/types-BIarq1qE.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/PLUGINS.md +340 -0
- package/docs/RESTATE.md +69 -3
- package/docs/SKILL.md +24 -8
- package/package.json +6 -1
- package/dist/agent-8sUadp5M.js.map +0 -1
- package/dist/index-C-Nd2ZbV.d.ts.map +0 -1
- package/dist/mcp-3GXUBwur.js.map +0 -1
- package/dist/resolve-CZJxds85.js.map +0 -1
- package/dist/skills-Dd6XCi4s.js +0 -420
- package/dist/skills-Dd6XCi4s.js.map +0 -1
- package/dist/transcript-anchors-BCUpbNxc.d.ts.map +0 -1
- package/dist/transcript-anchors-CgDzYin1.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { $t as toggleChainEntry, A as redirectUriFromAuthUrl, At as resolveTheme, Bt as useDiscovery, Cr as
|
|
2
|
-
import { $ as removeInstalledExtension,
|
|
1
|
+
import { $t as toggleChainEntry, A as redirectUriFromAuthUrl, At as resolveTheme, Bn as TODO_STATUS_GLYPHS, Bt as useDiscovery, Cr as stripSpawnTokensLine, Ct as SETTINGS_TOGGLES, D as formatPathForCwd, Dr as toolResultText, En as discoverAgentsMd, Er as toolCallPreview, Et as useSettings, F as loadMcpToolsCache, G as parentServerName, Gt as resolveStoragePaths, Ht as ConfigProvider, I as mcpToolsCachePath, Jt as chainsEqual, K as createFileMcpCredentialStore, Kt as buildRunModel, L as refreshMcpToolsCatalog, M as runOAuthLogin, N as supportsOAuth, O as fetchOAuthRedirect, Or as updateToolEventOutcomes, Pn as accentColor, Q as getMcpAuthStatus, Rt as createDiscoverySlot, Sr as serverToolResultSummary, St as SETTINGS_INPUTS, T as suggestSafelistEntries, Tn as chatAutoCompactBehavior, Tt as clampFps, U as buildVisibleMcpRows, Ut as useConfig, V as useMcpToolToggleMap, Vt as useDiscoveryOptional, W as indexOfServerRow, Wt as resolveConfig, X as useMcpAuthDispatch, Xn as useActiveTodos, Xt as providerKeySetChanged, Y as McpAuthProvider, Yt as filterChainFallbacks, Z as useMcpAuthState, Zt as restoreChainFallbacks, _ as useSafeModeQueue, _r as lastContextSizeFromTurns, _t as listProjectFiles, a as useSurfaces, an as removeMcpServer, b as getSafelist, br as marginTopFor, bt as SETTINGS_CATEGORIES, c as useStreamBuffer, cn as dedupMcpServersByName, cr as buildPlanSystem, d as discoverProjectSkills, dr as createChatTurnsReadTool, dt as EMPTY_HINTS, en as useCompletion, et as ASK_USER_TOOL, fn as tryOpenBrowser, ft as clipHintsToWidth, g as useSafeModeActions, gn as useUpdateCheck, gr as eventsFromTurns, h as SafeModeProvider, hn as buildUpdateHint, hr as deriveSessionTitle, ht as buildHints, i as useSelectStyle, in as mcpsWritePath, it as createInteractionTools, j as oauthUsesManualCodePaste, kr as usageByModel, kt as resolveChipColor, l as buildSkillsConfig, lr as envSection, lt as useInteractionsActions, m as writeSessionExport, mn as bootTick, mt as truncateTrailing, n as ThemeProvider, nn as hasClaudeCodeSetup, nt as PRESENT_PLAN_TOOL, on as upsertMcpServer, ot as makeRequestInteraction, pr as compactSummaryEvent, pt as hintsLength, qn as pruneTodosByRun, qt as chainPositionOf, r as useColors, rn as importFromClaudeCode, rt as buildResumedToolResultsTurn, s as useTheme, sn as buildMcpServers, sr as buildBuildSystem, st as pendingInteractionsFromTurns, t as computeTurnAnchors, tn as countClaudeSessionFiles, tt as InteractionsProvider, u as defaultSkillScanPaths, un as discoverProjectMcps, ur as chatReferenceTokensForPrompt, ut as useInteractionsQueue, vr as listSessionMeta, vt as useEnabledToggleSet, wr as sumRunCosts, wt as SettingsProvider, x as isOnSafelist, xt as SETTINGS_CHOICES, y as addToSafelist, yr as loadState, yt as DEFAULT_SETTINGS, z as subscribeMcpToolsCache, zt as DiscoveryProvider } from "./transcript-anchors-D4IxJifV.js";
|
|
2
|
+
import { $ as removeInstalledExtension, C as resolveTasksDir, Cn as effectiveContextWindow, E as selectFilesFromSession, Gt as KEYBINDING_KEY_COL_WIDTH, Jt as groupBindings, K as createExtensionPromptBridge, Kt as ensureKeybindingsFile, Nt as createModelSearchTool, O as compactConversation, S as resolveSessionDir, Sn as OUTPUT_RESERVE_TOKENS, St as isSpawnTool, V as summaryToTurn, Xt as matchesBinding, Y as installExtensionFromSpec, Yt as keybindingsPath, _ as rehomePersistedStubs, at as composeExtensionPreset, b as resolveMcpWarningsDir, c as applyCompactSummaryCutoff, ct as filterRegistryPlugins, dn as createDebugLog, dt as createExtensionHostRuntimeBridge, et as updateInstalledExtension, gt as basic_default, jt as looksBinary, kt as tailTruncate, ln as parseExtensionScanPathEnv, m as cleanupPersistedSession, mn as coerceExtensionConfigInput, nt as discoverAndBuildExtensionRegistry, ot as disposeExtensionRegistry, q as ExtensionInstallError, qt as formatBindingForDisplay, rn as registerExtensionHostModules, sn as inlineLoadedExtension, st as filterExtensionRegistry, t as createAgent, tn as resolveExtensionKeybindings, tt as createExtensionHud, un as scanExtensionDirectories, ut as partitionExtensionCatalog, v as resolveLegacyPersistDir, vn as resolveExtensionConfigValue, w as buildPostCompactAttachments, x as resolvePersistDir, y as resolveLegacySessionDir, yn as sanitizeExtensionConfigActionPatch } from "./agent-SQ6rxfpT.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-C9zukcCj.js";
|
|
5
5
|
import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-DWVt7MvF.js";
|
|
6
6
|
import { u as errorMessage } from "./errors-BwBxu3zD.js";
|
|
7
|
-
import {
|
|
7
|
+
import { R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-Cp9vnBwd.js";
|
|
8
|
+
import { n as defaultPluginDataRoot, r as loadAgentPlugin } from "./plugins-2PMTVcp-.js";
|
|
8
9
|
import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimestamp, n as createFileSink, o as fanoutSink, r as createLogger, s as formatLogLine, t as consoleSink } from "./logger-DBX9uYOw.js";
|
|
9
10
|
import { i as sniffImageMediaType } from "./media-sniff-UKijEaiQ.js";
|
|
10
|
-
import {
|
|
11
|
+
import { _ as hasAuthorizationHeader, h as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-D27rP47I.js";
|
|
11
12
|
import { C as estimateRebilledCostUsd } from "./openai-compat-B3c_hzwf.js";
|
|
12
13
|
import { replaceDynamicSection } from "./prompt.js";
|
|
13
14
|
import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-cr9VdoEO.js";
|
|
@@ -16,18 +17,17 @@ import { o as withTimeout } from "./timeout-BDetbuN6.js";
|
|
|
16
17
|
import { i as installCacheBreakLogger } from "./cache-telemetry-ehVdWsg2.js";
|
|
17
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";
|
|
18
19
|
import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
|
|
19
|
-
import { i as
|
|
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-CMVVnAHS.js";
|
|
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-XZDS4K2o.js";
|
|
21
21
|
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-BzYbbdN_.js";
|
|
22
22
|
import { createDockerContext } from "./contexts/docker.js";
|
|
23
23
|
import { createTuiStore } from "./session/sqlite.js";
|
|
24
24
|
import { $ as buildContextualDiff, A as createSkillsCompletionProvider, B as EDIT_TOOL_NAMES, C as chunkMarkdownForRender, D as splitMarkdownCodeBlocks, E as normalizeMarkdownCodeFences, G as isVisibleDuringSearch, H as isTurnHighlighted, I as createCommandsCompletionProvider, J as selectableTurnIds, K as promptHistoryFromEvents, L as blendHsl, N as createFilesCompletionProvider, O as stripHtmlCommentsOutsideCode, R as buildLinearRamp, T as codeTokenHasClosingFence, U as isUserBashEvent, V as isEditErrorResult, W as isVisible, X as streamEventSearchText, Y as selectionAfterTurnRemoval, Z as turnSelectionOwnership, _ as turnContextSize, _t as summarizeOutcomes, a as TOOL_DISPLAY, at as filetypeFromPath, b as filterModelCatalog, bt as createChatsCompletionProvider, ct as summarizeEditPayload, d as finalizeStreamingMarkdown, et as buildUnifiedDiff, f as finalizeStreamingMarkdownForOwner, ft as mergeApprovalAndBodyOutcomes, g as revealEarlierTranscriptEvents, gt as stripEditOutcomesAnnotation, h as replaceTranscriptTurnEvents, ht as rewriteMultiEditHeader, i as turnAsText, it as extractEditPayload, j as uniqueSkillNamesFromReferences, mt as resolveApprovalForPayload, n as deleteTurnSafely, o as displayNameFor, ot as previewEditPayload, p as hiddenEarlierTranscriptCount, pt as parseEditOutcomesFromResult, q as retagUserPromptEvents, r as truncateTurnsAt, s as formatToolCall, tt as clipDiffForRender, u as capTranscriptEvents, ut as buildEditOutcomesAnnotation, v as splitPromptSegments, w as closeDanglingCodeFence, y as buildModelCatalog, z as BASH_CALL_ID_PREFIX } from "./turn-operations-CAvi8NYt.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 { basename, dirname, join, relative, resolve } from "node:path";
|
|
26
|
+
import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
|
|
27
27
|
import { Buffer } from "node:buffer";
|
|
28
28
|
import * as fs from "node:fs";
|
|
29
29
|
import { createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
30
|
-
import { readFile, unlink } from "node:fs/promises";
|
|
30
|
+
import { readFile, readdir, stat, unlink } from "node:fs/promises";
|
|
31
31
|
import { spawn, spawnSync } from "node:child_process";
|
|
32
32
|
import { homedir, tmpdir } from "node:os";
|
|
33
33
|
import { Fzf, byLengthAsc } from "fzf";
|
|
@@ -43,6 +43,144 @@ import * as OpenTuiReactJsxDevRuntime from "@opentui/react/jsx-dev-runtime";
|
|
|
43
43
|
import * as OpenTuiReactJsxRuntime from "@opentui/react/jsx-runtime";
|
|
44
44
|
import { Fragment, jsx as jsx$1, jsxs } from "@opentui/react/jsx-runtime";
|
|
45
45
|
import * as ReactJsxDevRuntime from "react/jsx-dev-runtime";
|
|
46
|
+
//#region src/chat/plugins-discovery.ts
|
|
47
|
+
/** Subdirectory each search root is probed for. */
|
|
48
|
+
const PLUGINS_SUBPATH = "plugins";
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the plugin search roots. First-found wins on name collision;
|
|
51
|
+
* earlier entries take precedence.
|
|
52
|
+
*
|
|
53
|
+
* Search order — see {@link projectUserPaths}:
|
|
54
|
+
*
|
|
55
|
+
* 1. `{project}/.agents/plugins`
|
|
56
|
+
* 2. `{project}/.zidane/plugins`
|
|
57
|
+
* 3. `~/.agents/plugins`
|
|
58
|
+
* 4. `~/.zidane/plugins`
|
|
59
|
+
*
|
|
60
|
+
* Non-existent paths are returned; the walk skips them.
|
|
61
|
+
*
|
|
62
|
+
* Unlike skills and MCP servers there is no foreign-agent or Ruler variant:
|
|
63
|
+
* Agent Plugins is a vendor-neutral format, so `.agents/plugins` IS the
|
|
64
|
+
* cross-tool location. Nothing to import from a competitor's directory.
|
|
65
|
+
*/
|
|
66
|
+
function defaultPluginScanPaths(opts = {}) {
|
|
67
|
+
return projectUserPaths({
|
|
68
|
+
subPath: PLUGINS_SUBPATH,
|
|
69
|
+
...opts
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse a platform-delimited list of additional plugin scan roots. Mirrors
|
|
74
|
+
* `parseExtensionScanPathEnv` in `../extensions/discovery`:
|
|
75
|
+
*
|
|
76
|
+
* ZIDANE_PLUGIN_DIRS=/repo/plugins bun run tui
|
|
77
|
+
*
|
|
78
|
+
* Each path is a scan ROOT whose immediate children are plugin directories
|
|
79
|
+
* (`<root>/<name>/plugin.json`), not a single plugin directory.
|
|
80
|
+
*
|
|
81
|
+
* This exists because the four default roots are all dot-directories, and
|
|
82
|
+
* `.zidane/` is gitignored — so a repo has nowhere TRACKED to keep example or
|
|
83
|
+
* first-party plugins for local dogfooding. Extensions solved the same
|
|
84
|
+
* problem the same way.
|
|
85
|
+
*/
|
|
86
|
+
function parsePluginScanPathEnv(value = process.env.ZIDANE_PLUGIN_DIRS) {
|
|
87
|
+
if (!value) return [];
|
|
88
|
+
return value.split(delimiter).map((p) => p.trim()).filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Load every plugin reachable from the default search roots.
|
|
92
|
+
*
|
|
93
|
+
* Each immediate child directory of a root is one plugin — the same
|
|
94
|
+
* depth-1 rule the spec applies inside `skills/`. A child that isn't a
|
|
95
|
+
* directory, or that holds no `plugin.json`, is silently skipped: dropping a
|
|
96
|
+
* README next to your plugins is not an error.
|
|
97
|
+
*
|
|
98
|
+
* Dedup is by plugin NAME, not directory name, because the manifest owns
|
|
99
|
+
* identity — two directories called `tools/` shipping different plugins are
|
|
100
|
+
* two plugins, and one plugin vendored at two scopes is one plugin.
|
|
101
|
+
*/
|
|
102
|
+
async function discoverProjectPlugins(opts = {}) {
|
|
103
|
+
const roots = [...defaultPluginScanPaths(opts), ...(opts.extraRoots ?? []).map((path) => ({
|
|
104
|
+
path,
|
|
105
|
+
source: "project"
|
|
106
|
+
}))].filter((p) => existsSync(p.path));
|
|
107
|
+
const dataRoot = defaultPluginDataRoot({
|
|
108
|
+
...opts.prefix !== void 0 ? { prefix: opts.prefix } : {},
|
|
109
|
+
...opts.home !== void 0 ? { home: opts.home } : {}
|
|
110
|
+
});
|
|
111
|
+
const plugins = [];
|
|
112
|
+
const errors = [];
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const root of roots) {
|
|
115
|
+
if (opts.signal?.aborted) break;
|
|
116
|
+
let entries;
|
|
117
|
+
try {
|
|
118
|
+
entries = await readdir(root.path);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
errors.push({
|
|
121
|
+
path: root.path,
|
|
122
|
+
source: root.source,
|
|
123
|
+
message: `Could not read plugin directory: ${err instanceof Error ? err.message : String(err)}`
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
for (const entry of entries.sort()) {
|
|
128
|
+
if (opts.signal?.aborted) break;
|
|
129
|
+
const dir = join(root.path, entry);
|
|
130
|
+
try {
|
|
131
|
+
if (!(await stat(dir)).isDirectory()) continue;
|
|
132
|
+
} catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const loaded = await loadAgentPlugin(dir, {
|
|
136
|
+
source: root.source,
|
|
137
|
+
dataRoot
|
|
138
|
+
});
|
|
139
|
+
if (!loaded.ok && loaded.diagnostics.some((d) => d.code === "manifest-missing")) continue;
|
|
140
|
+
const name = loaded.manifest?.name ?? basename(dir);
|
|
141
|
+
if (seen.has(name)) continue;
|
|
142
|
+
seen.add(name);
|
|
143
|
+
plugins.push({
|
|
144
|
+
name,
|
|
145
|
+
plugin: loaded,
|
|
146
|
+
source: root.source,
|
|
147
|
+
path: dir
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
plugins,
|
|
153
|
+
errors
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Map the user's enabled-plugin list onto the contributions a host merges
|
|
158
|
+
* into the agent.
|
|
159
|
+
*
|
|
160
|
+
* Conventions match `enabledSkills` / `enabledMcps`:
|
|
161
|
+
* - `undefined` → every discovered plugin is enabled (default)
|
|
162
|
+
* - `[]` → all off
|
|
163
|
+
* - `[names]` → allowlist
|
|
164
|
+
*
|
|
165
|
+
* A plugin that failed to load contributes nothing regardless of the
|
|
166
|
+
* allowlist — `ok: false` means components were never discovered.
|
|
167
|
+
*/
|
|
168
|
+
function buildPluginContributions(opts) {
|
|
169
|
+
const allow = opts.enabled === void 0 ? null : new Set(opts.enabled);
|
|
170
|
+
const skills = [];
|
|
171
|
+
const mcpServers = [];
|
|
172
|
+
for (const entry of opts.discovered) {
|
|
173
|
+
if (!entry.plugin.ok) continue;
|
|
174
|
+
if (allow !== null && !allow.has(entry.name)) continue;
|
|
175
|
+
skills.push(...entry.plugin.skills);
|
|
176
|
+
mcpServers.push(...entry.plugin.mcpServers);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
skills,
|
|
180
|
+
mcpServers
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
46
184
|
//#region src/mcp/warn-log.ts
|
|
47
185
|
/**
|
|
48
186
|
* Subscribe to `mcp:warn`
|
|
@@ -5620,9 +5758,7 @@ function useProjectLocation() {
|
|
|
5620
5758
|
const FILES_REFRESH_THROTTLE_MS = 3e3;
|
|
5621
5759
|
const SKILLS_REFRESH_THROTTLE_MS = 3e4;
|
|
5622
5760
|
const MCP_TOOLS_CACHE_POLL_MS = 4e3;
|
|
5623
|
-
|
|
5624
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/tui] ${args.map(errorMessage).join(" ")}\n`);
|
|
5625
|
-
}
|
|
5761
|
+
const debugLog$1 = createDebugLog("tui");
|
|
5626
5762
|
function DiscoveryShell({ children }) {
|
|
5627
5763
|
const config = useConfig();
|
|
5628
5764
|
const { settings } = useSettings();
|
|
@@ -5635,6 +5771,7 @@ function DiscoveryShell({ children }) {
|
|
|
5635
5771
|
const dataDir = config.paths.userDir;
|
|
5636
5772
|
const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
|
|
5637
5773
|
const [skillsCatalog, setSkillsCatalog] = useState([]);
|
|
5774
|
+
const [pluginsCatalog, setPluginsCatalog] = useState([]);
|
|
5638
5775
|
const [mcpsCatalog, setMcpsCatalog] = useState([]);
|
|
5639
5776
|
const [mcpsErrors, setMcpsErrors] = useState([]);
|
|
5640
5777
|
const [filesCatalog, setFilesCatalog] = useState([]);
|
|
@@ -5674,13 +5811,16 @@ function DiscoveryShell({ children }) {
|
|
|
5674
5811
|
}, [mcpCredentialStore]);
|
|
5675
5812
|
const filesSlotRef = useRef(null);
|
|
5676
5813
|
const skillsSlotRef = useRef(null);
|
|
5814
|
+
const pluginsSlotRef = useRef(null);
|
|
5677
5815
|
/** Last-published MCP discovery result — see the value-compare in `refreshMcps`. */
|
|
5678
5816
|
const mcpsFingerprintRef = useRef(null);
|
|
5679
5817
|
useEffect(() => {
|
|
5680
5818
|
filesSlotRef.current?.abort();
|
|
5681
5819
|
skillsSlotRef.current?.abort();
|
|
5820
|
+
pluginsSlotRef.current?.abort();
|
|
5682
5821
|
setFilesCatalog([]);
|
|
5683
5822
|
setSkillsCatalog([]);
|
|
5823
|
+
setPluginsCatalog([]);
|
|
5684
5824
|
const filesSlot = createDiscoverySlot({
|
|
5685
5825
|
throttleMs: FILES_REFRESH_THROTTLE_MS,
|
|
5686
5826
|
walk: (signal) => listProjectFiles({
|
|
@@ -5709,9 +5849,30 @@ function DiscoveryShell({ children }) {
|
|
|
5709
5849
|
debugLog$1(`discoverProjectSkills ${phase} failed`, err);
|
|
5710
5850
|
}
|
|
5711
5851
|
});
|
|
5852
|
+
const pluginsSlot = createDiscoverySlot({
|
|
5853
|
+
throttleMs: SKILLS_REFRESH_THROTTLE_MS,
|
|
5854
|
+
walk: async (signal) => {
|
|
5855
|
+
const { plugins, errors } = await discoverProjectPlugins({
|
|
5856
|
+
cwd: projectDir,
|
|
5857
|
+
prefix: config.prefix,
|
|
5858
|
+
...config.pluginScanPaths.length > 0 ? { extraRoots: config.pluginScanPaths } : {},
|
|
5859
|
+
...signal ? { signal } : {}
|
|
5860
|
+
});
|
|
5861
|
+
for (const error of errors) debugLog$1("discoverProjectPlugins", `${error.source}: ${error.path} — ${error.message}`);
|
|
5862
|
+
return plugins;
|
|
5863
|
+
},
|
|
5864
|
+
onLoad: (items) => {
|
|
5865
|
+
if (pluginsSlotRef.current === pluginsSlot) setPluginsCatalog(items);
|
|
5866
|
+
},
|
|
5867
|
+
onError: (err, phase) => {
|
|
5868
|
+
debugLog$1(`discoverProjectPlugins ${phase} failed`, err);
|
|
5869
|
+
}
|
|
5870
|
+
});
|
|
5712
5871
|
filesSlotRef.current = filesSlot;
|
|
5713
5872
|
skillsSlotRef.current = skillsSlot;
|
|
5873
|
+
pluginsSlotRef.current = pluginsSlot;
|
|
5714
5874
|
skillsSlot.ensure().then((skills) => bootTick(`discovery:skills (${skills.length})`));
|
|
5875
|
+
pluginsSlot.ensure().then((plugins) => bootTick(`discovery:plugins (${plugins.length})`));
|
|
5715
5876
|
try {
|
|
5716
5877
|
const { servers, errors } = discoverProjectMcps({
|
|
5717
5878
|
cwd: projectDir,
|
|
@@ -5733,6 +5894,7 @@ function DiscoveryShell({ children }) {
|
|
|
5733
5894
|
return () => {
|
|
5734
5895
|
filesSlot.abort();
|
|
5735
5896
|
skillsSlot.abort();
|
|
5897
|
+
pluginsSlot.abort();
|
|
5736
5898
|
};
|
|
5737
5899
|
}, [
|
|
5738
5900
|
projectDir,
|
|
@@ -5745,6 +5907,7 @@ function DiscoveryShell({ children }) {
|
|
|
5745
5907
|
const ensureSkills = useCallback(() => skillsSlotRef.current?.ensure() ?? Promise.resolve([]), []);
|
|
5746
5908
|
const refreshFiles = useCallback(() => filesSlotRef.current?.refresh() ?? Promise.resolve(), []);
|
|
5747
5909
|
const refreshSkills = useCallback(() => skillsSlotRef.current?.refresh() ?? Promise.resolve(), []);
|
|
5910
|
+
const refreshPlugins = useCallback(() => pluginsSlotRef.current?.refresh() ?? Promise.resolve(), []);
|
|
5748
5911
|
const refreshMcps = useCallback(() => {
|
|
5749
5912
|
try {
|
|
5750
5913
|
const { servers, errors } = discoverProjectMcps({
|
|
@@ -5791,22 +5954,26 @@ function DiscoveryShell({ children }) {
|
|
|
5791
5954
|
return /* @__PURE__ */ jsx$1(DiscoveryProvider, {
|
|
5792
5955
|
value: useMemo(() => ({
|
|
5793
5956
|
skillsCatalog,
|
|
5957
|
+
pluginsCatalog,
|
|
5794
5958
|
mcpsCatalog,
|
|
5795
5959
|
mcpsErrors,
|
|
5796
5960
|
mcpToolsByServer,
|
|
5797
5961
|
filesCatalog,
|
|
5798
5962
|
refreshSkills,
|
|
5963
|
+
refreshPlugins,
|
|
5799
5964
|
refreshMcps,
|
|
5800
5965
|
refreshFiles,
|
|
5801
5966
|
ensureSkills,
|
|
5802
5967
|
ensureFiles
|
|
5803
5968
|
}), [
|
|
5804
5969
|
skillsCatalog,
|
|
5970
|
+
pluginsCatalog,
|
|
5805
5971
|
mcpsCatalog,
|
|
5806
5972
|
mcpsErrors,
|
|
5807
5973
|
mcpToolsByServer,
|
|
5808
5974
|
filesCatalog,
|
|
5809
5975
|
refreshSkills,
|
|
5976
|
+
refreshPlugins,
|
|
5810
5977
|
refreshMcps,
|
|
5811
5978
|
refreshFiles,
|
|
5812
5979
|
ensureSkills,
|
|
@@ -13340,9 +13507,230 @@ function statusColor(status, COLOR) {
|
|
|
13340
13507
|
}
|
|
13341
13508
|
}
|
|
13342
13509
|
//#endregion
|
|
13510
|
+
//#region src/chat/contribution-origin.ts
|
|
13511
|
+
/**
|
|
13512
|
+
* Union the three skill sources into the list the Skills tab shows, in the
|
|
13513
|
+
* precedence the AGENT applies — so the tab can never advertise a skill the
|
|
13514
|
+
* agent dropped, or attribute a name to the copy that lost.
|
|
13515
|
+
*
|
|
13516
|
+
* Precedence is extension > plugin > disk, and it is not arbitrary:
|
|
13517
|
+
* `resolveSkills` seeds its map in catalog order with later entries
|
|
13518
|
+
* overwriting earlier, `app.tsx` concatenates plugin skills BEFORE extension
|
|
13519
|
+
* ones, and catalog entries shadow anything discovered on disk. The reasoning
|
|
13520
|
+
* for extension-over-plugin lives at that concat: extension code is something
|
|
13521
|
+
* the user installed deliberately, whereas a project-scope plugin arrives with
|
|
13522
|
+
* a repo clone, so a cloned repo must not shadow a user's extension skill by
|
|
13523
|
+
* reusing its name.
|
|
13524
|
+
*/
|
|
13525
|
+
function mergeSkillCatalog(opts) {
|
|
13526
|
+
const byName = /* @__PURE__ */ new Map();
|
|
13527
|
+
for (const skill of opts.disk ?? []) byName.set(skill.name, skill);
|
|
13528
|
+
for (const skill of opts.plugin ?? []) byName.set(skill.name, skill);
|
|
13529
|
+
for (const skill of opts.extension ?? []) byName.set(skill.name, skill);
|
|
13530
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
13531
|
+
}
|
|
13532
|
+
/**
|
|
13533
|
+
* Badge for a skill row, read straight off the skill's own metadata.
|
|
13534
|
+
*
|
|
13535
|
+
* `undefined` for a skill discovered on disk — no badge means "this came from a
|
|
13536
|
+
* directory you control", which is what makes a badge mean "something
|
|
13537
|
+
* contributed this".
|
|
13538
|
+
*
|
|
13539
|
+
* A plugin loaded BY an extension stamps both keys; the plugin wins, because
|
|
13540
|
+
* the plugin is what supplied the skill and the extension is only how it
|
|
13541
|
+
* arrived. `mcpOriginBadge` resolves the same collision the same way, so a row
|
|
13542
|
+
* in either tab reads identically for the same underlying situation.
|
|
13543
|
+
*/
|
|
13544
|
+
function skillOriginBadge(skill) {
|
|
13545
|
+
const plugin = skill.metadata?.["zidane.plugin"];
|
|
13546
|
+
if (plugin) return `plugin:${plugin}`;
|
|
13547
|
+
const extension = skill.metadata?.["zidane.extension"];
|
|
13548
|
+
return extension ? `ext:${extension}` : void 0;
|
|
13549
|
+
}
|
|
13550
|
+
//#endregion
|
|
13551
|
+
//#region src/tui/plugin-list.ts
|
|
13552
|
+
/** Normalize a disk-discovered plugin. */
|
|
13553
|
+
function pluginEntryFromDisk(discovered) {
|
|
13554
|
+
return normalize({
|
|
13555
|
+
name: discovered.name,
|
|
13556
|
+
plugin: discovered.plugin,
|
|
13557
|
+
origin: {
|
|
13558
|
+
kind: "disk",
|
|
13559
|
+
scope: discovered.source,
|
|
13560
|
+
dir: discovered.path
|
|
13561
|
+
}
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13564
|
+
/** Normalize a plugin an extension loaded. */
|
|
13565
|
+
function pluginEntryFromExtension(record) {
|
|
13566
|
+
return normalize({
|
|
13567
|
+
name: record.name,
|
|
13568
|
+
plugin: record.plugin,
|
|
13569
|
+
origin: {
|
|
13570
|
+
kind: "extension",
|
|
13571
|
+
extensionName: record.extensionName,
|
|
13572
|
+
dir: record.dir
|
|
13573
|
+
}
|
|
13574
|
+
});
|
|
13575
|
+
}
|
|
13576
|
+
function normalize(opts) {
|
|
13577
|
+
const { name, plugin, origin } = opts;
|
|
13578
|
+
return {
|
|
13579
|
+
name,
|
|
13580
|
+
...plugin.manifest?.version !== void 0 ? { version: plugin.manifest.version } : {},
|
|
13581
|
+
...plugin.manifest?.description !== void 0 ? { description: plugin.manifest.description } : {},
|
|
13582
|
+
origin,
|
|
13583
|
+
ok: plugin.ok,
|
|
13584
|
+
components: { ...plugin.components },
|
|
13585
|
+
contributions: {
|
|
13586
|
+
skills: plugin.skills.map((s) => ({
|
|
13587
|
+
name: s.name,
|
|
13588
|
+
...s.description !== void 0 ? { description: s.description } : {}
|
|
13589
|
+
})),
|
|
13590
|
+
mcpServers: plugin.mcpServers.map((s) => ({
|
|
13591
|
+
name: s.name,
|
|
13592
|
+
transport: s.transport
|
|
13593
|
+
}))
|
|
13594
|
+
},
|
|
13595
|
+
diagnostics: plugin.diagnostics
|
|
13596
|
+
};
|
|
13597
|
+
}
|
|
13598
|
+
/**
|
|
13599
|
+
* Build the full plugin list from both sources, deduped and sorted by name.
|
|
13600
|
+
*
|
|
13601
|
+
* Disk entries win a name collision. An extension pointing at a plugin the
|
|
13602
|
+
* user also installed on disk is the same plugin; the disk copy is the one
|
|
13603
|
+
* with a stable path and a scope the user chose, so it's the one worth
|
|
13604
|
+
* showing.
|
|
13605
|
+
*/
|
|
13606
|
+
function buildPluginList(opts) {
|
|
13607
|
+
const byName = /* @__PURE__ */ new Map();
|
|
13608
|
+
for (const discovered of opts.discovered ?? []) byName.set(discovered.name, pluginEntryFromDisk(discovered));
|
|
13609
|
+
for (const record of opts.fromExtensions ?? []) if (!byName.has(record.name)) byName.set(record.name, pluginEntryFromExtension(record));
|
|
13610
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
13611
|
+
}
|
|
13612
|
+
/**
|
|
13613
|
+
* One-line origin label for the detail row, e.g. `project: .agents/plugins/x`
|
|
13614
|
+
* or `via extension: my-toolkit`. `compact` shortens the directory (the
|
|
13615
|
+
* caller passes its `compactPath` so this module stays renderer-agnostic).
|
|
13616
|
+
*/
|
|
13617
|
+
function pluginOriginLabel(origin, compact = (p) => p) {
|
|
13618
|
+
return origin.kind === "disk" ? `${origin.scope}: ${compact(origin.dir)}` : `via extension: ${origin.extensionName}`;
|
|
13619
|
+
}
|
|
13620
|
+
/**
|
|
13621
|
+
* Short component summary for the detail row, e.g. `3 skills, 1 server`.
|
|
13622
|
+
* Empty string when the plugin contributes nothing, so the caller can drop
|
|
13623
|
+
* the segment rather than print a bare `0 skills`.
|
|
13624
|
+
*
|
|
13625
|
+
* Counts what the PACKAGE ships, which is not always what ends up live: a
|
|
13626
|
+
* server whose name collides with one in `mcps.json` (or in a plugin merged
|
|
13627
|
+
* earlier) is dropped by `dedupMcpServers` at agent build, and a skill whose
|
|
13628
|
+
* name collides is shadowed by `resolveSkills`. Neither is knowable here —
|
|
13629
|
+
* this module only sees the plugin. The MCPs and Skills tabs are the authority
|
|
13630
|
+
* on what is actually loaded; collisions are reported at the merge, via
|
|
13631
|
+
* `name-collision-shadowed` for skills and a debug log for servers.
|
|
13632
|
+
*/
|
|
13633
|
+
function pluginComponentSummary(entry) {
|
|
13634
|
+
const parts = [];
|
|
13635
|
+
const skills = entry.contributions.skills.length;
|
|
13636
|
+
const servers = entry.contributions.mcpServers.length;
|
|
13637
|
+
if (skills > 0) parts.push(`${skills} skill${skills === 1 ? "" : "s"}`);
|
|
13638
|
+
if (servers > 0) parts.push(`${servers} server${servers === 1 ? "" : "s"}`);
|
|
13639
|
+
return parts.join(", ");
|
|
13640
|
+
}
|
|
13641
|
+
/**
|
|
13642
|
+
* Whether an expanded row would have anything to show.
|
|
13643
|
+
*
|
|
13644
|
+
* Drives the disclosure chevron: a plugin that ships nothing must not offer an
|
|
13645
|
+
* expansion that opens to an empty box. A REJECTED plugin never expands either
|
|
13646
|
+
* — its components were never discovered, so there is nothing to list beyond
|
|
13647
|
+
* the failure its detail line already names.
|
|
13648
|
+
*/
|
|
13649
|
+
function pluginHasContents(entry) {
|
|
13650
|
+
return entry.ok && (entry.contributions.skills.length > 0 || entry.contributions.mcpServers.length > 0);
|
|
13651
|
+
}
|
|
13652
|
+
/**
|
|
13653
|
+
* Why a rejected plugin failed, for the detail row. Uses the first error
|
|
13654
|
+
* diagnostic's code — the message itself is often long, and the code is the
|
|
13655
|
+
* part a user can search for.
|
|
13656
|
+
*/
|
|
13657
|
+
function pluginRejectionReason(entry) {
|
|
13658
|
+
if (entry.ok) return void 0;
|
|
13659
|
+
const first = entry.diagnostics.find((d) => d.severity === "error");
|
|
13660
|
+
return first ? `rejected: ${first.code}` : "rejected";
|
|
13661
|
+
}
|
|
13662
|
+
/**
|
|
13663
|
+
* Kind badge label for an Extensions-tab row, rendered as a filled pill at the
|
|
13664
|
+
* END of the title line.
|
|
13665
|
+
*
|
|
13666
|
+
* It started as a fixed-width LEADING column, which pushed every name right by
|
|
13667
|
+
* its width — pure waste on a list that is overwhelmingly extensions, and it
|
|
13668
|
+
* broke the left alignment names had before plugins shared the tab. Trailing
|
|
13669
|
+
* keeps names flush and still answers "what is this row" without leaving the
|
|
13670
|
+
* line.
|
|
13671
|
+
*
|
|
13672
|
+
* The inner spaces are the pill's padding: a background color butted straight
|
|
13673
|
+
* against the glyphs reads as a highlight artifact rather than a badge.
|
|
13674
|
+
*/
|
|
13675
|
+
const KIND_BADGE = {
|
|
13676
|
+
ext: " ext ",
|
|
13677
|
+
plugin: " plugin "
|
|
13678
|
+
};
|
|
13679
|
+
/**
|
|
13680
|
+
* Paint for the two kind pills, following the same shape as the theme's chip
|
|
13681
|
+
* colors (saturated `bg`, pre-paired legible `fg`).
|
|
13682
|
+
*
|
|
13683
|
+
* Derived from `ThemeColors` / `ThemeSurfaces` members that EVERY theme must
|
|
13684
|
+
* define rather than added as `surfaces.chips` entries. Two reasons: the chip
|
|
13685
|
+
* map is keyed by completion-provider id, so settings row kinds don't belong in
|
|
13686
|
+
* it; and a third-party theme that never opts in would fall back to
|
|
13687
|
+
* `chips.default` for both kinds, collapsing the distinction this badge exists
|
|
13688
|
+
* to draw.
|
|
13689
|
+
*
|
|
13690
|
+
* The hierarchy is deliberate. Extensions get the neutral `border` tier because
|
|
13691
|
+
* they're the majority — a saturated pill on nearly every row is noise. Plugins
|
|
13692
|
+
* get the `model` accent so they stand out of that field. `modal` as the plugin
|
|
13693
|
+
* foreground is what keeps this theme-safe: it's the panel the badge sits on,
|
|
13694
|
+
* so it contrasts against a saturated accent on dark AND light themes, which is
|
|
13695
|
+
* the same trick every shipped theme's chip map uses (`fg: base` / `fg: panel`
|
|
13696
|
+
* / `fg: butter`).
|
|
13697
|
+
*/
|
|
13698
|
+
function kindBadgeStyle(kind, colors, surfaces) {
|
|
13699
|
+
return kind === "plugin" ? {
|
|
13700
|
+
bg: colors.model,
|
|
13701
|
+
fg: surfaces.modal
|
|
13702
|
+
} : {
|
|
13703
|
+
bg: colors.border,
|
|
13704
|
+
fg: colors.dim
|
|
13705
|
+
};
|
|
13706
|
+
}
|
|
13707
|
+
/**
|
|
13708
|
+
* Detail line for an Agent Plugin row: what it is, where it came from, and what
|
|
13709
|
+
* it contributes.
|
|
13710
|
+
*
|
|
13711
|
+
* A rejected plugin leads with its failure code instead of its description — a
|
|
13712
|
+
* plugin that didn't load has nothing useful to say about itself, and the code
|
|
13713
|
+
* is what the user can search for.
|
|
13714
|
+
*
|
|
13715
|
+
* `compact` shortens the directory (callers pass `displayPath`), keeping this
|
|
13716
|
+
* module free of path-presentation policy.
|
|
13717
|
+
*/
|
|
13718
|
+
function pluginDetailLine(entry, compact = (p) => p) {
|
|
13719
|
+
const parts = [];
|
|
13720
|
+
const rejected = pluginRejectionReason(entry);
|
|
13721
|
+
if (rejected) parts.push(rejected);
|
|
13722
|
+
else if (entry.description) parts.push(entry.description);
|
|
13723
|
+
parts.push(`[${pluginOriginLabel(entry.origin, compact)}]`);
|
|
13724
|
+
const summary = pluginComponentSummary(entry);
|
|
13725
|
+
if (summary) parts.push(summary);
|
|
13726
|
+
return parts.join(" · ");
|
|
13727
|
+
}
|
|
13728
|
+
//#endregion
|
|
13343
13729
|
//#region src/tui/extension-rows.ts
|
|
13344
13730
|
function buildVisibleExtensionRows(opts) {
|
|
13345
13731
|
const { errored, toggleable, configItemsByExt, expanded, queryActive, matchExtension, matchConfigItem } = opts;
|
|
13732
|
+
const plugins = opts.plugins ?? [];
|
|
13733
|
+
const matchPlugin = opts.matchPlugin ?? (() => true);
|
|
13346
13734
|
const out = [];
|
|
13347
13735
|
for (const ext of errored) {
|
|
13348
13736
|
if (queryActive && !matchExtension(ext)) continue;
|
|
@@ -13351,10 +13739,39 @@ function buildVisibleExtensionRows(opts) {
|
|
|
13351
13739
|
ext
|
|
13352
13740
|
});
|
|
13353
13741
|
}
|
|
13354
|
-
for (const
|
|
13742
|
+
for (const plugin of plugins) {
|
|
13743
|
+
if (plugin.ok) continue;
|
|
13744
|
+
if (queryActive && !matchPlugin(plugin)) continue;
|
|
13745
|
+
out.push({
|
|
13746
|
+
kind: "plugin",
|
|
13747
|
+
plugin,
|
|
13748
|
+
hasContents: false,
|
|
13749
|
+
expanded: false
|
|
13750
|
+
});
|
|
13751
|
+
}
|
|
13752
|
+
const body = [...toggleable.map((ext) => ({
|
|
13753
|
+
name: ext.name,
|
|
13754
|
+
ext
|
|
13755
|
+
})), ...plugins.filter((p) => p.ok).map((plugin) => ({
|
|
13756
|
+
name: plugin.name,
|
|
13757
|
+
plugin
|
|
13758
|
+
}))].sort((a, b) => a.name.localeCompare(b.name));
|
|
13759
|
+
for (const item of body) {
|
|
13760
|
+
if ("plugin" in item) {
|
|
13761
|
+
if (queryActive && !matchPlugin(item.plugin)) continue;
|
|
13762
|
+
const hasContents = pluginHasContents(item.plugin);
|
|
13763
|
+
out.push({
|
|
13764
|
+
kind: "plugin",
|
|
13765
|
+
plugin: item.plugin,
|
|
13766
|
+
hasContents,
|
|
13767
|
+
expanded: hasContents && (queryActive ? true : expanded.has(item.plugin.name))
|
|
13768
|
+
});
|
|
13769
|
+
continue;
|
|
13770
|
+
}
|
|
13771
|
+
const ext = item.ext;
|
|
13355
13772
|
const items = Object.values(configItemsByExt[ext.name] ?? {});
|
|
13356
13773
|
const headerMatches = !queryActive || matchExtension(ext);
|
|
13357
|
-
const matchingItems = queryActive ? items.filter((
|
|
13774
|
+
const matchingItems = queryActive ? items.filter((configItem) => matchConfigItem(ext.name, configItem)) : items;
|
|
13358
13775
|
if (queryActive && !headerMatches && matchingItems.length === 0) continue;
|
|
13359
13776
|
const hasConfig = items.length > 0;
|
|
13360
13777
|
const isExpanded = hasConfig && (queryActive ? true : expanded.has(ext.name));
|
|
@@ -13366,10 +13783,10 @@ function buildVisibleExtensionRows(opts) {
|
|
|
13366
13783
|
});
|
|
13367
13784
|
if (!isExpanded) continue;
|
|
13368
13785
|
const rows = queryActive && !headerMatches ? matchingItems : items;
|
|
13369
|
-
for (const
|
|
13786
|
+
for (const configItem of rows) out.push({
|
|
13370
13787
|
kind: "config",
|
|
13371
13788
|
extName: ext.name,
|
|
13372
|
-
item
|
|
13789
|
+
item: configItem
|
|
13373
13790
|
});
|
|
13374
13791
|
}
|
|
13375
13792
|
return out;
|
|
@@ -13383,6 +13800,7 @@ function parentExtensionName(row) {
|
|
|
13383
13800
|
if (!row) return void 0;
|
|
13384
13801
|
if (row.kind === "extension") return row.ext.name;
|
|
13385
13802
|
if (row.kind === "config") return row.extName;
|
|
13803
|
+
if (row.kind === "plugin") return row.hasContents ? row.plugin.name : void 0;
|
|
13386
13804
|
}
|
|
13387
13805
|
/**
|
|
13388
13806
|
* Index of the `extension` header row for the given name. `-1` when absent.
|
|
@@ -14190,7 +14608,7 @@ const TAB_LABELS = {
|
|
|
14190
14608
|
authentication: "Authentication",
|
|
14191
14609
|
skills: "Skills",
|
|
14192
14610
|
mcps: "MCP servers",
|
|
14193
|
-
extensions: "Extensions",
|
|
14611
|
+
extensions: "Extensions & Plugins",
|
|
14194
14612
|
logs: "Logs"
|
|
14195
14613
|
};
|
|
14196
14614
|
function isCategoryTab(id) {
|
|
@@ -14224,7 +14642,7 @@ function truncateToCols(s, maxCols) {
|
|
|
14224
14642
|
if (maxCols === 1) return "…";
|
|
14225
14643
|
return `${clean.slice(0, maxCols - 1)}…`;
|
|
14226
14644
|
}
|
|
14227
|
-
function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, keybindings, keybindingsPath, extensionKeybindings, extensionThemes: extensionThemesProp, extensionsCatalog: extensionsCatalogProp, extensionsInactive: extensionsInactiveProp, extensionConfigCategories: extensionConfigCategoriesProp, extensionConfigItems: extensionConfigItemsProp, modelCatalog, logs, authentication, actions, version } = {}) {
|
|
14645
|
+
function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCatalogProp, mcpsErrors: mcpsErrorsProp, keybindings, keybindingsPath, extensionKeybindings, extensionThemes: extensionThemesProp, extensionsCatalog: extensionsCatalogProp, extensionsInactive: extensionsInactiveProp, pluginsCatalog, pluginsFromExtensions, extensionContributedSkills, extensionConfigCategories: extensionConfigCategoriesProp, extensionConfigItems: extensionConfigItemsProp, modelCatalog, logs, authentication, actions, version } = {}) {
|
|
14228
14646
|
const COLOR = useColors();
|
|
14229
14647
|
const SURFACE = useSurfaces();
|
|
14230
14648
|
const { settings, toggle: toggleBoolean, setSetting } = useSettings();
|
|
@@ -14236,12 +14654,45 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14236
14654
|
const { width: termWidth } = useTerminalDimensions();
|
|
14237
14655
|
const contentCols = Math.max(40, termWidth - 2 - 4 - 2 - 1);
|
|
14238
14656
|
const discovery = useDiscoveryOptional();
|
|
14239
|
-
const
|
|
14657
|
+
const [extSnapshot, setExtSnapshot] = useState(null);
|
|
14658
|
+
const diskSkillsCatalog = discovery?.skillsCatalog ?? skillsCatalogProp ?? [];
|
|
14659
|
+
/**
|
|
14660
|
+
* Plugins from the discovery CONTEXT, falling back to the open-time prop.
|
|
14661
|
+
*
|
|
14662
|
+
* `modal.open(<SettingsModal …/>)` builds this element once inside a key
|
|
14663
|
+
* handler, so every prop is frozen at open time. Anything that has to react
|
|
14664
|
+
* while the modal is open must come from context (live) or from a
|
|
14665
|
+
* ref-backed getter (`getExtensionsSnapshot`) — the same two rails
|
|
14666
|
+
* `skillsCatalog` / `mcpsCatalog` already ride.
|
|
14667
|
+
*/
|
|
14668
|
+
const livePluginsCatalog = discovery?.pluginsCatalog ?? pluginsCatalog ?? [];
|
|
14669
|
+
/**
|
|
14670
|
+
* Skills contributed by disk plugins, derived HERE rather than passed in.
|
|
14671
|
+
*
|
|
14672
|
+
* Both inputs are live — the discovery context and `useSettings()` — so
|
|
14673
|
+
* toggling a plugin updates the Skills tab list and its count in the same
|
|
14674
|
+
* frame. As a prop this was computed in `AppShell` and captured at open
|
|
14675
|
+
* time, so a toggle changed the checkbox and nothing else.
|
|
14676
|
+
*/
|
|
14677
|
+
const pluginContributedSkills = useMemo(() => buildPluginContributions({
|
|
14678
|
+
discovered: livePluginsCatalog,
|
|
14679
|
+
...settings.enabledPlugins !== void 0 ? { enabled: settings.enabledPlugins } : {}
|
|
14680
|
+
}).skills, [livePluginsCatalog, settings.enabledPlugins]);
|
|
14681
|
+
const skillsCatalog = useMemo(() => mergeSkillCatalog({
|
|
14682
|
+
disk: diskSkillsCatalog,
|
|
14683
|
+
plugin: pluginContributedSkills,
|
|
14684
|
+
extension: extSnapshot?.skills ?? extensionContributedSkills ?? []
|
|
14685
|
+
}), [
|
|
14686
|
+
diskSkillsCatalog,
|
|
14687
|
+
pluginContributedSkills,
|
|
14688
|
+
extSnapshot,
|
|
14689
|
+
extensionContributedSkills
|
|
14690
|
+
]);
|
|
14240
14691
|
const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? [];
|
|
14241
14692
|
const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp;
|
|
14242
14693
|
const mcpToolsByServer = discovery?.mcpToolsByServer ?? EMPTY_TOOLS;
|
|
14243
14694
|
const skillsToggle = useEnabledToggleSet({
|
|
14244
|
-
catalog:
|
|
14695
|
+
catalog: diskSkillsCatalog,
|
|
14245
14696
|
keyOf: (s) => s.name,
|
|
14246
14697
|
settingKey: "enabledSkills"
|
|
14247
14698
|
});
|
|
@@ -14250,11 +14701,29 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14250
14701
|
keyOf: (m) => m.config.name,
|
|
14251
14702
|
settingKey: "enabledMcps"
|
|
14252
14703
|
});
|
|
14253
|
-
const [extSnapshot, setExtSnapshot] = useState(null);
|
|
14254
14704
|
const refreshExtensionRows = useCallback(() => {
|
|
14255
14705
|
const snap = actions?.getExtensionsSnapshot?.();
|
|
14256
14706
|
if (snap) setExtSnapshot(snap);
|
|
14257
14707
|
}, [actions]);
|
|
14708
|
+
/**
|
|
14709
|
+
* Re-pull the registry snapshot whenever a toggle changes what it would
|
|
14710
|
+
* contain.
|
|
14711
|
+
*
|
|
14712
|
+
* The snapshot is a closure over refs on the `AppShell` side, so calling it
|
|
14713
|
+
* again reads current state — which is the only way registry-derived data
|
|
14714
|
+
* can react while this element's props stay frozen at open time. Without
|
|
14715
|
+
* this, disabling an extension or an extension-loaded plugin flipped its
|
|
14716
|
+
* checkbox and left its skills in the Skills tab.
|
|
14717
|
+
*
|
|
14718
|
+
* Cheap: reading a few refs and setting state that mostly compares equal.
|
|
14719
|
+
*/
|
|
14720
|
+
useEffect(() => {
|
|
14721
|
+
refreshExtensionRows();
|
|
14722
|
+
}, [
|
|
14723
|
+
refreshExtensionRows,
|
|
14724
|
+
settings.enabledExtensions,
|
|
14725
|
+
settings.enabledPlugins
|
|
14726
|
+
]);
|
|
14258
14727
|
const extensionsInactiveAll = extSnapshot?.inactive ?? extensionsInactiveProp ?? [];
|
|
14259
14728
|
const extensionsPartition = useMemo(() => partitionExtensionCatalog(extSnapshot?.catalog ?? extensionsCatalogProp ?? [], extensionsInactiveAll), [
|
|
14260
14729
|
extSnapshot,
|
|
@@ -14269,6 +14738,20 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14269
14738
|
settingKey: "enabledExtensions",
|
|
14270
14739
|
gcKeys: extensionsPartition.allNames
|
|
14271
14740
|
});
|
|
14741
|
+
const pluginList = useMemo(() => buildPluginList({
|
|
14742
|
+
discovered: livePluginsCatalog,
|
|
14743
|
+
fromExtensions: extSnapshot?.plugins ?? pluginsFromExtensions ?? []
|
|
14744
|
+
}), [
|
|
14745
|
+
livePluginsCatalog,
|
|
14746
|
+
extSnapshot,
|
|
14747
|
+
pluginsFromExtensions
|
|
14748
|
+
]);
|
|
14749
|
+
const pluginsToggle = useEnabledToggleSet({
|
|
14750
|
+
catalog: useMemo(() => pluginList.filter((p) => p.ok), [pluginList]),
|
|
14751
|
+
keyOf: (p) => p.name,
|
|
14752
|
+
settingKey: "enabledPlugins",
|
|
14753
|
+
gcKeys: pluginList.map((p) => p.name)
|
|
14754
|
+
});
|
|
14272
14755
|
const extensionConfigCategories = extSnapshot?.configCategories ?? extensionConfigCategoriesProp ?? EMPTY_CONFIG_CATEGORIES;
|
|
14273
14756
|
const extensionConfigItems = extSnapshot?.configItems ?? extensionConfigItemsProp ?? EMPTY_CONFIG_ITEMS;
|
|
14274
14757
|
const configInputRef = useRef(null);
|
|
@@ -14716,14 +15199,17 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14716
15199
|
expanded: expandedExtensions,
|
|
14717
15200
|
queryActive,
|
|
14718
15201
|
matchExtension: (ext) => matchesTerms(extensionCorpus(ext), queryTerms),
|
|
14719
|
-
matchConfigItem: (extName, item) => matchesTerms(extensionConfigItemCorpus(extName, item), queryTerms)
|
|
15202
|
+
matchConfigItem: (extName, item) => matchesTerms(extensionConfigItemCorpus(extName, item), queryTerms),
|
|
15203
|
+
plugins: pluginList,
|
|
15204
|
+
matchPlugin: (plugin) => matchesTerms(pluginCorpus(plugin), queryTerms)
|
|
14720
15205
|
}), [
|
|
14721
15206
|
extensionsErrored,
|
|
14722
15207
|
extensionsCatalog,
|
|
14723
15208
|
extensionConfigItems,
|
|
14724
15209
|
expandedExtensions,
|
|
14725
15210
|
queryActive,
|
|
14726
|
-
queryTerms
|
|
15211
|
+
queryTerms,
|
|
15212
|
+
pluginList
|
|
14727
15213
|
]);
|
|
14728
15214
|
const mcpVisibleRows = useMemo(() => buildVisibleMcpRows(filteredMcps, expandedMcps, mcpToolsByServer), [
|
|
14729
15215
|
filteredMcps,
|
|
@@ -14898,6 +15384,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14898
15384
|
const row = extensionVisibleRows[cursor];
|
|
14899
15385
|
if (!row) return void 0;
|
|
14900
15386
|
if (row.kind === "extension" || row.kind === "errored") return row.ext;
|
|
15387
|
+
if (row.kind === "plugin") return void 0;
|
|
14901
15388
|
return extensionsCatalog.find((e) => e.name === row.extName) ?? extensionsErrored.find((e) => e.name === row.extName);
|
|
14902
15389
|
}, [
|
|
14903
15390
|
activeTab,
|
|
@@ -14956,14 +15443,16 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14956
15443
|
}
|
|
14957
15444
|
if (activeTab === "skills") {
|
|
14958
15445
|
const s = filteredSkills[index];
|
|
14959
|
-
if (s) skillsToggle.toggle(s.name);
|
|
15446
|
+
if (s && !skillOriginBadge(s)) skillsToggle.toggle(s.name);
|
|
14960
15447
|
return;
|
|
14961
15448
|
}
|
|
14962
15449
|
if (activeTab === "extensions") {
|
|
14963
15450
|
const row = extensionVisibleRows[index];
|
|
14964
15451
|
if (!row) return;
|
|
14965
15452
|
if (row.kind === "extension") extensionsToggle.toggle(row.ext.name);
|
|
14966
|
-
else if (row.kind === "
|
|
15453
|
+
else if (row.kind === "plugin") {
|
|
15454
|
+
if (row.plugin.ok) pluginsToggle.toggle(row.plugin.name);
|
|
15455
|
+
} else if (row.kind === "config") activateConfigRow(row.extName, row.item);
|
|
14967
15456
|
return;
|
|
14968
15457
|
}
|
|
14969
15458
|
if (activeTab === "mcps") {
|
|
@@ -14995,6 +15484,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14995
15484
|
skillsToggle,
|
|
14996
15485
|
extensionVisibleRows,
|
|
14997
15486
|
extensionsToggle,
|
|
15487
|
+
pluginsToggle,
|
|
14998
15488
|
activateConfigRow,
|
|
14999
15489
|
mcpVisibleRows,
|
|
15000
15490
|
mcpsToggle,
|
|
@@ -15055,6 +15545,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15055
15545
|
return;
|
|
15056
15546
|
}
|
|
15057
15547
|
if (row?.kind === "config" && row.item.type === "boolean") return;
|
|
15548
|
+
if (row?.kind === "plugin") return;
|
|
15058
15549
|
activateRowAt(index);
|
|
15059
15550
|
return;
|
|
15060
15551
|
}
|
|
@@ -15341,6 +15832,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15341
15832
|
actions.onRefreshSkills();
|
|
15342
15833
|
return;
|
|
15343
15834
|
}
|
|
15835
|
+
if (activeTab === "extensions" && actions?.onRefreshPlugins) {
|
|
15836
|
+
actions.onRefreshPlugins();
|
|
15837
|
+
return;
|
|
15838
|
+
}
|
|
15344
15839
|
if (activeTab === "mcps") {
|
|
15345
15840
|
if (actions?.onRefreshMcps) actions.onRefreshMcps();
|
|
15346
15841
|
fireMcpToolsRefresh();
|
|
@@ -15366,8 +15861,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15366
15861
|
total: mcpsCatalog.length
|
|
15367
15862
|
},
|
|
15368
15863
|
extensions: {
|
|
15369
|
-
current: extensionVisibleRows.filter((r) => r.kind === "extension" || r.kind === "errored").length,
|
|
15370
|
-
total: extensionsCatalog.length + extensionsErrored.length
|
|
15864
|
+
current: extensionVisibleRows.filter((r) => r.kind === "extension" || r.kind === "errored" || r.kind === "plugin").length,
|
|
15865
|
+
total: extensionsCatalog.length + extensionsErrored.length + pluginList.length
|
|
15371
15866
|
}
|
|
15372
15867
|
};
|
|
15373
15868
|
const editingInputItem = editingInputKey ? SETTINGS_INPUTS.find((inp) => inp.key === editingInputKey) ?? null : null;
|
|
@@ -15452,9 +15947,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15452
15947
|
onRowClick: clickRowAt,
|
|
15453
15948
|
rows: extensionVisibleRows,
|
|
15454
15949
|
enabledSet: extensionsToggle.enabledSet,
|
|
15950
|
+
pluginEnabledSet: pluginsToggle.enabledSet,
|
|
15455
15951
|
extensionConfig: settings.extensionConfig,
|
|
15456
15952
|
categories: extensionConfigCategories,
|
|
15457
|
-
totalCount: extensionsCatalog.length + extensionsErrored.length,
|
|
15953
|
+
totalCount: extensionsCatalog.length + extensionsErrored.length + pluginList.length,
|
|
15458
15954
|
cursor,
|
|
15459
15955
|
highlightBg: SURFACE.selection,
|
|
15460
15956
|
query,
|
|
@@ -15609,6 +16105,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15609
16105
|
focusedMcpStatus,
|
|
15610
16106
|
canRefreshSkills: !!actions?.onRefreshSkills,
|
|
15611
16107
|
canRefreshMcps: !!actions?.onRefreshMcps,
|
|
16108
|
+
canRefreshPlugins: !!actions?.onRefreshPlugins,
|
|
15612
16109
|
canEditExtension: !!actions?.onEditExtension,
|
|
15613
16110
|
canCreateExtension: !!actions?.onCreateExtension,
|
|
15614
16111
|
canUpdateExtension: !!actions?.onUpdateExtension,
|
|
@@ -15782,57 +16279,89 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
|
|
|
15782
16279
|
});
|
|
15783
16280
|
}
|
|
15784
16281
|
const EXTENSION_CONFIG_INDENT = 3;
|
|
15785
|
-
function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit, actionStatus, actionProbes, onRowClick }) {
|
|
16282
|
+
function ExtensionsList({ rows, enabledSet, pluginEnabledSet, extensionConfig, categories, totalCount, cursor, highlightBg, query, contentCols, editing, inputRef, onCommitEdit, modelCatalog, onApplyModelEdit, onCancelEdit, actionStatus, actionProbes, onRowClick }) {
|
|
15786
16283
|
const COLOR = useColors();
|
|
16284
|
+
const SURFACE = useSurfaces();
|
|
16285
|
+
const extBadge = kindBadgeStyle("ext", COLOR, SURFACE);
|
|
16286
|
+
const pluginBadge = kindBadgeStyle("plugin", COLOR, SURFACE);
|
|
15787
16287
|
const home = homedir();
|
|
15788
16288
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
15789
16289
|
style: { flexDirection: "column" },
|
|
15790
|
-
children: [
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
16290
|
+
children: [
|
|
16291
|
+
/* @__PURE__ */ jsx$1("text", {
|
|
16292
|
+
fg: COLOR.dim,
|
|
16293
|
+
children: "No extensions or plugins discovered."
|
|
16294
|
+
}),
|
|
16295
|
+
/* @__PURE__ */ jsxs("text", {
|
|
16296
|
+
fg: COLOR.mute,
|
|
16297
|
+
children: [
|
|
16298
|
+
"Drop an",
|
|
16299
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16300
|
+
fg: COLOR.model,
|
|
16301
|
+
children: " index.ts "
|
|
16302
|
+
}),
|
|
16303
|
+
"into",
|
|
16304
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16305
|
+
fg: COLOR.model,
|
|
16306
|
+
children: " .zidane/extensions/<name>/ "
|
|
16307
|
+
}),
|
|
16308
|
+
"or",
|
|
16309
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16310
|
+
fg: COLOR.model,
|
|
16311
|
+
children: " .agents/extensions/<name>/ "
|
|
16312
|
+
}),
|
|
16313
|
+
"(project or",
|
|
16314
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16315
|
+
fg: COLOR.model,
|
|
16316
|
+
children: " ~/"
|
|
16317
|
+
}),
|
|
16318
|
+
"). Author with",
|
|
16319
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16320
|
+
fg: COLOR.model,
|
|
16321
|
+
children: " defineExtension({ name, setup }) "
|
|
16322
|
+
}),
|
|
16323
|
+
"from",
|
|
16324
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16325
|
+
fg: COLOR.model,
|
|
16326
|
+
children: " zidane "
|
|
16327
|
+
}),
|
|
16328
|
+
"— see",
|
|
16329
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16330
|
+
fg: COLOR.model,
|
|
16331
|
+
children: " docs/EXTENSIONS.md "
|
|
16332
|
+
}),
|
|
16333
|
+
"."
|
|
16334
|
+
]
|
|
16335
|
+
}),
|
|
16336
|
+
/* @__PURE__ */ jsxs("text", {
|
|
16337
|
+
fg: COLOR.mute,
|
|
16338
|
+
children: [
|
|
16339
|
+
"For an Agent Plugin, drop a package with a",
|
|
16340
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16341
|
+
fg: COLOR.model,
|
|
16342
|
+
children: " plugin.json "
|
|
16343
|
+
}),
|
|
16344
|
+
"into",
|
|
16345
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16346
|
+
fg: COLOR.model,
|
|
16347
|
+
children: " .agents/plugins/<name>/ "
|
|
16348
|
+
}),
|
|
16349
|
+
"(project or",
|
|
16350
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16351
|
+
fg: COLOR.model,
|
|
16352
|
+
children: " ~/"
|
|
16353
|
+
}),
|
|
16354
|
+
") — see",
|
|
16355
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16356
|
+
fg: COLOR.model,
|
|
16357
|
+
children: " docs/PLUGINS.md "
|
|
16358
|
+
}),
|
|
16359
|
+
"."
|
|
16360
|
+
]
|
|
16361
|
+
})
|
|
16362
|
+
]
|
|
15834
16363
|
});
|
|
15835
|
-
if (rows.length === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: `no extensions match "${query}"` });
|
|
16364
|
+
if (rows.length === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: `no extensions or plugins match "${query}"` });
|
|
15836
16365
|
const descCols = Math.max(0, contentCols - 1 - 1 - 6);
|
|
15837
16366
|
const groups = [];
|
|
15838
16367
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -15845,6 +16374,16 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
15845
16374
|
});
|
|
15846
16375
|
continue;
|
|
15847
16376
|
}
|
|
16377
|
+
if (row.kind === "plugin") {
|
|
16378
|
+
groups.push({
|
|
16379
|
+
kind: "plugin",
|
|
16380
|
+
plugin: row.plugin,
|
|
16381
|
+
hasContents: row.hasContents,
|
|
16382
|
+
expanded: row.expanded,
|
|
16383
|
+
index: i
|
|
16384
|
+
});
|
|
16385
|
+
continue;
|
|
16386
|
+
}
|
|
15848
16387
|
if (row.kind === "extension") {
|
|
15849
16388
|
groups.push({
|
|
15850
16389
|
kind: "extension",
|
|
@@ -15893,6 +16432,12 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
15893
16432
|
fg: focused ? COLOR.brand : COLOR.dim,
|
|
15894
16433
|
children: group.ext.name
|
|
15895
16434
|
}),
|
|
16435
|
+
/* @__PURE__ */ jsx$1("span", { children: " " }),
|
|
16436
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16437
|
+
fg: extBadge.fg,
|
|
16438
|
+
bg: extBadge.bg,
|
|
16439
|
+
children: KIND_BADGE.ext
|
|
16440
|
+
}),
|
|
15896
16441
|
/* @__PURE__ */ jsx$1("span", {
|
|
15897
16442
|
fg: COLOR.mute,
|
|
15898
16443
|
children: ` ${where}`
|
|
@@ -15911,6 +16456,108 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
15911
16456
|
})]
|
|
15912
16457
|
}, `err:${group.ext.name}`);
|
|
15913
16458
|
}
|
|
16459
|
+
if (group.kind === "plugin") {
|
|
16460
|
+
const focused = group.index === cursor;
|
|
16461
|
+
const bg = focused ? highlightBg : void 0;
|
|
16462
|
+
const entry = group.plugin;
|
|
16463
|
+
const rejected = pluginRejectionReason(entry);
|
|
16464
|
+
const enabled = pluginEnabledSet.has(entry.name);
|
|
16465
|
+
const detail = truncateToCols(pluginDetailLine(entry, (p) => displayPath$1(p, home)), descCols);
|
|
16466
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
16467
|
+
id: anchorIdFor(group.index),
|
|
16468
|
+
style: {
|
|
16469
|
+
flexDirection: "column",
|
|
16470
|
+
flexShrink: 0
|
|
16471
|
+
},
|
|
16472
|
+
children: [
|
|
16473
|
+
/* @__PURE__ */ jsxs("text", {
|
|
16474
|
+
wrapMode: "none",
|
|
16475
|
+
bg,
|
|
16476
|
+
onMouseDown: onRowClick && ((event) => {
|
|
16477
|
+
event.stopPropagation();
|
|
16478
|
+
event.preventDefault();
|
|
16479
|
+
onRowClick(group.index, inCheckboxZone(event, 4) ? "checkbox" : "row");
|
|
16480
|
+
}),
|
|
16481
|
+
children: [
|
|
16482
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16483
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
16484
|
+
children: focused ? "▶ " : " "
|
|
16485
|
+
}),
|
|
16486
|
+
rejected ? /* @__PURE__ */ jsx$1("span", {
|
|
16487
|
+
fg: COLOR.error,
|
|
16488
|
+
children: "✗ "
|
|
16489
|
+
}) : group.hasContents ? /* @__PURE__ */ jsx$1("span", {
|
|
16490
|
+
fg: COLOR.mute,
|
|
16491
|
+
children: group.expanded ? "▾ " : "▸ "
|
|
16492
|
+
}) : /* @__PURE__ */ jsx$1("span", {
|
|
16493
|
+
fg: COLOR.mute,
|
|
16494
|
+
children: " "
|
|
16495
|
+
}),
|
|
16496
|
+
rejected ? /* @__PURE__ */ jsx$1("span", {
|
|
16497
|
+
fg: COLOR.mute,
|
|
16498
|
+
children: " "
|
|
16499
|
+
}) : /* @__PURE__ */ jsx$1("span", {
|
|
16500
|
+
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
16501
|
+
children: enabled ? "[✓] " : "[ ] "
|
|
16502
|
+
}),
|
|
16503
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16504
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
16505
|
+
children: entry.name
|
|
16506
|
+
}),
|
|
16507
|
+
entry.version && /* @__PURE__ */ jsx$1("span", {
|
|
16508
|
+
fg: COLOR.mute,
|
|
16509
|
+
children: ` v${entry.version}`
|
|
16510
|
+
}),
|
|
16511
|
+
/* @__PURE__ */ jsx$1("span", { children: " " }),
|
|
16512
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16513
|
+
fg: pluginBadge.fg,
|
|
16514
|
+
bg: pluginBadge.bg,
|
|
16515
|
+
children: KIND_BADGE.plugin
|
|
16516
|
+
})
|
|
16517
|
+
]
|
|
16518
|
+
}),
|
|
16519
|
+
detail && /* @__PURE__ */ jsx$1("text", {
|
|
16520
|
+
wrapMode: "none",
|
|
16521
|
+
fg: rejected ? COLOR.error : COLOR.mute,
|
|
16522
|
+
children: `${COL_TITLE}${detail}`
|
|
16523
|
+
}),
|
|
16524
|
+
group.expanded && entry.contributions.skills.map((skill) => /* @__PURE__ */ jsxs("text", {
|
|
16525
|
+
wrapMode: "none",
|
|
16526
|
+
children: [
|
|
16527
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16528
|
+
fg: COLOR.mute,
|
|
16529
|
+
children: `${COL_TITLE} skill `
|
|
16530
|
+
}),
|
|
16531
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16532
|
+
fg: COLOR.dim,
|
|
16533
|
+
children: skill.name
|
|
16534
|
+
}),
|
|
16535
|
+
skill.description && /* @__PURE__ */ jsxs("span", {
|
|
16536
|
+
fg: COLOR.mute,
|
|
16537
|
+
children: [" ", truncateToCols(skill.description, Math.max(0, descCols - skill.name.length - 12))]
|
|
16538
|
+
})
|
|
16539
|
+
]
|
|
16540
|
+
}, `skill:${skill.name}`)),
|
|
16541
|
+
group.expanded && entry.contributions.mcpServers.map((server) => /* @__PURE__ */ jsxs("text", {
|
|
16542
|
+
wrapMode: "none",
|
|
16543
|
+
children: [
|
|
16544
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16545
|
+
fg: COLOR.mute,
|
|
16546
|
+
children: `${COL_TITLE} server `
|
|
16547
|
+
}),
|
|
16548
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16549
|
+
fg: COLOR.dim,
|
|
16550
|
+
children: server.name
|
|
16551
|
+
}),
|
|
16552
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16553
|
+
fg: COLOR.mute,
|
|
16554
|
+
children: ` ${server.transport}`
|
|
16555
|
+
})
|
|
16556
|
+
]
|
|
16557
|
+
}, `server:${server.name}`))
|
|
16558
|
+
]
|
|
16559
|
+
}, `plugin:${entry.name}`);
|
|
16560
|
+
}
|
|
15914
16561
|
const focused = group.index === cursor;
|
|
15915
16562
|
const bg = focused ? highlightBg : void 0;
|
|
15916
16563
|
const enabled = enabledSet.has(group.ext.name);
|
|
@@ -16029,6 +16676,12 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
16029
16676
|
group.ext.definition?.version && /* @__PURE__ */ jsx$1("span", {
|
|
16030
16677
|
fg: COLOR.mute,
|
|
16031
16678
|
children: ` v${group.ext.definition.version}`
|
|
16679
|
+
}),
|
|
16680
|
+
/* @__PURE__ */ jsx$1("span", { children: " " }),
|
|
16681
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
16682
|
+
fg: extBadge.fg,
|
|
16683
|
+
bg: extBadge.bg,
|
|
16684
|
+
children: KIND_BADGE.ext
|
|
16032
16685
|
})
|
|
16033
16686
|
]
|
|
16034
16687
|
}),
|
|
@@ -16272,6 +16925,7 @@ function ConfigModelPickerRow({ id, label, description, catalog, multi, initial,
|
|
|
16272
16925
|
}
|
|
16273
16926
|
function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query, contentCols, onRowClick }) {
|
|
16274
16927
|
const COLOR = useColors();
|
|
16928
|
+
const contributedBadge = kindBadgeStyle("plugin", COLOR, useSurfaces());
|
|
16275
16929
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
16276
16930
|
style: { flexDirection: "column" },
|
|
16277
16931
|
children: [/* @__PURE__ */ jsx$1("text", {
|
|
@@ -16314,6 +16968,7 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
16314
16968
|
const enabled = enabledSet.has(entry.name);
|
|
16315
16969
|
const description = truncateToCols(entry.description ?? "", descCols);
|
|
16316
16970
|
const origin = skillOriginLabel(entry);
|
|
16971
|
+
const badge = skillOriginBadge(entry);
|
|
16317
16972
|
return /* @__PURE__ */ jsxs("box", {
|
|
16318
16973
|
onMouseDown: onRowClick && ((event) => {
|
|
16319
16974
|
event.stopPropagation();
|
|
@@ -16333,14 +16988,17 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
16333
16988
|
onMouseDown: onRowClick && ((event) => {
|
|
16334
16989
|
event.stopPropagation();
|
|
16335
16990
|
event.preventDefault();
|
|
16336
|
-
onRowClick(i, inCheckboxZone(event, 2) ? "checkbox" : "row");
|
|
16991
|
+
onRowClick(i, !badge && inCheckboxZone(event, 2) ? "checkbox" : "row");
|
|
16337
16992
|
}),
|
|
16338
16993
|
children: [
|
|
16339
16994
|
/* @__PURE__ */ jsx$1("span", {
|
|
16340
16995
|
fg: focused ? COLOR.brand : COLOR.mute,
|
|
16341
16996
|
children: focused ? "▶ " : " "
|
|
16342
16997
|
}),
|
|
16343
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
16998
|
+
badge ? /* @__PURE__ */ jsx$1("span", {
|
|
16999
|
+
fg: COLOR.mute,
|
|
17000
|
+
children: " "
|
|
17001
|
+
}) : /* @__PURE__ */ jsx$1("span", {
|
|
16344
17002
|
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
16345
17003
|
children: enabled ? "[✓] " : "[ ] "
|
|
16346
17004
|
}),
|
|
@@ -16348,7 +17006,11 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
16348
17006
|
fg: focused ? COLOR.brand : COLOR.dim,
|
|
16349
17007
|
children: entry.name
|
|
16350
17008
|
}),
|
|
16351
|
-
|
|
17009
|
+
badge ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", { children: " " }), /* @__PURE__ */ jsx$1("span", {
|
|
17010
|
+
fg: contributedBadge.fg,
|
|
17011
|
+
bg: contributedBadge.bg,
|
|
17012
|
+
children: ` ${badge} `
|
|
17013
|
+
})] }) : origin && /* @__PURE__ */ jsxs("span", {
|
|
16352
17014
|
fg: COLOR.mute,
|
|
16353
17015
|
children: [" · ", origin]
|
|
16354
17016
|
})
|
|
@@ -17051,13 +17713,13 @@ function renderMcpErrors(errors, home, warnColor, dimColor) {
|
|
|
17051
17713
|
}, err.path))
|
|
17052
17714
|
});
|
|
17053
17715
|
}
|
|
17054
|
-
function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps, canEditExtension, canCreateExtension, canUpdateExtension, canRemoveExtension, canInstallExtension, canInstallSkill, canEditSkill, canAddMcp, canRemoveSkill, canRemoveMcp, focusedExtensionExpandable, focusedSecretClearable = false }) {
|
|
17716
|
+
function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps, canRefreshPlugins, canEditExtension, canCreateExtension, canUpdateExtension, canRemoveExtension, canInstallExtension, canInstallSkill, canEditSkill, canAddMcp, canRemoveSkill, canRemoveMcp, focusedExtensionExpandable, focusedSecretClearable = false }) {
|
|
17055
17717
|
const COLOR = useColors();
|
|
17056
17718
|
const onServerRow = focusedMcpRow?.kind === "server";
|
|
17057
17719
|
const showLogin = activeTab === "mcps" && onServerRow && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
|
|
17058
17720
|
const showLogout = activeTab === "mcps" && onServerRow && !!focusedMcpStatus && canLogout$1(focusedMcpStatus);
|
|
17059
17721
|
const showCancel = activeTab === "mcps" && authorizing;
|
|
17060
|
-
const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
|
|
17722
|
+
const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps || activeTab === "extensions" && canRefreshPlugins;
|
|
17061
17723
|
return /* @__PURE__ */ jsx$1(HintRow, { hints: [
|
|
17062
17724
|
{
|
|
17063
17725
|
key: "←→",
|
|
@@ -17183,6 +17845,21 @@ function extensionCorpus(e) {
|
|
|
17183
17845
|
].join(" ").toLowerCase();
|
|
17184
17846
|
}
|
|
17185
17847
|
/**
|
|
17848
|
+
* Search corpus for an Agent Plugin row. Includes the literal word `plugin`
|
|
17849
|
+
* and the origin, so typing `plugin` narrows to plugins and typing an
|
|
17850
|
+
* extension's name surfaces the plugins it loaded.
|
|
17851
|
+
*/
|
|
17852
|
+
function pluginCorpus(p) {
|
|
17853
|
+
return [
|
|
17854
|
+
"plugin",
|
|
17855
|
+
p.name,
|
|
17856
|
+
p.version ?? "",
|
|
17857
|
+
p.description ?? "",
|
|
17858
|
+
p.origin.kind === "disk" ? p.origin.scope : p.origin.extensionName,
|
|
17859
|
+
p.origin.dir
|
|
17860
|
+
].join(" ").toLowerCase();
|
|
17861
|
+
}
|
|
17862
|
+
/**
|
|
17186
17863
|
* What a config row shows at rest. Secrets are masked (last 4 kept so the user
|
|
17187
17864
|
* can tell WHICH token is stored), unset model rows read `(unset)`.
|
|
17188
17865
|
*
|
|
@@ -18134,7 +18811,7 @@ const SESSION_SEARCH_INDEX_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
18134
18811
|
*/
|
|
18135
18812
|
/** How long the `⚡ cache break` footer toast stays visible after a break. */
|
|
18136
18813
|
const CACHE_BREAK_CHIP_MS = 8e3;
|
|
18137
|
-
const debugLog =
|
|
18814
|
+
const debugLog = createDebugLog("tui");
|
|
18138
18815
|
/**
|
|
18139
18816
|
* Launch the user's preferred editor on `path`. Tries `$EDITOR` /
|
|
18140
18817
|
* `$VISUAL` first (the standard *nix convention for "open this
|
|
@@ -18220,21 +18897,6 @@ function reduceEditsByOutcomes(edits, outcomes) {
|
|
|
18220
18897
|
}
|
|
18221
18898
|
return out;
|
|
18222
18899
|
}
|
|
18223
|
-
/**
|
|
18224
|
-
* Dedup an MCP-server list by `name`. First entry wins — used at
|
|
18225
|
-
* agent build to give project-level `mcps.json` precedence over any
|
|
18226
|
-
* extension-contributed servers that happen to share a name.
|
|
18227
|
-
*/
|
|
18228
|
-
function dedupMcpServers(servers) {
|
|
18229
|
-
const seen = /* @__PURE__ */ new Set();
|
|
18230
|
-
const out = [];
|
|
18231
|
-
for (const server of servers) {
|
|
18232
|
-
if (seen.has(server.name)) continue;
|
|
18233
|
-
seen.add(server.name);
|
|
18234
|
-
out.push(server);
|
|
18235
|
-
}
|
|
18236
|
-
return out;
|
|
18237
|
-
}
|
|
18238
18900
|
const ExtensionRegistryContext = createContext(null);
|
|
18239
18901
|
function useExtensionRegistryState() {
|
|
18240
18902
|
const ctx = useContext(ExtensionRegistryContext);
|
|
@@ -18496,7 +19158,7 @@ function AppShell() {
|
|
|
18496
19158
|
const sessionSafelistRef = useRef(/* @__PURE__ */ new Set());
|
|
18497
19159
|
const streamRef = useRef(null);
|
|
18498
19160
|
const pendingAnnotationsRef = useRef(/* @__PURE__ */ new Map());
|
|
18499
|
-
const { skillsCatalog, mcpsCatalog, filesCatalog, ensureFiles: ensureFilesCatalog, ensureSkills: ensureSkillsCatalog, refreshSkills: onRefreshSkills, refreshMcps: onRefreshMcps } = useDiscovery();
|
|
19161
|
+
const { skillsCatalog, pluginsCatalog, mcpsCatalog, filesCatalog, ensureFiles: ensureFilesCatalog, ensureSkills: ensureSkillsCatalog, refreshSkills: onRefreshSkills, refreshMcps: onRefreshMcps, refreshPlugins: onRefreshPlugins } = useDiscovery();
|
|
18500
19162
|
const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
|
|
18501
19163
|
const dispatchAuth = useMcpAuthDispatch();
|
|
18502
19164
|
const dispatchAuthRef = useRef(dispatchAuth);
|
|
@@ -18505,8 +19167,38 @@ function AppShell() {
|
|
|
18505
19167
|
const onLoginMcpRef = useRef(null);
|
|
18506
19168
|
const skillsCatalogRef = useRef(skillsCatalog);
|
|
18507
19169
|
skillsCatalogRef.current = skillsCatalog;
|
|
19170
|
+
const pluginsCatalogRef = useRef(pluginsCatalog);
|
|
19171
|
+
pluginsCatalogRef.current = pluginsCatalog;
|
|
18508
19172
|
const enabledSkillsRef = useRef(settings.enabledSkills);
|
|
18509
19173
|
enabledSkillsRef.current = settings.enabledSkills;
|
|
19174
|
+
const enabledPluginsRef = useRef(settings.enabledPlugins);
|
|
19175
|
+
enabledPluginsRef.current = settings.enabledPlugins;
|
|
19176
|
+
/**
|
|
19177
|
+
* Registry with the user's toggles applied, for surfaces that must reflect
|
|
19178
|
+
* them: disabling an extension or a plugin re-runs this memo and its
|
|
19179
|
+
* contributions disappear in the same frame the agent is rebuilt.
|
|
19180
|
+
*
|
|
19181
|
+
* Deliberately NOT used for the Extensions & Plugins tab's own rows — those
|
|
19182
|
+
* come from the RAW registry, because a disabled extension or plugin must
|
|
19183
|
+
* stay listed or its toggle becomes unreachable.
|
|
19184
|
+
*/
|
|
19185
|
+
const liveRegistryForSettings = useMemo(() => filterRegistryPlugins(filterExtensionRegistry(extensionRegistry, settings.enabledExtensions), settings.enabledPlugins, new Set(pluginsCatalog.map((p) => p.name))), [
|
|
19186
|
+
extensionRegistry,
|
|
19187
|
+
settings.enabledExtensions,
|
|
19188
|
+
settings.enabledPlugins,
|
|
19189
|
+
pluginsCatalog
|
|
19190
|
+
]);
|
|
19191
|
+
/**
|
|
19192
|
+
* Ref mirror of the above, read by `getExtensionsSnapshot`.
|
|
19193
|
+
*
|
|
19194
|
+
* The settings modal is built once inside a key handler
|
|
19195
|
+
* (`modal.open(<SettingsModal …/>)`), so its props are frozen at open time.
|
|
19196
|
+
* A getter closing over a REF is how the modal pulls current state anyway —
|
|
19197
|
+
* this puts the filtered registry on that same rail, so a plugin toggled off
|
|
19198
|
+
* while settings is open drops out of the Skills tab.
|
|
19199
|
+
*/
|
|
19200
|
+
const liveRegistryForSettingsRef = useRef(liveRegistryForSettings);
|
|
19201
|
+
liveRegistryForSettingsRef.current = liveRegistryForSettings;
|
|
18510
19202
|
const importForeignConfigsRef = useRef(settings.importForeignConfigs);
|
|
18511
19203
|
importForeignConfigsRef.current = settings.importForeignConfigs;
|
|
18512
19204
|
const importRulerConfigsRef = useRef(settings.importRulerConfigs);
|
|
@@ -18517,14 +19209,36 @@ function AppShell() {
|
|
|
18517
19209
|
enabledMcpsRef.current = settings.enabledMcps;
|
|
18518
19210
|
const disabledMcpToolsRef = useRef(settings.disabledMcpTools);
|
|
18519
19211
|
disabledMcpToolsRef.current = settings.disabledMcpTools;
|
|
19212
|
+
/**
|
|
19213
|
+
* Fingerprint of everything that feeds the agent's MCP + plugin runtime.
|
|
19214
|
+
* A change schedules a rebuild (see the effect keyed on this below).
|
|
19215
|
+
*
|
|
19216
|
+
* Plugin state belongs here even though the name says "mcp": plugin
|
|
19217
|
+
* contributions are SNAPSHOTTED into the agent at build time (unlike
|
|
19218
|
+
* skills, which pass `scan` paths the agent resolves itself). The plugin
|
|
19219
|
+
* walk is async and nothing orders it before the first build, so without
|
|
19220
|
+
* the catalog in this key whether a cold boot picked up plugins would be
|
|
19221
|
+
* timing-dependent — and a late-landing catalog would have no recovery
|
|
19222
|
+
* path. `enabledPlugins` is here for the same reason a toggle of
|
|
19223
|
+
* `enabledMcps` is: it changes which servers should be connected.
|
|
19224
|
+
*/
|
|
18520
19225
|
const mcpRuntimeKey = useMemo(() => JSON.stringify({
|
|
18521
19226
|
catalog: mcpsCatalog.map(({ config }) => config),
|
|
18522
19227
|
enabled: settings.enabledMcps ?? null,
|
|
18523
|
-
disabledTools: settings.disabledMcpTools ?? null
|
|
19228
|
+
disabledTools: settings.disabledMcpTools ?? null,
|
|
19229
|
+
plugins: pluginsCatalog.map(({ name, plugin }) => ({
|
|
19230
|
+
name,
|
|
19231
|
+
ok: plugin.ok,
|
|
19232
|
+
skills: plugin.skills,
|
|
19233
|
+
servers: plugin.mcpServers
|
|
19234
|
+
})),
|
|
19235
|
+
enabledPlugins: settings.enabledPlugins ?? null
|
|
18524
19236
|
}), [
|
|
18525
19237
|
mcpsCatalog,
|
|
18526
19238
|
settings.enabledMcps,
|
|
18527
|
-
settings.disabledMcpTools
|
|
19239
|
+
settings.disabledMcpTools,
|
|
19240
|
+
pluginsCatalog,
|
|
19241
|
+
settings.enabledPlugins
|
|
18528
19242
|
]);
|
|
18529
19243
|
const filesCatalogRef = useRef(filesCatalog);
|
|
18530
19244
|
filesCatalogRef.current = filesCatalog;
|
|
@@ -19441,18 +20155,26 @@ function AppShell() {
|
|
|
19441
20155
|
foreignAgents: importForeignConfigsRef.current === true,
|
|
19442
20156
|
ruler: importRulerConfigsRef.current !== false
|
|
19443
20157
|
});
|
|
19444
|
-
const liveExtensionRegistry = filterExtensionRegistry(extensionRegistry, enabledExtensionsRef.current);
|
|
20158
|
+
const liveExtensionRegistry = filterRegistryPlugins(filterExtensionRegistry(extensionRegistry, enabledExtensionsRef.current), enabledPluginsRef.current, new Set(pluginsCatalogRef.current.map((p) => p.name)));
|
|
20159
|
+
const pluginContributions = buildPluginContributions({
|
|
20160
|
+
discovered: pluginsCatalogRef.current,
|
|
20161
|
+
...enabledPluginsRef.current !== void 0 ? { enabled: enabledPluginsRef.current } : {}
|
|
20162
|
+
});
|
|
19445
20163
|
const extensionSkillNames = liveExtensionRegistry.skills.map((s) => s.name);
|
|
19446
20164
|
const userEnabledSkills = enabledSkillsRef.current;
|
|
19447
20165
|
const skillsConfig = buildSkillsConfig({
|
|
19448
20166
|
scan: skillsScan,
|
|
19449
|
-
enabled: userEnabledSkills === void 0 ? void 0 : Array.from(/* @__PURE__ */ new Set([
|
|
20167
|
+
enabled: userEnabledSkills === void 0 ? void 0 : Array.from(/* @__PURE__ */ new Set([
|
|
20168
|
+
...userEnabledSkills,
|
|
20169
|
+
...extensionSkillNames,
|
|
20170
|
+
...pluginContributions.skills.map((s) => s.name)
|
|
20171
|
+
]))
|
|
19450
20172
|
});
|
|
19451
|
-
const projectMcps = buildMcpServers({
|
|
20173
|
+
const projectMcps = [...buildMcpServers({
|
|
19452
20174
|
discovered: mcpsCatalogRef.current,
|
|
19453
20175
|
enabled: enabledMcpsRef.current,
|
|
19454
20176
|
...disabledMcpToolsRef.current ? { disabledTools: disabledMcpToolsRef.current } : {}
|
|
19455
|
-
});
|
|
20177
|
+
}), ...pluginContributions.mcpServers];
|
|
19456
20178
|
const allowInteraction = allowInteractionRef.current !== false;
|
|
19457
20179
|
const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
|
|
19458
20180
|
const actualCwd = cwdRef.current;
|
|
@@ -19504,14 +20226,18 @@ function AppShell() {
|
|
|
19504
20226
|
...skillsConfig,
|
|
19505
20227
|
...composedPreset.skills ?? {}
|
|
19506
20228
|
};
|
|
19507
|
-
const
|
|
20229
|
+
const withPluginSkills = pluginContributions.skills.length > 0 ? {
|
|
20230
|
+
...mergedSkills,
|
|
20231
|
+
catalog: [...pluginContributions.skills, ...mergedSkills.catalog ?? []]
|
|
20232
|
+
} : mergedSkills;
|
|
20233
|
+
const withExtensionSkill = !!session.metadata["zidane.extensionEdit"] || extensionDirForCwd(actualCwd) !== null ? withZidaneExtensionSkill(withPluginSkills, { cwd: actualCwd }) : withPluginSkills;
|
|
19508
20234
|
const skillEditMarker = readSkillEditMarker(session.metadata[SKILL_EDIT_METADATA_KEY]);
|
|
19509
20235
|
const skills = skillEditMarker ? withZidaneSkillEditSkill(withExtensionSkill, skillEditMarker, { cwd: actualCwd }) : withExtensionSkill;
|
|
19510
|
-
const mergedMcpServers =
|
|
20236
|
+
const mergedMcpServers = dedupMcpServersByName([
|
|
19511
20237
|
...projectMcps,
|
|
19512
20238
|
...launch?.mcp ?? [],
|
|
19513
20239
|
...composedPreset.mcpServers ?? []
|
|
19514
|
-
]);
|
|
20240
|
+
], (names) => debugLog("mcp name collision — later configs dropped (first wins)", names));
|
|
19515
20241
|
const mergedTools = {
|
|
19516
20242
|
...interactionTools,
|
|
19517
20243
|
chatTurnsRead: chatTurnsReadTool,
|
|
@@ -22950,6 +23676,9 @@ function AppShell() {
|
|
|
22950
23676
|
} } : {},
|
|
22951
23677
|
extensionsCatalog: extensionRegistry.loaded,
|
|
22952
23678
|
extensionsInactive: extensionRegistry.inactive,
|
|
23679
|
+
pluginsCatalog,
|
|
23680
|
+
pluginsFromExtensions: extensionRegistry.plugins,
|
|
23681
|
+
extensionContributedSkills: liveRegistryForSettings.skills,
|
|
22953
23682
|
authentication: {
|
|
22954
23683
|
currentProviderLabel: picked?.provider.label,
|
|
22955
23684
|
currentProviderKey: picked?.provider.key,
|
|
@@ -22964,6 +23693,7 @@ function AppShell() {
|
|
|
22964
23693
|
onCancelLoginMcp,
|
|
22965
23694
|
onRefreshSkills,
|
|
22966
23695
|
onRefreshMcps,
|
|
23696
|
+
onRefreshPlugins,
|
|
22967
23697
|
onRefreshMcpTools,
|
|
22968
23698
|
onEditExtension,
|
|
22969
23699
|
onEditSkill,
|
|
@@ -22975,7 +23705,9 @@ function AppShell() {
|
|
|
22975
23705
|
catalog: extensionRegistryLiveRef.current.loaded,
|
|
22976
23706
|
inactive: extensionRegistryLiveRef.current.inactive,
|
|
22977
23707
|
configCategories: extensionRegistryLiveRef.current.configCategories,
|
|
22978
|
-
configItems: extensionRegistryLiveRef.current.configItems
|
|
23708
|
+
configItems: extensionRegistryLiveRef.current.configItems,
|
|
23709
|
+
plugins: extensionRegistryLiveRef.current.plugins,
|
|
23710
|
+
skills: liveRegistryForSettingsRef.current.skills
|
|
22979
23711
|
}),
|
|
22980
23712
|
onInstallSkill,
|
|
22981
23713
|
onAddMcp,
|
|
@@ -24888,6 +25620,7 @@ async function runTui(options = {}) {
|
|
|
24888
25620
|
});
|
|
24889
25621
|
const prefix = options.prefix ?? process.env.ZIDANE_PREFIX ?? ".zidane";
|
|
24890
25622
|
const extensionScanPaths = options.extensionScanPaths ?? parseExtensionScanPathEnv();
|
|
25623
|
+
const pluginScanPaths = options.pluginScanPaths ?? parsePluginScanPathEnv();
|
|
24891
25624
|
const explicitStorageDir = options.storageDir ?? process.env.ZIDANE_STORAGE_DIR;
|
|
24892
25625
|
const paths = resolveStoragePaths({
|
|
24893
25626
|
prefix,
|
|
@@ -24923,6 +25656,7 @@ async function runTui(options = {}) {
|
|
|
24923
25656
|
const config = resolveConfig({
|
|
24924
25657
|
...options,
|
|
24925
25658
|
extensionScanPaths,
|
|
25659
|
+
pluginScanPaths,
|
|
24926
25660
|
extensionRegistry,
|
|
24927
25661
|
trustProjectExtensions,
|
|
24928
25662
|
...options.launch && !options.preset && !options.agents ? { preset: basic_default } : {},
|