mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(copilot): keep tool transcript groups structurally complete (#114403)
* refactor(copilot): make SQLite own transcripts * fix(copilot): harden transcript event projection * test(copilot): tighten transcript journal types * fix(copilot): retain unmatched usage metadata * refactor(copilot): commit tool groups atomically * fix(copilot): break journal bridge import cycle * fix(copilot): preserve grouped transcript replay * test(copilot): cover opaque replay state * fix(copilot): invalidate lossy transcript replay * fix(copilot): guard transcript rewrite topology * fix(copilot): drain transcript finalization * fix(copilot): preserve resumed session history * fix(copilot): dedupe replayed transcript snapshots * fix(copilot): isolate transcript write hooks * fix(copilot): track staged transcript identities * fix(copilot): isolate ephemeral transcript events * fix(copilot): coalesce assistant snapshots * fix(copilot): invalidate unprojected SDK context * fix(copilot): guard resumed transcript replay * fix(plugin-sdk): expose strict transcript append * fix(copilot): persist journal provenance safely * fix(copilot): separate live and durable deltas * fix(copilot): require durable projection ownership * fix(copilot): preserve strict append typing * fix(copilot): gate deferred session deletion * fix(copilot): preserve transcript replay identity * fix(copilot): retain cited replay history * fix(copilot): require complete replay provenance * fix(copilot): reject ambiguous assistant snapshots * fix(copilot): return latest assistant snapshot * chore(copilot): align transcript lint gates * test(lint): cover Copilot line budgets * test(ci): dedupe Codex prewarm shard
This commit is contained in:
committed by
GitHub
parent
bbc73c36bd
commit
3dc3370d9e
@@ -13,6 +13,8 @@ import * as transcriptEvents from "../sessions/transcript-events.js";
|
||||
import {
|
||||
appendAssistantMirrorMessageByIdentity,
|
||||
appendSessionTranscriptMessageByIdentity,
|
||||
appendSessionTranscriptMessageByIdentityStrict,
|
||||
appendSessionTranscriptMessagesByIdentity,
|
||||
formatSessionTranscriptMemoryHitKey,
|
||||
parseSessionTranscriptMemoryHitKey,
|
||||
publishSessionTranscriptUpdateByIdentity,
|
||||
@@ -81,6 +83,74 @@ describe("session transcript runtime SDK", () => {
|
||||
expect(loadSessionEntry(scope)?.sessionFile).toBeUndefined();
|
||||
});
|
||||
|
||||
it("atomically appends and idempotently replays an ordered message group", async () => {
|
||||
const scope = {
|
||||
agentId: "main",
|
||||
sessionId: "batch-session",
|
||||
sessionKey: "agent:main:batch",
|
||||
storePath,
|
||||
};
|
||||
await upsertSessionEntry(scope, { sessionId: scope.sessionId, updatedAt: 10 });
|
||||
const messages = [
|
||||
{
|
||||
eventId: "batch-assistant",
|
||||
idempotencyLookup: "scan" as const,
|
||||
message: { role: "assistant", content: "checking", idempotencyKey: "batch:assistant" },
|
||||
now: 1_000,
|
||||
},
|
||||
{
|
||||
eventId: "batch-result",
|
||||
idempotencyLookup: "scan" as const,
|
||||
message: { role: "toolResult", content: "done", idempotencyKey: "batch:result" },
|
||||
now: 2_000,
|
||||
},
|
||||
];
|
||||
|
||||
const appended = await appendSessionTranscriptMessagesByIdentity({ ...scope, messages });
|
||||
const replayed = await appendSessionTranscriptMessagesByIdentity({ ...scope, messages });
|
||||
|
||||
expect(appended.map((result) => result.appended)).toEqual([true, true]);
|
||||
expect(replayed.map((result) => result.appended)).toEqual([false, false]);
|
||||
const events = await readSessionTranscriptEvents(scope);
|
||||
expect(events).toHaveLength(3);
|
||||
expect(events.slice(1)).toMatchObject([
|
||||
{ id: "batch-assistant", parentId: null },
|
||||
{ id: "batch-result", parentId: "batch-assistant" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("distinguishes strict singleton results, suppression, and session rebound", async () => {
|
||||
const scope = {
|
||||
agentId: "main",
|
||||
sessionId: "strict-session",
|
||||
sessionKey: "agent:main:strict",
|
||||
storePath,
|
||||
};
|
||||
await upsertSessionEntry(scope, { sessionId: scope.sessionId, updatedAt: 10 });
|
||||
|
||||
await expect(
|
||||
appendSessionTranscriptMessageByIdentityStrict({
|
||||
...scope,
|
||||
message: { role: "user", content: "blocked" },
|
||||
prepareMessageAfterIdempotencyCheck: () => undefined,
|
||||
}),
|
||||
).resolves.toEqual({ kind: "suppressed" });
|
||||
await expect(
|
||||
appendSessionTranscriptMessageByIdentityStrict({
|
||||
...scope,
|
||||
message: { role: "user", content: "persisted", idempotencyKey: "strict:user" },
|
||||
}),
|
||||
).resolves.toMatchObject({ kind: "result", result: { appended: true } });
|
||||
|
||||
await upsertSessionEntry(scope, { sessionId: "replacement-session", updatedAt: 20 });
|
||||
await expect(
|
||||
appendSessionTranscriptMessageByIdentityStrict({
|
||||
...scope,
|
||||
message: { role: "assistant", content: "stale" },
|
||||
}),
|
||||
).resolves.toEqual({ kind: "rejected", reason: "session-rebound" });
|
||||
});
|
||||
|
||||
it("pages raw events across appends and resets after replacement", async () => {
|
||||
const scope = {
|
||||
agentId: "main",
|
||||
|
||||
Reference in New Issue
Block a user