@khorsheed/dsh-capability-catalog 0.1.95
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +286 -0
- package/README.zh.md +138 -0
- package/cordis.patch.yml +8 -0
- package/lib/client.js +10100 -0
- package/lib/index.js +4022 -0
- package/lib/invariant.js +25 -0
- package/lib/mcpConnector-C2TbkPBY.js +69 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1417 -0
- package/lib/typert.remote-client.d.ts +68 -0
- package/lib/typert.remote-client.js +1136 -0
- package/lib/types/capabilities.d.ts +103 -0
- package/lib/types/capabilities.js +116 -0
- package/lib/types/channels.d.ts +41 -0
- package/lib/types/channels.js +75 -0
- package/lib/types/client/AddMcpDialog.d.ts +13 -0
- package/lib/types/client/AddMcpDialog.js +79 -0
- package/lib/types/client/AddSkillModal.d.ts +17 -0
- package/lib/types/client/AddSkillModal.js +138 -0
- package/lib/types/client/BundleFileTree.d.ts +7 -0
- package/lib/types/client/BundleFileTree.js +74 -0
- package/lib/types/client/CapabilityCatalogCard.d.ts +57 -0
- package/lib/types/client/CapabilityCatalogCard.js +479 -0
- package/lib/types/client/CredentialField.d.ts +14 -0
- package/lib/types/client/CredentialField.js +9 -0
- package/lib/types/client/McpCards.d.ts +13 -0
- package/lib/types/client/McpCards.js +12 -0
- package/lib/types/client/McpServerManageModal.d.ts +18 -0
- package/lib/types/client/McpServerManageModal.js +63 -0
- package/lib/types/client/MetadataRow.d.ts +10 -0
- package/lib/types/client/MetadataRow.js +6 -0
- package/lib/types/client/ModalShell.d.ts +16 -0
- package/lib/types/client/ModalShell.js +27 -0
- package/lib/types/client/ModeChips.d.ts +38 -0
- package/lib/types/client/ModeChips.js +42 -0
- package/lib/types/client/SchemaView.d.ts +11 -0
- package/lib/types/client/SchemaView.js +143 -0
- package/lib/types/client/SkillCards.d.ts +28 -0
- package/lib/types/client/SkillCards.js +43 -0
- package/lib/types/client/SkillDetailModal.d.ts +69 -0
- package/lib/types/client/SkillDetailModal.js +167 -0
- package/lib/types/client/ToolCards.d.ts +28 -0
- package/lib/types/client/ToolCards.js +52 -0
- package/lib/types/client/ToolDetailModal.d.ts +12 -0
- package/lib/types/client/ToolDetailModal.js +29 -0
- package/lib/types/client/icons.d.ts +26 -0
- package/lib/types/client/icons.js +10 -0
- package/lib/types/client/index.d.ts +21 -0
- package/lib/types/client/index.js +196 -0
- package/lib/types/client/locales.d.ts +392 -0
- package/lib/types/client/locales.js +395 -0
- package/lib/types/client/mcp-model.d.ts +24 -0
- package/lib/types/client/mcp-model.js +87 -0
- package/lib/types/client/mode-model.d.ts +108 -0
- package/lib/types/client/mode-model.js +140 -0
- package/lib/types/client/preset-display.d.ts +21 -0
- package/lib/types/client/preset-display.js +20 -0
- package/lib/types/client/settle.d.ts +40 -0
- package/lib/types/client/settle.js +57 -0
- package/lib/types/client/slots.d.ts +86 -0
- package/lib/types/client/slots.js +1 -0
- package/lib/types/envHint.d.ts +22 -0
- package/lib/types/envHint.js +76 -0
- package/lib/types/import.d.ts +106 -0
- package/lib/types/import.js +635 -0
- package/lib/types/index.d.ts +216 -0
- package/lib/types/index.js +739 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +23 -0
- package/lib/types/mcpConnector.d.ts +24 -0
- package/lib/types/mcpConnector.js +73 -0
- package/lib/types/mcpStore.d.ts +68 -0
- package/lib/types/mcpStore.js +235 -0
- package/lib/types/mcpTools.d.ts +45 -0
- package/lib/types/mcpTools.js +125 -0
- package/lib/types/mcps.d.ts +64 -0
- package/lib/types/mcps.js +157 -0
- package/lib/types/modes.d.ts +65 -0
- package/lib/types/modes.js +97 -0
- package/lib/types/namespace.d.ts +9 -0
- package/lib/types/namespace.js +8 -0
- package/lib/types/official-tools.d.ts +3 -0
- package/lib/types/official-tools.js +44 -0
- package/lib/types/preset-scope.d.ts +105 -0
- package/lib/types/preset-scope.js +117 -0
- package/lib/types/remote.d.ts +48 -0
- package/lib/types/remote.js +120 -0
- package/lib/types/scoped-delivery.d.ts +243 -0
- package/lib/types/scoped-delivery.js +686 -0
- package/lib/types/scoped-edits.d.ts +90 -0
- package/lib/types/scoped-edits.js +234 -0
- package/lib/types/settings.d.ts +28 -0
- package/lib/types/settings.js +28 -0
- package/lib/types/shellEnv.d.ts +65 -0
- package/lib/types/shellEnv.js +232 -0
- package/lib/types/skills.d.ts +103 -0
- package/lib/types/skills.js +303 -0
- package/lib/types/tool-origin.d.ts +29 -0
- package/lib/types/tool-origin.js +30 -0
- package/lib/types/types.d.ts +315 -0
- package/lib/types/types.js +8 -0
- package/lib/types/zip.d.ts +24 -0
- package/lib/types/zip.js +80 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +119 -0
- package/scripts/gen-official-tools.mts +52 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server management: parse a pasted mcp.json snippet into a normalized
|
|
3
|
+
* `CatalogMcpServerConfig`, keep the configured servers (this process — the
|
|
4
|
+
* first version carries them in memory; persistence to the settings namespace
|
|
5
|
+
* + credential store lands with the connection layer), and extract declared
|
|
6
|
+
* credentials so the UI can ask for them once and never show the plaintext.
|
|
7
|
+
*
|
|
8
|
+
* The actual connection + tool discovery is a pluggable seam: `connect` is a
|
|
9
|
+
* stub in v1 (tools list fills once we bind the dsh-mcp-client connection or
|
|
10
|
+
* the catalog's own thin client).
|
|
11
|
+
* @module @khorsheed/dsh-capability-catalog/mcps
|
|
12
|
+
*/
|
|
13
|
+
import type { CatalogJsonValue, CatalogMcpCredentialDecl, CatalogMcpServerConfig, CatalogMcpTool, McpTransport } from './types.ts';
|
|
14
|
+
/** Secret marker put in a config field whose value lives in the credential store. */
|
|
15
|
+
export declare const SECRET_REF_PREFIX = "secretRef:";
|
|
16
|
+
/** A parsed server from a pasted mcp.json server entry. */
|
|
17
|
+
export interface ParsedMcpServer {
|
|
18
|
+
readonly serverName: string;
|
|
19
|
+
readonly config: CatalogMcpServerConfig;
|
|
20
|
+
/** Credential declarations to prompt for (label → config reference path). */
|
|
21
|
+
readonly credentials: readonly {
|
|
22
|
+
readonly ref: string;
|
|
23
|
+
readonly label: string;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
/** Detect transport from a raw server entry shape. */
|
|
27
|
+
export declare function detectTransport(entry: Record<string, unknown>): McpTransport;
|
|
28
|
+
/**
|
|
29
|
+
* Parse one MCP server entry (a `mcpServers` value) into a normalized config,
|
|
30
|
+
* extracting credential references. The pasted payload may be the whole
|
|
31
|
+
* `mcpServers` object (we take a single entry — the Add-MCP flow is one server
|
|
32
|
+
* per add) or a bare server entry.
|
|
33
|
+
* @param serverName - the server key.
|
|
34
|
+
* @param entry - the server's raw config value.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseServerEntry(serverName: string, entry: Record<string, unknown>): ParsedMcpServer;
|
|
37
|
+
/** Flag a config field secret in the read-only display: `secretRef:x → ·secretRef·`,
|
|
38
|
+
* preserving any scheme prefix (e.g. `Bearer ·secretRef·`). */
|
|
39
|
+
export declare function maskSecret(value: string): string;
|
|
40
|
+
/** Mask a server config for the wire (the Remote DTO): replace every `secretRef:`
|
|
41
|
+
* marker (bare or scheme-prefixed) with `·secretRef·`, so neither the secret key
|
|
42
|
+
* name nor the marker is exposed to the browser. */
|
|
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
|
+
/** Resolve `secretRef:` markers in a config into plaintext before connecting
|
|
47
|
+
* (the catalog resolves via the stored credential values, then connects).
|
|
48
|
+
* Replaces embedded markers too, so a scheme-prefixed value (`Bearer secretRef:a`)
|
|
49
|
+
* resolves to `Bearer <value>`. */
|
|
50
|
+
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
|
+
/**
|
|
54
|
+
* Map each bare secret config key (env/header name, or a query param) to its
|
|
55
|
+
* catalog-stored, namespaced credential ref (`mcp.<server>.<kind>.<key>`). The
|
|
56
|
+
* host store namespaces refs by server so multiple servers with the same env
|
|
57
|
+
* key don't collide. Mirrors the store's `credentialRefsOf` scan — the client
|
|
58
|
+
* needs the same mapping to write values for a freshly-added server before the
|
|
59
|
+
* next snapshot round-trips the namespaced refs.
|
|
60
|
+
*/
|
|
61
|
+
export declare function credentialStoredRefs(serverName: string, config: CatalogMcpServerConfig): Readonly<Record<string, string>>;
|
|
62
|
+
export { SECRET_REF_PREFIX as _SECRET_REF_PREFIX };
|
|
63
|
+
export type { CatalogJsonValue };
|
|
64
|
+
//# sourceMappingURL=mcps.d.ts.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server management: parse a pasted mcp.json snippet into a normalized
|
|
3
|
+
* `CatalogMcpServerConfig`, keep the configured servers (this process — the
|
|
4
|
+
* first version carries them in memory; persistence to the settings namespace
|
|
5
|
+
* + credential store lands with the connection layer), and extract declared
|
|
6
|
+
* credentials so the UI can ask for them once and never show the plaintext.
|
|
7
|
+
*
|
|
8
|
+
* The actual connection + tool discovery is a pluggable seam: `connect` is a
|
|
9
|
+
* stub in v1 (tools list fills once we bind the dsh-mcp-client connection or
|
|
10
|
+
* the catalog's own thin client).
|
|
11
|
+
* @module @khorsheed/dsh-capability-catalog/mcps
|
|
12
|
+
*/
|
|
13
|
+
/** Secret marker put in a config field whose value lives in the credential store. */
|
|
14
|
+
export const SECRET_REF_PREFIX = 'secretRef:';
|
|
15
|
+
/** Values that look like a credential to prompt for (env/header/query key). */
|
|
16
|
+
const CREDENTIALISH = /(KEY|TOKEN|SECRET|PASSWORD|API_KEY|ACCESS?_?KEY|AUTH)/i;
|
|
17
|
+
/** Find every `secretRef:<token>` marker in a config value (scheme-prefixed or bare). */
|
|
18
|
+
const SECRET_REF_RE = /secretRef:([A-Za-z0-9_.-]+)/g;
|
|
19
|
+
/** Detect transport from a raw server entry shape. */
|
|
20
|
+
export function detectTransport(entry) {
|
|
21
|
+
if (typeof entry['command'] === 'string')
|
|
22
|
+
return 'stdio';
|
|
23
|
+
return 'streamable-http';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse one MCP server entry (a `mcpServers` value) into a normalized config,
|
|
27
|
+
* extracting credential references. The pasted payload may be the whole
|
|
28
|
+
* `mcpServers` object (we take a single entry — the Add-MCP flow is one server
|
|
29
|
+
* per add) or a bare server entry.
|
|
30
|
+
* @param serverName - the server key.
|
|
31
|
+
* @param entry - the server's raw config value.
|
|
32
|
+
*/
|
|
33
|
+
export function parseServerEntry(serverName, entry) {
|
|
34
|
+
const transport = detectTransport(entry);
|
|
35
|
+
const credentials = [];
|
|
36
|
+
const env = [];
|
|
37
|
+
const headers = [];
|
|
38
|
+
const extract = (value, ref, label) => {
|
|
39
|
+
if (typeof value !== 'string' || value === '')
|
|
40
|
+
return value ?? '';
|
|
41
|
+
if (CREDENTIALISH.test(label)) {
|
|
42
|
+
credentials.push({ ref, label });
|
|
43
|
+
// Preserve a scheme prefix (Bearer/Basic/Token) so the header stays valid;
|
|
44
|
+
// only the token is replaced by the secret marker.
|
|
45
|
+
const scheme = /^(Bearer|Basic|Token)\s+/i.exec(value);
|
|
46
|
+
return scheme !== null ? `${scheme[0]}${SECRET_REF_PREFIX}${ref}` : `${SECRET_REF_PREFIX}${ref}`;
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
};
|
|
50
|
+
if (transport === 'stdio') {
|
|
51
|
+
const cmd = typeof entry['command'] === 'string' ? entry['command'] : undefined;
|
|
52
|
+
const args = Array.isArray(entry['args'])
|
|
53
|
+
? entry['args'].filter((a) => typeof a === 'string')
|
|
54
|
+
: undefined;
|
|
55
|
+
const cwd = typeof entry['cwd'] === 'string' ? entry['cwd'] : undefined;
|
|
56
|
+
const rawEnv = (entry['env'] ?? {});
|
|
57
|
+
for (const [k, v] of Object.entries(rawEnv))
|
|
58
|
+
env.push([k, extract(v, k, k)]);
|
|
59
|
+
return {
|
|
60
|
+
serverName,
|
|
61
|
+
config: { serverName, transport, ...(cmd !== undefined ? { command: cmd } : {}), ...(args !== undefined ? { args } : {}), ...(cwd !== undefined ? { cwd } : {}), ...(env.length > 0 ? { env } : {}), enabled: false },
|
|
62
|
+
credentials,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const url = typeof entry['url'] === 'string' ? entry['url'] : '';
|
|
66
|
+
const rawHeaders = (entry['headers'] ?? {});
|
|
67
|
+
for (const [k, v] of Object.entries(rawHeaders))
|
|
68
|
+
headers.push([k, extract(v, k, k)]);
|
|
69
|
+
// Query-string secret (e.g. `?key=`). Keep a ref marker in the URL.
|
|
70
|
+
return {
|
|
71
|
+
serverName,
|
|
72
|
+
config: { serverName, transport, url, ...(headers.length > 0 ? { headers } : {}), enabled: false },
|
|
73
|
+
credentials,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Flag a config field secret in the read-only display: `secretRef:x → ·secretRef·`,
|
|
77
|
+
* preserving any scheme prefix (e.g. `Bearer ·secretRef·`). */
|
|
78
|
+
export function maskSecret(value) {
|
|
79
|
+
return value.replace(SECRET_REF_RE, '·secretRef·');
|
|
80
|
+
}
|
|
81
|
+
/** Mask a server config for the wire (the Remote DTO): replace every `secretRef:`
|
|
82
|
+
* marker (bare or scheme-prefixed) with `·secretRef·`, so neither the secret key
|
|
83
|
+
* name nor the marker is exposed to the browser. */
|
|
84
|
+
export function maskConfig(config) {
|
|
85
|
+
const env = (config.env ?? []).map(([k, v]) => [k, maskSecret(v)]);
|
|
86
|
+
const headers = (config.headers ?? []).map(([k, v]) => [k, maskSecret(v)]);
|
|
87
|
+
const url = config.url !== undefined ? maskSecret(config.url) : undefined;
|
|
88
|
+
return {
|
|
89
|
+
...config,
|
|
90
|
+
...(env.length > 0 ? { env } : {}),
|
|
91
|
+
...(headers.length > 0 ? { headers } : {}),
|
|
92
|
+
...(url !== undefined ? { url } : {}),
|
|
93
|
+
};
|
|
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
|
+
/** Resolve `secretRef:` markers in a config into plaintext before connecting
|
|
100
|
+
* (the catalog resolves via the stored credential values, then connects).
|
|
101
|
+
* Replaces embedded markers too, so a scheme-prefixed value (`Bearer secretRef:a`)
|
|
102
|
+
* resolves to `Bearer <value>`. */
|
|
103
|
+
export async function resolveConfigSecrets(config, resolve) {
|
|
104
|
+
const resolveValue = async (value) => {
|
|
105
|
+
let out = value;
|
|
106
|
+
for (const m of value.matchAll(SECRET_REF_RE)) {
|
|
107
|
+
const ref = m[1];
|
|
108
|
+
const resolvedValue = (await resolve(ref)) ?? '';
|
|
109
|
+
out = out.replace(`secretRef:${ref}`, resolvedValue);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
};
|
|
113
|
+
const env = [];
|
|
114
|
+
for (const [k, v] of config.env ?? [])
|
|
115
|
+
env.push([k, await resolveValue(v)]);
|
|
116
|
+
const headers = [];
|
|
117
|
+
for (const [k, v] of config.headers ?? [])
|
|
118
|
+
headers.push([k, await resolveValue(v)]);
|
|
119
|
+
const url = config.url !== undefined ? await resolveValue(config.url) : undefined;
|
|
120
|
+
return {
|
|
121
|
+
...config,
|
|
122
|
+
...(env.length > 0 ? { env } : {}),
|
|
123
|
+
...(headers.length > 0 ? { headers } : {}),
|
|
124
|
+
...(url !== undefined ? { url } : {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
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
|
+
/**
|
|
132
|
+
* Map each bare secret config key (env/header name, or a query param) to its
|
|
133
|
+
* catalog-stored, namespaced credential ref (`mcp.<server>.<kind>.<key>`). The
|
|
134
|
+
* host store namespaces refs by server so multiple servers with the same env
|
|
135
|
+
* key don't collide. Mirrors the store's `credentialRefsOf` scan — the client
|
|
136
|
+
* needs the same mapping to write values for a freshly-added server before the
|
|
137
|
+
* next snapshot round-trips the namespaced refs.
|
|
138
|
+
*/
|
|
139
|
+
export function credentialStoredRefs(serverName, config) {
|
|
140
|
+
const out = {};
|
|
141
|
+
const isSecret = (v) => /secretRef:/.test(v);
|
|
142
|
+
for (const [k, v] of config.env ?? []) {
|
|
143
|
+
if (isSecret(v))
|
|
144
|
+
out[k] = `mcp.${serverName}.env.${k}`;
|
|
145
|
+
}
|
|
146
|
+
for (const [k, v] of config.headers ?? []) {
|
|
147
|
+
if (isSecret(v))
|
|
148
|
+
out[k] = `mcp.${serverName}.header.${k}`;
|
|
149
|
+
}
|
|
150
|
+
if (config.url !== undefined) {
|
|
151
|
+
for (const m of config.url.matchAll(/secretRef:([A-Za-z0-9_.-]+)/g)) {
|
|
152
|
+
out[m[1]] = `mcp.${serverName}.query.${m[1]}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
export { SECRET_REF_PREFIX as _SECRET_REF_PREFIX }; // re-export for tests if needed
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MODES: reading the catalog through each agent preset.
|
|
3
|
+
*
|
|
4
|
+
* A dsh instance composes every session from an agent preset — 「模式」 in the
|
|
5
|
+
* settings copy, `preset` in the code: the same deployment can run a full
|
|
6
|
+
* coding agent, a minimal single-tool one, or a user-authored preset, and each
|
|
7
|
+
* of those sees a DIFFERENT set of skills and tools. The catalog's default read
|
|
8
|
+
* answers for one of them (the deployment default). "Which of these does this
|
|
9
|
+
* mode actually load, and which modes load it?" is a different question, and
|
|
10
|
+
* this module is the whole answer to it.
|
|
11
|
+
*
|
|
12
|
+
* Two shapes travel:
|
|
13
|
+
*
|
|
14
|
+
* - a mode ROSTER ({@link modeOptions}) — the presets a viewer offers, with the
|
|
15
|
+
* deployment default marked so a picker can label one 「标准模式(默认)」;
|
|
16
|
+
* - a mode FACE ({@link readModeFaces}) — what one mode's standing scope
|
|
17
|
+
* actually registers, read through the same `resolvePresetScope` +
|
|
18
|
+
* `catalogSnapshot` path the default read uses, so a mode view and the
|
|
19
|
+
* default view cannot drift apart.
|
|
20
|
+
*
|
|
21
|
+
* Reading a mode is NOT free: resolving a preset's standing scope MOUNTS its
|
|
22
|
+
* composition (the roster's single-flight standing mount), so a face read for a
|
|
23
|
+
* mode nothing has composed yet composes it. That is the price of the only
|
|
24
|
+
* honest answer — a composition file says which plugins a mode NAMES, not which
|
|
25
|
+
* tools and skills those plugins register once they run — and it is why the
|
|
26
|
+
* settings surface asks for faces only when a human chooses to compare modes.
|
|
27
|
+
*
|
|
28
|
+
* Both functions are pure over their inputs (the caller supplies the roster rows
|
|
29
|
+
* and the face reader), so their unit tests need no Cordis context.
|
|
30
|
+
* @module @khorsheed/dsh-capability-catalog/modes
|
|
31
|
+
*/
|
|
32
|
+
import type { CapabilityCatalogSnapshot, CatalogModeFace, CatalogPresetOption } from './types.ts';
|
|
33
|
+
import type { PresetRosterRow } from './preset-scope.ts';
|
|
34
|
+
/**
|
|
35
|
+
* The mode roster a viewer offers: the agent-preset rows, path-free, with the
|
|
36
|
+
* deployment default marked.
|
|
37
|
+
*
|
|
38
|
+
* Roster order is preserved — the roster already orders shipped presets by the
|
|
39
|
+
* `order` they publish — because a picker that re-sorts hands the human a
|
|
40
|
+
* different order every time the roster grows.
|
|
41
|
+
* @param rows - the roster rows (`agentPresets.list()`).
|
|
42
|
+
* @param defaultId - the preset a session naming none composes, when known.
|
|
43
|
+
* @returns the options a mode picker renders, in roster order.
|
|
44
|
+
*/
|
|
45
|
+
export declare function modeOptions(rows: readonly PresetRosterRow[], defaultId: string | undefined): readonly CatalogPresetOption[];
|
|
46
|
+
/**
|
|
47
|
+
* Read one face per mode, in roster order.
|
|
48
|
+
*
|
|
49
|
+
* A BROKEN preset is not read at all: discovery already refused it, and asking
|
|
50
|
+
* the roster to mount it would spend a mount attempt on a composition known to
|
|
51
|
+
* be unusable. It keeps its row with `unavailable` set, so a comparison can say
|
|
52
|
+
* which mode is missing instead of silently showing an empty face — "this mode
|
|
53
|
+
* has no tools" and "this mode could not be read" are different claims, and a
|
|
54
|
+
* viewer that conflates them is worse than one that omits the mode.
|
|
55
|
+
*
|
|
56
|
+
* A reader answering `undefined` means the same thing (its scope did not
|
|
57
|
+
* resolve, so the answer would be the global layer wearing a mode's name).
|
|
58
|
+
* Faces are read SEQUENTIALLY: each read may start a mount, and walking the
|
|
59
|
+
* roster's single-flight mount in order is cheaper than racing it.
|
|
60
|
+
* @param options - the mode roster (from {@link modeOptions}).
|
|
61
|
+
* @param readFace - read one mode's face; undefined when its scope refuses.
|
|
62
|
+
* @returns one face per option, roster order preserved.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readModeFaces(options: readonly CatalogPresetOption[], readFace: (presetId: string) => Promise<CapabilityCatalogSnapshot | undefined>): Promise<readonly CatalogModeFace[]>;
|
|
65
|
+
//# sourceMappingURL=modes.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MODES: reading the catalog through each agent preset.
|
|
3
|
+
*
|
|
4
|
+
* A dsh instance composes every session from an agent preset — 「模式」 in the
|
|
5
|
+
* settings copy, `preset` in the code: the same deployment can run a full
|
|
6
|
+
* coding agent, a minimal single-tool one, or a user-authored preset, and each
|
|
7
|
+
* of those sees a DIFFERENT set of skills and tools. The catalog's default read
|
|
8
|
+
* answers for one of them (the deployment default). "Which of these does this
|
|
9
|
+
* mode actually load, and which modes load it?" is a different question, and
|
|
10
|
+
* this module is the whole answer to it.
|
|
11
|
+
*
|
|
12
|
+
* Two shapes travel:
|
|
13
|
+
*
|
|
14
|
+
* - a mode ROSTER ({@link modeOptions}) — the presets a viewer offers, with the
|
|
15
|
+
* deployment default marked so a picker can label one 「标准模式(默认)」;
|
|
16
|
+
* - a mode FACE ({@link readModeFaces}) — what one mode's standing scope
|
|
17
|
+
* actually registers, read through the same `resolvePresetScope` +
|
|
18
|
+
* `catalogSnapshot` path the default read uses, so a mode view and the
|
|
19
|
+
* default view cannot drift apart.
|
|
20
|
+
*
|
|
21
|
+
* Reading a mode is NOT free: resolving a preset's standing scope MOUNTS its
|
|
22
|
+
* composition (the roster's single-flight standing mount), so a face read for a
|
|
23
|
+
* mode nothing has composed yet composes it. That is the price of the only
|
|
24
|
+
* honest answer — a composition file says which plugins a mode NAMES, not which
|
|
25
|
+
* tools and skills those plugins register once they run — and it is why the
|
|
26
|
+
* settings surface asks for faces only when a human chooses to compare modes.
|
|
27
|
+
*
|
|
28
|
+
* Both functions are pure over their inputs (the caller supplies the roster rows
|
|
29
|
+
* and the face reader), so their unit tests need no Cordis context.
|
|
30
|
+
* @module @khorsheed/dsh-capability-catalog/modes
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The mode roster a viewer offers: the agent-preset rows, path-free, with the
|
|
34
|
+
* deployment default marked.
|
|
35
|
+
*
|
|
36
|
+
* Roster order is preserved — the roster already orders shipped presets by the
|
|
37
|
+
* `order` they publish — because a picker that re-sorts hands the human a
|
|
38
|
+
* different order every time the roster grows.
|
|
39
|
+
* @param rows - the roster rows (`agentPresets.list()`).
|
|
40
|
+
* @param defaultId - the preset a session naming none composes, when known.
|
|
41
|
+
* @returns the options a mode picker renders, in roster order.
|
|
42
|
+
*/
|
|
43
|
+
export function modeOptions(rows, defaultId) {
|
|
44
|
+
return rows.map(row => ({
|
|
45
|
+
id: row.id,
|
|
46
|
+
...row.name === undefined ? {} : { name: row.name },
|
|
47
|
+
...row.description === undefined ? {} : { description: row.description },
|
|
48
|
+
...row.broken === undefined ? {} : { broken: row.broken },
|
|
49
|
+
...row.id === defaultId ? { isDefault: true } : {},
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read one face per mode, in roster order.
|
|
54
|
+
*
|
|
55
|
+
* A BROKEN preset is not read at all: discovery already refused it, and asking
|
|
56
|
+
* the roster to mount it would spend a mount attempt on a composition known to
|
|
57
|
+
* be unusable. It keeps its row with `unavailable` set, so a comparison can say
|
|
58
|
+
* which mode is missing instead of silently showing an empty face — "this mode
|
|
59
|
+
* has no tools" and "this mode could not be read" are different claims, and a
|
|
60
|
+
* viewer that conflates them is worse than one that omits the mode.
|
|
61
|
+
*
|
|
62
|
+
* A reader answering `undefined` means the same thing (its scope did not
|
|
63
|
+
* resolve, so the answer would be the global layer wearing a mode's name).
|
|
64
|
+
* Faces are read SEQUENTIALLY: each read may start a mount, and walking the
|
|
65
|
+
* roster's single-flight mount in order is cheaper than racing it.
|
|
66
|
+
* @param options - the mode roster (from {@link modeOptions}).
|
|
67
|
+
* @param readFace - read one mode's face; undefined when its scope refuses.
|
|
68
|
+
* @returns one face per option, roster order preserved.
|
|
69
|
+
*/
|
|
70
|
+
export async function readModeFaces(options, readFace) {
|
|
71
|
+
const faces = [];
|
|
72
|
+
for (const option of options) {
|
|
73
|
+
const identity = {
|
|
74
|
+
preset: option.id,
|
|
75
|
+
...option.name === undefined ? {} : { name: option.name },
|
|
76
|
+
...option.description === undefined ? {} : { description: option.description },
|
|
77
|
+
isDefault: option.isDefault === true,
|
|
78
|
+
};
|
|
79
|
+
if (option.broken !== undefined) {
|
|
80
|
+
faces.push({ ...identity, skills: [], tools: [], unavailable: option.broken });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
let face;
|
|
84
|
+
try {
|
|
85
|
+
face = await readFace(option.id);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// The reader owns its own reporting; a throw here must still leave the
|
|
89
|
+
// other modes comparable.
|
|
90
|
+
face = undefined;
|
|
91
|
+
}
|
|
92
|
+
faces.push(face === undefined
|
|
93
|
+
? { ...identity, skills: [], tools: [], unavailable: 'the preset resolved no standing scope' }
|
|
94
|
+
: { ...identity, skills: face.skills, tools: face.tools });
|
|
95
|
+
}
|
|
96
|
+
return faces;
|
|
97
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
/** Settings namespace owned by the capability-catalog plugin. */
|
|
8
|
+
export declare const CAPABILITY_CATALOG_NS = "capability-catalog";
|
|
9
|
+
//# sourceMappingURL=namespace.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
/** Settings namespace owned by the capability-catalog plugin. */
|
|
8
|
+
export const CAPABILITY_CATALOG_NS = 'capability-catalog';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Generated from the pinned harness checkout — do not edit by hand. Run scripts/gen-official-tools.mts. */
|
|
2
|
+
export const OFFICIAL_TOOLS = [
|
|
3
|
+
"ask_user_question",
|
|
4
|
+
"bash",
|
|
5
|
+
"cordis_define",
|
|
6
|
+
"cordis_inspect_list",
|
|
7
|
+
"cordis_inspect_query",
|
|
8
|
+
"cordis_inspect_self",
|
|
9
|
+
"cordis_run",
|
|
10
|
+
"cordis_stop",
|
|
11
|
+
"cordis_undefine",
|
|
12
|
+
"create_goal",
|
|
13
|
+
"edit",
|
|
14
|
+
"get_goal",
|
|
15
|
+
"interrupt_agent",
|
|
16
|
+
"job_kill",
|
|
17
|
+
"job_list",
|
|
18
|
+
"job_output",
|
|
19
|
+
"list_agents",
|
|
20
|
+
"list_subagent_models",
|
|
21
|
+
"lsp",
|
|
22
|
+
"pwsh",
|
|
23
|
+
"ralph",
|
|
24
|
+
"read",
|
|
25
|
+
"read_image",
|
|
26
|
+
"send_message",
|
|
27
|
+
"session_event_read",
|
|
28
|
+
"session_event_search",
|
|
29
|
+
"session_event_trace",
|
|
30
|
+
"session_search",
|
|
31
|
+
"session_trace",
|
|
32
|
+
"str_replace_editor",
|
|
33
|
+
"terminal_close",
|
|
34
|
+
"terminal_list",
|
|
35
|
+
"terminal_open",
|
|
36
|
+
"terminal_read",
|
|
37
|
+
"terminal_send",
|
|
38
|
+
"terminal_signal",
|
|
39
|
+
"todo_write",
|
|
40
|
+
"update_goal",
|
|
41
|
+
"web_fetch",
|
|
42
|
+
"web_search",
|
|
43
|
+
"write"
|
|
44
|
+
];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving WHICH scope the catalog reads — and the one place where a listing
|
|
3
|
+
* and a fingerprint part company.
|
|
4
|
+
*
|
|
5
|
+
* The catalog reads the skill and tool registries at an agent preset's
|
|
6
|
+
* standing scope, because that is the view the model actually has. Resolving
|
|
7
|
+
* it can fail: the composition may mount no roster, the named preset may not
|
|
8
|
+
* exist, or its composition may refuse to mount.
|
|
9
|
+
*
|
|
10
|
+
* The roster face depends on the host line: 0.1.5 exposes a lease-free
|
|
11
|
+
* `standingKeyFor(id)`, while rc.1 replaced it with a LEASED
|
|
12
|
+
* `acquireScope(id)` — the resolved lease carries the key and a release on
|
|
13
|
+
* `Symbol.asyncDispose`, and a lease never released leaks a generation user,
|
|
14
|
+
* so the preset's scope is never collected. Both faces resolve through
|
|
15
|
+
* {@link acquireStandingScope}; callers that hold the key across reads own
|
|
16
|
+
* the release (see {@link ResolvedPresetScope.dispose}).
|
|
17
|
+
*
|
|
18
|
+
* A LISTING degrades on all three. The settings card must not go blank
|
|
19
|
+
* because a preset has a bad row, and the global layer is a useful, honestly
|
|
20
|
+
* unlabelled answer there.
|
|
21
|
+
*
|
|
22
|
+
* A FINGERPRINT must not. Falling back to the global layer and stamping the
|
|
23
|
+
* requested preset's name on the result hands back a hash that is not that
|
|
24
|
+
* preset's face — which is precisely the "claim with nothing behind it" the
|
|
25
|
+
* hash exists to eliminate. It was not hypothetical: on a real sub-dsh whose
|
|
26
|
+
* preset carried one invalid row, two scopes rostering two different presets
|
|
27
|
+
* produced one identical hash, silently, and the only way to notice was to
|
|
28
|
+
* already know the answer.
|
|
29
|
+
* @module @khorsheed/dsh-capability-catalog/preset-scope
|
|
30
|
+
*/
|
|
31
|
+
/** The slice of the agent-preset roster the catalog reads. */
|
|
32
|
+
export interface PresetRosterSlice {
|
|
33
|
+
readonly defaultId?: string;
|
|
34
|
+
/** The 0.1.5 face: a lease-free read of one preset's standing scope key. */
|
|
35
|
+
readonly standingKeyFor?: (id?: string) => Promise<unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* The rc.1 face: a LEASED read resolving to `{ key } & AsyncDisposable`.
|
|
38
|
+
* An unknown preset rejects `agent-preset/not-found`, a broken one
|
|
39
|
+
* `agent-preset/invalid`. Typed as unknown: the key is opaque to the
|
|
40
|
+
* catalog, and `Symbol.asyncDispose` is untyped under the es2024 target —
|
|
41
|
+
* {@link acquireStandingScope} narrows both structurally.
|
|
42
|
+
*/
|
|
43
|
+
readonly acquireScope?: (id?: string) => Promise<unknown>;
|
|
44
|
+
/** Every preset the deployment supplies, for a scope picker. */
|
|
45
|
+
readonly list?: () => Promise<readonly PresetRosterRow[]>;
|
|
46
|
+
}
|
|
47
|
+
/** One roster row, structurally narrowed to what a picker needs. */
|
|
48
|
+
export interface PresetRosterRow {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly name?: string;
|
|
51
|
+
readonly description?: string;
|
|
52
|
+
/** Why this preset cannot compose a session, when it cannot. */
|
|
53
|
+
readonly broken?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A standing-scope read, normalized across roster faces: the key, and the
|
|
57
|
+
* release when the face leases. The key outlives the release — the roster
|
|
58
|
+
* reaps a generation only once its preset is unregistered AND unleased — so
|
|
59
|
+
* a reader that finished its reads may release without unmounting the scope.
|
|
60
|
+
*/
|
|
61
|
+
export interface StandingScopeRead {
|
|
62
|
+
/** The standing scope key, or undefined when the roster resolved none. */
|
|
63
|
+
readonly key: unknown | undefined;
|
|
64
|
+
/** Releases the rc.1 lease; undefined on the lease-free 0.1.5 face. */
|
|
65
|
+
readonly dispose?: () => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/** A resolved reading position: the scope key, and the preset it is honestly labelled with. */
|
|
68
|
+
export interface ResolvedPresetScope {
|
|
69
|
+
/** The standing scope key, or undefined for the global layer. */
|
|
70
|
+
readonly scope: unknown | undefined;
|
|
71
|
+
/** The preset id — set ONLY when a standing scope actually resolved. */
|
|
72
|
+
readonly preset: string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Releases the scope lease, when the roster leases (rc.1). Undefined on the
|
|
75
|
+
* 0.1.5 face, which carries no lease. A caller holding `scope` across reads
|
|
76
|
+
* must dispose in a `finally` after the LAST read — never releasing leaks a
|
|
77
|
+
* generation user, so the preset's scope is never collected.
|
|
78
|
+
*/
|
|
79
|
+
readonly dispose?: () => Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Acquire one preset's standing scope key from whichever face the roster
|
|
83
|
+
* offers — 0.1.5's lease-free `standingKeyFor` first, rc.1's leased
|
|
84
|
+
* `acquireScope` when that is absent. Errors propagate unchanged, so the
|
|
85
|
+
* caller owns the strict/degrade policy; the caller also owns the release.
|
|
86
|
+
* @param roster - the agent-preset roster (already probed present).
|
|
87
|
+
* @param id - the preset to read, or undefined for the deployment default.
|
|
88
|
+
* @returns the key and the lease release, if the face leases.
|
|
89
|
+
*/
|
|
90
|
+
export declare function acquireStandingScope(roster: PresetRosterSlice, id: string | undefined): Promise<StandingScopeRead>;
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the catalog's reading scope.
|
|
93
|
+
*
|
|
94
|
+
* Resolving a preset MOUNTS it (the roster's single-flight standing mount),
|
|
95
|
+
* so this is not a free read: asking for the capability face of a preset
|
|
96
|
+
* nothing has composed yet composes it.
|
|
97
|
+
* @param roster - the optional agent-preset roster (`ctx.get('agentPresets')`).
|
|
98
|
+
* @param presetId - the preset to read, or undefined for the deployment default.
|
|
99
|
+
* @param strict - fingerprint mode: throw rather than degrade to the global layer.
|
|
100
|
+
* @returns the scope to read at and the label it may carry.
|
|
101
|
+
* @throws in strict mode when the roster is absent for a named preset, or
|
|
102
|
+
* cannot resolve a standing scope.
|
|
103
|
+
*/
|
|
104
|
+
export declare function resolvePresetScope(roster: PresetRosterSlice | undefined, presetId: string | undefined, strict: boolean): Promise<ResolvedPresetScope>;
|
|
105
|
+
//# sourceMappingURL=preset-scope.d.ts.map
|