






















@@ -16,6 +16,7 @@ type TranscriptLine = {
1616const sessionEntryState = vi.hoisted(() => ({
1717transcriptPath: "",
1818sessionId: "",
19+hasEntry: true,
1920}));
20212122vi.mock("../session-utils.js", async () => {
@@ -26,10 +27,12 @@ vi.mock("../session-utils.js", async () => {
2627loadSessionEntry: () => ({
2728cfg: {},
2829storePath: path.join(path.dirname(sessionEntryState.transcriptPath), "sessions.json"),
29-entry: {
30-sessionId: sessionEntryState.sessionId,
31-sessionFile: sessionEntryState.transcriptPath,
32-},
30+entry: sessionEntryState.hasEntry
31+ ? {
32+sessionId: sessionEntryState.sessionId,
33+sessionFile: sessionEntryState.transcriptPath,
34+}
35+ : undefined,
3336canonicalKey: "main",
3437}),
3538};
@@ -142,9 +145,10 @@ function expectPersistedAbortMessage(
142145expect(abort.runId).toBe(expected.runId);
143146}
144147145-function setMockSessionEntry(transcriptPath: string, sessionId: string) {
148+function setMockSessionEntry(transcriptPath: string, sessionId: string, hasEntry = true) {
146149sessionEntryState.transcriptPath = transcriptPath;
147150sessionEntryState.sessionId = sessionId;
151+sessionEntryState.hasEntry = hasEntry;
148152}
149153150154async function createTranscriptFixture(prefix: string) {
@@ -156,6 +160,14 @@ async function createTranscriptFixture(prefix: string) {
156160return { transcriptPath, sessionId };
157161}
158162163+async function createMissingEntryFixture(prefix: string) {
164+const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
165+const transcriptPath = path.join(dir, "missing.jsonl");
166+const sessionId = "client-supplied-session";
167+setMockSessionEntry(transcriptPath, sessionId, false);
168+return { sessionId };
169+}
170+159171afterEach(() => {
160172vi.restoreAllMocks();
161173});
@@ -334,6 +346,73 @@ describe("chat abort transcript persistence", () => {
334346expect(context.chatAbortControllers.has("run-stop-canonical")).toBe(false);
335347});
336348349+it("plain stop aborts raw-alias runs for the same backing session", async () => {
350+const { sessionId } = await createTranscriptFixture("openclaw-chat-stop-raw-alias-");
351+const respond = vi.fn();
352+const active = createActiveRun("alias-main", { sessionId });
353+const context = createChatAbortContext({
354+chatAbortControllers: new Map([["run-stop-raw-alias", active]]),
355+removeChatRun: vi.fn().mockReturnValue({
356+sessionKey: "alias-main",
357+clientRunId: "run-stop-raw-alias",
358+}),
359+dedupe: {
360+get: vi.fn(),
361+},
362+});
363+364+await chatHandlers["chat.send"]({
365+params: {
366+sessionKey: "main",
367+message: "stop",
368+idempotencyKey: "idem-stop-raw-alias",
369+},
370+ respond,
371+context: context as never,
372+req: {} as never,
373+client: null,
374+isWebchatConnect: () => false,
375+});
376+377+const [ok, payload] = requireLastRespondCall(respond);
378+expect(ok).toBe(true);
379+expectAbortPayload(payload, { runIds: ["run-stop-raw-alias"] });
380+expect(active.controller.signal.aborted).toBe(true);
381+expect(context.chatAbortControllers.has("run-stop-raw-alias")).toBe(false);
382+});
383+384+it("does not match stop targets by client-supplied session id without a stored entry", async () => {
385+const { sessionId } = await createMissingEntryFixture("openclaw-chat-stop-client-session-");
386+const respond = vi.fn();
387+const active = createActiveRun("third-session", { sessionId });
388+const context = createChatAbortContext({
389+chatAbortControllers: new Map([["run-stop-client-session", active]]),
390+dedupe: {
391+get: vi.fn(),
392+},
393+});
394+395+await chatHandlers["chat.send"]({
396+params: {
397+sessionKey: "other-session",
398+ sessionId,
399+message: "stop",
400+idempotencyKey: "idem-stop-client-session",
401+},
402+ respond,
403+context: context as never,
404+req: {} as never,
405+client: null,
406+isWebchatConnect: () => false,
407+});
408+409+const [ok, payload] = requireLastRespondCall(respond);
410+expect(ok).toBe(true);
411+expect(expectRecord(payload, "abort payload").aborted).toBe(false);
412+expect(active.controller.signal.aborted).toBe(false);
413+expect(context.chatAbortControllers.has("run-stop-client-session")).toBe(true);
414+});
415+337416it("skips run-scoped transcript persistence when partial text is blank", async () => {
338417const { transcriptPath, sessionId } = await createTranscriptFixture(
339418"openclaw-chat-abort-run-blank-",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。