refactor: share expected artifact dir normalization
This commit is contained in:
parent
bec9567f13
commit
0682fbf7cf
1
dist/src/expectedArtifactDirs.d.ts
vendored
Normal file
1
dist/src/expectedArtifactDirs.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export declare function normalizeExpectedArtifactDirs(value: unknown): string[];
|
||||||
33
dist/src/expectedArtifactDirs.js
vendored
Normal file
33
dist/src/expectedArtifactDirs.js
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
function optionalString(value) {
|
||||||
|
return typeof value === "string" ? value.trim() : "";
|
||||||
|
}
|
||||||
|
function safeExpectedArtifactDir(value) {
|
||||||
|
const relativePath = optionalString(value);
|
||||||
|
if (!relativePath) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (/^[A-Za-z]:[\\/]/u.test(relativePath) || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
||||||
|
throw new Error("expectedArtifactDir must stay inside the workspace");
|
||||||
|
}
|
||||||
|
const normalized = relativePath.split(/[\\/]/).filter(Boolean).join("/");
|
||||||
|
if (!normalized || normalized.split("/").some((part) => part === ".." || part === ".")) {
|
||||||
|
throw new Error("expectedArtifactDir must stay inside the workspace");
|
||||||
|
}
|
||||||
|
return normalized.endsWith("/") ? normalized : `${normalized}/`;
|
||||||
|
}
|
||||||
|
export function normalizeExpectedArtifactDirs(value) {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const seen = new Set();
|
||||||
|
const result = [];
|
||||||
|
for (const entry of value) {
|
||||||
|
const normalized = safeExpectedArtifactDir(entry);
|
||||||
|
if (!normalized || seen.has(normalized)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(normalized);
|
||||||
|
result.push(normalized);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
14
dist/src/exportArtifacts.d.ts
vendored
14
dist/src/exportArtifacts.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
export type XWorkmateArtifact = {
|
type XWorkmateArtifact = {
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
label: string;
|
label: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
@ -10,8 +10,8 @@ export type XWorkmateArtifact = {
|
|||||||
encoding?: "base64";
|
encoding?: "base64";
|
||||||
content?: string;
|
content?: string;
|
||||||
};
|
};
|
||||||
export type XWorkmateArtifactScopeKind = "task";
|
type XWorkmateArtifactScopeKind = "task";
|
||||||
export type XWorkmateArtifactExport = {
|
type XWorkmateArtifactExport = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
@ -25,7 +25,7 @@ export type XWorkmateArtifactExport = {
|
|||||||
constraintSatisfied: boolean;
|
constraintSatisfied: boolean;
|
||||||
missingRequiredExtensions: string[];
|
missingRequiredExtensions: string[];
|
||||||
};
|
};
|
||||||
export type XWorkmateArtifactPrepare = {
|
type XWorkmateArtifactPrepare = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
@ -38,11 +38,11 @@ export type XWorkmateArtifactPrepare = {
|
|||||||
expectedArtifactDirs: string[];
|
expectedArtifactDirs: string[];
|
||||||
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
|
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
|
||||||
};
|
};
|
||||||
export type XWorkmateExpectedArtifactDirStatus = {
|
type XWorkmateExpectedArtifactDirStatus = {
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
exists: boolean;
|
exists: boolean;
|
||||||
};
|
};
|
||||||
export type XWorkmateArtifactSnapshot = {
|
type XWorkmateArtifactSnapshot = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
@ -68,8 +68,6 @@ export declare function prepareXWorkmateArtifacts(input: ExportInput): Promise<X
|
|||||||
export declare function collectAndSnapshotXWorkmateArtifacts(input: ExportInput): Promise<XWorkmateArtifactSnapshot>;
|
export declare function collectAndSnapshotXWorkmateArtifacts(input: ExportInput): Promise<XWorkmateArtifactSnapshot>;
|
||||||
export declare function exportXWorkmateArtifacts(input: ExportInput): Promise<XWorkmateArtifactExport>;
|
export declare function exportXWorkmateArtifacts(input: ExportInput): Promise<XWorkmateArtifactExport>;
|
||||||
export declare function readXWorkmateArtifact(input: ReadInput): Promise<XWorkmateArtifactExport>;
|
export declare function readXWorkmateArtifact(input: ReadInput): Promise<XWorkmateArtifactExport>;
|
||||||
export declare function normalizeExpectedArtifactDirs(value: unknown): string[];
|
|
||||||
export declare function normalizeRequiredExtensions(value: unknown): string[];
|
|
||||||
export declare function formatArtifactManifestMarkdown(input: {
|
export declare function formatArtifactManifestMarkdown(input: {
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
artifactScope?: string;
|
artifactScope?: string;
|
||||||
|
|||||||
20
dist/src/exportArtifacts.js
vendored
20
dist/src/exportArtifacts.js
vendored
@ -2,6 +2,7 @@ import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypt
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { normalizeExpectedArtifactDirs } from "./expectedArtifactDirs.js";
|
||||||
const DEFAULT_MAX_FILES = 64;
|
const DEFAULT_MAX_FILES = 64;
|
||||||
const DEFAULT_MAX_INLINE_BYTES = 10 * 1024 * 1024;
|
const DEFAULT_MAX_INLINE_BYTES = 10 * 1024 * 1024;
|
||||||
const TASK_SCOPE_ROOT = "tasks";
|
const TASK_SCOPE_ROOT = "tasks";
|
||||||
@ -369,24 +370,7 @@ export async function readXWorkmateArtifact(input) {
|
|||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
export function normalizeExpectedArtifactDirs(value) {
|
function normalizeRequiredExtensions(value) {
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const seen = new Set();
|
|
||||||
const result = [];
|
|
||||||
for (const entry of value) {
|
|
||||||
const normalized = safeInputRelativePath(entry, "expectedArtifactDir");
|
|
||||||
const withSlash = normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
||||||
if (seen.has(withSlash)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(withSlash);
|
|
||||||
result.push(withSlash);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
export function normalizeRequiredExtensions(value) {
|
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
12
dist/src/taskState.d.ts
vendored
12
dist/src/taskState.d.ts
vendored
@ -1,5 +1,4 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||||
export declare const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins";
|
|
||||||
export declare const XWORKMATE_SESSION_EXTENSION_NAMESPACE = "xworkmate.sessionMapping";
|
export declare const XWORKMATE_SESSION_EXTENSION_NAMESPACE = "xworkmate.sessionMapping";
|
||||||
export type XWorkmateTaskMetadataV1 = {
|
export type XWorkmateTaskMetadataV1 = {
|
||||||
schemaVersion: 1;
|
schemaVersion: 1;
|
||||||
@ -35,17 +34,6 @@ export declare function recordXWorkmateSessionMapping(input: {
|
|||||||
artifactScope?: string;
|
artifactScope?: string;
|
||||||
source?: XWorkmateSessionMappingSource;
|
source?: XWorkmateSessionMappingSource;
|
||||||
}): Promise<XWorkmateSessionMappingV1>;
|
}): Promise<XWorkmateSessionMappingV1>;
|
||||||
export declare function normalizeXWorkmateTaskMetadataV1(input: Record<string, unknown>): XWorkmateTaskMetadataV1;
|
|
||||||
export declare function normalizeExpectedArtifactDirs(value: unknown): string[];
|
|
||||||
export declare function upsertXWorkmateSessionMapping(api: OpenClawPluginApi, input: {
|
|
||||||
metadata: XWorkmateTaskMetadataV1;
|
|
||||||
openclawSessionKey: string;
|
|
||||||
source: XWorkmateSessionMappingSource;
|
|
||||||
}): Promise<XWorkmateSessionMappingV1>;
|
|
||||||
export declare function readXWorkmateSessionMapping(api: OpenClawPluginApi, lookup: {
|
|
||||||
appThreadKey?: string;
|
|
||||||
openclawSessionKey?: string;
|
|
||||||
}): Promise<XWorkmateSessionMappingV1 | undefined>;
|
|
||||||
export declare function getXWorkmateTaskSnapshot(input: {
|
export declare function getXWorkmateTaskSnapshot(input: {
|
||||||
api: OpenClawPluginApi;
|
api: OpenClawPluginApi;
|
||||||
params: Record<string, unknown>;
|
params: Record<string, unknown>;
|
||||||
|
|||||||
31
dist/src/taskState.js
vendored
31
dist/src/taskState.js
vendored
@ -1,5 +1,6 @@
|
|||||||
import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
|
import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
|
||||||
export const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins";
|
import { normalizeExpectedArtifactDirs } from "./expectedArtifactDirs.js";
|
||||||
|
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 registerXWorkmateSessionExtension(api) {
|
export function registerXWorkmateSessionExtension(api) {
|
||||||
const registerExtension = api.session?.state?.registerSessionExtension ?? api.registerSessionExtension;
|
const registerExtension = api.session?.state?.registerSessionExtension ?? api.registerSessionExtension;
|
||||||
@ -28,7 +29,7 @@ export async function recordXWorkmateSessionMapping(input) {
|
|||||||
source: input.source ?? "bridge_prepare",
|
source: input.source ?? "bridge_prepare",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export function normalizeXWorkmateTaskMetadataV1(input) {
|
function normalizeXWorkmateTaskMetadataV1(input) {
|
||||||
const envelope = asRecord(input.xworkmate) ?? asRecord(input.xworkmateMetadata) ?? input;
|
const envelope = asRecord(input.xworkmate) ?? asRecord(input.xworkmateMetadata) ?? input;
|
||||||
const schemaVersion = Number(envelope.schemaVersion ?? 1);
|
const schemaVersion = Number(envelope.schemaVersion ?? 1);
|
||||||
if (schemaVersion !== 1) {
|
if (schemaVersion !== 1) {
|
||||||
@ -46,29 +47,7 @@ export function normalizeXWorkmateTaskMetadataV1(input) {
|
|||||||
createdAt,
|
createdAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export function normalizeExpectedArtifactDirs(value) {
|
async function upsertXWorkmateSessionMapping(api, input) {
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const seen = new Set();
|
|
||||||
const result = [];
|
|
||||||
for (const entry of value) {
|
|
||||||
const text = optionalString(entry).replaceAll("\\", "/").replace(/^\.\/+/u, "");
|
|
||||||
if (!text || seen.has(text)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (text.startsWith("/") || /^[A-Za-z]:\//u.test(text) || text.split("/").includes("..")) {
|
|
||||||
throw new Error("expectedArtifactDirs must be relative paths without traversal");
|
|
||||||
}
|
|
||||||
const normalized = text.endsWith("/") ? text : `${text}/`;
|
|
||||||
if (!seen.has(normalized)) {
|
|
||||||
seen.add(normalized);
|
|
||||||
result.push(normalized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
export async function upsertXWorkmateSessionMapping(api, input) {
|
|
||||||
const patchSessionEntry = resolvePatchSessionEntry(api);
|
const patchSessionEntry = resolvePatchSessionEntry(api);
|
||||||
if (!patchSessionEntry) {
|
if (!patchSessionEntry) {
|
||||||
throw new Error("OpenClaw runtime session patch API is unavailable");
|
throw new Error("OpenClaw runtime session patch API is unavailable");
|
||||||
@ -114,7 +93,7 @@ export async function upsertXWorkmateSessionMapping(api, input) {
|
|||||||
}
|
}
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
export async function readXWorkmateSessionMapping(api, lookup) {
|
async function readXWorkmateSessionMapping(api, lookup) {
|
||||||
const getSessionEntry = resolveGetSessionEntry(api);
|
const getSessionEntry = resolveGetSessionEntry(api);
|
||||||
if (!getSessionEntry) {
|
if (!getSessionEntry) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
35
src/expectedArtifactDirs.ts
Normal file
35
src/expectedArtifactDirs.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
function optionalString(value: unknown): string {
|
||||||
|
return typeof value === "string" ? value.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeExpectedArtifactDir(value: unknown): string {
|
||||||
|
const relativePath = optionalString(value);
|
||||||
|
if (!relativePath) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (/^[A-Za-z]:[\\/]/u.test(relativePath) || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
||||||
|
throw new Error("expectedArtifactDir must stay inside the workspace");
|
||||||
|
}
|
||||||
|
const normalized = relativePath.split(/[\\/]/).filter(Boolean).join("/");
|
||||||
|
if (!normalized || normalized.split("/").some((part) => part === ".." || part === ".")) {
|
||||||
|
throw new Error("expectedArtifactDir must stay inside the workspace");
|
||||||
|
}
|
||||||
|
return normalized.endsWith("/") ? normalized : `${normalized}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeExpectedArtifactDirs(value: unknown): string[] {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const entry of value) {
|
||||||
|
const normalized = safeExpectedArtifactDir(entry);
|
||||||
|
if (!normalized || seen.has(normalized)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(normalized);
|
||||||
|
result.push(normalized);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypt
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { normalizeExpectedArtifactDirs } from "./expectedArtifactDirs.js";
|
||||||
|
|
||||||
const DEFAULT_MAX_FILES = 64;
|
const DEFAULT_MAX_FILES = 64;
|
||||||
const DEFAULT_MAX_INLINE_BYTES = 10 * 1024 * 1024;
|
const DEFAULT_MAX_INLINE_BYTES = 10 * 1024 * 1024;
|
||||||
@ -20,7 +21,7 @@ const SKIPPED_DIRS = new Set([
|
|||||||
"node_modules",
|
"node_modules",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type XWorkmateArtifact = {
|
type XWorkmateArtifact = {
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
label: string;
|
label: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
@ -33,9 +34,9 @@ export type XWorkmateArtifact = {
|
|||||||
content?: string;
|
content?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XWorkmateArtifactScopeKind = "task";
|
type XWorkmateArtifactScopeKind = "task";
|
||||||
|
|
||||||
export type XWorkmateArtifactExport = {
|
type XWorkmateArtifactExport = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
@ -50,7 +51,7 @@ export type XWorkmateArtifactExport = {
|
|||||||
missingRequiredExtensions: string[];
|
missingRequiredExtensions: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XWorkmateArtifactPrepare = {
|
type XWorkmateArtifactPrepare = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
@ -64,12 +65,12 @@ export type XWorkmateArtifactPrepare = {
|
|||||||
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
|
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XWorkmateExpectedArtifactDirStatus = {
|
type XWorkmateExpectedArtifactDirStatus = {
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
exists: boolean;
|
exists: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XWorkmateArtifactSnapshot = {
|
type XWorkmateArtifactSnapshot = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
@ -491,25 +492,7 @@ export async function readXWorkmateArtifact(input: ReadInput): Promise<XWorkmate
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeExpectedArtifactDirs(value: unknown): string[] {
|
function normalizeRequiredExtensions(value: unknown): string[] {
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const result: string[] = [];
|
|
||||||
for (const entry of value) {
|
|
||||||
const normalized = safeInputRelativePath(entry, "expectedArtifactDir");
|
|
||||||
const withSlash = normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
||||||
if (seen.has(withSlash)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(withSlash);
|
|
||||||
result.push(withSlash);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeRequiredExtensions(value: unknown): string[] {
|
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,13 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
XWORKMATE_PLUGIN_ID,
|
|
||||||
XWORKMATE_SESSION_EXTENSION_NAMESPACE,
|
XWORKMATE_SESSION_EXTENSION_NAMESPACE,
|
||||||
getXWorkmateTaskSnapshot,
|
getXWorkmateTaskSnapshot,
|
||||||
normalizeXWorkmateTaskMetadataV1,
|
|
||||||
recordXWorkmateSessionMapping,
|
recordXWorkmateSessionMapping,
|
||||||
readXWorkmateSessionMapping,
|
|
||||||
} from "./taskState.js";
|
} from "./taskState.js";
|
||||||
|
|
||||||
|
const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins";
|
||||||
|
|
||||||
function createApiFixture(tasks: Record<string, unknown> = {}, pluginConfig: Record<string, unknown> = {}) {
|
function createApiFixture(tasks: Record<string, unknown> = {}, pluginConfig: Record<string, unknown> = {}) {
|
||||||
const sessions = new Map<string, any>();
|
const sessions = new Map<string, any>();
|
||||||
const api = {
|
const api = {
|
||||||
@ -66,14 +65,19 @@ async function createWorkspaceFixture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("xworkmate task state mapping", () => {
|
describe("xworkmate task state mapping", () => {
|
||||||
it("requires typed appThreadKey metadata", () => {
|
it("requires typed appThreadKey metadata", async () => {
|
||||||
expect(() =>
|
const { api } = createApiFixture();
|
||||||
normalizeXWorkmateTaskMetadataV1({
|
|
||||||
schemaVersion: 1,
|
await expect(
|
||||||
sessionKey: "draft:legacy",
|
recordXWorkmateSessionMapping({
|
||||||
expectedArtifactDirs: ["artifacts/"],
|
api,
|
||||||
|
params: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
sessionKey: "draft:legacy",
|
||||||
|
expectedArtifactDirs: ["artifacts/"],
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
).toThrow("appThreadKey required");
|
).rejects.toThrow("appThreadKey required");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("writes a durable pluginExtensions mapping without deriving the OpenClaw key", async () => {
|
it("writes a durable pluginExtensions mapping without deriving the OpenClaw key", async () => {
|
||||||
@ -273,7 +277,13 @@ describe("xworkmate task state mapping", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("can read mapping by appThreadKey from pluginExtensions", async () => {
|
it("can read mapping by appThreadKey from pluginExtensions", async () => {
|
||||||
const { api } = createApiFixture();
|
const { api } = createApiFixture({
|
||||||
|
"draft:lookup:run-1": {
|
||||||
|
taskId: "task-1",
|
||||||
|
runId: "run-1",
|
||||||
|
status: "succeeded",
|
||||||
|
},
|
||||||
|
});
|
||||||
await recordXWorkmateSessionMapping({
|
await recordXWorkmateSessionMapping({
|
||||||
api,
|
api,
|
||||||
params: {
|
params: {
|
||||||
@ -284,7 +294,17 @@ describe("xworkmate task state mapping", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(readXWorkmateSessionMapping(api, { appThreadKey: "draft:lookup" })).resolves.toMatchObject({
|
await expect(
|
||||||
|
getXWorkmateTaskSnapshot({
|
||||||
|
api,
|
||||||
|
params: {
|
||||||
|
appThreadKey: "draft:lookup",
|
||||||
|
runId: "run-1",
|
||||||
|
includeArtifacts: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
success: true,
|
||||||
appThreadKey: "draft:lookup",
|
appThreadKey: "draft:lookup",
|
||||||
openclawSessionKey: "draft:lookup",
|
openclawSessionKey: "draft:lookup",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||||
import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
|
import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
|
||||||
|
import { normalizeExpectedArtifactDirs } from "./expectedArtifactDirs.js";
|
||||||
|
|
||||||
export const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins";
|
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 type XWorkmateTaskMetadataV1 = {
|
export type XWorkmateTaskMetadataV1 = {
|
||||||
@ -101,7 +102,7 @@ export async function recordXWorkmateSessionMapping(input: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeXWorkmateTaskMetadataV1(input: Record<string, unknown>): XWorkmateTaskMetadataV1 {
|
function normalizeXWorkmateTaskMetadataV1(input: Record<string, unknown>): XWorkmateTaskMetadataV1 {
|
||||||
const envelope = asRecord(input.xworkmate) ?? asRecord(input.xworkmateMetadata) ?? input;
|
const envelope = asRecord(input.xworkmate) ?? asRecord(input.xworkmateMetadata) ?? input;
|
||||||
const schemaVersion = Number(envelope.schemaVersion ?? 1);
|
const schemaVersion = Number(envelope.schemaVersion ?? 1);
|
||||||
if (schemaVersion !== 1) {
|
if (schemaVersion !== 1) {
|
||||||
@ -120,30 +121,7 @@ export function normalizeXWorkmateTaskMetadataV1(input: Record<string, unknown>)
|
|||||||
}) as XWorkmateTaskMetadataV1;
|
}) as XWorkmateTaskMetadataV1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeExpectedArtifactDirs(value: unknown): string[] {
|
async function upsertXWorkmateSessionMapping(
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const result: string[] = [];
|
|
||||||
for (const entry of value) {
|
|
||||||
const text = optionalString(entry).replaceAll("\\", "/").replace(/^\.\/+/u, "");
|
|
||||||
if (!text || seen.has(text)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (text.startsWith("/") || /^[A-Za-z]:\//u.test(text) || text.split("/").includes("..")) {
|
|
||||||
throw new Error("expectedArtifactDirs must be relative paths without traversal");
|
|
||||||
}
|
|
||||||
const normalized = text.endsWith("/") ? text : `${text}/`;
|
|
||||||
if (!seen.has(normalized)) {
|
|
||||||
seen.add(normalized);
|
|
||||||
result.push(normalized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function upsertXWorkmateSessionMapping(
|
|
||||||
api: OpenClawPluginApi,
|
api: OpenClawPluginApi,
|
||||||
input: {
|
input: {
|
||||||
metadata: XWorkmateTaskMetadataV1;
|
metadata: XWorkmateTaskMetadataV1;
|
||||||
@ -198,7 +176,7 @@ export async function upsertXWorkmateSessionMapping(
|
|||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readXWorkmateSessionMapping(
|
async function readXWorkmateSessionMapping(
|
||||||
api: OpenClawPluginApi,
|
api: OpenClawPluginApi,
|
||||||
lookup: {
|
lookup: {
|
||||||
appThreadKey?: string;
|
appThreadKey?: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user