fix: hard-stop sessions.delete cleanup
This commit is contained in:
@@ -58,6 +58,7 @@
|
|||||||
### Fixes
|
### Fixes
|
||||||
- Messages: make `/stop` clear queued followups and pending session lane work for a hard abort.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Auth: merge main auth profiles into per-agent stores for sub-agents and document inheritance. (#1013) — thanks @marcmarg.
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ import fs from "node:fs";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
abortEmbeddedPiRun,
|
abortEmbeddedPiRun,
|
||||||
isEmbeddedPiRunActive,
|
|
||||||
resolveEmbeddedSessionLane,
|
|
||||||
waitForEmbeddedPiRunEnd,
|
waitForEmbeddedPiRunEnd,
|
||||||
} from "../../agents/pi-embedded.js";
|
} 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 { loadConfig } from "../../config/config.js";
|
||||||
import {
|
import {
|
||||||
resolveMainSessionKey,
|
resolveMainSessionKey,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
updateSessionStore,
|
updateSessionStore,
|
||||||
} from "../../config/sessions.js";
|
} from "../../config/sessions.js";
|
||||||
import { clearCommandLane } from "../../process/command-queue.js";
|
|
||||||
import {
|
import {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
errorShape,
|
errorShape,
|
||||||
@@ -223,8 +222,12 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
const { entry } = loadSessionEntry(key);
|
const { entry } = loadSessionEntry(key);
|
||||||
const sessionId = entry?.sessionId;
|
const sessionId = entry?.sessionId;
|
||||||
const existed = Boolean(entry);
|
const existed = Boolean(entry);
|
||||||
clearCommandLane(resolveEmbeddedSessionLane(target.canonicalKey));
|
const queueKeys = new Set<string>(target.storeKeys);
|
||||||
if (sessionId && isEmbeddedPiRunActive(sessionId)) {
|
queueKeys.add(target.canonicalKey);
|
||||||
|
if (sessionId) queueKeys.add(sessionId);
|
||||||
|
clearSessionQueues([...queueKeys]);
|
||||||
|
stopSubagentsForRequester({ cfg, requesterSessionKey: target.canonicalKey });
|
||||||
|
if (sessionId) {
|
||||||
abortEmbeddedPiRun(sessionId);
|
abortEmbeddedPiRun(sessionId);
|
||||||
const ended = await waitForEmbeddedPiRunEnd(sessionId, 15_000);
|
const ended = await waitForEmbeddedPiRunEnd(sessionId, 15_000);
|
||||||
if (!ended) {
|
if (!ended) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, test } from "vitest";
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
connectOk,
|
connectOk,
|
||||||
embeddedRunMock,
|
embeddedRunMock,
|
||||||
@@ -13,9 +13,39 @@ import {
|
|||||||
} from "./test-helpers.js";
|
} from "./test-helpers.js";
|
||||||
import { DEFAULT_PROVIDER } from "../agents/defaults.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();
|
installGatewayTestHooks();
|
||||||
|
|
||||||
describe("gateway server sessions", () => {
|
describe("gateway server sessions", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sessionCleanupMocks.clearSessionQueues.mockClear();
|
||||||
|
sessionCleanupMocks.stopSubagentsForRequester.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
test("lists and patches session store via sessions.* RPC", async () => {
|
test("lists and patches session store via sessions.* RPC", async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-sessions-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-sessions-"));
|
||||||
const storePath = path.join(dir, "sessions.json");
|
const storePath = path.join(dir, "sessions.json");
|
||||||
@@ -349,6 +379,15 @@ describe("gateway server sessions", () => {
|
|||||||
});
|
});
|
||||||
expect(deleted.ok).toBe(true);
|
expect(deleted.ok).toBe(true);
|
||||||
expect(deleted.payload?.deleted).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.abortCalls).toEqual(["sess-active"]);
|
||||||
expect(embeddedRunMock.waitCalls).toEqual(["sess-active"]);
|
expect(embeddedRunMock.waitCalls).toEqual(["sess-active"]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user