zidane 6.4.3 → 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-Kr7LyExq.js → acp-B-AOovHz.js} +3 -3
- package/dist/{acp-Kr7LyExq.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-CEvzc1Ao.js → agent-SQ6rxfpT.js} +298 -110
- package/dist/agent-SQ6rxfpT.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- 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-DbzJctSl.js → context-transport-CDBatvQc.js} +3 -3
- package/dist/{context-transport-DbzJctSl.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-W_8010Lt.d.ts → index-CWBSrqI6.d.ts} +931 -3
- package/dist/index-CWBSrqI6.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/{login-Blkiy-58.js → login-XZDS4K2o.js} +3 -3
- package/dist/{login-Blkiy-58.js.map → login-XZDS4K2o.js.map} +1 -1
- package/dist/{mcp-CDN-m5lp.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 +52 -2
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js +156 -36
- 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-DWQKYKad.js → stdio-O-m9WVkE.js} +2 -2
- package/dist/{stdio-DWQKYKad.js.map → stdio-O-m9WVkE.js.map} +1 -1
- package/dist/{stdio-loader-3uz37d_3.js → stdio-loader-BSGQmG4L.js} +2 -2
- package/dist/{stdio-loader-3uz37d_3.js.map → stdio-loader-BSGQmG4L.js.map} +1 -1
- package/dist/{tool-formatters-bhC2Xp8z.d.ts → tool-formatters-gFqQbzPY.d.ts} +2 -2
- package/dist/{tool-formatters-bhC2Xp8z.d.ts.map → tool-formatters-gFqQbzPY.d.ts.map} +1 -1
- package/dist/{read-state-Dt7UbVsd.js → tool-state-DTs-3ZHH.js} +218 -2
- package/dist/tool-state-DTs-3ZHH.js.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 +2 -2
- package/dist/{transcript-anchors-D7wVCZ2S.js → transcript-anchors-D4IxJifV.js} +42 -5
- package/dist/transcript-anchors-D4IxJifV.js.map +1 -0
- package/dist/{transcript-anchors-mcMLuXM1.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 +893 -148
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-B5vVcG0Z.d.ts → turn-operations-M9-jUyDd.d.ts} +3 -3
- package/dist/{turn-operations-B5vVcG0Z.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 +2 -2
- package/docs/ARCHITECTURE.md +2 -0
- package/docs/PLUGINS.md +340 -0
- package/docs/RESTATE.md +133 -3
- package/docs/SKILL.md +24 -8
- package/package.json +6 -1
- package/dist/agent-CEvzc1Ao.js.map +0 -1
- package/dist/index-W_8010Lt.d.ts.map +0 -1
- package/dist/mcp-CDN-m5lp.js.map +0 -1
- package/dist/read-state-Dt7UbVsd.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-D7wVCZ2S.js.map +0 -1
- package/dist/transcript-anchors-mcMLuXM1.d.ts.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-Blkiy-58.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,
|
|
@@ -8589,6 +8756,7 @@ function InteractionShell({ title, maxHeight, children }) {
|
|
|
8589
8756
|
paddingRight: 1,
|
|
8590
8757
|
flexDirection: "column",
|
|
8591
8758
|
flexShrink: 0,
|
|
8759
|
+
overflow: "hidden",
|
|
8592
8760
|
...maxHeight !== void 0 ? { maxHeight } : {}
|
|
8593
8761
|
},
|
|
8594
8762
|
children
|
|
@@ -8817,6 +8985,25 @@ function PlanInteractionBlock({ request, onResolve }) {
|
|
|
8817
8985
|
*/
|
|
8818
8986
|
const QUESTION_WIZARD_MAX_HEIGHT = "60%";
|
|
8819
8987
|
/**
|
|
8988
|
+
* Rows the intro contributes to the wizard's content-sized height.
|
|
8989
|
+
*
|
|
8990
|
+
* A line count, plus the one-row bottom margin. Wrapping makes this a lower
|
|
8991
|
+
* bound, and long intros are capped: past {@link INTRO_ROWS_CAP} the panel would
|
|
8992
|
+
* hit its 60% ceiling anyway and the scrollbox takes over, so counting further
|
|
8993
|
+
* buys nothing. Zero when there is no intro, which keeps a bare confirm question
|
|
8994
|
+
* at its ~4-row size.
|
|
8995
|
+
*/
|
|
8996
|
+
const INTRO_ROWS_CAP = 12;
|
|
8997
|
+
function introRows(intro) {
|
|
8998
|
+
if (!intro) return 0;
|
|
8999
|
+
let lines = 1;
|
|
9000
|
+
for (let i = 0; i < intro.length; i++) {
|
|
9001
|
+
if (intro[i] === "\n") lines++;
|
|
9002
|
+
if (lines > INTRO_ROWS_CAP) return 13;
|
|
9003
|
+
}
|
|
9004
|
+
return lines + 1;
|
|
9005
|
+
}
|
|
9006
|
+
/**
|
|
8820
9007
|
* Estimated render height (rows) of one {@link QuestionRow}, mirroring its
|
|
8821
9008
|
* actual layout: prompt line + optional description + 1-row bottom margin,
|
|
8822
9009
|
* plus the status-dependent body — bordered text input (3) or textarea (5)
|
|
@@ -8900,12 +9087,23 @@ function QuestionInteractionBlock({ request, onResolve }) {
|
|
|
8900
9087
|
});
|
|
8901
9088
|
return () => cancelAnimationFrame(handle);
|
|
8902
9089
|
}, [activeIndex, questions]);
|
|
8903
|
-
const contentRows = useMemo(() => questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < activeIndex ? "done" : i === activeIndex ? "active" : "pending"), 0), [
|
|
9090
|
+
const contentRows = useMemo(() => introRows(intro) + questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < activeIndex ? "done" : i === activeIndex ? "active" : "pending"), 0), [
|
|
9091
|
+
intro,
|
|
9092
|
+
questions,
|
|
9093
|
+
activeIndex
|
|
9094
|
+
]);
|
|
8904
9095
|
return /* @__PURE__ */ jsxs(InteractionShell, {
|
|
8905
9096
|
title: questions.length > 1 ? ` answer ${activeIndex + 1}/${questions.length} · esc to abort run ` : " answer · esc to abort run ",
|
|
8906
9097
|
maxHeight: QUESTION_WIZARD_MAX_HEIGHT,
|
|
8907
|
-
children: [
|
|
8908
|
-
|
|
9098
|
+
children: [/* @__PURE__ */ jsxs("scrollbox", {
|
|
9099
|
+
ref: scrollboxRef,
|
|
9100
|
+
focusable: false,
|
|
9101
|
+
style: {
|
|
9102
|
+
height: contentRows,
|
|
9103
|
+
flexShrink: 1,
|
|
9104
|
+
minHeight: 0
|
|
9105
|
+
},
|
|
9106
|
+
children: [intro && /* @__PURE__ */ jsx$1("box", {
|
|
8909
9107
|
style: {
|
|
8910
9108
|
flexDirection: "column",
|
|
8911
9109
|
marginBottom: 1,
|
|
@@ -8918,46 +9116,26 @@ function QuestionInteractionBlock({ request, onResolve }) {
|
|
|
8918
9116
|
internalBlockMode: "coalesced",
|
|
8919
9117
|
fg: COLOR.dim
|
|
8920
9118
|
})
|
|
8921
|
-
}),
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
style: {
|
|
8942
|
-
marginTop: 1,
|
|
8943
|
-
flexShrink: 0
|
|
8944
|
-
},
|
|
8945
|
-
children: [canGoBack && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8946
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
8947
|
-
fg: COLOR.warn,
|
|
8948
|
-
children: "shift+↹"
|
|
8949
|
-
}),
|
|
8950
|
-
" back",
|
|
8951
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
8952
|
-
fg: COLOR.mute,
|
|
8953
|
-
children: " · "
|
|
8954
|
-
})
|
|
8955
|
-
] }), /* @__PURE__ */ jsx$1("span", {
|
|
8956
|
-
fg: COLOR.mute,
|
|
8957
|
-
children: `question ${activeIndex + 1} of ${questions.length}`
|
|
8958
|
-
})]
|
|
8959
|
-
})
|
|
8960
|
-
]
|
|
9119
|
+
}), questions.map((q, i) => /* @__PURE__ */ jsx$1(QuestionRow, {
|
|
9120
|
+
anchorId: anchorIdFor$1(q.id),
|
|
9121
|
+
index: i,
|
|
9122
|
+
question: q,
|
|
9123
|
+
status: i < activeIndex ? "done" : i === activeIndex ? "active" : "pending",
|
|
9124
|
+
answer: answers[q.id],
|
|
9125
|
+
initialAnswer: i === activeIndex ? answers[q.id] : void 0,
|
|
9126
|
+
onAnswer
|
|
9127
|
+
}, q.id))]
|
|
9128
|
+
}), canGoBack && /* @__PURE__ */ jsxs("text", {
|
|
9129
|
+
fg: COLOR.mute,
|
|
9130
|
+
style: {
|
|
9131
|
+
marginTop: 1,
|
|
9132
|
+
flexShrink: 0
|
|
9133
|
+
},
|
|
9134
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
9135
|
+
fg: COLOR.warn,
|
|
9136
|
+
children: "shift+↹"
|
|
9137
|
+
}), " back"]
|
|
9138
|
+
})]
|
|
8961
9139
|
});
|
|
8962
9140
|
}
|
|
8963
9141
|
/**
|
|
@@ -13329,9 +13507,230 @@ function statusColor(status, COLOR) {
|
|
|
13329
13507
|
}
|
|
13330
13508
|
}
|
|
13331
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
|
|
13332
13729
|
//#region src/tui/extension-rows.ts
|
|
13333
13730
|
function buildVisibleExtensionRows(opts) {
|
|
13334
13731
|
const { errored, toggleable, configItemsByExt, expanded, queryActive, matchExtension, matchConfigItem } = opts;
|
|
13732
|
+
const plugins = opts.plugins ?? [];
|
|
13733
|
+
const matchPlugin = opts.matchPlugin ?? (() => true);
|
|
13335
13734
|
const out = [];
|
|
13336
13735
|
for (const ext of errored) {
|
|
13337
13736
|
if (queryActive && !matchExtension(ext)) continue;
|
|
@@ -13340,10 +13739,39 @@ function buildVisibleExtensionRows(opts) {
|
|
|
13340
13739
|
ext
|
|
13341
13740
|
});
|
|
13342
13741
|
}
|
|
13343
|
-
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;
|
|
13344
13772
|
const items = Object.values(configItemsByExt[ext.name] ?? {});
|
|
13345
13773
|
const headerMatches = !queryActive || matchExtension(ext);
|
|
13346
|
-
const matchingItems = queryActive ? items.filter((
|
|
13774
|
+
const matchingItems = queryActive ? items.filter((configItem) => matchConfigItem(ext.name, configItem)) : items;
|
|
13347
13775
|
if (queryActive && !headerMatches && matchingItems.length === 0) continue;
|
|
13348
13776
|
const hasConfig = items.length > 0;
|
|
13349
13777
|
const isExpanded = hasConfig && (queryActive ? true : expanded.has(ext.name));
|
|
@@ -13355,10 +13783,10 @@ function buildVisibleExtensionRows(opts) {
|
|
|
13355
13783
|
});
|
|
13356
13784
|
if (!isExpanded) continue;
|
|
13357
13785
|
const rows = queryActive && !headerMatches ? matchingItems : items;
|
|
13358
|
-
for (const
|
|
13786
|
+
for (const configItem of rows) out.push({
|
|
13359
13787
|
kind: "config",
|
|
13360
13788
|
extName: ext.name,
|
|
13361
|
-
item
|
|
13789
|
+
item: configItem
|
|
13362
13790
|
});
|
|
13363
13791
|
}
|
|
13364
13792
|
return out;
|
|
@@ -13372,6 +13800,7 @@ function parentExtensionName(row) {
|
|
|
13372
13800
|
if (!row) return void 0;
|
|
13373
13801
|
if (row.kind === "extension") return row.ext.name;
|
|
13374
13802
|
if (row.kind === "config") return row.extName;
|
|
13803
|
+
if (row.kind === "plugin") return row.hasContents ? row.plugin.name : void 0;
|
|
13375
13804
|
}
|
|
13376
13805
|
/**
|
|
13377
13806
|
* Index of the `extension` header row for the given name. `-1` when absent.
|
|
@@ -14179,7 +14608,7 @@ const TAB_LABELS = {
|
|
|
14179
14608
|
authentication: "Authentication",
|
|
14180
14609
|
skills: "Skills",
|
|
14181
14610
|
mcps: "MCP servers",
|
|
14182
|
-
extensions: "Extensions",
|
|
14611
|
+
extensions: "Extensions & Plugins",
|
|
14183
14612
|
logs: "Logs"
|
|
14184
14613
|
};
|
|
14185
14614
|
function isCategoryTab(id) {
|
|
@@ -14213,7 +14642,7 @@ function truncateToCols(s, maxCols) {
|
|
|
14213
14642
|
if (maxCols === 1) return "…";
|
|
14214
14643
|
return `${clean.slice(0, maxCols - 1)}…`;
|
|
14215
14644
|
}
|
|
14216
|
-
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 } = {}) {
|
|
14217
14646
|
const COLOR = useColors();
|
|
14218
14647
|
const SURFACE = useSurfaces();
|
|
14219
14648
|
const { settings, toggle: toggleBoolean, setSetting } = useSettings();
|
|
@@ -14225,12 +14654,45 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14225
14654
|
const { width: termWidth } = useTerminalDimensions();
|
|
14226
14655
|
const contentCols = Math.max(40, termWidth - 2 - 4 - 2 - 1);
|
|
14227
14656
|
const discovery = useDiscoveryOptional();
|
|
14228
|
-
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
|
+
]);
|
|
14229
14691
|
const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? [];
|
|
14230
14692
|
const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp;
|
|
14231
14693
|
const mcpToolsByServer = discovery?.mcpToolsByServer ?? EMPTY_TOOLS;
|
|
14232
14694
|
const skillsToggle = useEnabledToggleSet({
|
|
14233
|
-
catalog:
|
|
14695
|
+
catalog: diskSkillsCatalog,
|
|
14234
14696
|
keyOf: (s) => s.name,
|
|
14235
14697
|
settingKey: "enabledSkills"
|
|
14236
14698
|
});
|
|
@@ -14239,11 +14701,29 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14239
14701
|
keyOf: (m) => m.config.name,
|
|
14240
14702
|
settingKey: "enabledMcps"
|
|
14241
14703
|
});
|
|
14242
|
-
const [extSnapshot, setExtSnapshot] = useState(null);
|
|
14243
14704
|
const refreshExtensionRows = useCallback(() => {
|
|
14244
14705
|
const snap = actions?.getExtensionsSnapshot?.();
|
|
14245
14706
|
if (snap) setExtSnapshot(snap);
|
|
14246
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
|
+
]);
|
|
14247
14727
|
const extensionsInactiveAll = extSnapshot?.inactive ?? extensionsInactiveProp ?? [];
|
|
14248
14728
|
const extensionsPartition = useMemo(() => partitionExtensionCatalog(extSnapshot?.catalog ?? extensionsCatalogProp ?? [], extensionsInactiveAll), [
|
|
14249
14729
|
extSnapshot,
|
|
@@ -14258,6 +14738,20 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14258
14738
|
settingKey: "enabledExtensions",
|
|
14259
14739
|
gcKeys: extensionsPartition.allNames
|
|
14260
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
|
+
});
|
|
14261
14755
|
const extensionConfigCategories = extSnapshot?.configCategories ?? extensionConfigCategoriesProp ?? EMPTY_CONFIG_CATEGORIES;
|
|
14262
14756
|
const extensionConfigItems = extSnapshot?.configItems ?? extensionConfigItemsProp ?? EMPTY_CONFIG_ITEMS;
|
|
14263
14757
|
const configInputRef = useRef(null);
|
|
@@ -14705,14 +15199,17 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14705
15199
|
expanded: expandedExtensions,
|
|
14706
15200
|
queryActive,
|
|
14707
15201
|
matchExtension: (ext) => matchesTerms(extensionCorpus(ext), queryTerms),
|
|
14708
|
-
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)
|
|
14709
15205
|
}), [
|
|
14710
15206
|
extensionsErrored,
|
|
14711
15207
|
extensionsCatalog,
|
|
14712
15208
|
extensionConfigItems,
|
|
14713
15209
|
expandedExtensions,
|
|
14714
15210
|
queryActive,
|
|
14715
|
-
queryTerms
|
|
15211
|
+
queryTerms,
|
|
15212
|
+
pluginList
|
|
14716
15213
|
]);
|
|
14717
15214
|
const mcpVisibleRows = useMemo(() => buildVisibleMcpRows(filteredMcps, expandedMcps, mcpToolsByServer), [
|
|
14718
15215
|
filteredMcps,
|
|
@@ -14887,6 +15384,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14887
15384
|
const row = extensionVisibleRows[cursor];
|
|
14888
15385
|
if (!row) return void 0;
|
|
14889
15386
|
if (row.kind === "extension" || row.kind === "errored") return row.ext;
|
|
15387
|
+
if (row.kind === "plugin") return void 0;
|
|
14890
15388
|
return extensionsCatalog.find((e) => e.name === row.extName) ?? extensionsErrored.find((e) => e.name === row.extName);
|
|
14891
15389
|
}, [
|
|
14892
15390
|
activeTab,
|
|
@@ -14945,14 +15443,16 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14945
15443
|
}
|
|
14946
15444
|
if (activeTab === "skills") {
|
|
14947
15445
|
const s = filteredSkills[index];
|
|
14948
|
-
if (s) skillsToggle.toggle(s.name);
|
|
15446
|
+
if (s && !skillOriginBadge(s)) skillsToggle.toggle(s.name);
|
|
14949
15447
|
return;
|
|
14950
15448
|
}
|
|
14951
15449
|
if (activeTab === "extensions") {
|
|
14952
15450
|
const row = extensionVisibleRows[index];
|
|
14953
15451
|
if (!row) return;
|
|
14954
15452
|
if (row.kind === "extension") extensionsToggle.toggle(row.ext.name);
|
|
14955
|
-
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);
|
|
14956
15456
|
return;
|
|
14957
15457
|
}
|
|
14958
15458
|
if (activeTab === "mcps") {
|
|
@@ -14984,6 +15484,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
14984
15484
|
skillsToggle,
|
|
14985
15485
|
extensionVisibleRows,
|
|
14986
15486
|
extensionsToggle,
|
|
15487
|
+
pluginsToggle,
|
|
14987
15488
|
activateConfigRow,
|
|
14988
15489
|
mcpVisibleRows,
|
|
14989
15490
|
mcpsToggle,
|
|
@@ -15044,6 +15545,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15044
15545
|
return;
|
|
15045
15546
|
}
|
|
15046
15547
|
if (row?.kind === "config" && row.item.type === "boolean") return;
|
|
15548
|
+
if (row?.kind === "plugin") return;
|
|
15047
15549
|
activateRowAt(index);
|
|
15048
15550
|
return;
|
|
15049
15551
|
}
|
|
@@ -15330,6 +15832,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15330
15832
|
actions.onRefreshSkills();
|
|
15331
15833
|
return;
|
|
15332
15834
|
}
|
|
15835
|
+
if (activeTab === "extensions" && actions?.onRefreshPlugins) {
|
|
15836
|
+
actions.onRefreshPlugins();
|
|
15837
|
+
return;
|
|
15838
|
+
}
|
|
15333
15839
|
if (activeTab === "mcps") {
|
|
15334
15840
|
if (actions?.onRefreshMcps) actions.onRefreshMcps();
|
|
15335
15841
|
fireMcpToolsRefresh();
|
|
@@ -15355,8 +15861,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15355
15861
|
total: mcpsCatalog.length
|
|
15356
15862
|
},
|
|
15357
15863
|
extensions: {
|
|
15358
|
-
current: extensionVisibleRows.filter((r) => r.kind === "extension" || r.kind === "errored").length,
|
|
15359
|
-
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
|
|
15360
15866
|
}
|
|
15361
15867
|
};
|
|
15362
15868
|
const editingInputItem = editingInputKey ? SETTINGS_INPUTS.find((inp) => inp.key === editingInputKey) ?? null : null;
|
|
@@ -15441,9 +15947,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15441
15947
|
onRowClick: clickRowAt,
|
|
15442
15948
|
rows: extensionVisibleRows,
|
|
15443
15949
|
enabledSet: extensionsToggle.enabledSet,
|
|
15950
|
+
pluginEnabledSet: pluginsToggle.enabledSet,
|
|
15444
15951
|
extensionConfig: settings.extensionConfig,
|
|
15445
15952
|
categories: extensionConfigCategories,
|
|
15446
|
-
totalCount: extensionsCatalog.length + extensionsErrored.length,
|
|
15953
|
+
totalCount: extensionsCatalog.length + extensionsErrored.length + pluginList.length,
|
|
15447
15954
|
cursor,
|
|
15448
15955
|
highlightBg: SURFACE.selection,
|
|
15449
15956
|
query,
|
|
@@ -15598,6 +16105,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15598
16105
|
focusedMcpStatus,
|
|
15599
16106
|
canRefreshSkills: !!actions?.onRefreshSkills,
|
|
15600
16107
|
canRefreshMcps: !!actions?.onRefreshMcps,
|
|
16108
|
+
canRefreshPlugins: !!actions?.onRefreshPlugins,
|
|
15601
16109
|
canEditExtension: !!actions?.onEditExtension,
|
|
15602
16110
|
canCreateExtension: !!actions?.onCreateExtension,
|
|
15603
16111
|
canUpdateExtension: !!actions?.onUpdateExtension,
|
|
@@ -15771,57 +16279,89 @@ function GeneralList({ items, cursor, highlightBg, query, contentCols, editingKe
|
|
|
15771
16279
|
});
|
|
15772
16280
|
}
|
|
15773
16281
|
const EXTENSION_CONFIG_INDENT = 3;
|
|
15774
|
-
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 }) {
|
|
15775
16283
|
const COLOR = useColors();
|
|
16284
|
+
const SURFACE = useSurfaces();
|
|
16285
|
+
const extBadge = kindBadgeStyle("ext", COLOR, SURFACE);
|
|
16286
|
+
const pluginBadge = kindBadgeStyle("plugin", COLOR, SURFACE);
|
|
15776
16287
|
const home = homedir();
|
|
15777
16288
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
15778
16289
|
style: { flexDirection: "column" },
|
|
15779
|
-
children: [
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
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
|
-
|
|
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
|
+
]
|
|
15823
16363
|
});
|
|
15824
|
-
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}"` });
|
|
15825
16365
|
const descCols = Math.max(0, contentCols - 1 - 1 - 6);
|
|
15826
16366
|
const groups = [];
|
|
15827
16367
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -15834,6 +16374,16 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
15834
16374
|
});
|
|
15835
16375
|
continue;
|
|
15836
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
|
+
}
|
|
15837
16387
|
if (row.kind === "extension") {
|
|
15838
16388
|
groups.push({
|
|
15839
16389
|
kind: "extension",
|
|
@@ -15882,6 +16432,12 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
15882
16432
|
fg: focused ? COLOR.brand : COLOR.dim,
|
|
15883
16433
|
children: group.ext.name
|
|
15884
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
|
+
}),
|
|
15885
16441
|
/* @__PURE__ */ jsx$1("span", {
|
|
15886
16442
|
fg: COLOR.mute,
|
|
15887
16443
|
children: ` ${where}`
|
|
@@ -15900,6 +16456,108 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
15900
16456
|
})]
|
|
15901
16457
|
}, `err:${group.ext.name}`);
|
|
15902
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
|
+
}
|
|
15903
16561
|
const focused = group.index === cursor;
|
|
15904
16562
|
const bg = focused ? highlightBg : void 0;
|
|
15905
16563
|
const enabled = enabledSet.has(group.ext.name);
|
|
@@ -16018,6 +16676,12 @@ function ExtensionsList({ rows, enabledSet, extensionConfig, categories, totalCo
|
|
|
16018
16676
|
group.ext.definition?.version && /* @__PURE__ */ jsx$1("span", {
|
|
16019
16677
|
fg: COLOR.mute,
|
|
16020
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
|
|
16021
16685
|
})
|
|
16022
16686
|
]
|
|
16023
16687
|
}),
|
|
@@ -16261,6 +16925,7 @@ function ConfigModelPickerRow({ id, label, description, catalog, multi, initial,
|
|
|
16261
16925
|
}
|
|
16262
16926
|
function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query, contentCols, onRowClick }) {
|
|
16263
16927
|
const COLOR = useColors();
|
|
16928
|
+
const contributedBadge = kindBadgeStyle("plugin", COLOR, useSurfaces());
|
|
16264
16929
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
16265
16930
|
style: { flexDirection: "column" },
|
|
16266
16931
|
children: [/* @__PURE__ */ jsx$1("text", {
|
|
@@ -16303,6 +16968,7 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
16303
16968
|
const enabled = enabledSet.has(entry.name);
|
|
16304
16969
|
const description = truncateToCols(entry.description ?? "", descCols);
|
|
16305
16970
|
const origin = skillOriginLabel(entry);
|
|
16971
|
+
const badge = skillOriginBadge(entry);
|
|
16306
16972
|
return /* @__PURE__ */ jsxs("box", {
|
|
16307
16973
|
onMouseDown: onRowClick && ((event) => {
|
|
16308
16974
|
event.stopPropagation();
|
|
@@ -16322,14 +16988,17 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
16322
16988
|
onMouseDown: onRowClick && ((event) => {
|
|
16323
16989
|
event.stopPropagation();
|
|
16324
16990
|
event.preventDefault();
|
|
16325
|
-
onRowClick(i, inCheckboxZone(event, 2) ? "checkbox" : "row");
|
|
16991
|
+
onRowClick(i, !badge && inCheckboxZone(event, 2) ? "checkbox" : "row");
|
|
16326
16992
|
}),
|
|
16327
16993
|
children: [
|
|
16328
16994
|
/* @__PURE__ */ jsx$1("span", {
|
|
16329
16995
|
fg: focused ? COLOR.brand : COLOR.mute,
|
|
16330
16996
|
children: focused ? "▶ " : " "
|
|
16331
16997
|
}),
|
|
16332
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
16998
|
+
badge ? /* @__PURE__ */ jsx$1("span", {
|
|
16999
|
+
fg: COLOR.mute,
|
|
17000
|
+
children: " "
|
|
17001
|
+
}) : /* @__PURE__ */ jsx$1("span", {
|
|
16333
17002
|
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
16334
17003
|
children: enabled ? "[✓] " : "[ ] "
|
|
16335
17004
|
}),
|
|
@@ -16337,7 +17006,11 @@ function SkillsList({ items, enabledSet, totalCount, cursor, highlightBg, query,
|
|
|
16337
17006
|
fg: focused ? COLOR.brand : COLOR.dim,
|
|
16338
17007
|
children: entry.name
|
|
16339
17008
|
}),
|
|
16340
|
-
|
|
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", {
|
|
16341
17014
|
fg: COLOR.mute,
|
|
16342
17015
|
children: [" · ", origin]
|
|
16343
17016
|
})
|
|
@@ -17040,13 +17713,13 @@ function renderMcpErrors(errors, home, warnColor, dimColor) {
|
|
|
17040
17713
|
}, err.path))
|
|
17041
17714
|
});
|
|
17042
17715
|
}
|
|
17043
|
-
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 }) {
|
|
17044
17717
|
const COLOR = useColors();
|
|
17045
17718
|
const onServerRow = focusedMcpRow?.kind === "server";
|
|
17046
17719
|
const showLogin = activeTab === "mcps" && onServerRow && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
|
|
17047
17720
|
const showLogout = activeTab === "mcps" && onServerRow && !!focusedMcpStatus && canLogout$1(focusedMcpStatus);
|
|
17048
17721
|
const showCancel = activeTab === "mcps" && authorizing;
|
|
17049
|
-
const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps;
|
|
17722
|
+
const showRefresh = activeTab === "skills" && canRefreshSkills || activeTab === "mcps" && canRefreshMcps || activeTab === "extensions" && canRefreshPlugins;
|
|
17050
17723
|
return /* @__PURE__ */ jsx$1(HintRow, { hints: [
|
|
17051
17724
|
{
|
|
17052
17725
|
key: "←→",
|
|
@@ -17172,6 +17845,21 @@ function extensionCorpus(e) {
|
|
|
17172
17845
|
].join(" ").toLowerCase();
|
|
17173
17846
|
}
|
|
17174
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
|
+
/**
|
|
17175
17863
|
* What a config row shows at rest. Secrets are masked (last 4 kept so the user
|
|
17176
17864
|
* can tell WHICH token is stored), unset model rows read `(unset)`.
|
|
17177
17865
|
*
|
|
@@ -18123,7 +18811,7 @@ const SESSION_SEARCH_INDEX_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
18123
18811
|
*/
|
|
18124
18812
|
/** How long the `⚡ cache break` footer toast stays visible after a break. */
|
|
18125
18813
|
const CACHE_BREAK_CHIP_MS = 8e3;
|
|
18126
|
-
const debugLog =
|
|
18814
|
+
const debugLog = createDebugLog("tui");
|
|
18127
18815
|
/**
|
|
18128
18816
|
* Launch the user's preferred editor on `path`. Tries `$EDITOR` /
|
|
18129
18817
|
* `$VISUAL` first (the standard *nix convention for "open this
|
|
@@ -18209,21 +18897,6 @@ function reduceEditsByOutcomes(edits, outcomes) {
|
|
|
18209
18897
|
}
|
|
18210
18898
|
return out;
|
|
18211
18899
|
}
|
|
18212
|
-
/**
|
|
18213
|
-
* Dedup an MCP-server list by `name`. First entry wins — used at
|
|
18214
|
-
* agent build to give project-level `mcps.json` precedence over any
|
|
18215
|
-
* extension-contributed servers that happen to share a name.
|
|
18216
|
-
*/
|
|
18217
|
-
function dedupMcpServers(servers) {
|
|
18218
|
-
const seen = /* @__PURE__ */ new Set();
|
|
18219
|
-
const out = [];
|
|
18220
|
-
for (const server of servers) {
|
|
18221
|
-
if (seen.has(server.name)) continue;
|
|
18222
|
-
seen.add(server.name);
|
|
18223
|
-
out.push(server);
|
|
18224
|
-
}
|
|
18225
|
-
return out;
|
|
18226
|
-
}
|
|
18227
18900
|
const ExtensionRegistryContext = createContext(null);
|
|
18228
18901
|
function useExtensionRegistryState() {
|
|
18229
18902
|
const ctx = useContext(ExtensionRegistryContext);
|
|
@@ -18485,7 +19158,7 @@ function AppShell() {
|
|
|
18485
19158
|
const sessionSafelistRef = useRef(/* @__PURE__ */ new Set());
|
|
18486
19159
|
const streamRef = useRef(null);
|
|
18487
19160
|
const pendingAnnotationsRef = useRef(/* @__PURE__ */ new Map());
|
|
18488
|
-
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();
|
|
18489
19162
|
const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir]);
|
|
18490
19163
|
const dispatchAuth = useMcpAuthDispatch();
|
|
18491
19164
|
const dispatchAuthRef = useRef(dispatchAuth);
|
|
@@ -18494,8 +19167,38 @@ function AppShell() {
|
|
|
18494
19167
|
const onLoginMcpRef = useRef(null);
|
|
18495
19168
|
const skillsCatalogRef = useRef(skillsCatalog);
|
|
18496
19169
|
skillsCatalogRef.current = skillsCatalog;
|
|
19170
|
+
const pluginsCatalogRef = useRef(pluginsCatalog);
|
|
19171
|
+
pluginsCatalogRef.current = pluginsCatalog;
|
|
18497
19172
|
const enabledSkillsRef = useRef(settings.enabledSkills);
|
|
18498
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;
|
|
18499
19202
|
const importForeignConfigsRef = useRef(settings.importForeignConfigs);
|
|
18500
19203
|
importForeignConfigsRef.current = settings.importForeignConfigs;
|
|
18501
19204
|
const importRulerConfigsRef = useRef(settings.importRulerConfigs);
|
|
@@ -18506,14 +19209,36 @@ function AppShell() {
|
|
|
18506
19209
|
enabledMcpsRef.current = settings.enabledMcps;
|
|
18507
19210
|
const disabledMcpToolsRef = useRef(settings.disabledMcpTools);
|
|
18508
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
|
+
*/
|
|
18509
19225
|
const mcpRuntimeKey = useMemo(() => JSON.stringify({
|
|
18510
19226
|
catalog: mcpsCatalog.map(({ config }) => config),
|
|
18511
19227
|
enabled: settings.enabledMcps ?? null,
|
|
18512
|
-
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
|
|
18513
19236
|
}), [
|
|
18514
19237
|
mcpsCatalog,
|
|
18515
19238
|
settings.enabledMcps,
|
|
18516
|
-
settings.disabledMcpTools
|
|
19239
|
+
settings.disabledMcpTools,
|
|
19240
|
+
pluginsCatalog,
|
|
19241
|
+
settings.enabledPlugins
|
|
18517
19242
|
]);
|
|
18518
19243
|
const filesCatalogRef = useRef(filesCatalog);
|
|
18519
19244
|
filesCatalogRef.current = filesCatalog;
|
|
@@ -19430,18 +20155,26 @@ function AppShell() {
|
|
|
19430
20155
|
foreignAgents: importForeignConfigsRef.current === true,
|
|
19431
20156
|
ruler: importRulerConfigsRef.current !== false
|
|
19432
20157
|
});
|
|
19433
|
-
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
|
+
});
|
|
19434
20163
|
const extensionSkillNames = liveExtensionRegistry.skills.map((s) => s.name);
|
|
19435
20164
|
const userEnabledSkills = enabledSkillsRef.current;
|
|
19436
20165
|
const skillsConfig = buildSkillsConfig({
|
|
19437
20166
|
scan: skillsScan,
|
|
19438
|
-
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
|
+
]))
|
|
19439
20172
|
});
|
|
19440
|
-
const projectMcps = buildMcpServers({
|
|
20173
|
+
const projectMcps = [...buildMcpServers({
|
|
19441
20174
|
discovered: mcpsCatalogRef.current,
|
|
19442
20175
|
enabled: enabledMcpsRef.current,
|
|
19443
20176
|
...disabledMcpToolsRef.current ? { disabledTools: disabledMcpToolsRef.current } : {}
|
|
19444
|
-
});
|
|
20177
|
+
}), ...pluginContributions.mcpServers];
|
|
19445
20178
|
const allowInteraction = allowInteractionRef.current !== false;
|
|
19446
20179
|
const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
|
|
19447
20180
|
const actualCwd = cwdRef.current;
|
|
@@ -19493,14 +20226,18 @@ function AppShell() {
|
|
|
19493
20226
|
...skillsConfig,
|
|
19494
20227
|
...composedPreset.skills ?? {}
|
|
19495
20228
|
};
|
|
19496
|
-
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;
|
|
19497
20234
|
const skillEditMarker = readSkillEditMarker(session.metadata[SKILL_EDIT_METADATA_KEY]);
|
|
19498
20235
|
const skills = skillEditMarker ? withZidaneSkillEditSkill(withExtensionSkill, skillEditMarker, { cwd: actualCwd }) : withExtensionSkill;
|
|
19499
|
-
const mergedMcpServers =
|
|
20236
|
+
const mergedMcpServers = dedupMcpServersByName([
|
|
19500
20237
|
...projectMcps,
|
|
19501
20238
|
...launch?.mcp ?? [],
|
|
19502
20239
|
...composedPreset.mcpServers ?? []
|
|
19503
|
-
]);
|
|
20240
|
+
], (names) => debugLog("mcp name collision — later configs dropped (first wins)", names));
|
|
19504
20241
|
const mergedTools = {
|
|
19505
20242
|
...interactionTools,
|
|
19506
20243
|
chatTurnsRead: chatTurnsReadTool,
|
|
@@ -22939,6 +23676,9 @@ function AppShell() {
|
|
|
22939
23676
|
} } : {},
|
|
22940
23677
|
extensionsCatalog: extensionRegistry.loaded,
|
|
22941
23678
|
extensionsInactive: extensionRegistry.inactive,
|
|
23679
|
+
pluginsCatalog,
|
|
23680
|
+
pluginsFromExtensions: extensionRegistry.plugins,
|
|
23681
|
+
extensionContributedSkills: liveRegistryForSettings.skills,
|
|
22942
23682
|
authentication: {
|
|
22943
23683
|
currentProviderLabel: picked?.provider.label,
|
|
22944
23684
|
currentProviderKey: picked?.provider.key,
|
|
@@ -22953,6 +23693,7 @@ function AppShell() {
|
|
|
22953
23693
|
onCancelLoginMcp,
|
|
22954
23694
|
onRefreshSkills,
|
|
22955
23695
|
onRefreshMcps,
|
|
23696
|
+
onRefreshPlugins,
|
|
22956
23697
|
onRefreshMcpTools,
|
|
22957
23698
|
onEditExtension,
|
|
22958
23699
|
onEditSkill,
|
|
@@ -22964,7 +23705,9 @@ function AppShell() {
|
|
|
22964
23705
|
catalog: extensionRegistryLiveRef.current.loaded,
|
|
22965
23706
|
inactive: extensionRegistryLiveRef.current.inactive,
|
|
22966
23707
|
configCategories: extensionRegistryLiveRef.current.configCategories,
|
|
22967
|
-
configItems: extensionRegistryLiveRef.current.configItems
|
|
23708
|
+
configItems: extensionRegistryLiveRef.current.configItems,
|
|
23709
|
+
plugins: extensionRegistryLiveRef.current.plugins,
|
|
23710
|
+
skills: liveRegistryForSettingsRef.current.skills
|
|
22968
23711
|
}),
|
|
22969
23712
|
onInstallSkill,
|
|
22970
23713
|
onAddMcp,
|
|
@@ -24877,6 +25620,7 @@ async function runTui(options = {}) {
|
|
|
24877
25620
|
});
|
|
24878
25621
|
const prefix = options.prefix ?? process.env.ZIDANE_PREFIX ?? ".zidane";
|
|
24879
25622
|
const extensionScanPaths = options.extensionScanPaths ?? parseExtensionScanPathEnv();
|
|
25623
|
+
const pluginScanPaths = options.pluginScanPaths ?? parsePluginScanPathEnv();
|
|
24880
25624
|
const explicitStorageDir = options.storageDir ?? process.env.ZIDANE_STORAGE_DIR;
|
|
24881
25625
|
const paths = resolveStoragePaths({
|
|
24882
25626
|
prefix,
|
|
@@ -24912,6 +25656,7 @@ async function runTui(options = {}) {
|
|
|
24912
25656
|
const config = resolveConfig({
|
|
24913
25657
|
...options,
|
|
24914
25658
|
extensionScanPaths,
|
|
25659
|
+
pluginScanPaths,
|
|
24915
25660
|
extensionRegistry,
|
|
24916
25661
|
trustProjectExtensions,
|
|
24917
25662
|
...options.launch && !options.preset && !options.agents ? { preset: basic_default } : {},
|