fix: preserve chat send user turns on started failures

This commit is contained in:
Shakker
2026-05-25 18:23:34 +01:00
committed by Shakker
parent ce5adbd2c2
commit 1c35ec6cd7
3 changed files with 42 additions and 6 deletions
+6 -6
View File
@@ -10,6 +10,7 @@ import {
} from "../auto-reply/reply/reply-run-registry.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
import { buildPersistedUserTurnMessage } from "../sessions/user-turn-transcript.js";
import { runPreparedCliAgent } from "./cli-runner.js";
import {
createManagedRun,
@@ -900,12 +901,11 @@ describe("runCliAgent reliability", () => {
workspaceDir: dir,
prompt: "runtime image prompt",
userTurnTranscript: {
message: {
role: "user",
content: "describe this",
MediaPath: "/tmp/image.png",
MediaType: "image/png",
},
message: buildPersistedUserTurnMessage({
text: "describe this",
media: [{ path: "/tmp/image.png", contentType: "image/png" }],
timestamp: 123,
}),
},
onUserMessagePersisted,
},
@@ -4422,6 +4422,34 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
expect(persistedUser?.content).toBe("hello before cli startup failure");
});
});
it("emits a user transcript update when a started agent returns an error before runtime persistence", async () => {
createTranscriptFixture("openclaw-chat-send-user-transcript-agent-error-no-runtime-persist-");
mockState.triggerAgentRunStart = true;
mockState.finalPayload = { text: "agent failed before prompt append", isError: true };
const respond = vi.fn();
const context = createChatContext();
await runNonStreamingChatSend({
context,
respond,
idempotencyKey: "idem-user-transcript-agent-error-no-runtime-persist",
message: "hello before agent error payload",
expectBroadcast: false,
});
await waitForAssertion(() => {
expect(
context.dedupe.get("chat:idem-user-transcript-agent-error-no-runtime-persist")?.ok,
).toBe(false);
const userUpdate = findUserUpdate();
const message = userUpdateMessage(userUpdate);
expect(userUpdate?.sessionFile.endsWith("sess.jsonl")).toBe(true);
expect(userUpdate?.sessionKey).toBe("main");
expect(message?.role).toBe("user");
expect(message?.content).toBe("hello before agent error payload");
});
});
});
describe("chat.send operator UI client sender context", () => {
+8
View File
@@ -2910,6 +2910,14 @@ export const chatHandlers: GatewayRequestHandlers = {
.map((payload) => payload.text?.trim())
.filter((text): text is string => Boolean(text))
.join(" | ") || undefined;
if (
agentRunStarted &&
returnedAgentErrorPayloads.length > 0 &&
!agentUserMessagePersisted &&
!beforeAgentRunHooksRegistered
) {
await persistGatewayUserTurnTranscript();
}
let broadcastedSourceReplyFinal = false;
// WebChat persistence has two owners. Agent runs persist model-visible turns
// through Pi's SessionManager; this dispatcher only owns live delivery payloads.