test: dedupe auto-reply web and signal flows

This commit is contained in:
Peter Steinberger
2026-02-22 17:11:26 +00:00
parent ad1072842e
commit 24ea941e28
12 changed files with 399 additions and 391 deletions

View File

@@ -42,6 +42,39 @@ vi.mock("../../agents/subagent-registry.js", () => ({
}));
describe("abort detection", () => {
async function writeSessionStore(
storePath: string,
sessionIdsByKey: Record<string, string>,
nowMs = Date.now(),
) {
const storeEntries = Object.fromEntries(
Object.entries(sessionIdsByKey).map(([key, sessionId]) => [
key,
{ sessionId, updatedAt: nowMs },
]),
);
await fs.writeFile(storePath, JSON.stringify(storeEntries, null, 2));
}
async function createAbortConfig(params?: {
commandsTextEnabled?: boolean;
sessionIdsByKey?: Record<string, string>;
nowMs?: number;
}) {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
const storePath = path.join(root, "sessions.json");
const cfg = {
session: { store: storePath },
...(typeof params?.commandsTextEnabled === "boolean"
? { commands: { text: params.commandsTextEnabled } }
: {}),
} as OpenClawConfig;
if (params?.sessionIdsByKey) {
await writeSessionStore(storePath, params.sessionIdsByKey, params.nowMs);
}
return { root, storePath, cfg };
}
async function runStopCommand(params: {
cfg: OpenClawConfig;
sessionKey: string;
@@ -142,9 +175,7 @@ describe("abort detection", () => {
});
it("fast-aborts even when text commands are disabled", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath }, commands: { text: false } } as OpenClawConfig;
const { cfg } = await createAbortConfig({ commandsTextEnabled: false });
const result = await runStopCommand({
cfg,
@@ -157,24 +188,11 @@ describe("abort detection", () => {
});
it("fast-abort clears queued followups and session lane", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath } } as OpenClawConfig;
const sessionKey = "telegram:123";
const sessionId = "session-123";
await fs.writeFile(
storePath,
JSON.stringify(
{
[sessionKey]: {
sessionId,
updatedAt: Date.now(),
},
},
null,
2,
),
);
const { root, cfg } = await createAbortConfig({
sessionIdsByKey: { [sessionKey]: sessionId },
});
const followupRun: FollowupRun = {
prompt: "queued",
enqueuedAt: Date.now(),
@@ -215,30 +233,16 @@ describe("abort detection", () => {
});
it("fast-abort stops active subagent runs for requester session", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath } } as OpenClawConfig;
const sessionKey = "telegram:parent";
const childKey = "agent:main:subagent:child-1";
const sessionId = "session-parent";
const childSessionId = "session-child";
await fs.writeFile(
storePath,
JSON.stringify(
{
[sessionKey]: {
sessionId,
updatedAt: Date.now(),
},
[childKey]: {
sessionId: childSessionId,
updatedAt: Date.now(),
},
},
null,
2,
),
);
const { cfg } = await createAbortConfig({
sessionIdsByKey: {
[sessionKey]: sessionId,
[childKey]: childSessionId,
},
});
subagentRegistryMocks.listSubagentRunsForRequester.mockReturnValueOnce([
{
@@ -264,36 +268,19 @@ describe("abort detection", () => {
});
it("cascade stop kills depth-2 children when stopping depth-1 agent", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath } } as OpenClawConfig;
const sessionKey = "telegram:parent";
const depth1Key = "agent:main:subagent:child-1";
const depth2Key = "agent:main:subagent:child-1:subagent:grandchild-1";
const sessionId = "session-parent";
const depth1SessionId = "session-child";
const depth2SessionId = "session-grandchild";
await fs.writeFile(
storePath,
JSON.stringify(
{
[sessionKey]: {
sessionId,
updatedAt: Date.now(),
},
[depth1Key]: {
sessionId: depth1SessionId,
updatedAt: Date.now(),
},
[depth2Key]: {
sessionId: depth2SessionId,
updatedAt: Date.now(),
},
},
null,
2,
),
);
const { cfg } = await createAbortConfig({
sessionIdsByKey: {
[sessionKey]: sessionId,
[depth1Key]: depth1SessionId,
[depth2Key]: depth2SessionId,
},
});
// First call: main session lists depth-1 children
// Second call (cascade): depth-1 session lists depth-2 children
@@ -339,34 +326,18 @@ describe("abort detection", () => {
it("cascade stop traverses ended depth-1 parents to stop active depth-2 children", async () => {
subagentRegistryMocks.listSubagentRunsForRequester.mockClear();
subagentRegistryMocks.markSubagentRunTerminated.mockClear();
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath } } as OpenClawConfig;
const sessionKey = "telegram:parent";
const depth1Key = "agent:main:subagent:child-ended";
const depth2Key = "agent:main:subagent:child-ended:subagent:grandchild-active";
const now = Date.now();
await fs.writeFile(
storePath,
JSON.stringify(
{
[sessionKey]: {
sessionId: "session-parent",
updatedAt: now,
},
[depth1Key]: {
sessionId: "session-child-ended",
updatedAt: now,
},
[depth2Key]: {
sessionId: "session-grandchild-active",
updatedAt: now,
},
},
null,
2,
),
);
const { cfg } = await createAbortConfig({
nowMs: now,
sessionIdsByKey: {
[sessionKey]: "session-parent",
[depth1Key]: "session-child-ended",
[depth2Key]: "session-grandchild-active",
},
});
// main -> ended depth-1 parent
// depth-1 parent -> active depth-2 child