Merge pull request 'chore: remove Bun, migrate to pnpm + Node + vitest + esbuild' (#29) from chore/26-remove-bun into main
CI / check (push) Failing after 4m34s
CI / check (push) Failing after 4m34s
This commit was merged in pull request #29.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"better-sqlite3@12.10.0": true, "esbuild@0.27.7": true}
|
||||
+11
-13
@@ -1,23 +1,21 @@
|
||||
{
|
||||
"name": "@united-workforce/monorepo",
|
||||
"private": true,
|
||||
"packageManager": "bun@1.3.14",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"uwf": "bun packages/cli/src/cli.ts",
|
||||
"preinstall": "npx only-allow bun",
|
||||
"prepublishOnly": "echo 'Use bun run release instead' && exit 1",
|
||||
"build": "bunx tsc --build",
|
||||
"check": "bunx tsc --build && biome check . && bash scripts/lint-log-tags.sh",
|
||||
"typecheck": "bunx tsc --build",
|
||||
"uwf": "pnpm tsx packages/cli/src/cli.ts",
|
||||
"prepublishOnly": "echo 'Use pnpm run release instead' && exit 1",
|
||||
"build": "npx tsc --build",
|
||||
"check": "npx tsc --build && biome check . && bash scripts/lint-log-tags.sh",
|
||||
"typecheck": "npx tsc --build",
|
||||
"format": "biome format --write .",
|
||||
"test": "bun run --filter './packages/*' test",
|
||||
"test:ci": "bun run --filter './packages/*' test:ci",
|
||||
"changeset": "bunx changeset",
|
||||
"version": "bunx changeset version",
|
||||
"release": "bun run build && bun run test && node scripts/publish-all.mjs"
|
||||
"test": "pnpm -r run test",
|
||||
"test:ci": "pnpm -r run test:ci",
|
||||
"changeset": "npx changeset",
|
||||
"version": "npx changeset version",
|
||||
"release": "pnpm run build && pnpm run test && node scripts/publish-all.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.22.1",
|
||||
@@ -26,8 +24,8 @@
|
||||
"@types/node": "^25.7.0",
|
||||
"@types/xxhashjs": "^0.2.4",
|
||||
"@united-workforce/agent-hermes": "workspace:*",
|
||||
"bun-types": "^1.3.13",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.1",
|
||||
"yaml": "^2.9.0"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { LlmToolCall } from "../src/llm/types.js";
|
||||
|
||||
/** Mirror OpenAI response shape for parser coverage via chatCompletionWithTools integration later. */
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
||||
|
||||
const mockChatCompletionWithTools = mock(async () => ({
|
||||
content: "---\nstatus: done\n---",
|
||||
toolCalls: [],
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
const { mockChatCompletionWithTools, mockAppendSessionTurn, mockExecuteBuiltinTool } = vi.hoisted(() => ({
|
||||
mockChatCompletionWithTools: vi.fn(async () => ({
|
||||
content: "---\nstatus: done\n---",
|
||||
toolCalls: [],
|
||||
})),
|
||||
mockAppendSessionTurn: vi.fn(async () => {}),
|
||||
mockExecuteBuiltinTool: vi.fn(async () => "tool-result"),
|
||||
}));
|
||||
const mockAppendSessionTurn = mock(async () => {});
|
||||
const mockExecuteBuiltinTool = mock(async () => "tool-result");
|
||||
|
||||
mock.module("../src/llm/index.js", () => ({
|
||||
vi.mock("../src/llm/index.js", () => ({
|
||||
chatCompletionWithTools: mockChatCompletionWithTools,
|
||||
}));
|
||||
mock.module("../src/session.js", () => ({
|
||||
vi.mock("../src/session.js", () => ({
|
||||
appendSessionTurn: mockAppendSessionTurn,
|
||||
}));
|
||||
mock.module("../src/tools/index.js", () => ({
|
||||
vi.mock("../src/tools/index.js", () => ({
|
||||
builtinToolsToOpenAi: () => [],
|
||||
executeBuiltinTool: mockExecuteBuiltinTool,
|
||||
getBuiltinTools: () => [],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { resolve } from "node:path";
|
||||
import { resolvePath } from "../src/tools/path.js";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { AgentContext } from "@united-workforce/util-agent";
|
||||
|
||||
import { buildBuiltinMessages } from "../src/prompt.js";
|
||||
|
||||
@@ -12,20 +12,19 @@
|
||||
},
|
||||
"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__/",
|
||||
"test:ci": "bun test __tests__/"
|
||||
"prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1",
|
||||
"test": "vitest run __tests__/",
|
||||
"test:ci": "vitest run __tests__/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ocas/core": "^0.1.1",
|
||||
"@united-workforce/util-agent": "workspace:^",
|
||||
"@united-workforce/util": "workspace:^"
|
||||
"@ocas/core": "^0.2.2",
|
||||
"@united-workforce/util": "workspace:^",
|
||||
"@united-workforce/util-agent": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createBuiltinAgent } from "./agent.js";
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function storeBuiltinDetail(
|
||||
|
||||
const turnHashes: string[] = [];
|
||||
for (const turn of turns) {
|
||||
const hash = await store.put(schemas.turn, turn);
|
||||
const hash = await store.cas.put(schemas.turn, turn);
|
||||
turnHashes.push(hash);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ export async function storeBuiltinDetail(
|
||||
turnCount: turnHashes.length,
|
||||
turns: turnHashes,
|
||||
};
|
||||
const detailHash = await store.put(schemas.detail, detail);
|
||||
const detailHash = await store.cas.put(schemas.detail, detail);
|
||||
return { detailHash, turnCount: turnHashes.length };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { ThreadId } from "@united-workforce/protocol";
|
||||
import type { AgentContext } from "@united-workforce/util-agent";
|
||||
import { buildClaudeCodePrompt } from "../src/claude-code.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { createMemoryStore, walk } from "@ocas/core";
|
||||
import {
|
||||
parseClaudeCodeJsonOutput,
|
||||
@@ -278,7 +278,7 @@ describe("storeClaudeCodeDetail", () => {
|
||||
expect(output).toBe("The answer");
|
||||
expect(sessionId).toBe("abc-123");
|
||||
|
||||
const node = await store.get(detailHash);
|
||||
const node = await store.cas.get(detailHash);
|
||||
expect(node).not.toBeNull();
|
||||
expect(node!.payload.model).toBe("claude-sonnet-4.5");
|
||||
expect(node!.payload.stopReason).toBe("end_turn");
|
||||
@@ -286,7 +286,7 @@ describe("storeClaudeCodeDetail", () => {
|
||||
expect(node!.payload.turns).toHaveLength(2);
|
||||
|
||||
// Verify turn CAS nodes
|
||||
const turn0 = await store.get(node!.payload.turns[0]);
|
||||
const turn0 = await store.cas.get(node!.payload.turns[0]);
|
||||
expect(turn0).not.toBeNull();
|
||||
expect(turn0!.payload.role).toBe("assistant");
|
||||
expect(turn0!.payload.content).toBe("hello");
|
||||
@@ -466,7 +466,7 @@ describe("storeClaudeCodeDetail — incomplete results", () => {
|
||||
expect(output).toBe("Partial output");
|
||||
expect(sessionId).toBe("sess-incomplete");
|
||||
|
||||
const node = await store.get(detailHash);
|
||||
const node = await store.cas.get(detailHash);
|
||||
expect(node).not.toBeNull();
|
||||
expect(node!.payload.subtype).toBe("incomplete");
|
||||
expect(node!.payload.stopReason).toBe("incomplete_no_result_line");
|
||||
@@ -480,7 +480,7 @@ describe("storeClaudeCodeRawOutput", () => {
|
||||
const rawText = "Claude produced plain text without JSON";
|
||||
const hash = await storeClaudeCodeRawOutput(store, rawText);
|
||||
expect(hash).toHaveLength(13);
|
||||
const node = await store.get(hash);
|
||||
const node = await store.cas.get(hash);
|
||||
expect(node).not.toBeNull();
|
||||
expect(node!.payload.text).toBe(rawText);
|
||||
});
|
||||
|
||||
@@ -12,20 +12,19 @@
|
||||
},
|
||||
"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__/",
|
||||
"test:ci": "bun test __tests__/"
|
||||
"prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1",
|
||||
"test": "vitest run __tests__/",
|
||||
"test:ci": "vitest run __tests__/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ocas/core": "^0.1.1",
|
||||
"@united-workforce/util-agent": "workspace:^",
|
||||
"@united-workforce/util": "workspace:^"
|
||||
"@ocas/core": "^0.2.2",
|
||||
"@united-workforce/util": "workspace:^",
|
||||
"@united-workforce/util-agent": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createClaudeCodeAgent } from "./claude-code.js";
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ export async function storeClaudeCodeDetail(
|
||||
// Store each turn as an individual CAS node
|
||||
const turnHashes: string[] = [];
|
||||
for (const turn of parsed.turns) {
|
||||
const hash = await store.put(schemas.turn, turn);
|
||||
const hash = await store.cas.put(schemas.turn, turn);
|
||||
turnHashes.push(hash);
|
||||
}
|
||||
|
||||
@@ -306,12 +306,12 @@ export async function storeClaudeCodeDetail(
|
||||
turns: turnHashes,
|
||||
};
|
||||
|
||||
const detailHash = await store.put(schemas.detail, detail);
|
||||
const detailHash = await store.cas.put(schemas.detail, detail);
|
||||
return { detailHash, output: parsed.result, sessionId: parsed.sessionId };
|
||||
}
|
||||
|
||||
/** Fallback: store raw text output when JSON parsing fails. */
|
||||
export async function storeClaudeCodeRawOutput(store: Store, rawOutput: string): Promise<string> {
|
||||
const schemas = await registerSchemas(store);
|
||||
return store.put(schemas.rawOutput, { text: rawOutput });
|
||||
return store.cas.put(schemas.rawOutput, { text: rawOutput });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { HermesAcpClient } from "../src/acp-client.js";
|
||||
|
||||
describe("handleSessionUpdate — text extraction", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { ThreadId } from "@united-workforce/protocol";
|
||||
import type { AgentContext } from "@united-workforce/util-agent";
|
||||
import { buildHermesPrompt } from "../src/hermes.js";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { HermesAcpClient } from "../../src/acp-client.js";
|
||||
|
||||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { afterEach, describe, expect, it } from "bun:test";
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { HermesAcpClient } from "../../src/acp-client.js";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const PKG_ROOT = join(import.meta.dir, "..");
|
||||
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
describe("Issue #551 — bin entry & engines", () => {
|
||||
test("package.json declares bun in engines", () => {
|
||||
test("package.json no longer declares bun in engines", () => {
|
||||
const pkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf-8"));
|
||||
expect(pkg.engines).toBeDefined();
|
||||
expect(pkg.engines.bun).toBeDefined();
|
||||
expect(pkg.engines.bun).toMatch(/^>=?\s*[\d.]+/);
|
||||
expect(pkg.engines?.bun).toBeUndefined();
|
||||
});
|
||||
|
||||
test("bin entry file has bun shebang", () => {
|
||||
test("bin entry file has node shebang", () => {
|
||||
const pkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf-8"));
|
||||
const binPath = pkg.bin["uwf-hermes"];
|
||||
const content = readFileSync(join(PKG_ROOT, binPath), "utf-8");
|
||||
expect(content.startsWith("#!/usr/bin/env bun")).toBe(true);
|
||||
expect(content.startsWith("#!/usr/bin/env node")).toBe(true);
|
||||
});
|
||||
|
||||
test("README.md explains uwf-hermes is an adapter", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import Database from "better-sqlite3";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -106,7 +106,7 @@ describe("storeHermesSessionDetail", () => {
|
||||
|
||||
expect(output).toBe("done");
|
||||
|
||||
const detailNode = store.get(detailHash);
|
||||
const detailNode = store.cas.get(detailHash);
|
||||
expect(detailNode).not.toBeNull();
|
||||
if (detailNode === null) {
|
||||
return;
|
||||
@@ -133,14 +133,16 @@ describe("storeHermesSessionDetail", () => {
|
||||
|
||||
// ── SQLite fallback tests ──────────────────────────────────────────
|
||||
|
||||
function createTestDb(dbPath: string): Database {
|
||||
type TestDb = InstanceType<typeof Database>;
|
||||
|
||||
function createTestDb(dbPath: string): TestDb {
|
||||
const db = new Database(dbPath);
|
||||
db.run(`CREATE TABLE sessions (
|
||||
db.exec(`CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
model TEXT NOT NULL,
|
||||
started_at INTEGER NOT NULL
|
||||
)`);
|
||||
db.run(`CREATE TABLE messages (
|
||||
db.exec(`CREATE TABLE messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
@@ -152,6 +154,27 @@ function createTestDb(dbPath: string): Database {
|
||||
return db;
|
||||
}
|
||||
|
||||
function insertSession(db: TestDb, id: string, model: string, startedAt: number): void {
|
||||
db.prepare("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)").run(
|
||||
id,
|
||||
model,
|
||||
startedAt,
|
||||
);
|
||||
}
|
||||
|
||||
function insertMessage(
|
||||
db: TestDb,
|
||||
sessionId: string,
|
||||
role: string,
|
||||
content: string | null,
|
||||
reasoning: string | null,
|
||||
toolCalls: string | null,
|
||||
): void {
|
||||
db.prepare(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
).run(sessionId, role, content, reasoning, toolCalls);
|
||||
}
|
||||
|
||||
describe("getHermesDbPath", () => {
|
||||
test("returns correct path", () => {
|
||||
const { homedir } = require("node:os");
|
||||
@@ -168,19 +191,9 @@ describe("loadHermesSessionFromDb", () => {
|
||||
|
||||
const sessionId = "test-session-001";
|
||||
const startedAt = 1748099519;
|
||||
db.run("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)", [
|
||||
sessionId,
|
||||
"claude-opus-4.6",
|
||||
startedAt,
|
||||
]);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "user", "hello", null, null],
|
||||
);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "assistant", "hi there", "thinking...", null],
|
||||
);
|
||||
insertSession(db, sessionId, "claude-opus-4.6", startedAt);
|
||||
insertMessage(db, sessionId, "user", "hello", null, null);
|
||||
insertMessage(db, sessionId, "assistant", "hi there", "thinking...", null);
|
||||
db.close();
|
||||
|
||||
const result = await loadHermesSessionFromDb(sessionId, dbPath);
|
||||
@@ -220,18 +233,11 @@ describe("loadHermesSessionFromDb", () => {
|
||||
const db = createTestDb(dbPath);
|
||||
|
||||
const sessionId = "test-tool-calls";
|
||||
db.run("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)", [
|
||||
sessionId,
|
||||
"gpt-4",
|
||||
1748099519,
|
||||
]);
|
||||
insertSession(db, sessionId, "gpt-4", 1748099519);
|
||||
const toolCallsJson = JSON.stringify([
|
||||
{ function: { name: "read_file", arguments: '{"path":"x"}' } },
|
||||
]);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "assistant", "", null, toolCallsJson],
|
||||
);
|
||||
insertMessage(db, sessionId, "assistant", "", null, toolCallsJson);
|
||||
db.close();
|
||||
|
||||
const result = await loadHermesSessionFromDb(sessionId, dbPath);
|
||||
@@ -249,15 +255,8 @@ describe("loadHermesSessionFromDb", () => {
|
||||
const db = createTestDb(dbPath);
|
||||
|
||||
const sessionId = "test-nulls";
|
||||
db.run("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)", [
|
||||
sessionId,
|
||||
"model",
|
||||
1748099519,
|
||||
]);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "assistant", null, null, null],
|
||||
);
|
||||
insertSession(db, sessionId, "model", 1748099519);
|
||||
insertMessage(db, sessionId, "assistant", null, null, null);
|
||||
db.close();
|
||||
|
||||
const result = await loadHermesSessionFromDb(sessionId, dbPath);
|
||||
@@ -276,23 +275,10 @@ describe("loadHermesSessionFromDb", () => {
|
||||
const db = createTestDb(dbPath);
|
||||
|
||||
const sessionId = "test-order";
|
||||
db.run("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)", [
|
||||
sessionId,
|
||||
"model",
|
||||
1748099519,
|
||||
]);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "user", "first", null, null],
|
||||
);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "assistant", "second", null, null],
|
||||
);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "user", "third", null, null],
|
||||
);
|
||||
insertSession(db, sessionId, "model", 1748099519);
|
||||
insertMessage(db, sessionId, "user", "first", null, null);
|
||||
insertMessage(db, sessionId, "assistant", "second", null, null);
|
||||
insertMessage(db, sessionId, "user", "third", null, null);
|
||||
db.close();
|
||||
|
||||
const result = await loadHermesSessionFromDb(sessionId, dbPath);
|
||||
@@ -309,11 +295,7 @@ describe("loadHermesSessionFromDb", () => {
|
||||
|
||||
const sessionId = "test-timestamp";
|
||||
const startedAt = 1748099519;
|
||||
db.run("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)", [
|
||||
sessionId,
|
||||
"model",
|
||||
startedAt,
|
||||
]);
|
||||
insertSession(db, sessionId, "model", startedAt);
|
||||
db.close();
|
||||
|
||||
const result = await loadHermesSessionFromDb(sessionId, dbPath);
|
||||
@@ -333,15 +315,8 @@ describe("loadHermesSession with SQLite fallback", () => {
|
||||
// Create DB with one model value
|
||||
const db = createTestDb(dbPath);
|
||||
const sessionId = "test-priority";
|
||||
db.run("INSERT INTO sessions (id, model, started_at) VALUES (?, ?, ?)", [
|
||||
sessionId,
|
||||
"db-model",
|
||||
1748099519,
|
||||
]);
|
||||
db.run(
|
||||
"INSERT INTO messages (session_id, role, content, reasoning, tool_calls) VALUES (?, ?, ?, ?, ?)",
|
||||
[sessionId, "user", "from db", null, null],
|
||||
);
|
||||
insertSession(db, sessionId, "db-model", 1748099519);
|
||||
insertMessage(db, sessionId, "user", "from db", null, null);
|
||||
db.close();
|
||||
|
||||
// Create JSON file with a different model value
|
||||
|
||||
@@ -12,23 +12,24 @@
|
||||
},
|
||||
"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__/",
|
||||
"test:ci": "bun test __tests__/"
|
||||
"prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1",
|
||||
"test": "vitest run __tests__/",
|
||||
"test:ci": "vitest run __tests__/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ocas/core": "^0.1.1",
|
||||
"@united-workforce/util-agent": "workspace:^",
|
||||
"@ocas/core": "^0.2.2",
|
||||
"@united-workforce/protocol": "workspace:^",
|
||||
"@united-workforce/util": "workspace:^"
|
||||
"@united-workforce/util": "workspace:^",
|
||||
"@united-workforce/util-agent": "workspace:^",
|
||||
"better-sqlite3": "^12.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
@@ -43,8 +44,5 @@
|
||||
"bugs": {
|
||||
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||
},
|
||||
"engines": {
|
||||
"bun": ">= 1.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createHermesAgent } from "./hermes.js";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import Database from "better-sqlite3";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -156,13 +156,13 @@ export function loadHermesSessionFromDb(
|
||||
try {
|
||||
db = new Database(resolvedPath, { readonly: true });
|
||||
const session = db
|
||||
.query("SELECT id, model, started_at FROM sessions WHERE id = ?")
|
||||
.prepare("SELECT id, model, started_at FROM sessions WHERE id = ?")
|
||||
.get(sessionId) as DbSessionRow | null;
|
||||
if (session === null) {
|
||||
return null;
|
||||
}
|
||||
const rows = db
|
||||
.query(
|
||||
.prepare(
|
||||
"SELECT role, content, reasoning, tool_calls FROM messages WHERE session_id = ? ORDER BY id",
|
||||
)
|
||||
.all(sessionId) as DbMessageRow[];
|
||||
@@ -285,7 +285,7 @@ export async function storeHermesSessionDetail(
|
||||
if (turn === null) {
|
||||
continue;
|
||||
}
|
||||
const hash = await store.put(schemas.turn, turn);
|
||||
const hash = await store.cas.put(schemas.turn, turn);
|
||||
turnHashes.push(hash);
|
||||
turnIndex += 1;
|
||||
}
|
||||
@@ -297,12 +297,12 @@ export async function storeHermesSessionDetail(
|
||||
turnCount: turnHashes.length,
|
||||
turns: turnHashes,
|
||||
};
|
||||
const detailHash = await store.put(schemas.detail, detail);
|
||||
const detailHash = await store.cas.put(schemas.detail, detail);
|
||||
const output = extractLastAssistantContent(session.messages);
|
||||
return { detailHash, output };
|
||||
}
|
||||
|
||||
export async function storeHermesRawOutput(store: Store, rawOutput: string): Promise<string> {
|
||||
const schemas = await registerHermesSchemas(store);
|
||||
return store.put(schemas.rawOutput, { text: rawOutput });
|
||||
return store.cas.put(schemas.rawOutput, { text: rawOutput });
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
"uwf": "./dist/cli.js"
|
||||
},
|
||||
"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:^",
|
||||
"@united-workforce/util-agent": "workspace:^",
|
||||
@@ -22,9 +22,9 @@
|
||||
"yaml": "^2.8.4"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "echo 'Use bun run release from repo root' && exit 1",
|
||||
"test": "bun test src/",
|
||||
"test:ci": "bun test src/"
|
||||
"prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1",
|
||||
"test": "vitest run src/",
|
||||
"test:ci": "vitest run src/"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -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,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,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,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.
|
||||
*
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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, [], []);
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun server.ts",
|
||||
"dev": "npx tsx server.ts",
|
||||
"build": "vite build",
|
||||
"test": "bun test src/",
|
||||
"test:ci": "bun test src/"
|
||||
"test": "vitest run src/",
|
||||
"test:ci": "vitest run src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.5.0",
|
||||
@@ -29,12 +29,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@types/bun": "^1.2.14",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^8.0.13"
|
||||
"vite": "^8.0.13",
|
||||
"@types/node": "^25.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Edge, Node } from "@xyflow/react";
|
||||
import { LayoutLR } from "../index.js";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { transIn } from "../trans-in.js";
|
||||
import type { WorkFlowStep } from "../type.js";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { AnyWorkEdge, AnyWorkNode } from "../../type.js";
|
||||
import { validate } from "../validate.js";
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"types": ["bun-types"],
|
||||
"types": ["node"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
|
||||
@@ -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 src/__tests__/",
|
||||
"test:ci": "bun test src/__tests__/"
|
||||
"prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1",
|
||||
"test": "vitest run src/__tests__/",
|
||||
"test:ci": "vitest run src/__tests__/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ocas/core": "^0.1.1",
|
||||
"@ocas/fs": "^0.1.1"
|
||||
"@ocas/core": "^0.2.2",
|
||||
"@ocas/fs": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import {
|
||||
createThreadIndexEntry,
|
||||
markThreadSuspended,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { StartNodePayload, StepRecord, Target } from "../types.js";
|
||||
|
||||
describe("Protocol types for thread/edge location", () => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = await ctx.meta.store.cas.put(ctx.meta.schemas.text, promptText);
|
||||
} catch {
|
||||
assembledPromptHash = null;
|
||||
}
|
||||
}
|
||||
|
||||
const stepHash = await persistStep({
|
||||
ctx,
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { env } from "../src/env.js";
|
||||
|
||||
describe("env", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { AgentFrontmatter } from "../src/index.js";
|
||||
import { parseFrontmatterMarkdown, validateFrontmatter } from "../src/index.js";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test";
|
||||
import { afterEach, describe, expect, test } from 'vitest';
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
"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": {},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { extractUlidTimestamp, generateUlid } from "../ulid.js";
|
||||
|
||||
describe("extractUlidTimestamp", () => {
|
||||
|
||||
Generated
+6019
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
"sourceMap": true,
|
||||
"composite": true,
|
||||
"outDir": "dist",
|
||||
"types": ["bun-types", "node"]
|
||||
"types": ["node"]
|
||||
},
|
||||
"references": [
|
||||
{ "path": "packages/util" },
|
||||
|
||||
Reference in New Issue
Block a user