forked from EduCraft/curriculum-project-hub
feat(hub): concurrent multi-PDF convert_pdf_to_md + readable skills (v0.0.40)
Teachers convert many PDFs in one tool call with bounded Docmind concurrency. Each item keeps its own output_dir/document.md and UsageFact; failures are per-file. Mirror role skills to .cph/runtime-skills and CPH_RUNTIME_SKILLS_DIR so agents can Read SKILL.md instead of dead .claude/sandbox stubs.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { mkdir, mkdtemp, realpath, rm, symlink } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createAgentSecurityPolicy } from "../../src/agent/security.js";
|
||||
import { importSkillDirectory } from "../../src/agent/skillStore.js";
|
||||
|
||||
describe("agent subprocess security policy", () => {
|
||||
const roots: string[] = [];
|
||||
@@ -135,6 +136,43 @@ describe("agent subprocess security policy", () => {
|
||||
})).rejects.toThrow("Agent temp path is too long for sandbox bridge sockets");
|
||||
});
|
||||
|
||||
|
||||
it("mirrors selected skills under .cph/runtime-skills and exposes CPH_RUNTIME_SKILLS_DIR", async () => {
|
||||
const { root, workspaceRoot, workspace } = await makeWorkspace();
|
||||
const storeRoot = join(root, "skills-store");
|
||||
const skillSource = join(root, "skill-src", "pdf-to-md");
|
||||
await mkdir(skillSource, { recursive: true });
|
||||
await writeFile(
|
||||
join(skillSource, "SKILL.md"),
|
||||
"---\nname: pdf-to-md\ndescription: convert\n---\n# pdf-to-md\nbatch items\n",
|
||||
);
|
||||
const installed = await importSkillDirectory({ sourceDir: skillSource, storeRoot });
|
||||
const policy = await createAgentSecurityPolicy({
|
||||
runId: "run-skills",
|
||||
workspaceRoot,
|
||||
workspaceDir: workspace,
|
||||
skills: [{
|
||||
name: "pdf-to-md",
|
||||
version: "1",
|
||||
contentDigest: installed.contentDigest,
|
||||
}],
|
||||
hostEnv: {
|
||||
PATH: "/usr/bin:/bin",
|
||||
HUB_SKILL_STORE_ROOT: storeRoot,
|
||||
},
|
||||
});
|
||||
const canonicalWorkspace = await realpath(workspace);
|
||||
const mirrored = join(canonicalWorkspace, ".cph", "runtime-skills", "pdf-to-md", "SKILL.md");
|
||||
await expect(readFile(mirrored, "utf8")).resolves.toContain("batch items");
|
||||
expect(policy.env.CPH_RUNTIME_SKILLS_DIR).toBe(join(canonicalWorkspace, ".cph", "runtime-skills"));
|
||||
expect(policy.env.CPH_RUNTIME_SKILLS_REL).toBe(join(".cph", "runtime-skills"));
|
||||
expect(policy.skillIds).toEqual(["cph-runtime:pdf-to-md"]);
|
||||
expect(policy.sandbox.filesystem.allowRead).toEqual(
|
||||
expect.arrayContaining([canonicalWorkspace, policy.skillPluginRoot]),
|
||||
);
|
||||
await policy.cleanup();
|
||||
});
|
||||
|
||||
it("rejects a project workspace whose real path escapes the configured workspace root", async () => {
|
||||
const { root, workspaceRoot } = await makeWorkspace();
|
||||
const outside = join(root, "outside");
|
||||
|
||||
Reference in New Issue
Block a user