fix(swads): make daily report generation resilient

This commit is contained in:
Jeffrey Wu
2026-09-09 17:59:05 +08:00
parent f4e3f41c2c
commit 8361b10c66
13 changed files with 278 additions and 38 deletions
+8 -5
View File
@@ -102,11 +102,9 @@ export class HarnessClientStore {
} else if (event.type === "assistant.completed") {
messages = updateAssistant(messages, event.payload.messageId, event.timestamp, (parts) => parts, { type: "complete", reason: "stop" }, event.runId);
} else if (event.type === "report.generated") {
const { artifact, assistantMessageId } = event.payload;
if (artifact.previewUrl) {
messages = updateAssistant(messages, assistantMessageId, event.timestamp, (parts) => parts, { type: "running" }, event.runId);
void this.loadReportImage(event);
}
const { artifact, assistantMessageId, markdown } = event.payload;
messages = updateAssistant(messages, assistantMessageId, event.timestamp, (parts) => markdown ? insertReportMarkdown(parts, markdown) : parts, { type: "running" }, event.runId);
if (artifact.previewUrl) void this.loadReportImage(event);
} else if (event.type === "tool.requested") {
const id = assistantIdForRun(messages, event.runId);
messages = updateAssistant(messages, id, event.timestamp, (parts) => upsertToolCall(parts, event.payload.toolCallId, event.payload.toolName, asArgs(event.payload.arguments)), { type: "running" }, event.runId);
@@ -243,6 +241,11 @@ function updateAssistant(messages: ThreadMessageLike[], id: string, timestamp: s
return messages.map((item, current) => current === index ? { ...item, id, content: change(typeof item.content === "string" ? [{ type: "text", text: item.content }] : item.content), status } : item);
}
function appendText(parts: ContentParts, delta: string): ContentParts { const last = parts.at(-1); return last?.type === "text" ? [...parts.slice(0, -1), { ...last, text: last.text + delta }] : [...parts, { type: "text", text: delta }]; }
function insertReportMarkdown(parts: ContentParts, markdown: string): ContentParts {
if (parts.some((part) => part.type === "text" && part.text.trim())) return parts;
const renderIndex = parts.findIndex((part) => part.type === "tool-call" && part.toolName === "render_swads_daily_report");
return renderIndex < 0 ? [...parts, { type: "text", text: markdown }] : [...parts.slice(0, renderIndex), { type: "text", text: markdown }, ...parts.slice(renderIndex)];
}
function upsertToolCall(parts: ContentParts, toolCallId: string, toolName: string, args: Record<string, string | number | boolean | null>): ContentParts {
const index = parts.findIndex((part) => part.type === "tool-call" && part.toolCallId === toolCallId);
if (index < 0) return [...parts, { type: "tool-call", toolCallId, toolName, args }];
+8
View File
@@ -48,6 +48,14 @@ describe("HarnessClientStore reducer", () => {
});
describe("generated report events", () => {
it("adds a generated Markdown fallback when the model returned no visible report text", () => {
const store = new HarnessClientStore(); const base = { sessionId: "ses_report", runId: "run_report", timestamp: new Date().toISOString() };
const accountKey = "acct_0123456789abcdef01234567", reportDate = "2026-09-06", reportId = "12345678-1234-4234-8234-123456789abc";
const url = `/api/reports/${accountKey}/${reportDate}/${reportId}`; const artifact = { accountKey, reportDate, reportId, downloads: { json: `${url}/json`, html: `${url}/html` } };
store.reduce({ ...base, eventId: "fallback_1", sequence: 1, type: "tool.requested", payload: { toolCallId: "render", toolName: "render_swads_daily_report", arguments: {} } });
store.reduce({ ...base, eventId: "fallback_2", sequence: 2, type: "report.generated", payload: { assistantMessageId: "assistant", artifact, markdown: "## 自动日报摘要" } });
const content = store.getSnapshot().messages[0]?.content; expect(content).toEqual(expect.arrayContaining([{ type: "text", text: "## 自动日报摘要" }]));
});
it("deduplicates images and merges provisional tool messages with the correct assistant", async () => {
vi.stubGlobal("fetch", vi.fn(async () => new Response(new Blob(["png"], { type: "image/png" }), { headers: { "content-type": "image/png" } })));
Object.defineProperty(URL, "createObjectURL", { configurable: true, value: vi.fn(() => "blob:report-image") });