fix: hard-stop sessions.delete cleanup

This commit is contained in:
Peter Steinberger
2026-01-16 22:24:04 +00:00
parent 7df37c2dbd
commit 3567dc4a47
3 changed files with 49 additions and 6 deletions

View File

@@ -58,6 +58,7 @@
### Fixes
- Messages: make `/stop` clear queued followups and pending session lane work for a hard abort.
- Messages: make `/stop` abort active sub-agent runs spawned from the requester session and report how many were stopped.
- Sessions: ensure `sessions.delete` clears queues, aborts embedded runs, and stops sub-agents before deletion.
- WhatsApp: default response prefix only for self-chat, using identity name when set.
- Signal/iMessage: bound transport readiness waits to 30s with periodic logging. (#1014) — thanks @Szpadel.
- Auth: merge main auth profiles into per-agent stores for sub-agents and document inheritance. (#1013) — thanks @marcmarg.

View File

@@ -3,17 +3,16 @@ import fs from "node:fs";
import {
abortEmbeddedPiRun,
isEmbeddedPiRunActive,
resolveEmbeddedSessionLane,
waitForEmbeddedPiRunEnd,
} from "../../agents/pi-embedded.js";
import { stopSubagentsForRequester } from "../../auto-reply/reply/abort.js";
import { clearSessionQueues } from "../../auto-reply/reply/queue.js";
import { loadConfig } from "../../config/config.js";
import {
resolveMainSessionKey,
type SessionEntry,
updateSessionStore,
} from "../../config/sessions.js";
import { clearCommandLane } from "../../process/command-queue.js";
import {
ErrorCodes,
errorShape,
@@ -223,8 +222,12 @@ export const sessionsHandlers: GatewayRequestHandlers = {
const { entry } = loadSessionEntry(key);
const sessionId = entry?.sessionId;
const existed = Boolean(entry);
clearCommandLane(resolveEmbeddedSessionLane(target.canonicalKey));
if (sessionId && isEmbeddedPiRunActive(sessionId)) {
const queueKeys = new Set<string>(target.storeKeys);
queueKeys.add(target.canonicalKey);
if (sessionId) queueKeys.add(sessionId);
clearSessionQueues([...queueKeys]);
stopSubagentsForRequester({ cfg, requesterSessionKey: target.canonicalKey });
if (sessionId) {
abortEmbeddedPiRun(sessionId);
const ended = await waitForEmbeddedPiRunEnd(sessionId, 15_000);
if (!ended) {

View File

@@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, test } from "vitest";
import { beforeEach, describe, expect, test, vi } from "vitest";
import {
connectOk,
embeddedRunMock,
@@ -13,9 +13,39 @@ import {
} from "./test-helpers.js";
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
const sessionCleanupMocks = vi.hoisted(() => ({
clearSessionQueues: vi.fn(() => ({ followupCleared: 0, laneCleared: 0, keys: [] })),
stopSubagentsForRequester: vi.fn(() => ({ stopped: 0 })),
}));
vi.mock("../auto-reply/reply/queue.js", async () => {
const actual = await vi.importActual<typeof import("../auto-reply/reply/queue.js")>(
"../auto-reply/reply/queue.js",
);
return {
...actual,
clearSessionQueues: sessionCleanupMocks.clearSessionQueues,
};
});
vi.mock("../auto-reply/reply/abort.js", async () => {
const actual = await vi.importActual<typeof import("../auto-reply/reply/abort.js")>(
"../auto-reply/reply/abort.js",
);
return {
...actual,
stopSubagentsForRequester: sessionCleanupMocks.stopSubagentsForRequester,
};
});
installGatewayTestHooks();
describe("gateway server sessions", () => {
beforeEach(() => {
sessionCleanupMocks.clearSessionQueues.mockClear();
sessionCleanupMocks.stopSubagentsForRequester.mockClear();
});
test("lists and patches session store via sessions.* RPC", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-sessions-"));
const storePath = path.join(dir, "sessions.json");
@@ -349,6 +379,15 @@ describe("gateway server sessions", () => {
});
expect(deleted.ok).toBe(true);
expect(deleted.payload?.deleted).toBe(true);
expect(sessionCleanupMocks.stopSubagentsForRequester).toHaveBeenCalledWith({
cfg: expect.any(Object),
requesterSessionKey: "agent:main:discord:group:dev",
});
expect(sessionCleanupMocks.clearSessionQueues).toHaveBeenCalledTimes(1);
const clearedKeys = sessionCleanupMocks.clearSessionQueues.mock.calls[0]?.[0] as string[];
expect(clearedKeys).toEqual(
expect.arrayContaining(["discord:group:dev", "agent:main:discord:group:dev", "sess-active"]),
);
expect(embeddedRunMock.abortCalls).toEqual(["sess-active"]);
expect(embeddedRunMock.waitCalls).toEqual(["sess-active"]);