@lapis-notes/ai 0.1.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/CHANGELOG.md +6 -0
- package/LICENSE.md +8 -0
- package/README.md +20 -0
- package/dist/ai-plugin.d.ts +88 -0
- package/dist/ai-plugin.js +742 -0
- package/dist/bootstrap/build.d.ts +15 -0
- package/dist/bootstrap/build.js +28 -0
- package/dist/bootstrap/folder-instructions.d.ts +4 -0
- package/dist/bootstrap/folder-instructions.js +49 -0
- package/dist/bootstrap/format.d.ts +2 -0
- package/dist/bootstrap/format.js +46 -0
- package/dist/bootstrap/instructions.d.ts +1 -0
- package/dist/bootstrap/instructions.js +8 -0
- package/dist/bootstrap/types.d.ts +36 -0
- package/dist/bootstrap/types.js +3 -0
- package/dist/catalog/ai-catalog-panel.svelte +742 -0
- package/dist/catalog/ai-catalog-panel.svelte.d.ts +13 -0
- package/dist/catalog/ai-catalog-view-type.d.ts +1 -0
- package/dist/catalog/ai-catalog-view-type.js +1 -0
- package/dist/catalog/ai-catalog-view.d.ts +22 -0
- package/dist/catalog/ai-catalog-view.js +48 -0
- package/dist/catalog/filter.d.ts +3 -0
- package/dist/catalog/filter.js +24 -0
- package/dist/catalog/inventory.d.ts +21 -0
- package/dist/catalog/inventory.js +255 -0
- package/dist/catalog/tree.d.ts +12 -0
- package/dist/catalog/tree.js +60 -0
- package/dist/catalog/types.d.ts +44 -0
- package/dist/catalog/types.js +1 -0
- package/dist/chat/ai-approval-card.svelte +54 -0
- package/dist/chat/ai-approval-card.svelte.d.ts +9 -0
- package/dist/chat/ai-chat-panel.svelte +1288 -0
- package/dist/chat/ai-chat-panel.svelte.d.ts +50 -0
- package/dist/chat/ai-inventory-result.css +55 -0
- package/dist/chat/ai-inventory-result.svelte +63 -0
- package/dist/chat/ai-inventory-result.svelte.d.ts +5 -0
- package/dist/chat/ai-question-card.svelte +132 -0
- package/dist/chat/ai-question-card.svelte.d.ts +9 -0
- package/dist/chat/ai-view-bootstrap.d.ts +13 -0
- package/dist/chat/ai-view-bootstrap.js +86 -0
- package/dist/chat/ai-view-panel.svelte +89 -0
- package/dist/chat/ai-view-panel.svelte.d.ts +13 -0
- package/dist/chat/ai-view-type.d.ts +1 -0
- package/dist/chat/ai-view-type.js +1 -0
- package/dist/chat/ai-view.d.ts +60 -0
- package/dist/chat/ai-view.js +121 -0
- package/dist/chat/chat-controller.svelte.d.ts +82 -0
- package/dist/chat/chat-controller.svelte.js +1926 -0
- package/dist/chat/chat-items.d.ts +88 -0
- package/dist/chat/chat-items.js +9 -0
- package/dist/chat/chat-mentions.d.ts +15 -0
- package/dist/chat/chat-mentions.js +77 -0
- package/dist/chat/chat-result-views.d.ts +5 -0
- package/dist/chat/chat-result-views.js +15 -0
- package/dist/chat/chat-session.d.ts +28 -0
- package/dist/chat/chat-session.js +50 -0
- package/dist/chat/chat-status.d.ts +1 -0
- package/dist/chat/chat-status.js +3 -0
- package/dist/chat/chat-time.d.ts +18 -0
- package/dist/chat/chat-time.js +90 -0
- package/dist/chat/chat-tool-display.d.ts +17 -0
- package/dist/chat/chat-tool-display.js +213 -0
- package/dist/chat/chat-tool-identity.d.ts +4 -0
- package/dist/chat/chat-tool-identity.js +42 -0
- package/dist/chat/chat-trace.d.ts +6 -0
- package/dist/chat/chat-trace.js +256 -0
- package/dist/chat/live-runtime-unavailable.d.ts +1 -0
- package/dist/chat/live-runtime-unavailable.js +1 -0
- package/dist/commands/agent.d.ts +8 -0
- package/dist/commands/agent.js +38 -0
- package/dist/commands/catalog.d.ts +18 -0
- package/dist/commands/catalog.js +115 -0
- package/dist/commands/discovery.d.ts +36 -0
- package/dist/commands/discovery.js +151 -0
- package/dist/commands/groups.d.ts +16 -0
- package/dist/commands/groups.js +121 -0
- package/dist/commands/inspect.d.ts +18 -0
- package/dist/commands/inspect.js +29 -0
- package/dist/commands/markdown.d.ts +21 -0
- package/dist/commands/markdown.js +117 -0
- package/dist/commands/parser.d.ts +4 -0
- package/dist/commands/parser.js +24 -0
- package/dist/commands/reserved.d.ts +4 -0
- package/dist/commands/reserved.js +107 -0
- package/dist/commands/router.d.ts +17 -0
- package/dist/commands/router.js +170 -0
- package/dist/commands/seed.d.ts +4 -0
- package/dist/commands/seed.js +8 -0
- package/dist/commands/types.d.ts +54 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/user-agents-node.d.ts +3 -0
- package/dist/commands/user-agents-node.js +52 -0
- package/dist/commands/user-agents.d.ts +22 -0
- package/dist/commands/user-agents.js +40 -0
- package/dist/conversations/approval-grants.d.ts +20 -0
- package/dist/conversations/approval-grants.js +75 -0
- package/dist/conversations/binding-state.d.ts +6 -0
- package/dist/conversations/binding-state.js +35 -0
- package/dist/conversations/context-handoff.d.ts +25 -0
- package/dist/conversations/context-handoff.js +207 -0
- package/dist/conversations/conversation-index-coordinator.d.ts +18 -0
- package/dist/conversations/conversation-index-coordinator.js +81 -0
- package/dist/conversations/conversation-index.d.ts +16 -0
- package/dist/conversations/conversation-index.js +125 -0
- package/dist/conversations/conversation-locator.d.ts +2 -0
- package/dist/conversations/conversation-locator.js +20 -0
- package/dist/conversations/conversation-palette.d.ts +18 -0
- package/dist/conversations/conversation-palette.js +29 -0
- package/dist/conversations/conversation-repository.d.ts +39 -0
- package/dist/conversations/conversation-repository.js +213 -0
- package/dist/conversations/handoff-summary.d.ts +48 -0
- package/dist/conversations/handoff-summary.js +269 -0
- package/dist/conversations/hashes.d.ts +5 -0
- package/dist/conversations/hashes.js +40 -0
- package/dist/conversations/history-creation-scope.d.ts +12 -0
- package/dist/conversations/history-creation-scope.js +44 -0
- package/dist/conversations/jsonl.d.ts +10 -0
- package/dist/conversations/jsonl.js +41 -0
- package/dist/conversations/memory-transcript-store.d.ts +15 -0
- package/dist/conversations/memory-transcript-store.js +119 -0
- package/dist/conversations/paths.d.ts +14 -0
- package/dist/conversations/paths.js +92 -0
- package/dist/conversations/redaction.d.ts +13 -0
- package/dist/conversations/redaction.js +94 -0
- package/dist/conversations/reveal-scope.d.ts +3 -0
- package/dist/conversations/reveal-scope.js +8 -0
- package/dist/conversations/scope-resolver.d.ts +11 -0
- package/dist/conversations/scope-resolver.js +18 -0
- package/dist/conversations/scope-tree.d.ts +10 -0
- package/dist/conversations/scope-tree.js +71 -0
- package/dist/conversations/transcript-projection.d.ts +9 -0
- package/dist/conversations/transcript-projection.js +409 -0
- package/dist/conversations/transcript-store.d.ts +24 -0
- package/dist/conversations/transcript-store.js +27 -0
- package/dist/conversations/types.d.ts +224 -0
- package/dist/conversations/types.js +12 -0
- package/dist/conversations/validation.d.ts +4 -0
- package/dist/conversations/validation.js +383 -0
- package/dist/conversations/vault-transcript-store.d.ts +22 -0
- package/dist/conversations/vault-transcript-store.js +219 -0
- package/dist/core/event-queue.d.ts +6 -0
- package/dist/core/event-queue.js +39 -0
- package/dist/core/types.d.ts +241 -0
- package/dist/core/types.js +50 -0
- package/dist/history/ai-history-panel.svelte +937 -0
- package/dist/history/ai-history-panel.svelte.d.ts +17 -0
- package/dist/history/ai-history-view-type.d.ts +1 -0
- package/dist/history/ai-history-view-type.js +1 -0
- package/dist/history/ai-history-view.d.ts +26 -0
- package/dist/history/ai-history-view.js +51 -0
- package/dist/history/conversation-history-tree.d.ts +15 -0
- package/dist/history/conversation-history-tree.js +71 -0
- package/dist/host/agent-workspace.d.ts +6 -0
- package/dist/host/agent-workspace.js +16 -0
- package/dist/host/create-host-runtimes.d.ts +2 -0
- package/dist/host/create-host-runtimes.js +14 -0
- package/dist/host/desktop-process-host.d.ts +6 -0
- package/dist/host/desktop-process-host.js +74 -0
- package/dist/host/host-runtime-availability.d.ts +1 -0
- package/dist/host/host-runtime-availability.js +3 -0
- package/dist/host/process-host.d.ts +31 -0
- package/dist/host/process-host.js +6 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +49 -0
- package/dist/jsonl/ai-jsonl-preview.d.ts +27 -0
- package/dist/jsonl/ai-jsonl-preview.js +68 -0
- package/dist/jsonl/ai-jsonl-view-type.d.ts +1 -0
- package/dist/jsonl/ai-jsonl-view-type.js +1 -0
- package/dist/jsonl/ai-jsonl-view.d.ts +16 -0
- package/dist/jsonl/ai-jsonl-view.js +57 -0
- package/dist/jsonl/ai-jsonl-view.svelte +260 -0
- package/dist/jsonl/ai-jsonl-view.svelte.d.ts +9 -0
- package/dist/memory/candidate-extractor.d.ts +25 -0
- package/dist/memory/candidate-extractor.js +191 -0
- package/dist/memory/forget-confirmation.d.ts +3 -0
- package/dist/memory/forget-confirmation.js +45 -0
- package/dist/memory/hashes.d.ts +5 -0
- package/dist/memory/hashes.js +37 -0
- package/dist/memory/memory-ingestion-coordinator.d.ts +19 -0
- package/dist/memory/memory-ingestion-coordinator.js +108 -0
- package/dist/memory/memory-record-store.d.ts +62 -0
- package/dist/memory/memory-record-store.js +369 -0
- package/dist/memory/memory-scheduler.d.ts +21 -0
- package/dist/memory/memory-scheduler.js +143 -0
- package/dist/memory/memory-tools.d.ts +61 -0
- package/dist/memory/memory-tools.js +99 -0
- package/dist/memory/native-memory-service.d.ts +72 -0
- package/dist/memory/native-memory-service.js +1256 -0
- package/dist/memory/paths.d.ts +13 -0
- package/dist/memory/paths.js +60 -0
- package/dist/memory/runtime-consolidation-provider.d.ts +18 -0
- package/dist/memory/runtime-consolidation-provider.js +154 -0
- package/dist/memory/types.d.ts +139 -0
- package/dist/memory/types.js +1 -0
- package/dist/providers/acp-model-provider.d.ts +11 -0
- package/dist/providers/acp-model-provider.js +99 -0
- package/dist/providers/codex-model-catalog.d.ts +7 -0
- package/dist/providers/codex-model-catalog.js +179 -0
- package/dist/providers/codex-model-provider.d.ts +12 -0
- package/dist/providers/codex-model-provider.js +42 -0
- package/dist/providers/model-provider.d.ts +26 -0
- package/dist/providers/model-provider.js +26 -0
- package/dist/public-components.d.ts +3 -0
- package/dist/public-components.js +3 -0
- package/dist/registry/runtime-registry.d.ts +11 -0
- package/dist/registry/runtime-registry.js +71 -0
- package/dist/registry/select-runtime.d.ts +9 -0
- package/dist/registry/select-runtime.js +31 -0
- package/dist/runtime-adapters.d.ts +16 -0
- package/dist/runtime-adapters.js +9 -0
- package/dist/runtimes/acp/acp-event-mapper.d.ts +56 -0
- package/dist/runtimes/acp/acp-event-mapper.js +237 -0
- package/dist/runtimes/acp/acp-runtime.d.ts +46 -0
- package/dist/runtimes/acp/acp-runtime.js +165 -0
- package/dist/runtimes/acp/desktop-acp-backend.d.ts +16 -0
- package/dist/runtimes/acp/desktop-acp-backend.js +243 -0
- package/dist/runtimes/codex/app-server-protocol.d.ts +28 -0
- package/dist/runtimes/codex/app-server-protocol.js +274 -0
- package/dist/runtimes/codex/codex-runtime.d.ts +24 -0
- package/dist/runtimes/codex/codex-runtime.js +318 -0
- package/dist/runtimes/fake/fake-runtime.d.ts +51 -0
- package/dist/runtimes/fake/fake-runtime.js +257 -0
- package/dist/sessions/plugin-data.d.ts +8 -0
- package/dist/sessions/plugin-data.js +29 -0
- package/dist/sessions/session-store.d.ts +47 -0
- package/dist/sessions/session-store.js +117 -0
- package/dist/settings/acp-agents.d.ts +8 -0
- package/dist/settings/acp-agents.js +11 -0
- package/dist/settings/ai-settings-tab.d.ts +8 -0
- package/dist/settings/ai-settings-tab.js +249 -0
- package/dist/settings/ai-settings.d.ts +43 -0
- package/dist/settings/ai-settings.js +147 -0
- package/dist/settings/app-tool-setting-rows.d.ts +16 -0
- package/dist/settings/app-tool-setting-rows.js +30 -0
- package/dist/settings/register-ai-settings.d.ts +3 -0
- package/dist/settings/register-ai-settings.js +393 -0
- package/dist/skills/activation.d.ts +2 -0
- package/dist/skills/activation.js +20 -0
- package/dist/skills/bundled/lapis-notes.d.ts +3 -0
- package/dist/skills/bundled/lapis-notes.js +52 -0
- package/dist/skills/bundled/research.d.ts +6 -0
- package/dist/skills/bundled/research.js +29 -0
- package/dist/skills/discovery.d.ts +21 -0
- package/dist/skills/discovery.js +227 -0
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +39 -0
- package/dist/skills/parser.d.ts +12 -0
- package/dist/skills/parser.js +124 -0
- package/dist/skills/registry.d.ts +21 -0
- package/dist/skills/registry.js +89 -0
- package/dist/skills/seed.d.ts +5 -0
- package/dist/skills/seed.js +18 -0
- package/dist/skills/skill-tools.d.ts +8 -0
- package/dist/skills/skill-tools.js +113 -0
- package/dist/skills/types.d.ts +67 -0
- package/dist/skills/types.js +1 -0
- package/dist/styles.css +514 -0
- package/dist/tools/app-tool-host.d.ts +47 -0
- package/dist/tools/app-tool-host.js +419 -0
- package/dist/tools/desktop-app-tool-bridge.d.ts +23 -0
- package/dist/tools/desktop-app-tool-bridge.js +223 -0
- package/dist/tools/mcp-server-registry.d.ts +7 -0
- package/dist/tools/mcp-server-registry.js +30 -0
- package/manifest.json +34 -0
- package/package.json +83 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { parseSkillMarkdown, SkillParseError } from "./parser";
|
|
2
|
+
const SOURCE_RANK = {
|
|
3
|
+
folder: 5,
|
|
4
|
+
vault: 4,
|
|
5
|
+
user: 3,
|
|
6
|
+
extension: 2,
|
|
7
|
+
programmatic: 2,
|
|
8
|
+
bundled: 1,
|
|
9
|
+
};
|
|
10
|
+
function skillGlob(prefix) {
|
|
11
|
+
const root = prefix.replace(/\/$/u, "");
|
|
12
|
+
return root ? `${root}/**/SKILL.md` : ".agents/skills/**/SKILL.md";
|
|
13
|
+
}
|
|
14
|
+
function folderPrefix(scopeDir) {
|
|
15
|
+
return scopeDir ? `${scopeDir}/.agents/skills` : ".agents/skills";
|
|
16
|
+
}
|
|
17
|
+
export class SkillDiscovery {
|
|
18
|
+
options;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
async discover(context) {
|
|
23
|
+
const diagnostics = [];
|
|
24
|
+
const buckets = new Map();
|
|
25
|
+
await this.#collectVault("folder", folderPrefix(context.scopeDir), buckets, diagnostics);
|
|
26
|
+
await this.#collectVault("vault", ".agents/skills", buckets, diagnostics);
|
|
27
|
+
await this.#collectVault("user", this.options.userPrefix ?? ".agents/user/skills", buckets, diagnostics);
|
|
28
|
+
await this.#collectRegistered(buckets, diagnostics, context);
|
|
29
|
+
for (const bundled of this.options.bundled ?? []) {
|
|
30
|
+
addToBucket(buckets, bundled);
|
|
31
|
+
}
|
|
32
|
+
return mergeByPrecedence(buckets, diagnostics, context);
|
|
33
|
+
}
|
|
34
|
+
async #collectVault(source, prefix, buckets, diagnostics) {
|
|
35
|
+
const files = this.options.vault.getFilesByGlob(skillGlob(prefix));
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
if (file.path.includes("/scripts/") || file.path.includes("/references/")) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const root = file.path.replace(/\/SKILL\.md$/u, "");
|
|
41
|
+
if (rootIncludesEscape(root, prefix)) {
|
|
42
|
+
diagnostics.push({
|
|
43
|
+
path: file.path,
|
|
44
|
+
source,
|
|
45
|
+
message: "Skill path escapes the trusted root.",
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const content = await this.options.vault.cachedRead(file);
|
|
50
|
+
try {
|
|
51
|
+
addToBucket(buckets, parseSkillMarkdown(content, {
|
|
52
|
+
path: file.path,
|
|
53
|
+
source,
|
|
54
|
+
root,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
path: file.path,
|
|
60
|
+
source,
|
|
61
|
+
message: error instanceof SkillParseError
|
|
62
|
+
? error.message
|
|
63
|
+
: "Skill could not be parsed.",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async #collectRegistered(buckets, diagnostics, context) {
|
|
69
|
+
for (const source of this.options.appSkills?.list() ?? []) {
|
|
70
|
+
if (context.enabledPluginIds &&
|
|
71
|
+
context.enabledPluginIds.length > 0 &&
|
|
72
|
+
!context.enabledPluginIds.includes(source.ownerPluginId)) {
|
|
73
|
+
diagnostics.push({
|
|
74
|
+
path: source.path ?? source.skill?.name ?? source.ownerPluginId,
|
|
75
|
+
source: source.kind === "programmatic" ? "programmatic" : "extension",
|
|
76
|
+
message: `Disabled extension root is excluded: ${source.ownerPluginId}`,
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (source.kind === "programmatic" && source.skill) {
|
|
81
|
+
addToBucket(buckets, programmaticSkill(source));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const files = this.options.extensionFiles?.get(`${source.ownerPluginId}:${source.path ?? ""}`) ?? (await this.#vaultExtensionFiles(source));
|
|
85
|
+
if (!files)
|
|
86
|
+
continue;
|
|
87
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
88
|
+
if (!relativePath.endsWith("SKILL.md"))
|
|
89
|
+
continue;
|
|
90
|
+
try {
|
|
91
|
+
addToBucket(buckets, parseSkillMarkdown(content, {
|
|
92
|
+
path: `${source.ownerPluginId}/${relativePath}`,
|
|
93
|
+
source: "extension",
|
|
94
|
+
root: `${source.ownerPluginId}/${relativePath.replace(/\/SKILL\.md$/u, "")}`,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
diagnostics.push({
|
|
99
|
+
path: `${source.ownerPluginId}/${relativePath}`,
|
|
100
|
+
source: "extension",
|
|
101
|
+
message: error instanceof SkillParseError
|
|
102
|
+
? error.message
|
|
103
|
+
: "Skill could not be parsed.",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async #vaultExtensionFiles(source) {
|
|
110
|
+
if (!source.path)
|
|
111
|
+
return undefined;
|
|
112
|
+
const pluginRoot = this.options.extensionRootFor?.(source.ownerPluginId);
|
|
113
|
+
if (!pluginRoot)
|
|
114
|
+
return undefined;
|
|
115
|
+
const prefix = `${pluginRoot.replace(/\/$/u, "")}/${source.path}`;
|
|
116
|
+
const files = this.options.vault.getFilesByGlob(skillGlob(prefix));
|
|
117
|
+
const contents = {};
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (rootIncludesEscape(file.path.replace(/\/SKILL\.md$/u, ""), prefix)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
contents[file.path.slice(prefix.length + 1)] =
|
|
123
|
+
await this.options.vault.cachedRead(file);
|
|
124
|
+
}
|
|
125
|
+
return Object.keys(contents).length > 0 ? contents : undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function programmaticSkill(source) {
|
|
129
|
+
const skill = source.skill;
|
|
130
|
+
const frontmatter = [
|
|
131
|
+
"---",
|
|
132
|
+
`name: ${skill.name}`,
|
|
133
|
+
`description: ${skill.description}`,
|
|
134
|
+
skill.userInvocable === false ? "user-invocable: false" : undefined,
|
|
135
|
+
skill.disableModelInvocation ? "disable-model-invocation: true" : undefined,
|
|
136
|
+
skill.argumentHint ? `argument-hint: ${JSON.stringify(skill.argumentHint)}` : undefined,
|
|
137
|
+
skill.command?.kind === "tool" ? "command-dispatch: tool" : undefined,
|
|
138
|
+
skill.command?.kind === "tool" ? `command-tool: ${skill.command.tool}` : undefined,
|
|
139
|
+
"---",
|
|
140
|
+
].filter((line) => Boolean(line));
|
|
141
|
+
const content = [...frontmatter, "", skill.instructions].join("\n");
|
|
142
|
+
return parseSkillMarkdown(content, {
|
|
143
|
+
path: `programmatic:${source.ownerPluginId}/${skill.name}`,
|
|
144
|
+
source: "programmatic",
|
|
145
|
+
root: `programmatic:${source.ownerPluginId}/${skill.name}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function addToBucket(buckets, skill) {
|
|
149
|
+
const bucket = buckets.get(skill.source) ?? [];
|
|
150
|
+
bucket.push(skill);
|
|
151
|
+
buckets.set(skill.source, bucket);
|
|
152
|
+
}
|
|
153
|
+
function rootIncludesEscape(root, prefix) {
|
|
154
|
+
const normalized = prefix.replace(/\/$/u, "");
|
|
155
|
+
if (root.includes("/../") || root.endsWith("/..") || root.includes("/./")) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return normalized !== "" && root !== normalized && !root.startsWith(`${normalized}/`);
|
|
159
|
+
}
|
|
160
|
+
function isEligible(skill, context) {
|
|
161
|
+
const requiredTools = skill.requirements?.tools ?? [];
|
|
162
|
+
const available = new Set(context.availableToolNames ?? []);
|
|
163
|
+
const missingTool = requiredTools.find((tool) => !available.has(tool));
|
|
164
|
+
if (missingTool)
|
|
165
|
+
return `Required tool is unavailable: ${missingTool}`;
|
|
166
|
+
const requiredPlugins = skill.requirements?.extensions ?? [];
|
|
167
|
+
const enabled = new Set(context.enabledPluginIds ?? []);
|
|
168
|
+
if (requiredPlugins.length > 0 && enabled.size > 0) {
|
|
169
|
+
const missing = requiredPlugins.find((id) => !enabled.has(id));
|
|
170
|
+
if (missing)
|
|
171
|
+
return `Required extension is unavailable: ${missing}`;
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function mergeByPrecedence(buckets, diagnostics, context) {
|
|
176
|
+
const winners = new Map();
|
|
177
|
+
const kinds = [
|
|
178
|
+
"bundled",
|
|
179
|
+
"extension",
|
|
180
|
+
"programmatic",
|
|
181
|
+
"user",
|
|
182
|
+
"vault",
|
|
183
|
+
"folder",
|
|
184
|
+
];
|
|
185
|
+
for (const kind of kinds) {
|
|
186
|
+
const skills = buckets.get(kind) ?? [];
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
for (const skill of skills) {
|
|
189
|
+
if (seen.has(skill.name)) {
|
|
190
|
+
diagnostics.push({
|
|
191
|
+
path: skill.root,
|
|
192
|
+
source: skill.source,
|
|
193
|
+
message: `Duplicate skill name at the same precedence: ${skill.name}`,
|
|
194
|
+
});
|
|
195
|
+
winners.delete(skill.name);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
seen.add(skill.name);
|
|
199
|
+
const reason = isEligible(skill, context);
|
|
200
|
+
if (reason) {
|
|
201
|
+
diagnostics.push({
|
|
202
|
+
path: skill.root,
|
|
203
|
+
source: skill.source,
|
|
204
|
+
message: reason,
|
|
205
|
+
});
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const existing = winners.get(skill.name);
|
|
209
|
+
if (existing && SOURCE_RANK[existing.source] > SOURCE_RANK[skill.source]) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (existing) {
|
|
213
|
+
diagnostics.push({
|
|
214
|
+
path: existing.root,
|
|
215
|
+
source: existing.source,
|
|
216
|
+
message: `Shadowed by ${skill.source} skill ${skill.name}`,
|
|
217
|
+
shadowedBy: skill.root,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
winners.set(skill.name, skill);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
skills: [...winners.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
|
225
|
+
diagnostics,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const MANIFEST_BUDGET = 8_000;
|
|
2
|
+
export function hasHostFilesystemPath(value) {
|
|
3
|
+
return /(?:(?:^|[\s"'>=])(?:\/|[A-Za-z]:\\|file:\/\/)|\.lapis\/skills\/|\.agents\/(?:user\/)?skills\/)/u.test(value);
|
|
4
|
+
}
|
|
5
|
+
export function buildAvailableSkillsManifest(snapshot) {
|
|
6
|
+
const visible = snapshot.skills.filter((skill) => skill.modelInvocable);
|
|
7
|
+
const body = visible
|
|
8
|
+
.map((skill) => renderSkill(skill))
|
|
9
|
+
.join("\n");
|
|
10
|
+
const xml = [
|
|
11
|
+
"<available_skills>",
|
|
12
|
+
body,
|
|
13
|
+
"</available_skills>",
|
|
14
|
+
"",
|
|
15
|
+
"When a listed skill clearly applies, load it using skills_read before proceeding.",
|
|
16
|
+
"Only load skills listed in available_skills.",
|
|
17
|
+
].join("\n");
|
|
18
|
+
if (xml.length > MANIFEST_BUDGET) {
|
|
19
|
+
throw new Error(`Available-skills manifest exceeds ${MANIFEST_BUDGET} characters.`);
|
|
20
|
+
}
|
|
21
|
+
return xml;
|
|
22
|
+
}
|
|
23
|
+
function renderSkill(skill) {
|
|
24
|
+
return [
|
|
25
|
+
" <skill>",
|
|
26
|
+
` <name>${escapeXml(skill.name)}</name>`,
|
|
27
|
+
` <description>${escapeXml(skill.description)}</description>`,
|
|
28
|
+
` <id>${escapeXml(skill.skillId)}</id>`,
|
|
29
|
+
` <version>${escapeXml(skill.version)}</version>`,
|
|
30
|
+
" </skill>",
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
function escapeXml(value) {
|
|
34
|
+
return value
|
|
35
|
+
.replaceAll("&", "&")
|
|
36
|
+
.replaceAll("<", "<")
|
|
37
|
+
.replaceAll(">", ">")
|
|
38
|
+
.replaceAll('"', """);
|
|
39
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SkillSourceKind } from "@lapis-notes/api/agent-skills";
|
|
2
|
+
import type { LoadedAppSkill } from "./types";
|
|
3
|
+
export declare class SkillParseError extends Error {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
constructor(path: string, message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare function skillContentVersion(content: string): string;
|
|
8
|
+
export declare function parseSkillMarkdown(content: string, input: {
|
|
9
|
+
path: string;
|
|
10
|
+
source: SkillSourceKind;
|
|
11
|
+
root: string;
|
|
12
|
+
}): LoadedAppSkill;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { parse as parseYaml } from "yaml";
|
|
2
|
+
import { SKILL_NAME_PATTERN } from "@lapis-notes/api/agent-skills";
|
|
3
|
+
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/u;
|
|
4
|
+
export class SkillParseError extends Error {
|
|
5
|
+
path;
|
|
6
|
+
constructor(path, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.name = "SkillParseError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function skillContentVersion(content) {
|
|
13
|
+
let hash = 2166136261;
|
|
14
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
15
|
+
hash ^= content.charCodeAt(index);
|
|
16
|
+
hash = Math.imul(hash, 16777619);
|
|
17
|
+
}
|
|
18
|
+
return `fnv1a:${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
19
|
+
}
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
function readBoolean(value, fallback) {
|
|
24
|
+
return typeof value === "boolean" ? value : fallback;
|
|
25
|
+
}
|
|
26
|
+
function readString(value) {
|
|
27
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
28
|
+
}
|
|
29
|
+
function readStringList(value) {
|
|
30
|
+
if (!Array.isArray(value))
|
|
31
|
+
return undefined;
|
|
32
|
+
const items = value
|
|
33
|
+
.filter((item) => typeof item === "string")
|
|
34
|
+
.map((item) => item.trim())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
return items.length > 0 ? items : undefined;
|
|
37
|
+
}
|
|
38
|
+
function parseCommand(data) {
|
|
39
|
+
const dispatch = readString(data["command-dispatch"]);
|
|
40
|
+
if (!dispatch || dispatch === "model")
|
|
41
|
+
return { kind: "model" };
|
|
42
|
+
if (dispatch !== "tool") {
|
|
43
|
+
throw new Error(`Unsupported command-dispatch: ${dispatch}`);
|
|
44
|
+
}
|
|
45
|
+
const tool = readString(data["command-tool"]);
|
|
46
|
+
if (!tool) {
|
|
47
|
+
throw new Error("command-dispatch: tool requires command-tool");
|
|
48
|
+
}
|
|
49
|
+
const argMode = readString(data["command-arg-mode"]) ?? "raw";
|
|
50
|
+
if (argMode !== "raw") {
|
|
51
|
+
throw new Error(`Unsupported command-arg-mode: ${argMode}`);
|
|
52
|
+
}
|
|
53
|
+
return { kind: "tool", tool, argMode: "raw" };
|
|
54
|
+
}
|
|
55
|
+
function parseRequirements(data) {
|
|
56
|
+
const metadata = isRecord(data.metadata) ? data.metadata : undefined;
|
|
57
|
+
const lapis = metadata && isRecord(metadata.lapis) ? metadata.lapis : undefined;
|
|
58
|
+
const requires = lapis && isRecord(lapis.requires) ? lapis.requires : undefined;
|
|
59
|
+
if (!requires)
|
|
60
|
+
return undefined;
|
|
61
|
+
const requirements = {
|
|
62
|
+
tools: readStringList(requires.tools),
|
|
63
|
+
capabilities: readStringList(requires.capabilities),
|
|
64
|
+
extensions: readStringList(requires.extensions),
|
|
65
|
+
};
|
|
66
|
+
if (!requirements.tools &&
|
|
67
|
+
!requirements.capabilities &&
|
|
68
|
+
!requirements.extensions) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return requirements;
|
|
72
|
+
}
|
|
73
|
+
export function parseSkillMarkdown(content, input) {
|
|
74
|
+
const match = content.match(FRONTMATTER);
|
|
75
|
+
if (!match) {
|
|
76
|
+
throw new SkillParseError(input.path, "SKILL.md must start with YAML frontmatter.");
|
|
77
|
+
}
|
|
78
|
+
let data;
|
|
79
|
+
try {
|
|
80
|
+
data = parseYaml(match[1] ?? "");
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new SkillParseError(input.path, "SKILL.md frontmatter is not valid YAML.");
|
|
84
|
+
}
|
|
85
|
+
if (!isRecord(data)) {
|
|
86
|
+
throw new SkillParseError(input.path, "SKILL.md frontmatter must be a mapping.");
|
|
87
|
+
}
|
|
88
|
+
const name = readString(data.name);
|
|
89
|
+
const description = readString(data.description);
|
|
90
|
+
if (!name) {
|
|
91
|
+
throw new SkillParseError(input.path, "SKILL.md must declare name.");
|
|
92
|
+
}
|
|
93
|
+
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
94
|
+
throw new SkillParseError(input.path, `Invalid skill name: ${name}`);
|
|
95
|
+
}
|
|
96
|
+
if (!description) {
|
|
97
|
+
throw new SkillParseError(input.path, "SKILL.md must declare description.");
|
|
98
|
+
}
|
|
99
|
+
let command;
|
|
100
|
+
try {
|
|
101
|
+
command = parseCommand(data);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new SkillParseError(input.path, error instanceof Error ? error.message : String(error));
|
|
105
|
+
}
|
|
106
|
+
const disableModel = readBoolean(data["disable-model-invocation"], false);
|
|
107
|
+
const descriptor = {
|
|
108
|
+
id: `${input.source}:${name}`,
|
|
109
|
+
name,
|
|
110
|
+
description,
|
|
111
|
+
source: input.source,
|
|
112
|
+
root: input.root,
|
|
113
|
+
version: skillContentVersion(content),
|
|
114
|
+
userInvocable: readBoolean(data["user-invocable"], true),
|
|
115
|
+
modelInvocable: !disableModel,
|
|
116
|
+
argumentHint: readString(data["argument-hint"]),
|
|
117
|
+
command,
|
|
118
|
+
requirements: parseRequirements(data),
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
...descriptor,
|
|
122
|
+
instructions: (match[2] ?? "").trim(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type SkillDiscoveryOptions } from "./discovery";
|
|
2
|
+
import type { AppSkillDescriptor, LoadedAppSkill, SkillActivation, SkillDiagnostic, SkillDiscoveryContext, SkillSnapshot } from "./types";
|
|
3
|
+
export declare class SkillRegistry {
|
|
4
|
+
#private;
|
|
5
|
+
diagnostics: SkillDiagnostic[];
|
|
6
|
+
constructor(options: SkillDiscoveryOptions);
|
|
7
|
+
invalidate(): void;
|
|
8
|
+
discover(context: SkillDiscoveryContext): Promise<readonly AppSkillDescriptor[]>;
|
|
9
|
+
resolve(name: string, context: SkillDiscoveryContext): Promise<AppSkillDescriptor | undefined>;
|
|
10
|
+
load(id: string, version: string): Promise<LoadedAppSkill>;
|
|
11
|
+
snapshot(context: SkillDiscoveryContext): Promise<SkillSnapshot>;
|
|
12
|
+
activate(name: string, context: SkillDiscoveryContext, source: SkillActivation["source"], args?: string): Promise<SkillActivation>;
|
|
13
|
+
getLoaded(name: string): LoadedAppSkill | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare class SkillSnapshotStore {
|
|
16
|
+
#private;
|
|
17
|
+
set(bindingId: string, snapshot: SkillSnapshot): void;
|
|
18
|
+
get(bindingId: string): SkillSnapshot | undefined;
|
|
19
|
+
close(bindingId: string): void;
|
|
20
|
+
clear(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { SkillDiscovery } from "./discovery";
|
|
2
|
+
export class SkillRegistry {
|
|
3
|
+
#discovery;
|
|
4
|
+
#loaded = new Map();
|
|
5
|
+
#history = new Map();
|
|
6
|
+
#generation = 0;
|
|
7
|
+
diagnostics = [];
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.#discovery = new SkillDiscovery(options);
|
|
10
|
+
}
|
|
11
|
+
invalidate() {
|
|
12
|
+
this.#generation += 1;
|
|
13
|
+
this.#loaded.clear();
|
|
14
|
+
}
|
|
15
|
+
async discover(context) {
|
|
16
|
+
const discovered = await this.#discovery.discover(context);
|
|
17
|
+
this.diagnostics = discovered.diagnostics;
|
|
18
|
+
this.#loaded.clear();
|
|
19
|
+
for (const skill of discovered.skills) {
|
|
20
|
+
this.#loaded.set(skill.id, skill);
|
|
21
|
+
this.#history.set(`${skill.id}@${skill.version}`, skill);
|
|
22
|
+
}
|
|
23
|
+
return discovered.skills;
|
|
24
|
+
}
|
|
25
|
+
async resolve(name, context) {
|
|
26
|
+
const skills = await this.discover(context);
|
|
27
|
+
return skills.find((skill) => skill.name === name);
|
|
28
|
+
}
|
|
29
|
+
async load(id, version) {
|
|
30
|
+
const loaded = this.#history.get(`${id}@${version}`) ??
|
|
31
|
+
this.#loaded.get(id) ??
|
|
32
|
+
[...this.#history.values()].find((skill) => skill.version === version &&
|
|
33
|
+
(skill.id === id || skill.name === id));
|
|
34
|
+
if (!loaded || loaded.version !== version) {
|
|
35
|
+
throw new Error(`Skill is not available: ${id}`);
|
|
36
|
+
}
|
|
37
|
+
return loaded;
|
|
38
|
+
}
|
|
39
|
+
async snapshot(context) {
|
|
40
|
+
const skills = await this.discover(context);
|
|
41
|
+
const entries = skills.map((skill) => ({
|
|
42
|
+
skillId: skill.id,
|
|
43
|
+
name: skill.name,
|
|
44
|
+
description: skill.description,
|
|
45
|
+
version: skill.version,
|
|
46
|
+
userInvocable: skill.userInvocable,
|
|
47
|
+
modelInvocable: skill.modelInvocable,
|
|
48
|
+
argumentHint: skill.argumentHint,
|
|
49
|
+
}));
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
id: `skill-snapshot-${this.#generation}-${crypto.randomUUID()}`,
|
|
52
|
+
createdAt: new Date().toISOString(),
|
|
53
|
+
skills: Object.freeze(entries),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async activate(name, context, source, args) {
|
|
57
|
+
const descriptor = await this.resolve(name, context);
|
|
58
|
+
if (!descriptor) {
|
|
59
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
60
|
+
}
|
|
61
|
+
const loaded = await this.load(descriptor.id, descriptor.version);
|
|
62
|
+
return {
|
|
63
|
+
skillId: loaded.id,
|
|
64
|
+
skillName: loaded.name,
|
|
65
|
+
version: loaded.version,
|
|
66
|
+
source,
|
|
67
|
+
arguments: args,
|
|
68
|
+
instructions: loaded.instructions,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
getLoaded(name) {
|
|
72
|
+
return [...this.#loaded.values()].find((skill) => skill.name === name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export class SkillSnapshotStore {
|
|
76
|
+
#snapshots = new Map();
|
|
77
|
+
set(bindingId, snapshot) {
|
|
78
|
+
this.#snapshots.set(bindingId, snapshot);
|
|
79
|
+
}
|
|
80
|
+
get(bindingId) {
|
|
81
|
+
return this.#snapshots.get(bindingId);
|
|
82
|
+
}
|
|
83
|
+
close(bindingId) {
|
|
84
|
+
this.#snapshots.delete(bindingId);
|
|
85
|
+
}
|
|
86
|
+
clear() {
|
|
87
|
+
this.#snapshots.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BUNDLED_SKILL_MARKDOWN } from "./bundled/research";
|
|
2
|
+
export function bundledSkillVaultPath(name) {
|
|
3
|
+
return `.agents/skills/${name}/SKILL.md`;
|
|
4
|
+
}
|
|
5
|
+
export async function seedBundledSkills(vault, options = {}) {
|
|
6
|
+
for (const [name, markdown] of Object.entries(BUNDLED_SKILL_MARKDOWN)) {
|
|
7
|
+
const path = bundledSkillVaultPath(name);
|
|
8
|
+
const existing = vault.getFileByPath(path);
|
|
9
|
+
if (existing && !options.overwrite)
|
|
10
|
+
continue;
|
|
11
|
+
if (existing) {
|
|
12
|
+
await vault.modify(existing, markdown);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
await vault.mkpath(`.agents/skills/${name}`);
|
|
16
|
+
await vault.create(path, markdown);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppTool } from "@lapis-notes/api/agent-tools";
|
|
2
|
+
import type { Vault } from "@lapis-notes/api";
|
|
3
|
+
import type { SkillRegistry, SkillSnapshotStore } from "./registry";
|
|
4
|
+
export declare function createSkillAppTools(options: {
|
|
5
|
+
registry: SkillRegistry;
|
|
6
|
+
snapshots: SkillSnapshotStore;
|
|
7
|
+
vault: Vault;
|
|
8
|
+
}): AppTool[];
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const MAX_RESOURCE_BYTES = 64 * 1024;
|
|
2
|
+
export function createSkillAppTools(options) {
|
|
3
|
+
return [
|
|
4
|
+
{
|
|
5
|
+
name: "skills_read",
|
|
6
|
+
description: "Load the full instructions for an available application skill.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: { name: { type: "string" } },
|
|
10
|
+
required: ["name"],
|
|
11
|
+
},
|
|
12
|
+
effect: "read",
|
|
13
|
+
async execute(input, context) {
|
|
14
|
+
const name = input && typeof input === "object" && "name" in input
|
|
15
|
+
? String(input.name)
|
|
16
|
+
: "";
|
|
17
|
+
const snapshot = options.snapshots.get(context.agentBindingId);
|
|
18
|
+
const entry = snapshot?.skills.find((skill) => skill.name === name);
|
|
19
|
+
if (!entry) {
|
|
20
|
+
return {
|
|
21
|
+
isError: true,
|
|
22
|
+
content: [{ type: "text", text: `Unknown skill: ${name}` }],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!entry.modelInvocable) {
|
|
26
|
+
return {
|
|
27
|
+
isError: true,
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Skill ${name} is not available for model invocation.`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const loaded = await options.registry.load(entry.skillId, entry.version);
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: loaded.instructions }],
|
|
39
|
+
structuredContent: {
|
|
40
|
+
name: loaded.name,
|
|
41
|
+
version: loaded.version,
|
|
42
|
+
instructions: loaded.instructions,
|
|
43
|
+
resources: [],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "skills_resource",
|
|
50
|
+
description: "Read a resource belonging to a skill already available to this agent.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
skill: { type: "string" },
|
|
55
|
+
path: { type: "string" },
|
|
56
|
+
},
|
|
57
|
+
required: ["skill", "path"],
|
|
58
|
+
},
|
|
59
|
+
effect: "read",
|
|
60
|
+
async execute(input, context) {
|
|
61
|
+
const body = input && typeof input === "object"
|
|
62
|
+
? input
|
|
63
|
+
: {};
|
|
64
|
+
const skillName = String(body.skill ?? "");
|
|
65
|
+
const relative = String(body.path ?? "").replaceAll("\\", "/");
|
|
66
|
+
const snapshot = options.snapshots.get(context.agentBindingId);
|
|
67
|
+
const entry = snapshot?.skills.find((skill) => skill.name === skillName);
|
|
68
|
+
if (!entry) {
|
|
69
|
+
return {
|
|
70
|
+
isError: true,
|
|
71
|
+
content: [{ type: "text", text: `Unknown skill: ${skillName}` }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (!relative ||
|
|
75
|
+
relative.startsWith("/") ||
|
|
76
|
+
relative.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
77
|
+
return {
|
|
78
|
+
isError: true,
|
|
79
|
+
content: [{ type: "text", text: "Skill resource path is invalid." }],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let loaded;
|
|
83
|
+
try {
|
|
84
|
+
loaded = await options.registry.load(entry.skillId, entry.version);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return {
|
|
88
|
+
isError: true,
|
|
89
|
+
content: [{ type: "text", text: `Skill is not loaded: ${skillName}` }],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const target = `${loaded.root}/${relative}`;
|
|
93
|
+
const file = options.vault.getFileByPath(target);
|
|
94
|
+
if (!file) {
|
|
95
|
+
return {
|
|
96
|
+
isError: true,
|
|
97
|
+
content: [{ type: "text", text: `Skill resource not found: ${relative}` }],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const text = await options.vault.cachedRead(file);
|
|
101
|
+
if (new TextEncoder().encode(text).byteLength > MAX_RESOURCE_BYTES) {
|
|
102
|
+
return {
|
|
103
|
+
isError: true,
|
|
104
|
+
content: [{ type: "text", text: "Skill resource exceeds the size limit." }],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text }],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
}
|