@khorsheed/dsh-capability-catalog 0.1.97 → 0.1.99

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.en.md +31 -7
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +31 -7
  5. package/cordis.patch.yml +1 -1
  6. package/lib/client.js +14790 -902
  7. package/lib/index.js +760 -265
  8. package/lib/typert.host.js +614 -295
  9. package/lib/typert.remote-client.d.ts +27 -4
  10. package/lib/typert.remote-client.js +599 -288
  11. package/lib/types/client/CapabilityCatalogCard.js +20 -5
  12. package/lib/types/client/SkillDetailModal.d.ts +13 -5
  13. package/lib/types/client/SkillDetailModal.js +9 -3
  14. package/lib/types/client/locales.d.ts +0 -4
  15. package/lib/types/client/locales.js +4 -8
  16. package/lib/types/envHint.d.ts +7 -1
  17. package/lib/types/envHint.js +52 -18
  18. package/lib/types/evidence.d.ts +142 -0
  19. package/lib/types/evidence.js +179 -0
  20. package/lib/types/frontmatter.d.ts +78 -0
  21. package/lib/types/frontmatter.js +186 -0
  22. package/lib/types/import.js +10 -19
  23. package/lib/types/index.d.ts +16 -4
  24. package/lib/types/index.js +32 -53
  25. package/lib/types/mcpStore.d.ts +6 -8
  26. package/lib/types/mcpStore.js +18 -43
  27. package/lib/types/mcps.d.ts +1 -6
  28. package/lib/types/mcps.js +0 -9
  29. package/lib/types/namespace.d.ts +2 -3
  30. package/lib/types/namespace.js +2 -3
  31. package/lib/types/remote.d.ts +11 -2
  32. package/lib/types/remote.js +4 -1
  33. package/lib/types/scoped-delivery.d.ts +36 -24
  34. package/lib/types/scoped-delivery.js +136 -100
  35. package/lib/types/scoped-edits.js +2 -1
  36. package/lib/types/shellEnv.d.ts +8 -1
  37. package/lib/types/shellEnv.js +37 -16
  38. package/lib/types/skills.d.ts +41 -0
  39. package/lib/types/skills.js +51 -9
  40. package/lib/types/tool-face.d.ts +68 -0
  41. package/lib/types/tool-face.js +82 -0
  42. package/lib/types/types.d.ts +40 -19
  43. package/package.json +20 -18
  44. package/lib/invariant.js +0 -25
  45. package/lib/types/invariant.d.ts +0 -16
  46. package/lib/types/invariant.js +0 -23
@@ -51,6 +51,7 @@ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
51
51
  import { defineTool } from '@deepseek-ai/dsh-tools';
52
52
  import { catalogAddSkill, catalogDetail, catalogListDirSkills, catalogReadSkillFile, catalogSetCredential, catalogSnapshot, catalogDeleteSkill, catalogPickDirectory } from './remote.js';
53
53
  import { resolveServices } from './skills.js';
54
+ import { toolSchemasFacing, toolOriginsFacing, mcpServerNamesFacing } from './tool-face.js';
54
55
  import { definitionFor, scanManagedSkills, ScopedSkillDelivery, scopedSkillsRoot, } from './scoped-delivery.js';
55
56
  import { adoptManagedSkill, releaseManagedSkill, setManagedPresetScope, } from './scoped-edits.js';
56
57
  import { installSkillEnvInjection } from './shellEnv.js';
@@ -58,8 +59,6 @@ import { McpStore } from './mcpStore.js';
58
59
  import { reconcileRegisteredMcpTools, desiredMcpTools } from './mcpTools.js';
59
60
  import { maskConfig } from './mcps.js';
60
61
  import { installSkillEnvHint } from './envHint.js';
61
- import { MCP_TOOL_PREFIX } from './channels.js';
62
- import { toolOrigin, isValidOrigin } from './tool-origin.js';
63
62
  import { CAPABILITY_CATALOG_NS } from './namespace.js';
64
63
  import { CapabilityCatalogSettingsSchema } from './settings.js';
65
64
  import { capsTag, hashOf } from './capabilities.js';
@@ -72,12 +71,20 @@ export { resolveSkillNameFromContent } from './import.js';
72
71
  export { resolvePresetScope } from './preset-scope.js';
73
72
  // The mode view: the roster a picker offers and one face per mode.
74
73
  export { modeOptions, readModeFaces } from './modes.js';
75
- export { ScopedSkillDelivery, scopedSkillsRoot, scanManagedSkills, managedSkillFrom, parsePresetScopeFrontmatter, defaultSkillRoots, detectConflicts, SCOPED_PROVIDER_NAME, MANAGED_SKILL_RANK, } from './scoped-delivery.js';
74
+ export { ScopedSkillDelivery, scopedSkillsRoot, scanManagedSkills, managedSkillFrom, defaultSkillRoots, detectConflicts, SCOPED_PROVIDER_NAME, MANAGED_SKILL_RANK, } from './scoped-delivery.js';
76
75
  export { adoptManagedSkill, releaseManagedSkill, setManagedPresetScope, withPresetScope, findSkillSource, managedLocation, isDirectory, } from './scoped-edits.js';
77
76
  // The capability fingerprint: the canonical form, its digest, and the tag.
78
77
  // Exported from the package root so a host reader can hash a snapshot it
79
78
  // already holds without a second Remote round-trip.
80
79
  export { canonicalCapabilities, canonicalJson, capsTag, hashOf, hashSkillBody, CAPS_TAG_PREFIX, } from './capabilities.js';
80
+ // Capability-composition EVIDENCE: the pure-data production face a target
81
+ // side runs where the composition lives. It reuses this package's own
82
+ // snapshot/digest algorithms (parity by construction), mounts no standing
83
+ // scope, registers no service, and returns plain JSON-safe data: the digest,
84
+ // the per-section completeness, the measurement object, and the caller's
85
+ // coverage declaration. Consumers never recompute the digest and never treat
86
+ // a partial/unavailable section as verified.
87
+ export { produceCapabilityEvidence } from './evidence.js';
81
88
  /** The remote namespace name (matches the generated remote-client type). */
82
89
  export const REMOTE_NS = 'capabilityCatalog';
83
90
  /** Custom inject: subscribe no extra service beyond the two we probe. */
@@ -107,7 +114,6 @@ let CapabilityCatalogService = (() => {
107
114
  let _addSkill_decorators;
108
115
  let _deleteSkill_decorators;
109
116
  let _pickDirectory_decorators;
110
- let _mcpList_decorators;
111
117
  let _mcpAdd_decorators;
112
118
  let _mcpRemove_decorators;
113
119
  let _mcpSetEnabled_decorators;
@@ -134,7 +140,6 @@ let CapabilityCatalogService = (() => {
134
140
  _addSkill_decorators = [Remote('addSkill')];
135
141
  _deleteSkill_decorators = [Remote('deleteSkill')];
136
142
  _pickDirectory_decorators = [Remote('pickDirectory')];
137
- _mcpList_decorators = [Remote('mcpList')];
138
143
  _mcpAdd_decorators = [Remote('mcpAdd')];
139
144
  _mcpRemove_decorators = [Remote('mcpRemove')];
140
145
  _mcpSetEnabled_decorators = [Remote('mcpSetEnabled')];
@@ -158,7 +163,6 @@ let CapabilityCatalogService = (() => {
158
163
  __esDecorate(this, null, _addSkill_decorators, { kind: "method", name: "addSkill", static: false, private: false, access: { has: obj => "addSkill" in obj, get: obj => obj.addSkill }, metadata: _metadata }, null, _instanceExtraInitializers);
159
164
  __esDecorate(this, null, _deleteSkill_decorators, { kind: "method", name: "deleteSkill", static: false, private: false, access: { has: obj => "deleteSkill" in obj, get: obj => obj.deleteSkill }, metadata: _metadata }, null, _instanceExtraInitializers);
160
165
  __esDecorate(this, null, _pickDirectory_decorators, { kind: "method", name: "pickDirectory", static: false, private: false, access: { has: obj => "pickDirectory" in obj, get: obj => obj.pickDirectory }, metadata: _metadata }, null, _instanceExtraInitializers);
161
- __esDecorate(this, null, _mcpList_decorators, { kind: "method", name: "mcpList", static: false, private: false, access: { has: obj => "mcpList" in obj, get: obj => obj.mcpList }, metadata: _metadata }, null, _instanceExtraInitializers);
162
166
  __esDecorate(this, null, _mcpAdd_decorators, { kind: "method", name: "mcpAdd", static: false, private: false, access: { has: obj => "mcpAdd" in obj, get: obj => obj.mcpAdd }, metadata: _metadata }, null, _instanceExtraInitializers);
163
167
  __esDecorate(this, null, _mcpRemove_decorators, { kind: "method", name: "mcpRemove", static: false, private: false, access: { has: obj => "mcpRemove" in obj, get: obj => obj.mcpRemove }, metadata: _metadata }, null, _instanceExtraInitializers);
164
168
  __esDecorate(this, null, _mcpSetEnabled_decorators, { kind: "method", name: "mcpSetEnabled", static: false, private: false, access: { has: obj => "mcpSetEnabled" in obj, get: obj => obj.mcpSetEnabled }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -278,7 +282,7 @@ let CapabilityCatalogService = (() => {
278
282
  // inject so the env-injection runs once shellEnv/credentials/skills are
279
283
  // registered (at the catalog's apply they may not be composed yet).
280
284
  ctx.inject(['shellEnv', 'credentials', 'skills'], () => {
281
- installSkillEnvInjection(ctx, () => this.catalogScope());
285
+ installSkillEnvInjection(ctx, () => this.catalogScope(), () => this.dshHome());
282
286
  });
283
287
  // Runtime companion hint (generic): when the skill tool loads a skill,
284
288
  // tell the model the configured credential env mappings so it uses the
@@ -402,18 +406,26 @@ let CapabilityCatalogService = (() => {
402
406
  /** Record tools that appeared after the apply-time baseline (reads the live
403
407
  * registry each time, so a change event never sees a stale/undefined ref). */
404
408
  markNewTools() {
405
- const tools = this.ctx.get?.('tools');
409
+ const tools = this.toolsRegistry();
406
410
  const current = new Set(tools?.schemas().map(t => t.name) ?? []);
407
411
  for (const name of current) {
408
412
  if (!this.baseline.has(name))
409
413
  this.appearedAfterApply.add(name);
410
414
  }
411
415
  }
412
- /** The managed skills root under `$DSH_HOME`. */
416
+ /**
417
+ * The managed skills root under the harness home. Mirrors the official
418
+ * precedence (`resolveDshHome` in `@deepseek-ai/dsh-home-paths`):
419
+ * the settings service's home, then a non-blank `$DSH_HOME`, then the
420
+ * `~/.dsh` installation default. The fallback was once hardcoded to
421
+ * `.dsh-official`, which silently pointed a desktop instance (no settings
422
+ * home, no env) at ANOTHER instance's managed skills (2026-09-30).
423
+ */
413
424
  dshHome() {
414
425
  const settings = this.ctx.get?.('settings');
415
- const home = settings?.home;
416
- return home ?? process.env.DSH_HOME ?? join(homedir(), '.dsh-official');
426
+ const fromEnv = process.env.DSH_HOME;
427
+ const envHome = fromEnv !== undefined && fromEnv.trim().length > 0 ? fromEnv : undefined;
428
+ return settings?.home ?? envHome ?? join(homedir(), '.dsh');
417
429
  }
418
430
  /**
419
431
  * The standing skill view scope of ONE preset. In the web bundle the
@@ -602,10 +614,6 @@ let CapabilityCatalogService = (() => {
602
614
  async pickDirectory() {
603
615
  return catalogPickDirectory(this.ctx);
604
616
  }
605
- async mcpList() {
606
- // Masked DTO: never surface secretRef markers (or the secret key names) to the browser.
607
- return this.mcp.list().map(maskConfig);
608
- }
609
617
  async mcpAdd(config) {
610
618
  if (config.serverName.trim() === '')
611
619
  return false;
@@ -642,57 +650,28 @@ let CapabilityCatalogService = (() => {
642
650
  credentials: this.mcp.credentials(),
643
651
  };
644
652
  }
653
+ /** The live tools registry, when the composition provides one. */
654
+ toolsRegistry() {
655
+ return this.ctx.get?.('tools');
656
+ }
645
657
  /** The visible tool schemas in the standing scope (the set the model sees),
646
658
  * or the global layer when no preset standing key resolves. Skills already
647
659
  * enumerate through the same scope; tools must use it too so the
648
660
  * settings reader sees the official/plugin/MCP tools the model actually has. */
649
661
  toolSchemasIn(scope) {
650
- const tools = this.ctx.get?.('tools');
651
- if (tools?.schemas === undefined)
652
- return [];
653
- return scope === undefined ? tools.schemas() : tools.schemas(scope);
662
+ return toolSchemasFacing(this.toolsRegistry(), scope);
654
663
  }
655
664
  /** tool name → author-declared origin, read via `ctx.tools.get(name)` so the
656
665
  * `Symbol.for('dsh.tool.origin')` tag on the retained definition survives (the
657
666
  * system-prompt assembly strips it, but `get()` returns the full definition). */
658
667
  toolOriginsMap(scope) {
659
- const tools = this.ctx.get?.('tools');
660
- if (tools?.get === undefined)
661
- return new Map();
662
- const schemas = scope === undefined ? tools.schemas() : tools.schemas(scope);
663
- const out = new Map();
664
- for (const schema of schemas) {
665
- const def = scope === undefined ? tools.get(schema.name) : tools.get(schema.name, scope);
666
- if (def === undefined) {
667
- // `schemas()` and `get()` disagree for this scope — the author's origin
668
- // tag (if any) cannot be read, so the tool falls back to heuristics.
669
- this.ctx.logger.warn(`capability-catalog: tool "${schema.name}" is visible but ctx.tools.get() is undefined in this scope — its origin tag cannot be read; it will be classified by heuristics`);
670
- continue;
671
- }
672
- const origin = toolOrigin(def);
673
- if (origin !== undefined) {
674
- if (!isValidOrigin(origin)) {
675
- this.ctx.logger.warn(`capability-catalog: tool "${schema.name}" origin channel "${String(origin.channel)}" is not a known channel (plugin/builtin/mcp) — ignoring the tag, falling back to heuristics`);
676
- continue;
677
- }
678
- out.set(schema.name, origin);
679
- }
680
- }
681
- return out;
668
+ const tools = this.toolsRegistry();
669
+ const { origins } = toolOriginsFacing(tools, toolSchemasFacing(tools, scope), scope, (message) => this.ctx.logger.warn(message));
670
+ return origins;
682
671
  }
683
672
  /** MCP server names from live `mcp__`-prefixed tools (prefix-derived baseline). */
684
673
  mcpServerNamesIn(scope) {
685
- const names = [];
686
- for (const schema of this.toolSchemasIn(scope)) {
687
- if (!schema.name.startsWith(MCP_TOOL_PREFIX))
688
- continue;
689
- const rest = schema.name.slice(MCP_TOOL_PREFIX.length);
690
- const sep = rest.indexOf('__');
691
- const server = sep > 0 ? rest.slice(0, sep) : rest;
692
- if (server.length > 0 && !names.includes(server))
693
- names.push(server);
694
- }
695
- return names;
674
+ return mcpServerNamesFacing(this.toolSchemasIn(scope));
696
675
  }
697
676
  /**
698
677
  * Reconcile which discovered MCP tools are registered on `ctx.tools` so the
@@ -8,7 +8,8 @@
8
8
  import type { CatalogMcpCredentialDecl, CatalogMcpServerConfig, CatalogMcpTool } from './types.ts';
9
9
  /** One tool's persisted record: the user's toggle choice only. Descriptions and
10
10
  * parameter schemas are discovered at connect time and deliberately NOT persisted
11
- * (they bloat settings.yaml and go stale); `reconnectAll` re-fills them at boot. */
11
+ * (they bloat settings.yaml and go stale); the service re-fills them at boot by
12
+ * re-connecting the enabled servers. */
12
13
  export interface PersistedMcpTool {
13
14
  readonly name: string;
14
15
  readonly enabled: boolean;
@@ -50,15 +51,12 @@ export declare class McpStore {
50
51
  setToolEnabled(serverName: string, toolName: string, enabled: boolean): void;
51
52
  /** Connect + discover tools for a server; caches the list (connect errors surface). */
52
53
  discover(serverName: string): Promise<readonly CatalogMcpTool[]>;
53
- /** Re-connect + re-discover every enabled server — boot-time refill of the
54
- * discovered descriptions/parameters (the persisted block stores only
55
- * name+enabled). Preserves per-tool enable flags; per-server errors are
56
- * recorded and do not stop the others. */
57
- reconnectAll(): Promise<void>;
58
- /** A server's most recent connection/dev error. */
59
- errorOf(serverName: string): string | undefined;
60
54
  /** Forward one tool call to a server (connect → call → close). Resolves secrets. */
61
55
  callTool(serverName: string, rawName: string, args: Record<string, unknown>): Promise<unknown>;
56
+ /** The `resolveConfigSecrets` callback for one server: a bare secret ref
57
+ * resolves to the stored credential under the server's env/header/query
58
+ * namespace (the same namespaces {@link credentialRefsOf} writes). */
59
+ private secretResolver;
62
60
  /** Extract credential reference paths from a config (env/header/url-query).
63
61
  * Namespaced `mcp.<serverName>.env.<KEY>` so multiple servers don't collide. */
64
62
  private credentialRefsOf;
@@ -32,7 +32,6 @@ export class McpStore {
32
32
  config: server.config,
33
33
  credentials: existing?.credentials ?? new Map(),
34
34
  tools,
35
- error: undefined,
36
35
  });
37
36
  }
38
37
  for (const [ref, value] of Object.entries(state.credentials ?? {})) {
@@ -83,7 +82,6 @@ export class McpStore {
83
82
  config,
84
83
  credentials: existing?.credentials ?? new Map(),
85
84
  tools: existing?.tools ?? [],
86
- error: existing?.error,
87
85
  });
88
86
  this.persist();
89
87
  }
@@ -130,56 +128,41 @@ export class McpStore {
130
128
  const { connectMcpServer } = await import('./mcpConnector.js');
131
129
  // Resolve secretRef markers to the stored credential values before connecting,
132
130
  // so a configured API key is sent as its real value (never the secretRef).
133
- const resolved = await resolveConfigSecrets(s.config, async (ref) => {
134
- const candidates = [
135
- `mcp.${serverName}.env.${ref}`,
136
- `mcp.${serverName}.header.${ref}`,
137
- `mcp.${serverName}.query.${ref}`,
138
- ];
139
- for (const candidate of candidates) {
140
- const value = s.credentials.get(candidate);
141
- if (value !== undefined)
142
- return value;
143
- }
144
- return undefined;
145
- });
131
+ const resolved = await resolveConfigSecrets(s.config, this.secretResolver(serverName, s));
146
132
  const connection = await connectMcpServer(resolved);
147
133
  // Preserve the user's per-tool enable flags — re-discovering refreshes
148
134
  // descriptions/parameters without resetting toggles (new tools default on).
149
135
  const prevEnabled = new Map(s.tools.map(t => [t.name, t.enabled]));
150
136
  s.tools = connection.tools.map(t => ({ ...t, enabled: prevEnabled.get(t.name) ?? true }));
151
- s.error = undefined;
152
137
  await connection.close();
153
138
  this.persist();
154
139
  return s.tools;
155
140
  }
156
- catch (error) {
157
- s.error = String(error);
141
+ catch {
158
142
  this.persist();
159
143
  return s.tools;
160
144
  }
161
145
  }
162
- /** Re-connect + re-discover every enabled server — boot-time refill of the
163
- * discovered descriptions/parameters (the persisted block stores only
164
- * name+enabled). Preserves per-tool enable flags; per-server errors are
165
- * recorded and do not stop the others. */
166
- async reconnectAll() {
167
- for (const server of this.list()) {
168
- if (!server.enabled)
169
- continue;
170
- await this.discover(server.serverName);
171
- }
172
- }
173
- /** A server's most recent connection/dev error. */
174
- errorOf(serverName) {
175
- return this.servers.get(serverName)?.error;
176
- }
177
146
  /** Forward one tool call to a server (connect → call → close). Resolves secrets. */
178
147
  async callTool(serverName, rawName, args) {
179
148
  const s = this.servers.get(serverName);
180
149
  if (s === undefined)
181
150
  throw new Error(`unknown MCP server: ${serverName}`);
182
- const resolved = await resolveConfigSecrets(s.config, async (ref) => {
151
+ const resolved = await resolveConfigSecrets(s.config, this.secretResolver(serverName, s));
152
+ const { connectMcpServer } = await import('./mcpConnector.js');
153
+ const connection = await connectMcpServer(resolved);
154
+ try {
155
+ return await connection.call(rawName, args);
156
+ }
157
+ finally {
158
+ await connection.close();
159
+ }
160
+ }
161
+ /** The `resolveConfigSecrets` callback for one server: a bare secret ref
162
+ * resolves to the stored credential under the server's env/header/query
163
+ * namespace (the same namespaces {@link credentialRefsOf} writes). */
164
+ secretResolver(serverName, s) {
165
+ return async (ref) => {
183
166
  const candidates = [
184
167
  `mcp.${serverName}.env.${ref}`,
185
168
  `mcp.${serverName}.header.${ref}`,
@@ -191,15 +174,7 @@ export class McpStore {
191
174
  return value;
192
175
  }
193
176
  return undefined;
194
- });
195
- const { connectMcpServer } = await import('./mcpConnector.js');
196
- const connection = await connectMcpServer(resolved);
197
- try {
198
- return await connection.call(rawName, args);
199
- }
200
- finally {
201
- await connection.close();
202
- }
177
+ };
203
178
  }
204
179
  /** Extract credential reference paths from a config (env/header/url-query).
205
180
  * Namespaced `mcp.<serverName>.env.<KEY>` so multiple servers don't collide. */
@@ -10,7 +10,7 @@
10
10
  * the catalog's own thin client).
11
11
  * @module @khorsheed/dsh-capability-catalog/mcps
12
12
  */
13
- import type { CatalogJsonValue, CatalogMcpCredentialDecl, CatalogMcpServerConfig, CatalogMcpTool, McpTransport } from './types.ts';
13
+ import type { CatalogJsonValue, CatalogMcpServerConfig, McpTransport } from './types.ts';
14
14
  /** Secret marker put in a config field whose value lives in the credential store. */
15
15
  export declare const SECRET_REF_PREFIX = "secretRef:";
16
16
  /** A parsed server from a pasted mcp.json server entry. */
@@ -41,15 +41,11 @@ export declare function maskSecret(value: string): string;
41
41
  * marker (bare or scheme-prefixed) with `·secretRef·`, so neither the secret key
42
42
  * name nor the marker is exposed to the browser. */
43
43
  export declare function maskConfig(config: CatalogMcpServerConfig): CatalogMcpServerConfig;
44
- /** Whether the config still has an unfilled credential (= prompts for it). */
45
- export declare function hasUnconfiguredCredentials(credentials: readonly CatalogMcpCredentialDecl[]): boolean;
46
44
  /** Resolve `secretRef:` markers in a config into plaintext before connecting
47
45
  * (the catalog resolves via the stored credential values, then connects).
48
46
  * Replaces embedded markers too, so a scheme-prefixed value (`Bearer secretRef:a`)
49
47
  * resolves to `Bearer <value>`. */
50
48
  export declare function resolveConfigSecrets(config: CatalogMcpServerConfig, resolve: (ref: string) => Promise<string | undefined>): Promise<CatalogMcpServerConfig>;
51
- /** Sort tools by name and default each to enabled. */
52
- export declare function finalizeTools(tools: readonly CatalogMcpTool[]): CatalogMcpTool[];
53
49
  /**
54
50
  * Map each bare secret config key (env/header name, or a query param) to its
55
51
  * catalog-stored, namespaced credential ref (`mcp.<server>.<kind>.<key>`). The
@@ -59,6 +55,5 @@ export declare function finalizeTools(tools: readonly CatalogMcpTool[]): Catalog
59
55
  * next snapshot round-trips the namespaced refs.
60
56
  */
61
57
  export declare function credentialStoredRefs(serverName: string, config: CatalogMcpServerConfig): Readonly<Record<string, string>>;
62
- export { SECRET_REF_PREFIX as _SECRET_REF_PREFIX };
63
58
  export type { CatalogJsonValue };
64
59
  //# sourceMappingURL=mcps.d.ts.map
package/lib/types/mcps.js CHANGED
@@ -92,10 +92,6 @@ export function maskConfig(config) {
92
92
  ...(url !== undefined ? { url } : {}),
93
93
  };
94
94
  }
95
- /** Whether the config still has an unfilled credential (= prompts for it). */
96
- export function hasUnconfiguredCredentials(credentials) {
97
- return credentials.some(c => !c.configured);
98
- }
99
95
  /** Resolve `secretRef:` markers in a config into plaintext before connecting
100
96
  * (the catalog resolves via the stored credential values, then connects).
101
97
  * Replaces embedded markers too, so a scheme-prefixed value (`Bearer secretRef:a`)
@@ -124,10 +120,6 @@ export async function resolveConfigSecrets(config, resolve) {
124
120
  ...(url !== undefined ? { url } : {}),
125
121
  };
126
122
  }
127
- /** Sort tools by name and default each to enabled. */
128
- export function finalizeTools(tools) {
129
- return [...tools].sort((a, b) => a.name.localeCompare(b.name));
130
- }
131
123
  /**
132
124
  * Map each bare secret config key (env/header name, or a query param) to its
133
125
  * catalog-stored, namespaced credential ref (`mcp.<server>.<kind>.<key>`). The
@@ -154,4 +146,3 @@ export function credentialStoredRefs(serverName, config) {
154
146
  }
155
147
  return out;
156
148
  }
157
- export { SECRET_REF_PREFIX as _SECRET_REF_PREFIX }; // re-export for tests if needed
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * The settings namespace owned by the capability-catalog plugin. Plain string
3
- * so both the host half (settings registration) and the browser half (the
4
- * settings.plugin.item card key) import it without dragging host-only
5
- * dependencies into the client bundle.
3
+ * so both the host half (settings registration) and the browser half import
4
+ * it without dragging host-only dependencies into the client bundle.
6
5
  */
7
6
  /** Settings namespace owned by the capability-catalog plugin. */
8
7
  export declare const CAPABILITY_CATALOG_NS = "capability-catalog";
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * The settings namespace owned by the capability-catalog plugin. Plain string
3
- * so both the host half (settings registration) and the browser half (the
4
- * settings.plugin.item card key) import it without dragging host-only
5
- * dependencies into the client bundle.
3
+ * so both the host half (settings registration) and the browser half import
4
+ * it without dragging host-only dependencies into the client bundle.
6
5
  */
7
6
  /** Settings namespace owned by the capability-catalog plugin. */
8
7
  export const CAPABILITY_CATALOG_NS = 'capability-catalog';
@@ -6,10 +6,10 @@
6
6
  */
7
7
  import type { Context } from '@deepseek-ai/cordis';
8
8
  import type { CapabilityCatalogSnapshot, CatalogAddSkillRequest, CatalogAddSkillResult, CatalogCredentialSetRequest, CatalogDeleteSkillResult, CatalogDirSkillInfo, CatalogJsonValue, CatalogSkillDetail, CatalogSkillFileRead, CatalogToolRow } from './types.ts';
9
- import { type CredentialsSlice, type RegistrySlice, type SkillDefinitionLike } from './skills.ts';
9
+ import { type CredentialsSlice, type RegistrySlice, type SkillDefinitionLike, type SkillsCollectionReport } from './skills.ts';
10
10
  import type { ToolOrigin } from './tool-origin.ts';
11
11
  import { resolveSkillNameFromContent } from './import.ts';
12
- export type { CredentialsSlice, RegistrySlice };
12
+ export type { CredentialsSlice, RegistrySlice, SkillsCollectionReport };
13
13
  /** Project visible tools onto catalog rows with channel attribution. */
14
14
  export declare function projectTools(schemas: readonly ToolSchemaLike[], mcpServers: readonly string[], appearedAfterApply: Set<string>, toolOrigins?: ReadonlyMap<string, ToolOrigin>): CatalogToolRow[];
15
15
  /** Minimal ToolSchema shape (model-facing name/description/parameters). */
@@ -18,6 +18,14 @@ interface ToolSchemaLike {
18
18
  readonly description?: string;
19
19
  readonly parameters?: CatalogJsonValue;
20
20
  }
21
+ /**
22
+ * Out-bag a snapshot caller may pass to receive the collection health the
23
+ * rows were gathered with. Evidence production reads it to build the
24
+ * per-section completeness descriptors; the Remote verbs never pass one.
25
+ */
26
+ export interface CatalogSnapshotReport {
27
+ skills?: SkillsCollectionReport;
28
+ }
21
29
  /**
22
30
  * Build the full catalog snapshot (skills + tools).
23
31
  *
@@ -29,6 +37,7 @@ interface ToolSchemaLike {
29
37
  export declare function catalogSnapshot(workdir: string | undefined, registry: RegistrySlice, toolsSchemas: readonly ToolSchemaLike[], mcpServers: readonly string[], appearedAfterApply: ReadonlySet<string>, scopes?: readonly unknown[], toolOrigins?: ReadonlyMap<string, ToolOrigin>, options?: {
30
38
  fingerprint?: boolean;
31
39
  preset?: string;
40
+ report?: CatalogSnapshotReport;
32
41
  }): Promise<CapabilityCatalogSnapshot>;
33
42
  /** Load one skill detail. */
34
43
  export declare function catalogDetail(ctx: Context, registry: RegistrySlice, name: string, workdir: string | undefined, scope?: unknown, fallback?: SkillDefinitionLike): Promise<CatalogSkillDetail | undefined>;
@@ -41,7 +41,10 @@ export function projectTools(schemas, mcpServers, appearedAfterApply, toolOrigin
41
41
  */
42
42
  export async function catalogSnapshot(workdir, registry, toolsSchemas, mcpServers, appearedAfterApply, scopes = [undefined], toolOrigins = new Map(), options = {}) {
43
43
  const fingerprint = options.fingerprint === true;
44
- const base = await collectSkills(registry, workdir, scopes, { bodySha: fingerprint });
44
+ const skillsReport = { attemptedScopes: 0, failedScopes: 0, incompleteReads: 0, unreadableBodies: 0 };
45
+ const base = await collectSkills(registry, workdir, scopes, { bodySha: fingerprint, report: skillsReport });
46
+ if (options.report !== undefined)
47
+ options.report.skills = skillsReport;
45
48
  const tools = projectTools(toolsSchemas, mcpServers, appearedAfterApply, toolOrigins);
46
49
  const snapshot = {
47
50
  skills: base.skills,
@@ -57,6 +57,8 @@ export interface ManagedSkill {
57
57
  readonly userInvocable: boolean;
58
58
  /** Preset ids this skill is delivered to; absent/empty means "not scoped". */
59
59
  readonly presetScope: readonly string[] | undefined;
60
+ /** The frontmatter's full `metadata:` block, preserved verbatim when parseable. */
61
+ readonly metadata?: Readonly<Record<string, unknown>>;
60
62
  /** The skill's own directory (its `resourceBase`). */
61
63
  readonly dir: string;
62
64
  /** Absolute `SKILL.md` path. */
@@ -115,31 +117,15 @@ export interface ScopedDeliveryDeps {
115
117
  export interface ScopedDeliveryRegistry {
116
118
  readonly registerProvider?: (create: (control: SkillProviderControl) => SkillProvider) => () => void;
117
119
  }
118
- /**
119
- * Read the `presetScope` list out of a frontmatter block.
120
- *
121
- * Accepted forms, at any indentation (the canonical one is
122
- * `metadata.presetScope`):
123
- *
124
- * ```yaml
125
- * metadata:
126
- * presetScope: [dsh-writing, dev]
127
- * presetScope:
128
- * - dsh-writing
129
- * ```
130
- * @param front - the frontmatter text without its `---` fences.
131
- * @returns the declared ids, or undefined when the key is absent.
132
- */
133
- export declare function parsePresetScopeFrontmatter(front: string): readonly string[] | undefined;
134
- /** Whether a string is a usable skill / preset id. */
135
- export declare function isKebabId(value: string): boolean;
136
- /** Split a SKILL.md into its frontmatter block and body. */
137
- export declare function splitFrontmatter(content: string): {
138
- front: string;
139
- body: string;
140
- } | undefined;
141
120
  /**
142
121
  * Parse one managed `SKILL.md` into its delivery record.
122
+ *
123
+ * The frontmatter is ONE real-YAML parse (see `./frontmatter.ts`): name and
124
+ * description read top-level only, the invocation flags accept the host
125
+ * loader's boolean variants, and the scope list comes from the canonical
126
+ * `metadata.presetScope` (falling back to a legacy top-level key). A malformed
127
+ * `metadata:` block degrades to no-metadata — the skill still delivers —
128
+ * rather than rejecting a file the rest of the block could still serve.
143
129
  * @param content - the file text.
144
130
  * @param dir - the skill's directory (its resource base).
145
131
  * @param path - the absolute `SKILL.md` path.
@@ -194,6 +180,9 @@ export declare class ScopedSkillDelivery {
194
180
  private failure;
195
181
  private started;
196
182
  private disposed;
183
+ /** Reconcile gate: one pass in flight, one queued rerun (see reconcile). */
184
+ private reconcileInFlight;
185
+ private reconcileQueued;
197
186
  /** The probed registry's mount list (undefined until start()'s probe resolves). */
198
187
  private presetMounts;
199
188
  /**
@@ -203,8 +192,22 @@ export declare class ScopedSkillDelivery {
203
192
  constructor(ctx: Context, deps: ScopedDeliveryDeps);
204
193
  /** Start watching the managed root and deliver what it currently holds. */
205
194
  start(): Promise<void>;
206
- /** Re-read policy and files, then converge the delivery registrations. */
195
+ /**
196
+ * Re-read policy and files, then converge the delivery registrations.
197
+ *
198
+ * Triggers arrive concurrently — the filesystem watcher, the
199
+ * `agent-preset/selected` event, and the management Remotes — and two
200
+ * overlapping passes can both decide a preset has no registration and each
201
+ * register one, leaving two same-named providers serving one preset. The
202
+ * gate allows ONE pass in flight and coalesces every trigger that arrives
203
+ * during it into ONE queued rerun (the pass re-reads policy and files from
204
+ * scratch, so a rerun loses no update). A caller that finds a pass in
205
+ * flight waits for the chain its flag provoked, so an awaited reconcile
206
+ * still reflects the write that triggered it.
207
+ */
207
208
  reconcile(): Promise<void>;
209
+ /** One reconcile pass (call only through {@link reconcile}'s gate). */
210
+ private reconcileNow;
208
211
  /** The current status, for the settings surface and the model-facing tool. */
209
212
  status(): ScopedDeliveryStatus;
210
213
  /** Stop watching and dispose every delivery registration. */
@@ -233,6 +236,13 @@ export declare class ScopedSkillDelivery {
233
236
  private providerFor;
234
237
  /** The managed skills one delivery currently serves. */
235
238
  private skillsFor;
239
+ /** The managed skills readable through one preset's reader: its own scoped
240
+ * delivery plus the unscoped ones every preset sees through the global layer. */
241
+ private readableFor;
242
+ /** The read-only bundle-file reader served with one preset's delivery. */
243
+ private bundleFileTool;
244
+ /** Serve one bundle file (or the bundle's file list) to a delivered skill's caller. */
245
+ private readBundleFile;
236
246
  /** Invalidate every registration's completed catalogs. */
237
247
  private invalidateAll;
238
248
  /** Dispose every registration. */
@@ -240,4 +250,6 @@ export declare class ScopedSkillDelivery {
240
250
  /** Dispose one registration and forget it. */
241
251
  private dropEntry;
242
252
  }
253
+ /** The scoped bundle-file reader's tool name. */
254
+ export declare const READ_SKILL_FILE_TOOL = "read_skill_file";
243
255
  //# sourceMappingURL=scoped-delivery.d.ts.map