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,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { createMemoryStore, putSchema } from "@ocas/core";
import { describe, expect, test } from 'vitest';
import { createMemoryStore, putSchema, bootstrap } from "@ocas/core";
import { tryFrontmatterFastPath } from "../src/frontmatter.js";
@@ -18,6 +18,7 @@ const PLANNER_SCHEMA = {
describe("adapter-stdout: A4 retry loop survives JSON output", () => {
test("A4. first extraction fails, second succeeds — final result has correct data", async () => {
const store = createMemoryStore();
bootstrap(store);
const schemaHash = await putSchema(store, PLANNER_SCHEMA);
// Simulate the retry loop from createAgent (run.ts lines 163-173):
@@ -56,6 +57,7 @@ describe("adapter-stdout: A4 retry loop survives JSON output", () => {
test("A4. all retries fail — extraction returns null on every attempt", async () => {
const store = createMemoryStore();
bootstrap(store);
const schemaHash = await putSchema(store, PLANNER_SCHEMA);
const MAX_RETRIES = 2;
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { createMemoryStore, putSchema } from "@ocas/core";
import { describe, expect, test } from 'vitest';
import { createMemoryStore, putSchema, bootstrap } from "@ocas/core";
import { tryFrontmatterFastPath } from "../src/frontmatter.js";
@@ -31,6 +31,7 @@ const FRONTMATTER_SCHEMA = {
describe("adapter-stdout: FrontmatterFastPathResult includes frontmatter", () => {
test("A2. frontmatter field contains the parsed YAML frontmatter object", async () => {
const store = createMemoryStore();
bootstrap(store);
const schemaHash = await putSchema(store, PLANNER_SCHEMA);
const raw = `---\n$status: ready\nplan: abc123\n---\nSome body text`;
@@ -42,6 +43,7 @@ describe("adapter-stdout: FrontmatterFastPathResult includes frontmatter", () =>
test("A3. body field contains the markdown body after frontmatter", async () => {
const store = createMemoryStore();
bootstrap(store);
const schemaHash = await putSchema(store, PLANNER_SCHEMA);
const raw = `---\n$status: ready\nplan: hash123\n---\nHere is the body.\n\nWith multiple paragraphs.`;
@@ -53,6 +55,7 @@ describe("adapter-stdout: FrontmatterFastPathResult includes frontmatter", () =>
test("A1. result contains outputHash as valid CasRef", async () => {
const store = createMemoryStore();
bootstrap(store);
const schemaHash = await putSchema(store, FRONTMATTER_SCHEMA);
const raw = `---\nstatus: done\nnext: null\nconfidence: 0.9\nartifacts: []\nscope: test\n---\nBody`;
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import type { StepContext } from "@united-workforce/protocol";
import { buildContinuationPrompt } from "../src/build-continuation-prompt.js";
@@ -1,5 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import { buildOutputFormatInstruction } from "../src/build-output-format-instruction.js";
const PLANNER_SCHEMA = {
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import type { RoleDefinition } from "@united-workforce/protocol";
import { buildRolePrompt } from "../src/build-role-prompt.js";
@@ -1,5 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
// We need to test buildHistory indirectly through buildContext
// since buildHistory is not exported. For now, we'll test the integration
// through the public API in a separate integration test.
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { createMemoryStore, putSchema } from "@ocas/core";
import { describe, expect, test } from 'vitest';
import { createMemoryStore, putSchema, bootstrap } from "@ocas/core";
import { tryFrontmatterFastPath } from "../src/frontmatter.js";
@@ -48,6 +48,7 @@ const PLANNER_SCHEMA = {
async function makeStoreWithSchema(schema: Record<string, unknown>) {
const store = createMemoryStore();
bootstrap(store);
const schemaHash = await putSchema(store, schema);
return { store, schemaHash };
}
@@ -68,7 +69,7 @@ describe("STANDARD_KEYS contains only status", () => {
const result = await tryFrontmatterFastPath(raw, schemaHash, store);
expect(result).not.toBeNull();
const node = store.get(result!.outputHash);
const node = store.cas.get(result!.outputHash);
expect(node).not.toBeNull();
const payload = node!.payload as Record<string, unknown>;
expect(payload.status).toBe("done");
@@ -106,7 +107,7 @@ describe("tryFrontmatterFastPath — happy path", () => {
const result = await tryFrontmatterFastPath(raw, schemaHash, store);
expect(result).not.toBeNull();
const node = store.get(result!.outputHash);
const node = store.cas.get(result!.outputHash);
expect(node).not.toBeNull();
const payload = node!.payload as Record<string, unknown>;
expect(payload.status).toBe("done");
@@ -126,7 +127,7 @@ describe("tryFrontmatterFastPath — legacy fields ignored", () => {
const result = await tryFrontmatterFastPath(raw, schemaHash, store);
expect(result).not.toBeNull();
const node = store.get(result!.outputHash);
const node = store.cas.get(result!.outputHash);
const payload = node!.payload as Record<string, unknown>;
expect(payload.status).toBe("done");
expect(payload.next).toBeUndefined();
@@ -176,7 +177,7 @@ describe("tryFrontmatterFastPath — role-specific fields", () => {
const result = await tryFrontmatterFastPath(raw, schemaHash, store);
expect(result).not.toBeNull();
const node = store.get(result!.outputHash);
const node = store.cas.get(result!.outputHash);
expect(node).not.toBeNull();
const payload = node!.payload as Record<string, unknown>;
expect(payload).toEqual({ approved: true });
@@ -192,7 +193,7 @@ describe("tryFrontmatterFastPath — role-specific fields", () => {
const result = await tryFrontmatterFastPath(raw, schemaHash, store);
expect(result).not.toBeNull();
const node = store.get(result!.outputHash);
const node = store.cas.get(result!.outputHash);
expect(node).not.toBeNull();
const payload = node!.payload as Record<string, unknown>;
expect(payload.status).toBe("ready");
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from 'vitest';
import type { WorkflowConfig } from "@united-workforce/protocol";
import { resolveExtractModelAlias } from "../src/extract.js";
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import type { ThreadId } from "@united-workforce/protocol";
+5 -6
View File
@@ -9,19 +9,18 @@
"type": "module",
"exports": {
".": {
"bun": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"prepublishOnly": "echo 'Use bun run release from repo root' && exit 1",
"test": "bun test __tests__/ src/__tests__/",
"test:ci": "bun test __tests__/ src/__tests__/"
"prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1",
"test": "vitest run __tests__/ src/__tests__/",
"test:ci": "vitest run __tests__/ src/__tests__/"
},
"dependencies": {
"@ocas/core": "^0.1.1",
"@ocas/fs": "^0.1.1",
"@ocas/core": "^0.2.2",
"@ocas/fs": "^0.2.2",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
"dotenv": "^16.6.1",
@@ -1,5 +1,4 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
describe("parseArgv empty prompt error message", () => {
let stderrOutput: string;
let _exitCode: number | null;
+7 -7
View File
@@ -22,7 +22,7 @@ function fail(message: string): never {
}
function walkChain(store: Store, schemas: AgentStore["schemas"], headHash: CasRef): ChainState {
const headNode = store.get(headHash);
const headNode = store.cas.get(headHash);
if (headNode === null) {
fail(`CAS node not found: ${headHash}`);
}
@@ -44,7 +44,7 @@ function walkChain(store: Store, schemas: AgentStore["schemas"], headHash: CasRe
let hash: CasRef | null = headHash;
while (hash !== null) {
const node = store.get(hash);
const node = store.cas.get(hash);
if (node === null) {
fail(`CAS node not found while walking chain: ${hash}`);
}
@@ -61,7 +61,7 @@ function walkChain(store: Store, schemas: AgentStore["schemas"], headHash: CasRe
fail(`empty step chain at head ${headHash}`);
}
const startNode = store.get(newest.start);
const startNode = store.cas.get(newest.start);
if (startNode === null || startNode.type !== schemas.startNode) {
fail(`StartNode not found: ${newest.start}`);
}
@@ -75,7 +75,7 @@ function walkChain(store: Store, schemas: AgentStore["schemas"], headHash: CasRe
}
function expandOutput(store: Store, outputRef: CasRef): unknown {
const node = store.get(outputRef);
const node = store.cas.get(outputRef);
if (node === null) {
return {};
}
@@ -83,7 +83,7 @@ function expandOutput(store: Store, outputRef: CasRef): unknown {
}
function extractStepContent(store: Store, detailRef: CasRef): string | null {
const detailNode = store.get(detailRef);
const detailNode = store.cas.get(detailRef);
if (detailNode === null) {
return null;
}
@@ -98,7 +98,7 @@ function extractStepContent(store: Store, detailRef: CasRef): string | null {
if (typeof turnRef !== "string") {
continue;
}
const turnNode = store.get(turnRef as CasRef);
const turnNode = store.cas.get(turnRef as CasRef);
if (turnNode === null) {
continue;
}
@@ -139,7 +139,7 @@ async function buildHistory(
}
async function loadWorkflow(store: Store, schemas: AgentStore["schemas"], workflowRef: CasRef) {
const node = store.get(workflowRef);
const node = store.cas.get(workflowRef);
if (node === null) {
fail(`workflow CAS node not found: ${workflowRef}`);
}
+2 -2
View File
@@ -169,8 +169,8 @@ export async function extract(
throw new Error(`failed to parse extracted JSON: ${message}`);
}
const outputHash = await store.put(outputSchema, structured);
const node = store.get(outputHash);
const outputHash = await store.cas.put(outputSchema, structured);
const node = store.cas.get(outputHash);
if (node === null || !validate(store, node)) {
throw new Error("extracted output failed JSON Schema validation");
}
+5 -5
View File
@@ -162,13 +162,13 @@ export async function tryFrontmatterFastPath(
const candidate = buildCandidate(frontmatter, rawFields, schemaFields);
let outputHash: CasRef;
let node: ReturnType<Store["get"]>;
let node: ReturnType<Store["cas"]["get"]>;
try {
outputHash = await store.put(outputSchema, candidate);
node = store.get(outputHash);
} catch {
log("2KMQT7NR", "failed to store frontmatter candidate in CAS");
outputHash = await store.cas.put(outputSchema, candidate);
node = store.cas.get(outputHash);
} catch (e) {
log("2KMQT7NR", `failed to store frontmatter candidate in CAS: ${e}`);
return null;
}
+10 -6
View File
@@ -79,8 +79,8 @@ async function writeStepNode(options: {
cwd: process.cwd(),
assembledPrompt: options.assembledPromptHash,
};
const hash = await options.store.put(options.schemas.stepNode, payload);
const node = options.store.get(hash);
const hash = await options.store.cas.put(options.schemas.stepNode, payload);
const node = options.store.cas.get(hash);
if (node === null || !validate(options.store, node)) {
fail("stored StepNode failed schema validation");
}
@@ -189,10 +189,14 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
// Store the assembled prompt in CAS for later inspection via `step read --prompt`
const promptText = agentResult.assembledPrompt;
const assembledPromptHash =
promptText !== ""
? await ctx.meta.store.put(ctx.meta.schemas.text, promptText).catch(() => null)
: null;
let assembledPromptHash: CasRef | null = null;
if (promptText !== "") {
try {
assembledPromptHash = ctx.meta.store.cas.put(ctx.meta.schemas.text, promptText);
} catch {
assembledPromptHash = null;
}
}
const stepHash = await persistStep({
ctx,
+9 -5
View File
@@ -2,8 +2,8 @@ import { readFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join } from "node:path";
import { createVariableStore, type Store } from "@ocas/core";
import { createFsStore } from "@ocas/fs";
import { bootstrap, type Store } from "@ocas/core";
import { createFsStore, createSqliteVarStore } from "@ocas/fs";
import type {
AgentAlias,
AgentConfig,
@@ -79,8 +79,8 @@ export async function getActiveThreadEntry(
threadId: ThreadId,
): Promise<ThreadIndexEntry | null> {
const casDir = getGlobalCasDir();
const store = createFsStore(casDir);
const varStore = createVariableStore(join(casDir, "variables.db"), store);
const cas = createFsStore(casDir);
const { var: varStore } = createSqliteVarStore(join(casDir, "vars"), cas);
const vars = varStore.list({ exactName: threadVarName(threadId) });
const v = vars[0];
if (v === undefined) {
@@ -100,7 +100,11 @@ export type AgentStore = {
};
export async function createAgentStore(storageRoot: string): Promise<AgentStore> {
const store = createFsStore(getGlobalCasDir());
const casDir = getGlobalCasDir();
const cas = createFsStore(casDir);
const { var: varSub, tag } = createSqliteVarStore(join(casDir, "vars"), cas);
const store: Store = { cas, var: varSub, tag };
bootstrap(store);
const schemas = await registerAgentSchemas(store);
return { storageRoot, store, schemas };
}