import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getDetachedTaskLifecycleRuntime, setDetachedTaskLifecycleRuntime, } from "../../tasks/detached-task-runtime.js"; import { getRuntimeTaskMocks, installRuntimeTaskDeliveryMock, resetRuntimeTaskTestState, } from "./runtime-task-test-harness.js"; import { createRuntimeTaskFlow } from "./runtime-taskflow.js"; import { createRuntimeTaskFlows, createRuntimeTaskRuns } from "./runtime-tasks.js"; const runtimeTaskMocks = getRuntimeTaskMocks(); afterEach(() => { resetRuntimeTaskTestState(); }); function requireRecord(value: unknown): Record { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error("Expected a non-array record"); } return value as Record; } function requireRecordById(items: readonly unknown[], id: string): Record { for (const item of items) { const record = requireRecord(item); if (record.id === id) { return record; } } throw new Error(`Missing record ${id}`); } describe("runtime tasks", () => { beforeEach(() => { installRuntimeTaskDeliveryMock(); }); it("exposes canonical task and TaskFlow DTOs without leaking raw registry fields", () => { const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ sessionKey: "agent:main:main", requesterOrigin: { channel: "telegram", to: "telegram:123", }, }); const taskFlows = createRuntimeTaskFlows().bindSession({ sessionKey: "agent:main:main", }); const taskRuns = createRuntimeTaskRuns().bindSession({ sessionKey: "agent:main:main", }); const otherTaskFlows = createRuntimeTaskFlows().bindSession({ sessionKey: "agent:main:other", }); const otherTaskRuns = createRuntimeTaskRuns().bindSession({ sessionKey: "agent:main:other", }); const created = legacyTaskFlow.createManaged({ controllerId: "tests/runtime-tasks", goal: "Review inbox", currentStep: "triage", stateJson: { lane: "priority" }, }); const child = legacyTaskFlow.runTask({ flowId: created.flowId, runtime: "acp", childSessionKey: "agent:main:subagent:child", runId: "runtime-task-run", label: "Inbox triage", task: "Review PR 1", status: "running", startedAt: 10, lastEventAt: 11, progressSummary: "Inspecting", }); if (!child.created) { throw new Error("expected child task creation to succeed"); } const listedFlow = requireRecordById(taskFlows.list(), created.flowId); expect(listedFlow.ownerKey).toBe("agent:main:main"); expect(listedFlow.goal).toBe("Review inbox"); expect(listedFlow.currentStep).toBe("triage"); const flow = requireRecord(taskFlows.get(created.flowId)); expect(flow.id).toBe(created.flowId); expect(flow.ownerKey).toBe("agent:main:main"); expect(flow.goal).toBe("Review inbox"); expect(flow.currentStep).toBe("triage"); expect(flow.state).toEqual({ lane: "priority" }); const taskSummary = requireRecord(flow.taskSummary); expect(taskSummary.total).toBe(1); expect(taskSummary.active).toBe(1); const flowTasks = flow.tasks; expect(Array.isArray(flowTasks)).toBe(true); const flowTask = requireRecordById(flowTasks as unknown[], child.task.taskId); expect(flowTask.flowId).toBe(created.flowId); expect(flowTask.title).toBe("Review PR 1"); expect(flowTask.label).toBe("Inbox triage"); expect(flowTask.runId).toBe("runtime-task-run"); const listedRun = requireRecordById(taskRuns.list(), child.task.taskId); expect(listedRun.flowId).toBe(created.flowId); expect(listedRun.sessionKey).toBe("agent:main:main"); expect(listedRun.title).toBe("Review PR 1"); expect(listedRun.status).toBe("running"); const taskRun = requireRecord(taskRuns.get(child.task.taskId)); expect(taskRun.id).toBe(child.task.taskId); expect(taskRun.flowId).toBe(created.flowId); expect(taskRun.title).toBe("Review PR 1"); expect(taskRun.progressSummary).toBe("Inspecting"); expect(taskRuns.findLatest()?.id).toBe(child.task.taskId); expect(taskRuns.resolve("runtime-task-run")?.id).toBe(child.task.taskId); const summary = requireRecord(taskFlows.getTaskSummary(created.flowId)); expect(summary.total).toBe(1); expect(summary.active).toBe(1); expect(otherTaskFlows.get(created.flowId)).toBeUndefined(); expect(otherTaskRuns.get(child.task.taskId)).toBeUndefined(); const flowDetail = taskFlows.get(created.flowId); expect(flowDetail).not.toHaveProperty("revision"); expect(flowDetail).not.toHaveProperty("controllerId"); expect(flowDetail).not.toHaveProperty("syncMode"); const taskDetail = taskRuns.get(child.task.taskId); expect(taskDetail).not.toHaveProperty("taskId"); expect(taskDetail).not.toHaveProperty("requesterSessionKey"); expect(taskDetail).not.toHaveProperty("scopeKind"); }); it("maps task cancellation results onto canonical task DTOs", async () => { const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ sessionKey: "agent:main:main", }); const taskRuns = createRuntimeTaskRuns().bindSession({ sessionKey: "agent:main:main", }); const created = legacyTaskFlow.createManaged({ controllerId: "tests/runtime-tasks", goal: "Cancel active task", }); const child = legacyTaskFlow.runTask({ flowId: created.flowId, runtime: "acp", childSessionKey: "agent:main:subagent:child", runId: "runtime-task-cancel", task: "Cancel me", status: "running", startedAt: 20, lastEventAt: 21, }); if (!child.created) { throw new Error("expected child task creation to succeed"); } const result = await taskRuns.cancel({ taskId: child.task.taskId, cfg: {} as never, }); expect(runtimeTaskMocks.cancelSessionMock).toHaveBeenCalledWith({ cfg: {}, sessionKey: "agent:main:subagent:child", reason: "task-cancel", }); expect(result.found).toBe(true); expect(result.cancelled).toBe(true); const task = requireRecord(result.task); expect(task.id).toBe(child.task.taskId); expect(task.title).toBe("Cancel me"); expect(task.status).toBe("cancelled"); }); it("routes runtime task cancellation through the detached task runtime seam", async () => { const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ sessionKey: "agent:main:main", }); const taskRuns = createRuntimeTaskRuns().bindSession({ sessionKey: "agent:main:main", }); const created = legacyTaskFlow.createManaged({ controllerId: "tests/runtime-tasks", goal: "Cancel through runtime seam", }); const child = legacyTaskFlow.runTask({ flowId: created.flowId, runtime: "acp", childSessionKey: "agent:main:subagent:child", runId: "runtime-task-cancel-seam", task: "Cancel via seam", status: "running", startedAt: 22, lastEventAt: 23, }); if (!child.created) { throw new Error("expected child task creation to succeed"); } const defaultRuntime = getDetachedTaskLifecycleRuntime(); const cancelDetachedTaskRunByIdSpy = vi.fn( (...args: Parameters) => defaultRuntime.cancelDetachedTaskRunById(...args), ); setDetachedTaskLifecycleRuntime({ ...defaultRuntime, cancelDetachedTaskRunById: cancelDetachedTaskRunByIdSpy, }); await taskRuns.cancel({ taskId: child.task.taskId, cfg: {} as never, }); expect(cancelDetachedTaskRunByIdSpy).toHaveBeenCalledWith({ cfg: {} as never, taskId: child.task.taskId, }); }); it("does not allow cross-owner task cancellation or leak task details", async () => { const legacyTaskFlow = createRuntimeTaskFlow().bindSession({ sessionKey: "agent:main:main", }); const otherTaskRuns = createRuntimeTaskRuns().bindSession({ sessionKey: "agent:main:other", }); const created = legacyTaskFlow.createManaged({ controllerId: "tests/runtime-tasks", goal: "Keep owner isolation", }); const child = legacyTaskFlow.runTask({ flowId: created.flowId, runtime: "acp", childSessionKey: "agent:main:subagent:child", runId: "runtime-task-isolation", task: "Do not cancel me", status: "running", startedAt: 30, lastEventAt: 31, }); if (!child.created) { throw new Error("expected child task creation to succeed"); } const result = await otherTaskRuns.cancel({ taskId: child.task.taskId, cfg: {} as never, }); expect(runtimeTaskMocks.cancelSessionMock).not.toHaveBeenCalled(); expect(result).toEqual({ found: false, cancelled: false, reason: "Task not found.", }); expect(otherTaskRuns.get(child.task.taskId)).toBeUndefined(); }); });