Merge OpenClaw thin adapter refactor

# Conflicts:
#	dist/index.js
#	dist/src/exportArtifacts.js
#	dist/src/taskState.d.ts
#	dist/src/taskState.js
This commit is contained in:
Haitao Pan 2026-06-06 07:58:35 +08:00
commit d5ffc79098
9 changed files with 131 additions and 143 deletions

15
dist/index.js vendored
View File

@ -1,6 +1,6 @@
import { getPluginRuntimeGatewayRequestScope } from "openclaw/plugin-sdk/plugin-runtime"; import { getPluginRuntimeGatewayRequestScope } from "openclaw/plugin-sdk/plugin-runtime";
import { collectAndSnapshotXWorkmateArtifacts, exportXWorkmateArtifacts, prepareXWorkmateArtifacts, readXWorkmateArtifact, formatArtifactManifestMarkdown, } from "./src/exportArtifacts.js"; import { collectAndSnapshotXWorkmateArtifacts, exportXWorkmateArtifacts, prepareXWorkmateArtifacts, readXWorkmateArtifact, formatArtifactManifestMarkdown, } from "./src/exportArtifacts.js";
import { getXWorkmateTaskSnapshot, recordXWorkmateSessionMapping, registerXWorkmateDetachedTaskRuntime, registerXWorkmateSessionExtension, } from "./src/taskState.js"; import { getXWorkmateTaskSnapshot, recordXWorkmateSessionMapping, registerXWorkmateSessionExtension, } from "./src/taskState.js";
function scopedGatewayParams(params) { function scopedGatewayParams(params) {
const sessionScope = getPluginRuntimeGatewayRequestScope()?.sessionScope; const sessionScope = getPluginRuntimeGatewayRequestScope()?.sessionScope;
const runScope = resolveRunScope({ sessionScope }); const runScope = resolveRunScope({ sessionScope });
@ -9,7 +9,7 @@ function scopedGatewayParams(params) {
} }
return { return {
...params, ...params,
sessionKey: runScope.sessionKey, openclawSessionKey: runScope.sessionKey,
runId: runScope.runId, runId: runScope.runId,
...(runScope.workspaceDir ? { workspaceDir: runScope.workspaceDir } : {}), ...(runScope.workspaceDir ? { workspaceDir: runScope.workspaceDir } : {}),
...(runScope.artifactScope ? { artifactScope: runScope.artifactScope } : {}), ...(runScope.artifactScope ? { artifactScope: runScope.artifactScope } : {}),
@ -40,13 +40,11 @@ const plugin = {
}; };
export default plugin; export default plugin;
function register(api) { function register(api) {
const taskStore = {};
registerXWorkmateSessionExtension(api); registerXWorkmateSessionExtension(api);
registerXWorkmateDetachedTaskRuntime(api, taskStore);
api.registerHook("session_start", async (event) => { api.registerHook("session_start", async (event) => {
try { try {
const params = scopedGatewayParams(event?.context ?? event); const params = scopedGatewayParams(event?.context ?? event);
const openclawSessionKey = stringParam(params.openclawSessionKey) || stringParam(params.sessionKey); const openclawSessionKey = stringParam(params.openclawSessionKey);
if (openclawSessionKey && params.runId) { if (openclawSessionKey && params.runId) {
const hookParams = { ...params, openclawSessionKey }; const hookParams = { ...params, openclawSessionKey };
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
@ -56,7 +54,6 @@ function register(api) {
}); });
await recordXWorkmateSessionMapping({ await recordXWorkmateSessionMapping({
api, api,
taskStore,
params: hookParams, params: hookParams,
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
source: "session_start", source: "session_start",
@ -72,7 +69,6 @@ function register(api) {
const params = scopedGatewayParams(opts.params); const params = scopedGatewayParams(opts.params);
const mapping = await recordXWorkmateSessionMapping({ const mapping = await recordXWorkmateSessionMapping({
api, api,
taskStore,
params, params,
source: "bridge_prepare", source: "bridge_prepare",
}); });
@ -104,7 +100,6 @@ function register(api) {
try { try {
const payload = await getXWorkmateTaskSnapshot({ const payload = await getXWorkmateTaskSnapshot({
api, api,
taskStore,
params: scopedGatewayParams(opts.params), params: scopedGatewayParams(opts.params),
}); });
opts.respond(true, payload, undefined); opts.respond(true, payload, undefined);
@ -238,10 +233,10 @@ function createXWorkmateArtifactsTool(api, ctx) {
throw new Error("runId required"); throw new Error("runId required");
} }
const workspaceDir = ctx.sessionScope?.workspaceDir || ctx.workspaceDir; const workspaceDir = ctx.sessionScope?.workspaceDir || ctx.workspaceDir;
const { sessionKey: _ignoredSessionKey, runId: _ignoredRunId, workspaceDir: _ignoredWorkspaceDir, ...operationParams } = params; const { sessionKey: _ignoredSessionKey, openclawSessionKey: _ignoredOpenclawSessionKey, runId: _ignoredRunId, workspaceDir: _ignoredWorkspaceDir, ...operationParams } = params;
const baseParams = { const baseParams = {
...operationParams, ...operationParams,
sessionKey, openclawSessionKey: sessionKey,
runId, runId,
...(workspaceDir ? { workspaceDir } : {}), ...(workspaceDir ? { workspaceDir } : {}),
...(runScope?.artifactScope ? { artifactScope: runScope.artifactScope } : {}), ...(runScope?.artifactScope ? { artifactScope: runScope.artifactScope } : {}),

View File

@ -21,7 +21,7 @@ export async function prepareXWorkmateArtifacts(input) {
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const expectedArtifactDirs = normalizeExpectedArtifactDirs(params.expectedArtifactDirs); const expectedArtifactDirs = normalizeExpectedArtifactDirs(params.expectedArtifactDirs);
const expectedArtifactScope = artifactScopeFor(sessionKey, runId); const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
const requestedArtifactScope = optionalArtifactScope(params.artifactScope); const requestedArtifactScope = optionalArtifactScope(params.artifactScope);
@ -57,7 +57,7 @@ export async function collectAndSnapshotXWorkmateArtifacts(input) {
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0); const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0);
const maxFiles = positiveInteger(params.maxFiles, pluginConfig.snapshotMaxFiles, DEFAULT_MAX_FILES); const maxFiles = positiveInteger(params.maxFiles, pluginConfig.snapshotMaxFiles, DEFAULT_MAX_FILES);
const expectedArtifactScope = artifactScopeFor(sessionKey, runId); const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
@ -124,7 +124,7 @@ export async function exportXWorkmateArtifacts(input) {
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const maxFiles = positiveInteger(params.maxFiles, pluginConfig.maxFiles, DEFAULT_MAX_FILES); const maxFiles = positiveInteger(params.maxFiles, pluginConfig.maxFiles, DEFAULT_MAX_FILES);
const maxInlineBytes = nonNegativeInteger(params.maxInlineBytes, pluginConfig.maxInlineBytes, DEFAULT_MAX_INLINE_BYTES); const maxInlineBytes = nonNegativeInteger(params.maxInlineBytes, pluginConfig.maxInlineBytes, DEFAULT_MAX_INLINE_BYTES);
const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0); const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0);
@ -157,7 +157,9 @@ export async function exportXWorkmateArtifacts(input) {
const scopeStat = await fs.stat(scopeRoot); const scopeStat = await fs.stat(scopeRoot);
effectiveSince = Math.min(sinceUnixMs, scopeStat.birthtimeMs || scopeStat.mtimeMs); effectiveSince = Math.min(sinceUnixMs, scopeStat.birthtimeMs || scopeStat.mtimeMs);
} }
catch { } catch (error) {
warnings.push(`Unable to read artifact scope timestamp: ${String(error)}`);
}
} }
const scopedCandidates = (await directoryExists(scopeRoot)) const scopedCandidates = (await directoryExists(scopeRoot))
? await collectCandidates({ ? await collectCandidates({
@ -256,7 +258,7 @@ export async function readXWorkmateArtifact(input) {
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const expectedArtifactScope = artifactScopeFor(sessionKey, runId); const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
const expectedSessionScope = taskSessionScopeFor(sessionKey); const expectedSessionScope = taskSessionScopeFor(sessionKey);
const requestedArtifactRef = optionalString(params.artifactRef); const requestedArtifactRef = optionalString(params.artifactRef);

View File

@ -28,13 +28,9 @@ export type XWorkmateTaskLookupError = {
mapping?: XWorkmateSessionMappingV1; mapping?: XWorkmateSessionMappingV1;
expectedArtifactDirs?: string[]; expectedArtifactDirs?: string[];
}; };
export type XWorkmateTaskStore = Record<string, never>;
export declare function createXWorkmateTaskStore(): XWorkmateTaskStore;
export declare function registerXWorkmateSessionExtension(api: OpenClawPluginApi): void; export declare function registerXWorkmateSessionExtension(api: OpenClawPluginApi): void;
export declare function registerXWorkmateDetachedTaskRuntime(_api: OpenClawPluginApi, _taskStore: XWorkmateTaskStore): void;
export declare function recordXWorkmateSessionMapping(input: { export declare function recordXWorkmateSessionMapping(input: {
api: OpenClawPluginApi; api: OpenClawPluginApi;
taskStore?: XWorkmateTaskStore;
params: Record<string, unknown>; params: Record<string, unknown>;
artifactScope?: string; artifactScope?: string;
source?: XWorkmateSessionMappingSource; source?: XWorkmateSessionMappingSource;
@ -52,6 +48,5 @@ export declare function readXWorkmateSessionMapping(api: OpenClawPluginApi, look
}): Promise<XWorkmateSessionMappingV1 | undefined>; }): Promise<XWorkmateSessionMappingV1 | undefined>;
export declare function getXWorkmateTaskSnapshot(input: { export declare function getXWorkmateTaskSnapshot(input: {
api: OpenClawPluginApi; api: OpenClawPluginApi;
taskStore?: XWorkmateTaskStore;
params: Record<string, unknown>; params: Record<string, unknown>;
}): Promise<Record<string, unknown>>; }): Promise<Record<string, unknown>>;

View File

@ -1,9 +1,6 @@
import { exportXWorkmateArtifacts } from "./exportArtifacts.js"; import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
export const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins"; export const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins";
export const XWORKMATE_SESSION_EXTENSION_NAMESPACE = "xworkmate.sessionMapping"; export const XWORKMATE_SESSION_EXTENSION_NAMESPACE = "xworkmate.sessionMapping";
export function createXWorkmateTaskStore() {
return {};
}
export function registerXWorkmateSessionExtension(api) { export function registerXWorkmateSessionExtension(api) {
const registerExtension = api.session?.state?.registerSessionExtension ?? api.registerSessionExtension; const registerExtension = api.session?.state?.registerSessionExtension ?? api.registerSessionExtension;
if (typeof registerExtension !== "function") { if (typeof registerExtension !== "function") {
@ -19,9 +16,6 @@ export function registerXWorkmateSessionExtension(api) {
}, },
}); });
} }
export function registerXWorkmateDetachedTaskRuntime(_api, _taskStore) {
// OpenClaw native task-registry is the only task status source for this plugin.
}
export async function recordXWorkmateSessionMapping(input) { export async function recordXWorkmateSessionMapping(input) {
const metadata = normalizeXWorkmateTaskMetadataV1(input.params); const metadata = normalizeXWorkmateTaskMetadataV1(input.params);
const openclawSessionKey = requiredString(input.params.openclawSessionKey ?? metadata.openclawSessionKey, "openclawSessionKey required"); const openclawSessionKey = requiredString(input.params.openclawSessionKey ?? metadata.openclawSessionKey, "openclawSessionKey required");

View File

@ -92,12 +92,12 @@ describe("plugin registration", () => {
openclawSessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
}); });
console.log(prepared); expect(prepared.ok).toBe(true); expect(prepared.ok).toBe(true);
expect(prepared.payload?.artifactScope).toBe("tasks/thread-main/turn-1"); expect(prepared.payload?.artifactScope).toBe("tasks/thread-main/turn-1");
const artifactDirectory = String(prepared.payload?.artifactDirectory); const artifactDirectory = String(prepared.payload?.artifactDirectory);
const emptyExport = await callGatewayMethod(methods, "xworkmate.artifacts.export", { const emptyExport = await callGatewayMethod(methods, "xworkmate.artifacts.export", {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.payload?.artifactScope, artifactScope: prepared.payload?.artifactScope,
}); });
@ -109,7 +109,7 @@ describe("plugin registration", () => {
await fs.promises.writeFile(path.join(artifactDirectory, "reports", "final.md"), "final"); await fs.promises.writeFile(path.join(artifactDirectory, "reports", "final.md"), "final");
const listed = await callGatewayMethod(methods, "xworkmate.artifacts.list", { const listed = await callGatewayMethod(methods, "xworkmate.artifacts.list", {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.payload?.artifactScope, artifactScope: prepared.payload?.artifactScope,
}); });
@ -119,7 +119,7 @@ describe("plugin registration", () => {
expect(listedArtifacts[0]).not.toHaveProperty("content"); expect(listedArtifacts[0]).not.toHaveProperty("content");
const read = await callGatewayMethod(methods, "xworkmate.artifacts.read", { const read = await callGatewayMethod(methods, "xworkmate.artifacts.read", {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.payload?.artifactScope, artifactScope: prepared.payload?.artifactScope,
relativePath: "reports/final.md", relativePath: "reports/final.md",
@ -128,7 +128,7 @@ describe("plugin registration", () => {
expect(read.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md", encoding: "base64" }]); expect(read.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md", encoding: "base64" }]);
const unprepared = await callGatewayMethod(methods, "xworkmate.artifacts.export", { const unprepared = await callGatewayMethod(methods, "xworkmate.artifacts.export", {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-unprepared", runId: "turn-unprepared",
}); });
expect(unprepared.ok).toBe(true); expect(unprepared.ok).toBe(true);
@ -211,15 +211,21 @@ describe("plugin registration", () => {
sessionEntrySlotKey: "xworkmate", sessionEntrySlotKey: "xworkmate",
}); });
const projected = (sessionExtensions[0]?.project as (ctx: Record<string, unknown>) => unknown)({ const projected = (sessionExtensions[0]?.project as (ctx: Record<string, unknown>) => unknown)({
sessionKey: "draft:1780636411666238-3", openclawSessionKey: "draft:1780636411666238-3",
state: {}, state: {},
}); });
expect(projected).toMatchObject({}); expect(projected).toMatchObject({});
expect(detachedRuntimes).toHaveLength(0); expect(detachedRuntimes).toHaveLength(0);
await hooks.get("session_start")?.({
appThreadKey: "draft:legacy-session-key-only",
sessionKey: "draft:legacy-session-key-only",
runId: "turn-legacy",
});
expect(sessionExtensionPatches).toHaveLength(0);
await hooks.get("session_start")?.({ await hooks.get("session_start")?.({
appThreadKey: "draft:1780636411666238-3", appThreadKey: "draft:1780636411666238-3",
sessionKey: "draft-1780636411666238-3",
openclawSessionKey: "draft:1780636411666238-3", openclawSessionKey: "draft:1780636411666238-3",
threadId: "draft-1780636411666238-3", threadId: "draft-1780636411666238-3",
runId: "turn-1", runId: "turn-1",
@ -302,11 +308,11 @@ describe("plugin registration", () => {
it("uses host context scope for the optional agent tool", async () => { it("uses host context scope for the optional agent tool", async () => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-tool-")); const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-tool-"));
const current = await prepareXWorkmateArtifacts({ const current = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
const other = await prepareXWorkmateArtifacts({ const other = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-2" }, params: { openclawSessionKey: "thread-main", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.promises.writeFile(path.join(current.artifactDirectory, "current.txt"), "current"); await fs.promises.writeFile(path.join(current.artifactDirectory, "current.txt"), "current");
@ -340,7 +346,7 @@ describe("plugin registration", () => {
}); });
const result = await tool.execute("call-1", { const result = await tool.execute("call-1", {
action: "list", action: "list",
sessionKey: "thread-other", openclawSessionKey: "thread-other",
runId: "turn-2", runId: "turn-2",
workspaceDir: "/", workspaceDir: "/",
}); });

View File

@ -14,7 +14,6 @@ import {
import { import {
getXWorkmateTaskSnapshot, getXWorkmateTaskSnapshot,
recordXWorkmateSessionMapping, recordXWorkmateSessionMapping,
registerXWorkmateDetachedTaskRuntime,
registerXWorkmateSessionExtension, registerXWorkmateSessionExtension,
} from "./src/taskState.js"; } from "./src/taskState.js";
@ -53,7 +52,7 @@ function scopedGatewayParams(params: Record<string, unknown>): Record<string, un
} }
return { return {
...params, ...params,
sessionKey: runScope.sessionKey, openclawSessionKey: runScope.sessionKey,
runId: runScope.runId, runId: runScope.runId,
...(runScope.workspaceDir ? { workspaceDir: runScope.workspaceDir } : {}), ...(runScope.workspaceDir ? { workspaceDir: runScope.workspaceDir } : {}),
...(runScope.artifactScope ? { artifactScope: runScope.artifactScope } : {}), ...(runScope.artifactScope ? { artifactScope: runScope.artifactScope } : {}),
@ -94,16 +93,14 @@ const plugin = {
export default plugin; export default plugin;
function register(api: OpenClawPluginApi) { function register(api: OpenClawPluginApi) {
const taskStore = {};
registerXWorkmateSessionExtension(api); registerXWorkmateSessionExtension(api);
registerXWorkmateDetachedTaskRuntime(api, taskStore);
api.registerHook( api.registerHook(
"session_start", "session_start",
async (event: any) => { async (event: any) => {
try { try {
const params = scopedGatewayParams(event?.context ?? event); const params = scopedGatewayParams(event?.context ?? event);
const openclawSessionKey = stringParam(params.openclawSessionKey) || stringParam(params.sessionKey); const openclawSessionKey = stringParam(params.openclawSessionKey);
if (openclawSessionKey && params.runId) { if (openclawSessionKey && params.runId) {
const hookParams = { ...params, openclawSessionKey }; const hookParams = { ...params, openclawSessionKey };
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
@ -113,7 +110,6 @@ function register(api: OpenClawPluginApi) {
}); });
await recordXWorkmateSessionMapping({ await recordXWorkmateSessionMapping({
api, api,
taskStore,
params: hookParams, params: hookParams,
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
source: "session_start", source: "session_start",
@ -131,7 +127,6 @@ function register(api: OpenClawPluginApi) {
const params = scopedGatewayParams(opts.params); const params = scopedGatewayParams(opts.params);
const mapping = await recordXWorkmateSessionMapping({ const mapping = await recordXWorkmateSessionMapping({
api, api,
taskStore,
params, params,
source: "bridge_prepare", source: "bridge_prepare",
}); });
@ -167,7 +162,6 @@ function register(api: OpenClawPluginApi) {
try { try {
const payload = await getXWorkmateTaskSnapshot({ const payload = await getXWorkmateTaskSnapshot({
api, api,
taskStore,
params: scopedGatewayParams(opts.params), params: scopedGatewayParams(opts.params),
}); });
opts.respond(true, payload, undefined); opts.respond(true, payload, undefined);
@ -303,13 +297,14 @@ function createXWorkmateArtifactsTool(
const workspaceDir = ctx.sessionScope?.workspaceDir || ctx.workspaceDir; const workspaceDir = ctx.sessionScope?.workspaceDir || ctx.workspaceDir;
const { const {
sessionKey: _ignoredSessionKey, sessionKey: _ignoredSessionKey,
openclawSessionKey: _ignoredOpenclawSessionKey,
runId: _ignoredRunId, runId: _ignoredRunId,
workspaceDir: _ignoredWorkspaceDir, workspaceDir: _ignoredWorkspaceDir,
...operationParams ...operationParams
} = params; } = params;
const baseParams = { const baseParams = {
...operationParams, ...operationParams,
sessionKey, openclawSessionKey: sessionKey,
runId, runId,
...(workspaceDir ? { workspaceDir } : {}), ...(workspaceDir ? { workspaceDir } : {}),
...(runScope?.artifactScope ? { artifactScope: runScope.artifactScope } : {}), ...(runScope?.artifactScope ? { artifactScope: runScope.artifactScope } : {}),

View File

@ -15,11 +15,11 @@ describe("exportXWorkmateArtifacts", () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const first = await prepareXWorkmateArtifacts({ const first = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
const second = await prepareXWorkmateArtifacts({ const second = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-2" }, params: { openclawSessionKey: "thread-main", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
@ -31,11 +31,22 @@ describe("exportXWorkmateArtifacts", () => {
expect(first.scopeKind).toBe("task"); expect(first.scopeKind).toBe("task");
}); });
it("rejects legacy sessionKey artifact params", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
await expect(
prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root },
}),
).rejects.toThrow("openclawSessionKey required");
});
it("normalizes task scope segments like the OpenClaw session scope runtime", async () => { it("normalizes task scope segments like the OpenClaw session scope runtime", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "agent::main:main", runId: "run alpha" }, params: { openclawSessionKey: "agent::main:main", runId: "run alpha" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
@ -45,7 +56,7 @@ describe("exportXWorkmateArtifacts", () => {
it("exports changed files with metadata and base64 content", async () => { it("exports changed files with metadata and base64 content", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(prepared.artifactDirectory, "reports"), { recursive: true }); await fs.mkdir(path.join(prepared.artifactDirectory, "reports"), { recursive: true });
@ -55,7 +66,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
sinceUnixMs: stat.mtimeMs - 1, sinceUnixMs: stat.mtimeMs - 1,
}, },
@ -84,7 +95,7 @@ describe("exportXWorkmateArtifacts", () => {
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-expected", runId: "run-expected",
expectedArtifactDirs: ["artifacts/", "assets/images"], expectedArtifactDirs: ["artifacts/", "assets/images"],
}, },
@ -92,7 +103,7 @@ describe("exportXWorkmateArtifacts", () => {
}); });
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-expected", runId: "run-expected",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
expectedArtifactDirs: ["artifacts/", "assets/images"], expectedArtifactDirs: ["artifacts/", "assets/images"],
@ -114,7 +125,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
prepareXWorkmateArtifacts({ prepareXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-unsafe", runId: "run-unsafe",
expectedArtifactDirs: ["../outside"], expectedArtifactDirs: ["../outside"],
}, },
@ -130,7 +141,7 @@ describe("exportXWorkmateArtifacts", () => {
const mediaRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-media-")); const mediaRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-media-"));
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-global-")); const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-global-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(mediaRoot, "browser"), { recursive: true }); await fs.mkdir(path.join(mediaRoot, "browser"), { recursive: true });
@ -144,7 +155,7 @@ describe("exportXWorkmateArtifacts", () => {
const snapshot = await collectAndSnapshotXWorkmateArtifacts({ const snapshot = await collectAndSnapshotXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
sinceUnixMs: snapshotSinceUnixMs, sinceUnixMs: snapshotSinceUnixMs,
@ -165,7 +176,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
includeContent: false, includeContent: false,
@ -182,7 +193,7 @@ describe("exportXWorkmateArtifacts", () => {
it("skips excluded directories and symlinks", async () => { it("skips excluded directories and symlinks", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(prepared.artifactDirectory, ".git"), { recursive: true }); await fs.mkdir(path.join(prepared.artifactDirectory, ".git"), { recursive: true });
@ -194,7 +205,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -207,7 +218,7 @@ describe("exportXWorkmateArtifacts", () => {
it("applies artifact-ignore.md inside the current task scope", async () => { it("applies artifact-ignore.md inside the current task scope", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(prepared.artifactDirectory, "tmp"), { recursive: true }); await fs.mkdir(path.join(prepared.artifactDirectory, "tmp"), { recursive: true });
@ -234,7 +245,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -246,11 +257,11 @@ describe("exportXWorkmateArtifacts", () => {
it("exports only files inside a task artifact scope", async () => { it("exports only files inside a task artifact scope", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const first = await prepareXWorkmateArtifacts({ const first = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
const second = await prepareXWorkmateArtifacts({ const second = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-2" }, params: { openclawSessionKey: "thread-main", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(first.artifactDirectory, "reports"), { recursive: true }); await fs.mkdir(path.join(first.artifactDirectory, "reports"), { recursive: true });
@ -260,7 +271,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: first.artifactScope, artifactScope: first.artifactScope,
}, },
@ -279,7 +290,7 @@ describe("exportXWorkmateArtifacts", () => {
it("exports nested dist and build deliverables inside the task scope", async () => { it("exports nested dist and build deliverables inside the task scope", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(prepared.artifactDirectory, "dist"), { recursive: true }); await fs.mkdir(path.join(prepared.artifactDirectory, "dist"), { recursive: true });
@ -289,7 +300,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
maxInlineBytes: 0, maxInlineBytes: 0,
}, },
@ -305,11 +316,11 @@ describe("exportXWorkmateArtifacts", () => {
it("uses the current task scope when artifactScope is omitted", async () => { it("uses the current task scope when artifactScope is omitted", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const current = await prepareXWorkmateArtifacts({ const current = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
const other = await prepareXWorkmateArtifacts({ const other = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-2" }, params: { openclawSessionKey: "thread-main", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(root, "global.txt"), "global"); await fs.writeFile(path.join(root, "global.txt"), "global");
@ -318,7 +329,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -332,14 +343,14 @@ describe("exportXWorkmateArtifacts", () => {
it("does not scan the workspace root without a current-run timestamp", async () => { it("does not scan the workspace root without a current-run timestamp", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
await prepareXWorkmateArtifacts({ await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(root, "global.txt"), "global"); await fs.writeFile(path.join(root, "global.txt"), "global");
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -356,7 +367,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "draft-article", openclawSessionKey: "draft-article",
runId: "openclaw-run-1", runId: "openclaw-run-1",
sinceUnixMs, sinceUnixMs,
maxInlineBytes: 0, maxInlineBytes: 0,
@ -372,7 +383,7 @@ describe("exportXWorkmateArtifacts", () => {
it("exports explicitly expected artifact dirs when the task scope is empty", async () => { it("exports explicitly expected artifact dirs when the task scope is empty", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "draft-article", runId: "openclaw-run-1" }, params: { openclawSessionKey: "draft-article", runId: "openclaw-run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(root, "assets", "images"), { recursive: true }); await fs.mkdir(path.join(root, "assets", "images"), { recursive: true });
@ -383,7 +394,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "draft-article", openclawSessionKey: "draft-article",
runId: "openclaw-run-1", runId: "openclaw-run-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
expectedArtifactDirs: ["assets/images", "reports"], expectedArtifactDirs: ["assets/images", "reports"],
@ -403,7 +414,7 @@ describe("exportXWorkmateArtifacts", () => {
it("keeps scoped artifacts authoritative over expected artifact dirs", async () => { it("keeps scoped artifacts authoritative over expected artifact dirs", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "draft-article", runId: "openclaw-run-1" }, params: { openclawSessionKey: "draft-article", runId: "openclaw-run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(prepared.artifactDirectory, "reports"), { recursive: true }); await fs.mkdir(path.join(prepared.artifactDirectory, "reports"), { recursive: true });
@ -413,7 +424,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "draft-article", openclawSessionKey: "draft-article",
runId: "openclaw-run-1", runId: "openclaw-run-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
expectedArtifactDirs: ["reports"], expectedArtifactDirs: ["reports"],
@ -428,7 +439,7 @@ describe("exportXWorkmateArtifacts", () => {
it("does not adopt old workspace root files into a later task scope", async () => { it("does not adopt old workspace root files into a later task scope", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
await prepareXWorkmateArtifacts({ await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(root, "old-root.md"), "old"); await fs.writeFile(path.join(root, "old-root.md"), "old");
@ -436,7 +447,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
sinceUnixMs: stat.mtimeMs + 10_000, sinceUnixMs: stat.mtimeMs + 10_000,
}, },
@ -450,18 +461,18 @@ describe("exportXWorkmateArtifacts", () => {
it("rejects scoped exports that do not match the requested session/run", async () => { it("rejects scoped exports that do not match the requested session/run", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const first = await prepareXWorkmateArtifacts({ const first = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await prepareXWorkmateArtifacts({ await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-2" }, params: { openclawSessionKey: "thread-main", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await expect( await expect(
exportXWorkmateArtifacts({ exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-2", runId: "turn-2",
artifactScope: first.artifactScope, artifactScope: first.artifactScope,
}, },
@ -473,11 +484,11 @@ describe("exportXWorkmateArtifacts", () => {
it("does not adopt old workspace files when the scoped directory is empty", async () => { it("does not adopt old workspace files when the scoped directory is empty", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
const otherTask = await prepareXWorkmateArtifacts({ const otherTask = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-2" }, params: { openclawSessionKey: "thread-main", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(root, "existing.pdf"), "pdf"); await fs.writeFile(path.join(root, "existing.pdf"), "pdf");
@ -488,7 +499,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
sinceUnixMs: stat.mtimeMs + 10_000, sinceUnixMs: stat.mtimeMs + 10_000,
@ -505,11 +516,11 @@ describe("exportXWorkmateArtifacts", () => {
it("does not borrow previous session task files when current task scope is empty", async () => { it("does not borrow previous session task files when current task scope is empty", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const previousTask = await prepareXWorkmateArtifacts({ const previousTask = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-previous" }, params: { openclawSessionKey: "thread-main", runId: "turn-previous" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await prepareXWorkmateArtifacts({ await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-follow-up" }, params: { openclawSessionKey: "thread-main", runId: "turn-follow-up" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(previousTask.artifactDirectory, "k8s-networking.pdf"), "pdf"); await fs.writeFile(path.join(previousTask.artifactDirectory, "k8s-networking.pdf"), "pdf");
@ -517,7 +528,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-follow-up", runId: "turn-follow-up",
sinceUnixMs: Date.now() + 10_000, sinceUnixMs: Date.now() + 10_000,
}, },
@ -534,7 +545,7 @@ describe("exportXWorkmateArtifacts", () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
await prepareXWorkmateArtifacts({ await prepareXWorkmateArtifacts({
params: { params: {
sessionKey: "draft:1779524982823421-3", openclawSessionKey: "draft:1779524982823421-3",
runId: "turn-1779685283403237342", runId: "turn-1779685283403237342",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -561,7 +572,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "draft:1779524982823421-3", openclawSessionKey: "draft:1779524982823421-3",
runId: "turn-1779685283403237342", runId: "turn-1779685283403237342",
sinceUnixMs: Date.now() + 10_000, sinceUnixMs: Date.now() + 10_000,
}, },
@ -594,7 +605,7 @@ describe("exportXWorkmateArtifacts", () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
await prepareXWorkmateArtifacts({ await prepareXWorkmateArtifacts({
params: { params: {
sessionKey: "draft:1779524982823421-3", openclawSessionKey: "draft:1779524982823421-3",
runId: "turn-1779685283403237342", runId: "turn-1779685283403237342",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -605,7 +616,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "draft:1779524982823421-3", openclawSessionKey: "draft:1779524982823421-3",
runId: "turn-1779685283403237342", runId: "turn-1779685283403237342",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -630,15 +641,15 @@ describe("exportXWorkmateArtifacts", () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await Promise.all([ const prepared = await Promise.all([
prepareXWorkmateArtifacts({ prepareXWorkmateArtifacts({
params: { sessionKey: "thread-a", runId: "turn-1" }, params: { openclawSessionKey: "thread-a", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}), }),
prepareXWorkmateArtifacts({ prepareXWorkmateArtifacts({
params: { sessionKey: "thread-b", runId: "turn-1" }, params: { openclawSessionKey: "thread-b", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}), }),
prepareXWorkmateArtifacts({ prepareXWorkmateArtifacts({
params: { sessionKey: "thread-a", runId: "turn-2" }, params: { openclawSessionKey: "thread-a", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}), }),
]); ]);
@ -648,15 +659,15 @@ describe("exportXWorkmateArtifacts", () => {
const results = await Promise.all([ const results = await Promise.all([
exportXWorkmateArtifacts({ exportXWorkmateArtifacts({
params: { sessionKey: "thread-a", runId: "turn-1" }, params: { openclawSessionKey: "thread-a", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}), }),
exportXWorkmateArtifacts({ exportXWorkmateArtifacts({
params: { sessionKey: "thread-b", runId: "turn-1" }, params: { openclawSessionKey: "thread-b", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}), }),
exportXWorkmateArtifacts({ exportXWorkmateArtifacts({
params: { sessionKey: "thread-a", runId: "turn-2" }, params: { openclawSessionKey: "thread-a", runId: "turn-2" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}), }),
]); ]);
@ -672,14 +683,14 @@ describe("exportXWorkmateArtifacts", () => {
it("leaves oversized artifacts out of inline content", async () => { it("leaves oversized artifacts out of inline content", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "large.pdf"), Buffer.from("large-content")); await fs.writeFile(path.join(prepared.artifactDirectory, "large.pdf"), Buffer.from("large-content"));
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
maxInlineBytes: 2, maxInlineBytes: 2,
}, },
@ -695,14 +706,14 @@ describe("exportXWorkmateArtifacts", () => {
it("can list artifacts without inline content", async () => { it("can list artifacts without inline content", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "small.txt"), "small"); await fs.writeFile(path.join(prepared.artifactDirectory, "small.txt"), "small");
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
maxInlineBytes: 0, maxInlineBytes: 0,
}, },
@ -718,7 +729,7 @@ describe("exportXWorkmateArtifacts", () => {
it("limits exported files", async () => { it("limits exported files", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "a.txt"), "a"); await fs.writeFile(path.join(prepared.artifactDirectory, "a.txt"), "a");
@ -726,7 +737,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
maxFiles: 1, maxFiles: 1,
}, },
@ -742,14 +753,14 @@ describe("exportXWorkmateArtifacts", () => {
const agentRoot = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-agent-")); 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(mainRoot, "main.txt"), "main");
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "agent:research:thread-1", runId: "run-1" }, params: { openclawSessionKey: "agent:research:thread-1", runId: "run-1" },
pluginConfig: { workspaceDir: agentRoot }, pluginConfig: { workspaceDir: agentRoot },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "agent.txt"), "agent"); await fs.writeFile(path.join(prepared.artifactDirectory, "agent.txt"), "agent");
const result = await exportXWorkmateArtifacts({ const result = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "agent:research:thread-1", openclawSessionKey: "agent:research:thread-1",
runId: "run-1", runId: "run-1",
}, },
config: { config: {
@ -771,7 +782,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
relativePath: "reports/final.txt", relativePath: "reports/final.txt",
}, },
@ -783,7 +794,7 @@ describe("exportXWorkmateArtifacts", () => {
it("reads one artifact inside a task artifact scope", async () => { it("reads one artifact inside a task artifact scope", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.mkdir(path.join(prepared.artifactDirectory, "reports"), { recursive: true }); await fs.mkdir(path.join(prepared.artifactDirectory, "reports"), { recursive: true });
@ -791,7 +802,7 @@ describe("exportXWorkmateArtifacts", () => {
const result = await readXWorkmateArtifact({ const result = await readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
relativePath: "reports/final.txt", relativePath: "reports/final.txt",
@ -814,7 +825,7 @@ describe("exportXWorkmateArtifacts", () => {
it("rejects direct reads from another run artifact scope", async () => { it("rejects direct reads from another run artifact scope", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const first = await prepareXWorkmateArtifacts({ const first = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(first.artifactDirectory, "first.txt"), "first"); await fs.writeFile(path.join(first.artifactDirectory, "first.txt"), "first");
@ -822,7 +833,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-2", runId: "turn-2",
artifactScope: first.artifactScope, artifactScope: first.artifactScope,
relativePath: "first.txt", relativePath: "first.txt",
@ -835,13 +846,13 @@ describe("exportXWorkmateArtifacts", () => {
it("rejects signed task artifact refs from another session", async () => { it("rejects signed task artifact refs from another session", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "first.txt"), "first"); await fs.writeFile(path.join(prepared.artifactDirectory, "first.txt"), "first");
const exported = await exportXWorkmateArtifacts({ const exported = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
}, },
@ -851,7 +862,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-other", openclawSessionKey: "thread-other",
runId: "turn-1", runId: "turn-1",
artifactRef: exported.artifacts[0]?.artifactRef, artifactRef: exported.artifacts[0]?.artifactRef,
}, },
@ -863,14 +874,14 @@ describe("exportXWorkmateArtifacts", () => {
it("rejects signed task artifact refs from another run", async () => { it("rejects signed task artifact refs from another run", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "existing.txt"), "existing"); await fs.writeFile(path.join(prepared.artifactDirectory, "existing.txt"), "existing");
const exported = await exportXWorkmateArtifacts({ const exported = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
}, },
@ -880,7 +891,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-2", runId: "turn-2",
artifactRef: exported.artifacts[0]?.artifactRef, artifactRef: exported.artifacts[0]?.artifactRef,
}, },
@ -892,13 +903,13 @@ describe("exportXWorkmateArtifacts", () => {
it("rejects tampered artifact refs", async () => { it("rejects tampered artifact refs", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "run-1" }, params: { openclawSessionKey: "thread-main", runId: "run-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "existing.txt"), "existing"); await fs.writeFile(path.join(prepared.artifactDirectory, "existing.txt"), "existing");
const exported = await exportXWorkmateArtifacts({ const exported = await exportXWorkmateArtifacts({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
}, },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
@ -909,7 +920,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
artifactRef: tampered, artifactRef: tampered,
}, },
@ -938,7 +949,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
artifactRef: legacyRef, artifactRef: legacyRef,
}, },
@ -950,14 +961,14 @@ describe("exportXWorkmateArtifacts", () => {
it("reads artifact metadata without inline content when the file exceeds the limit", async () => { it("reads artifact metadata without inline content when the file exceeds the limit", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.writeFile(path.join(prepared.artifactDirectory, "large.bin"), Buffer.from("large-content")); await fs.writeFile(path.join(prepared.artifactDirectory, "large.bin"), Buffer.from("large-content"));
const result = await readXWorkmateArtifact({ const result = await readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
relativePath: "large.bin", relativePath: "large.bin",
@ -981,14 +992,14 @@ describe("exportXWorkmateArtifacts", () => {
it("rejects relative path traversal when reading artifacts", async () => { it("rejects relative path traversal when reading artifacts", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-")); const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
relativePath: "../outside.txt", relativePath: "../outside.txt",
@ -1004,7 +1015,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "run-1", runId: "run-1",
artifactScope: "../outside", artifactScope: "../outside",
relativePath: "secret.txt", relativePath: "secret.txt",
@ -1020,7 +1031,7 @@ describe("exportXWorkmateArtifacts", () => {
const outsideFile = path.join(outsideRoot, "secret.txt"); const outsideFile = path.join(outsideRoot, "secret.txt");
await fs.writeFile(outsideFile, "secret"); await fs.writeFile(outsideFile, "secret");
const prepared = await prepareXWorkmateArtifacts({ const prepared = await prepareXWorkmateArtifacts({
params: { sessionKey: "thread-main", runId: "turn-1" }, params: { openclawSessionKey: "thread-main", runId: "turn-1" },
pluginConfig: { workspaceDir: root }, pluginConfig: { workspaceDir: root },
}); });
await fs.symlink(outsideFile, path.join(prepared.artifactDirectory, "linked-secret.txt")); await fs.symlink(outsideFile, path.join(prepared.artifactDirectory, "linked-secret.txt"));
@ -1028,7 +1039,7 @@ describe("exportXWorkmateArtifacts", () => {
await expect( await expect(
readXWorkmateArtifact({ readXWorkmateArtifact({
params: { params: {
sessionKey: "thread-main", openclawSessionKey: "thread-main",
runId: "turn-1", runId: "turn-1",
artifactScope: prepared.artifactScope, artifactScope: prepared.artifactScope,
relativePath: "linked-secret.txt", relativePath: "linked-secret.txt",

View File

@ -117,7 +117,7 @@ export async function prepareXWorkmateArtifacts(input: ExportInput): Promise<XWo
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const expectedArtifactDirs = normalizeExpectedArtifactDirs(params.expectedArtifactDirs); const expectedArtifactDirs = normalizeExpectedArtifactDirs(params.expectedArtifactDirs);
const expectedArtifactScope = artifactScopeFor(sessionKey, runId); const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
const requestedArtifactScope = optionalArtifactScope(params.artifactScope); const requestedArtifactScope = optionalArtifactScope(params.artifactScope);
@ -154,7 +154,7 @@ export async function collectAndSnapshotXWorkmateArtifacts(input: ExportInput):
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0); const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0);
const maxFiles = positiveInteger(params.maxFiles, pluginConfig.snapshotMaxFiles, DEFAULT_MAX_FILES); const maxFiles = positiveInteger(params.maxFiles, pluginConfig.snapshotMaxFiles, DEFAULT_MAX_FILES);
const expectedArtifactScope = artifactScopeFor(sessionKey, runId); const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
@ -224,7 +224,7 @@ export async function exportXWorkmateArtifacts(input: ExportInput): Promise<XWor
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const maxFiles = positiveInteger(params.maxFiles, pluginConfig.maxFiles, DEFAULT_MAX_FILES); const maxFiles = positiveInteger(params.maxFiles, pluginConfig.maxFiles, DEFAULT_MAX_FILES);
const maxInlineBytes = nonNegativeInteger( const maxInlineBytes = nonNegativeInteger(
@ -261,7 +261,9 @@ export async function exportXWorkmateArtifacts(input: ExportInput): Promise<XWor
try { try {
const scopeStat = await fs.stat(scopeRoot); const scopeStat = await fs.stat(scopeRoot);
effectiveSince = Math.min(sinceUnixMs, scopeStat.birthtimeMs || scopeStat.mtimeMs); effectiveSince = Math.min(sinceUnixMs, scopeStat.birthtimeMs || scopeStat.mtimeMs);
} catch {} } catch (error) {
warnings.push(`Unable to read artifact scope timestamp: ${String(error)}`);
}
} }
const scopedCandidates = (await directoryExists(scopeRoot)) const scopedCandidates = (await directoryExists(scopeRoot))
? await collectCandidates({ ? await collectCandidates({
@ -368,7 +370,7 @@ export async function readXWorkmateArtifact(input: ReadInput): Promise<XWorkmate
const params = input.params ?? {}; const params = input.params ?? {};
const pluginConfig = input.pluginConfig ?? {}; const pluginConfig = input.pluginConfig ?? {};
const runId = requiredString(params.runId, "runId required"); const runId = requiredString(params.runId, "runId required");
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required"); const sessionKey = requiredString(params.openclawSessionKey, "openclawSessionKey required");
const expectedArtifactScope = artifactScopeFor(sessionKey, runId); const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
const expectedSessionScope = taskSessionScopeFor(sessionKey); const expectedSessionScope = taskSessionScopeFor(sessionKey);
const requestedArtifactRef = optionalString(params.artifactRef); const requestedArtifactRef = optionalString(params.artifactRef);

View File

@ -43,8 +43,6 @@ export type XWorkmateTaskLookupError = {
expectedArtifactDirs?: string[]; expectedArtifactDirs?: string[];
}; };
export type XWorkmateTaskStore = Record<string, never>;
type SessionEntry = Record<string, unknown> & { type SessionEntry = Record<string, unknown> & {
pluginExtensions?: Record<string, Record<string, unknown>>; pluginExtensions?: Record<string, Record<string, unknown>>;
}; };
@ -64,10 +62,6 @@ type BoundTaskRunsRuntime = {
resolve?: (token: string) => unknown; resolve?: (token: string) => unknown;
}; };
export function createXWorkmateTaskStore(): XWorkmateTaskStore {
return {};
}
export function registerXWorkmateSessionExtension(api: OpenClawPluginApi) { export function registerXWorkmateSessionExtension(api: OpenClawPluginApi) {
const registerExtension = const registerExtension =
api.session?.state?.registerSessionExtension ?? (api as any).registerSessionExtension; api.session?.state?.registerSessionExtension ?? (api as any).registerSessionExtension;
@ -85,13 +79,8 @@ export function registerXWorkmateSessionExtension(api: OpenClawPluginApi) {
}); });
} }
export function registerXWorkmateDetachedTaskRuntime(_api: OpenClawPluginApi, _taskStore: XWorkmateTaskStore) {
// OpenClaw native task-registry is the only task status source for this plugin.
}
export async function recordXWorkmateSessionMapping(input: { export async function recordXWorkmateSessionMapping(input: {
api: OpenClawPluginApi; api: OpenClawPluginApi;
taskStore?: XWorkmateTaskStore;
params: Record<string, unknown>; params: Record<string, unknown>;
artifactScope?: string; artifactScope?: string;
source?: XWorkmateSessionMappingSource; source?: XWorkmateSessionMappingSource;
@ -235,7 +224,6 @@ export async function readXWorkmateSessionMapping(
export async function getXWorkmateTaskSnapshot(input: { export async function getXWorkmateTaskSnapshot(input: {
api: OpenClawPluginApi; api: OpenClawPluginApi;
taskStore?: XWorkmateTaskStore;
params: Record<string, unknown>; params: Record<string, unknown>;
}): Promise<Record<string, unknown>> { }): Promise<Record<string, unknown>> {
const params = input.params ?? {}; const params = input.params ?? {};