@intentic/extension-manifest 1.312.0 → 1.314.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +5 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +38 -3
- package/dist/manifest.js.map +1 -1
- package/dist/meaning.d.ts +21 -0
- package/dist/meaning.d.ts.map +1 -0
- package/dist/meaning.js +127 -0
- package/dist/meaning.js.map +1 -0
- package/dist/points/agent.d.ts +1 -1
- package/dist/points/agent.d.ts.map +1 -1
- package/dist/points/agent.js +3 -2
- package/dist/points/agent.js.map +1 -1
- package/dist/points/bin.d.ts.map +1 -1
- package/dist/points/bin.js +2 -1
- package/dist/points/bin.js.map +1 -1
- package/dist/points/capabilities.d.ts +3 -0
- package/dist/points/capabilities.d.ts.map +1 -1
- package/dist/points/capabilities.js +51 -10
- package/dist/points/capabilities.js.map +1 -1
- package/dist/points/commands.d.ts.map +1 -1
- package/dist/points/commands.js +4 -2
- package/dist/points/commands.js.map +1 -1
- package/dist/points/documents.d.ts.map +1 -1
- package/dist/points/documents.js +2 -1
- package/dist/points/documents.js.map +1 -1
- package/dist/points/environment.d.ts.map +1 -1
- package/dist/points/environment.js +2 -1
- package/dist/points/environment.js.map +1 -1
- package/dist/points/files.d.ts.map +1 -1
- package/dist/points/files.js +2 -1
- package/dist/points/files.js.map +1 -1
- package/dist/points/index.d.ts +15 -1
- package/dist/points/index.d.ts.map +1 -1
- package/dist/points/index.js +3 -0
- package/dist/points/index.js.map +1 -1
- package/dist/points/listener.d.ts.map +1 -1
- package/dist/points/listener.js +2 -1
- package/dist/points/listener.js.map +1 -1
- package/dist/points/processes.d.ts.map +1 -1
- package/dist/points/processes.js +2 -1
- package/dist/points/processes.js.map +1 -1
- package/dist/points/settings.d.ts.map +1 -1
- package/dist/points/settings.js +2 -1
- package/dist/points/settings.js.map +1 -1
- package/dist/points/tools.d.ts +18 -0
- package/dist/points/tools.d.ts.map +1 -0
- package/dist/points/tools.js +31 -0
- package/dist/points/tools.js.map +1 -0
- package/dist/points/viewers.d.ts.map +1 -1
- package/dist/points/viewers.js +2 -1
- package/dist/points/viewers.js.map +1 -1
- package/dist/points/views.d.ts.map +1 -1
- package/dist/points/views.js +4 -2
- package/dist/points/views.js.map +1 -1
- package/dist/powers-diff.d.ts +1 -1
- package/dist/powers-diff.d.ts.map +1 -1
- package/dist/powers-diff.js +6 -56
- package/dist/powers-diff.js.map +1 -1
- package/dist/tool-servers.d.ts +19 -0
- package/dist/tool-servers.d.ts.map +1 -0
- package/dist/tool-servers.js +21 -0
- package/dist/tool-servers.js.map +1 -0
- package/intentic-extension.schema.json +120 -8
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/manifest.ts +40 -3
- package/src/meaning.ts +189 -0
- package/src/points/agent.ts +5 -3
- package/src/points/bin.ts +2 -1
- package/src/points/capabilities.ts +68 -20
- package/src/points/commands.ts +4 -2
- package/src/points/documents.ts +2 -1
- package/src/points/environment.ts +2 -1
- package/src/points/files.ts +2 -1
- package/src/points/index.ts +3 -0
- package/src/points/listener.ts +2 -1
- package/src/points/processes.ts +2 -1
- package/src/points/settings.ts +2 -1
- package/src/points/tools.ts +48 -0
- package/src/points/viewers.ts +2 -1
- package/src/points/views.ts +4 -2
- package/src/powers-diff.ts +9 -60
- package/src/tool-servers.ts +50 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { evaluateWhen, isWhenExpression, parseWhen } from "@intentic/base/when";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { ContributionPoint } from "../contribution-point.js";
|
|
4
|
+
import { TOOL_PATH } from "./tools.js";
|
|
4
5
|
import { MARK_FIELDS } from "../mark.js";
|
|
6
|
+
import { type EffectMeaning, fieldMeanings, unwrapMeaning } from "../meaning.js";
|
|
5
7
|
|
|
6
8
|
// A field the install dialog's config form renders (key, label, secret/optional flags, a select, a `when` gate);
|
|
7
9
|
// mirrors the platform catalog's own field shape.
|
|
@@ -167,7 +169,8 @@ export const CapabilityContributionSchema = z
|
|
|
167
169
|
.string()
|
|
168
170
|
.min(1)
|
|
169
171
|
.optional()
|
|
170
|
-
.describe("A Dockerfile fragment holding the client binary this tool needs (psql, mysql, whisper).")
|
|
172
|
+
.describe("A Dockerfile fragment holding the client binary this tool needs (psql, mysql, whisper).")
|
|
173
|
+
.meta({ effect: "image" }),
|
|
171
174
|
// Prefer over `fragment` whenever the sandbox already ships a pack for the tool: an image that bakes the
|
|
172
175
|
// pack needs no rebuild, and the overlay can't drift from it.
|
|
173
176
|
pack: z
|
|
@@ -176,20 +179,25 @@ export const CapabilityContributionSchema = z
|
|
|
176
179
|
.optional()
|
|
177
180
|
.describe(
|
|
178
181
|
"A sandbox feature pack name (whisper, llamacpp, browser, …) supplying this tool. Preferred over `fragment`: an image that already bakes the pack needs no rebuild, and there is no copy to drift.",
|
|
179
|
-
)
|
|
182
|
+
)
|
|
183
|
+
.meta({ effect: "image" }),
|
|
180
184
|
probe: ProbeSchema.optional().describe(
|
|
181
185
|
"One authenticated request that tests this card's settings before they are saved, so a wrong token or an unreachable host is answered on the form rather than by a card that says 'not connected' afterwards.",
|
|
182
186
|
),
|
|
183
|
-
//
|
|
184
|
-
//
|
|
187
|
+
// The first way a card served tools, kept one release as an alias of `contributes.tools` with this card as
|
|
188
|
+
// `perCard` and this value as `path` (points/tools.ts); a manifest declaring both is served by `tools`.
|
|
185
189
|
mcp: z
|
|
186
190
|
.string()
|
|
187
|
-
.regex(
|
|
188
|
-
.refine((value) => !value.endsWith("/"), { message: "mcp must not end in a slash" })
|
|
191
|
+
.regex(TOOL_PATH)
|
|
189
192
|
.optional()
|
|
190
193
|
.describe(
|
|
191
|
-
"A path in this extension's backend (`server`) answering MCP over Streamable HTTP
|
|
192
|
-
)
|
|
194
|
+
"Deprecated: declare `contributes.tools` with `perCard` naming this card instead, and serve the tools with `api.tools.serve`. A path in this extension's backend (`server`) answering MCP over Streamable HTTP; every turn granted a card of this kind gets it as a server named by the card's id, each request arriving at `<path>/<card id>`.",
|
|
195
|
+
)
|
|
196
|
+
.meta({
|
|
197
|
+
effect: "mcp",
|
|
198
|
+
mintsServer: true,
|
|
199
|
+
power: { key: "capability-tools:${id}", sentence: 'serves MCP tools to the agent for each "${catalog.name}" card' },
|
|
200
|
+
}),
|
|
193
201
|
}),
|
|
194
202
|
// A site the agent acts on as the owner through the shared browser; `loginUrl`/`homeUrl` are both optional so a
|
|
195
203
|
// card can be the generic one that asks for them on its form instead.
|
|
@@ -217,20 +225,24 @@ export const CapabilityContributionSchema = z
|
|
|
217
225
|
}),
|
|
218
226
|
// An operating system a connected computer can run; enrollment, socket and scope enforcement are core, only the
|
|
219
227
|
// skill pack varies.
|
|
220
|
-
z
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
z
|
|
229
|
+
.object({
|
|
230
|
+
...contributionBase,
|
|
231
|
+
kind: z.literal("device"),
|
|
232
|
+
skill: z.string().min(1).describe("Checkout-relative SKILL.md teaching the agent that machine's shell."),
|
|
233
|
+
})
|
|
234
|
+
.meta({ mintsServer: true }),
|
|
225
235
|
// A browser family the user connects their own copy of; `install` is a URL since each family has its own store
|
|
226
236
|
// or none at all. Omitted until that family's listing exists: the connect dialog then shows no link, which is
|
|
227
237
|
// better than a store URL that quietly lands on the store's front page.
|
|
228
|
-
z
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
z
|
|
239
|
+
.object({
|
|
240
|
+
...contributionBase,
|
|
241
|
+
kind: z.literal("webext"),
|
|
242
|
+
install: z.url().optional().describe("Where this browser's extension is installed from: its store listing, or a page offering the build."),
|
|
243
|
+
skill: z.string().min(1).describe("Checkout-relative SKILL.md teaching the agent to drive this browser."),
|
|
244
|
+
})
|
|
245
|
+
.meta({ mintsServer: true }),
|
|
234
246
|
// A preset over a core kind: no payload, just a name, a logo and a filled-in form.
|
|
235
247
|
z.object({ ...contributionBase, kind: z.literal("agent") }),
|
|
236
248
|
])
|
|
@@ -248,7 +260,8 @@ export const CapabilityContributionSchema = z
|
|
|
248
260
|
});
|
|
249
261
|
}
|
|
250
262
|
}
|
|
251
|
-
})
|
|
263
|
+
})
|
|
264
|
+
.meta({ power: { key: "capability:${id}", sentence: 'a ${kind} capability card "${catalog.name}"' } });
|
|
252
265
|
export type CapabilityContribution = z.infer<typeof CapabilityContributionSchema>;
|
|
253
266
|
// Arms carrying a per-instance SKILL.md, templated and installed identically by the daemon.
|
|
254
267
|
export type SkillContribution = Extract<CapabilityContribution, { skill: string }>;
|
|
@@ -281,3 +294,38 @@ export const capabilitiesPoint = {
|
|
|
281
294
|
'Capability cards this pack adds to the "+" grid: a connected CLI tool, a site the agent acts on as the owner through the shared browser, an operating system pack, a browser family the owner connects their own copy of, or a preset over a core kind. The card and its form are data here; the machinery that acts on them is core, which is why a card may only name one of these five kinds.',
|
|
282
295
|
schema: z.array(CapabilityContributionSchema),
|
|
283
296
|
} as const satisfies ContributionPoint;
|
|
297
|
+
|
|
298
|
+
// Every contributable kind a card of which registers an `mcp__<card id>__` server: the arm itself says so (a device, a
|
|
299
|
+
// connected browser), or one of its fields may (a cli card's `mcp`; `contributes.tools` can name only a cli card as
|
|
300
|
+
// `perCard`). Read off the schema's meanings, so a kind that starts minting names itself here.
|
|
301
|
+
export const contributedServerMintingKinds = (): ReadonlySet<CapabilityContribution["kind"]> => {
|
|
302
|
+
const kinds = new Set<CapabilityContribution["kind"]>();
|
|
303
|
+
for (const option of CapabilityContributionSchema.options) {
|
|
304
|
+
const shape = option.shape as Record<string, z.ZodType>;
|
|
305
|
+
const kind = (shape["kind"]?._zod.def as { values?: readonly string[] } | undefined)?.values?.[0] as CapabilityContribution["kind"] | undefined;
|
|
306
|
+
if (kind === undefined) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (unwrapMeaning(option).meaning.mintsServer === true || [...fieldMeanings(option).values()].some((meaning) => meaning.mintsServer === true)) {
|
|
310
|
+
kinds.add(kind);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return kinds;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// What the fields a card sets add to the sandbox, as effect kinds: an image rebuild for a fragment or a pack, an MCP
|
|
317
|
+
// server for a card that serves tools.
|
|
318
|
+
export const contributionEffectsOf = (contribution: CapabilityContribution): ReadonlySet<EffectMeaning> => {
|
|
319
|
+
const effects = new Set<EffectMeaning>();
|
|
320
|
+
const option = CapabilityContributionSchema.options.find((candidate) => candidate.shape.kind.value === contribution.kind);
|
|
321
|
+
if (option === undefined) {
|
|
322
|
+
return effects;
|
|
323
|
+
}
|
|
324
|
+
for (const [key, meaning] of fieldMeanings(option)) {
|
|
325
|
+
const value = (contribution as Record<string, unknown>)[key];
|
|
326
|
+
if (meaning.effect !== undefined && value !== undefined && value !== false) {
|
|
327
|
+
effects.add(meaning.effect);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return effects;
|
|
331
|
+
};
|
package/src/points/commands.ts
CHANGED
|
@@ -22,7 +22,8 @@ export const CommandContributionSchema = z.object({
|
|
|
22
22
|
.optional()
|
|
23
23
|
.describe(
|
|
24
24
|
'A global keyboard shortcut, e.g. "Mod+Shift+K" — `Mod` is ⌘ on Apple and Ctrl elsewhere. Declared here because a global shortcut is consequential: the owner approves it at install, and the host binds only what was approved.',
|
|
25
|
-
)
|
|
25
|
+
)
|
|
26
|
+
.meta({ power: { key: "keybinding:${command}", sentence: 'the global shortcut ${keybinding} ("${title}")' } }),
|
|
26
27
|
// When the keybinding applies, as a condition over the shell's context keys; the palette ignores it since a command
|
|
27
28
|
// is always runnable by name. Without one the chord is claimed everywhere, including inside a terminal.
|
|
28
29
|
when: z
|
|
@@ -32,7 +33,8 @@ export const CommandContributionSchema = z.object({
|
|
|
32
33
|
.describe(
|
|
33
34
|
"When the shortcut applies, as a condition over the shell's context keys, `tabSurface == 'chat'`, `!editableTarget`. Without one the chord is claimed everywhere, including inside a terminal where a bare key belongs to the program running in it. The command palette ignores this: a command is always runnable by name.",
|
|
34
35
|
),
|
|
35
|
-
})
|
|
36
|
+
})
|
|
37
|
+
.meta({ power: { key: "command:${command}", sentence: 'a palette command "${title}"' } });
|
|
36
38
|
export type CommandContribution = z.infer<typeof CommandContributionSchema>;
|
|
37
39
|
|
|
38
40
|
export const commandsPoint = {
|
package/src/points/documents.ts
CHANGED
|
@@ -13,7 +13,8 @@ export const DocumentContributionSchema = z.object({
|
|
|
13
13
|
.describe(
|
|
14
14
|
"The family's name, shown in the install dialog beside your other contributions. Per-row wording stays with the provider, which is the only thing that knows what it found.",
|
|
15
15
|
),
|
|
16
|
-
})
|
|
16
|
+
})
|
|
17
|
+
.meta({ power: { key: "document:${id}", sentence: 'marks workspace directories ("${label}")' } });
|
|
17
18
|
export type DocumentContribution = z.infer<typeof DocumentContributionSchema>;
|
|
18
19
|
|
|
19
20
|
export const documentsPoint = {
|
|
@@ -14,7 +14,8 @@ export const EnvironmentContributionSchema = z.object({
|
|
|
14
14
|
.describe(
|
|
15
15
|
"Checkout-relative path to a file holding ONLY RUN and ENV instructions. FROM and privileged directives are rejected: those stay daemon-owned.",
|
|
16
16
|
),
|
|
17
|
-
})
|
|
17
|
+
})
|
|
18
|
+
.meta({ effect: "image", power: { key: "environment", sentence: "bakes an environment fragment into the sandbox image" } });
|
|
18
19
|
export type EnvironmentContribution = z.infer<typeof EnvironmentContributionSchema>;
|
|
19
20
|
|
|
20
21
|
export const environmentPoint = {
|
package/src/points/files.ts
CHANGED
|
@@ -24,7 +24,8 @@ export const FileContributionSchema = z.object({
|
|
|
24
24
|
.describe(
|
|
25
25
|
"The query keys this path makes stale, the first element of your own api.sandbox.key(...) keys. Keep both this and the path as narrow as the view actually needs: a broad prefix costs every connected browser a refetch on every matching write.",
|
|
26
26
|
),
|
|
27
|
-
})
|
|
27
|
+
})
|
|
28
|
+
.meta({ power: { key: "files:${path}", sentence: 'is told when ${path} changes' } });
|
|
28
29
|
export type FileContribution = z.infer<typeof FileContributionSchema>;
|
|
29
30
|
|
|
30
31
|
export const filesPoint = {
|
package/src/points/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { filesPoint } from "./files.js";
|
|
|
11
11
|
import { listenerPoint } from "./listener.js";
|
|
12
12
|
import { processesPoint } from "./processes.js";
|
|
13
13
|
import { settingsPoint } from "./settings.js";
|
|
14
|
+
import { toolsPoint } from "./tools.js";
|
|
14
15
|
import { viewersPoint } from "./viewers.js";
|
|
15
16
|
import { viewsPoint } from "./views.js";
|
|
16
17
|
|
|
@@ -25,6 +26,7 @@ export * from "./files.js";
|
|
|
25
26
|
export * from "./listener.js";
|
|
26
27
|
export * from "./processes.js";
|
|
27
28
|
export * from "./settings.js";
|
|
29
|
+
export * from "./tools.js";
|
|
28
30
|
export * from "./viewers.js";
|
|
29
31
|
export * from "./views.js";
|
|
30
32
|
|
|
@@ -45,6 +47,7 @@ export const CONTRIBUTION_POINTS = [
|
|
|
45
47
|
listenerPoint,
|
|
46
48
|
automationTemplatesPoint,
|
|
47
49
|
binPoint,
|
|
50
|
+
toolsPoint,
|
|
48
51
|
] as const satisfies readonly ContributionPoint[];
|
|
49
52
|
|
|
50
53
|
// The `contributes` shape those points assemble to. A mapped type rather than a widened record, so
|
package/src/points/listener.ts
CHANGED
|
@@ -66,7 +66,8 @@ export const ListenerContributionSchema = z.object({
|
|
|
66
66
|
),
|
|
67
67
|
})
|
|
68
68
|
.describe("How the generic automation editor presents this source: its name, its filters, and the prompt it starts people on."),
|
|
69
|
-
})
|
|
69
|
+
})
|
|
70
|
+
.meta({ power: { key: "listener:${provider}", sentence: 'a realtime listener provider "${provider}"' } });
|
|
70
71
|
export type ListenerContribution = z.infer<typeof ListenerContributionSchema>;
|
|
71
72
|
|
|
72
73
|
export const listenerPoint = {
|
package/src/points/processes.ts
CHANGED
|
@@ -9,7 +9,8 @@ export const ProcessContributionSchema = z.object({
|
|
|
9
9
|
port: z.literal("auto").optional().describe("Assign a free port and inject it as PORT."),
|
|
10
10
|
preview: z.boolean().optional().describe("Expose the port on a tunnelled preview hostname."),
|
|
11
11
|
autoStart: z.boolean().optional().describe("Launch it on install and on daemon boot, rather than waiting to be started."),
|
|
12
|
-
})
|
|
12
|
+
})
|
|
13
|
+
.meta({ effect: "process", power: { key: "process:${name}", sentence: 'a background process "${name}"${autoStart? (starts on boot):}' } });
|
|
13
14
|
export type ProcessContribution = z.infer<typeof ProcessContributionSchema>;
|
|
14
15
|
|
|
15
16
|
export const processesPoint = {
|
package/src/points/settings.ts
CHANGED
|
@@ -19,7 +19,8 @@ export const SettingContributionSchema = z.object({
|
|
|
19
19
|
.optional()
|
|
20
20
|
.describe(
|
|
21
21
|
"Inject the stored value into the agent's shell environment under this name, every turn. How a credential you hold reaches the agent's command-line tools.",
|
|
22
|
-
)
|
|
22
|
+
)
|
|
23
|
+
.meta({ power: { key: "setting-env:${key}", sentence: 'puts the "${key}" setting into the agent\'s environment as ${env}' } }),
|
|
23
24
|
});
|
|
24
25
|
export type SettingContribution = z.infer<typeof SettingContributionSchema>;
|
|
25
26
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ContributionPoint } from "../contribution-point.js";
|
|
3
|
+
|
|
4
|
+
// The one way an extension gives the agent tools: an MCP server the daemon mounts into every turn at its own door
|
|
5
|
+
// (`/mcp/<name>`), whichever runtime serves the turn. A plugin's `.mcp.json` reached only Claude Code and spawned a
|
|
6
|
+
// process per session; this is served once, for every session.
|
|
7
|
+
|
|
8
|
+
// A path inside the extension's namespace or on a process's port: segments of [a-z0-9-], each starting alphanumeric,
|
|
9
|
+
// joined by single slashes, with none leading or trailing. The regex says all of it, so the authoring schema does too.
|
|
10
|
+
export const TOOL_PATH = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/;
|
|
11
|
+
|
|
12
|
+
export const ToolsContributionSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
perCard: z
|
|
15
|
+
.string()
|
|
16
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe(
|
|
19
|
+
"The id of one of this extension's `cli` capability cards. Every turn granted a card of that kind gets one server named by the card's id, handed that card's settings (secrets included) with each call. Absent ⇒ one server for the extension, named by its `name`, in every turn while the extension is enabled.",
|
|
20
|
+
),
|
|
21
|
+
process: z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe(
|
|
26
|
+
'A process from `contributes.processes`, declared with `port: "auto"`, that answers MCP over Streamable HTTP at `path` on its port. Absent ⇒ your `server` bundle serves the tools.',
|
|
27
|
+
),
|
|
28
|
+
path: z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(TOOL_PATH)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(
|
|
33
|
+
"Where the MCP endpoint answers, without a leading or trailing slash: on the process's port, or in your backend's own namespace when your `server` bundle speaks MCP itself. Absent with no `process` ⇒ the host serves what `api.tools.serve` returns, which is what you want: the host owns the transport, the deadlines and the card lookup. With `perCard`, a request arrives at `<path>/<card id>`.",
|
|
34
|
+
),
|
|
35
|
+
})
|
|
36
|
+
.meta({
|
|
37
|
+
effect: "mcp",
|
|
38
|
+
mintsServer: true,
|
|
39
|
+
power: { key: "tools${perCard?-${perCard}:}", sentence: 'gives the agent MCP tools${perCard?, one server for each "${perCard}" card:}' },
|
|
40
|
+
});
|
|
41
|
+
export type ToolsContribution = z.infer<typeof ToolsContributionSchema>;
|
|
42
|
+
|
|
43
|
+
export const toolsPoint = {
|
|
44
|
+
name: "tools",
|
|
45
|
+
description:
|
|
46
|
+
"Tools for the agent, as an MCP server the daemon mounts into every turn and every runtime (Claude Code, Codex, Cursor, ACP agents). Serve them from your `server` bundle with `api.tools.serve((card) => [...])`, or from a declared process's port. Replaces an agent plugin's `.mcp.json`, which only Claude Code read.",
|
|
47
|
+
schema: ToolsContributionSchema,
|
|
48
|
+
} as const satisfies ContributionPoint;
|
package/src/points/viewers.ts
CHANGED
|
@@ -39,7 +39,8 @@ export const ViewerContributionSchema = z.object({
|
|
|
39
39
|
.describe(
|
|
40
40
|
"Whether this viewer also draws two versions of a file as one, with what changed marked in place: its registration then carries a `compare` component the host renders with `before` and `after` blobs. Only for `fetch: \"blob\"`.",
|
|
41
41
|
),
|
|
42
|
-
})
|
|
42
|
+
})
|
|
43
|
+
.meta({ power: { key: "viewer:${id}", sentence: '${edit?opens and edits:opens}${compare? and compares:} .${extensions|, .} files (${fetch})' } });
|
|
43
44
|
export type ViewerContribution = z.infer<typeof ViewerContributionSchema>;
|
|
44
45
|
|
|
45
46
|
export const viewersPoint = {
|
package/src/points/views.ts
CHANGED
|
@@ -22,8 +22,10 @@ export const ViewContributionSchema = z.object({
|
|
|
22
22
|
.optional()
|
|
23
23
|
.describe(
|
|
24
24
|
"Allow this view to say something on its tile: a count, a glyph, or that work is running there. Declared because a badge interrupts from every other screen in the app; leave it out and any badge the extension registers is dropped.",
|
|
25
|
-
)
|
|
26
|
-
})
|
|
25
|
+
)
|
|
26
|
+
.meta({ power: { key: "view-badge:${id}", sentence: 'may badge the "${label}" tile from any screen' } }),
|
|
27
|
+
})
|
|
28
|
+
.meta({ power: { key: "view:${id}", sentence: 'a ${surface} view "${label}"' } });
|
|
27
29
|
export type ViewContribution = z.infer<typeof ViewContributionSchema>;
|
|
28
30
|
|
|
29
31
|
export const viewsPoint = {
|
package/src/powers-diff.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ExtensionManifest, ExtensionManifestSchema } from "./manifest.js";
|
|
2
|
+
import { fillTemplate, walkMeaning } from "./meaning.js";
|
|
2
3
|
|
|
3
4
|
// What an update asks for, mechanically. Each manifest folds to a set of POWERS, the consequential facts an owner
|
|
4
5
|
// approved, under a stable key (compared) with a plain sentence (shown). The diff is set arithmetic over the keys.
|
|
@@ -12,68 +13,16 @@ export interface PowersDiff {
|
|
|
12
13
|
readonly unchanged: string[];
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// The fold itself: every power a manifest declares, its stable key to the sentence shown for it.
|
|
16
|
+
// The fold itself: every power a manifest declares, its stable key to the sentence shown for it. Read off the schema:
|
|
17
|
+
// each field that grants a power says so in its `.meta({ power })` (meaning.ts), so a field added to the manifest is
|
|
18
|
+
// either declared a power where it is defined or is not one.
|
|
19
19
|
export const powersOf = (manifest: ExtensionManifest): Map<string, string> => {
|
|
20
20
|
const powers = new Map<string, string>();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (manifest.server !== undefined) {
|
|
25
|
-
powers.set("server", "runs a backend bundle inside the daemon's extension host");
|
|
26
|
-
}
|
|
27
|
-
for (const route of manifest.permissions?.sandbox ?? []) {
|
|
28
|
-
powers.set(`sandbox:${route}`, `its UI calls the sandbox route ${route}`);
|
|
29
|
-
}
|
|
30
|
-
for (const route of manifest.permissions?.daemon ?? []) {
|
|
31
|
-
powers.set(`daemon:${route}`, `its backend calls the daemon route ${route}`);
|
|
32
|
-
}
|
|
33
|
-
for (const view of manifest.contributes?.views ?? []) {
|
|
34
|
-
powers.set(`view:${view.id}`, `a ${view.surface} view "${view.label}"`);
|
|
35
|
-
if (view.badge === true) {
|
|
36
|
-
powers.set(`view-badge:${view.id}`, `may badge the "${view.label}" tile from any screen`);
|
|
21
|
+
walkMeaning(ExtensionManifestSchema, manifest, (meaning, context) => {
|
|
22
|
+
if (meaning.power !== undefined) {
|
|
23
|
+
powers.set(fillTemplate(meaning.power.key, context), fillTemplate(meaning.power.sentence, context));
|
|
37
24
|
}
|
|
38
|
-
}
|
|
39
|
-
for (const viewer of manifest.contributes?.viewers ?? []) {
|
|
40
|
-
powers.set(`viewer:${viewer.id}`, viewerPower(viewer));
|
|
41
|
-
}
|
|
42
|
-
for (const document of manifest.contributes?.documents ?? []) {
|
|
43
|
-
powers.set(`document:${document.id}`, `marks workspace directories ("${document.label}")`);
|
|
44
|
-
}
|
|
45
|
-
for (const command of manifest.contributes?.commands ?? []) {
|
|
46
|
-
powers.set(`command:${command.command}`, `a palette command "${command.title}"`);
|
|
47
|
-
if (command.keybinding !== undefined) {
|
|
48
|
-
powers.set(`keybinding:${command.command}`, `the global shortcut ${command.keybinding} ("${command.title}")`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
for (const setting of manifest.contributes?.settings ?? []) {
|
|
52
|
-
if (setting.env !== undefined) {
|
|
53
|
-
powers.set(`setting-env:${setting.key}`, `puts the "${setting.key}" setting into the agent's environment as ${setting.env}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
for (const process of manifest.contributes?.processes ?? []) {
|
|
57
|
-
powers.set(`process:${process.name}`, `a background process "${process.name}"${process.autoStart === true ? " (starts on boot)" : ""}`);
|
|
58
|
-
}
|
|
59
|
-
for (const file of manifest.contributes?.files ?? []) {
|
|
60
|
-
powers.set(`files:${file.path}`, `is told when ${file.path} changes`);
|
|
61
|
-
}
|
|
62
|
-
if (manifest.contributes?.agent !== undefined) {
|
|
63
|
-
powers.set("agent", "contributes skills, agents and hooks to the agent's turns");
|
|
64
|
-
}
|
|
65
|
-
if (manifest.contributes?.environment !== undefined) {
|
|
66
|
-
powers.set("environment", "bakes an environment fragment into the sandbox image");
|
|
67
|
-
}
|
|
68
|
-
for (const capability of manifest.contributes?.capabilities ?? []) {
|
|
69
|
-
powers.set(`capability:${capability.id}`, `a ${capability.kind} capability card "${capability.catalog.name}"`);
|
|
70
|
-
}
|
|
71
|
-
if (manifest.contributes?.listener !== undefined) {
|
|
72
|
-
powers.set(`listener:${manifest.contributes.listener.provider}`, `a realtime listener provider "${manifest.contributes.listener.provider}"`);
|
|
73
|
-
}
|
|
74
|
-
if (manifest.contributes?.bin !== undefined) {
|
|
75
|
-
powers.set("bin", "puts its shipped tools on the agent's PATH");
|
|
76
|
-
}
|
|
25
|
+
});
|
|
77
26
|
return powers;
|
|
78
27
|
};
|
|
79
28
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ExtensionManifest } from "./manifest.js";
|
|
2
|
+
|
|
3
|
+
// Every MCP server one manifest serves the agent, whichever way it declares it: `contributes.tools`, or a cli card's
|
|
4
|
+
// `mcp` (the alias kept one release, points/capabilities.ts). One reading, so the daemon's mounts, the /x refusal and
|
|
5
|
+
// the effects a card discloses cannot disagree about what a manifest serves.
|
|
6
|
+
|
|
7
|
+
export type ToolServe =
|
|
8
|
+
// The host serves what the backend's `api.tools.serve` returns; nothing in the namespace answers MCP itself.
|
|
9
|
+
| { readonly by: "host" }
|
|
10
|
+
// The backend answers Streamable HTTP itself, at this path in its /x namespace.
|
|
11
|
+
| { readonly by: "backend"; readonly path: string }
|
|
12
|
+
// A declared process answers it on its port, at this path.
|
|
13
|
+
| { readonly by: "process"; readonly process: string; readonly path: string };
|
|
14
|
+
|
|
15
|
+
export interface ToolServer {
|
|
16
|
+
// The cli card kind whose every granted card gets a server named by the card's id; absent, one server for the
|
|
17
|
+
// extension named by its `name`.
|
|
18
|
+
readonly perCard?: string;
|
|
19
|
+
readonly serve: ToolServe;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const toolServersOf = (manifest: ExtensionManifest): ToolServer[] => {
|
|
23
|
+
const servers: ToolServer[] = [];
|
|
24
|
+
const tools = manifest.contributes?.tools;
|
|
25
|
+
if (tools !== undefined) {
|
|
26
|
+
const serve: ToolServe =
|
|
27
|
+
tools.process !== undefined
|
|
28
|
+
? { by: "process", process: tools.process, path: tools.path ?? "" }
|
|
29
|
+
: tools.path !== undefined
|
|
30
|
+
? { by: "backend", path: tools.path }
|
|
31
|
+
: { by: "host" };
|
|
32
|
+
servers.push({ ...(tools.perCard === undefined ? {} : { perCard: tools.perCard }), serve });
|
|
33
|
+
}
|
|
34
|
+
for (const card of manifest.contributes?.capabilities ?? []) {
|
|
35
|
+
// `tools` naming the same card wins: a manifest may keep `mcp` for a host older than `tools`.
|
|
36
|
+
if (card.kind === "cli" && card.mcp !== undefined && tools?.perCard !== card.id) {
|
|
37
|
+
servers.push({ perCard: card.id, serve: { by: "backend", path: card.mcp } });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return servers;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// The server a card of this cli kind gets, if the manifest serves one per card of it.
|
|
44
|
+
export const toolServerForCard = (manifest: ExtensionManifest, cardKind: string): ToolServer | undefined =>
|
|
45
|
+
toolServersOf(manifest).find((server) => server.perCard === cardKind);
|
|
46
|
+
|
|
47
|
+
// The /x paths (relative to the namespace) the backend answers MCP at itself: what /x/* refuses, since only the door
|
|
48
|
+
// checks the calling turn's lease.
|
|
49
|
+
export const backendToolPathsOf = (manifest: ExtensionManifest): string[] =>
|
|
50
|
+
toolServersOf(manifest).flatMap((server) => (server.serve.by === "backend" ? [server.serve.path] : []));
|