chore: migrate from bun to pnpm + vitest + esbuild

- Replace bun:test with vitest across all packages
- Replace bun build with esbuild
- Replace bun:sqlite with better-sqlite3
- Fix OCAS Store API: store.put/get → store.cas.put/get
- Fix vitest vi.mock hoisting (vi.hoisted)
- Add pnpm-workspace.yaml and pnpm-lock.yaml
- Update all package.json test/build scripts

WIP: 8 failures remain in agent-hermes (bun engines check + sqlite migration)

Refs #26
This commit is contained in:
2026-06-03 14:33:03 +00:00
parent 0d93e56acd
commit e5e6de2fad
93 changed files with 6675 additions and 647 deletions
@@ -1,10 +1,12 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from "node:child_process";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef, StepNodePayload, ThreadId } from "@united-workforce/protocol";
import { registerUwfSchemas } from "../schemas.js";
import { seedThreads } from "./thread-test-helpers.js";
@@ -38,12 +40,12 @@ describe("C1: adapter JSON round-trip integration", () => {
// 1. Set up CAS store with workflow, start node, and output schema
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-roundtrip",
description: "roundtrip integration test",
roles: {
@@ -62,7 +64,7 @@ describe("C1: adapter JSON round-trip integration", () => {
},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test round-trip task",
});
@@ -73,18 +75,18 @@ describe("C1: adapter JSON round-trip integration", () => {
await seedThreads(tmpDir, { [threadId]: startHash });
// 2. Pre-create CAS nodes that the mock agent would produce
const outputHash = await store.put(outputSchemaHash, {
const outputHash = await store.cas.put(outputSchemaHash, {
$status: "done",
result: "test-ok",
});
// Use text schema for detail (simple placeholder)
const detailHash = await store.put(schemas.text, "mock detail");
const detailHash = await store.cas.put(schemas.text, "mock detail");
const startedAtMs = 1716600000000;
const completedAtMs = 1716600001500;
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -119,15 +121,15 @@ describe("C1: adapter JSON round-trip integration", () => {
);
// 5. Run CLI with agent override pointing to our mock
const cliPath = join(import.meta.dirname, "..", "cli.js");
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
let stdout: string;
let stderr: string;
let exitCode: number;
try {
stdout = execFileSync(
"bun",
["run", cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
process.execPath,
[cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
{
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
@@ -165,8 +167,8 @@ describe("C1: adapter JSON round-trip integration", () => {
expect(cliOutput.head).toMatch(/^[0-9A-HJ-NP-TV-Z]{13}$/);
// Verify the CAS step node exists and has correct metadata
const storeAfter = createFsStore(casDir);
const stepNode = storeAfter.get(cliOutput.head as CasRef);
const storeAfter = await openStore(casDir);
const stepNode = storeAfter.cas.get(cliOutput.head as CasRef);
expect(stepNode).not.toBeNull();
const payload = stepNode!.payload as StepNodePayload;
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -181,18 +181,18 @@ async function insertStepNode(
const head = headEntry.head;
const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
const outputHash = await uwf.store.put(outputSchemaHash, outputPayload);
const outputHash = await uwf.store.cas.put(outputSchemaHash, outputPayload);
// Use text schema for detail (simple placeholder)
const detailHash = await uwf.store.put(uwf.schemas.text, "detail-placeholder");
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "detail-placeholder");
// Resolve start hash from head
const headNode = uwf.store.get(head);
const headNode = uwf.store.cas.get(head);
if (headNode === null) throw new Error(`head ${head} not found`);
const isStart = headNode.type === uwf.schemas.startNode;
const startHash = isStart ? head : (headNode.payload as { start: CasRef }).start;
const stepHash = (await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = (await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: isStart ? null : head,
role,
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
+1 -1
View File
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import type { Target, WorkflowPayload } from "@united-workforce/protocol";
import { evaluate } from "../moderator/evaluate.js";
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { execFileSync } from "node:child_process";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -23,7 +23,7 @@ describe("resolveHeadHash", () => {
test("returns head hash from variable store for active thread", async () => {
const threadId = "01JTEST0000000000000000001" as ThreadId;
const uwf = await createUwfStore(tmpDir);
const headHash = (await uwf.store.put(uwf.schemas.text, "active")) as CasRef;
const headHash = (await uwf.store.cas.put(uwf.schemas.text, "active")) as CasRef;
setThread(uwf.varStore, threadId, createThreadIndexEntry(headHash as CasRef));
const result = await resolveHeadHash(tmpDir, threadId);
@@ -36,7 +36,7 @@ describe("resolveHeadHash", () => {
const workflowHash = "workflow_hash_789" as CasRef;
const uwf = await createUwfStore(tmpDir);
const headHash = (await uwf.store.put(uwf.schemas.text, "completed-head")) as CasRef;
const headHash = (await uwf.store.cas.put(uwf.schemas.text, "completed-head")) as CasRef;
addHistoryEntry(uwf.varStore, {
thread: threadId,
workflow: workflowHash,
@@ -59,8 +59,8 @@ describe("resolveHeadHash", () => {
const workflowHash = "workflow_hash_xyz" as CasRef;
const uwf = await createUwfStore(tmpDir);
const activeHead = (await uwf.store.put(uwf.schemas.text, "active-v2")) as CasRef;
const historicalHash = (await uwf.store.put(uwf.schemas.text, "historical-v1")) as CasRef;
const activeHead = (await uwf.store.cas.put(uwf.schemas.text, "active-v2")) as CasRef;
const historicalHash = (await uwf.store.cas.put(uwf.schemas.text, "historical-v1")) as CasRef;
setThread(uwf.varStore, threadId, createThreadIndexEntry(activeHead));
addHistoryEntry(uwf.varStore, {
thread: threadId,
@@ -82,9 +82,9 @@ describe("resolveHeadHash", () => {
const threadId3 = "01JTEST0000000000000000007" as ThreadId;
const workflowHash = "workflow_hash_abc" as CasRef;
const uwf = await createUwfStore(tmpDir);
const hash1 = (await uwf.store.put(uwf.schemas.text, "hash-thread1")) as CasRef;
const hash2 = (await uwf.store.put(uwf.schemas.text, "hash-thread2")) as CasRef;
const hash3 = (await uwf.store.put(uwf.schemas.text, "hash-thread3")) as CasRef;
const hash1 = (await uwf.store.cas.put(uwf.schemas.text, "hash-thread1")) as CasRef;
const hash2 = (await uwf.store.cas.put(uwf.schemas.text, "hash-thread2")) as CasRef;
const hash3 = (await uwf.store.cas.put(uwf.schemas.text, "hash-thread3")) as CasRef;
addHistoryEntry(uwf.varStore, {
thread: threadId1,
workflow: workflowHash,
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { readFileSync } from "node:fs";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
@@ -31,7 +31,7 @@ describe("_agentNameFromBinary", () => {
describe("_printAgentMenu", () => {
test("prints known agents with labels", () => {
const logs: string[] = [];
spyOn(console, "log").mockImplementation((...args: unknown[]) => {
vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => {
logs.push(args.join(" "));
});
@@ -40,12 +40,12 @@ describe("_printAgentMenu", () => {
expect(logs.some((l) => l.includes("Hermes"))).toBe(true);
expect(logs.some((l) => l.includes("Claude Code"))).toBe(true);
mock.restore();
vi.restoreAllMocks();
});
test("prints unknown agents with binary name as label", () => {
const logs: string[] = [];
spyOn(console, "log").mockImplementation((...args: unknown[]) => {
vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => {
logs.push(args.join(" "));
});
@@ -53,7 +53,7 @@ describe("_printAgentMenu", () => {
expect(logs.some((l) => l.includes("uwf-custom-agent"))).toBe(true);
mock.restore();
vi.restoreAllMocks();
});
});
@@ -67,7 +67,7 @@ describe("cmdSetup agent configuration", () => {
});
afterEach(async () => {
mock.restore();
vi.restoreAllMocks();
await rm(storageRoot, { recursive: true, force: true });
});
@@ -80,7 +80,7 @@ describe("cmdSetup agent configuration", () => {
});
test("defaults to hermes agent when no agent specified", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
const result = await cmdSetup(baseArgs());
@@ -91,7 +91,7 @@ describe("cmdSetup agent configuration", () => {
});
test("writes specified agent as default", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
const result = await cmdSetup({ ...baseArgs(), agent: "claude-code" });
@@ -102,7 +102,7 @@ describe("cmdSetup agent configuration", () => {
});
test("preserves existing agents when adding new one", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
// First setup with hermes
await cmdSetup(baseArgs());
@@ -116,7 +116,7 @@ describe("cmdSetup agent configuration", () => {
});
test("updates defaultAgent on re-run with different agent", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
await cmdSetup(baseArgs());
const config1 = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
@@ -128,7 +128,7 @@ describe("cmdSetup agent configuration", () => {
});
test("normalizes agent name with uwf- prefix to bare name", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
const result = await cmdSetup({ ...baseArgs(), agent: "uwf-hermes" });
@@ -141,7 +141,7 @@ describe("cmdSetup agent configuration", () => {
});
test("normalizes uwf-claude-code to claude-code", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
const result = await cmdSetup({ ...baseArgs(), agent: "uwf-claude-code" });
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
import { afterEach, describe, expect, test, vi } from 'vitest';
import { mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -178,7 +178,7 @@ describe("_isBackspace", () => {
describe("_printProviderMenu", () => {
afterEach(() => {
mock.restore();
vi.restoreAllMocks();
});
const providers = [
@@ -188,7 +188,7 @@ describe("_printProviderMenu", () => {
test("prints correct number of lines (one per provider + custom)", () => {
const lines: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
lines.push(msg);
});
_printProviderMenu(providers);
@@ -198,7 +198,7 @@ describe("_printProviderMenu", () => {
test("custom option number = providers.length + 1", () => {
const lines: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
lines.push(msg);
});
_printProviderMenu(providers);
@@ -208,7 +208,7 @@ describe("_printProviderMenu", () => {
test("each provider line contains its label and baseUrl", () => {
const lines: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
lines.push(msg);
});
_printProviderMenu(providers);
@@ -294,12 +294,12 @@ describe("_resolveModelChoice", () => {
describe("_printModelMenu", () => {
afterEach(() => {
mock.restore();
vi.restoreAllMocks();
});
test("prints all models — each model name appears in output", () => {
const output: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
output.push(msg);
});
const models = ["model-a", "model-b", "model-c"];
@@ -312,7 +312,7 @@ describe("_printModelMenu", () => {
test("single column when termCols is very small", () => {
const output: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
output.push(msg);
});
_printModelMenu(["a", "b", "c"], 1);
@@ -322,7 +322,7 @@ describe("_printModelMenu", () => {
test("wide terminal fits multiple columns", () => {
const output: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
output.push(msg);
});
const models = Array.from({ length: 6 }, (_, i) => `m${i}`);
@@ -338,12 +338,12 @@ describe("_printModelMenu", () => {
describe("_printValidationResult", () => {
afterEach(() => {
mock.restore();
vi.restoreAllMocks();
});
test("ok=true prints success message containing '✓'", () => {
const lines: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
lines.push(msg);
});
_printValidationResult({ ok: true, error: null });
@@ -352,7 +352,7 @@ describe("_printValidationResult", () => {
test("ok=false prints warning message containing '⚠'", () => {
const lines: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
lines.push(msg);
});
_printValidationResult({ ok: false, error: "HTTP 401" });
@@ -361,7 +361,7 @@ describe("_printValidationResult", () => {
test("ok=false includes the error string in output", () => {
const lines: string[] = [];
spyOn(console, "log").mockImplementation((msg: string) => {
vi.spyOn(console, "log").mockImplementation((msg: string) => {
lines.push(msg);
});
_printValidationResult({ ok: false, error: "HTTP 401" });
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -10,11 +10,11 @@ describe("validateModel", () => {
const MODEL = "test-model";
afterEach(() => {
mock.restore();
vi.restoreAllMocks();
});
test("success path — returns ok on 200", async () => {
const mockFetch = spyOn(globalThis, "fetch").mockResolvedValue(
const mockFetch = vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({}), { status: 200 }),
);
@@ -37,7 +37,7 @@ describe("validateModel", () => {
});
test("HTTP 401 — returns error containing 401", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("Unauthorized", { status: 401, statusText: "Unauthorized" }),
);
@@ -50,7 +50,7 @@ describe("validateModel", () => {
});
test("HTTP 404 — returns error containing 404", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("Not Found", { status: 404, statusText: "Not Found" }),
);
@@ -64,7 +64,7 @@ describe("validateModel", () => {
test("network timeout — returns error mentioning timeout", async () => {
const err = new DOMException("signal timed out", "AbortError");
spyOn(globalThis, "fetch").mockRejectedValue(err);
vi.spyOn(globalThis, "fetch").mockRejectedValue(err);
const result = await validateModel(BASE_URL, API_KEY, MODEL);
@@ -75,7 +75,7 @@ describe("validateModel", () => {
});
test("network error (DNS/connection) — returns error mentioning connectivity", async () => {
spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("fetch failed"));
vi.spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("fetch failed"));
const result = await validateModel(BASE_URL, API_KEY, MODEL);
@@ -86,7 +86,7 @@ describe("validateModel", () => {
});
test("request body correctness", async () => {
const mockFetch = spyOn(globalThis, "fetch").mockResolvedValue(
const mockFetch = vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({}), { status: 200 }),
);
@@ -109,7 +109,7 @@ describe("cmdSetup with validation", () => {
});
afterEach(async () => {
mock.restore();
vi.restoreAllMocks();
await rm(storageRoot, { recursive: true, force: true });
});
@@ -122,7 +122,7 @@ describe("cmdSetup with validation", () => {
});
test("includes validation result on success", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
const result = await cmdSetup(setupArgs());
@@ -132,7 +132,7 @@ describe("cmdSetup with validation", () => {
});
test("includes validation failure — config still saved", async () => {
spyOn(globalThis, "fetch").mockResolvedValue(
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("Unauthorized", { status: 401, statusText: "Unauthorized" }),
);
@@ -1,4 +1,6 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import type { WorkflowPayload } from "@united-workforce/protocol";
@@ -15,7 +17,7 @@ import { parse } from "yaml";
describe("solve-issue workflow: Gitea API PR creation", () => {
// Navigate up from packages/cli/src/__tests__ to repo root
const workflowPath = join(
import.meta.dirname,
dirname(fileURLToPath(import.meta.url)),
"..",
"..",
"..",
@@ -1,5 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
/**
* B-group tests: validate JSON parsing logic used by spawnAgent.
*
+49 -49
View File
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap, putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef } from "@united-workforce/protocol";
import { cmdStepRead } from "../commands/step.js";
import { registerUwfSchemas } from "../schemas.js";
@@ -48,7 +48,7 @@ const DETAIL_SCHEMA = {
// ── helpers ───────────────────────────────────────────────────────────────────
async function registerDetailSchemas(store: ReturnType<typeof createFsStore>) {
async function registerDetailSchemas(store: Awaited<ReturnType<typeof openStore>>) {
await bootstrap(store);
const [turn, detail] = await Promise.all([
putSchema(store, TURN_SCHEMA),
@@ -92,11 +92,11 @@ describe("step read", () => {
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -113,12 +113,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -130,7 +130,7 @@ describe("step read", () => {
const turnHashes: CasRef[] = [];
for (let i = 1; i <= 3; i++) {
const content = `Turn ${i} content with some text to make it readable.`;
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: i - 1,
role: "assistant",
content,
@@ -140,7 +140,7 @@ describe("step read", () => {
turnHashes.push(turnHash);
}
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
@@ -148,7 +148,7 @@ describe("step read", () => {
turns: turnHashes,
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -180,11 +180,11 @@ describe("step read", () => {
process.env.OCAS_DIR = casDir;
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -201,12 +201,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -218,7 +218,7 @@ describe("step read", () => {
const turnHashes: CasRef[] = [];
for (let i = 1; i <= 4; i++) {
const content = generateContent(300, `Turn${i}`);
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: i - 1,
role: "assistant",
content,
@@ -228,7 +228,7 @@ describe("step read", () => {
turnHashes.push(turnHash);
}
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
@@ -236,7 +236,7 @@ describe("step read", () => {
turns: turnHashes,
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -266,11 +266,11 @@ describe("step read", () => {
process.env.OCAS_DIR = casDir;
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -287,12 +287,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -302,7 +302,7 @@ describe("step read", () => {
// Create 1 turn of 500 chars
const content = generateContent(500, "LongTurn");
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
@@ -310,7 +310,7 @@ describe("step read", () => {
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
@@ -318,7 +318,7 @@ describe("step read", () => {
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -343,10 +343,10 @@ describe("step read", () => {
process.env.OCAS_DIR = casDir;
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -363,12 +363,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -376,7 +376,7 @@ describe("step read", () => {
graph: {},
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -404,11 +404,11 @@ describe("step read", () => {
process.env.OCAS_DIR = casDir;
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -425,12 +425,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -451,11 +451,11 @@ describe("step read", () => {
await bootstrap(store);
const simpleDetailType = await putSchema(store, SIMPLE_DETAIL_SCHEMA);
const detailHash = await store.put(simpleDetailType, {
const detailHash = await store.cas.put(simpleDetailType, {
sessionId: "session-1",
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -482,11 +482,11 @@ describe("step read", () => {
process.env.OCAS_DIR = casDir;
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -503,12 +503,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -516,7 +516,7 @@ describe("step read", () => {
graph: {},
});
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "",
@@ -524,7 +524,7 @@ describe("step read", () => {
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
@@ -532,7 +532,7 @@ describe("step read", () => {
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -556,11 +556,11 @@ describe("step read", () => {
process.env.OCAS_DIR = casDir;
await mkdir(casDir, { recursive: true });
process.env.OCAS_DIR = casDir;
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -577,12 +577,12 @@ describe("step read", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -592,7 +592,7 @@ describe("step read", () => {
// Create turn with special markdown characters
const content = "This has `backticks`, **bold**, *italic*, and [links](http://example.com)";
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
@@ -600,7 +600,7 @@ describe("step read", () => {
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
@@ -608,7 +608,7 @@ describe("step read", () => {
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap, type Hash, type JSONSchema, putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef, StepNodePayload } from "@united-workforce/protocol";
import { cmdStepShow } from "../commands/step.js";
import { formatOutput } from "../format.js";
@@ -52,7 +52,7 @@ const DETAIL_SCHEMA: JSONSchema = {
};
type TestSetup = {
store: ReturnType<typeof createFsStore>;
store: Awaited<ReturnType<typeof openStore>>;
schemas: {
workflow: Hash;
startNode: Hash;
@@ -64,7 +64,7 @@ type TestSetup = {
};
async function setupTest(casDir: string): Promise<TestSetup> {
const store = createFsStore(casDir);
const store = await openStore(casDir);
await bootstrap(store);
const schemas = await registerUwfSchemas(store);
const [turnType, detailType] = await Promise.all([
@@ -88,22 +88,22 @@ async function createTestStep(
// Create turn nodes
const turnHashes: CasRef[] = [];
for (const payload of turnPayloads) {
const turnHash = await store.put(turnType, payload);
const turnHash = await store.cas.put(turnType, payload);
turnHashes.push(turnHash);
}
// Create detail node
const detailHash = await store.put(detailType, { turns: turnHashes });
const detailHash = await store.cas.put(detailType, { turns: turnHashes });
// Create dummy start node
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: "0000000000000" as CasRef,
prompt: "test prompt",
cwd: "/tmp",
});
// Create dummy output node
const outputHash = await store.put(schemas.text, { $status: "done" });
const outputHash = await store.cas.put(schemas.text, { $status: "done" });
// Create step node
const stepPayload: StepNodePayload = {
@@ -119,7 +119,7 @@ async function createTestStep(
assembledPrompt: null,
cwd: "/tmp",
};
return store.put(schemas.stepNode, stepPayload);
return store.cas.put(schemas.stepNode, stepPayload);
}
describe("cmdStepShow JSON serialization", () => {
+28 -28
View File
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap, putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef, ThreadId } from "@united-workforce/protocol";
import { STEP_NODE_SCHEMA } from "@united-workforce/protocol";
import { cmdStepList } from "../commands/step.js";
@@ -51,7 +51,7 @@ const DETAIL_SCHEMA = {
// ── helpers ──────────────────────────────────────────────────────────────────
async function registerDetailSchemas(store: ReturnType<typeof createFsStore>) {
async function registerDetailSchemas(store: Awaited<ReturnType<typeof openStore>>) {
await bootstrap(store);
const [turn, detail] = await Promise.all([
putSchema(store, TURN_SCHEMA),
@@ -133,18 +133,18 @@ describe("StepNode JSON schema", () => {
test("StepNode with timing fields passes CAS validation", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: "placeholder0000" as CasRef,
prompt: "test",
});
const outputHash = await store.put(schemas.text, "output text");
const outputHash = await store.cas.put(schemas.text, "output text");
const detailSchemas = await registerDetailSchemas(store);
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "s1",
model: "m1",
duration: 100,
@@ -153,7 +153,7 @@ describe("StepNode JSON schema", () => {
});
// Should succeed — valid timing fields
const hash = await store.put(schemas.stepNode, {
const hash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -175,24 +175,24 @@ describe("step list timing", () => {
test("step list includes durationMs = completedAtMs - startedAtMs", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {},
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "test",
});
const outputHash = await store.put(schemas.text, "output");
const detailHash = await store.put(detailSchemas.detail, {
const outputHash = await store.cas.put(schemas.text, "output");
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "s1",
model: "m1",
duration: 100,
@@ -203,7 +203,7 @@ describe("step list timing", () => {
const startedAt = 1716600000000;
const completedAt = 1716600003500;
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -233,11 +233,11 @@ describe("thread read timing", () => {
test("thread read header includes Duration", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -256,28 +256,28 @@ describe("thread read timing", () => {
},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "test task",
});
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "Done.",
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "s1",
model: "m1",
duration: 100,
turnCount: 1,
turns: [turnHash],
});
const outputHash = await store.put(schemas.text, "output");
const outputHash = await store.cas.put(schemas.text, "output");
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -299,11 +299,11 @@ describe("thread read timing", () => {
test("thread read shows sub-second duration as ms", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -322,28 +322,28 @@ describe("thread read timing", () => {
},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "test",
});
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "Done.",
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "s1",
model: "m1",
duration: 100,
turnCount: 1,
turns: [turnHash],
});
const outputHash = await store.put(schemas.text, "output");
const outputHash = await store.cas.put(schemas.text, "output");
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -113,7 +113,7 @@ describe("Global CAS directory", () => {
// Store a node in the first store
const testData = { test: "data" };
const _hash = uwf1.store.put(uwf1.schemas.text, JSON.stringify(testData));
const _hash = uwf1.store.cas.put(uwf1.schemas.text, JSON.stringify(testData));
// Both stores share the same CAS filesystem directory
// Since schemas are registered idempotently, they should have the same hash
@@ -133,14 +133,14 @@ describe("Global CAS directory", () => {
await mkdir(storageRoot, { recursive: true });
const uwf = await createUwfStore(storageRoot);
const hash = await uwf.store.put(uwf.schemas.text, "registry-test");
const hash = await uwf.store.cas.put(uwf.schemas.text, "registry-test");
saveWorkflowRegistry(uwf.varStore, "test-workflow", hash);
const registry = loadWorkflowRegistry(uwf.varStore);
expect(registry["test-workflow"]).toBe(hash);
const { access } = await import("node:fs/promises");
await access(join(globalCasDir, "variables.db"));
await access(join(globalCasDir, "vars"));
const registryPath = join(storageRoot, "workflows.yaml");
await expect(access(registryPath)).rejects.toThrow();
@@ -154,7 +154,7 @@ describe("Global CAS directory", () => {
await mkdir(storageRoot, { recursive: true });
const uwfSeed = await createUwfStore(storageRoot);
const hash = await uwfSeed.store.put(uwfSeed.schemas.text, "migrated-workflow");
const hash = await uwfSeed.store.cas.put(uwfSeed.schemas.text, "migrated-workflow");
const registryPath = getRegistryPath(storageRoot);
const { writeFile, access, readFile } = await import("node:fs/promises");
@@ -180,7 +180,7 @@ describe("Global CAS directory", () => {
const threadId = "01JTEST0000000000000000AB" as ThreadId;
const uwfSeed = await createUwfStore(storageRoot);
const headHash = await uwfSeed.store.put(uwfSeed.schemas.text, "migrated-thread-head");
const headHash = await uwfSeed.store.cas.put(uwfSeed.schemas.text, "migrated-thread-head");
const { writeFile, access, readFile } = await import("node:fs/promises");
const threadsPath = join(storageRoot, "threads.yaml");
await writeFile(threadsPath, `${threadId}: ${headHash}\n`, "utf8");
@@ -204,7 +204,7 @@ describe("Global CAS directory", () => {
const threadId = "01JTEST000000000000000123" as ThreadId;
const uwfSeed = await createUwfStore(storageRoot);
const headHash = await uwfSeed.store.put(uwfSeed.schemas.text, "hash-456");
const headHash = await uwfSeed.store.cas.put(uwfSeed.schemas.text, "hash-456");
setThread(uwfSeed.varStore, threadId, createThreadIndexEntry(headHash));
const uwf = await createUwfStore(storageRoot);
@@ -225,7 +225,7 @@ describe("Global CAS directory", () => {
const uwf = await createUwfStore(storageRoot);
const threadId = "thread-123" as ThreadId;
const headHash = await uwf.store.put(uwf.schemas.text, "history-head");
const headHash = await uwf.store.cas.put(uwf.schemas.text, "history-head");
const { addHistoryEntry, findHistoryEntry } = await import("../store.js");
addHistoryEntry(uwf.varStore, {
thread: threadId,
@@ -241,7 +241,7 @@ describe("Global CAS directory", () => {
expect(entry?.head).toBe(headHash);
const { access } = await import("node:fs/promises");
await access(join(globalCasDir, "variables.db"));
await access(join(globalCasDir, "vars"));
const historyPath = join(storageRoot, "history.jsonl");
await expect(access(historyPath)).rejects.toThrow();
@@ -256,8 +256,8 @@ describe("Global CAS directory", () => {
const threadId = "01JTEST0000000000000000CD" as ThreadId;
const uwfSeed = await createUwfStore(storageRoot);
const workflowHash = await uwfSeed.store.put(uwfSeed.schemas.text, "migrated-workflow");
const headHash = await uwfSeed.store.put(uwfSeed.schemas.text, "migrated-head");
const workflowHash = await uwfSeed.store.cas.put(uwfSeed.schemas.text, "migrated-workflow");
const headHash = await uwfSeed.store.cas.put(uwfSeed.schemas.text, "migrated-head");
const completedAt = 1780410000000;
const { writeFile, access, readFile } = await import("node:fs/promises");
const historyPath = join(storageRoot, "history.jsonl");
@@ -301,7 +301,7 @@ describe("Global CAS directory", () => {
// Store a CAS node
const testPayload = JSON.stringify({ test: "node" });
const _hash = uwf.store.put(uwf.schemas.text, testPayload);
const _hash = uwf.store.cas.put(uwf.schemas.text, testPayload);
// Verify the node is in global CAS directory
const { readdir } = await import("node:fs/promises");
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { homedir } from "node:os";
import { join } from "node:path";
import { getDefaultStorageRoot, getGlobalCasDir, resolveStorageRoot } from "../store.js";
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { mkdir, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -16,7 +16,7 @@ async function seedHistoryHead(
uwf: Awaited<ReturnType<typeof createUwfStore>>,
label: string,
): Promise<CasRef> {
return (await uwf.store.put(uwf.schemas.text, label)) as CasRef;
return (await uwf.store.cas.put(uwf.schemas.text, label)) as CasRef;
}
describe("thread cancel status", () => {
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -39,7 +39,7 @@ async function createTestWorkflow(uwf: UwfStore): Promise<CasRef> {
graph: { start: "role1" },
conditions: {},
};
return await uwf.store.put(uwf.schemas.workflow, workflowPayload);
return await uwf.store.cas.put(uwf.schemas.workflow, workflowPayload);
}
async function createTestThread(
@@ -54,7 +54,7 @@ async function createTestThread(
prompt: "test prompt",
cwd: storageRoot,
};
const headHash = await uwf.store.put(uwf.schemas.startNode, startPayload);
const headHash = await uwf.store.cas.put(uwf.schemas.startNode, startPayload);
setThread(uwf.varStore, threadId, createThreadIndexEntry(headHash));
@@ -494,7 +494,7 @@ describe("edge cases", () => {
const uwfIdx = await createUwfStore(tmpDir);
const index = loadAllThreads(uwfIdx.varStore);
const placeholderHead = (await uwfIdx.store.put(
const placeholderHead = (await uwfIdx.store.cas.put(
uwfIdx.schemas.text,
"invalid-ulid-placeholder",
)) as CasRef;
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -82,7 +82,7 @@ graph:
const headHash = getThread(uwf.varStore, result.thread as ThreadId)!.head;
expect(headHash).toBeDefined();
const startNode = uwf.store.get(headHash as CasRef);
const startNode = uwf.store.cas.get(headHash as CasRef);
expect(startNode).not.toBe(null);
expect(startNode?.type).toBe(uwf.schemas.startNode);
@@ -175,7 +175,7 @@ graph:
const uwf = await createUwfStore(storageRoot);
const headHash = getThread(uwf.varStore, result.thread as ThreadId)!.head;
const startNode = uwf.store.get(headHash as CasRef);
const startNode = uwf.store.cas.get(headHash as CasRef);
const startPayload = startNode?.payload as StartNodePayload;
// Should default to process.cwd()
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap, putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef, ThreadId } from "@united-workforce/protocol";
import { cmdThreadRead } from "../commands/thread.js";
import { registerUwfSchemas } from "../schemas.js";
@@ -49,7 +49,7 @@ const DETAIL_SCHEMA = {
// ── helpers ───────────────────────────────────────────────────────────────────
async function registerDetailSchemas(store: ReturnType<typeof createFsStore>) {
async function registerDetailSchemas(store: Awaited<ReturnType<typeof openStore>>) {
await bootstrap(store);
const [turn, detail] = await Promise.all([
putSchema(store, TURN_SCHEMA),
@@ -91,11 +91,11 @@ describe("thread read --quota flag", () => {
test("test 1: basic quota enforcement with 3 steps", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -112,12 +112,12 @@ describe("thread read --quota flag", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -129,21 +129,21 @@ describe("thread read --quota flag", () => {
const steps: CasRef[] = [];
for (let i = 1; i <= 3; i++) {
const content = generateContent(500, `Step${i}`);
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: `session-${i}`,
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: steps[i - 2] ?? null,
role: "worker",
@@ -176,11 +176,11 @@ describe("thread read --quota flag", () => {
test("test 2: quota check order - verifies bug is fixed", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -197,12 +197,12 @@ describe("thread read --quota flag", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -212,21 +212,21 @@ describe("thread read --quota flag", () => {
// Create 2 steps: first=300 chars, second=600 chars
const step1Content = generateContent(300, "First");
const step1TurnHash = await store.put(detailSchemas.turn, {
const step1TurnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: step1Content,
toolCalls: null,
reasoning: null,
});
const step1DetailHash = await store.put(detailSchemas.detail, {
const step1DetailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [step1TurnHash],
});
const step1Hash = await store.put(schemas.stepNode, {
const step1Hash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -239,21 +239,21 @@ describe("thread read --quota flag", () => {
});
const step2Content = generateContent(600, "Second");
const step2TurnHash = await store.put(detailSchemas.turn, {
const step2TurnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: step2Content,
toolCalls: null,
reasoning: null,
});
const step2DetailHash = await store.put(detailSchemas.detail, {
const step2DetailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-2",
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [step2TurnHash],
});
const step2Hash = await store.put(schemas.stepNode, {
const step2Hash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: step1Hash,
role: "worker",
@@ -287,11 +287,11 @@ describe("thread read --quota flag", () => {
test("test 3: quota with --start section", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -308,12 +308,12 @@ describe("thread read --quota flag", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task with a moderately long prompt to test quota accounting",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -325,21 +325,21 @@ describe("thread read --quota flag", () => {
const steps: CasRef[] = [];
for (let i = 1; i <= 2; i++) {
const content = generateContent(400, `Step${i}`);
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: `session-${i}`,
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: steps[i - 2] ?? null,
role: "worker",
@@ -370,11 +370,11 @@ describe("thread read --quota flag", () => {
test("test 5a: quota edge case - minimal quota", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -391,12 +391,12 @@ describe("thread read --quota flag", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -405,21 +405,21 @@ describe("thread read --quota flag", () => {
});
const content = generateContent(500, "Test");
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: "session-1",
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -445,11 +445,11 @@ describe("thread read --quota flag", () => {
test("test 5b: quota edge case - very large quota", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -466,12 +466,12 @@ describe("thread read --quota flag", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -483,21 +483,21 @@ describe("thread read --quota flag", () => {
const steps: CasRef[] = [];
for (let i = 1; i <= 3; i++) {
const content = generateContent(300, `Step${i}`);
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: `session-${i}`,
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: steps[i - 2] ?? null,
role: "worker",
@@ -527,11 +527,11 @@ describe("thread read --quota flag", () => {
test("test 6: quota with --before parameter", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const detailSchemas = await registerDetailSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -548,12 +548,12 @@ describe("thread read --quota flag", () => {
graph: {},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
});
const outputHash = await store.put(schemas.workflow, {
const outputHash = await store.cas.put(schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -565,21 +565,21 @@ describe("thread read --quota flag", () => {
const steps: CasRef[] = [];
for (let i = 1; i <= 5; i++) {
const content = generateContent(300, `Step${i}`);
const turnHash = await store.put(detailSchemas.turn, {
const turnHash = await store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content,
toolCalls: null,
reasoning: null,
});
const detailHash = await store.put(detailSchemas.detail, {
const detailHash = await store.cas.put(detailSchemas.detail, {
sessionId: `session-${i}`,
model: "test-model",
duration: 1000,
turnCount: 1,
turns: [turnHash],
});
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: steps[i - 2] ?? null,
role: "worker",
@@ -1,9 +1,8 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap, putSchema } from "@ocas/core";
import type { createFsStore } from "@ocas/fs";
import { bootstrap, putSchema, type Store } from "@ocas/core";
import type { CasRef, ThreadId } from "@united-workforce/protocol";
import { cmdThreadRead, THREAD_READ_DEFAULT_QUOTA } from "../commands/thread.js";
import type { UwfStore } from "../store.js";
@@ -57,7 +56,7 @@ async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
return createUwfStore(storageRoot);
}
async function registerDetailSchemas(store: ReturnType<typeof createFsStore>) {
async function registerDetailSchemas(store: Store) {
await bootstrap(store);
const [turn, detail] = await Promise.all([
putSchema(store, TURN_SCHEMA),
@@ -85,7 +84,7 @@ describe("thread read XML tag isolation", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -102,12 +101,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Fix issue #459",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -115,7 +114,7 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content:
@@ -123,7 +122,7 @@ describe("thread read XML tag isolation", () => {
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sx",
model: "mx",
duration: 500,
@@ -131,7 +130,7 @@ describe("thread read XML tag isolation", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "planner",
@@ -164,7 +163,7 @@ describe("thread read XML tag isolation", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -181,12 +180,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Fix issue",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -194,14 +193,14 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "---\nstatus: ready\n---\n\nContent here...",
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sx",
model: "mx",
duration: 500,
@@ -209,7 +208,7 @@ describe("thread read XML tag isolation", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "planner",
@@ -242,7 +241,7 @@ describe("thread read XML tag isolation", () => {
test("scenario 3: same role repeated does not show prompt twice", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -259,12 +258,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Write something",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -272,7 +271,7 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const step1 = await uwf.store.put(uwf.schemas.stepNode, {
const step1 = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "writer",
@@ -284,7 +283,7 @@ describe("thread read XML tag isolation", () => {
assembledPrompt: null,
});
const step2 = await uwf.store.put(uwf.schemas.stepNode, {
const step2 = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step1 as CasRef,
role: "writer",
@@ -309,7 +308,7 @@ describe("thread read XML tag isolation", () => {
test("scenario 4: step with no detail shows no output tags", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -326,12 +325,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Do stuff",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -339,7 +338,7 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -370,7 +369,7 @@ describe("thread read XML tag isolation", () => {
test("scenario 5: empty content shows no output tags", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {},
@@ -378,12 +377,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Do stuff",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -394,7 +393,7 @@ describe("thread read XML tag isolation", () => {
// A detail ref that doesn't exist → extractLastAssistantContent returns null
const missingDetailRef = "missingdetail0" as CasRef;
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -419,7 +418,7 @@ describe("thread read XML tag isolation", () => {
test("scenario 6: thread read with --start flag shows task section", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -436,12 +435,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Initial prompt",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -449,7 +448,7 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "roleA",
@@ -478,7 +477,7 @@ describe("thread read XML tag isolation", () => {
test("scenario 7: thread read with --before parameter", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -511,12 +510,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Initial prompt",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -524,7 +523,7 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const step1 = await uwf.store.put(uwf.schemas.stepNode, {
const step1 = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "roleA",
@@ -536,7 +535,7 @@ describe("thread read XML tag isolation", () => {
assembledPrompt: null,
});
const step2 = await uwf.store.put(uwf.schemas.stepNode, {
const step2 = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step1 as CasRef,
role: "roleB",
@@ -548,7 +547,7 @@ describe("thread read XML tag isolation", () => {
assembledPrompt: null,
});
const step3 = await uwf.store.put(uwf.schemas.stepNode, {
const step3 = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step2 as CasRef,
role: "roleC",
@@ -584,7 +583,7 @@ describe("thread read XML tag isolation", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -601,12 +600,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Write something",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -614,14 +613,14 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "Content with <special> & characters > like <this>",
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sx",
model: "mx",
duration: 500,
@@ -629,7 +628,7 @@ describe("thread read XML tag isolation", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "writer",
@@ -653,7 +652,7 @@ describe("thread read XML tag isolation", () => {
test("scenario 10: quota limit with XML tags", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -670,12 +669,12 @@ describe("thread read XML tag isolation", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Initial prompt",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -686,7 +685,7 @@ describe("thread read XML tag isolation", () => {
const steps: CasRef[] = [];
let prev: CasRef | null = null;
for (let i = 0; i < 5; i++) {
const step = (await uwf.store.put(uwf.schemas.stepNode, {
const step = (await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev,
role: "roleA",
@@ -1,10 +1,12 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from "node:child_process";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef, StepNodePayload, ThreadId } from "@united-workforce/protocol";
import { cmdThreadShow } from "../commands/thread.js";
import { registerUwfSchemas } from "../schemas.js";
@@ -43,11 +45,11 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-resume",
description: "resume command integration test",
roles: {
@@ -82,7 +84,7 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test resume task",
cwd: tmpDir,
@@ -91,16 +93,16 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
process.env.OCAS_DIR = casDir;
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
const outputHash = await store.put(outputSchemaHash, {
const outputHash = await store.cas.put(outputSchemaHash, {
$status: "needs_input",
question: "Which API?",
});
const detailHash = await store.put(schemas.text, "mock detail");
const detailHash = await store.cas.put(schemas.text, "mock detail");
const startedAtMs = 1716600000000;
const completedAtMs = 1716600001500;
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -129,11 +131,11 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
mode === "suspend" ? { $status: "needs_input", question: "Which API?" } : { $status: "ok" };
const adapterJson = JSON.stringify({
stepHash: await store.put(schemas.stepNode, {
stepHash: await store.cas.put(schemas.stepNode, {
start: startHash,
prev: stepHash,
role: "worker",
output: await store.put(outputSchemaHash, frontmatter),
output: await store.cas.put(outputSchemaHash, frontmatter),
detail: detailHash,
agent: "uwf-mock",
edgePrompt: "resume prompt placeholder",
@@ -181,9 +183,9 @@ function runUwf(
args: string[],
casDir: string,
): { stdout: string; stderr: string; status: number } {
const cliPath = join(import.meta.dirname, "..", "cli.js");
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
try {
const stdout = execFileSync("bun", ["run", cliPath, ...args], {
const stdout = execFileSync(process.execPath, [cliPath, ...args], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
env: {
@@ -213,10 +215,10 @@ describe("uwf thread resume", () => {
test("resume non-suspended thread returns error", async () => {
const casDir = join(tmpDir, "cas");
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "idle-workflow",
description: "idle thread",
roles: {
@@ -235,7 +237,7 @@ describe("uwf thread resume", () => {
},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "task",
cwd: tmpDir,
@@ -382,22 +384,22 @@ async function setupOkMockAgent(
casDir: string,
prevHead: CasRef,
): Promise<{ mockAgentPath: string }> {
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
const prevNode = store.get(prevHead);
const prevNode = store.cas.get(prevHead);
if (prevNode === null || prevNode.type !== schemas.stepNode) {
throw new Error(`expected StepNode at ${prevHead}`);
}
const prevPayload = prevNode.payload as StepNodePayload;
const outputHash = await store.put(outputSchemaHash, { $status: "ok" });
const detailHash = await store.put(schemas.text, "ok detail");
const outputHash = await store.cas.put(outputSchemaHash, { $status: "ok" });
const detailHash = await store.cas.put(schemas.text, "ok detail");
const startedAtMs = Date.now();
const completedAtMs = startedAtMs + 1;
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: prevPayload.start,
prev: prevHead,
role: "worker",
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -96,15 +96,15 @@ async function insertStepNode(
const head = headEntry.head;
const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
const outputHash = await uwf.store.put(outputSchemaHash, outputPayload);
const detailHash = await uwf.store.put(uwf.schemas.text, "detail-placeholder");
const outputHash = await uwf.store.cas.put(outputSchemaHash, outputPayload);
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "detail-placeholder");
const headNode = uwf.store.get(head);
const headNode = uwf.store.cas.get(head);
if (headNode === null) throw new Error(`head ${head} not found`);
const isStart = headNode.type === uwf.schemas.startNode;
const startHash = isStart ? head : (headNode.payload as { start: CasRef }).start;
const stepHash = (await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = (await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: isStart ? null : head,
role,
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { execFileSync } from "node:child_process";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
@@ -78,7 +78,7 @@ graph:
const headHash = entry!.head;
expect(headHash).toBeDefined();
const startNode = uwf.store.get(headHash as CasRef);
const startNode = uwf.store.cas.get(headHash as CasRef);
expect(startNode).not.toBe(null);
expect(startNode?.type).toBe(uwf.schemas.startNode);
@@ -136,14 +136,14 @@ graph:
const uwfBin = join(process.cwd(), "dist", "cli.js");
// Register the workflow
execFileSync("bun", [uwfBin, "workflow", "add", workflowPath], {
execFileSync(process.execPath, [uwfBin, "workflow", "add", workflowPath], {
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, OCAS_DIR: casDir },
encoding: "utf8",
});
// Verify CLI accepts --cwd option (no error thrown)
const output = execFileSync(
"bun",
process.execPath,
[uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
{
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, OCAS_DIR: casDir },
@@ -1,8 +1,10 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from "node:child_process";
import { join } from "node:path";
const CLI_PATH = join(import.meta.dirname, "..", "cli.js");
const CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "cli.js");
function runCli(args: string[]): { stdout: string; stderr: string; exitCode: number } {
try {
@@ -1,10 +1,12 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from "node:child_process";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { putSchema } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { openStore } from "@ocas/fs";
import type { CasRef, StepNodePayload, ThreadId } from "@united-workforce/protocol";
import { cmdThreadShow } from "../commands/thread.js";
import { registerUwfSchemas } from "../schemas.js";
@@ -38,12 +40,12 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
process.env.OCAS_DIR = casDir;
try {
const store = createFsStore(casDir);
const store = await openStore(casDir);
const schemas = await registerUwfSchemas(store);
const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
const workflowHash = await store.put(schemas.workflow, {
const workflowHash = await store.cas.put(schemas.workflow, {
name: "test-suspend-step",
description: "suspend step integration test",
roles: {
@@ -68,7 +70,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
},
});
const startHash = await store.put(schemas.startNode, {
const startHash = await store.cas.put(schemas.startNode, {
workflow: workflowHash,
prompt: "Test suspend task",
cwd: tmpDir,
@@ -77,16 +79,16 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
const threadId = "01SUSPENDSTEPTEST0000000" as ThreadId;
await seedThreads(tmpDir, { [threadId]: startHash });
const outputHash = await store.put(outputSchemaHash, {
const outputHash = await store.cas.put(outputSchemaHash, {
$status: "needs_input",
question: "Which API?",
});
const detailHash = await store.put(schemas.text, "mock detail");
const detailHash = await store.cas.put(schemas.text, "mock detail");
const startedAtMs = 1716600000000;
const completedAtMs = 1716600001500;
const stepHash = await store.put(schemas.stepNode, {
const stepHash = await store.cas.put(schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -118,10 +120,10 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
`defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
);
const cliPath = join(import.meta.dirname, "..", "cli.js");
const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
const stdout = execFileSync(
"bun",
["run", cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
process.execPath,
[cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
{
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
@@ -141,14 +143,14 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
expect(cliOutput.suspendedRole).toBe("worker");
expect(cliOutput.suspendMessage).toBe("Please clarify: Which API?");
const storeAfter = createFsStore(casDir);
const stepNode = storeAfter.get(cliOutput.head as CasRef);
const storeAfter = await openStore(casDir);
const stepNode = storeAfter.cas.get(cliOutput.head as CasRef);
expect(stepNode).not.toBeNull();
const payload = stepNode!.payload as StepNodePayload;
expect(payload.role).toBe("worker");
expect(payload.output).toBe(outputHash);
const outputNode = storeAfter.get(outputHash);
const outputNode = storeAfter.cas.get(outputHash);
expect(outputNode?.payload).toEqual({
$status: "needs_input",
question: "Which API?",
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -41,7 +41,7 @@ describe("suspended thread display", () => {
const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
// Create test workflow with suspend capability
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-suspend-display",
description: "test suspended display",
roles: {
@@ -66,7 +66,7 @@ describe("suspended thread display", () => {
},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Test task requiring input",
cwd: tmpDir,
@@ -74,13 +74,13 @@ describe("suspended thread display", () => {
// Create suspended thread
const suspendedThreadId = "01SUSPENDEDTHREAD0000000" as ThreadId;
const outputHash = await uwf.store.put(outputSchemaHash, {
const outputHash = await uwf.store.cas.put(outputSchemaHash, {
$status: "needs_input",
question: "What is the target API?",
});
const detailHash = await uwf.store.put(uwf.schemas.text, "mock detail");
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -103,7 +103,7 @@ describe("suspended thread display", () => {
// Create normal (idle) thread
const idleThreadId = "01IDLETHREAD00000000000" as ThreadId;
const idleStartHash = await uwf.store.put(uwf.schemas.startNode, {
const idleStartHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Normal task",
cwd: tmpDir,
@@ -148,7 +148,7 @@ describe("suspended thread display", () => {
const uwf = await createUwfStore(tmpDir);
const outputSchemaHash = await putSchema(uwf.store, OUTPUT_SCHEMA);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-suspend-show",
description: "test suspended show",
roles: {
@@ -173,20 +173,20 @@ describe("suspended thread display", () => {
},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Test task",
cwd: tmpDir,
});
const threadId = "01SUSPENDSHOW000000000" as ThreadId;
const outputHash = await uwf.store.put(outputSchemaHash, {
const outputHash = await uwf.store.cas.put(outputSchemaHash, {
$status: "needs_input",
question: "Which database to use?",
});
const detailHash = await uwf.store.put(uwf.schemas.text, "mock detail");
const detailHash = await uwf.store.cas.put(uwf.schemas.text, "mock detail");
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -235,7 +235,7 @@ describe("suspended thread display", () => {
try {
const uwf = await createUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-normal",
description: "test normal thread",
roles: {
@@ -252,7 +252,7 @@ describe("suspended thread display", () => {
},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Normal task",
cwd: tmpDir,
@@ -7,10 +7,10 @@ async function ensureHeadInCas(
head: CasRef,
threadId: ThreadId,
): Promise<CasRef> {
if (uwf.store.get(head) !== null) {
if (uwf.store.cas.get(head) !== null) {
return head;
}
return (await uwf.store.put(uwf.schemas.text, `thread-head:${threadId}:${head}`)) as CasRef;
return (await uwf.store.cas.put(uwf.schemas.text, `thread-head:${threadId}:${head}`)) as CasRef;
}
export async function seedThread(
+74 -75
View File
@@ -1,9 +1,8 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap, putSchema } from "@ocas/core";
import type { createFsStore } from "@ocas/fs";
import { bootstrap, putSchema, type Store } from "@ocas/core";
import type { CasRef, ThreadId } from "@united-workforce/protocol";
import { cmdStepList, cmdStepShow } from "../commands/step.js";
import {
@@ -62,7 +61,7 @@ async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
return createUwfStore(storageRoot);
}
async function registerDetailSchemas(store: ReturnType<typeof createFsStore>) {
async function registerDetailSchemas(store: Store) {
await bootstrap(store);
const [turn, detail] = await Promise.all([
putSchema(store, TURN_SCHEMA),
@@ -90,21 +89,21 @@ describe("extractLastAssistantContent", () => {
const uwf = await makeUwfStore(tmpDir);
const schemas = await registerDetailSchemas(uwf.store);
const turn1 = await uwf.store.put(schemas.turn, {
const turn1 = await uwf.store.cas.put(schemas.turn, {
index: 0,
role: "assistant",
content: "intermediate",
toolCalls: null,
reasoning: null,
});
const turn2 = await uwf.store.put(schemas.turn, {
const turn2 = await uwf.store.cas.put(schemas.turn, {
index: 1,
role: "tool",
content: "ok",
toolCalls: null,
reasoning: null,
});
const turn3 = await uwf.store.put(schemas.turn, {
const turn3 = await uwf.store.cas.put(schemas.turn, {
index: 2,
role: "assistant",
content: "final answer",
@@ -112,7 +111,7 @@ describe("extractLastAssistantContent", () => {
reasoning: null,
});
const detailHash = await uwf.store.put(schemas.detail, {
const detailHash = await uwf.store.cas.put(schemas.detail, {
sessionId: "s1",
model: "m1",
duration: 1000,
@@ -132,7 +131,7 @@ describe("extractLastAssistantContent", () => {
const uwf = await makeUwfStore(tmpDir);
const schemas = await registerDetailSchemas(uwf.store);
const detailHash = await uwf.store.put(schemas.detail, {
const detailHash = await uwf.store.cas.put(schemas.detail, {
sessionId: "s2",
model: "m2",
duration: 0,
@@ -147,7 +146,7 @@ describe("extractLastAssistantContent", () => {
const uwf = await makeUwfStore(tmpDir);
const schemas = await registerDetailSchemas(uwf.store);
const turn1 = await uwf.store.put(schemas.turn, {
const turn1 = await uwf.store.cas.put(schemas.turn, {
index: 0,
role: "assistant",
content: "",
@@ -155,7 +154,7 @@ describe("extractLastAssistantContent", () => {
reasoning: null,
});
const detailHash = await uwf.store.put(schemas.detail, {
const detailHash = await uwf.store.cas.put(schemas.detail, {
sessionId: "s3",
model: "m3",
duration: 0,
@@ -170,14 +169,14 @@ describe("extractLastAssistantContent", () => {
const uwf = await makeUwfStore(tmpDir);
const schemas = await registerDetailSchemas(uwf.store);
const turn1 = await uwf.store.put(schemas.turn, {
const turn1 = await uwf.store.cas.put(schemas.turn, {
index: 0,
role: "assistant",
content: "real content",
toolCalls: null,
reasoning: null,
});
const turn2 = await uwf.store.put(schemas.turn, {
const turn2 = await uwf.store.cas.put(schemas.turn, {
index: 1,
role: "assistant",
content: " ",
@@ -185,7 +184,7 @@ describe("extractLastAssistantContent", () => {
reasoning: null,
});
const detailHash = await uwf.store.put(schemas.detail, {
const detailHash = await uwf.store.cas.put(schemas.detail, {
sessionId: "s4",
model: "m4",
duration: 0,
@@ -204,7 +203,7 @@ describe("cmdThreadRead <output> section", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf",
description: "desc",
roles: {
@@ -221,12 +220,12 @@ describe("cmdThreadRead <output> section", () => {
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Write something",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -234,14 +233,14 @@ describe("cmdThreadRead <output> section", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "The assistant response text",
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sx",
model: "mx",
duration: 500,
@@ -249,7 +248,7 @@ describe("cmdThreadRead <output> section", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "writer",
@@ -272,18 +271,18 @@ describe("cmdThreadRead <output> section", () => {
test("omits <output> tags when detail has no matching assistant turns", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf2",
description: "desc",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Do stuff",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -294,7 +293,7 @@ describe("cmdThreadRead <output> section", () => {
// A detail ref that doesn't exist in the store → extractLastAssistantContent returns null
const missingDetailRef = "missingdetail0" as CasRef;
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "worker",
@@ -321,18 +320,18 @@ describe("cmdStepShow", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "wf",
description: "",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "p",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -340,14 +339,14 @@ describe("cmdStepShow", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "done",
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sess42",
model: "gpt-4o",
duration: 3000,
@@ -355,7 +354,7 @@ describe("cmdStepShow", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "coder",
@@ -400,18 +399,18 @@ describe("cmdThreadRead <prompt> deduplication", () => {
meta: "placeholder00" as CasRef,
};
}
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "dedup-wf",
description: "desc",
roles: roleMap,
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Start",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -422,7 +421,7 @@ describe("cmdThreadRead <prompt> deduplication", () => {
let prev: string | null = null;
let stepHash = "";
for (const role of roles) {
stepHash = await uwf.store.put(uwf.schemas.stepNode, {
stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: prev as CasRef | null,
role,
@@ -477,7 +476,7 @@ describe("cmdThreadRead start section / before / quota", () => {
roles: string[],
): Promise<{ startHash: CasRef; stepHashes: CasRef[] }> {
const uniqueRoles = [...new Set(roles)];
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "simple-wf",
description: "desc",
roles: Object.fromEntries(
@@ -496,11 +495,11 @@ describe("cmdThreadRead start section / before / quota", () => {
conditions: {},
graph: {},
});
const startHash = (await uwf.store.put(uwf.schemas.startNode, {
const startHash = (await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Initial prompt",
})) as CasRef;
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -511,7 +510,7 @@ describe("cmdThreadRead start section / before / quota", () => {
const stepHashes: CasRef[] = [];
let prev: CasRef | null = null;
for (const role of roles) {
const stepHash = (await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = (await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev,
role,
@@ -593,7 +592,7 @@ describe("cmdStepShow (process.exit tests - must be last)", () => {
test("before with unknown hash rejects", async () => {
const uwfStore = await makeUwfStore(tmpDir);
const workflowHash = await uwfStore.store.put(uwfStore.schemas.workflow, {
const workflowHash = await uwfStore.store.cas.put(uwfStore.schemas.workflow, {
name: "wf2",
description: "",
roles: {
@@ -609,18 +608,18 @@ describe("cmdStepShow (process.exit tests - must be last)", () => {
conditions: {},
graph: {},
});
const startHash = await uwfStore.store.put(uwfStore.schemas.startNode, {
const startHash = await uwfStore.store.cas.put(uwfStore.schemas.startNode, {
workflow: workflowHash,
prompt: "p",
});
const outputHash = await uwfStore.store.put(uwfStore.schemas.workflow, {
const outputHash = await uwfStore.store.cas.put(uwfStore.schemas.workflow, {
name: "out",
description: "",
roles: {},
conditions: {},
graph: {},
});
const stepHash = await uwfStore.store.put(uwfStore.schemas.stepNode, {
const stepHash = await uwfStore.store.cas.put(uwfStore.schemas.stepNode, {
start: startHash,
prev: null,
role: "roleA",
@@ -648,18 +647,18 @@ describe("cmdStepList with completed threads", () => {
test("lists steps from active thread", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf-active",
description: "desc",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Start prompt",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -667,7 +666,7 @@ describe("cmdStepList with completed threads", () => {
graph: {},
});
const step1Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step1Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "role1",
@@ -675,7 +674,7 @@ describe("cmdStepList with completed threads", () => {
detail: null,
agent: "uwf-test",
});
const step2Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step2Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step1Hash,
role: "role2",
@@ -683,7 +682,7 @@ describe("cmdStepList with completed threads", () => {
detail: null,
agent: "uwf-test",
});
const step3Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step3Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step2Hash,
role: "role3",
@@ -707,18 +706,18 @@ describe("cmdStepList with completed threads", () => {
test("lists steps from completed thread", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf-completed",
description: "desc",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Start prompt",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -726,7 +725,7 @@ describe("cmdStepList with completed threads", () => {
graph: {},
});
const step1Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step1Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "roleA",
@@ -734,7 +733,7 @@ describe("cmdStepList with completed threads", () => {
detail: null,
agent: "uwf-test",
});
const step2Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step2Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step1Hash,
role: "roleB",
@@ -768,18 +767,18 @@ describe("cmdStepShow with completed threads", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf-step-active",
description: "desc",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "p",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -787,14 +786,14 @@ describe("cmdStepShow with completed threads", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "Active thread response",
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sess-active",
model: "model-x",
duration: 1234,
@@ -802,7 +801,7 @@ describe("cmdStepShow with completed threads", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "coder",
@@ -828,18 +827,18 @@ describe("cmdStepShow with completed threads", () => {
const uwf = await makeUwfStore(tmpDir);
const detailSchemas = await registerDetailSchemas(uwf.store);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf-step-completed",
description: "desc",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "p",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -847,14 +846,14 @@ describe("cmdStepShow with completed threads", () => {
graph: {},
});
const turnHash = await uwf.store.put(detailSchemas.turn, {
const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
index: 0,
role: "assistant",
content: "Completed thread response",
toolCalls: null,
reasoning: null,
});
const detailHash = await uwf.store.put(detailSchemas.detail, {
const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
sessionId: "sess-completed",
model: "model-y",
duration: 5678,
@@ -862,7 +861,7 @@ describe("cmdStepShow with completed threads", () => {
turns: [turnHash],
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "reviewer",
@@ -897,7 +896,7 @@ describe("cmdThreadRead with completed threads", () => {
test("reads completed thread context", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf-read-completed",
description: "desc",
roles: {
@@ -913,11 +912,11 @@ describe("cmdThreadRead with completed threads", () => {
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Write something",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -925,7 +924,7 @@ describe("cmdThreadRead with completed threads", () => {
graph: {},
});
const stepHash = await uwf.store.put(uwf.schemas.stepNode, {
const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "writer",
@@ -954,18 +953,18 @@ describe("cmdThreadRead with completed threads", () => {
test("reads completed thread with before filter", async () => {
const uwf = await makeUwfStore(tmpDir);
const workflowHash = await uwf.store.put(uwf.schemas.workflow, {
const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "test-wf-read-before",
description: "desc",
roles: {},
conditions: {},
graph: {},
});
const startHash = await uwf.store.put(uwf.schemas.startNode, {
const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
workflow: workflowHash,
prompt: "Do task",
});
const outputHash = await uwf.store.put(uwf.schemas.workflow, {
const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
name: "out",
description: "",
roles: {},
@@ -973,7 +972,7 @@ describe("cmdThreadRead with completed threads", () => {
graph: {},
});
const step1Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step1Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: null,
role: "roleX",
@@ -981,7 +980,7 @@ describe("cmdThreadRead with completed threads", () => {
detail: null,
agent: "uwf-test",
});
const step2Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step2Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step1Hash,
role: "roleY",
@@ -989,7 +988,7 @@ describe("cmdThreadRead with completed threads", () => {
detail: null,
agent: "uwf-test",
});
const step3Hash = await uwf.store.put(uwf.schemas.stepNode, {
const step3Hash = await uwf.store.cas.put(uwf.schemas.stepNode, {
start: startHash,
prev: step2Hash,
role: "roleZ",
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import type { WorkflowPayload } from "@united-workforce/protocol";
import { validateWorkflow } from "../validate-semantic.js";
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -46,7 +46,7 @@ function makeMinimalPayload(name: string, description: string): WorkflowPayload
async function storeWorkflow(uwf: UwfStore, name: string): Promise<CasRef> {
const payload = makeMinimalPayload(name, "Test workflow");
return await uwf.store.put(uwf.schemas.workflow, payload);
return await uwf.store.cas.put(uwf.schemas.workflow, payload);
}
async function createWorkflowYaml(name: string, version: string | null = null): Promise<string> {
@@ -124,7 +124,7 @@ describe("Strategy 2: File Path Resolution", () => {
expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
const uwf = await makeUwfStore(storageRoot);
const node = uwf.store.get(result.workflow);
const node = uwf.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).name).toBe("test-workflow");
@@ -187,7 +187,7 @@ describe("Strategy 3: Local Discovery", () => {
expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
const uwf = await makeUwfStore(storageRoot);
const node = uwf.store.get(result.workflow);
const node = uwf.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).name).toBe("solve-issue");
@@ -235,7 +235,7 @@ describe("Strategy 3: Local Discovery", () => {
const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", projectRoot);
const uwf = await makeUwfStore(storageRoot);
const node = uwf.store.get(result.workflow);
const node = uwf.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).description).toBe("Test workflow (1)");
@@ -263,7 +263,7 @@ describe("Strategy 3: Local Discovery", () => {
expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
const uwf = await makeUwfStore(storageRoot);
const node = uwf.store.get(result.workflow);
const node = uwf.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).name).toBe("solve-issue");
@@ -289,7 +289,7 @@ describe("Strategy 3: Local Discovery", () => {
const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", projectRoot);
const uwf = await makeUwfStore(storageRoot);
const node = uwf.store.get(result.workflow);
const node = uwf.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).description).toBe("Test workflow (flat)");
@@ -341,7 +341,7 @@ describe("Resolution Priority", () => {
const result = await cmdThreadStart(storageRoot, explicitPath, "prompt", projectRoot);
const uwf = await makeUwfStore(storageRoot);
const node = uwf.store.get(result.workflow);
const node = uwf.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).description).toBe("Test workflow (explicit)");
@@ -364,7 +364,7 @@ describe("Resolution Priority", () => {
const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", projectRoot);
const uwf2 = await makeUwfStore(storageRoot);
const node = uwf2.store.get(result.workflow);
const node = uwf2.store.cas.get(result.workflow);
expect(node).not.toBeNull();
if (node !== null) {
expect((node.payload as WorkflowPayload).description).toBe("Test workflow (local)");
+4 -6
View File
@@ -175,13 +175,11 @@ export async function _discoverAgents(): Promise<string[]> {
async function _tryWhichDiscovery(): Promise<string[] | null> {
try {
const proc = Bun.spawn(["which", "-a", "uwf-hermes", "uwf-claude-code", "uwf-cursor"], {
stdout: "pipe",
stderr: "pipe",
const { execFileSync } = await import("node:child_process");
const text = execFileSync("which", ["-a", "uwf-hermes", "uwf-claude-code", "uwf-cursor"], {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
});
const text = await new Response(proc.stdout).text();
await proc.exited;
if (proc.exitCode !== 0) return null;
return _parseWhichOutput(text);
} catch {
return null;
+6 -6
View File
@@ -27,7 +27,7 @@ function fail(message: string): never {
}
function walkChain(uwf: UwfStore, headHash: CasRef): ChainState {
const headNode = uwf.store.get(headHash);
const headNode = uwf.store.cas.get(headHash);
if (headNode === null) {
fail(`CAS node not found: ${headHash}`);
}
@@ -49,7 +49,7 @@ function walkChain(uwf: UwfStore, headHash: CasRef): ChainState {
let hash: CasRef | null = headHash;
while (hash !== null) {
const node = uwf.store.get(hash);
const node = uwf.store.cas.get(hash);
if (node === null) {
fail(`CAS node not found while walking chain: ${hash}`);
}
@@ -66,7 +66,7 @@ function walkChain(uwf: UwfStore, headHash: CasRef): ChainState {
fail(`empty step chain at head ${headHash}`);
}
const startNode = uwf.store.get(newest.start);
const startNode = uwf.store.cas.get(newest.start);
if (startNode === null || startNode.type !== uwf.schemas.startNode) {
fail(`StartNode not found: ${newest.start}`);
}
@@ -80,7 +80,7 @@ function walkChain(uwf: UwfStore, headHash: CasRef): ChainState {
}
function expandOutput(uwf: UwfStore, outputRef: CasRef): unknown {
const node = uwf.store.get(outputRef);
const node = uwf.store.cas.get(outputRef);
if (node === null) {
return {};
}
@@ -96,7 +96,7 @@ function expandDeep(store: CasStore, hash: CasRef, visited?: Set<string>): unkno
if (seen.has(hash)) return hash; // cycle guard
seen.add(hash);
const node = store.get(hash);
const node = store.cas.get(hash);
if (node === null) return hash;
const schema = getSchema(store, node.type);
@@ -177,7 +177,7 @@ function collectOrderedSteps(
let hash: CasRef | null = headHash;
const hashToNode = new Map<string, { payload: StepNodePayload; timestamp: number }>();
while (hash !== null) {
const node = uwf.store.get(hash);
const node = uwf.store.cas.get(hash);
if (node === null || node.type !== uwf.schemas.stepNode) {
break;
}
+11 -11
View File
@@ -1,4 +1,4 @@
import type { BootstrapCapableStore } from "@ocas/core";
import type { CasStore } from "@ocas/core";
import type {
CasRef,
StartEntry,
@@ -42,7 +42,7 @@ export async function cmdStepList(
const uwf = await createUwfStore(storageRoot);
const chain = walkChain(uwf, headHash);
const startNode = uwf.store.get(chain.startHash);
const startNode = uwf.store.cas.get(chain.startHash);
if (startNode === null) {
fail(`StartNode not found: ${chain.startHash}`);
}
@@ -81,7 +81,7 @@ export async function cmdStepList(
*/
export async function cmdStepShow(storageRoot: string, stepHash: CasRef): Promise<unknown> {
const uwf = await createUwfStore(storageRoot);
const node = uwf.store.get(stepHash);
const node = uwf.store.cas.get(stepHash);
if (node === null) {
fail(`CAS node not found: ${stepHash}`);
}
@@ -103,7 +103,7 @@ export async function cmdStepFork(
stepHash: CasRef,
): Promise<ThreadForkOutput> {
const uwf = await createUwfStore(storageRoot);
const node = uwf.store.get(stepHash);
const node = uwf.store.cas.get(stepHash);
if (node === null) {
fail(`CAS node not found: ${stepHash}`);
}
@@ -129,7 +129,7 @@ export async function cmdStepFork(
/**
* Load and validate step detail node from CAS store
*/
function loadStepDetail(store: BootstrapCapableStore, detailRef: CasRef): Record<string, unknown> {
function loadStepDetail(store: CasStore, detailRef: CasRef): Record<string, unknown> {
const detailNode = store.get(detailRef);
if (detailNode === null) {
fail(`detail node not found: ${detailRef}`);
@@ -178,7 +178,7 @@ function formatTurnBody(turn: TurnData): string {
}
function parseSingleTurn(
store: BootstrapCapableStore,
store: CasStore,
turnRef: unknown,
fallbackIndex: number,
): TurnData | null {
@@ -206,7 +206,7 @@ function parseSingleTurn(
/**
* Load all turn nodes from CAS store and extract display fields
*/
function loadTurnData(store: BootstrapCapableStore, turns: unknown): TurnData[] {
function loadTurnData(store: CasStore, turns: unknown): TurnData[] {
if (!Array.isArray(turns) || turns.length === 0) {
return [];
}
@@ -294,7 +294,7 @@ export async function cmdStepRead(
showPrompt: boolean,
): Promise<string> {
const uwf = await createUwfStore(storageRoot);
const node = uwf.store.get(stepHash);
const node = uwf.store.cas.get(stepHash);
if (node === null) {
fail(`CAS node not found: ${stepHash}`);
}
@@ -309,7 +309,7 @@ export async function cmdStepRead(
if (typeof promptRef !== "string") {
return `# Step ${stepHash}\n\n_Prompt not recorded (legacy step)._`;
}
const promptNode = uwf.store.get(promptRef as CasRef);
const promptNode = uwf.store.cas.get(promptRef as CasRef);
if (promptNode === null) {
return `# Step ${stepHash}\n\n_Prompt CAS node not found: ${promptRef}_`;
}
@@ -324,8 +324,8 @@ export async function cmdStepRead(
return formatStepMarkdown(stepHash, payload.role, payload.agent, [], []);
}
const detail = loadStepDetail(uwf.store, payload.detail);
const turnData = loadTurnData(uwf.store, detail.turns);
const detail = loadStepDetail(uwf.store.cas, payload.detail);
const turnData = loadTurnData(uwf.store.cas, detail.turns);
if (turnData.length === 0) {
return formatStepMarkdown(stepHash, payload.role, payload.agent, [], []);
+15 -15
View File
@@ -1,7 +1,7 @@
import { execFileSync, spawn } from "node:child_process";
import { access, readFile } from "node:fs/promises";
import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
import type { VariableStore } from "@ocas/core";
import type { VarStore } from "@ocas/core";
import { validate } from "@ocas/core";
import type {
AgentAlias,
@@ -139,7 +139,7 @@ function resolveSuspendFieldsForShow(
}
async function ensureThreadSuspendMetadata(
varStore: VariableStore,
varStore: VarStore,
threadId: ThreadId,
entry: ThreadIndexEntry,
suspendedRole: string,
@@ -352,8 +352,8 @@ async function materializeLocalWorkflow(uwf: UwfStore, filePath: string): Promis
}
const materialized = await materializeWorkflowPayload(uwf, payload);
const hash = await uwf.store.put(uwf.schemas.workflow, materialized);
const stored = uwf.store.get(hash);
const hash = await uwf.store.cas.put(uwf.schemas.workflow, materialized);
const stored = uwf.store.cas.get(hash);
if (stored === null || !validate(uwf.store, stored)) {
fail("stored local workflow failed schema validation");
}
@@ -374,7 +374,7 @@ async function resolveWorkflowCasRef(
// Strategy 1: Direct CAS hash
if (isCasRef(trimmed)) {
const node = uwf.store.get(trimmed);
const node = uwf.store.cas.get(trimmed);
if (node === null) {
fail(`CAS node not found: ${trimmed}`);
}
@@ -402,7 +402,7 @@ async function resolveWorkflowCasRef(
if (!isCasRef(hash)) {
fail(`workflow not found: ${trimmed}`);
}
const node = uwf.store.get(hash);
const node = uwf.store.cas.get(hash);
if (node === null) {
fail(`CAS node not found: ${hash}`);
}
@@ -413,7 +413,7 @@ async function resolveWorkflowCasRef(
}
function resolveWorkflowFromHead(uwf: UwfStore, head: CasRef): CasRef | null {
const node = uwf.store.get(head);
const node = uwf.store.cas.get(head);
if (node === null) {
return null;
}
@@ -428,7 +428,7 @@ function resolveWorkflowFromHead(uwf: UwfStore, head: CasRef): CasRef | null {
return null;
}
const startNode = uwf.store.get(payload.start);
const startNode = uwf.store.cas.get(payload.start);
if (startNode === null || startNode.type !== uwf.schemas.startNode) {
return null;
}
@@ -462,8 +462,8 @@ export async function cmdThreadStart(
cwd,
};
const headHash = await uwf.store.put(uwf.schemas.startNode, startPayload);
const node = uwf.store.get(headHash);
const headHash = await uwf.store.cas.put(uwf.schemas.startNode, startPayload);
const node = uwf.store.cas.get(headHash);
if (node === null || !validate(uwf.store, node)) {
fail("stored StartNode failed schema validation");
}
@@ -594,7 +594,7 @@ async function collectActiveThreads(
}
function collectCompletedThreads(
varStore: VariableStore,
varStore: VarStore,
activeIds: Set<ThreadId>,
): ThreadListItemWithStatus[] {
const items: ThreadListItemWithStatus[] = [];
@@ -691,7 +691,7 @@ export async function cmdThreadList(
}
export function extractLastAssistantContent(uwf: UwfStore, detailRef: CasRef): string | null {
const detailNode = uwf.store.get(detailRef);
const detailNode = uwf.store.cas.get(detailRef);
if (detailNode === null) {
return null;
}
@@ -705,7 +705,7 @@ export function extractLastAssistantContent(uwf: UwfStore, detailRef: CasRef): s
if (typeof turnRef !== "string") {
continue;
}
const turnNode = uwf.store.get(turnRef as CasRef);
const turnNode = uwf.store.cas.get(turnRef as CasRef);
if (turnNode === null) {
continue;
}
@@ -940,7 +940,7 @@ function resolveEvaluateArgs(
}
function loadWorkflowPayload(uwf: UwfStore, workflowRef: CasRef): WorkflowPayload {
const node = uwf.store.get(workflowRef);
const node = uwf.store.cas.get(workflowRef);
if (node === null) {
fail(`workflow CAS node not found: ${workflowRef}`);
}
@@ -1436,7 +1436,7 @@ async function cmdThreadStepOnce(
plog.log(PL_AGENT_DONE, `agent returned head=${newHead}`, null);
const uwfAfter = await createUwfStore(storageRoot);
const newNode = uwfAfter.store.get(newHead);
const newNode = uwfAfter.store.cas.get(newHead);
if (newNode === null || newNode.type !== uwfAfter.schemas.stepNode) {
failStep(plog, `agent returned hash that is not a StepNode: ${newHead}`);
}
+3 -3
View File
@@ -150,8 +150,8 @@ export async function cmdWorkflowAdd(
const uwf = await createUwfStore(storageRoot);
const materialized = await materializeWorkflowPayload(uwf, payload);
const hash = await uwf.store.put(uwf.schemas.workflow, materialized);
const node = uwf.store.get(hash);
const hash = await uwf.store.cas.put(uwf.schemas.workflow, materialized);
const node = uwf.store.cas.get(hash);
if (node === null || !validate(uwf.store, node)) {
fail("stored workflow failed schema validation");
}
@@ -169,7 +169,7 @@ export async function cmdWorkflowShow(
const registry = loadWorkflowRegistry(uwf.varStore);
const hash = resolveWorkflowHash(registry, id);
const node = uwf.store.get(hash);
const node = uwf.store.cas.get(hash);
if (node === null) {
fail(`CAS node not found: ${hash}`);
}
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { evaluate } from "../evaluate.js";
import { isSuspendResult } from "../types.js";
+20 -19
View File
@@ -4,9 +4,8 @@ import { access, mkdir, readdir, readFile, rename } from "node:fs/promises";
import { homedir } from "node:os";
import { join } from "node:path";
import type { BootstrapCapableStore, Hash } from "@ocas/core";
import { createVariableStore, type VariableStore } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { bootstrap, type Hash, type Store, type VarStore } from "@ocas/core";
import { createFsStore, createSqliteVarStore } from "@ocas/fs";
import type {
CasRef,
ThreadId,
@@ -168,17 +167,19 @@ export type ThreadHistoryLine = ThreadListItem & {
export type UwfStore = {
storageRoot: string;
store: BootstrapCapableStore;
store: Store;
schemas: UwfSchemaHashes;
varStore: VariableStore;
varStore: VarStore;
};
export async function createUwfStore(storageRoot: string): Promise<UwfStore> {
const casDir = getGlobalCasDir();
await mkdir(casDir, { recursive: true });
const store = createFsStore(casDir);
const cas = createFsStore(casDir);
const { var: varStore, tag } = createSqliteVarStore(join(casDir, "vars"), cas);
const store: Store = { cas, var: varStore, tag };
bootstrap(store);
const schemas = await registerUwfSchemas(store);
const varStore = createVariableStore(join(casDir, "variables.db"), store);
await migrateWorkflowRegistryIfNeeded(storageRoot, varStore);
await migrateThreadsIndexIfNeeded(storageRoot, varStore);
await migrateHistoryIfNeeded(storageRoot, varStore);
@@ -204,7 +205,7 @@ async function loadWorkflowRegistryFromYaml(storageRoot: string): Promise<Workfl
/** One-time migration: `~/.uwf/workflows.yaml` → `@uwf/registry/*` variables. */
export async function migrateWorkflowRegistryIfNeeded(
storageRoot: string,
varStore: VariableStore,
varStore: VarStore,
): Promise<void> {
const path = getRegistryPath(storageRoot);
if (!existsSync(path)) {
@@ -219,7 +220,7 @@ export async function migrateWorkflowRegistryIfNeeded(
await rename(path, `${path}.migrated`);
}
export function loadWorkflowRegistry(varStore: VariableStore): WorkflowRegistry {
export function loadWorkflowRegistry(varStore: VarStore): WorkflowRegistry {
const vars = varStore.list({ namePrefix: REGISTRY_VAR_PREFIX });
const registry: WorkflowRegistry = {};
for (const v of vars) {
@@ -229,7 +230,7 @@ export function loadWorkflowRegistry(varStore: VariableStore): WorkflowRegistry
return registry;
}
export function saveWorkflowRegistry(varStore: VariableStore, name: string, hash: CasRef): void {
export function saveWorkflowRegistry(varStore: VarStore, name: string, hash: CasRef): void {
varStore.set(`${REGISTRY_VAR_PREFIX}${name}`, hash);
}
@@ -280,7 +281,7 @@ async function loadThreadsIndexFromYaml(storageRoot: string): Promise<ThreadsInd
/** One-time migration: `~/.uwf/threads.yaml` → `@uwf/thread/*` variables. */
export async function migrateThreadsIndexIfNeeded(
storageRoot: string,
varStore: VariableStore,
varStore: VarStore,
): Promise<void> {
const path = getThreadsPath(storageRoot);
if (!existsSync(path)) {
@@ -308,7 +309,7 @@ function entryFromVariable(v: { value: string; tags: Record<string, string> }):
}
/** Load all active threads (equivalent to legacy `loadThreadsIndex`). */
export function loadAllThreads(varStore: VariableStore): ThreadsIndex {
export function loadAllThreads(varStore: VarStore): ThreadsIndex {
const vars = varStore.list({ namePrefix: THREAD_VAR_PREFIX });
const index: ThreadsIndex = {};
for (const v of vars) {
@@ -319,7 +320,7 @@ export function loadAllThreads(varStore: VariableStore): ThreadsIndex {
}
/** Get a single active thread entry, or null if not found. */
export function getThread(varStore: VariableStore, threadId: ThreadId): ThreadIndexEntry | null {
export function getThread(varStore: VarStore, threadId: ThreadId): ThreadIndexEntry | null {
const vars = varStore.list({ exactName: threadVarName(threadId) });
const v = vars[0];
if (v === undefined) {
@@ -330,7 +331,7 @@ export function getThread(varStore: VariableStore, threadId: ThreadId): ThreadIn
/** Set or update a single active thread entry. */
export function setThread(
varStore: VariableStore,
varStore: VarStore,
threadId: ThreadId,
entry: ThreadIndexEntry,
): void {
@@ -348,7 +349,7 @@ export function setThread(
}
/** Remove an active thread entry (on complete/cancel). */
export function deleteThread(varStore: VariableStore, threadId: ThreadId): void {
export function deleteThread(varStore: VarStore, threadId: ThreadId): void {
varStore.remove(threadVarName(threadId));
}
@@ -389,7 +390,7 @@ function parseHistoryJsonlLine(trimmed: string): ThreadHistoryLine | null {
/** One-time migration: `~/.uwf/history.jsonl` → `@uwf/history/*` variables. */
export async function migrateHistoryIfNeeded(
storageRoot: string,
varStore: VariableStore,
varStore: VarStore,
): Promise<void> {
const path = join(storageRoot, "history.jsonl");
if (!existsSync(path)) {
@@ -411,7 +412,7 @@ export async function migrateHistoryIfNeeded(
await rename(path, `${path}.migrated`);
}
export function loadAllHistory(varStore: VariableStore): ThreadHistoryLine[] {
export function loadAllHistory(varStore: VarStore): ThreadHistoryLine[] {
const vars = varStore.list({ namePrefix: HISTORY_VAR_PREFIX });
return vars.map((v) => ({
thread: v.name.slice(HISTORY_VAR_PREFIX.length) as ThreadId,
@@ -423,7 +424,7 @@ export function loadAllHistory(varStore: VariableStore): ThreadHistoryLine[] {
}
export function findHistoryEntry(
varStore: VariableStore,
varStore: VarStore,
threadId: ThreadId,
): ThreadHistoryLine | null {
const vars = varStore.list({ namePrefix: `${HISTORY_VAR_PREFIX}${threadId}` });
@@ -440,7 +441,7 @@ export function findHistoryEntry(
};
}
export function addHistoryEntry(varStore: VariableStore, entry: ThreadHistoryLine): void {
export function addHistoryEntry(varStore: VarStore, entry: ThreadHistoryLine): void {
varStore.set(`${HISTORY_VAR_PREFIX}${entry.thread}`, entry.head, {
tags: {
workflow: entry.workflow,