dsh-agent-plugins-market 0.6.2 → 0.7.1
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 +117 -111
- package/README.zh.md +112 -98
- package/client/client.js +28533 -1511
- package/client/client.js.map +1 -1
- package/lib/application/catalog-context.js +188 -0
- package/lib/application/catalog.js +229 -1109
- package/lib/application/details.js +19 -8
- package/lib/application/install-store.js +95 -0
- package/lib/application/lsp-service.js +97 -0
- package/lib/application/mcp-service.js +263 -0
- package/lib/application/panel-resources.js +43 -14
- package/lib/application/ports.js +21 -0
- package/lib/application/project-agent-roles.js +8 -2
- package/lib/application/snapshot-cache.js +160 -0
- package/lib/application/source-store.js +521 -0
- package/lib/catalog/archive.js +35 -30
- package/lib/catalog/component-files.js +14 -6
- package/lib/catalog/fs-probes.js +43 -0
- package/lib/catalog/manifests.js +53 -92
- package/lib/catalog/native-project.js +2 -2
- package/lib/catalog/paths.js +74 -55
- package/lib/catalog/plugin-variables.js +38 -0
- package/lib/catalog/project-config.js +2 -1
- package/lib/catalog/project-hooks.js +23 -2
- package/lib/catalog/scan-resolvers.js +29 -45
- package/lib/catalog/skills-parse.js +61 -10
- package/lib/catalog/source-catalog.js +10 -4
- package/lib/catalog/suite-components.js +7 -7
- package/lib/catalog/suite-scanner.js +3 -1
- package/lib/catalog/surfaces.js +5 -26
- package/lib/catalog/validate.js +7 -11
- package/lib/contracts/market.js +2 -0
- package/lib/contracts/settings.js +63 -0
- package/lib/index.js +115 -217
- package/lib/model/layouts.js +10 -1
- package/lib/model/types.js +26 -3
- package/lib/routes.js +57 -15
- package/lib/runtime/agent-role-names.js +70 -0
- package/lib/runtime/agent-role-router.js +142 -106
- package/lib/runtime/commands-mounts.js +55 -11
- package/lib/runtime/deadline.js +54 -0
- package/lib/runtime/dynamic-context.js +140 -0
- package/lib/runtime/feedback-tool.js +101 -46
- package/lib/runtime/hooks-mounts.js +9 -11
- package/lib/runtime/host-locale.js +49 -24
- package/lib/runtime/lsp-direct-config.js +19 -19
- package/lib/runtime/lsp-mounts.js +182 -70
- package/lib/runtime/lsp-server-state.js +4 -4
- package/lib/runtime/lsp-status.js +1 -1
- package/lib/runtime/mcp-auth-record.js +42 -0
- package/lib/runtime/mcp-backend.js +14 -14
- package/lib/runtime/mcp-client/config.js +8 -1
- package/lib/runtime/mcp-client/connection.js +38 -43
- package/lib/runtime/mcp-client/host-contract.js +10 -0
- package/lib/runtime/mcp-client/host-seams.js +8 -88
- package/lib/runtime/mcp-client/oauth.js +5 -7
- package/lib/runtime/mcp-client/plugin-identity.js +28 -0
- package/lib/runtime/mcp-client/projection.js +202 -0
- package/lib/runtime/mcp-client/tools.js +4 -189
- package/lib/runtime/mcp-config.js +36 -14
- package/lib/runtime/mcp-direct-config.js +33 -16
- package/lib/runtime/mcp-mounts.js +35 -75
- package/lib/runtime/mcp-overrides.js +5 -5
- package/lib/runtime/mcp-redaction.js +3 -2
- package/lib/runtime/mcp-status.js +7 -8
- package/lib/runtime/mount-lifecycle.js +73 -0
- package/lib/runtime/profile-seam.js +321 -0
- package/lib/runtime/project-runtime.js +34 -17
- package/lib/runtime/reconcile-scheduler.js +97 -0
- package/lib/runtime/reconciler.js +6 -1
- package/lib/runtime/regions.js +0 -19
- package/lib/runtime/server-config.js +6 -4
- package/lib/runtime/settings-namespace.js +144 -0
- package/lib/runtime/skills-provider.js +38 -14
- package/lib/runtime/source-auto-update.js +66 -0
- package/lib/{model/state.js → runtime/state-store.js} +9 -11
- package/lib/runtime/storage-migration.js +35 -18
- package/lib/runtime/subagent-catalog.js +5 -4
- package/lib/runtime/timer-seat.js +80 -0
- package/lib/runtime/tool-registry-observer.js +20 -20
- package/lib/runtime/user-commands.js +7 -3
- package/lib/runtime/user-panels.js +125 -46
- package/lib/runtime/user-store.js +124 -69
- package/lib/types/application/catalog-context.d.ts +124 -0
- package/lib/types/application/catalog.d.ts +108 -304
- package/lib/types/application/install-store.d.ts +21 -0
- package/lib/types/application/lsp-service.d.ts +36 -0
- package/lib/types/application/mcp-service.d.ts +77 -0
- package/lib/types/application/panel-resources.d.ts +8 -0
- package/lib/types/application/ports.d.ts +72 -0
- package/lib/types/application/queries.d.ts +15 -26
- package/lib/types/application/snapshot-cache.d.ts +51 -0
- package/lib/types/application/source-store.d.ts +100 -0
- package/lib/types/catalog/archive.d.ts +3 -11
- package/lib/types/catalog/component-files.d.ts +4 -0
- package/lib/types/catalog/fs-probes.d.ts +10 -0
- package/lib/types/catalog/manifests.d.ts +17 -16
- package/lib/types/catalog/native-project.d.ts +2 -2
- package/lib/types/catalog/paths.d.ts +53 -15
- package/lib/types/catalog/plugin-variables.d.ts +16 -0
- package/lib/types/catalog/scan-pipeline.d.ts +3 -3
- package/lib/types/catalog/scan-resolvers.d.ts +3 -9
- package/lib/types/catalog/skills-parse.d.ts +13 -7
- package/lib/types/catalog/source-catalog.d.ts +10 -5
- package/lib/types/catalog/suite-scanner.d.ts +8 -6
- package/lib/types/catalog/validate.d.ts +0 -2
- package/lib/types/contracts/lsp-status.d.ts +35 -0
- package/lib/types/contracts/market.d.ts +13 -2
- package/lib/types/contracts/mcp-status.d.ts +4 -0
- package/lib/types/contracts/settings.d.ts +59 -0
- package/lib/types/model/layouts.d.ts +10 -0
- package/lib/types/model/types.d.ts +29 -5
- package/lib/types/runtime/agent-role-names.d.ts +6 -0
- package/lib/types/runtime/agent-role-router.d.ts +59 -52
- package/lib/types/runtime/commands-mounts.d.ts +15 -7
- package/lib/types/runtime/deadline.d.ts +17 -0
- package/lib/types/runtime/dynamic-context.d.ts +81 -0
- package/lib/types/runtime/feedback-tool.d.ts +33 -11
- package/lib/types/runtime/hooks-mounts.d.ts +1 -1
- package/lib/types/runtime/host-locale.d.ts +20 -2
- package/lib/types/runtime/lsp-direct-config.d.ts +3 -3
- package/lib/types/runtime/lsp-mounts.d.ts +69 -19
- package/lib/types/runtime/lsp-status.d.ts +2 -1
- package/lib/types/runtime/mcp-auth-record.d.ts +29 -0
- package/lib/types/runtime/mcp-backend.d.ts +10 -14
- package/lib/types/runtime/mcp-client/config.d.ts +8 -2
- package/lib/types/runtime/mcp-client/host-contract.d.ts +78 -0
- package/lib/types/runtime/mcp-client/host-seams.d.ts +20 -64
- package/lib/types/runtime/mcp-client/oauth.d.ts +1 -1
- package/lib/types/runtime/mcp-client/plugin-identity.d.ts +16 -0
- package/lib/types/runtime/mcp-client/projection.d.ts +45 -0
- package/lib/types/runtime/mcp-client/tools.d.ts +5 -72
- package/lib/types/runtime/mcp-config.d.ts +25 -4
- package/lib/types/runtime/mcp-direct-config.d.ts +18 -4
- package/lib/types/runtime/mcp-mounts.d.ts +10 -15
- package/lib/types/runtime/mcp-overrides.d.ts +1 -1
- package/lib/types/runtime/mcp-status.d.ts +1 -1
- package/lib/types/runtime/mount-lifecycle.d.ts +53 -0
- package/lib/types/runtime/profile-seam.d.ts +64 -0
- package/lib/types/runtime/project-runtime.d.ts +10 -3
- package/lib/types/runtime/reconcile-scheduler.d.ts +45 -0
- package/lib/types/runtime/reconciler.d.ts +2 -0
- package/lib/types/runtime/regions.d.ts +1 -4
- package/lib/types/runtime/settings-namespace.d.ts +76 -0
- package/lib/types/runtime/skills-provider.d.ts +11 -2
- package/lib/types/runtime/source-auto-update.d.ts +32 -0
- package/lib/types/{model/state.d.ts → runtime/state-store.d.ts} +2 -2
- package/lib/types/runtime/storage-migration.d.ts +5 -3
- package/lib/types/runtime/subagent-catalog.d.ts +2 -0
- package/lib/types/runtime/timer-seat.d.ts +51 -0
- package/lib/types/runtime/tool-registry-observer.d.ts +12 -6
- package/lib/types/runtime/user-panels.d.ts +69 -19
- package/lib/types/runtime/user-store.d.ts +67 -20
- package/package.json +68 -24
- package/schemas/README.md +1 -1
package/lib/index.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { Catalog } from './application/catalog.js';
|
|
2
|
+
import { settlesWithin } from './runtime/deadline.js';
|
|
2
3
|
import { RuntimeReconciler } from './runtime/reconciler.js';
|
|
3
|
-
import {
|
|
4
|
+
import { ReconcileScheduler } from './runtime/reconcile-scheduler.js';
|
|
5
|
+
import { MarketSettingsNamespace } from './runtime/settings-namespace.js';
|
|
6
|
+
import { deleteMcpAuthGrant } from './runtime/mcp-auth-record.js';
|
|
7
|
+
import { inspectToolRegistry, toolsServiceOf } from './runtime/tool-registry-observer.js';
|
|
4
8
|
import { migratePluginStorage } from './runtime/storage-migration.js';
|
|
5
9
|
import { mountAgentRoleTool } from './runtime/agent-role-router.js';
|
|
6
10
|
import { projectAgentRoles } from './application/project-agent-roles.js';
|
|
7
11
|
import { mountProjectCommands, mountProjectMcp, mountProjectHooks, mountSuiteInstructions } from './runtime/project-runtime.js';
|
|
8
12
|
import { createPanelResources } from './application/panel-resources.js';
|
|
9
|
-
import { resolveDataRoot, resolveUserRoot } from './catalog/paths.js';
|
|
13
|
+
import { resolveAgentsRoot, resolveDataRoot, resolveUserRoot } from './catalog/paths.js';
|
|
10
14
|
import { mountSuiteRoutes } from './routes.js';
|
|
11
|
-
import { MCP_SETTINGS_NAMESPACE, MarketSettingsSchema, readMcpBackend } from './runtime/mcp-backend.js';
|
|
12
|
-
import { narrowDownloadRegion } from './runtime/regions.js';
|
|
13
15
|
import { SuiteSkillProvider } from './runtime/skills-provider.js';
|
|
16
|
+
import { shellSeamOf } from './runtime/dynamic-context.js';
|
|
14
17
|
import { loadLspServers } from './runtime/lsp-direct-config.js';
|
|
15
18
|
import { loadDisabledLspServers } from './runtime/lsp-server-state.js';
|
|
16
|
-
import { bindHostLocale, loadHostLocale } from './runtime/host-locale.js';
|
|
17
|
-
import { mountFeedbackTool } from './runtime/feedback-tool.js';
|
|
19
|
+
import { bindHostLocale, loadHostLocale, readHostLocalePreference, setHostLocaleSource } from './runtime/host-locale.js';
|
|
18
20
|
import { createUserPanelStores } from './runtime/user-panels.js';
|
|
21
|
+
import { SourceAutoUpdater } from './runtime/source-auto-update.js';
|
|
19
22
|
import { UserPanelSkillProvider } from './runtime/user-panels.js';
|
|
20
23
|
import { UserCommandMountRegistry } from './runtime/user-commands.js';
|
|
21
24
|
export const name = 'dsh-agent-plugins-market';
|
|
@@ -23,6 +26,7 @@ export const inject = ['skills', 'commands'];
|
|
|
23
26
|
export async function apply(ctx, config = {}) {
|
|
24
27
|
const userRoot = resolveUserRoot(config.userRoot);
|
|
25
28
|
const dataRoot = resolveDataRoot(config.dataRoot, userRoot);
|
|
29
|
+
const agentsRoot = resolveAgentsRoot();
|
|
26
30
|
const migration = await migratePluginStorage(config);
|
|
27
31
|
if (migration.conflicts.length > 0)
|
|
28
32
|
throw new Error(`Plugin storage migration conflicts (original files retained): ${migration.conflicts.join(', ')}`);
|
|
@@ -30,251 +34,151 @@ export async function apply(ctx, config = {}) {
|
|
|
30
34
|
let userPanelControl;
|
|
31
35
|
// Host runtime copy resolves from the harness `locale.preference` setting;
|
|
32
36
|
// the async settings read lands before the first session starts in practice.
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const hostLocale = { t: bindHostLocale(undefined) };
|
|
38
|
+
const refreshHostLocale = () => {
|
|
39
|
+
void loadHostLocale().then(locale => {
|
|
40
|
+
hostLocale.t = locale.t;
|
|
41
|
+
providerControl?.invalidate();
|
|
42
|
+
userPanelControl?.invalidate();
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
void refreshHostLocale();
|
|
46
|
+
// The locale preference the GUI writes is owned by the host settings service:
|
|
47
|
+
// read it there rather than re-parsing the document, and re-bind once the
|
|
48
|
+
// service lands (the locale namespace may register after this plugin).
|
|
49
|
+
ctx.inject(['settings'], settingsCtx => {
|
|
50
|
+
setHostLocaleSource(() => readHostLocalePreference(settingsCtx));
|
|
51
|
+
refreshHostLocale();
|
|
38
52
|
});
|
|
39
|
-
const hostLocale = { t };
|
|
40
|
-
/** Settings watchers to release at teardown (backend watch + feedback switch). */
|
|
41
|
-
const settingsWatchers = [];
|
|
42
53
|
const runtime = new RuntimeReconciler(ctx, dataRoot, key => hostLocale.t(key));
|
|
43
54
|
// User panel stores (skills / commands / agent personas) and their runtime
|
|
44
55
|
// contributions: one extra skill provider plus one command mount registry.
|
|
45
|
-
const panels = createUserPanelStores(
|
|
56
|
+
const panels = createUserPanelStores(agentsRoot);
|
|
46
57
|
const userCommands = new UserCommandMountRegistry(ctx, panels.commands, key => hostLocale.t(key));
|
|
47
58
|
let disposed = false;
|
|
48
59
|
let projectCommands;
|
|
49
60
|
let projectMcp;
|
|
50
61
|
let projectHooks;
|
|
51
62
|
let suitePrompts;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const suites = await catalog.enabledUserSuites();
|
|
57
|
-
if (disposed)
|
|
58
|
-
return;
|
|
59
|
-
const diagnostics = await runtime.reconcile(suites);
|
|
60
|
-
catalog.mcpDiagnostics = diagnostics.mcp;
|
|
61
|
-
for (const diagnostic of diagnostics.mcp) {
|
|
62
|
-
ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" mcp server "${diagnostic.serverKey}": ${diagnostic.reason}`);
|
|
63
|
-
}
|
|
64
|
-
for (const diagnostic of diagnostics.lsp) {
|
|
65
|
-
ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" lsp server "${diagnostic.serverKey}": ${diagnostic.reason}`);
|
|
66
|
-
}
|
|
67
|
-
for (const diagnostic of diagnostics.commands) {
|
|
68
|
-
if (diagnostic.reason !== '')
|
|
69
|
-
ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" command "${diagnostic.command}": ${diagnostic.reason}`);
|
|
70
|
-
}
|
|
71
|
-
for (const diagnostic of diagnostics.hooks) {
|
|
72
|
-
ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" hooks: ${diagnostic.reason}`);
|
|
73
|
-
}
|
|
74
|
-
for (const error of diagnostics.errors) {
|
|
75
|
-
ctx.logger?.warn(`[dsh-agent-plugins-market] ${error.surface} reconcile failed: ${error.reason}`);
|
|
76
|
-
}
|
|
63
|
+
const scheduler = new ReconcileScheduler(ctx, runtime, {
|
|
64
|
+
enabledSuites: () => catalog.enabledUserSuites(),
|
|
65
|
+
publishMcpDiagnostics: diagnostics => {
|
|
66
|
+
catalog.mcpDiagnostics = diagnostics;
|
|
77
67
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
});
|
|
69
|
+
/** Reconcile the user command mounts and report each failure once. */
|
|
70
|
+
const reconcileUserCommands = async () => {
|
|
71
|
+
const diagnostics = await userCommands.reconcile().catch(() => []);
|
|
72
|
+
for (const reason of diagnostics) {
|
|
73
|
+
ctx.logger?.warn(`[dsh-agent-plugins-market] user commands: ${reason}`);
|
|
81
74
|
}
|
|
82
75
|
};
|
|
83
|
-
// Settings, credential and catalog events can arrive during a slow MCP
|
|
84
|
-
// connection. Keep one current pass and one fresh follow-up, not a queue
|
|
85
|
-
// of stale snapshots that all reconnect the same servers.
|
|
86
|
-
const reconcileMounts = () => {
|
|
87
|
-
if (disposed)
|
|
88
|
-
return Promise.resolve();
|
|
89
|
-
reconcileRequested = true;
|
|
90
|
-
reconciliation ??= (async () => {
|
|
91
|
-
try {
|
|
92
|
-
do {
|
|
93
|
-
reconcileRequested = false;
|
|
94
|
-
await reconcileOnce();
|
|
95
|
-
} while (reconcileRequested && !disposed);
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
reconciliation = undefined;
|
|
99
|
-
}
|
|
100
|
-
})();
|
|
101
|
-
return reconciliation;
|
|
102
|
-
};
|
|
103
76
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* and the remount lost. Pending refs are therefore kept until the next pass
|
|
109
|
-
* observes them, and bursts are collapsed into one reconcile.
|
|
77
|
+
* How long the change pipeline holds the queue on one stage. A mount that
|
|
78
|
+
* stops responding — a stdio server that never finishes its handshake, a
|
|
79
|
+
* browser authorization nobody completed — delays its own surface only; the
|
|
80
|
+
* stage keeps running while the pipeline moves on to the next change.
|
|
110
81
|
*/
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (refs.length > 0) {
|
|
121
|
-
ctx.logger?.info?.(`[dsh-agent-plugins-market] reconciling after credential update: ${refs.join(', ')}`);
|
|
82
|
+
const CHANGE_STAGE_DEADLINE_MS = 20_000;
|
|
83
|
+
/** Run one pipeline stage, logging its failure or its overrun instead of holding the pipeline. */
|
|
84
|
+
const runStage = async (stage, work) => {
|
|
85
|
+
const running = (async () => await work())();
|
|
86
|
+
void running.catch(error => {
|
|
87
|
+
ctx.logger?.warn(`[dsh-agent-plugins-market] ${stage} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
|
+
});
|
|
89
|
+
if (!(await settlesWithin(running, CHANGE_STAGE_DEADLINE_MS))) {
|
|
90
|
+
ctx.logger?.warn(`[dsh-agent-plugins-market] ${stage} is still running after ${CHANGE_STAGE_DEADLINE_MS}ms; the change pipeline continues without waiting for it`);
|
|
122
91
|
}
|
|
123
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Catalog change pipeline: invalidate the derived skill and command
|
|
95
|
+
* surfaces, refresh the project mounts, then reconcile every runtime mount.
|
|
96
|
+
* `CatalogContext` schedules it in the background, so a stage that overruns
|
|
97
|
+
* costs freshness, never the mutation that asked for it.
|
|
98
|
+
*/
|
|
124
99
|
const onChanged = async () => {
|
|
125
100
|
if (disposed)
|
|
126
101
|
return;
|
|
127
102
|
providerControl?.invalidate();
|
|
128
103
|
userPanelControl?.invalidate();
|
|
129
|
-
await projectCommands?.refresh();
|
|
130
|
-
await suitePrompts?.refresh();
|
|
131
|
-
await Promise.all([projectMcp?.refresh(), projectHooks?.refresh()]);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
104
|
+
await runStage('project commands', () => projectCommands?.refresh());
|
|
105
|
+
await runStage('suite instructions', () => suitePrompts?.refresh());
|
|
106
|
+
await runStage('project MCP and hooks', () => Promise.all([projectMcp?.refresh(), projectHooks?.refresh()]));
|
|
107
|
+
await runStage('user commands', () => reconcileUserCommands());
|
|
108
|
+
await runStage('runtime mounts', () => scheduler.request());
|
|
109
|
+
};
|
|
110
|
+
// Background source updates: off until the settings switch says otherwise.
|
|
111
|
+
const autoUpdate = new SourceAutoUpdater(ctx, () => catalog.refreshSource());
|
|
112
|
+
const settings = new MarketSettingsNamespace(ctx, dataRoot, hostLocale, {
|
|
113
|
+
setScanProjectLayouts: enabled => catalog.setScanProjectLayouts(enabled),
|
|
114
|
+
refreshMcpMounts: () => {
|
|
115
|
+
void Promise.all([scheduler.request(), projectMcp?.refresh()]).catch(() => { });
|
|
116
|
+
},
|
|
117
|
+
setAutoUpdateSources: enabled => autoUpdate.setEnabled(enabled)
|
|
118
|
+
});
|
|
119
|
+
// The credentials store powers the MCP re-authorize action (dropping a grant
|
|
120
|
+
// record forces the next mount through a fresh browser authorization). Every
|
|
121
|
+
// port below is read at call time: this plugin's apply may run before the
|
|
122
|
+
// credentials, tools and settings services provision, and a snapshot taken
|
|
123
|
+
// here would be permanently undefined even after the service is live.
|
|
124
|
+
// Read at call time: the tools service provisions after apply() returns.
|
|
125
|
+
let toolsRegistry = toolsServiceOf(ctx);
|
|
126
|
+
const ports = {
|
|
127
|
+
mcpToolSnapshot: () => inspectToolRegistry(toolsRegistry),
|
|
128
|
+
credentialsStore: {
|
|
129
|
+
deleteGrantRecord: async (serverName) => {
|
|
130
|
+
const store = ctx.get?.('credentials');
|
|
131
|
+
await deleteMcpAuthGrant(store, serverName);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
// Re-authorize must rebuild the live bridge, not just drop the grant: the
|
|
135
|
+
// registry flags the mount and the following reconcile tears it down and
|
|
136
|
+
// remounts, so the server's 401 restarts the browser authorization.
|
|
137
|
+
mcpRemount: (suiteId, serverKey) => runtime.forceMcpRemount(suiteId, serverKey),
|
|
138
|
+
mcpRemountAll: () => runtime.forceMcpRemountAll(),
|
|
139
|
+
mcpServerOwner: serverName => runtime.mcpServerOwner(serverName),
|
|
140
|
+
lspStatusSource: runtime.lsp,
|
|
141
|
+
mcpBackend: () => settings.backend(),
|
|
142
|
+
setMcpBackend: backend => settings.setBackend(backend),
|
|
143
|
+
downloadRegion: () => settings.downloadRegion()
|
|
137
144
|
};
|
|
138
|
-
|
|
139
|
-
// record forces the next mount through a fresh browser authorization).
|
|
140
|
-
// Resolved lazily at call time: this plugin's apply may run before the
|
|
141
|
-
// credentials plugin provisions, and a snapshot taken here would be
|
|
142
|
-
// permanently undefined even after the service is live.
|
|
143
|
-
const catalog = new Catalog({ userRoot, dataRoot, onChanged, ...(config.git === undefined ? {} : { git: config.git }) });
|
|
145
|
+
const catalog = new Catalog({ userRoot, dataRoot, agentsRoot, onChanged, ports, ...(config.git === undefined ? {} : { git: config.git }) });
|
|
144
146
|
await catalog.load();
|
|
145
147
|
await catalog.mergeSources(config.sources ?? []);
|
|
146
148
|
const resources = createPanelResources(catalog, panels);
|
|
147
|
-
// Mirror oauth.ts's `credentialIdFor`: the record is stored under the folded
|
|
148
|
-
// serverName, so the delete must fold the same way or it misses the record.
|
|
149
|
-
const credentialIdFor = (serverName) => serverName
|
|
150
|
-
.toLowerCase()
|
|
151
|
-
.replace(/[^a-z0-9-]+/g, '-')
|
|
152
|
-
.replace(/^-+|-+$/g, '') || 'server';
|
|
153
|
-
catalog.setCredentialsStore({
|
|
154
|
-
deleteGrantRecord: async (serverName) => {
|
|
155
|
-
const store = ctx.get?.('credentials');
|
|
156
|
-
if (store?.deleteRecord === undefined)
|
|
157
|
-
throw new Error('credentials service is not mounted');
|
|
158
|
-
await store.deleteRecord(`mcp-auth/${credentialIdFor(serverName)}`);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
149
|
runtime.setMcpOverridesProvider(async () => catalog.allMcpOverrides(await catalog.enabledUserSuites()));
|
|
162
|
-
|
|
163
|
-
// registry flags the mount and the following reconcile tears it down and
|
|
164
|
-
// remounts, so the server's 401 restarts the browser authorization.
|
|
165
|
-
catalog.setMcpRemountHook((suiteId, serverKey) => runtime.forceMcpRemount(suiteId, serverKey), serverName => runtime.mcpServerOwner(serverName));
|
|
166
|
-
catalog.setLspStatusSource(runtime.lsp);
|
|
167
|
-
runtime.lsp.setDirectProvider(async () => (await loadLspServers(dataRoot)).servers);
|
|
150
|
+
runtime.lsp.setDirectProvider(async () => (await loadLspServers(agentsRoot)).servers);
|
|
168
151
|
runtime.lsp.setDisabledProvider(() => loadDisabledLspServers(dataRoot));
|
|
169
|
-
// The MCP backend choice is a host settings namespace (the registration is
|
|
170
|
-
// also what makes the plugin-config tab serve our card). The node half owns
|
|
171
|
-
// it: the provider derives the mount backend from the scope, the writer
|
|
172
|
-
// flips the switch, and the watcher remounts servers when it flips. The
|
|
173
|
-
// same single registration also gates the experience-feedback tool —
|
|
174
|
-
// `settings.register` throws on a duplicate namespace, so there is exactly
|
|
175
|
-
// one inject block and one `register` call.
|
|
176
|
-
//
|
|
177
|
-
// The whole callback is failure-contained on purpose: a throw here is
|
|
178
|
-
// INVISIBLE in the UI (the inject resolves asynchronously and cordis only
|
|
179
|
-
// logs), and it silently removes the namespace from settings.describe —
|
|
180
|
-
// which is exactly what hides the plugin-config card. Every failure mode
|
|
181
|
-
// lands in the logger with a loud prefix instead.
|
|
182
|
-
let feedbackDisposer;
|
|
183
|
-
ctx.inject(['settings'], settingsCtx => {
|
|
184
|
-
try {
|
|
185
|
-
ctx.logger?.info?.('[dsh-agent-plugins-market] settings inject resolved — registering namespace');
|
|
186
|
-
const settings = settingsCtx.settings;
|
|
187
|
-
const scope = settings.register(MCP_SETTINGS_NAMESPACE, MarketSettingsSchema, {
|
|
188
|
-
base: { mcpEnhanced: true, downloadRegion: 'auto', feedbackEnabled: true, scanProjectLayouts: true }
|
|
189
|
-
});
|
|
190
|
-
ctx.logger?.info?.('[dsh-agent-plugins-market] settings namespace registered — plugin-config card will serve');
|
|
191
|
-
catalog.setMcpBackendProvider(async () => (scope.get().mcpEnhanced === false ? 'host' : 'builtin'));
|
|
192
|
-
catalog.setMcpBackendWriter(async (backend) => {
|
|
193
|
-
await scope.update({ mcpEnhanced: backend !== 'host' });
|
|
194
|
-
});
|
|
195
|
-
catalog.setDownloadRegionProvider(async () => narrowDownloadRegion(scope.get().downloadRegion));
|
|
196
|
-
const syncProjectLayouts = () => {
|
|
197
|
-
void catalog.setScanProjectLayouts(scope.get().scanProjectLayouts !== false).catch(error => {
|
|
198
|
-
ctx.logger?.error?.(`[dsh-agent-plugins-market] project layout reconciliation failed: ${String(error)}`);
|
|
199
|
-
});
|
|
200
|
-
};
|
|
201
|
-
syncProjectLayouts();
|
|
202
|
-
settingsWatchers.push(scope.watch(syncProjectLayouts));
|
|
203
|
-
// One-time migration from the earlier data-root settings.json choice.
|
|
204
|
-
void readMcpBackend(dataRoot).then(backend => {
|
|
205
|
-
if (backend === 'host')
|
|
206
|
-
void scope.update({ mcpEnhanced: false }).catch(() => { });
|
|
207
|
-
});
|
|
208
|
-
let previousBackend = scope.get().mcpEnhanced !== false;
|
|
209
|
-
settingsWatchers.push(scope.watch(() => {
|
|
210
|
-
const backend = scope.get().mcpEnhanced !== false;
|
|
211
|
-
if (backend === previousBackend)
|
|
212
|
-
return;
|
|
213
|
-
previousBackend = backend;
|
|
214
|
-
void Promise.all([reconcileMounts(), projectMcp?.refresh()]).catch(() => { });
|
|
215
|
-
}));
|
|
216
|
-
// The experience-feedback model tool: gated by the namespace's
|
|
217
|
-
// `feedbackEnabled` field (default on); the switch unregisters it. The
|
|
218
|
-
// tool mount is doubly contained — its failure must never take the
|
|
219
|
-
// settings namespace (and with it the config card) down with it.
|
|
220
|
-
const syncFeedbackTool = () => {
|
|
221
|
-
try {
|
|
222
|
-
const state = scope.get();
|
|
223
|
-
const wanted = state.feedbackEnabled !== false;
|
|
224
|
-
if (wanted && feedbackDisposer === undefined) {
|
|
225
|
-
feedbackDisposer = mountFeedbackTool(ctx, dataRoot, (key, params) => hostLocale.t(key, params)) ?? undefined;
|
|
226
|
-
}
|
|
227
|
-
else if (!wanted && feedbackDisposer !== undefined) {
|
|
228
|
-
feedbackDisposer();
|
|
229
|
-
feedbackDisposer = undefined;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
ctx.logger?.error?.(`[dsh-agent-plugins-market] feedback tool mount failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
syncFeedbackTool();
|
|
237
|
-
settingsWatchers.push(scope.watch(() => syncFeedbackTool()));
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
ctx.logger?.error?.(`[dsh-agent-plugins-market] settings namespace registration failed — the plugin-config card will be hidden this boot: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
152
|
runtime.setMcpBackendProvider(() => catalog.mcpBackend());
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
clearTimeout(credentialFlush);
|
|
249
|
-
credentialFlush = setTimeout(flushCredentialUpdates, 150);
|
|
250
|
-
credentialFlush.unref?.();
|
|
251
|
-
}) ?? (() => { });
|
|
153
|
+
// The namespace must be registered after the catalog exists: the host
|
|
154
|
+
// resolves the inject callback synchronously when the settings service is
|
|
155
|
+
// already mounted, and the registration reads back the project-layout switch.
|
|
156
|
+
settings.mount();
|
|
252
157
|
ctx.inject(['tools'], toolsCtx => {
|
|
253
|
-
|
|
254
|
-
catalog.setMcpToolSnapshotProvider(() => inspectToolRegistry(registry));
|
|
158
|
+
toolsRegistry = toolsCtx.tools;
|
|
255
159
|
// Foreign-namespace guard: a native host MCP client (or another plugin)
|
|
256
160
|
// owning `mcp__<serverName>__` names makes the mount registry skip its
|
|
257
161
|
// own server with a clear diagnostic instead of failing mid-registration.
|
|
258
|
-
runtime.setMcpToolNamesProvider(() => inspectToolRegistry(
|
|
162
|
+
runtime.setMcpToolNamesProvider(() => inspectToolRegistry(toolsRegistry).map(tool => tool.name));
|
|
259
163
|
});
|
|
260
164
|
void Promise.resolve()
|
|
261
165
|
.then(async () => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
ctx.logger?.warn(`[dsh-agent-plugins-market] user commands: ${reason}`);
|
|
265
|
-
}
|
|
266
|
-
await reconcileMounts();
|
|
166
|
+
await reconcileUserCommands();
|
|
167
|
+
await scheduler.request();
|
|
267
168
|
})
|
|
268
169
|
.catch(() => { });
|
|
170
|
+
// The shell seam resolves lazily: the service may land after this plugin, and
|
|
171
|
+
// a profile without one keeps dynamic-context placeholders literal.
|
|
172
|
+
const shellSeam = () => shellSeamOf(ctx);
|
|
269
173
|
ctx.skills.registerProvider(control => {
|
|
270
174
|
providerControl = control;
|
|
271
|
-
return new SuiteSkillProvider(catalog);
|
|
175
|
+
return new SuiteSkillProvider(catalog, { dataRoot, shell: shellSeam });
|
|
272
176
|
});
|
|
273
177
|
// User panel skills ride a second provider so a panel
|
|
274
178
|
// edit invalidates only its own catalog contribution.
|
|
275
179
|
ctx.skills.registerProvider(control => {
|
|
276
180
|
userPanelControl = control;
|
|
277
|
-
return new UserPanelSkillProvider(panels.skills
|
|
181
|
+
return new UserPanelSkillProvider(panels.skills);
|
|
278
182
|
});
|
|
279
183
|
ctx.inject(['tools', 'llm', 'subagents', 'agents'], hostCtx => {
|
|
280
184
|
hostCtx.effect(() => mountAgentRoleTool(hostCtx, async (parent) => [
|
|
@@ -284,10 +188,10 @@ export async function apply(ctx, config = {}) {
|
|
|
284
188
|
});
|
|
285
189
|
ctx.inject(['agents'], hostCtx => {
|
|
286
190
|
hostCtx.effect(() => {
|
|
287
|
-
const mounted = mountProjectCommands(hostCtx, catalog, key => hostLocale.t(key));
|
|
191
|
+
const mounted = mountProjectCommands(hostCtx, catalog, key => hostLocale.t(key), dataRoot);
|
|
288
192
|
const mcp = mountProjectMcp(hostCtx, catalog, dataRoot);
|
|
289
193
|
const hooks = mountProjectHooks(hostCtx, catalog);
|
|
290
|
-
const prompts = mountSuiteInstructions(hostCtx, catalog);
|
|
194
|
+
const prompts = mountSuiteInstructions(hostCtx, catalog, dataRoot);
|
|
291
195
|
projectCommands = mounted;
|
|
292
196
|
projectMcp = mcp;
|
|
293
197
|
projectHooks = hooks;
|
|
@@ -310,15 +214,9 @@ export async function apply(ctx, config = {}) {
|
|
|
310
214
|
});
|
|
311
215
|
ctx.effect(() => () => {
|
|
312
216
|
disposed = true;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
credentialFlush = undefined;
|
|
317
|
-
pendingCredentialRefs.clear();
|
|
318
|
-
for (const dispose of settingsWatchers.splice(0))
|
|
319
|
-
dispose();
|
|
320
|
-
feedbackDisposer?.();
|
|
321
|
-
feedbackDisposer = undefined;
|
|
217
|
+
scheduler.dispose();
|
|
218
|
+
autoUpdate.dispose();
|
|
219
|
+
settings.dispose();
|
|
322
220
|
userCommands.disposeAll();
|
|
323
221
|
void runtime.dispose();
|
|
324
222
|
}, 'dsh-agent-plugins-market: lifecycle');
|
package/lib/model/layouts.js
CHANGED
|
@@ -15,6 +15,10 @@ export const MANIFEST_ALIASES = { kimi: ['kimi.plugin.json'] };
|
|
|
15
15
|
/** Equivalent plugin-owned path variables; these never resolve as credentials. */
|
|
16
16
|
export const PLUGIN_ROOT_VARIABLES = new Set(['PLUGIN_ROOT', 'CLAUDE_PLUGIN_ROOT', 'CODEX_PLUGIN_ROOT', 'ZCODE_PLUGIN_ROOT', 'QODER_PLUGIN_ROOT']);
|
|
17
17
|
export const PLUGIN_DATA_VARIABLES = new Set(['PLUGIN_DATA', 'CLAUDE_PLUGIN_DATA', 'ZCODE_PLUGIN_DATA', 'QODER_PLUGIN_DATA']);
|
|
18
|
+
/** Equivalent session project-directory variables; only a calling session supplies the directory. */
|
|
19
|
+
export const PROJECT_DIR_VARIABLES = new Set(['CLAUDE_PROJECT_DIR', 'ZCODE_PROJECT_DIR', 'QODER_PROJECT_DIR']);
|
|
20
|
+
/** Claude Code names the directory holding the skill's own file; no dialect alias exists. */
|
|
21
|
+
export const SKILL_DIR_VARIABLE = 'CLAUDE_SKILL_DIR';
|
|
18
22
|
export const PROJECT_LAYOUTS = [
|
|
19
23
|
{
|
|
20
24
|
dirName: '.claude',
|
|
@@ -23,7 +27,12 @@ export const PROJECT_LAYOUTS = [
|
|
|
23
27
|
mcpFiles: ['.mcp.json'],
|
|
24
28
|
hookFiles: ['.claude/settings.json', '.claude/settings.local.json']
|
|
25
29
|
},
|
|
26
|
-
{
|
|
30
|
+
{
|
|
31
|
+
dirName: '.agents',
|
|
32
|
+
label: 'agents',
|
|
33
|
+
subdirs: ['skills', 'agents', 'commands'],
|
|
34
|
+
hookFiles: ['.agents/hooks/hooks.json', '.agents/hooks.json']
|
|
35
|
+
},
|
|
27
36
|
{ dirName: '.codex', label: 'Codex', subdirs: ['skills'], mcpFiles: ['.codex/config.toml'], mcpFormat: 'codex' },
|
|
28
37
|
{ dirName: '.cursor', label: 'Cursor', subdirs: ['skills', 'agents', 'commands'], mcpFiles: ['.cursor/mcp.json'] },
|
|
29
38
|
{ dirName: '.kimi', label: 'Kimi', subdirs: ['skills'] },
|
package/lib/model/types.js
CHANGED
|
@@ -7,11 +7,34 @@
|
|
|
7
7
|
* Codex `.codex-plugin/plugin.json`). Discovery maps every layout onto this
|
|
8
8
|
* internal shape; runtime injection consumes only the internal shape.
|
|
9
9
|
*/
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Archive URL suffixes with the payload kind each names, longest suffix first
|
|
12
|
+
* so `.tar.gz` wins over `.tar`. Source-kind inference, source-id derivation,
|
|
13
|
+
* and archive extraction all read this one table.
|
|
14
|
+
*/
|
|
15
|
+
export const ARCHIVE_SUFFIXES = [
|
|
16
|
+
['.tar.gz', 'targz'],
|
|
17
|
+
['.tgz', 'targz'],
|
|
18
|
+
['.zip', 'zip'],
|
|
19
|
+
['.tar', 'tar']
|
|
20
|
+
];
|
|
21
|
+
/** The archive suffix `url` ends with, or undefined when it names no known payload. */
|
|
22
|
+
function matchArchiveSuffix(url) {
|
|
23
|
+
const clean = url.trim().toLowerCase();
|
|
24
|
+
return ARCHIVE_SUFFIXES.find(([suffix]) => clean.endsWith(suffix));
|
|
25
|
+
}
|
|
26
|
+
/** Classify an archive URL by extension; undefined when unsupported. */
|
|
27
|
+
export function archiveFormatOf(url) {
|
|
28
|
+
return matchArchiveSuffix(url)?.[1];
|
|
29
|
+
}
|
|
12
30
|
/** Whether a URL points at a downloadable archive. */
|
|
13
31
|
export function isArchiveUrl(url) {
|
|
14
|
-
return
|
|
32
|
+
return matchArchiveSuffix(url) !== undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Drop a trailing archive suffix (`plugin-0.1.zip` → `plugin-0.1`); other values pass through. */
|
|
35
|
+
export function stripArchiveSuffix(value) {
|
|
36
|
+
const suffix = matchArchiveSuffix(value)?.[0];
|
|
37
|
+
return suffix === undefined ? value : value.slice(0, -suffix.length);
|
|
15
38
|
}
|
|
16
39
|
/**
|
|
17
40
|
* Effective acquisition kind of a source: the explicit `kind` wins, the
|