@jini-ai/agentic 0.3.6 → 0.4.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 +94 -0
- package/README.md +33 -6
- package/dist/core/a2ui/agent-to-renderer.d.ts +3 -1
- package/dist/core/a2ui/agent-to-renderer.js +1 -1
- package/dist/core/a2ui/catalog.d.ts +14 -5
- package/dist/core/a2ui/catalog.js +5 -5
- package/dist/core/a2ui/common-types.d.ts +11 -3
- package/dist/core/a2ui/common-types.js +5 -3
- package/dist/core/a2ui/index.d.ts +1 -1
- package/dist/core/a2ui/index.js +1 -1
- package/dist/core/a2ui/interpreter.d.ts +45 -10
- package/dist/core/a2ui/interpreter.js +38 -43
- package/dist/core/a2ui/json-pointer.d.ts +16 -4
- package/dist/core/a2ui/json-pointer.js +7 -7
- package/dist/core/a2ui/renderer-to-agent.d.ts +22 -8
- package/dist/core/a2ui/renderer-to-agent.js +5 -5
- package/dist/core/a2ui/resolve.d.ts +7 -2
- package/dist/core/a2ui/resolve.js +10 -9
- package/dist/core/a2ui/tree.d.ts +8 -2
- package/dist/core/a2ui/tree.js +2 -2
- package/dist/core/ag-ui.d.ts +17 -9
- package/dist/core/ag-ui.js +4 -4
- package/dist/core/capability.d.ts +12 -3
- package/dist/core/capability.js +3 -3
- package/dist/core/dom/dom-page-driver.d.ts +8 -4
- package/dist/core/dom/dom-page-driver.js +47 -26
- package/dist/core/dom/index.d.ts +3 -3
- package/dist/core/dom/index.js +1 -1
- package/dist/core/dom/model-context.d.ts +18 -8
- package/dist/core/dom/model-context.js +12 -15
- package/dist/core/dom/native-model-context.d.ts +6 -0
- package/dist/core/dom/native-model-context.js +19 -0
- package/dist/core/element-handles.d.ts +12 -2
- package/dist/core/element-handles.js +9 -3
- package/dist/core/gen-ui/encoder.d.ts +21 -11
- package/dist/core/gen-ui/encoder.js +8 -7
- package/dist/core/gen-ui/events.d.ts +6 -5
- package/dist/core/gen-ui/events.js +6 -5
- package/dist/core/gen-ui/index.d.ts +1 -1
- package/dist/core/gen-ui/index.js +1 -1
- package/dist/core/guards.d.ts +17 -5
- package/dist/core/guards.js +6 -6
- package/dist/core/handle.d.ts +12 -5
- package/dist/core/handle.js +9 -8
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +3 -2
- package/dist/core/list-handles.d.ts +4 -1
- package/dist/core/list-handles.js +1 -1
- package/dist/core/mcp-ui-apps.d.ts +39 -9
- package/dist/core/mcp-ui-apps.js +10 -8
- package/dist/core/messages.d.ts +6 -0
- package/dist/core/messages.js +6 -0
- package/dist/core/page-capabilities.d.ts +0 -11
- package/dist/core/page-capabilities.js +13 -1
- package/dist/core/page-driver.d.ts +32 -11
- package/dist/core/page-executor.d.ts +8 -2
- package/dist/core/page-executor.js +31 -31
- package/dist/core/webmcp.d.ts +27 -8
- package/dist/core/webmcp.js +14 -14
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/skills/install/archive.d.ts +8 -0
- package/dist/skills/install/archive.js +47 -0
- package/dist/skills/install/fetch-adapter.d.ts +6 -0
- package/dist/skills/install/fetch-adapter.js +6 -0
- package/dist/skills/install/github.d.ts +21 -0
- package/dist/skills/install/github.js +92 -0
- package/dist/skills/install/index.d.ts +11 -0
- package/dist/skills/install/index.js +11 -0
- package/dist/skills/install/install-service.d.ts +44 -0
- package/dist/skills/install/install-service.js +121 -0
- package/dist/skills/install/layout.d.ts +13 -0
- package/dist/skills/install/layout.js +22 -0
- package/dist/skills/install/live-registration.d.ts +61 -0
- package/dist/skills/install/live-registration.js +64 -0
- package/dist/skills/install/node-filesystem.d.ts +4 -0
- package/dist/skills/install/node-filesystem.js +41 -0
- package/dist/skills/install/ports.d.ts +101 -0
- package/dist/skills/install/ports.js +2 -0
- package/dist/skills/install/state.d.ts +13 -0
- package/dist/skills/install/state.js +23 -0
- package/dist/skills/install/validation.d.ts +45 -0
- package/dist/skills/install/validation.js +104 -0
- package/package.json +19 -3
- package/dist/a2ui/agent-to-renderer.d.ts.map +0 -1
- package/dist/a2ui/agent-to-renderer.js.map +0 -1
- package/dist/a2ui/catalog.d.ts.map +0 -1
- package/dist/a2ui/catalog.js.map +0 -1
- package/dist/a2ui/common-types.d.ts.map +0 -1
- package/dist/a2ui/common-types.js.map +0 -1
- package/dist/a2ui/index.d.ts.map +0 -1
- package/dist/a2ui/index.js.map +0 -1
- package/dist/a2ui/interpreter.d.ts.map +0 -1
- package/dist/a2ui/interpreter.js.map +0 -1
- package/dist/a2ui/json-pointer.d.ts.map +0 -1
- package/dist/a2ui/json-pointer.js.map +0 -1
- package/dist/a2ui/renderer-to-agent.d.ts.map +0 -1
- package/dist/a2ui/renderer-to-agent.js.map +0 -1
- package/dist/a2ui/resolve.d.ts.map +0 -1
- package/dist/a2ui/resolve.js.map +0 -1
- package/dist/a2ui/tree.d.ts.map +0 -1
- package/dist/a2ui/tree.js.map +0 -1
- package/dist/ag-ui.d.ts.map +0 -1
- package/dist/ag-ui.js.map +0 -1
- package/dist/agui/encoder.d.ts.map +0 -1
- package/dist/agui/encoder.js.map +0 -1
- package/dist/agui/events.d.ts.map +0 -1
- package/dist/agui/events.js.map +0 -1
- package/dist/capability.d.ts.map +0 -1
- package/dist/capability.js.map +0 -1
- package/dist/core/a2ui/agent-to-renderer.d.ts.map +0 -1
- package/dist/core/a2ui/agent-to-renderer.js.map +0 -1
- package/dist/core/a2ui/catalog.d.ts.map +0 -1
- package/dist/core/a2ui/catalog.js.map +0 -1
- package/dist/core/a2ui/common-types.d.ts.map +0 -1
- package/dist/core/a2ui/common-types.js.map +0 -1
- package/dist/core/a2ui/index.d.ts.map +0 -1
- package/dist/core/a2ui/index.js.map +0 -1
- package/dist/core/a2ui/interpreter.d.ts.map +0 -1
- package/dist/core/a2ui/interpreter.js.map +0 -1
- package/dist/core/a2ui/json-pointer.d.ts.map +0 -1
- package/dist/core/a2ui/json-pointer.js.map +0 -1
- package/dist/core/a2ui/renderer-to-agent.d.ts.map +0 -1
- package/dist/core/a2ui/renderer-to-agent.js.map +0 -1
- package/dist/core/a2ui/resolve.d.ts.map +0 -1
- package/dist/core/a2ui/resolve.js.map +0 -1
- package/dist/core/a2ui/tree.d.ts.map +0 -1
- package/dist/core/a2ui/tree.js.map +0 -1
- package/dist/core/ag-ui.d.ts.map +0 -1
- package/dist/core/ag-ui.js.map +0 -1
- package/dist/core/capability.d.ts.map +0 -1
- package/dist/core/capability.js.map +0 -1
- package/dist/core/dom/dom-page-driver.d.ts.map +0 -1
- package/dist/core/dom/dom-page-driver.js.map +0 -1
- package/dist/core/dom/index.d.ts.map +0 -1
- package/dist/core/dom/index.js.map +0 -1
- package/dist/core/dom/model-context.d.ts.map +0 -1
- package/dist/core/dom/model-context.js.map +0 -1
- package/dist/core/element-handles.d.ts.map +0 -1
- package/dist/core/element-handles.js.map +0 -1
- package/dist/core/gen-ui/encoder.d.ts.map +0 -1
- package/dist/core/gen-ui/encoder.js.map +0 -1
- package/dist/core/gen-ui/events.d.ts.map +0 -1
- package/dist/core/gen-ui/events.js.map +0 -1
- package/dist/core/gen-ui/index.d.ts.map +0 -1
- package/dist/core/gen-ui/index.js.map +0 -1
- package/dist/core/guards.d.ts.map +0 -1
- package/dist/core/guards.js.map +0 -1
- package/dist/core/handle.d.ts.map +0 -1
- package/dist/core/handle.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/core/list-handles.d.ts.map +0 -1
- package/dist/core/list-handles.js.map +0 -1
- package/dist/core/mcp-ui-apps.d.ts.map +0 -1
- package/dist/core/mcp-ui-apps.js.map +0 -1
- package/dist/core/page-capabilities.d.ts.map +0 -1
- package/dist/core/page-capabilities.js.map +0 -1
- package/dist/core/page-driver.d.ts.map +0 -1
- package/dist/core/page-driver.js.map +0 -1
- package/dist/core/page-executor.d.ts.map +0 -1
- package/dist/core/page-executor.js.map +0 -1
- package/dist/core/webmcp.d.ts.map +0 -1
- package/dist/core/webmcp.js.map +0 -1
- package/dist/dom/dom-page-driver.d.ts.map +0 -1
- package/dist/dom/dom-page-driver.js.map +0 -1
- package/dist/dom/index.d.ts.map +0 -1
- package/dist/dom/index.js.map +0 -1
- package/dist/dom/model-context.d.ts.map +0 -1
- package/dist/dom/model-context.js.map +0 -1
- package/dist/element-handles.d.ts.map +0 -1
- package/dist/element-handles.js.map +0 -1
- package/dist/gen-ui/encoder.d.ts.map +0 -1
- package/dist/gen-ui/encoder.js.map +0 -1
- package/dist/gen-ui/events.d.ts.map +0 -1
- package/dist/gen-ui/events.js.map +0 -1
- package/dist/gen-ui/index.d.ts.map +0 -1
- package/dist/gen-ui/index.js.map +0 -1
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js.map +0 -1
- package/dist/handle.d.ts.map +0 -1
- package/dist/handle.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mcp-ui-apps.d.ts.map +0 -1
- package/dist/mcp-ui-apps.js.map +0 -1
- package/dist/mcp-ui.d.ts.map +0 -1
- package/dist/mcp-ui.js.map +0 -1
- package/dist/page-capabilities.d.ts.map +0 -1
- package/dist/page-capabilities.js.map +0 -1
- package/dist/page-driver.d.ts.map +0 -1
- package/dist/page-driver.js.map +0 -1
- package/dist/page-executor.d.ts.map +0 -1
- package/dist/page-executor.js.map +0 -1
- package/dist/webmcp.d.ts.map +0 -1
- package/dist/webmcp.js.map +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./validation.js";
|
|
2
|
+
export * from "./ports.js";
|
|
3
|
+
export * from "./archive.js";
|
|
4
|
+
export * from "./github.js";
|
|
5
|
+
export * from "./state.js";
|
|
6
|
+
export * from "./install-service.js";
|
|
7
|
+
export * from "./layout.js";
|
|
8
|
+
export * from "./live-registration.js";
|
|
9
|
+
export * from "./fetch-adapter.js";
|
|
10
|
+
export * from "./node-filesystem.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./validation.js";
|
|
2
|
+
export * from "./ports.js";
|
|
3
|
+
export * from "./archive.js";
|
|
4
|
+
export * from "./github.js";
|
|
5
|
+
export * from "./state.js";
|
|
6
|
+
export * from "./install-service.js";
|
|
7
|
+
export * from "./layout.js";
|
|
8
|
+
export * from "./live-registration.js";
|
|
9
|
+
export * from "./fetch-adapter.js";
|
|
10
|
+
export * from "./node-filesystem.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type SkillUploadFile } from "./validation.js";
|
|
2
|
+
import { type GitHubSkillSource } from "./github.js";
|
|
3
|
+
import type { SkillInstallDeps } from "./ports.js";
|
|
4
|
+
export interface ManagedSkill {
|
|
5
|
+
readonly toolId: string;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly enabled: boolean;
|
|
9
|
+
readonly source: "uploaded" | GitHubSkillSource;
|
|
10
|
+
}
|
|
11
|
+
export type SkillInstallInput = {
|
|
12
|
+
readonly workspaceId: string;
|
|
13
|
+
} & ({
|
|
14
|
+
readonly githubUrl: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly files: readonly SkillUploadFile[];
|
|
17
|
+
} | {
|
|
18
|
+
readonly archiveBase64: string;
|
|
19
|
+
});
|
|
20
|
+
export interface SkillInstaller {
|
|
21
|
+
installSkill(required: SkillInstallInput): Promise<ManagedSkill>;
|
|
22
|
+
listManagedSkills(required: {
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
}): Promise<ManagedSkill[]>;
|
|
25
|
+
setSkillEnabled(required: {
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
toolId: string;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
uninstallSkill(required: {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
toolId: string;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Validate before writes; publish via rename, with instance-local queues keyed by resolved root.
|
|
36
|
+
* Cross-process exclusivity remains the host filesystem/layout adapter's responsibility.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createSkillInstaller(deps: SkillInstallDeps, optional?: {
|
|
39
|
+
onChanged?: (required: {
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
toolId: string;
|
|
42
|
+
}) => void | Promise<void>;
|
|
43
|
+
}): SkillInstaller;
|
|
44
|
+
//# sourceMappingURL=install-service.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { SkillInputError, validateSkillFiles } from "./validation.js";
|
|
3
|
+
import { readSkillArchive } from "./archive.js";
|
|
4
|
+
import { fetchGitHubSkill } from "./github.js";
|
|
5
|
+
import { readSkillState } from "./state.js";
|
|
6
|
+
/** Validate before writes; publish via rename, with instance-local queues keyed by resolved root.
|
|
7
|
+
* Cross-process exclusivity remains the host filesystem/layout adapter's responsibility.
|
|
8
|
+
*/
|
|
9
|
+
export function createSkillInstaller(deps, optional = {}) {
|
|
10
|
+
const queues = new Map();
|
|
11
|
+
const layoutFor = (workspaceId) => {
|
|
12
|
+
const layout = deps.layout.resolve({ workspaceId });
|
|
13
|
+
if (!path.isAbsolute(layout.workspaceRoot) || !path.isAbsolute(layout.stagingRoot))
|
|
14
|
+
throw new SkillInputError({ message: "Skill roots must be absolute." });
|
|
15
|
+
if (!layout.stateFileName || /[/\\\x00]/.test(layout.stateFileName) || [".", ".."].includes(layout.stateFileName) || ["SKILL.md", "README.md", "LICENSE", "references", "scripts", "assets"].includes(layout.stateFileName))
|
|
16
|
+
throw new SkillInputError({ message: "Invalid skill state file name." });
|
|
17
|
+
return layout;
|
|
18
|
+
};
|
|
19
|
+
async function locked(workspaceId, work) {
|
|
20
|
+
const key = layoutFor(workspaceId).workspaceRoot;
|
|
21
|
+
const result = (queues.get(key) ?? Promise.resolve()).catch(() => { }).then(work);
|
|
22
|
+
queues.set(key, result);
|
|
23
|
+
try {
|
|
24
|
+
return await result;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
if (queues.get(key) === result)
|
|
28
|
+
queues.delete(key);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function listManagedSkills(required) {
|
|
32
|
+
const layout = layoutFor(required.workspaceId);
|
|
33
|
+
const sources = await deps.toolSourceLoader.load(required, { includeDisabled: true });
|
|
34
|
+
return Promise.all(sources.map(async (skill) => {
|
|
35
|
+
const directory = containedDirectory(layout.workspaceRoot, skill.directory);
|
|
36
|
+
return { toolId: skill.id, name: skill.skillName, description: skill.description,
|
|
37
|
+
...await readSkillState({ directory, stateFileName: layout.stateFileName, filesystem: deps.filesystem }) };
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
async function managedDirectory(required) {
|
|
41
|
+
const skill = (await deps.toolSourceLoader.load({ workspaceId: required.workspaceId }, { includeDisabled: true })).find(source => source.id === required.toolId);
|
|
42
|
+
if (!skill)
|
|
43
|
+
throw new SkillInputError({ message: "Skill was not found." });
|
|
44
|
+
const directory = containedDirectory(layoutFor(required.workspaceId).workspaceRoot, skill.directory);
|
|
45
|
+
if ((await deps.filesystem.lstat({ path: directory })).kind !== "directory")
|
|
46
|
+
throw new SkillInputError({ message: "Skill directory must be a regular directory." });
|
|
47
|
+
return directory;
|
|
48
|
+
}
|
|
49
|
+
const changed = async (workspaceId, toolId) => { await optional.onChanged?.({ workspaceId, toolId }); };
|
|
50
|
+
return {
|
|
51
|
+
listManagedSkills,
|
|
52
|
+
async installSkill(input) {
|
|
53
|
+
let upload;
|
|
54
|
+
let source = "uploaded";
|
|
55
|
+
if ("githubUrl" in input)
|
|
56
|
+
({ files: upload, source } = await fetchGitHubSkill({ githubUrl: input.githubUrl, fetch: deps.fetch }));
|
|
57
|
+
else if ("archiveBase64" in input)
|
|
58
|
+
upload = await readSkillArchive({ base64: input.archiveBase64, archiveReader: deps.archiveReader });
|
|
59
|
+
else
|
|
60
|
+
upload = input.files;
|
|
61
|
+
const validated = validateSkillFiles({ files: upload, yamlReader: deps.yamlReader });
|
|
62
|
+
const toolId = deps.toolId({ name: validated.name });
|
|
63
|
+
const layout = layoutFor(input.workspaceId);
|
|
64
|
+
return locked(input.workspaceId, async () => {
|
|
65
|
+
if ((await listManagedSkills(input)).some(skill => skill.toolId === toolId))
|
|
66
|
+
throw new SkillInputError({ message: `Skill '${validated.name}' is already installed. Remove it before installing another version.` });
|
|
67
|
+
await deps.filesystem.mkdir({ path: layout.workspaceRoot });
|
|
68
|
+
await deps.filesystem.mkdir({ path: layout.stagingRoot });
|
|
69
|
+
const staging = await deps.filesystem.mkdtemp({ prefix: path.join(layout.stagingRoot, "skill-") });
|
|
70
|
+
try {
|
|
71
|
+
for (const [relative, bytes] of validated.files) {
|
|
72
|
+
const target = path.join(staging, relative);
|
|
73
|
+
await deps.filesystem.mkdir({ path: path.dirname(target) });
|
|
74
|
+
await deps.filesystem.writeFile({ path: target, bytes, exclusive: true, mode: 0o600 });
|
|
75
|
+
}
|
|
76
|
+
await deps.filesystem.writeFile({ path: path.join(staging, layout.stateFileName), bytes: Buffer.from(JSON.stringify({ enabled: true, source })), exclusive: true, mode: 0o600 });
|
|
77
|
+
await deps.filesystem.rename({ from: staging, to: path.join(layout.workspaceRoot, validated.name) });
|
|
78
|
+
await changed(input.workspaceId, toolId);
|
|
79
|
+
return { toolId, name: validated.name, description: validated.description, enabled: true, source };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await deps.filesystem.remove({ path: staging }, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
async setSkillEnabled(input) {
|
|
87
|
+
if (typeof input.enabled !== "boolean")
|
|
88
|
+
throw new SkillInputError({ message: "enabled must be a boolean." });
|
|
89
|
+
await locked(input.workspaceId, async () => {
|
|
90
|
+
const directory = await managedDirectory(input);
|
|
91
|
+
const layout = layoutFor(input.workspaceId);
|
|
92
|
+
const state = await readSkillState({ directory, stateFileName: layout.stateFileName, filesystem: deps.filesystem });
|
|
93
|
+
const id = deps.ids.next({});
|
|
94
|
+
if (!id || /[/\\\x00]/.test(id))
|
|
95
|
+
throw new SkillInputError({ message: "Invalid skill temporary id." });
|
|
96
|
+
const temporary = path.join(directory, `.skill-state-${id}.tmp`);
|
|
97
|
+
try {
|
|
98
|
+
// A rejected write can leave partial bytes; cleanup must cover writing as well as rename.
|
|
99
|
+
await deps.filesystem.writeFile({ path: temporary, bytes: Buffer.from(JSON.stringify({ ...state, enabled: input.enabled })), exclusive: true, mode: 0o600 });
|
|
100
|
+
await deps.filesystem.rename({ from: temporary, to: path.join(directory, layout.stateFileName) });
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await deps.filesystem.remove({ path: temporary }, { force: true });
|
|
104
|
+
}
|
|
105
|
+
await changed(input.workspaceId, input.toolId);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
async uninstallSkill(input) {
|
|
109
|
+
await locked(input.workspaceId, async () => {
|
|
110
|
+
await deps.filesystem.remove({ path: await managedDirectory(input) }, { recursive: true });
|
|
111
|
+
await changed(input.workspaceId, input.toolId);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function containedDirectory(root, directory) {
|
|
117
|
+
if (!directory || directory === "." || directory === ".." || /[/\\\x00]/.test(directory))
|
|
118
|
+
throw new SkillInputError({ message: "Skill directory must be one safe segment." });
|
|
119
|
+
return path.join(root, directory);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=install-service.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SkillLayoutPort } from './ports.js';
|
|
2
|
+
/** Every storage location is host-owned; this adapter has no product or cwd defaults.
|
|
3
|
+
* Layout resolves the same required root/stagingRoot/workspaceDirectory/stateFileName
|
|
4
|
+
* contract used by the installer. No site, environment or cwd defaults are allowed: separate
|
|
5
|
+
* workspace roots and staging roots keep installation writes inside the host's selected paths.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSkillLayout(required: {
|
|
8
|
+
root: string;
|
|
9
|
+
stagingRoot: string;
|
|
10
|
+
workspaceDirectory: string;
|
|
11
|
+
stateFileName: string;
|
|
12
|
+
}, _optional?: Record<string, never>): SkillLayoutPort;
|
|
13
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/** Every storage location is host-owned; this adapter has no product or cwd defaults.
|
|
3
|
+
* Layout resolves the same required root/stagingRoot/workspaceDirectory/stateFileName
|
|
4
|
+
* contract used by the installer. No site, environment or cwd defaults are allowed: separate
|
|
5
|
+
* workspace roots and staging roots keep installation writes inside the host's selected paths.
|
|
6
|
+
*/
|
|
7
|
+
export function createSkillLayout(required, _optional = {}) {
|
|
8
|
+
const { root, stagingRoot, workspaceDirectory, stateFileName } = required;
|
|
9
|
+
if (!path.isAbsolute(root) || !path.isAbsolute(stagingRoot))
|
|
10
|
+
throw new Error('Skill roots must be absolute paths.');
|
|
11
|
+
if (!/^[a-z0-9]+(?:[-.][a-z0-9]+)*$/.test(workspaceDirectory))
|
|
12
|
+
throw new Error('Invalid workspace directory segment.');
|
|
13
|
+
if (!stateFileName || /[/\\\x00]/.test(stateFileName) || ['.', '..', 'SKILL.md', 'README.md', 'LICENSE', 'references', 'scripts', 'assets'].includes(stateFileName))
|
|
14
|
+
throw new Error('Invalid skill state file name.');
|
|
15
|
+
return Object.freeze({ resolve({ workspaceId }) {
|
|
16
|
+
const normalized = workspaceId.toLowerCase();
|
|
17
|
+
if (normalized.length > 64 || !/^[a-z0-9]+(?:[-.][a-z0-9]+)*$/.test(normalized))
|
|
18
|
+
throw new Error(`resolve: '${workspaceId}' is not a valid workspace id`);
|
|
19
|
+
return { workspaceRoot: path.join(root, workspaceDirectory, normalized), stagingRoot: path.resolve(stagingRoot), stateFileName };
|
|
20
|
+
} });
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface SkillRegistration<Descriptor extends {
|
|
2
|
+
id: string;
|
|
3
|
+
} = {
|
|
4
|
+
id: string;
|
|
5
|
+
}, Context = unknown> {
|
|
6
|
+
readonly descriptor: Descriptor;
|
|
7
|
+
readonly policy: {
|
|
8
|
+
authorize(required: {
|
|
9
|
+
context: Context;
|
|
10
|
+
}): unknown;
|
|
11
|
+
};
|
|
12
|
+
readonly handler: (required: {
|
|
13
|
+
context: Context;
|
|
14
|
+
}) => unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface SkillRegistryPort<Descriptor extends {
|
|
17
|
+
id: string;
|
|
18
|
+
} = {
|
|
19
|
+
id: string;
|
|
20
|
+
}, Context = unknown> {
|
|
21
|
+
list(required: Record<string, never>): Descriptor[];
|
|
22
|
+
has(required: {
|
|
23
|
+
id: string;
|
|
24
|
+
}): boolean;
|
|
25
|
+
register(required: {
|
|
26
|
+
tool: SkillRegistration<Descriptor, Context>;
|
|
27
|
+
}): void;
|
|
28
|
+
}
|
|
29
|
+
export interface SkillsRefreshRegistry {
|
|
30
|
+
refreshInstalledSkills?(required: Record<string, never>): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
/** Keep append-only registry slots while dispatching current registrations or failing closed. */
|
|
33
|
+
export declare function createLiveSkillRegistration<Descriptor extends {
|
|
34
|
+
id: string;
|
|
35
|
+
}, Context>(required: {
|
|
36
|
+
registry: SkillRegistryPort<Descriptor, Context>;
|
|
37
|
+
inactiveError: (required: {
|
|
38
|
+
id: string;
|
|
39
|
+
}) => Error;
|
|
40
|
+
}, _optional?: Record<string, never>): ({ tools }: {
|
|
41
|
+
tools: readonly SkillRegistration<Descriptor, Context>[];
|
|
42
|
+
}) => boolean;
|
|
43
|
+
/** Prevent older loads overwriting newer registrations; reset after both success and failure. */
|
|
44
|
+
export declare function createSkillRefresher<T>(required: {
|
|
45
|
+
load: (required: Record<string, never>) => Promise<readonly T[]>;
|
|
46
|
+
replace: (required: {
|
|
47
|
+
tools: readonly T[];
|
|
48
|
+
}) => boolean;
|
|
49
|
+
}, _optional?: Record<string, never>): {
|
|
50
|
+
refreshInstalledSkills(required: Record<string, never>): Promise<boolean>;
|
|
51
|
+
};
|
|
52
|
+
export declare function createSkillRefreshMiddleware(required: {
|
|
53
|
+
registry: SkillsRefreshRegistry;
|
|
54
|
+
}, optional?: {
|
|
55
|
+
onChanged?: (required: Record<string, never>) => void;
|
|
56
|
+
}): ({ next }: {
|
|
57
|
+
next: (required: Record<string, never>, optional?: {
|
|
58
|
+
error?: unknown;
|
|
59
|
+
}) => void;
|
|
60
|
+
}) => Promise<void>;
|
|
61
|
+
//# sourceMappingURL=live-registration.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Keep append-only registry slots while dispatching current registrations or failing closed. */
|
|
2
|
+
export function createLiveSkillRegistration(required, _optional = {}) {
|
|
3
|
+
const { registry } = required;
|
|
4
|
+
const active = new Map();
|
|
5
|
+
const slots = new Map();
|
|
6
|
+
const list = registry.list.bind(registry), has = registry.has.bind(registry);
|
|
7
|
+
registry.list = input => list(input).filter(d => !slots.has(d.id) || active.has(d.id));
|
|
8
|
+
registry.has = input => slots.has(input.id) ? active.has(input.id) : has(input);
|
|
9
|
+
let fingerprint = '';
|
|
10
|
+
return ({ tools }) => {
|
|
11
|
+
const nextFingerprint = JSON.stringify(tools.map(t => t.descriptor));
|
|
12
|
+
const next = new Map(tools.map(t => [t.descriptor.id, t]));
|
|
13
|
+
if (next.size !== tools.length)
|
|
14
|
+
throw new Error('Duplicate skill registration ID.');
|
|
15
|
+
for (const tool of tools) {
|
|
16
|
+
const id = tool.descriptor.id, existing = slots.get(id);
|
|
17
|
+
if (existing)
|
|
18
|
+
Object.assign(existing.descriptor, tool.descriptor);
|
|
19
|
+
else {
|
|
20
|
+
const slot = {
|
|
21
|
+
descriptor: { ...tool.descriptor },
|
|
22
|
+
policy: { authorize: input => active.get(id)?.policy.authorize(input) ?? 'deny' },
|
|
23
|
+
handler: input => {
|
|
24
|
+
const current = active.get(id);
|
|
25
|
+
if (!current)
|
|
26
|
+
throw required.inactiveError({ id });
|
|
27
|
+
return current.handler(input);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
registry.register({ tool: slot });
|
|
31
|
+
slots.set(id, slot);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
active.clear();
|
|
35
|
+
for (const [id, tool] of next)
|
|
36
|
+
active.set(id, tool);
|
|
37
|
+
const changed = fingerprint !== nextFingerprint;
|
|
38
|
+
fingerprint = nextFingerprint;
|
|
39
|
+
return changed;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Prevent older loads overwriting newer registrations; reset after both success and failure. */
|
|
43
|
+
export function createSkillRefresher(required, _optional = {}) {
|
|
44
|
+
let pending;
|
|
45
|
+
return { refreshInstalledSkills: (_required) => {
|
|
46
|
+
if (!pending)
|
|
47
|
+
pending = required.load({}).then(tools => required.replace({ tools })).finally(() => { pending = undefined; });
|
|
48
|
+
return pending;
|
|
49
|
+
} };
|
|
50
|
+
}
|
|
51
|
+
export function createSkillRefreshMiddleware(required, optional = {}) {
|
|
52
|
+
return async ({ next }) => {
|
|
53
|
+
try {
|
|
54
|
+
if (await required.registry.refreshInstalledSkills?.({}))
|
|
55
|
+
optional.onChanged?.({});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
next({}, { error });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
next({});
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=live-registration.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FilesystemPort } from './ports.js';
|
|
2
|
+
/** Node adapter kept separate from the injected installation policy. */
|
|
3
|
+
export declare function createNodeSkillFilesystem(_required: Record<string, never>, _optional?: Record<string, never>): FilesystemPort;
|
|
4
|
+
//# sourceMappingURL=node-filesystem.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { lstat, mkdir, mkdtemp, open, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
/** Node adapter kept separate from the injected installation policy. */
|
|
4
|
+
export function createNodeSkillFilesystem(_required, _optional = {}) {
|
|
5
|
+
return {
|
|
6
|
+
mkdir: async ({ path }) => { await mkdir(path, { recursive: true }); },
|
|
7
|
+
mkdtemp: ({ prefix }) => mkdtemp(prefix),
|
|
8
|
+
lstat: async ({ path }) => {
|
|
9
|
+
const stat = await lstat(path);
|
|
10
|
+
return { kind: stat.isSymbolicLink() ? 'symlink' : stat.isFile() ? 'file' : stat.isDirectory() ? 'directory' : 'other', size: stat.size };
|
|
11
|
+
},
|
|
12
|
+
readFile: async ({ path, maxBytes }) => {
|
|
13
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
14
|
+
try {
|
|
15
|
+
const stat = await handle.stat();
|
|
16
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
17
|
+
throw new Error('Invalid skill installation record.');
|
|
18
|
+
const chunks = [];
|
|
19
|
+
let size = 0;
|
|
20
|
+
for (;;) {
|
|
21
|
+
const chunk = Buffer.alloc(Math.min(4096, maxBytes + 1 - size));
|
|
22
|
+
const result = await handle.read(chunk, 0, chunk.length, null);
|
|
23
|
+
if (!result.bytesRead)
|
|
24
|
+
break;
|
|
25
|
+
size += result.bytesRead;
|
|
26
|
+
if (size > maxBytes)
|
|
27
|
+
throw new Error('Invalid skill installation record.');
|
|
28
|
+
chunks.push(chunk.subarray(0, result.bytesRead));
|
|
29
|
+
}
|
|
30
|
+
return Buffer.concat(chunks);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
await handle.close();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
writeFile: ({ path, bytes, exclusive, mode }) => writeFile(path, bytes, { flag: exclusive ? 'wx' : 'w', mode }),
|
|
37
|
+
rename: ({ from, to }) => rename(from, to),
|
|
38
|
+
remove: ({ path }, options = {}) => rm(path, options),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=node-filesystem.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** The adapter uses failsafe YAML and rejects duplicate keys; the installer never parses YAML itself. */
|
|
2
|
+
export interface YamlReaderPort {
|
|
3
|
+
read(required: {
|
|
4
|
+
yaml: string;
|
|
5
|
+
}): unknown;
|
|
6
|
+
}
|
|
7
|
+
/** Layout is host-owned. Staging and workspace roots must share a rename-capable filesystem. */
|
|
8
|
+
export interface SkillLayoutPort {
|
|
9
|
+
resolve(required: {
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
}): {
|
|
12
|
+
workspaceRoot: string;
|
|
13
|
+
stagingRoot: string;
|
|
14
|
+
stateFileName: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface SkillFilesystemPort {
|
|
18
|
+
mkdir(required: {
|
|
19
|
+
path: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
mkdtemp(required: {
|
|
22
|
+
prefix: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
writeFile(required: {
|
|
25
|
+
path: string;
|
|
26
|
+
bytes: Uint8Array;
|
|
27
|
+
mode: number;
|
|
28
|
+
exclusive: true;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
readFile(required: {
|
|
31
|
+
path: string;
|
|
32
|
+
maxBytes: number;
|
|
33
|
+
}): Promise<Uint8Array>;
|
|
34
|
+
lstat(required: {
|
|
35
|
+
path: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
kind: "file" | "directory" | "symlink" | "other";
|
|
38
|
+
size: number;
|
|
39
|
+
}>;
|
|
40
|
+
rename(required: {
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
remove(required: {
|
|
45
|
+
path: string;
|
|
46
|
+
}, optional?: {
|
|
47
|
+
recursive?: boolean;
|
|
48
|
+
force?: boolean;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/** Compatibility name used by the original node adapter and copied contract tests. */
|
|
52
|
+
export type FilesystemPort = SkillFilesystemPort;
|
|
53
|
+
/** Adapters must expose lstat semantics: symlinks are never classified as regular files/directories. */
|
|
54
|
+
export interface SkillToolSource {
|
|
55
|
+
id: string;
|
|
56
|
+
skillName: string;
|
|
57
|
+
description: string;
|
|
58
|
+
directory: string;
|
|
59
|
+
}
|
|
60
|
+
export interface ToolSourceLoaderPort {
|
|
61
|
+
load(required: {
|
|
62
|
+
workspaceId: string;
|
|
63
|
+
}, optional?: {
|
|
64
|
+
includeDisabled?: boolean;
|
|
65
|
+
}): Promise<readonly SkillToolSource[]>;
|
|
66
|
+
}
|
|
67
|
+
export interface ArchiveEntry {
|
|
68
|
+
path: string;
|
|
69
|
+
kind: "file" | "directory" | "other";
|
|
70
|
+
size: number;
|
|
71
|
+
/** Closing/returning the iterator must release the entry stream, including early refusal. */
|
|
72
|
+
read(required: Record<string, never>): AsyncIterable<Uint8Array>;
|
|
73
|
+
}
|
|
74
|
+
/** ZIP adapter must use strict names, validate sizes, and expose lazy entries without extraction. */
|
|
75
|
+
export interface ArchiveReaderPort {
|
|
76
|
+
open(required: {
|
|
77
|
+
bytes: Uint8Array;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
entries: AsyncIterable<ArchiveEntry>;
|
|
80
|
+
close(required: Record<string, never>): void;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
/** HTTP transport follows the argument-object convention; hosts own credentials and networking. */
|
|
84
|
+
export type SkillFetchPort = (required: {
|
|
85
|
+
url: string;
|
|
86
|
+
}, optional?: RequestInit) => Promise<Response>;
|
|
87
|
+
export interface SkillInstallDeps {
|
|
88
|
+
layout: SkillLayoutPort;
|
|
89
|
+
filesystem: SkillFilesystemPort;
|
|
90
|
+
archiveReader: ArchiveReaderPort;
|
|
91
|
+
yamlReader: YamlReaderPort;
|
|
92
|
+
fetch: SkillFetchPort;
|
|
93
|
+
ids: {
|
|
94
|
+
next(required: Record<string, never>): string;
|
|
95
|
+
};
|
|
96
|
+
toolId: (required: {
|
|
97
|
+
name: string;
|
|
98
|
+
}) => string;
|
|
99
|
+
toolSourceLoader: ToolSourceLoaderPort;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GitHubSkillSource } from "./github.js";
|
|
2
|
+
import type { SkillFilesystemPort } from "./ports.js";
|
|
3
|
+
export interface SkillState {
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly source: "uploaded" | GitHubSkillSource;
|
|
6
|
+
}
|
|
7
|
+
/** Missing legacy records default to enabled; malformed records and symlinks fail closed. */
|
|
8
|
+
export declare function readSkillState(required: {
|
|
9
|
+
directory: string;
|
|
10
|
+
stateFileName: string;
|
|
11
|
+
filesystem: SkillFilesystemPort;
|
|
12
|
+
}, _optional?: Record<string, never>): Promise<SkillState>;
|
|
13
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/** Missing legacy records default to enabled; malformed records and symlinks fail closed. */
|
|
3
|
+
export async function readSkillState(required, _optional = {}) {
|
|
4
|
+
const statePath = path.join(required.directory, required.stateFileName);
|
|
5
|
+
try {
|
|
6
|
+
const info = await required.filesystem.lstat({ path: statePath });
|
|
7
|
+
if (info.kind !== "file" || info.size > 8192)
|
|
8
|
+
throw new Error("Invalid skill installation record.");
|
|
9
|
+
const bytes = await required.filesystem.readFile({ path: statePath, maxBytes: 8192 });
|
|
10
|
+
if (bytes.length > 8192)
|
|
11
|
+
throw new Error("Invalid skill installation record.");
|
|
12
|
+
const state = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
13
|
+
if (!state || typeof state.enabled !== "boolean" || (state.source !== "uploaded" && (!state.source || typeof state.source.githubUrl !== "string" || !/^[a-f0-9]{40}$/.test(state.source.commit))))
|
|
14
|
+
throw new Error("Invalid skill installation record.");
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === "ENOENT")
|
|
19
|
+
return { enabled: true, source: "uploaded" };
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { YamlReaderPort } from "./ports.js";
|
|
2
|
+
export type SkillYamlPort = YamlReaderPort;
|
|
3
|
+
export declare class SkillInputError extends Error {
|
|
4
|
+
constructor({ message }: {
|
|
5
|
+
message: string;
|
|
6
|
+
}, optional?: {
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export interface SkillUploadFile {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly contentBase64: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const MAX_SKILL_BYTES: number;
|
|
15
|
+
export declare const MAX_SKILL_FILES = 256;
|
|
16
|
+
export declare const MAX_SKILL_FILE_BYTES: number;
|
|
17
|
+
/** Decodes bounded canonical base64; malformed encodings never silently turn into empty files. */
|
|
18
|
+
export declare function decodeSkillBase64({ value }: {
|
|
19
|
+
value: string;
|
|
20
|
+
}, { maxBytes }?: {
|
|
21
|
+
maxBytes?: number;
|
|
22
|
+
}): Buffer;
|
|
23
|
+
export declare function validateSkillPath({ filePath }: {
|
|
24
|
+
filePath: string;
|
|
25
|
+
}, _optional?: Record<string, never>): void;
|
|
26
|
+
/** Strict frontmatter for new installs; the legacy disk loader remains backwards compatible. */
|
|
27
|
+
export declare function validateSkillMarkdown({ markdown, yamlReader }: {
|
|
28
|
+
markdown: string;
|
|
29
|
+
yamlReader: YamlReaderPort;
|
|
30
|
+
}, _optional?: Record<string, never>): {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
/** Validates the whole package before writes, including cross-file collisions and combined limits.
|
|
35
|
+
* @complexity O(total bytes + files × path length), bounded to 8 MiB / 256 files.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateSkillFiles({ files: upload, yamlReader }: {
|
|
38
|
+
files: readonly SkillUploadFile[];
|
|
39
|
+
yamlReader: YamlReaderPort;
|
|
40
|
+
}, _optional?: Record<string, never>): {
|
|
41
|
+
files: Map<string, Buffer<ArrayBufferLike>>;
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=validation.d.ts.map
|