Merge pull request 'feat(cli): unify uwf CAS store with global json-cas store' (#575) from fix/573-unify-cas-store into main
feat(cli): unify uwf CAS store with global json-cas store Fixes #573
This commit is contained in:
@@ -270,7 +270,7 @@ node scripts/publish-all.mjs --dry-run # preview without publishing
|
|||||||
examples/solve-issue.yaml — write a workflow YAML definition
|
examples/solve-issue.yaml — write a workflow YAML definition
|
||||||
│ uwf workflow put
|
│ uwf workflow put
|
||||||
▼
|
▼
|
||||||
~/.uncaged/workflow/cas/ — Workflow stored as CAS node
|
~/.uncaged/json-cas/ — Workflow stored as CAS node (unified CAS store)
|
||||||
~/.uncaged/workflow/registry.yaml — name → hash mapping updated
|
~/.uncaged/workflow/registry.yaml — name → hash mapping updated
|
||||||
│ uwf thread start <name> -p "..."
|
│ uwf thread start <name> -p "..."
|
||||||
▼
|
▼
|
||||||
|
|||||||
@@ -209,4 +209,13 @@ src/
|
|||||||
| `~/.uncaged/workflow/.env` | API keys (referenced by `apiKeyEnv` in config) |
|
| `~/.uncaged/workflow/.env` | API keys (referenced by `apiKeyEnv` in config) |
|
||||||
| `~/.uncaged/workflow/registry.yaml` | Workflow name → CAS hash |
|
| `~/.uncaged/workflow/registry.yaml` | Workflow name → CAS hash |
|
||||||
| `~/.uncaged/workflow/threads.yaml` | Active thread head pointers |
|
| `~/.uncaged/workflow/threads.yaml` | Active thread head pointers |
|
||||||
| `~/.uncaged/workflow/cas/` | Content-addressed node storage |
|
| `~/.uncaged/json-cas/` | Content-addressed node storage (unified CAS store, shared with `json-cas` CLI) |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Purpose | Default |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| `UNCAGED_CAS_DIR` | Override the global CAS directory location | `~/.uncaged/json-cas` |
|
||||||
|
| `UNCAGED_WORKFLOW_STORAGE_ROOT` | Internal override for workflow metadata storage | `~/.uncaged/workflow` |
|
||||||
|
| `WORKFLOW_STORAGE_ROOT` | User override for workflow metadata storage | `~/.uncaged/workflow` |
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,11 @@ describe("C1: adapter JSON round-trip integration", () => {
|
|||||||
{
|
{
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
env: { ...process.env, WORKFLOW_STORAGE_ROOT: tmpDir },
|
env: {
|
||||||
|
...process.env,
|
||||||
|
WORKFLOW_STORAGE_ROOT: tmpDir,
|
||||||
|
UNCAGED_CAS_DIR: casDir,
|
||||||
|
},
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,14 +6,22 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|||||||
import { cmdCasPutText } from "../commands/cas.js";
|
import { cmdCasPutText } from "../commands/cas.js";
|
||||||
|
|
||||||
let storageRoot: string;
|
let storageRoot: string;
|
||||||
|
let casDir: string;
|
||||||
let uwfPath: string;
|
let uwfPath: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
storageRoot = join(
|
storageRoot = join(
|
||||||
tmpdir(),
|
tmpdir(),
|
||||||
`uwf-cas-exit-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
`uwf-cas-exit-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||||
);
|
);
|
||||||
|
casDir = join(storageRoot, "cas");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
|
// Set UNCAGED_CAS_DIR for this test
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
|
|
||||||
// Find the uwf CLI path
|
// Find the uwf CLI path
|
||||||
uwfPath = join(__dirname, "../../src/cli.ts");
|
uwfPath = join(__dirname, "../../src/cli.ts");
|
||||||
@@ -21,6 +29,13 @@ beforeEach(async () => {
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(storageRoot, { recursive: true, force: true });
|
await rm(storageRoot, { recursive: true, force: true });
|
||||||
|
|
||||||
|
// Restore original environment
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
type ExecResult = {
|
type ExecResult = {
|
||||||
@@ -32,7 +47,11 @@ type ExecResult = {
|
|||||||
function execUwf(args: string[]): ExecResult {
|
function execUwf(args: string[]): ExecResult {
|
||||||
try {
|
try {
|
||||||
const stdout = execSync(`bun ${uwfPath} ${args.join(" ")}`, {
|
const stdout = execSync(`bun ${uwfPath} ${args.join(" ")}`, {
|
||||||
env: { ...process.env, WORKFLOW_STORAGE_ROOT: storageRoot },
|
env: {
|
||||||
|
...process.env,
|
||||||
|
WORKFLOW_STORAGE_ROOT: storageRoot,
|
||||||
|
UNCAGED_CAS_DIR: casDir,
|
||||||
|
},
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -206,6 +206,8 @@ async function insertStepNode(
|
|||||||
describe("currentRole field", () => {
|
describe("currentRole field", () => {
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
let storageRoot: string;
|
let storageRoot: string;
|
||||||
|
let casDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
tmpDir = join(
|
tmpDir = join(
|
||||||
@@ -213,13 +215,25 @@ describe("currentRole field", () => {
|
|||||||
`uwf-test-current-role-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
`uwf-test-current-role-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||||
);
|
);
|
||||||
storageRoot = join(tmpDir, "storage");
|
storageRoot = join(tmpDir, "storage");
|
||||||
|
casDir = join(tmpDir, "cas");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
|
// Set UNCAGED_CAS_DIR for this test
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
if (tmpDir) {
|
if (tmpDir) {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
// Restore original environment
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// T1: idle at start — currentRole = first role from graph
|
// T1: idle at start — currentRole = first role from graph
|
||||||
|
|||||||
@@ -66,13 +66,21 @@ function generateContent(size: number, prefix = "Content"): string {
|
|||||||
// ── fixture ───────────────────────────────────────────────────────────────────
|
// ── fixture ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-read-test-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-read-test-"));
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
|
// Restore original environment
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── step read tests ───────────────────────────────────────────────────────────
|
// ── step read tests ───────────────────────────────────────────────────────────
|
||||||
@@ -80,7 +88,10 @@ afterEach(async () => {
|
|||||||
describe("step read", () => {
|
describe("step read", () => {
|
||||||
test("test 1: basic single-step read with 3 turns", async () => {
|
test("test 1: basic single-step read with 3 turns", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -166,7 +177,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 2: quota enforcement - multiple turns", async () => {
|
test("test 2: quota enforcement - multiple turns", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -250,7 +263,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 3: minimal quota edge case - always show at least one turn", async () => {
|
test("test 3: minimal quota edge case - always show at least one turn", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -325,7 +340,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 4: step with no detail field", async () => {
|
test("test 4: step with no detail field", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
|
|
||||||
@@ -384,7 +401,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 5: step with detail but no turns array", async () => {
|
test("test 5: step with detail but no turns array", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
await registerDetailSchemas(store);
|
await registerDetailSchemas(store);
|
||||||
@@ -460,7 +479,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 6: displays role and tool calls in turn body", async () => {
|
test("test 6: displays role and tool calls in turn body", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -532,7 +553,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 7: turn content with special characters", async () => {
|
test("test 7: turn content with special characters", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
|
|||||||
@@ -125,15 +125,23 @@ async function createTestStep(
|
|||||||
describe("cmdStepShow JSON serialization", () => {
|
describe("cmdStepShow JSON serialization", () => {
|
||||||
let testDir: string;
|
let testDir: string;
|
||||||
let casDir: string;
|
let casDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testDir = await mkdtemp(join(tmpdir(), "uwf-test-"));
|
testDir = await mkdtemp(join(tmpdir(), "uwf-test-"));
|
||||||
casDir = join(testDir, "cas");
|
casDir = join(testDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(testDir, { recursive: true, force: true });
|
await rm(testDir, { recursive: true, force: true });
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("escapes newlines in tool call args", async () => {
|
test("escapes newlines in tool call args", async () => {
|
||||||
|
|||||||
@@ -63,13 +63,22 @@ async function registerDetailSchemas(store: ReturnType<typeof createFsStore>) {
|
|||||||
// ── fixture ──────────────────────────────────────────────────────────────────
|
// ── fixture ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-timing-test-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-timing-test-"));
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = join(tmpDir, "cas");
|
||||||
|
await mkdir(process.env.UNCAGED_CAS_DIR, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 1. Protocol types (compile-time) ─────────────────────────────────────────
|
// ── 1. Protocol types (compile-time) ─────────────────────────────────────────
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
import { mkdir, rm } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||||
|
import { createUwfStore, getCasDir, getGlobalCasDir } from "../store.js";
|
||||||
|
|
||||||
|
describe("Global CAS directory", () => {
|
||||||
|
let tmpDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tmpDir = join(tmpdir(), `uwf-test-global-cas-${Date.now()}`);
|
||||||
|
await mkdir(tmpDir, { recursive: true });
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (tmpDir) {
|
||||||
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getGlobalCasDir returns default path when no env var set", () => {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
const casDir = getGlobalCasDir();
|
||||||
|
// Should return ~/.uncaged/json-cas
|
||||||
|
expect(casDir).toContain(".uncaged");
|
||||||
|
expect(casDir).toContain("json-cas");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getGlobalCasDir respects UNCAGED_CAS_DIR environment variable", () => {
|
||||||
|
const customPath = join(tmpDir, "custom-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = customPath;
|
||||||
|
const casDir = getGlobalCasDir();
|
||||||
|
expect(casDir).toBe(customPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getGlobalCasDir ignores empty UNCAGED_CAS_DIR", () => {
|
||||||
|
process.env.UNCAGED_CAS_DIR = "";
|
||||||
|
const casDir = getGlobalCasDir();
|
||||||
|
expect(casDir).toContain(".uncaged");
|
||||||
|
expect(casDir).toContain("json-cas");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getCasDir is deprecated but still works for backward compatibility", () => {
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
const casDir = getCasDir(storageRoot);
|
||||||
|
expect(casDir).toBe(join(storageRoot, "cas"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createUwfStore uses global CAS directory", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|
||||||
|
const uwf = await createUwfStore(storageRoot);
|
||||||
|
|
||||||
|
// Verify the store was created in the global CAS directory
|
||||||
|
expect(uwf.storageRoot).toBe(storageRoot);
|
||||||
|
expect(uwf.store).toBeDefined();
|
||||||
|
expect(uwf.schemas).toBeDefined();
|
||||||
|
|
||||||
|
// The global CAS directory should be created
|
||||||
|
const { stat } = await import("node:fs/promises");
|
||||||
|
const stats = await stat(globalCasDir);
|
||||||
|
expect(stats.isDirectory()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createUwfStore creates global CAS directory if it does not exist", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "new-global-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|
||||||
|
await createUwfStore(storageRoot);
|
||||||
|
|
||||||
|
// Verify the directory was created
|
||||||
|
const { stat } = await import("node:fs/promises");
|
||||||
|
const stats = await stat(globalCasDir);
|
||||||
|
expect(stats.isDirectory()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple uwfStore instances share the same global CAS filesystem", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "shared-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot1 = join(tmpDir, "storage1");
|
||||||
|
const storageRoot2 = join(tmpDir, "storage2");
|
||||||
|
await mkdir(storageRoot1, { recursive: true });
|
||||||
|
await mkdir(storageRoot2, { recursive: true });
|
||||||
|
|
||||||
|
const uwf1 = await createUwfStore(storageRoot1);
|
||||||
|
const uwf2 = await createUwfStore(storageRoot2);
|
||||||
|
|
||||||
|
// Both should use the same global CAS directory
|
||||||
|
expect(uwf1.store).toBeDefined();
|
||||||
|
expect(uwf2.store).toBeDefined();
|
||||||
|
|
||||||
|
// Store a node in the first store
|
||||||
|
const testData = { test: "data" };
|
||||||
|
const _hash = uwf1.store.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
|
||||||
|
expect(uwf2.schemas.text).toBe(uwf1.schemas.text);
|
||||||
|
|
||||||
|
// Verify the CAS files are written to the shared directory
|
||||||
|
const { readdir } = await import("node:fs/promises");
|
||||||
|
const files = await readdir(globalCasDir);
|
||||||
|
expect(files.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("workflow metadata remains in storageRoot, not global CAS", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|
||||||
|
const _uwf = await createUwfStore(storageRoot);
|
||||||
|
|
||||||
|
// Write workflow registry file
|
||||||
|
const { saveWorkflowRegistry } = await import("../store.js");
|
||||||
|
await saveWorkflowRegistry(storageRoot, { "test-workflow": "ABC123" });
|
||||||
|
|
||||||
|
// Verify registry is in storageRoot, not global CAS
|
||||||
|
const { readFile } = await import("node:fs/promises");
|
||||||
|
const registryPath = join(storageRoot, "workflows.yaml");
|
||||||
|
const content = await readFile(registryPath, "utf8");
|
||||||
|
expect(content).toContain("test-workflow");
|
||||||
|
expect(content).toContain("ABC123");
|
||||||
|
|
||||||
|
// Verify registry is NOT in global CAS directory
|
||||||
|
const globalRegistryPath = join(globalCasDir, "workflows.yaml");
|
||||||
|
await expect(readFile(globalRegistryPath, "utf8")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("thread metadata remains in storageRoot", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|
||||||
|
await createUwfStore(storageRoot);
|
||||||
|
|
||||||
|
// Write threads index
|
||||||
|
const { saveThreadsIndex } = await import("../store.js");
|
||||||
|
await saveThreadsIndex(storageRoot, { "thread-123": "hash-456" });
|
||||||
|
|
||||||
|
// Verify threads.yaml is in storageRoot, not global CAS
|
||||||
|
const { readFile } = await import("node:fs/promises");
|
||||||
|
const threadsPath = join(storageRoot, "threads.yaml");
|
||||||
|
const content = await readFile(threadsPath, "utf8");
|
||||||
|
expect(content).toContain("thread-123");
|
||||||
|
expect(content).toContain("hash-456");
|
||||||
|
|
||||||
|
// Verify threads.yaml is NOT in global CAS directory
|
||||||
|
const globalThreadsPath = join(globalCasDir, "threads.yaml");
|
||||||
|
await expect(readFile(globalThreadsPath, "utf8")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("history remains in storageRoot", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|
||||||
|
await createUwfStore(storageRoot);
|
||||||
|
|
||||||
|
// Write history
|
||||||
|
const { appendThreadHistory } = await import("../store.js");
|
||||||
|
await appendThreadHistory(storageRoot, {
|
||||||
|
thread: "thread-123" as any,
|
||||||
|
workflow: "workflow-456",
|
||||||
|
head: "hash-789",
|
||||||
|
completedAt: Date.now(),
|
||||||
|
reason: "completed",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify history.jsonl is in storageRoot, not global CAS
|
||||||
|
const { readFile } = await import("node:fs/promises");
|
||||||
|
const historyPath = join(storageRoot, "history.jsonl");
|
||||||
|
const content = await readFile(historyPath, "utf8");
|
||||||
|
expect(content).toContain("thread-123");
|
||||||
|
expect(content).toContain("workflow-456");
|
||||||
|
|
||||||
|
// Verify history.jsonl is NOT in global CAS directory
|
||||||
|
const globalHistoryPath = join(globalCasDir, "history.jsonl");
|
||||||
|
await expect(readFile(globalHistoryPath, "utf8")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("CAS nodes are stored in global directory", async () => {
|
||||||
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
|
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
||||||
|
|
||||||
|
const storageRoot = join(tmpDir, "storage");
|
||||||
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|
||||||
|
const uwf = await createUwfStore(storageRoot);
|
||||||
|
|
||||||
|
// Store a CAS node
|
||||||
|
const testPayload = JSON.stringify({ test: "node" });
|
||||||
|
const _hash = uwf.store.put(uwf.schemas.text, testPayload);
|
||||||
|
|
||||||
|
// Verify the node is in global CAS directory
|
||||||
|
const { readdir } = await import("node:fs/promises");
|
||||||
|
const files = await readdir(globalCasDir);
|
||||||
|
expect(files.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Verify the node is NOT in the old storageRoot/cas location
|
||||||
|
const oldCasDir = join(storageRoot, "cas");
|
||||||
|
await expect(readdir(oldCasDir)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,6 +15,8 @@ import { appendThreadHistory, createUwfStore, saveThreadsIndex } from "../store.
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
// Set UNCAGED_CAS_DIR to use the test's CAS directory
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
return createUwfStore(storageRoot);
|
return createUwfStore(storageRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,31 @@ import { createUwfStore } from "../store.js";
|
|||||||
describe("Thread and edge location integration", () => {
|
describe("Thread and edge location integration", () => {
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
let storageRoot: string;
|
let storageRoot: string;
|
||||||
|
let casDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
async function setupTestEnv() {
|
async function setupTestEnv() {
|
||||||
tmpDir = join(tmpdir(), `uwf-test-location-${Date.now()}`);
|
tmpDir = join(tmpdir(), `uwf-test-location-${Date.now()}`);
|
||||||
storageRoot = join(tmpDir, "storage");
|
storageRoot = join(tmpDir, "storage");
|
||||||
|
casDir = join(tmpDir, "cas");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
|
// Set UNCAGED_CAS_DIR for this test
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
if (tmpDir) {
|
if (tmpDir) {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
// Restore original environment
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("thread start captures cwd in StartNode", async () => {
|
test("thread start captures cwd in StartNode", async () => {
|
||||||
|
|||||||
@@ -67,13 +67,22 @@ function generateContent(size: number, prefix = "Content"): string {
|
|||||||
// ── fixture ───────────────────────────────────────────────────────────────────
|
// ── fixture ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-quota-test-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-quota-test-"));
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = join(tmpDir, "cas");
|
||||||
|
await mkdir(process.env.UNCAGED_CAS_DIR, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── thread read quota enforcement ─────────────────────────────────────────────
|
// ── thread read quota enforcement ─────────────────────────────────────────────
|
||||||
@@ -143,7 +152,7 @@ describe("thread read --quota flag", () => {
|
|||||||
agent: "uwf-test",
|
agent: "uwf-test",
|
||||||
startedAtMs: 1000000000000,
|
startedAtMs: 1000000000000,
|
||||||
completedAtMs: 1000000005000,
|
completedAtMs: 1000000005000,
|
||||||
assembledPrompt: null,
|
assembledPrompt: null,
|
||||||
});
|
});
|
||||||
steps.push(stepHash);
|
steps.push(stepHash);
|
||||||
}
|
}
|
||||||
@@ -339,7 +348,7 @@ describe("thread read --quota flag", () => {
|
|||||||
agent: "uwf-test",
|
agent: "uwf-test",
|
||||||
startedAtMs: 1000000000000,
|
startedAtMs: 1000000000000,
|
||||||
completedAtMs: 1000000005000,
|
completedAtMs: 1000000005000,
|
||||||
assembledPrompt: null,
|
assembledPrompt: null,
|
||||||
});
|
});
|
||||||
steps.push(stepHash);
|
steps.push(stepHash);
|
||||||
}
|
}
|
||||||
@@ -497,7 +506,7 @@ describe("thread read --quota flag", () => {
|
|||||||
agent: "uwf-test",
|
agent: "uwf-test",
|
||||||
startedAtMs: 1000000000000,
|
startedAtMs: 1000000000000,
|
||||||
completedAtMs: 1000000005000,
|
completedAtMs: 1000000005000,
|
||||||
assembledPrompt: null,
|
assembledPrompt: null,
|
||||||
});
|
});
|
||||||
steps.push(stepHash);
|
steps.push(stepHash);
|
||||||
}
|
}
|
||||||
@@ -579,7 +588,7 @@ describe("thread read --quota flag", () => {
|
|||||||
agent: "uwf-test",
|
agent: "uwf-test",
|
||||||
startedAtMs: 1000000000000,
|
startedAtMs: 1000000000000,
|
||||||
completedAtMs: 1000000005000,
|
completedAtMs: 1000000005000,
|
||||||
assembledPrompt: null,
|
assembledPrompt: null,
|
||||||
});
|
});
|
||||||
steps.push(stepHash);
|
steps.push(stepHash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ const DETAIL_SCHEMA = {
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
// Set UNCAGED_CAS_DIR to use the test's CAS directory
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
return { storageRoot, store, schemas };
|
return { storageRoot, store, schemas };
|
||||||
@@ -696,7 +698,7 @@ describe("thread read XML tag isolation", () => {
|
|||||||
agent: "uwf-test",
|
agent: "uwf-test",
|
||||||
startedAtMs: 1000000000000,
|
startedAtMs: 1000000000000,
|
||||||
completedAtMs: 1000000005000,
|
completedAtMs: 1000000005000,
|
||||||
assembledPrompt: null,
|
assembledPrompt: null,
|
||||||
})) as CasRef;
|
})) as CasRef;
|
||||||
steps.push(step);
|
steps.push(step);
|
||||||
prev = step;
|
prev = step;
|
||||||
|
|||||||
@@ -10,17 +10,31 @@ import { createUwfStore, loadThreadsIndex } from "../store.js";
|
|||||||
describe("thread start --cwd CLI option", () => {
|
describe("thread start --cwd CLI option", () => {
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
let storageRoot: string;
|
let storageRoot: string;
|
||||||
|
let casDir: string;
|
||||||
|
let originalEnv: string | undefined;
|
||||||
|
|
||||||
async function setupTestEnv() {
|
async function setupTestEnv() {
|
||||||
tmpDir = join(tmpdir(), `uwf-test-cwd-cli-${Date.now()}`);
|
tmpDir = join(tmpdir(), `uwf-test-cwd-cli-${Date.now()}`);
|
||||||
storageRoot = join(tmpDir, "storage");
|
storageRoot = join(tmpDir, "storage");
|
||||||
|
casDir = join(tmpDir, "cas");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
|
// Set UNCAGED_CAS_DIR for this test
|
||||||
|
originalEnv = process.env.UNCAGED_CAS_DIR;
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
if (tmpDir) {
|
if (tmpDir) {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
// Restore original environment
|
||||||
|
if (originalEnv === undefined) {
|
||||||
|
delete process.env.UNCAGED_CAS_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.UNCAGED_CAS_DIR = originalEnv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTestWorkflow(): Promise<string> {
|
async function createTestWorkflow(): Promise<string> {
|
||||||
@@ -123,7 +137,7 @@ graph:
|
|||||||
|
|
||||||
// Register the workflow
|
// Register the workflow
|
||||||
execFileSync("node", [uwfBin, "workflow", "add", workflowPath], {
|
execFileSync("node", [uwfBin, "workflow", "add", workflowPath], {
|
||||||
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot },
|
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, UNCAGED_CAS_DIR: casDir },
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +146,7 @@ graph:
|
|||||||
"node",
|
"node",
|
||||||
[uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
|
[uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
|
||||||
{
|
{
|
||||||
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot },
|
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, UNCAGED_CAS_DIR: casDir },
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ const DETAIL_SCHEMA = {
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
// Set UNCAGED_CAS_DIR to use the test's CAS directory
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
return { storageRoot, store, schemas };
|
return { storageRoot, store, schemas };
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { loadWorkflowRegistry, saveWorkflowRegistry } from "../store.js";
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
// Set UNCAGED_CAS_DIR to use the test's CAS directory
|
||||||
|
process.env.UNCAGED_CAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
return { storageRoot, store, schemas };
|
return { storageRoot, store, schemas };
|
||||||
|
|||||||
@@ -373,7 +373,12 @@ step
|
|||||||
process.stderr.write("invalid --quota: must be a positive integer\n");
|
process.stderr.write("invalid --quota: must be a positive integer\n");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const markdown = await cmdStepRead(storageRoot, stepHash as CasRef, quota, opts.prompt === true);
|
const markdown = await cmdStepRead(
|
||||||
|
storageRoot,
|
||||||
|
stepHash as CasRef,
|
||||||
|
quota,
|
||||||
|
opts.prompt === true,
|
||||||
|
);
|
||||||
process.stdout.write(markdown.endsWith("\n") ? markdown : `${markdown}\n`);
|
process.stdout.write(markdown.endsWith("\n") ? markdown : `${markdown}\n`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export {
|
export {
|
||||||
generateBootstrapReference as cmdSkillBootstrap,
|
|
||||||
generateAdapterReference as cmdSkillAdapter,
|
generateAdapterReference as cmdSkillAdapter,
|
||||||
generateAuthorReference as cmdSkillAuthor,
|
generateAuthorReference as cmdSkillAuthor,
|
||||||
|
generateBootstrapReference as cmdSkillBootstrap,
|
||||||
generateDeveloperReference as cmdSkillDeveloper,
|
generateDeveloperReference as cmdSkillDeveloper,
|
||||||
generateUserReference as cmdSkillUser,
|
generateUserReference as cmdSkillUser,
|
||||||
} from "@uncaged/workflow-util";
|
} from "@uncaged/workflow-util";
|
||||||
|
|||||||
@@ -311,7 +311,10 @@ export async function cmdStepRead(
|
|||||||
if (promptNode === null) {
|
if (promptNode === null) {
|
||||||
return `# Step ${stepHash}\n\n_Prompt CAS node not found: ${promptRef}_`;
|
return `# Step ${stepHash}\n\n_Prompt CAS node not found: ${promptRef}_`;
|
||||||
}
|
}
|
||||||
const promptText = typeof promptNode.payload === "string" ? promptNode.payload : JSON.stringify(promptNode.payload);
|
const promptText =
|
||||||
|
typeof promptNode.payload === "string"
|
||||||
|
? promptNode.payload
|
||||||
|
: JSON.stringify(promptNode.payload);
|
||||||
return `# Step ${stepHash}\n\n**Role:** ${payload.role}\n**Agent:** ${payload.agent}\n\n## Prompt\n\n${promptText}`;
|
return `# Step ${stepHash}\n\n**Role:** ${payload.role}\n**Agent:** ${payload.agent}\n\n## Prompt\n\n${promptText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,10 +70,26 @@ export function resolveStorageRoot(): string {
|
|||||||
return getDefaultStorageRoot();
|
return getDefaultStorageRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated: Use `getGlobalCasDir()` instead.
|
||||||
|
* Returns the old CAS directory for backward compatibility.
|
||||||
|
*/
|
||||||
export function getCasDir(storageRoot: string): string {
|
export function getCasDir(storageRoot: string): string {
|
||||||
return join(storageRoot, "cas");
|
return join(storageRoot, "cas");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the global CAS directory shared by all uwf and json-cas tools.
|
||||||
|
* Priority: UNCAGED_CAS_DIR environment variable → default ~/.uncaged/json-cas
|
||||||
|
*/
|
||||||
|
export function getGlobalCasDir(): string {
|
||||||
|
const envPath = process.env.UNCAGED_CAS_DIR;
|
||||||
|
if (envPath !== undefined && envPath !== "") {
|
||||||
|
return envPath;
|
||||||
|
}
|
||||||
|
return join(homedir(), ".uncaged", "json-cas");
|
||||||
|
}
|
||||||
|
|
||||||
export function getRegistryPath(storageRoot: string): string {
|
export function getRegistryPath(storageRoot: string): string {
|
||||||
return join(storageRoot, "workflows.yaml");
|
return join(storageRoot, "workflows.yaml");
|
||||||
}
|
}
|
||||||
@@ -98,7 +114,7 @@ export type UwfStore = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function createUwfStore(storageRoot: string): Promise<UwfStore> {
|
export async function createUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = getCasDir(storageRoot);
|
const casDir = getGlobalCasDir();
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
|
|||||||
@@ -94,7 +94,12 @@ async function runBuiltinWithMessages(
|
|||||||
session.startedAtMs,
|
session.startedAtMs,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { output: stripPreamble(loopResult.finalText), detailHash, sessionId: session.sessionId, assembledPrompt: "" };
|
return {
|
||||||
|
output: stripPreamble(loopResult.finalText),
|
||||||
|
detailHash,
|
||||||
|
sessionId: session.sessionId,
|
||||||
|
assembledPrompt: "",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runBuiltin(ctx: AgentContext): Promise<AgentRunResult> {
|
async function runBuiltin(ctx: AgentContext): Promise<AgentRunResult> {
|
||||||
|
|||||||
@@ -120,7 +120,11 @@ function spawnClaudeResume(
|
|||||||
return spawnClaude(args);
|
return spawnClaude(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processClaudeOutput(stdout: string, store: Store, assembledPrompt: string): Promise<AgentRunResult> {
|
async function processClaudeOutput(
|
||||||
|
stdout: string,
|
||||||
|
store: Store,
|
||||||
|
assembledPrompt: string,
|
||||||
|
): Promise<AgentRunResult> {
|
||||||
const parsed = parseClaudeCodeStreamOutput(stdout);
|
const parsed = parseClaudeCodeStreamOutput(stdout);
|
||||||
|
|
||||||
if (parsed !== null) {
|
if (parsed !== null) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const editNodeViewModel = define.view("editNodeView", editNodeView, (set,
|
|||||||
function start(nodeId: string) {
|
function start(nodeId: string) {
|
||||||
const [nodes] = model.use(nodesModel);
|
const [nodes] = model.use(nodesModel);
|
||||||
const node = nodes.find((n) => n.id === nodeId);
|
const node = nodes.find((n) => n.id === nodeId);
|
||||||
if (!node || node.type !== "role") return;
|
if (node?.type !== "role") return;
|
||||||
set({ node: node as WorkNode<"role"> });
|
set({ node: node as WorkNode<"role"> });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function traverse(
|
|||||||
visited.add(nodeId);
|
visited.add(nodeId);
|
||||||
|
|
||||||
const node = nodeMap.get(nodeId);
|
const node = nodeMap.get(nodeId);
|
||||||
if (!node || node.type !== "role") return;
|
if (node?.type !== "role") return;
|
||||||
|
|
||||||
const roleNode = node as WorkNode<"role">;
|
const roleNode = node as WorkNode<"role">;
|
||||||
const outEdges = outgoingEdges.get(nodeId) ?? [];
|
const outEdges = outgoingEdges.get(nodeId) ?? [];
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe("Protocol types for thread/edge location", () => {
|
|||||||
edgePrompt: "Plan the implementation",
|
edgePrompt: "Plan the implementation",
|
||||||
startedAtMs: Date.now(),
|
startedAtMs: Date.now(),
|
||||||
completedAtMs: Date.now() + 1000,
|
completedAtMs: Date.now() + 1000,
|
||||||
assembledPrompt: null,
|
assembledPrompt: null,
|
||||||
cwd: "/home/user/project",
|
cwd: "/home/user/project",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export { generateActorReference } from "./actor-reference.js";
|
export { generateActorReference } from "./actor-reference.js";
|
||||||
export { generateBootstrapReference } from "./bootstrap-reference.js";
|
|
||||||
export { generateAdapterReference } from "./adapter-reference.js";
|
export { generateAdapterReference } from "./adapter-reference.js";
|
||||||
export { generateArchitectureReference } from "./architecture-reference.js";
|
export { generateArchitectureReference } from "./architecture-reference.js";
|
||||||
export { generateAuthorReference } from "./author-reference.js";
|
export { generateAuthorReference } from "./author-reference.js";
|
||||||
export { encodeUint64AsCrockford } from "./base32.js";
|
export { encodeUint64AsCrockford } from "./base32.js";
|
||||||
|
export { generateBootstrapReference } from "./bootstrap-reference.js";
|
||||||
export { generateCliReference } from "./cli-reference.js";
|
export { generateCliReference } from "./cli-reference.js";
|
||||||
export { generateDeveloperReference } from "./developer-reference.js";
|
export { generateDeveloperReference } from "./developer-reference.js";
|
||||||
export { env } from "./env.js";
|
export { env } from "./env.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user