Files
wehub-resource-sync 979fb22d7c
CI / test (20, macos-latest) (push) Waiting to run
CI / test (20, ubuntu-latest) (push) Waiting to run
CI / test (22, macos-latest) (push) Waiting to run
CI / test (22, ubuntu-latest) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:01:18 +08:00

493 lines
15 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
vi.mock("../src/logger.js", () => ({
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
import { registerFrontierFunction } from "../src/functions/frontier.js";
import { registerActionsFunction } from "../src/functions/actions.js";
import type { Action, ActionEdge, Checkpoint, Lease } from "../src/types.js";
import type { FrontierItem } from "../src/functions/frontier.js";
function mockKV() {
const store = new Map<string, Map<string, unknown>>();
return {
get: async <T>(scope: string, key: string): Promise<T | null> => {
return (store.get(scope)?.get(key) as T) ?? null;
},
set: async <T>(scope: string, key: string, data: T): Promise<T> => {
if (!store.has(scope)) store.set(scope, new Map());
store.get(scope)!.set(key, data);
return data;
},
delete: async (scope: string, key: string): Promise<void> => {
store.get(scope)?.delete(key);
},
list: async <T>(scope: string): Promise<T[]> => {
const entries = store.get(scope);
return entries ? (Array.from(entries.values()) as T[]) : [];
},
};
}
function mockSdk() {
const functions = new Map<string, Function>();
return {
registerFunction: (idOrOpts: string | { id: string }, handler: Function) => {
const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id;
functions.set(id, handler);
},
registerTrigger: () => {},
trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => {
const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id;
const payload = typeof idOrInput === "string" ? data : idOrInput.payload;
const fn = functions.get(id);
if (!fn) throw new Error(`No function: ${id}`);
return fn(payload);
},
};
}
function makeAction(overrides: Partial<Action>): Action {
const now = new Date().toISOString();
return {
id: overrides.id || `act_${Math.random().toString(36).slice(2, 10)}`,
title: overrides.title || "Test action",
description: overrides.description || "",
status: overrides.status || "pending",
priority: overrides.priority || 5,
createdAt: overrides.createdAt || now,
updatedAt: overrides.updatedAt || now,
createdBy: overrides.createdBy || "agent-1",
assignedTo: overrides.assignedTo,
project: overrides.project,
tags: overrides.tags || [],
sourceObservationIds: overrides.sourceObservationIds || [],
sourceMemoryIds: overrides.sourceMemoryIds || [],
result: overrides.result,
parentId: overrides.parentId,
metadata: overrides.metadata,
};
}
describe("Frontier Functions", () => {
let sdk: ReturnType<typeof mockSdk>;
let kv: ReturnType<typeof mockKV>;
beforeEach(() => {
sdk = mockSdk();
kv = mockKV();
registerActionsFunction(sdk as never, kv as never);
registerFrontierFunction(sdk as never, kv as never);
});
describe("mem::frontier", () => {
it("returns empty frontier when no actions exist", async () => {
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
totalActions: number;
totalUnblocked: number;
};
expect(result.success).toBe(true);
expect(result.frontier).toEqual([]);
expect(result.totalActions).toBe(0);
expect(result.totalUnblocked).toBe(0);
});
it("returns pending actions sorted by score", async () => {
const lowPriority = makeAction({
id: "act_low",
title: "Low priority",
priority: 2,
});
const highPriority = makeAction({
id: "act_high",
title: "High priority",
priority: 9,
});
await kv.set("mem:actions", lowPriority.id, lowPriority);
await kv.set("mem:actions", highPriority.id, highPriority);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
};
expect(result.success).toBe(true);
expect(result.frontier.length).toBe(2);
expect(result.frontier[0].action.id).toBe("act_high");
expect(result.frontier[1].action.id).toBe("act_low");
expect(result.frontier[0].score).toBeGreaterThan(
result.frontier[1].score,
);
});
it("excludes done and cancelled actions", async () => {
const pending = makeAction({
id: "act_pending",
title: "Pending",
status: "pending",
});
const done = makeAction({
id: "act_done",
title: "Done",
status: "done",
});
const cancelled = makeAction({
id: "act_cancelled",
title: "Cancelled",
status: "cancelled",
});
await kv.set("mem:actions", pending.id, pending);
await kv.set("mem:actions", done.id, done);
await kv.set("mem:actions", cancelled.id, cancelled);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
totalActions: number;
};
expect(result.success).toBe(true);
expect(result.frontier.length).toBe(1);
expect(result.frontier[0].action.id).toBe("act_pending");
expect(result.totalActions).toBe(3);
});
it("excludes blocked actions with unsatisfied requires edge", async () => {
const dependency = makeAction({
id: "act_dep",
title: "Dependency",
status: "pending",
});
const blocked = makeAction({
id: "act_blocked",
title: "Blocked",
status: "blocked",
});
await kv.set("mem:actions", dependency.id, dependency);
await kv.set("mem:actions", blocked.id, blocked);
const edge: ActionEdge = {
id: "ae_1",
type: "requires",
sourceActionId: blocked.id,
targetActionId: dependency.id,
createdAt: new Date().toISOString(),
};
await kv.set("mem:action-edges", edge.id, edge);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
};
expect(result.success).toBe(true);
const ids = result.frontier.map((f) => f.action.id);
expect(ids).toContain("act_dep");
expect(ids).not.toContain("act_blocked");
});
it("respects project filter", async () => {
const alphaAction = makeAction({
id: "act_alpha",
title: "Alpha task",
project: "alpha",
});
const betaAction = makeAction({
id: "act_beta",
title: "Beta task",
project: "beta",
});
await kv.set("mem:actions", alphaAction.id, alphaAction);
await kv.set("mem:actions", betaAction.id, betaAction);
const result = (await sdk.trigger("mem::frontier", {
project: "alpha",
})) as { success: boolean; frontier: FrontierItem[] };
expect(result.success).toBe(true);
expect(result.frontier.length).toBe(1);
expect(result.frontier[0].action.project).toBe("alpha");
});
it("higher priority scores higher", async () => {
const low = makeAction({
id: "act_low",
title: "Low",
priority: 1,
createdAt: new Date().toISOString(),
});
const high = makeAction({
id: "act_high",
title: "High",
priority: 10,
createdAt: new Date().toISOString(),
});
await kv.set("mem:actions", low.id, low);
await kv.set("mem:actions", high.id, high);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
};
expect(result.frontier[0].action.id).toBe("act_high");
expect(result.frontier[0].score).toBeGreaterThan(
result.frontier[1].score,
);
});
it("excludes actions gated by pending checkpoint", async () => {
const gatedAction = makeAction({
id: "act_gated",
title: "Gated action",
status: "pending",
});
const checkpoint: Checkpoint = {
id: "ckpt_1",
name: "CI check",
description: "Waiting for CI",
status: "pending",
type: "ci",
createdAt: new Date().toISOString(),
linkedActionIds: ["act_gated"],
};
await kv.set("mem:actions", gatedAction.id, gatedAction);
await kv.set("mem:checkpoints", checkpoint.id, checkpoint);
const gateEdge: ActionEdge = {
id: "ae_gate",
type: "gated_by",
sourceActionId: gatedAction.id,
targetActionId: checkpoint.id,
createdAt: new Date().toISOString(),
};
await kv.set("mem:action-edges", gateEdge.id, gateEdge);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
};
expect(result.frontier.length).toBe(0);
});
it("excludes actions conflicting with active actions", async () => {
const activeAction = makeAction({
id: "act_active",
title: "Active task",
status: "active",
});
const conflictAction = makeAction({
id: "act_conflict",
title: "Conflicting task",
status: "pending",
});
await kv.set("mem:actions", activeAction.id, activeAction);
await kv.set("mem:actions", conflictAction.id, conflictAction);
const conflictEdge: ActionEdge = {
id: "ae_conflict",
type: "conflicts_with",
sourceActionId: conflictAction.id,
targetActionId: activeAction.id,
createdAt: new Date().toISOString(),
};
await kv.set("mem:action-edges", conflictEdge.id, conflictEdge);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
};
const ids = result.frontier.map((f) => f.action.id);
expect(ids).toContain("act_active");
expect(ids).not.toContain("act_conflict");
});
it("active actions get score bonus", async () => {
const pendingAction = makeAction({
id: "act_pending",
title: "Pending",
status: "pending",
priority: 5,
createdAt: new Date().toISOString(),
});
const activeAction = makeAction({
id: "act_active",
title: "Active",
status: "active",
priority: 5,
createdAt: new Date().toISOString(),
});
await kv.set("mem:actions", pendingAction.id, pendingAction);
await kv.set("mem:actions", activeAction.id, activeAction);
const result = (await sdk.trigger("mem::frontier", {})) as {
success: boolean;
frontier: FrontierItem[];
};
const activeItem = result.frontier.find(
(f) => f.action.id === "act_active",
)!;
const pendingItem = result.frontier.find(
(f) => f.action.id === "act_pending",
)!;
expect(activeItem.score).toBeGreaterThan(pendingItem.score);
});
});
describe("mem::next", () => {
it("returns top suggestion when actions exist", async () => {
const action = makeAction({
id: "act_1",
title: "Top task",
priority: 8,
tags: ["urgent"],
});
await kv.set("mem:actions", action.id, action);
const result = (await sdk.trigger("mem::next", {})) as {
success: boolean;
suggestion: {
actionId: string;
title: string;
description: string;
priority: number;
score: number;
tags: string[];
} | null;
message: string;
totalActions: number;
};
expect(result.success).toBe(true);
expect(result.suggestion).not.toBeNull();
expect(result.suggestion!.actionId).toBe("act_1");
expect(result.suggestion!.title).toBe("Top task");
expect(result.suggestion!.priority).toBe(8);
expect(result.suggestion!.tags).toEqual(["urgent"]);
expect(result.message).toContain("Top task");
expect(result.totalActions).toBe(1);
});
it("returns null suggestion when no actions exist", async () => {
const result = (await sdk.trigger("mem::next", {})) as {
success: boolean;
suggestion: null;
message: string;
totalActions: number;
};
expect(result.success).toBe(true);
expect(result.suggestion).toBeNull();
expect(result.message).toContain("No actionable work");
expect(result.totalActions).toBe(0);
});
it("returns null when all actions are done", async () => {
const doneAction = makeAction({
id: "act_done",
title: "Completed",
status: "done",
});
await kv.set("mem:actions", doneAction.id, doneAction);
const result = (await sdk.trigger("mem::next", {})) as {
success: boolean;
suggestion: null;
message: string;
totalActions: number;
};
expect(result.success).toBe(true);
expect(result.suggestion).toBeNull();
expect(result.totalActions).toBe(1);
});
it("propagates failure when frontier fails", async () => {
const originalFunctions = new Map<string, Function>();
const failSdk = {
registerFunction: (idOrOpts: string | { id: string }, handler: Function) => {
const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id;
originalFunctions.set(id, handler);
},
registerTrigger: () => {},
trigger: async (
idOrInput: string | { function_id: string; payload: unknown },
data?: unknown,
) => {
const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id;
const payload = typeof idOrInput === "string" ? data : idOrInput.payload;
if (id === "mem::frontier") {
return { success: false, error: "internal failure" };
}
const fn = originalFunctions.get(id);
if (!fn) throw new Error(`No function: ${id}`);
return fn(payload);
},
};
const failKv = mockKV();
registerFrontierFunction(failSdk as never, failKv as never);
const nextFn = originalFunctions.get("mem::next")!;
const result = (await nextFn({})) as {
success: boolean;
suggestion: null;
message: string;
totalActions: number;
};
expect(result.success).toBe(false);
expect(result.suggestion).toBeNull();
expect(result.message).toContain("Failed to compute frontier");
expect(result.totalActions).toBe(0);
});
it("respects project filter", async () => {
const alphaAction = makeAction({
id: "act_alpha",
title: "Alpha task",
project: "alpha",
priority: 5,
});
const betaAction = makeAction({
id: "act_beta",
title: "Beta task",
project: "beta",
priority: 10,
});
await kv.set("mem:actions", alphaAction.id, alphaAction);
await kv.set("mem:actions", betaAction.id, betaAction);
const result = (await sdk.trigger("mem::next", {
project: "alpha",
})) as {
success: boolean;
suggestion: { actionId: string; title: string } | null;
};
expect(result.success).toBe(true);
expect(result.suggestion).not.toBeNull();
expect(result.suggestion!.actionId).toBe("act_alpha");
});
});
});