import { afterEach, describe, expect, it, vi } from "vitest"; import { getTaskFlowById, resetTaskFlowRegistryForTests } from "../../tasks/task-flow-registry.js"; import { getTaskById, resetTaskRegistryForTests } from "../../tasks/task-registry.js"; import { createRuntimeTaskFlow } from "./runtime-taskflow.js"; const hoisted = vi.hoisted(() => { const sendMessageMock = vi.fn(); const cancelSessionMock = vi.fn(); const killSubagentRunAdminMock = vi.fn(); return { sendMessageMock, cancelSessionMock, killSubagentRunAdminMock, }; }); vi.mock("../../tasks/task-registry-delivery-runtime.js", () => ({ sendMessage: hoisted.sendMessageMock, })); vi.mock("../../acp/control-plane/manager.js", () => ({ getAcpSessionManager: () => ({ cancelSession: hoisted.cancelSessionMock, }), })); vi.mock("../../agents/subagent-control.js", () => ({ killSubagentRunAdmin: (params: unknown) => hoisted.killSubagentRunAdminMock(params), })); afterEach(() => { resetTaskRegistryForTests(); resetTaskFlowRegistryForTests({ persist: false }); vi.clearAllMocks(); }); describe("runtime TaskFlow", () => { it("binds managed TaskFlow operations to a session key", () => { const runtime = createRuntimeTaskFlow(); const taskFlow = runtime.bindSession({ sessionKey: "agent:main:main", requesterOrigin: { channel: "telegram", to: "telegram:123", }, }); const created = taskFlow.createManaged({ controllerId: "tests/runtime-taskflow", goal: "Triage inbox", currentStep: "classify", stateJson: { lane: "inbox" }, }); expect(created).toMatchObject({ syncMode: "managed", ownerKey: "agent:main:main", controllerId: "tests/runtime-taskflow", requesterOrigin: { channel: "telegram", to: "telegram:123", }, goal: "Triage inbox", }); expect(taskFlow.get(created.flowId)?.flowId).toBe(created.flowId); expect(taskFlow.findLatest()?.flowId).toBe(created.flowId); expect(taskFlow.resolve("agent:main:main")?.flowId).toBe(created.flowId); }); it("binds TaskFlows from trusted tool context", () => { const runtime = createRuntimeTaskFlow(); const taskFlow = runtime.fromToolContext({ sessionKey: "agent:main:main", deliveryContext: { channel: "discord", to: "channel:123", threadId: "thread:456", }, }); const created = taskFlow.createManaged({ controllerId: "tests/runtime-taskflow", goal: "Review queue", }); expect(created.requesterOrigin).toMatchObject({ channel: "discord", to: "channel:123", threadId: "thread:456", }); }); it("rejects tool contexts without a bound session key", () => { const runtime = createRuntimeTaskFlow(); expect(() => runtime.fromToolContext({ sessionKey: undefined, deliveryContext: undefined, }), ).toThrow("TaskFlow runtime requires tool context with a sessionKey."); }); it("keeps TaskFlow reads owner-scoped and runs child tasks under the bound TaskFlow", () => { const runtime = createRuntimeTaskFlow(); const ownerTaskFlow = runtime.bindSession({ sessionKey: "agent:main:main", }); const otherTaskFlow = runtime.bindSession({ sessionKey: "agent:main:other", }); const created = ownerTaskFlow.createManaged({ controllerId: "tests/runtime-taskflow", goal: "Inspect PR batch", }); expect(otherTaskFlow.get(created.flowId)).toBeUndefined(); expect(otherTaskFlow.list()).toEqual([]); const child = ownerTaskFlow.runTask({ flowId: created.flowId, runtime: "acp", childSessionKey: "agent:main:subagent:child", runId: "runtime-taskflow-child", task: "Inspect PR 1", status: "running", startedAt: 10, lastEventAt: 10, }); expect(child).toMatchObject({ created: true, flow: expect.objectContaining({ flowId: created.flowId, }), task: expect.objectContaining({ parentFlowId: created.flowId, ownerKey: "agent:main:main", runId: "runtime-taskflow-child", }), }); if (!child.created) { throw new Error("expected child task creation to succeed"); } expect(getTaskById(child.task.taskId)).toMatchObject({ parentFlowId: created.flowId, ownerKey: "agent:main:main", }); expect(getTaskFlowById(created.flowId)).toMatchObject({ flowId: created.flowId, }); expect(ownerTaskFlow.getTaskSummary(created.flowId)).toMatchObject({ total: 1, active: 1, }); }); });