mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-10 00:31:22 +00:00
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:
157
src/plugins/runtime/runtime-taskflow.test.ts
Normal file
157
src/plugins/runtime/runtime-taskflow.test.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user