Plugins: add bound TaskFlow runtime (#59622)

Merged via squash.

Prepared head SHA: b4649f3238
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
Mariano
2026-04-02 13:17:09 +02:00
committed by GitHub
parent 474409deb5
commit bbf9800a8e
9 changed files with 703 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { getFlowById, resetFlowRegistryForTests } from "../../tasks/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();
resetFlowRegistryForTests({ 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(getFlowById(created.flowId)).toMatchObject({
flowId: created.flowId,
});
expect(ownerTaskFlow.getTaskSummary(created.flowId)).toMatchObject({
total: 1,
active: 1,
});
});
});