diff --git a/README.md b/README.md index 47891fd..6d3f04d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# xworkmate-artifacts +# openclaw-multi-session-plugins -OpenClaw Gateway plugin that exports structured workspace artifact manifests for XWorkmate. +OpenClaw plugin for logical multi-session isolation and scoped XWorkmate artifact manifests. ## Why XWorkmate talks to OpenClaw through `xworkmate-bridge` using the existing `/gateway/openclaw` task contract. The bridge sends `chat.send`, waits for -`agent.wait`, then asks this plugin for a structured artifact manifest. The APP -can then sync generated files into its local thread workspace without changing -the UI or adding provider-specific routes. +`agent.wait`, then asks this plugin for a session/run-scoped artifact manifest. +The APP can then sync generated files into its local thread workspace without +changing the UI or adding provider-specific routes. It registers four Gateway methods: @@ -28,16 +28,16 @@ into the APP `artifacts[]` contract. Install from the npm package through OpenClaw: ```bash -openclaw plugins install xworkmate-artifacts -openclaw plugins enable xworkmate-artifacts +openclaw plugins install openclaw-multi-session-plugins +openclaw plugins enable openclaw-multi-session-plugins ``` Or install from a Git checkout for development: ```bash -git clone https://github.com/x-evor/xworkmate-artifacts.git -openclaw plugins install --link ./xworkmate-artifacts -openclaw plugins enable xworkmate-artifacts +git clone https://github.com/x-evor/openclaw-multi-session-plugins.git +openclaw plugins install --link ./openclaw-multi-session-plugins +openclaw plugins enable openclaw-multi-session-plugins ``` Equivalent config shape for a linked checkout: @@ -47,11 +47,11 @@ Equivalent config shape for a linked checkout: "plugins": { "load": { "paths": [ - "/path/to/xworkmate-artifacts" + "/path/to/openclaw-multi-session-plugins" ] }, "entries": { - "xworkmate-artifacts": { + "openclaw-multi-session-plugins": { "enabled": true } } @@ -151,7 +151,7 @@ show a quick artifact table: { "id": "main", "tools": { - "allow": ["xworkmate_artifacts"] + "allow": ["openclaw_multi_session_artifacts"] } } ] diff --git a/dist/index.js b/dist/index.js index 9f133ec..0e48a47 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,8 +1,8 @@ import { exportXWorkmateArtifacts, prepareXWorkmateArtifacts, readXWorkmateArtifact, } from "./src/exportArtifacts.js"; const plugin = { - id: "xworkmate-artifacts", - name: "XWorkmate Artifacts", - description: "Exports structured artifact manifests from the OpenClaw workspace for XWorkmate.", + id: "openclaw-multi-session-plugins", + name: "openclaw-multi-session-plugins", + description: "OpenClaw logical isolation support for multi-session plugin runtimes and scoped XWorkmate artifacts.", register, }; export default plugin; @@ -72,14 +72,14 @@ function register(api) { } }); api.registerTool((ctx) => createXWorkmateArtifactsTool(api, ctx), { - names: ["xworkmate_artifacts"], + names: ["openclaw_multi_session_artifacts"], optional: true, }); } function createXWorkmateArtifactsTool(api, ctx) { return { - name: "xworkmate_artifacts", - label: "XWorkmate Artifacts", + name: "openclaw_multi_session_artifacts", + label: "openclaw-multi-session-plugins", description: "List generated artifacts in the current OpenClaw workspace or read one small artifact as base64 for XWorkmate.", parameters: { type: "object", diff --git a/index.test.ts b/index.test.ts index 01e8c7e..de5ac9e 100644 --- a/index.test.ts +++ b/index.test.ts @@ -12,7 +12,7 @@ describe("plugin registration", () => { configSchema?: { properties?: Record }; }; - expect(manifest.contracts?.tools).toContain("xworkmate_artifacts"); + expect(manifest.contracts?.tools).toContain("openclaw_multi_session_artifacts"); expect(manifest.configSchema?.properties?.artifactRefSigningSecret).toBeTruthy(); }); diff --git a/index.ts b/index.ts index dafd63d..332f29e 100644 --- a/index.ts +++ b/index.ts @@ -16,9 +16,9 @@ type XWorkmateToolContext = { }; const plugin = { - id: "xworkmate-artifacts", - name: "XWorkmate Artifacts", - description: "Exports structured artifact manifests from the OpenClaw workspace for XWorkmate.", + id: "openclaw-multi-session-plugins", + name: "openclaw-multi-session-plugins", + description: "OpenClaw logical isolation support for multi-session plugin runtimes and scoped XWorkmate artifacts.", register, }; @@ -86,7 +86,7 @@ function register(api: OpenClawPluginApi) { } }); api.registerTool((ctx) => createXWorkmateArtifactsTool(api, ctx), { - names: ["xworkmate_artifacts"], + names: ["openclaw_multi_session_artifacts"], optional: true, }); } @@ -96,8 +96,8 @@ function createXWorkmateArtifactsTool( ctx: XWorkmateToolContext, ): AnyAgentTool { return { - name: "xworkmate_artifacts", - label: "XWorkmate Artifacts", + name: "openclaw_multi_session_artifacts", + label: "openclaw-multi-session-plugins", description: "List generated artifacts in the current OpenClaw workspace or read one small artifact as base64 for XWorkmate.", parameters: { diff --git a/openclaw.plugin.json b/openclaw.plugin.json index fe81cdb..9f725d4 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,12 +1,12 @@ { - "id": "xworkmate-artifacts", - "name": "XWorkmate Artifacts", - "description": "Exports structured artifact manifests from the OpenClaw workspace for XWorkmate.", + "id": "openclaw-multi-session-plugins", + "name": "openclaw-multi-session-plugins", + "description": "OpenClaw logical isolation support for multi-session plugin runtimes and scoped XWorkmate artifacts.", "activation": { "onStartup": true }, "contracts": { - "tools": ["xworkmate_artifacts"] + "tools": ["openclaw_multi_session_artifacts"] }, "configSchema": { "type": "object", diff --git a/package.json b/package.json index 2a49e9f..7554c98 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "xworkmate-artifacts", + "name": "openclaw-multi-session-plugins", "version": "0.1.8", - "description": "XWorkmate artifact export plugin for OpenClaw Gateway", + "description": "OpenClaw multi-session plugin runtime support for scoped XWorkmate artifacts", "type": "module", "license": "MIT", "keywords": [ @@ -9,15 +9,18 @@ "xworkmate", "artifacts", "gateway", - "plugin" + "plugin", + "multi-session", + "session-isolation", + "logical-isolation" ], - "homepage": "https://github.com/x-evor/xworkmate-artifacts#readme", + "homepage": "https://github.com/x-evor/openclaw-multi-session-plugins#readme", "repository": { "type": "git", - "url": "git+https://github.com/x-evor/xworkmate-artifacts.git" + "url": "git+https://github.com/x-evor/openclaw-multi-session-plugins.git" }, "bugs": { - "url": "https://github.com/x-evor/xworkmate-artifacts/issues" + "url": "https://github.com/x-evor/openclaw-multi-session-plugins/issues" }, "engines": { "node": ">=22" diff --git a/src/exportArtifacts.test.ts b/src/exportArtifacts.test.ts index 2d49311..f724f40 100644 --- a/src/exportArtifacts.test.ts +++ b/src/exportArtifacts.test.ts @@ -11,7 +11,7 @@ import { describe("exportXWorkmateArtifacts", () => { it("prepares isolated task artifact scopes", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const first = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, @@ -31,7 +31,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("exports changed files with metadata and base64 content", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.mkdir(path.join(root, "reports"), { recursive: true }); const filePath = path.join(root, "reports", "final.md"); await fs.writeFile(filePath, "# Done\n"); @@ -64,7 +64,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("filters old files by sinceUnixMs", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const oldFile = path.join(root, "old.txt"); await fs.writeFile(oldFile, "old"); const stat = await fs.stat(oldFile); @@ -82,7 +82,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("skips excluded directories and symlinks", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.mkdir(path.join(root, ".git"), { recursive: true }); await fs.mkdir(path.join(root, ".xworkmate", "artifacts"), { recursive: true }); await fs.writeFile(path.join(root, ".git", "secret.txt"), "secret"); @@ -103,7 +103,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("exports only files inside a task artifact scope", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const first = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, pluginConfig: { workspaceDir: root }, @@ -136,7 +136,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("falls back to latest workspace files when the scoped directory is empty", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const prepared = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, pluginConfig: { workspaceDir: root }, @@ -172,7 +172,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("falls back to latest session task files when requested", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const previousTask = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-previous" }, pluginConfig: { workspaceDir: root }, @@ -210,7 +210,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("leaves oversized artifacts out of inline content", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.writeFile(path.join(root, "large.pdf"), Buffer.from("large-content")); const result = await exportXWorkmateArtifacts({ @@ -229,7 +229,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("can list artifacts without inline content", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.writeFile(path.join(root, "small.txt"), "small"); const result = await exportXWorkmateArtifacts({ @@ -248,7 +248,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("limits exported files", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.writeFile(path.join(root, "a.txt"), "a"); await fs.writeFile(path.join(root, "b.txt"), "b"); @@ -266,8 +266,8 @@ describe("exportXWorkmateArtifacts", () => { }); it("selects an agent workspace from agent session keys", async () => { - const mainRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-main-")); - const agentRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-agent-")); + const mainRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-main-")); + const agentRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-agent-")); await fs.writeFile(path.join(mainRoot, "main.txt"), "main"); await fs.writeFile(path.join(agentRoot, "agent.txt"), "agent"); @@ -288,7 +288,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("rejects unscoped artifact reads by relative path", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.mkdir(path.join(root, "reports"), { recursive: true }); await fs.writeFile(path.join(root, "reports", "final.txt"), "final"); @@ -305,7 +305,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("reads one artifact inside a task artifact scope", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const prepared = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, pluginConfig: { workspaceDir: root }, @@ -336,7 +336,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("reads a latest workspace artifact only through its artifactRef", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const prepared = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, pluginConfig: { workspaceDir: root }, @@ -374,7 +374,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("rejects tampered artifact refs", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await fs.writeFile(path.join(root, "existing.txt"), "existing"); const exported = await exportXWorkmateArtifacts({ params: { @@ -399,7 +399,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("reads artifact metadata without inline content when the file exceeds the limit", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const prepared = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, pluginConfig: { workspaceDir: root }, @@ -430,7 +430,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("rejects relative path traversal when reading artifacts", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const prepared = await prepareXWorkmateArtifacts({ params: { sessionKey: "thread-main", runId: "turn-1" }, pluginConfig: { workspaceDir: root }, @@ -450,7 +450,7 @@ describe("exportXWorkmateArtifacts", () => { }); it("rejects artifact scope traversal when reading artifacts", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); await expect( readXWorkmateArtifact({ @@ -466,8 +466,8 @@ describe("exportXWorkmateArtifacts", () => { }); it("rejects symlink escapes when reading artifacts", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-artifacts-")); - const outsideRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-xworkmate-outside-")); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); + const outsideRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-outside-")); const outsideFile = path.join(outsideRoot, "secret.txt"); await fs.writeFile(outsideFile, "secret"); const prepared = await prepareXWorkmateArtifacts({