fix(tasks): return durable assistant output
This commit is contained in:
parent
6ec2c10924
commit
d396760a4e
1
dist/index.d.ts
vendored
1
dist/index.d.ts
vendored
@ -1,3 +1,4 @@
|
|||||||
|
export declare function lastAssistantText(messages: unknown): string | undefined;
|
||||||
declare const plugin: {
|
declare const plugin: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
32
dist/index.js
vendored
32
dist/index.js
vendored
@ -33,6 +33,37 @@ function resolveRunScope(ctx) {
|
|||||||
function stringParam(value) {
|
function stringParam(value) {
|
||||||
return typeof value === "string" ? value.trim() : "";
|
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({
|
const plugin = definePluginEntry({
|
||||||
id: "openclaw-multi-session-plugins",
|
id: "openclaw-multi-session-plugins",
|
||||||
name: "openclaw-multi-session-plugins",
|
name: "openclaw-multi-session-plugins",
|
||||||
@ -77,6 +108,7 @@ function register(api) {
|
|||||||
openclawSessionKey,
|
openclawSessionKey,
|
||||||
runId,
|
runId,
|
||||||
success: event?.success === true,
|
success: event?.success === true,
|
||||||
|
output: lastAssistantText(event?.messages),
|
||||||
error: event?.error,
|
error: event?.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
2
dist/src/taskState.d.ts
vendored
2
dist/src/taskState.d.ts
vendored
@ -36,6 +36,7 @@ export type XWorkmateRecordedTaskRunV1 = {
|
|||||||
startedAt: string;
|
startedAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
completedAt?: string;
|
completedAt?: string;
|
||||||
|
output?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
export declare function registerXWorkmateSessionExtension(api: OpenClawPluginApi): void;
|
export declare function registerXWorkmateSessionExtension(api: OpenClawPluginApi): void;
|
||||||
@ -55,6 +56,7 @@ export declare function recordXWorkmateTaskRunTerminal(input: {
|
|||||||
openclawSessionKey: string;
|
openclawSessionKey: string;
|
||||||
runId: string;
|
runId: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
output?: unknown;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
}): Promise<XWorkmateRecordedTaskRunV1>;
|
}): Promise<XWorkmateRecordedTaskRunV1>;
|
||||||
export declare function getXWorkmateTaskSnapshot(input: {
|
export declare function getXWorkmateTaskSnapshot(input: {
|
||||||
|
|||||||
14
dist/src/taskState.js
vendored
14
dist/src/taskState.js
vendored
@ -51,6 +51,7 @@ export async function recordXWorkmateTaskRunTerminal(input) {
|
|||||||
success: input.success,
|
success: input.success,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
completedAt: now,
|
completedAt: now,
|
||||||
|
output: sanitizeTaskRunOutput(input.output),
|
||||||
error: sanitizeTaskRunError(input.error),
|
error: sanitizeTaskRunError(input.error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -194,8 +195,10 @@ export async function getXWorkmateTaskSnapshot(input) {
|
|||||||
completedAt: recordedRun.completedAt,
|
completedAt: recordedRun.completedAt,
|
||||||
error: recordedRun.error,
|
error: recordedRun.error,
|
||||||
},
|
},
|
||||||
|
output: recordedRun.output,
|
||||||
|
resultSummary: recordedRun.output,
|
||||||
error: recordedRun.error,
|
error: recordedRun.error,
|
||||||
message: recordedRun.error,
|
message: recordedRun.output ?? recordedRun.error,
|
||||||
expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [],
|
expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [],
|
||||||
artifactScope: exported?.artifactScope,
|
artifactScope: exported?.artifactScope,
|
||||||
remoteWorkingDirectory: exported?.remoteWorkingDirectory,
|
remoteWorkingDirectory: exported?.remoteWorkingDirectory,
|
||||||
@ -295,6 +298,7 @@ async function upsertXWorkmateTaskRun(api, input) {
|
|||||||
startedAt: existing?.startedAt ?? input.startedAt ?? input.updatedAt,
|
startedAt: existing?.startedAt ?? input.startedAt ?? input.updatedAt,
|
||||||
updatedAt: input.updatedAt,
|
updatedAt: input.updatedAt,
|
||||||
completedAt: input.completedAt,
|
completedAt: input.completedAt,
|
||||||
|
output: input.output,
|
||||||
error: input.error,
|
error: input.error,
|
||||||
});
|
});
|
||||||
runs[input.runId] = recorded;
|
runs[input.runId] = recorded;
|
||||||
@ -347,11 +351,19 @@ function readTaskRunsFromEntry(entry) {
|
|||||||
startedAt: optionalString(raw?.startedAt) || new Date(0).toISOString(),
|
startedAt: optionalString(raw?.startedAt) || new Date(0).toISOString(),
|
||||||
updatedAt: optionalString(raw?.updatedAt) || new Date(0).toISOString(),
|
updatedAt: optionalString(raw?.updatedAt) || new Date(0).toISOString(),
|
||||||
completedAt: optionalString(raw?.completedAt),
|
completedAt: optionalString(raw?.completedAt),
|
||||||
|
output: optionalString(raw?.output),
|
||||||
error: optionalString(raw?.error),
|
error: optionalString(raw?.error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function sanitizeTaskRunOutput(value) {
|
||||||
|
const raw = optionalString(value);
|
||||||
|
if (!raw) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return raw.slice(0, 16 * 1024);
|
||||||
|
}
|
||||||
function sanitizeTaskRunError(value) {
|
function sanitizeTaskRunError(value) {
|
||||||
const raw = optionalString(value);
|
const raw = optionalString(value);
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import plugin from "./index.js";
|
import plugin, { lastAssistantText } from "./index.js";
|
||||||
import { prepareXWorkmateArtifacts } from "./src/exportArtifacts.js";
|
import { prepareXWorkmateArtifacts } from "./src/exportArtifacts.js";
|
||||||
|
|
||||||
type GatewayMethodHandler = Parameters<OpenClawPluginApi["registerGatewayMethod"]>[1];
|
type GatewayMethodHandler = Parameters<OpenClawPluginApi["registerGatewayMethod"]>[1];
|
||||||
@ -14,6 +14,12 @@ type GatewayMethodResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("plugin registration", () => {
|
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", () => {
|
it("declares registered agent tools in the manifest contract", () => {
|
||||||
const manifest = JSON.parse(fs.readFileSync("openclaw.plugin.json", "utf8")) as {
|
const manifest = JSON.parse(fs.readFileSync("openclaw.plugin.json", "utf8")) as {
|
||||||
contracts?: { tools?: string[]; sessionScopedTools?: string[] };
|
contracts?: { tools?: string[]; sessionScopedTools?: string[] };
|
||||||
@ -264,7 +270,7 @@ describe("plugin registration", () => {
|
|||||||
expect(snapshot.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md" }]);
|
expect(snapshot.payload?.artifacts).toMatchObject([{ relativePath: "reports/final.md" }]);
|
||||||
|
|
||||||
await hooks.get("agent_end")?.(
|
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" },
|
{ sessionKey: "draft:1780636411666238-3", runId: "turn-1" },
|
||||||
);
|
);
|
||||||
expect(sessionExtensionPatches.at(-1)).toMatchObject({
|
expect(sessionExtensionPatches.at(-1)).toMatchObject({
|
||||||
|
|||||||
26
index.ts
26
index.ts
@ -86,6 +86,31 @@ function stringParam(value: unknown): string {
|
|||||||
return typeof value === "string" ? value.trim() : "";
|
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<string, unknown>;
|
||||||
|
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<string, unknown>;
|
||||||
|
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({
|
const plugin = definePluginEntry({
|
||||||
id: "openclaw-multi-session-plugins",
|
id: "openclaw-multi-session-plugins",
|
||||||
name: "openclaw-multi-session-plugins",
|
name: "openclaw-multi-session-plugins",
|
||||||
@ -139,6 +164,7 @@ function register(api: OpenClawPluginApi) {
|
|||||||
openclawSessionKey,
|
openclawSessionKey,
|
||||||
runId,
|
runId,
|
||||||
success: event?.success === true,
|
success: event?.success === true,
|
||||||
|
output: lastAssistantText(event?.messages),
|
||||||
error: event?.error,
|
error: event?.error,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -275,6 +275,7 @@ describe("xworkmate task state mapping", () => {
|
|||||||
openclawSessionKey: "agent:main:draft:failed-run",
|
openclawSessionKey: "agent:main:draft:failed-run",
|
||||||
runId: "turn-failed",
|
runId: "turn-failed",
|
||||||
success: false,
|
success: false,
|
||||||
|
output: "任务执行失败前的说明",
|
||||||
error: "401 Authentication Fails, api_key=sk-secret-value",
|
error: "401 Authentication Fails, api_key=sk-secret-value",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -292,6 +293,9 @@ describe("xworkmate task state mapping", () => {
|
|||||||
taskStatus: "failed",
|
taskStatus: "failed",
|
||||||
terminal: true,
|
terminal: true,
|
||||||
terminalSource: "agent_end",
|
terminalSource: "agent_end",
|
||||||
|
output: "任务执行失败前的说明",
|
||||||
|
resultSummary: "任务执行失败前的说明",
|
||||||
|
message: "任务执行失败前的说明",
|
||||||
task: {
|
task: {
|
||||||
runId: "turn-failed",
|
runId: "turn-failed",
|
||||||
status: "failed",
|
status: "failed",
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export type XWorkmateRecordedTaskRunV1 = {
|
|||||||
startedAt: string;
|
startedAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
completedAt?: string;
|
completedAt?: string;
|
||||||
|
output?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -136,6 +137,7 @@ export async function recordXWorkmateTaskRunTerminal(input: {
|
|||||||
openclawSessionKey: string;
|
openclawSessionKey: string;
|
||||||
runId: string;
|
runId: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
output?: unknown;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
}): Promise<XWorkmateRecordedTaskRunV1> {
|
}): Promise<XWorkmateRecordedTaskRunV1> {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
@ -146,6 +148,7 @@ export async function recordXWorkmateTaskRunTerminal(input: {
|
|||||||
success: input.success,
|
success: input.success,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
completedAt: now,
|
completedAt: now,
|
||||||
|
output: sanitizeTaskRunOutput(input.output),
|
||||||
error: sanitizeTaskRunError(input.error),
|
error: sanitizeTaskRunError(input.error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -311,8 +314,10 @@ export async function getXWorkmateTaskSnapshot(input: {
|
|||||||
completedAt: recordedRun.completedAt,
|
completedAt: recordedRun.completedAt,
|
||||||
error: recordedRun.error,
|
error: recordedRun.error,
|
||||||
},
|
},
|
||||||
|
output: recordedRun.output,
|
||||||
|
resultSummary: recordedRun.output,
|
||||||
error: recordedRun.error,
|
error: recordedRun.error,
|
||||||
message: recordedRun.error,
|
message: recordedRun.output ?? recordedRun.error,
|
||||||
expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [],
|
expectedArtifactDirs: mapping?.expectedArtifactDirs ?? [],
|
||||||
artifactScope: exported?.artifactScope,
|
artifactScope: exported?.artifactScope,
|
||||||
remoteWorkingDirectory: exported?.remoteWorkingDirectory,
|
remoteWorkingDirectory: exported?.remoteWorkingDirectory,
|
||||||
@ -427,6 +432,7 @@ async function upsertXWorkmateTaskRun(
|
|||||||
startedAt: existing?.startedAt ?? input.startedAt ?? input.updatedAt,
|
startedAt: existing?.startedAt ?? input.startedAt ?? input.updatedAt,
|
||||||
updatedAt: input.updatedAt,
|
updatedAt: input.updatedAt,
|
||||||
completedAt: input.completedAt,
|
completedAt: input.completedAt,
|
||||||
|
output: input.output,
|
||||||
error: input.error,
|
error: input.error,
|
||||||
}) as XWorkmateRecordedTaskRunV1;
|
}) as XWorkmateRecordedTaskRunV1;
|
||||||
runs[input.runId] = recorded;
|
runs[input.runId] = recorded;
|
||||||
@ -487,12 +493,21 @@ function readTaskRunsFromEntry(entry: SessionEntry | undefined | null): Record<s
|
|||||||
startedAt: optionalString(raw?.startedAt) || new Date(0).toISOString(),
|
startedAt: optionalString(raw?.startedAt) || new Date(0).toISOString(),
|
||||||
updatedAt: optionalString(raw?.updatedAt) || new Date(0).toISOString(),
|
updatedAt: optionalString(raw?.updatedAt) || new Date(0).toISOString(),
|
||||||
completedAt: optionalString(raw?.completedAt),
|
completedAt: optionalString(raw?.completedAt),
|
||||||
|
output: optionalString(raw?.output),
|
||||||
error: optionalString(raw?.error),
|
error: optionalString(raw?.error),
|
||||||
}) as XWorkmateRecordedTaskRunV1;
|
}) as XWorkmateRecordedTaskRunV1;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeTaskRunOutput(value: unknown): string | undefined {
|
||||||
|
const raw = optionalString(value);
|
||||||
|
if (!raw) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return raw.slice(0, 16 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizeTaskRunError(value: unknown): string | undefined {
|
function sanitizeTaskRunError(value: unknown): string | undefined {
|
||||||
const raw = optionalString(value);
|
const raw = optionalString(value);
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user