refactor: simplify task state to read openclawSessionKey directly
This commit is contained in:
parent
8c4dcb45bc
commit
cd3b645abe
74
dist/index.js
vendored
74
dist/index.js
vendored
@ -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 { createOrUpdateXWorkmateTaskRecord, createXWorkmateTaskStore, getXWorkmateTaskSnapshot, recordXWorkmateSessionMapping, registerXWorkmateDetachedTaskRuntime, registerXWorkmateSessionExtension, } from "./src/taskState.js";
|
import { getXWorkmateTaskSnapshot, recordXWorkmateSessionMapping, registerXWorkmateDetachedTaskRuntime, 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 });
|
||||||
@ -29,6 +29,9 @@ function resolveRunScope(ctx) {
|
|||||||
...(scope?.relativeTaskDirectory ? { artifactScope: scope.relativeTaskDirectory } : {}),
|
...(scope?.relativeTaskDirectory ? { artifactScope: scope.relativeTaskDirectory } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function stringParam(value) {
|
||||||
|
return typeof value === "string" ? value.trim() : "";
|
||||||
|
}
|
||||||
const plugin = {
|
const plugin = {
|
||||||
id: "openclaw-multi-session-plugins",
|
id: "openclaw-multi-session-plugins",
|
||||||
name: "openclaw-multi-session-plugins",
|
name: "openclaw-multi-session-plugins",
|
||||||
@ -37,35 +40,66 @@ const plugin = {
|
|||||||
};
|
};
|
||||||
export default plugin;
|
export default plugin;
|
||||||
function register(api) {
|
function register(api) {
|
||||||
const taskStore = createXWorkmateTaskStore();
|
const taskStore = {};
|
||||||
registerXWorkmateSessionExtension(api);
|
registerXWorkmateSessionExtension(api);
|
||||||
registerXWorkmateDetachedTaskRuntime(api, taskStore);
|
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);
|
||||||
if (params.sessionKey && params.runId) {
|
const openclawSessionKey = stringParam(params.openclawSessionKey) || stringParam(params.sessionKey);
|
||||||
createOrUpdateXWorkmateTaskRecord(taskStore, {
|
if (openclawSessionKey && params.runId) {
|
||||||
params,
|
const hookParams = { ...params, openclawSessionKey };
|
||||||
status: "running",
|
|
||||||
progressSummary: "OpenClaw task is running",
|
|
||||||
});
|
|
||||||
const prepared = await prepareXWorkmateArtifacts({
|
const prepared = await prepareXWorkmateArtifacts({
|
||||||
params,
|
params: hookParams,
|
||||||
config: api.config,
|
config: api.config,
|
||||||
pluginConfig: api.pluginConfig,
|
pluginConfig: api.pluginConfig,
|
||||||
});
|
});
|
||||||
await recordXWorkmateSessionMapping({
|
await recordXWorkmateSessionMapping({
|
||||||
api,
|
api,
|
||||||
taskStore,
|
taskStore,
|
||||||
params,
|
params: hookParams,
|
||||||
artifactScope: prepared.artifactScope,
|
artifactScope: prepared.artifactScope,
|
||||||
|
source: "session_start",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
api.logger?.warn?.(`xworkmate session.start preparation failed: ${String(error)}`);
|
api.logger?.warn?.(`xworkmate session_start preparation failed: ${String(error)}`);
|
||||||
}
|
}
|
||||||
}, { name: "openclaw-multi-session-plugins.session-start" });
|
}, { name: "openclaw-multi-session-plugins.session-start" });
|
||||||
|
api.registerGatewayMethod("xworkmate.session.prepare", async (opts) => {
|
||||||
|
try {
|
||||||
|
const params = scopedGatewayParams(opts.params);
|
||||||
|
const mapping = await recordXWorkmateSessionMapping({
|
||||||
|
api,
|
||||||
|
taskStore,
|
||||||
|
params,
|
||||||
|
source: "bridge_prepare",
|
||||||
|
});
|
||||||
|
const payload = await prepareXWorkmateArtifacts({
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
openclawSessionKey: mapping.openclawSessionKey,
|
||||||
|
expectedArtifactDirs: mapping.expectedArtifactDirs,
|
||||||
|
},
|
||||||
|
config: api.config,
|
||||||
|
pluginConfig: api.pluginConfig,
|
||||||
|
});
|
||||||
|
opts.respond(true, {
|
||||||
|
...payload,
|
||||||
|
mapping,
|
||||||
|
appThreadKey: mapping.appThreadKey,
|
||||||
|
openclawSessionKey: mapping.openclawSessionKey,
|
||||||
|
expectedArtifactDirs: mapping.expectedArtifactDirs,
|
||||||
|
}, undefined);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
opts.respond(false, undefined, {
|
||||||
|
code: String(error).includes("conflict") ? "CONFLICT" : "INVALID_REQUEST",
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
api.registerGatewayMethod("xworkmate.tasks.get", async (opts) => {
|
api.registerGatewayMethod("xworkmate.tasks.get", async (opts) => {
|
||||||
try {
|
try {
|
||||||
const payload = await getXWorkmateTaskSnapshot({
|
const payload = await getXWorkmateTaskSnapshot({
|
||||||
@ -82,22 +116,6 @@ function register(api) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
api.registerGatewayMethod("xworkmate.artifacts.prepare", async (opts) => {
|
|
||||||
try {
|
|
||||||
const payload = await prepareXWorkmateArtifacts({
|
|
||||||
params: scopedGatewayParams(opts.params),
|
|
||||||
config: api.config,
|
|
||||||
pluginConfig: api.pluginConfig,
|
|
||||||
});
|
|
||||||
opts.respond(true, payload, undefined);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
opts.respond(false, undefined, {
|
|
||||||
code: "INVALID_REQUEST",
|
|
||||||
message: error instanceof Error ? error.message : String(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
api.registerGatewayMethod("xworkmate.artifacts.export", async (opts) => {
|
api.registerGatewayMethod("xworkmate.artifacts.export", async (opts) => {
|
||||||
try {
|
try {
|
||||||
const payload = await exportXWorkmateArtifacts({
|
const payload = await exportXWorkmateArtifacts({
|
||||||
|
|||||||
9
dist/src/exportArtifacts.d.ts
vendored
9
dist/src/exportArtifacts.d.ts
vendored
@ -20,6 +20,8 @@ export type XWorkmateArtifactExport = {
|
|||||||
scopeKind: XWorkmateArtifactScopeKind;
|
scopeKind: XWorkmateArtifactScopeKind;
|
||||||
artifacts: XWorkmateArtifact[];
|
artifacts: XWorkmateArtifact[];
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
|
expectedArtifactDirs: string[];
|
||||||
|
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
|
||||||
};
|
};
|
||||||
export type XWorkmateArtifactPrepare = {
|
export type XWorkmateArtifactPrepare = {
|
||||||
runId: string;
|
runId: string;
|
||||||
@ -31,6 +33,12 @@ export type XWorkmateArtifactPrepare = {
|
|||||||
artifactDirectory: string;
|
artifactDirectory: string;
|
||||||
relativeArtifactDirectory: string;
|
relativeArtifactDirectory: string;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
|
expectedArtifactDirs: string[];
|
||||||
|
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
|
||||||
|
};
|
||||||
|
export type XWorkmateExpectedArtifactDirStatus = {
|
||||||
|
relativePath: string;
|
||||||
|
exists: boolean;
|
||||||
};
|
};
|
||||||
export type XWorkmateArtifactSnapshot = {
|
export type XWorkmateArtifactSnapshot = {
|
||||||
runId: string;
|
runId: string;
|
||||||
@ -58,6 +66,7 @@ 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 formatArtifactManifestMarkdown(input: {
|
export declare function formatArtifactManifestMarkdown(input: {
|
||||||
remoteWorkingDirectory: string;
|
remoteWorkingDirectory: string;
|
||||||
artifactScope?: string;
|
artifactScope?: string;
|
||||||
|
|||||||
48
dist/src/exportArtifacts.js
vendored
48
dist/src/exportArtifacts.js
vendored
@ -21,7 +21,8 @@ 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.sessionKey, "sessionKey required");
|
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "openclawSessionKey required");
|
||||||
|
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);
|
||||||
if (requestedArtifactScope && requestedArtifactScope !== expectedArtifactScope) {
|
if (requestedArtifactScope && requestedArtifactScope !== expectedArtifactScope) {
|
||||||
@ -37,6 +38,7 @@ export async function prepareXWorkmateArtifacts(input) {
|
|||||||
const artifactScope = expectedArtifactScope;
|
const artifactScope = expectedArtifactScope;
|
||||||
const scopeRoot = resolveScopeRoot(workspaceRoot, artifactScope);
|
const scopeRoot = resolveScopeRoot(workspaceRoot, artifactScope);
|
||||||
await fs.mkdir(scopeRoot, { recursive: true });
|
await fs.mkdir(scopeRoot, { recursive: true });
|
||||||
|
const expectedArtifactDirStatus = await expectedArtifactDirStatuses(workspaceRoot, expectedArtifactDirs);
|
||||||
return {
|
return {
|
||||||
runId,
|
runId,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
@ -47,13 +49,15 @@ export async function prepareXWorkmateArtifacts(input) {
|
|||||||
artifactDirectory: scopeRoot,
|
artifactDirectory: scopeRoot,
|
||||||
relativeArtifactDirectory: artifactScope,
|
relativeArtifactDirectory: artifactScope,
|
||||||
warnings: [],
|
warnings: [],
|
||||||
|
expectedArtifactDirs,
|
||||||
|
expectedArtifactDirStatus,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export async function collectAndSnapshotXWorkmateArtifacts(input) {
|
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.sessionKey, "sessionKey required");
|
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "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);
|
||||||
@ -120,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.sessionKey, "sessionKey required");
|
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "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);
|
||||||
@ -133,6 +137,7 @@ export async function exportXWorkmateArtifacts(input) {
|
|||||||
});
|
});
|
||||||
const workspaceRoot = await fs.realpath(workspaceDir);
|
const workspaceRoot = await fs.realpath(workspaceDir);
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
const expectedDirs = normalizeExpectedArtifactDirs(params.expectedArtifactDirs);
|
||||||
const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
|
const expectedArtifactScope = artifactScopeFor(sessionKey, runId);
|
||||||
const requestedArtifactScope = optionalArtifactScope(params.artifactScope);
|
const requestedArtifactScope = optionalArtifactScope(params.artifactScope);
|
||||||
if (requestedArtifactScope && requestedArtifactScope !== expectedArtifactScope) {
|
if (requestedArtifactScope && requestedArtifactScope !== expectedArtifactScope) {
|
||||||
@ -165,9 +170,6 @@ export async function exportXWorkmateArtifacts(input) {
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
const candidates = scopedCandidates;
|
const candidates = scopedCandidates;
|
||||||
const expectedDirs = Array.isArray(params.expectedArtifactDirs)
|
|
||||||
? params.expectedArtifactDirs.map((d) => String(d).trim()).filter(Boolean)
|
|
||||||
: [];
|
|
||||||
if (candidates.length === 0 && expectedDirs.length > 0) {
|
if (candidates.length === 0 && expectedDirs.length > 0) {
|
||||||
for (const dir of expectedDirs) {
|
for (const dir of expectedDirs) {
|
||||||
const dirPath = path.join(workspaceRoot, safeInputRelativePath(dir, "expectedArtifactDir"));
|
const dirPath = path.join(workspaceRoot, safeInputRelativePath(dir, "expectedArtifactDir"));
|
||||||
@ -245,6 +247,8 @@ export async function exportXWorkmateArtifacts(input) {
|
|||||||
scopeKind,
|
scopeKind,
|
||||||
artifacts,
|
artifacts,
|
||||||
warnings,
|
warnings,
|
||||||
|
expectedArtifactDirs: expectedDirs,
|
||||||
|
expectedArtifactDirStatus: await expectedArtifactDirStatuses(workspaceRoot, expectedDirs),
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -252,7 +256,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.sessionKey, "sessionKey required");
|
const sessionKey = requiredString(params.openclawSessionKey ?? params.sessionKey, "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);
|
||||||
@ -347,9 +351,39 @@ export async function readXWorkmateArtifact(input) {
|
|||||||
scopeKind,
|
scopeKind,
|
||||||
artifacts: [artifact],
|
artifacts: [artifact],
|
||||||
warnings,
|
warnings,
|
||||||
|
expectedArtifactDirs: [],
|
||||||
|
expectedArtifactDirStatus: [],
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
export function normalizeExpectedArtifactDirs(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;
|
||||||
|
}
|
||||||
|
async function expectedArtifactDirStatuses(workspaceRoot, expectedArtifactDirs) {
|
||||||
|
const statuses = [];
|
||||||
|
for (const relativePath of expectedArtifactDirs) {
|
||||||
|
const dirPath = path.join(workspaceRoot, safeInputRelativePath(relativePath, "expectedArtifactDir"));
|
||||||
|
statuses.push({
|
||||||
|
relativePath,
|
||||||
|
exists: await directoryExists(dirPath),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return statuses;
|
||||||
|
}
|
||||||
export function formatArtifactManifestMarkdown(input) {
|
export function formatArtifactManifestMarkdown(input) {
|
||||||
const lines = [
|
const lines = [
|
||||||
"## XWorkmate artifacts",
|
"## XWorkmate artifacts",
|
||||||
|
|||||||
86
dist/src/taskState.d.ts
vendored
86
dist/src/taskState.d.ts
vendored
@ -1,57 +1,57 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||||
type XWorkmateTaskRecord = {
|
export declare const XWORKMATE_PLUGIN_ID = "openclaw-multi-session-plugins";
|
||||||
taskId: string;
|
export declare const XWORKMATE_SESSION_EXTENSION_NAMESPACE = "xworkmate.sessionMapping";
|
||||||
runtime: "acp";
|
export type XWorkmateTaskMetadataV1 = {
|
||||||
taskKind: "xworkmate-openclaw";
|
schemaVersion: 1;
|
||||||
requesterSessionKey: string;
|
appThreadKey: string;
|
||||||
ownerKey: string;
|
openclawSessionKey?: string;
|
||||||
scopeKind: "session";
|
expectedArtifactDirs: string[];
|
||||||
runId: string;
|
requestId?: string;
|
||||||
label: string;
|
externalTaskId?: string;
|
||||||
task: string;
|
createdAt: string;
|
||||||
status: "queued" | "running" | "succeeded" | "failed" | "timed_out" | "cancelled" | "lost";
|
|
||||||
deliveryStatus: "pending" | "delivered" | "session_queued" | "failed" | "parent_missing" | "not_applicable";
|
|
||||||
notifyPolicy: "done_only" | "state_changes" | "silent";
|
|
||||||
createdAt: number;
|
|
||||||
startedAt?: number;
|
|
||||||
endedAt?: number;
|
|
||||||
lastEventAt?: number;
|
|
||||||
error?: string;
|
|
||||||
progressSummary?: string;
|
|
||||||
terminalSummary?: string;
|
|
||||||
terminalOutcome?: "succeeded" | "blocked";
|
|
||||||
};
|
};
|
||||||
type XWorkmateSessionMapping = {
|
export type XWorkmateSessionMappingSource = "session_start" | "bridge_prepare";
|
||||||
appSessionKey: string;
|
export type XWorkmateSessionMappingV1 = {
|
||||||
openClawSessionKey: string;
|
schemaVersion: 1;
|
||||||
appThreadId?: string;
|
appThreadKey: string;
|
||||||
sessionId?: string;
|
openclawSessionKey: string;
|
||||||
runId: string;
|
expectedArtifactDirs: string[];
|
||||||
artifactScope?: string;
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
source: XWorkmateSessionMappingSource;
|
||||||
|
};
|
||||||
|
export type XWorkmateTaskLookupErrorCode = "mapping_not_found" | "task_not_found" | "no_native_task_record" | "conflict" | "invalid_lookup";
|
||||||
|
export type XWorkmateTaskLookupError = {
|
||||||
|
ok: false;
|
||||||
|
code: XWorkmateTaskLookupErrorCode;
|
||||||
|
message: string;
|
||||||
|
mapping?: XWorkmateSessionMappingV1;
|
||||||
expectedArtifactDirs?: string[];
|
expectedArtifactDirs?: string[];
|
||||||
};
|
};
|
||||||
export type XWorkmateTaskStore = {
|
export type XWorkmateTaskStore = Record<string, never>;
|
||||||
records: Map<string, XWorkmateTaskRecord>;
|
|
||||||
sessionMappingsByAppKey: Map<string, XWorkmateSessionMapping>;
|
|
||||||
sessionMappingsByOpenClawKey: Map<string, XWorkmateSessionMapping>;
|
|
||||||
};
|
|
||||||
export declare function createXWorkmateTaskStore(): XWorkmateTaskStore;
|
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;
|
taskStore?: XWorkmateTaskStore;
|
||||||
params: Record<string, unknown>;
|
params: Record<string, unknown>;
|
||||||
artifactScope?: string;
|
artifactScope?: string;
|
||||||
}): Promise<void>;
|
source?: XWorkmateSessionMappingSource;
|
||||||
export declare function registerXWorkmateDetachedTaskRuntime(api: OpenClawPluginApi, taskStore: XWorkmateTaskStore): void;
|
}): 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;
|
||||||
taskStore: XWorkmateTaskStore;
|
taskStore?: XWorkmateTaskStore;
|
||||||
params: Record<string, unknown>;
|
params: Record<string, unknown>;
|
||||||
}): Promise<Record<string, unknown>>;
|
}): Promise<Record<string, unknown>>;
|
||||||
export declare function createOrUpdateXWorkmateTaskRecord(input: XWorkmateTaskStore, options: {
|
|
||||||
params: Record<string, unknown>;
|
|
||||||
status: XWorkmateTaskRecord["status"];
|
|
||||||
progressSummary?: string;
|
|
||||||
}): XWorkmateTaskRecord;
|
|
||||||
export {};
|
|
||||||
|
|||||||
516
dist/src/taskState.js
vendored
516
dist/src/taskState.js
vendored
@ -1,12 +1,8 @@
|
|||||||
import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
|
import { exportXWorkmateArtifacts } from "./exportArtifacts.js";
|
||||||
const XWORKMATE_SESSION_EXTENSION_NAMESPACE = "xworkmate";
|
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 function createXWorkmateTaskStore() {
|
export function createXWorkmateTaskStore() {
|
||||||
return {
|
return {};
|
||||||
records: new Map(),
|
|
||||||
sessionMappingsByAppKey: new Map(),
|
|
||||||
sessionMappingsByOpenClawKey: new Map(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export function registerXWorkmateSessionExtension(api) {
|
export function registerXWorkmateSessionExtension(api) {
|
||||||
const registerExtension = api.session?.state?.registerSessionExtension ?? api.registerSessionExtension;
|
const registerExtension = api.session?.state?.registerSessionExtension ?? api.registerSessionExtension;
|
||||||
@ -15,251 +11,272 @@ export function registerXWorkmateSessionExtension(api) {
|
|||||||
}
|
}
|
||||||
registerExtension({
|
registerExtension({
|
||||||
namespace: XWORKMATE_SESSION_EXTENSION_NAMESPACE,
|
namespace: XWORKMATE_SESSION_EXTENSION_NAMESPACE,
|
||||||
description: "XWorkmate OpenClaw/App session key mapping for artifact and task recovery.",
|
description: "Durable XWorkmate app/OpenClaw session key mapping.",
|
||||||
sessionEntrySlotKey: "xworkmate",
|
sessionEntrySlotKey: "xworkmate",
|
||||||
project: (ctx) => {
|
project: (ctx) => {
|
||||||
const state = asRecord(ctx.state) ?? {};
|
const state = asRecord(ctx.state);
|
||||||
const appSessionKey = optionalString(state.appSessionKey) ||
|
return state ?? {};
|
||||||
optionalString(state.appThreadId) ||
|
},
|
||||||
optionalString(state.threadId) ||
|
});
|
||||||
appSessionKeyFromOpenClawSessionKey(ctx.sessionKey);
|
}
|
||||||
const openClawSessionKey = optionalString(state.openClawSessionKey) || ctx.sessionKey;
|
export function registerXWorkmateDetachedTaskRuntime(_api, _taskStore) {
|
||||||
|
// OpenClaw native task-registry is the only task status source for this plugin.
|
||||||
|
}
|
||||||
|
export async function recordXWorkmateSessionMapping(input) {
|
||||||
|
const metadata = normalizeXWorkmateTaskMetadataV1(input.params);
|
||||||
|
const openclawSessionKey = requiredString(input.params.openclawSessionKey ?? metadata.openclawSessionKey, "openclawSessionKey required");
|
||||||
|
return upsertXWorkmateSessionMapping(input.api, {
|
||||||
|
metadata: {
|
||||||
|
...metadata,
|
||||||
|
openclawSessionKey,
|
||||||
|
},
|
||||||
|
openclawSessionKey,
|
||||||
|
source: input.source ?? "bridge_prepare",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function normalizeXWorkmateTaskMetadataV1(input) {
|
||||||
|
const envelope = asRecord(input.xworkmate) ?? asRecord(input.xworkmateMetadata) ?? input;
|
||||||
|
const schemaVersion = Number(envelope.schemaVersion ?? 1);
|
||||||
|
if (schemaVersion !== 1) {
|
||||||
|
throw new Error("schemaVersion must be 1");
|
||||||
|
}
|
||||||
|
const appThreadKey = requiredString(envelope.appThreadKey, "appThreadKey required");
|
||||||
|
const createdAt = optionalString(envelope.createdAt) || new Date().toISOString();
|
||||||
|
return compactObject({
|
||||||
|
schemaVersion: 1,
|
||||||
|
appThreadKey,
|
||||||
|
openclawSessionKey: optionalString(envelope.openclawSessionKey),
|
||||||
|
expectedArtifactDirs: normalizeExpectedArtifactDirs(envelope.expectedArtifactDirs),
|
||||||
|
requestId: optionalString(envelope.requestId),
|
||||||
|
externalTaskId: optionalString(envelope.externalTaskId ?? envelope.taskId),
|
||||||
|
createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function normalizeExpectedArtifactDirs(value) {
|
||||||
|
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);
|
||||||
|
if (!patchSessionEntry) {
|
||||||
|
throw new Error("OpenClaw runtime session patch API is unavailable");
|
||||||
|
}
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
let mapping;
|
||||||
|
await patchSessionEntry({
|
||||||
|
sessionKey: input.openclawSessionKey,
|
||||||
|
preserveActivity: true,
|
||||||
|
update: (entry) => {
|
||||||
|
const existing = readMappingFromEntry(entry);
|
||||||
|
if (existing) {
|
||||||
|
assertMappingCompatible(existing, input.metadata.appThreadKey, input.openclawSessionKey);
|
||||||
|
mapping = {
|
||||||
|
...existing,
|
||||||
|
expectedArtifactDirs: input.metadata.expectedArtifactDirs,
|
||||||
|
updatedAt: now,
|
||||||
|
source: existing.source,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mapping = compactObject({
|
||||||
|
schemaVersion: 1,
|
||||||
|
appThreadKey: input.metadata.appThreadKey,
|
||||||
|
openclawSessionKey: input.openclawSessionKey,
|
||||||
|
expectedArtifactDirs: input.metadata.expectedArtifactDirs,
|
||||||
|
createdAt: input.metadata.createdAt || now,
|
||||||
|
updatedAt: now,
|
||||||
|
source: input.source,
|
||||||
|
});
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
pluginExtensions: writeMappingToPluginExtensions(entry.pluginExtensions, mapping),
|
||||||
appSessionKey,
|
|
||||||
openClawSessionKey,
|
|
||||||
sessionId: optionalString(state.sessionId) || optionalString(ctx.sessionId),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
if (!mapping) {
|
||||||
export async function recordXWorkmateSessionMapping(input) {
|
throw new Error("failed to write xworkmate session mapping");
|
||||||
const appSessionKey = requiredString(input.params.sessionKey || input.params.appSessionKey, "sessionKey required");
|
|
||||||
const runId = requiredString(input.params.runId, "runId required");
|
|
||||||
const openClawSessionKey = optionalString(input.params.openClawSessionKey) ||
|
|
||||||
optionalString(input.params.openClawSessionId) ||
|
|
||||||
agentMainSessionKeyFor(appSessionKey);
|
|
||||||
const expectedArtifactDirs = stringList(input.params.expectedArtifactDirs);
|
|
||||||
const mapping = compactObject({
|
|
||||||
appSessionKey,
|
|
||||||
openClawSessionKey,
|
|
||||||
appThreadId: optionalString(input.params.threadId) || appSessionKey,
|
|
||||||
sessionId: optionalString(input.params.sessionId),
|
|
||||||
runId,
|
|
||||||
artifactScope: input.artifactScope || optionalString(input.params.artifactScope),
|
|
||||||
expectedArtifactDirs: expectedArtifactDirs.length > 0 ? expectedArtifactDirs : undefined,
|
|
||||||
});
|
|
||||||
input.taskStore.sessionMappingsByAppKey.set(appSessionKey, mapping);
|
|
||||||
input.taskStore.sessionMappingsByOpenClawKey.set(openClawSessionKey, mapping);
|
|
||||||
const patchSessionExtension = resolvePatchSessionExtension(input.api);
|
|
||||||
if (!patchSessionExtension) {
|
|
||||||
// Legacy fallback owner: this plugin. Scope: tests and OpenClaw hosts that do not expose
|
|
||||||
// session extension patching yet. Exit: remove this map once 2026.6.1+ hosts expose the patch
|
|
||||||
// method on the public plugin API in all supported deployments.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
await patchSessionExtension({
|
return mapping;
|
||||||
key: openClawSessionKey,
|
|
||||||
sessionKey: openClawSessionKey,
|
|
||||||
pluginId: XWORKMATE_PLUGIN_ID,
|
|
||||||
namespace: XWORKMATE_SESSION_EXTENSION_NAMESPACE,
|
|
||||||
value: mapping,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
export function registerXWorkmateDetachedTaskRuntime(api, taskStore) {
|
export async function readXWorkmateSessionMapping(api, lookup) {
|
||||||
const registerRuntime = api.registerDetachedTaskRuntime;
|
const getSessionEntry = resolveGetSessionEntry(api);
|
||||||
if (typeof registerRuntime !== "function") {
|
if (!getSessionEntry) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
registerRuntime({
|
const openclawSessionKey = optionalString(lookup.openclawSessionKey);
|
||||||
createQueuedTaskRun: (params) => createOrUpdateXWorkmateTaskRecord(taskStore, { params, status: "queued" }),
|
if (openclawSessionKey) {
|
||||||
createRunningTaskRun: (params) => createOrUpdateXWorkmateTaskRecord(taskStore, { params, status: "running" }),
|
return readMappingFromEntry(getSessionEntry({ sessionKey: openclawSessionKey }));
|
||||||
startTaskRunByRunId: (params) => updateXWorkmateTaskRecordsByRunId(taskStore, params, { status: "running", startedAt: Date.now() }),
|
}
|
||||||
recordTaskRunProgressByRunId: (params) => updateXWorkmateTaskRecordsByRunId(taskStore, params, {
|
const appThreadKey = optionalString(lookup.appThreadKey);
|
||||||
lastEventAt: Date.now(),
|
if (!appThreadKey) {
|
||||||
progressSummary: optionalString(params.progressSummary) || optionalString(params.eventSummary),
|
return undefined;
|
||||||
}),
|
}
|
||||||
finalizeTaskRunByRunId: (params) => updateXWorkmateTaskRecordsByRunId(taskStore, params, terminalPatch(params)),
|
const listSessionEntries = resolveListSessionEntries(api);
|
||||||
completeTaskRunByRunId: (params) => updateXWorkmateTaskRecordsByRunId(taskStore, params, {
|
for (const item of listSessionEntries?.() ?? []) {
|
||||||
status: "succeeded",
|
const mapping = readMappingFromEntry(item.entry);
|
||||||
endedAt: numberOrNow(params.endedAt),
|
if (mapping?.appThreadKey === appThreadKey) {
|
||||||
lastEventAt: numberOrNow(params.lastEventAt),
|
return mapping;
|
||||||
terminalSummary: optionalString(params.terminalSummary) || optionalString(params.progressSummary),
|
}
|
||||||
terminalOutcome: "succeeded",
|
}
|
||||||
}),
|
return undefined;
|
||||||
failTaskRunByRunId: (params) => updateXWorkmateTaskRecordsByRunId(taskStore, params, {
|
|
||||||
status: taskStatusFrom(params.status, "failed"),
|
|
||||||
endedAt: numberOrNow(params.endedAt),
|
|
||||||
lastEventAt: numberOrNow(params.lastEventAt),
|
|
||||||
error: optionalString(params.error),
|
|
||||||
terminalSummary: optionalString(params.terminalSummary) || optionalString(params.progressSummary),
|
|
||||||
}),
|
|
||||||
setDetachedTaskDeliveryStatusByRunId: (params) => updateXWorkmateTaskRecordsByRunId(taskStore, params, {
|
|
||||||
deliveryStatus: deliveryStatusFrom(params.deliveryStatus, "delivered"),
|
|
||||||
error: optionalString(params.error),
|
|
||||||
}),
|
|
||||||
cancelDetachedTaskRunById: async (params) => {
|
|
||||||
const taskId = optionalString(params.taskId);
|
|
||||||
const record = taskId ? findXWorkmateTaskByTaskId(taskStore, taskId) : undefined;
|
|
||||||
if (!record) {
|
|
||||||
return { found: false, cancelled: false };
|
|
||||||
}
|
|
||||||
record.status = "cancelled";
|
|
||||||
record.endedAt = Date.now();
|
|
||||||
record.lastEventAt = record.endedAt;
|
|
||||||
return { found: true, cancelled: true, reason: optionalString(params.reason), task: record };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
export async function getXWorkmateTaskSnapshot(input) {
|
export async function getXWorkmateTaskSnapshot(input) {
|
||||||
const sessionKey = requiredString(input.params.sessionKey, "sessionKey required");
|
const params = input.params ?? {};
|
||||||
const runId = requiredString(input.params.runId, "runId required");
|
const appThreadKey = optionalString(params.appThreadKey);
|
||||||
const mapping = resolveSessionMapping(input.taskStore, input.params, sessionKey);
|
const explicitOpenclawSessionKey = optionalString(params.openclawSessionKey);
|
||||||
const openClawSessionKey = mapping?.openClawSessionKey || optionalString(input.params.openClawSessionKey) || agentMainSessionKeyFor(sessionKey);
|
const mapping = await readXWorkmateSessionMapping(input.api, {
|
||||||
const appSessionKey = mapping?.appSessionKey || sessionKey;
|
appThreadKey,
|
||||||
const nativeTask = resolveNativeTask(input.api, openClawSessionKey, runId) || resolveNativeTask(input.api, sessionKey, runId);
|
openclawSessionKey: explicitOpenclawSessionKey,
|
||||||
const storedTask = findXWorkmateTask(input.taskStore, sessionKey, runId);
|
|
||||||
const exported = await exportXWorkmateArtifacts({
|
|
||||||
params: input.params,
|
|
||||||
config: input.api.config,
|
|
||||||
pluginConfig: input.api.pluginConfig,
|
|
||||||
});
|
});
|
||||||
const task = nativeTask || storedTask;
|
if (!mapping && appThreadKey && !explicitOpenclawSessionKey) {
|
||||||
const taskStatus = normalizeTaskStatus(optionalString(task.status), exported.artifacts.length > 0);
|
return lookupError("mapping_not_found", `No OpenClaw session mapping found for ${appThreadKey}`);
|
||||||
if (storedTask && taskStatus === "succeeded" && storedTask.status !== "succeeded") {
|
|
||||||
storedTask.status = "succeeded";
|
|
||||||
storedTask.endedAt = Date.now();
|
|
||||||
storedTask.lastEventAt = storedTask.endedAt;
|
|
||||||
storedTask.terminalOutcome = "succeeded";
|
|
||||||
}
|
}
|
||||||
|
const openclawSessionKey = mapping?.openclawSessionKey || explicitOpenclawSessionKey;
|
||||||
|
if (!openclawSessionKey) {
|
||||||
|
return lookupError("invalid_lookup", "openclawSessionKey or appThreadKey required");
|
||||||
|
}
|
||||||
|
const runId = optionalString(params.runId);
|
||||||
|
const taskId = optionalString(params.taskId);
|
||||||
|
const task = resolveNativeTask(input.api, {
|
||||||
|
openclawSessionKey,
|
||||||
|
runId,
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
|
if (!task) {
|
||||||
|
const code = runId || taskId ? "no_native_task_record" : "task_not_found";
|
||||||
|
return lookupError(code, `No native OpenClaw task record found for ${openclawSessionKey}`, mapping);
|
||||||
|
}
|
||||||
|
const taskStatus = optionalString(task.status) || "running";
|
||||||
|
const includeArtifacts = params.includeArtifacts !== false;
|
||||||
|
const exported = includeArtifacts
|
||||||
|
? await exportXWorkmateArtifacts({
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
openclawSessionKey,
|
||||||
|
runId: runId || optionalString(task.runId) || optionalString(task.taskId),
|
||||||
|
expectedArtifactDirs: mapping?.expectedArtifactDirs ?? normalizeExpectedArtifactDirs(params.expectedArtifactDirs),
|
||||||
|
includeContent: params.includeContent ?? false,
|
||||||
|
},
|
||||||
|
config: input.api.config,
|
||||||
|
pluginConfig: input.api.pluginConfig,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: appStatusFromTaskStatus(taskStatus),
|
status: appStatusFromTaskStatus(taskStatus),
|
||||||
taskStatus,
|
taskStatus,
|
||||||
mode: "gateway-chat",
|
mode: "gateway-chat",
|
||||||
sessionKey,
|
mapping,
|
||||||
openClawSessionKey,
|
appThreadKey: mapping?.appThreadKey ?? appThreadKey,
|
||||||
appSessionKey,
|
openclawSessionKey,
|
||||||
runId,
|
runId: runId || optionalString(task.runId),
|
||||||
|
taskId: taskId || optionalString(task.taskId),
|
||||||
task,
|
task,
|
||||||
artifactScope: exported.artifactScope,
|
expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [],
|
||||||
remoteWorkingDirectory: exported.remoteWorkingDirectory,
|
artifactScope: exported?.artifactScope,
|
||||||
remoteWorkspaceRefKind: exported.remoteWorkspaceRefKind,
|
remoteWorkingDirectory: exported?.remoteWorkingDirectory,
|
||||||
scopeKind: exported.scopeKind,
|
remoteWorkspaceRefKind: exported?.remoteWorkspaceRefKind,
|
||||||
artifacts: exported.artifacts,
|
scopeKind: exported?.scopeKind,
|
||||||
warnings: exported.warnings,
|
artifacts: exported?.artifacts ?? [],
|
||||||
artifactCount: exported.artifacts.length,
|
warnings: exported?.warnings ?? [],
|
||||||
|
artifactCount: exported?.artifacts.length ?? 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function createOrUpdateXWorkmateTaskRecord(input, options) {
|
function resolveNativeTask(api, input) {
|
||||||
const sessionKey = requiredString(options.params.sessionKey || options.params.requesterSessionKey, "sessionKey required");
|
|
||||||
const runId = requiredString(options.params.runId, "runId required");
|
|
||||||
const key = taskRecordKey(sessionKey, runId);
|
|
||||||
const now = Date.now();
|
|
||||||
const existing = input.records.get(key);
|
|
||||||
if (existing) {
|
|
||||||
existing.status = options.status;
|
|
||||||
existing.lastEventAt = now;
|
|
||||||
if (options.status === "running" && !existing.startedAt) {
|
|
||||||
existing.startedAt = now;
|
|
||||||
}
|
|
||||||
if (options.progressSummary) {
|
|
||||||
existing.progressSummary = options.progressSummary;
|
|
||||||
}
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
const record = {
|
|
||||||
taskId: `xworkmate:${safeTaskIdSegment(sessionKey)}:${safeTaskIdSegment(runId)}`,
|
|
||||||
runtime: "acp",
|
|
||||||
taskKind: "xworkmate-openclaw",
|
|
||||||
requesterSessionKey: optionalString(options.params.openClawSessionKey) || agentMainSessionKeyFor(sessionKey),
|
|
||||||
ownerKey: sessionKey,
|
|
||||||
scopeKind: "session",
|
|
||||||
runId,
|
|
||||||
label: optionalString(options.params.label) || "XWorkmate OpenClaw task",
|
|
||||||
task: optionalString(options.params.taskPrompt) || optionalString(options.params.task) || "XWorkmate OpenClaw task",
|
|
||||||
status: options.status,
|
|
||||||
deliveryStatus: "pending",
|
|
||||||
notifyPolicy: "state_changes",
|
|
||||||
createdAt: now,
|
|
||||||
startedAt: options.status === "running" ? now : undefined,
|
|
||||||
lastEventAt: now,
|
|
||||||
progressSummary: options.progressSummary,
|
|
||||||
};
|
|
||||||
input.records.set(key, record);
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
function updateXWorkmateTaskRecordsByRunId(input, params, patch) {
|
|
||||||
const runId = optionalString(params.runId);
|
|
||||||
const sessionKey = optionalString(params.sessionKey || params.requesterSessionKey);
|
|
||||||
const records = [...input.records.values()].filter((record) => {
|
|
||||||
if (runId && record.runId !== runId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (sessionKey && record.ownerKey !== sessionKey && record.requesterSessionKey !== sessionKey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
for (const record of records) {
|
|
||||||
Object.assign(record, compactObject(patch));
|
|
||||||
}
|
|
||||||
return records;
|
|
||||||
}
|
|
||||||
function resolveNativeTask(api, sessionKey, runId) {
|
|
||||||
try {
|
try {
|
||||||
const bound = api.runtime?.tasks?.runs?.bindSession?.({ sessionKey });
|
const bound = api.runtime?.tasks?.runs?.bindSession?.({ sessionKey: input.openclawSessionKey });
|
||||||
const resolved = bound?.resolve?.(runId) || bound?.get?.(runId);
|
if (!bound) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const lookup = input.taskId || input.runId || "";
|
||||||
|
const resolved = lookup ? bound.resolve?.(lookup) || bound.get?.(lookup) : bound.findLatest?.();
|
||||||
return asRecord(resolved);
|
return asRecord(resolved);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
api.logger?.warn?.(`xworkmate task native registry lookup failed: sessionKey=${sessionKey} runId=${runId} error=${String(error)}`);
|
api.logger?.warn?.(`xworkmate native task lookup failed: sessionKey=${input.openclawSessionKey} error=${String(error)}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function resolveSessionMapping(input, params, sessionKey) {
|
function lookupError(code, message, mapping) {
|
||||||
const explicitOpenClawKey = optionalString(params.openClawSessionKey);
|
|
||||||
if (explicitOpenClawKey) {
|
|
||||||
const byOpenClaw = input.sessionMappingsByOpenClawKey.get(explicitOpenClawKey);
|
|
||||||
if (byOpenClaw) {
|
|
||||||
return byOpenClaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return input.sessionMappingsByAppKey.get(sessionKey) || input.sessionMappingsByOpenClawKey.get(sessionKey);
|
|
||||||
}
|
|
||||||
function findXWorkmateTask(input, sessionKey, runId) {
|
|
||||||
return input.records.get(taskRecordKey(sessionKey, runId));
|
|
||||||
}
|
|
||||||
function findXWorkmateTaskByTaskId(input, taskId) {
|
|
||||||
return [...input.records.values()].find((record) => record.taskId === taskId);
|
|
||||||
}
|
|
||||||
function taskRecordKey(sessionKey, runId) {
|
|
||||||
return `${sessionKey}\u0000${runId}`;
|
|
||||||
}
|
|
||||||
function appSessionKeyFromOpenClawSessionKey(sessionKey) {
|
|
||||||
return sessionKey.startsWith("agent:main:") ? sessionKey.slice("agent:main:".length) : sessionKey;
|
|
||||||
}
|
|
||||||
function agentMainSessionKeyFor(sessionKey) {
|
|
||||||
return sessionKey.startsWith("agent:") ? sessionKey : `agent:main:${sessionKey}`;
|
|
||||||
}
|
|
||||||
function terminalPatch(params) {
|
|
||||||
const status = taskStatusFrom(params.status, "succeeded");
|
|
||||||
return {
|
return {
|
||||||
status,
|
ok: false,
|
||||||
endedAt: numberOrNow(params.endedAt),
|
code,
|
||||||
lastEventAt: numberOrNow(params.lastEventAt),
|
message,
|
||||||
error: optionalString(params.error),
|
...(mapping ? { mapping, expectedArtifactDirs: mapping.expectedArtifactDirs } : {}),
|
||||||
progressSummary: optionalString(params.progressSummary),
|
|
||||||
terminalSummary: optionalString(params.terminalSummary),
|
|
||||||
terminalOutcome: status === "succeeded" ? "succeeded" : "blocked",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function normalizeTaskStatus(status, hasArtifacts) {
|
function readMappingFromEntry(entry) {
|
||||||
const normalized = taskStatusFrom(status, hasArtifacts ? "succeeded" : "running");
|
const pluginState = asRecord(entry?.pluginExtensions?.[XWORKMATE_PLUGIN_ID]);
|
||||||
if (normalized === "running" && hasArtifacts) {
|
const raw = asRecord(pluginState?.[XWORKMATE_SESSION_EXTENSION_NAMESPACE]);
|
||||||
return "succeeded";
|
if (!raw || raw.schemaVersion !== 1) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return normalized;
|
const appThreadKey = optionalString(raw.appThreadKey);
|
||||||
|
const openclawSessionKey = optionalString(raw.openclawSessionKey);
|
||||||
|
if (!appThreadKey || !openclawSessionKey) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
schemaVersion: 1,
|
||||||
|
appThreadKey,
|
||||||
|
openclawSessionKey,
|
||||||
|
expectedArtifactDirs: normalizeExpectedArtifactDirs(raw.expectedArtifactDirs),
|
||||||
|
createdAt: optionalString(raw.createdAt) || new Date(0).toISOString(),
|
||||||
|
updatedAt: optionalString(raw.updatedAt) || optionalString(raw.createdAt) || new Date(0).toISOString(),
|
||||||
|
source: parseMappingSource(raw.source),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function writeMappingToPluginExtensions(current, mapping) {
|
||||||
|
if (!mapping) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(current ?? {}),
|
||||||
|
[XWORKMATE_PLUGIN_ID]: {
|
||||||
|
...(current?.[XWORKMATE_PLUGIN_ID] ?? {}),
|
||||||
|
[XWORKMATE_SESSION_EXTENSION_NAMESPACE]: mapping,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function assertMappingCompatible(existing, appThreadKey, openclawSessionKey) {
|
||||||
|
if (existing.appThreadKey !== appThreadKey || existing.openclawSessionKey !== openclawSessionKey) {
|
||||||
|
throw new Error("conflict: xworkmate session mapping already points to a different session");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function resolvePatchSessionEntry(api) {
|
||||||
|
const runtimeSession = (api.runtime?.agent?.session ?? {});
|
||||||
|
const candidate = runtimeSession.patchSessionEntry;
|
||||||
|
return typeof candidate === "function" ? candidate : undefined;
|
||||||
|
}
|
||||||
|
function resolveGetSessionEntry(api) {
|
||||||
|
const runtimeSession = (api.runtime?.agent?.session ?? {});
|
||||||
|
const candidate = runtimeSession.getSessionEntry;
|
||||||
|
return typeof candidate === "function" ? candidate : undefined;
|
||||||
|
}
|
||||||
|
function resolveListSessionEntries(api) {
|
||||||
|
const runtimeSession = (api.runtime?.agent?.session ?? {});
|
||||||
|
const candidate = runtimeSession.listSessionEntries;
|
||||||
|
return typeof candidate === "function"
|
||||||
|
? candidate
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
function appStatusFromTaskStatus(status) {
|
function appStatusFromTaskStatus(status) {
|
||||||
if (status === "succeeded") {
|
if (status === "succeeded") {
|
||||||
@ -270,38 +287,12 @@ function appStatusFromTaskStatus(status) {
|
|||||||
}
|
}
|
||||||
return "running";
|
return "running";
|
||||||
}
|
}
|
||||||
function taskStatusFrom(value, fallback) {
|
function parseMappingSource(value) {
|
||||||
const status = optionalString(value);
|
const source = optionalString(value);
|
||||||
if (status === "queued" ||
|
if (source === "session_start" || source === "bridge_prepare") {
|
||||||
status === "running" ||
|
return source;
|
||||||
status === "succeeded" ||
|
|
||||||
status === "failed" ||
|
|
||||||
status === "timed_out" ||
|
|
||||||
status === "cancelled" ||
|
|
||||||
status === "lost") {
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
return fallback;
|
return "bridge_prepare";
|
||||||
}
|
|
||||||
function deliveryStatusFrom(value, fallback) {
|
|
||||||
const status = optionalString(value);
|
|
||||||
if (status === "pending" ||
|
|
||||||
status === "delivered" ||
|
|
||||||
status === "session_queued" ||
|
|
||||||
status === "failed" ||
|
|
||||||
status === "parent_missing" ||
|
|
||||||
status === "not_applicable") {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
function resolvePatchSessionExtension(api) {
|
|
||||||
const stateApi = (api.session?.state ?? {});
|
|
||||||
const apiRecord = api;
|
|
||||||
const candidate = stateApi.patchSessionExtension || apiRecord.patchSessionExtension;
|
|
||||||
return typeof candidate === "function"
|
|
||||||
? candidate
|
|
||||||
: undefined;
|
|
||||||
}
|
}
|
||||||
function requiredString(value, message) {
|
function requiredString(value, message) {
|
||||||
const text = optionalString(value);
|
const text = optionalString(value);
|
||||||
@ -317,26 +308,6 @@ function optionalString(value) {
|
|||||||
const text = String(value).trim();
|
const text = String(value).trim();
|
||||||
return text === "<nil>" ? "" : text;
|
return text === "<nil>" ? "" : text;
|
||||||
}
|
}
|
||||||
function stringList(value) {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const seen = new Set();
|
|
||||||
const result = [];
|
|
||||||
for (const entry of value) {
|
|
||||||
const text = optionalString(entry);
|
|
||||||
if (!text || seen.has(text)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(text);
|
|
||||||
result.push(text);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
function numberOrNow(value) {
|
|
||||||
const parsed = Number(value);
|
|
||||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : Date.now();
|
|
||||||
}
|
|
||||||
function asRecord(value) {
|
function asRecord(value) {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -346,6 +317,3 @@ function asRecord(value) {
|
|||||||
function compactObject(value) {
|
function compactObject(value) {
|
||||||
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined && entry[1] !== ""));
|
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined && entry[1] !== ""));
|
||||||
}
|
}
|
||||||
function safeTaskIdSegment(value) {
|
|
||||||
return value.replace(/[^A-Za-z0-9._:-]+/g, "_");
|
|
||||||
}
|
|
||||||
|
|||||||
5524
package-lock.json
generated
Normal file
5524
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,6 @@ export type XWorkmateSessionMappingV1 = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
source: XWorkmateSessionMappingSource;
|
source: XWorkmateSessionMappingSource;
|
||||||
legacyDerived?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XWorkmateTaskLookupErrorCode =
|
export type XWorkmateTaskLookupErrorCode =
|
||||||
@ -160,7 +159,6 @@ export async function upsertXWorkmateSessionMapping(
|
|||||||
metadata: XWorkmateTaskMetadataV1;
|
metadata: XWorkmateTaskMetadataV1;
|
||||||
openclawSessionKey: string;
|
openclawSessionKey: string;
|
||||||
source: XWorkmateSessionMappingSource;
|
source: XWorkmateSessionMappingSource;
|
||||||
legacyDerived?: boolean;
|
|
||||||
},
|
},
|
||||||
): Promise<XWorkmateSessionMappingV1> {
|
): Promise<XWorkmateSessionMappingV1> {
|
||||||
const patchSessionEntry = resolvePatchSessionEntry(api);
|
const patchSessionEntry = resolvePatchSessionEntry(api);
|
||||||
@ -192,7 +190,6 @@ export async function upsertXWorkmateSessionMapping(
|
|||||||
createdAt: input.metadata.createdAt || now,
|
createdAt: input.metadata.createdAt || now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
source: input.source,
|
source: input.source,
|
||||||
legacyDerived: input.legacyDerived === true ? true : undefined,
|
|
||||||
}) as XWorkmateSessionMappingV1;
|
}) as XWorkmateSessionMappingV1;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -360,7 +357,6 @@ function readMappingFromEntry(entry: SessionEntry | undefined | null): XWorkmate
|
|||||||
createdAt: optionalString(raw.createdAt) || new Date(0).toISOString(),
|
createdAt: optionalString(raw.createdAt) || new Date(0).toISOString(),
|
||||||
updatedAt: optionalString(raw.updatedAt) || optionalString(raw.createdAt) || new Date(0).toISOString(),
|
updatedAt: optionalString(raw.updatedAt) || optionalString(raw.createdAt) || new Date(0).toISOString(),
|
||||||
source: parseMappingSource(raw.source),
|
source: parseMappingSource(raw.source),
|
||||||
...(raw.legacyDerived === true ? { legacyDerived: true } : {}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user