diff --git a/dist/index.d.ts b/dist/index.d.ts index ee2d980..4a553a0 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,3 +1,4 @@ +export declare function lastAssistantText(messages: unknown): string | undefined; declare const plugin: { id: string; name: string; diff --git a/dist/index.js b/dist/index.js index 295a470..0931661 100644 --- a/dist/index.js +++ b/dist/index.js @@ -33,6 +33,37 @@ function resolveRunScope(ctx) { function stringParam(value) { return typeof value === "string" ? value.trim() : ""; } +export function lastAssistantText(messages) { + if (!Array.isArray(messages)) + return undefined; + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index]; + if (!message || typeof message !== "object") + continue; + const record = message; + if (stringParam(record.role).toLowerCase() !== "assistant") + continue; + const content = record.content; + if (typeof content === "string" && content.trim()) + return content.trim(); + if (!Array.isArray(content)) + continue; + const text = content + .map((block) => { + if (!block || typeof block !== "object") + return ""; + const item = block; + const type = stringParam(item.type).toLowerCase(); + return type === "text" || type === "output_text" ? stringParam(item.text) : ""; + }) + .filter(Boolean) + .join("\n") + .trim(); + if (text) + return text; + } + return undefined; +} const plugin = definePluginEntry({ id: "openclaw-multi-session-plugins", name: "openclaw-multi-session-plugins", @@ -77,6 +108,7 @@ function register(api) { openclawSessionKey, runId, success: event?.success === true, + output: lastAssistantText(event?.messages), error: event?.error, }); } diff --git a/dist/src/taskState.d.ts b/dist/src/taskState.d.ts index 5c37161..92d5b4d 100644 --- a/dist/src/taskState.d.ts +++ b/dist/src/taskState.d.ts @@ -36,6 +36,7 @@ export type XWorkmateRecordedTaskRunV1 = { startedAt: string; updatedAt: string; completedAt?: string; + output?: string; error?: string; }; export declare function registerXWorkmateSessionExtension(api: OpenClawPluginApi): void; @@ -55,6 +56,7 @@ export declare function recordXWorkmateTaskRunTerminal(input: { openclawSessionKey: string; runId: string; success: boolean; + output?: unknown; error?: unknown; }): Promise; export declare function getXWorkmateTaskSnapshot(input: { diff --git a/dist/src/taskState.js b/dist/src/taskState.js index e6f4c2a..dc7877a 100644 --- a/dist/src/taskState.js +++ b/dist/src/taskState.js @@ -51,6 +51,7 @@ export async function recordXWorkmateTaskRunTerminal(input) { success: input.success, updatedAt: now, completedAt: now, + output: sanitizeTaskRunOutput(input.output), error: sanitizeTaskRunError(input.error), }); } @@ -194,8 +195,10 @@ export async function getXWorkmateTaskSnapshot(input) { completedAt: recordedRun.completedAt, error: recordedRun.error, }, + output: recordedRun.output, + resultSummary: recordedRun.output, error: recordedRun.error, - message: recordedRun.error, + message: recordedRun.output ?? recordedRun.error, expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [], artifactScope: exported?.artifactScope, remoteWorkingDirectory: exported?.remoteWorkingDirectory, @@ -295,6 +298,7 @@ async function upsertXWorkmateTaskRun(api, input) { startedAt: existing?.startedAt ?? input.startedAt ?? input.updatedAt, updatedAt: input.updatedAt, completedAt: input.completedAt, + output: input.output, error: input.error, }); runs[input.runId] = recorded; @@ -347,11 +351,19 @@ function readTaskRunsFromEntry(entry) { startedAt: optionalString(raw?.startedAt) || new Date(0).toISOString(), updatedAt: optionalString(raw?.updatedAt) || new Date(0).toISOString(), completedAt: optionalString(raw?.completedAt), + output: optionalString(raw?.output), error: optionalString(raw?.error), }); } return result; } +function sanitizeTaskRunOutput(value) { + const raw = optionalString(value); + if (!raw) { + return undefined; + } + return raw.slice(0, 16 * 1024); +} function sanitizeTaskRunError(value) { const raw = optionalString(value); if (!raw) { diff --git a/index.test.ts b/index.test.ts index 4eb3a9f..8503664 100644 --- a/index.test.ts +++ b/index.test.ts @@ -3,7 +3,7 @@ import os from "node:os"; import path from "node:path"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { describe, expect, it } from "vitest"; -import plugin from "./index.js"; +import plugin, { lastAssistantText } from "./index.js"; import { prepareXWorkmateArtifacts } from "./src/exportArtifacts.js"; type GatewayMethodHandler = Parameters[1]; @@ -14,6 +14,12 @@ type GatewayMethodResponse = { }; describe("plugin registration", () => { + it("extracts only the final assistant display text", () => { + expect(lastAssistantText([ + { role: "user", content: "secret prompt" }, + { role: "assistant", content: [{ type: "tool_call", text: "ignored" }, { type: "text", text: "完成并已保存。" }] }, + ])).toBe("完成并已保存。"); + }); it("declares registered agent tools in the manifest contract", () => { const manifest = JSON.parse(fs.readFileSync("openclaw.plugin.json", "utf8")) as { contracts?: { tools?: string[]; sessionScopedTools?: string[] }; @@ -264,7 +270,7 @@ describe("plugin registration", () => { expect(snapshot.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md" }]); await hooks.get("agent_end")?.( - { runId: "turn-1", success: false, error: "401 authentication failed" }, + { runId: "turn-1", success: false, error: "401 authentication failed", messages: [{ role: "assistant", content: [{ type: "text", text: "上游认证失败。" }] }] }, { sessionKey: "draft:1780636411666238-3", runId: "turn-1" }, ); expect(sessionExtensionPatches.at(-1)).toMatchObject({ diff --git a/index.ts b/index.ts index c47ba9a..80c319f 100644 --- a/index.ts +++ b/index.ts @@ -86,6 +86,31 @@ function stringParam(value: unknown): string { return typeof value === "string" ? value.trim() : ""; } +export function lastAssistantText(messages: unknown): string | undefined { + if (!Array.isArray(messages)) return undefined; + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index]; + if (!message || typeof message !== "object") continue; + const record = message as Record; + if (stringParam(record.role).toLowerCase() !== "assistant") continue; + const content = record.content; + if (typeof content === "string" && content.trim()) return content.trim(); + if (!Array.isArray(content)) continue; + const text = content + .map((block) => { + if (!block || typeof block !== "object") return ""; + const item = block as Record; + const type = stringParam(item.type).toLowerCase(); + return type === "text" || type === "output_text" ? stringParam(item.text) : ""; + }) + .filter(Boolean) + .join("\n") + .trim(); + if (text) return text; + } + return undefined; +} + const plugin = definePluginEntry({ id: "openclaw-multi-session-plugins", name: "openclaw-multi-session-plugins", @@ -139,6 +164,7 @@ function register(api: OpenClawPluginApi) { openclawSessionKey, runId, success: event?.success === true, + output: lastAssistantText(event?.messages), error: event?.error, }); } catch (error) { diff --git a/src/taskState.test.ts b/src/taskState.test.ts index 8fd0f67..d1faed4 100644 --- a/src/taskState.test.ts +++ b/src/taskState.test.ts @@ -275,6 +275,7 @@ describe("xworkmate task state mapping", () => { openclawSessionKey: "agent:main:draft:failed-run", runId: "turn-failed", success: false, + output: "任务执行失败前的说明", error: "401 Authentication Fails, api_key=sk-secret-value", }); @@ -292,6 +293,9 @@ describe("xworkmate task state mapping", () => { taskStatus: "failed", terminal: true, terminalSource: "agent_end", + output: "任务执行失败前的说明", + resultSummary: "任务执行失败前的说明", + message: "任务执行失败前的说明", task: { runId: "turn-failed", status: "failed", diff --git a/src/taskState.ts b/src/taskState.ts index 7381c3f..e96ba56 100644 --- a/src/taskState.ts +++ b/src/taskState.ts @@ -54,6 +54,7 @@ export type XWorkmateRecordedTaskRunV1 = { startedAt: string; updatedAt: string; completedAt?: string; + output?: string; error?: string; }; @@ -136,6 +137,7 @@ export async function recordXWorkmateTaskRunTerminal(input: { openclawSessionKey: string; runId: string; success: boolean; + output?: unknown; error?: unknown; }): Promise { const now = new Date().toISOString(); @@ -146,6 +148,7 @@ export async function recordXWorkmateTaskRunTerminal(input: { success: input.success, updatedAt: now, completedAt: now, + output: sanitizeTaskRunOutput(input.output), error: sanitizeTaskRunError(input.error), }); } @@ -311,8 +314,10 @@ export async function getXWorkmateTaskSnapshot(input: { completedAt: recordedRun.completedAt, error: recordedRun.error, }, + output: recordedRun.output, + resultSummary: recordedRun.output, error: recordedRun.error, - message: recordedRun.error, + message: recordedRun.output ?? recordedRun.error, expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [], artifactScope: exported?.artifactScope, remoteWorkingDirectory: exported?.remoteWorkingDirectory, @@ -427,6 +432,7 @@ async function upsertXWorkmateTaskRun( startedAt: existing?.startedAt ?? input.startedAt ?? input.updatedAt, updatedAt: input.updatedAt, completedAt: input.completedAt, + output: input.output, error: input.error, }) as XWorkmateRecordedTaskRunV1; runs[input.runId] = recorded; @@ -487,12 +493,21 @@ function readTaskRunsFromEntry(entry: SessionEntry | undefined | null): Record