Files
united-workforce/legacy-packages/workflow-runtime/__tests__/build-context.test.ts
T
xiaoju d63d58ccb5 chore: reorganize repo — legacy packages to legacy-packages/, templates to examples/
- Move 15 old workflow-* packages to legacy-packages/ (inactive, preserved for reference)
- Rename templates/ → examples/ for clarity
- Rewrite docs/architecture.md to reflect current uwf architecture
- Active packages remain in packages/: cli-uwf, uwf-agent-hermes, uwf-agent-kit, uwf-moderator, uwf-protocol, workflow-util

小橘 🍊(NEKO Team)
2026-05-19 07:19:40 +00:00

124 lines
3.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
createCasStore,
putContentNodeWithRefs,
putStartNode,
putStateNode,
} from "@uncaged/workflow-cas";
import { buildThreadContext, END, START } from "../src/index.js";
describe("buildThreadContext", () => {
let dir: string;
beforeEach(async () => {
dir = await mkdtemp(join(tmpdir(), "wf-build-ctx-"));
});
afterEach(async () => {
await rm(dir, { recursive: true, force: true });
});
test("walks ancestor chain, resolves prompt, orders steps chronologically", async () => {
const cas = createCasStore(join(dir, "cas"));
const promptHash = await cas.put("hello-task");
const bundleHash = "BHAAAAAAAAAAA";
const startHash = await putStartNode(
cas,
{ name: "demo", hash: bundleHash, depth: 2, parentState: null },
promptHash,
);
const art = await cas.put("artifact-a");
const chPlan = await putContentNodeWithRefs(cas, "plan body", [art]);
const statePlan = await putStateNode(cas, {
role: "planner",
meta: { phase: 1 },
start: startHash,
content: chPlan,
ancestors: [],
compact: null,
timestamp: 1000,
childThread: null,
});
const chCode = await putContentNodeWithRefs(cas, "code body", []);
const stateCode = await putStateNode(cas, {
role: "coder",
meta: { phase: 2 },
start: startHash,
content: chCode,
ancestors: [statePlan],
compact: null,
timestamp: 2000,
childThread: null,
});
const ctx = await buildThreadContext(stateCode, cas);
expect(ctx.threadId).toBe("");
expect(ctx.depth).toBe(2);
expect(ctx.start.role).toBe(START);
expect(ctx.start.content).toBe("hello-task");
expect(ctx.steps.map((s) => s.role)).toEqual(["planner", "coder"]);
expect(ctx.steps[0]?.refs).toEqual([art]);
expect(ctx.steps[1]?.refs).toEqual([]);
expect(ctx.steps[0]?.timestamp).toBe(1000);
expect(ctx.steps[1]?.timestamp).toBe(2000);
});
test("StartNode head yields empty steps", async () => {
const cas = createCasStore(join(dir, "cas"));
const promptHash = await cas.put("only-prompt");
const startHash = await putStartNode(
cas,
{ name: "solo", hash: "BHBBBBBBBBBBB", depth: 1, parentState: null },
promptHash,
);
const ctx = await buildThreadContext(startHash, cas);
expect(ctx.steps).toEqual([]);
expect(ctx.start.content).toBe("only-prompt");
expect(ctx.depth).toBe(1);
});
test("omits __end__ states from steps", async () => {
const cas = createCasStore(join(dir, "cas"));
const promptHash = await cas.put("task");
const bundleHash = "BHCCCCCCCCCCC";
const startHash = await putStartNode(
cas,
{ name: "demo", hash: bundleHash, depth: 0, parentState: null },
promptHash,
);
const ch1 = await putContentNodeWithRefs(cas, "step-one", []);
const state1 = await putStateNode(cas, {
role: "worker",
meta: { done: false },
start: startHash,
content: ch1,
ancestors: [],
compact: null,
timestamp: 500,
childThread: null,
});
const endContent = await putContentNodeWithRefs(cas, "finished", []);
const endState = await putStateNode(cas, {
role: END,
meta: { returnCode: 0, summary: "finished" },
start: startHash,
content: endContent,
ancestors: [state1],
compact: null,
timestamp: 600,
childThread: null,
});
const ctx = await buildThreadContext(endState, cas);
expect(ctx.steps.map((s) => s.role)).toEqual(["worker"]);
});
});