diff --git a/dist/src/exportArtifacts.js b/dist/src/exportArtifacts.js index 9dd1203..e1380df 100644 --- a/dist/src/exportArtifacts.js +++ b/dist/src/exportArtifacts.js @@ -76,13 +76,19 @@ export async function exportXWorkmateArtifacts(input) { const artifactScope = requestedArtifactScope || expectedArtifactScope; const scopeRoot = resolveScopeRoot(workspaceRoot, artifactScope); const scopeKind = "task"; - const candidates = await collectCandidates({ - scanRoot: scopeRoot, - relativeRoot: scopeRoot, - sinceUnixMs, - skipTaskScopeRoot: false, - warnings, - }); + const scopePrepared = await directoryExists(scopeRoot); + const candidates = scopePrepared + ? await collectCandidates({ + scanRoot: scopeRoot, + relativeRoot: scopeRoot, + sinceUnixMs, + skipTaskScopeRoot: false, + warnings, + }) + : []; + if (!scopePrepared) { + warnings.push("artifact scope is not prepared"); + } candidates.sort((left, right) => { if (right.mtimeMs !== left.mtimeMs) { return right.mtimeMs - left.mtimeMs; @@ -397,6 +403,15 @@ function safeTaskSessionScope(value) { } return scope; } +async function directoryExists(absolutePath) { + try { + const stat = await fs.stat(absolutePath); + return stat.isDirectory(); + } + catch { + return false; + } +} function safeArtifactRefRunScope(value) { try { return safeTaskArtifactScope(value); diff --git a/index.test.ts b/index.test.ts index fc0a412..470cbc5 100644 --- a/index.test.ts +++ b/index.test.ts @@ -7,6 +7,11 @@ import plugin from "./index.js"; import { prepareXWorkmateArtifacts } from "./src/exportArtifacts.js"; type GatewayMethodHandler = Parameters[1]; +type GatewayMethodResponse = { + ok: boolean; + payload?: Record; + error?: { code?: string; message?: string }; +}; describe("plugin registration", () => { it("declares registered agent tools in the manifest contract", () => { @@ -50,6 +55,68 @@ describe("plugin registration", () => { }); }); + it("executes registered gateway methods against the current task scope", async () => { + const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-gateway-")); + const methods = new Map(); + const api = { + config: {}, + pluginConfig: { workspaceDir: root }, + registerGatewayMethod: (method: string, handler: GatewayMethodHandler) => { + methods.set(method, handler); + }, + registerTool: () => undefined, + } as unknown as OpenClawPluginApi; + + plugin.register(api); + + const prepared = await callGatewayMethod(methods, "xworkmate.artifacts.prepare", { + sessionKey: "thread-main", + runId: "turn-1", + }); + expect(prepared.ok).toBe(true); + expect(prepared.payload?.artifactScope).toBe("tasks/thread-main/turn-1"); + const artifactDirectory = String(prepared.payload?.artifactDirectory); + + const emptyExport = await callGatewayMethod(methods, "xworkmate.artifacts.export", { + sessionKey: "thread-main", + runId: "turn-1", + artifactScope: prepared.payload?.artifactScope, + }); + expect(emptyExport.ok).toBe(true); + expect(emptyExport.payload?.artifacts).toEqual([]); + expect(emptyExport.payload?.warnings).toEqual([]); + + await fs.promises.mkdir(path.join(artifactDirectory, "reports"), { recursive: true }); + await fs.promises.writeFile(path.join(artifactDirectory, "reports", "final.md"), "final"); + + const listed = await callGatewayMethod(methods, "xworkmate.artifacts.list", { + sessionKey: "thread-main", + runId: "turn-1", + artifactScope: prepared.payload?.artifactScope, + }); + expect(listed.ok).toBe(true); + expect(listed.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md" }]); + const listedArtifacts = listed.payload?.artifacts as Array>; + expect(listedArtifacts[0]).not.toHaveProperty("content"); + + const read = await callGatewayMethod(methods, "xworkmate.artifacts.read", { + sessionKey: "thread-main", + runId: "turn-1", + artifactScope: prepared.payload?.artifactScope, + relativePath: "reports/final.md", + }); + expect(read.ok).toBe(true); + expect(read.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md", encoding: "base64" }]); + + const unprepared = await callGatewayMethod(methods, "xworkmate.artifacts.export", { + sessionKey: "thread-main", + runId: "turn-unprepared", + }); + expect(unprepared.ok).toBe(true); + expect(unprepared.payload?.artifacts).toEqual([]); + expect(unprepared.payload?.warnings).toEqual(["artifact scope is not prepared"]); + }); + it("does not invent default session or run ids for the optional agent tool", async () => { const tools: Array<{ tool: unknown; options: unknown }> = []; const api = { @@ -120,3 +187,25 @@ describe("plugin registration", () => { expect(result.content[0]?.text).not.toContain("global.txt"); }); }); + +async function callGatewayMethod( + methods: Map, + method: string, + params: Record, +): Promise { + const handler = methods.get(method); + if (!handler) { + throw new Error(`missing gateway method ${method}`); + } + let response: GatewayMethodResponse | undefined; + await handler({ + params, + respond: (ok: boolean, payload?: Record, error?: GatewayMethodResponse["error"]) => { + response = { ok, payload, error }; + }, + } as Parameters[0]); + if (!response) { + throw new Error(`gateway method ${method} did not respond`); + } + return response; +} diff --git a/src/exportArtifacts.ts b/src/exportArtifacts.ts index dba4b71..f454cef 100644 --- a/src/exportArtifacts.ts +++ b/src/exportArtifacts.ts @@ -157,13 +157,19 @@ export async function exportXWorkmateArtifacts(input: ExportInput): Promise { if (right.mtimeMs !== left.mtimeMs) { @@ -523,6 +529,15 @@ function safeTaskSessionScope(value: unknown): string { return scope; } +async function directoryExists(absolutePath: string): Promise { + try { + const stat = await fs.stat(absolutePath); + return stat.isDirectory(); + } catch { + return false; + } +} + function safeArtifactRefRunScope(value: unknown): string { try { return safeTaskArtifactScope(value);