feat: publish Agent Studio integrations and setup guide
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { access, realpath, stat } from "node:fs/promises";
|
||||
import { access, mkdir, mkdtemp, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
||||
import { realpathSync } from "node:fs";
|
||||
import { delimiter, isAbsolute, relative, resolve, sep } from "node:path";
|
||||
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
import { SafeWorkspace } from "../workspace/safe-workspace.js";
|
||||
import {
|
||||
DefaultResourceLoader,
|
||||
SettingsManager,
|
||||
@@ -49,13 +50,16 @@ export type SkillCatalogOptions = {
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
extraDirsValue?: string;
|
||||
resources?: Record<string, readonly string[]>;
|
||||
};
|
||||
|
||||
export class SkillCatalog {
|
||||
readonly #cwd: string;
|
||||
readonly #agentDir: string;
|
||||
readonly #extraDirsValue: string | undefined;
|
||||
readonly #resources: Record<string, readonly string[]>;
|
||||
#entries: SkillCatalogEntry[] = [];
|
||||
#skills: Skill[] = [];
|
||||
#indexedSkills: IndexedSkill[] = [];
|
||||
#diagnostics: SkillDiagnostic[] = [];
|
||||
|
||||
@@ -63,6 +67,7 @@ export class SkillCatalog {
|
||||
this.#cwd = canonicalPathSync(options.cwd);
|
||||
this.#agentDir = canonicalPathSync(options.agentDir);
|
||||
this.#extraDirsValue = options.extraDirsValue;
|
||||
this.#resources = Object.fromEntries(Object.entries(options.resources ?? {}).map(([path, files]) => [canonicalPathSync(path), files]));
|
||||
}
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
@@ -81,6 +86,7 @@ export class SkillCatalog {
|
||||
|
||||
await loader.reload();
|
||||
const loaded = loader.getSkills();
|
||||
this.#skills = loaded.skills;
|
||||
const loaderDiagnostics = loaded.diagnostics.map((diagnostic) =>
|
||||
mapDiagnostic(diagnostic, this.#cwd),
|
||||
);
|
||||
@@ -106,6 +112,34 @@ export class SkillCatalog {
|
||||
return new SkillIndex(this.#indexedSkills);
|
||||
}
|
||||
|
||||
/** Copy only catalogued instruction bodies, never arbitrary project files or credentials. */
|
||||
public async createSessionSnapshot(workspaceRoot: string): Promise<{ skills: Skill[]; index: SkillIndex }> {
|
||||
const root = await realpath(workspaceRoot);
|
||||
const snapshotRoot = await mkdtemp(join(root, ".skills-"));
|
||||
const skills = await Promise.all(this.#skills.map(async (skill, index) => {
|
||||
const baseDir = join(snapshotRoot, String(index));
|
||||
await mkdir(baseDir, { mode: 0o700 });
|
||||
const filePath = join(baseDir, "SKILL.md");
|
||||
await writeFile(filePath, await readFile(skill.filePath), { flag: "wx", mode: 0o400 });
|
||||
const source = await SafeWorkspace.create(skill.baseDir);
|
||||
for (const resource of this.#resources[canonicalPathSync(skill.filePath)] ?? []) {
|
||||
if (isAbsolute(resource) || resource.split(/[\\/]/).includes("..")) throw new Error("Unsafe Skill resource path");
|
||||
const from = await source.resolveReadable(resource);
|
||||
if ((await stat(from)).size > 1_048_576) throw new Error("Skill resource exceeds size limit");
|
||||
const target = join(baseDir, resource);
|
||||
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
||||
await writeFile(target, await readFile(from), { flag: "wx", mode: 0o400 });
|
||||
}
|
||||
return { ...skill, baseDir, filePath };
|
||||
}));
|
||||
return {
|
||||
skills,
|
||||
index: new SkillIndex(skills.map((skill) => ({
|
||||
name: skill.name, source: displaySource(skill), canonicalFilePath: skill.filePath,
|
||||
}))),
|
||||
};
|
||||
}
|
||||
|
||||
public readonly diagnostics = (): SkillDiagnostic[] => {
|
||||
return this.#diagnostics.map((diagnostic) => ({ ...diagnostic }));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user