chore: rename plugin for multi-session scope

This commit is contained in:
Haitao Pan 2026-05-07 11:26:34 +08:00
parent aa0c2323ea
commit b9c05e3657
7 changed files with 60 additions and 57 deletions

View File

@ -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"]
}
}
]

12
dist/index.js vendored
View File

@ -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",

View File

@ -12,7 +12,7 @@ describe("plugin registration", () => {
configSchema?: { properties?: Record<string, unknown> };
};
expect(manifest.contracts?.tools).toContain("xworkmate_artifacts");
expect(manifest.contracts?.tools).toContain("openclaw_multi_session_artifacts");
expect(manifest.configSchema?.properties?.artifactRefSigningSecret).toBeTruthy();
});

View File

@ -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: {

View File

@ -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",

View File

@ -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"

View File

@ -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({