Compare commits

...

8 Commits

Author SHA1 Message Date
xingyue 3ed38c65ec feat(cli): add serve command — Hono HTTP API server
Adds `uncaged-workflow serve` command that exposes workflow data
via a local HTTP API for the upcoming Web UI (RFC #118 Phase 1).

Routes:
- GET /healthz — health check
- GET /api/workflows — list registered workflows
- GET /api/workflows/:name — show workflow details
- GET /api/workflows/:name/history — version history
- GET /api/threads — list threads (optional ?workflow= filter)
- GET /api/threads/running — list running threads
- GET /api/threads/:id — show thread records (parsed JSONL)
- GET /api/cas — list CAS hashes
- GET /api/cas/:hash — get CAS content
- POST /api/cas — store content, returns hash
- DELETE /api/cas/:hash — remove CAS entry
- POST /api/cas/gc — garbage collect

Default: 127.0.0.1:7860, configurable via --port/-p and --host.

Refs: #118
2026-05-08 11:07:13 +08:00
xiaoju 38f2b0eeb2 Merge pull request 'chore: bump all packages to 0.2.0' (#117) from chore/bump-0.2.0 into main 2026-05-08 02:55:49 +00:00
xiaoju 586a0f824e chore: gitignore .npmrc (contains auth token) 2026-05-08 02:55:35 +00:00
xiaoju 178f6c7519 chore: bump all packages to 0.2.0 2026-05-08 02:55:29 +00:00
xiaomo 3153ab26f6 Merge pull request 'feat(engine): supervisor scene — opt-in LLM thread stop (Phase 3)' (#116) from feat/110-phase3-supervisor into main 2026-05-08 02:45:20 +00:00
xingyue 1f7851d5e3 chore: remove outdated examples/ folder
Delete examples/ workspace and remove from workspaces config.
2026-05-08 10:32:57 +08:00
xiaomo e68790dfc7 Merge pull request 'chore: remove all deprecated code' (#115) from chore/114-remove-deprecated into main 2026-05-08 02:29:15 +00:00
xingyue 520b17b351 chore: remove all deprecated code
- Remove createThreadCas alias (CAS is global, not per-thread)
- Remove formatSkillDoc() legacy compat shim
- Remove help command (replaced by skill)
- Remove all 13 DEPRECATED_ALIASES flat commands + printDeprecation
- Fix CAS prompts in develop roles: remove stale <THREAD_ID> param
- Update README.md to remove createThreadCas reference
- Net: -86 lines, 241 tests pass

Closes #114
2026-05-08 10:27:27 +08:00
34 changed files with 398 additions and 173 deletions
+1
View File
@@ -3,3 +3,4 @@ dist/
bun.lock
*.tgz
tsconfig.tsbuildinfo
.npmrc
+2 -2
View File
@@ -48,7 +48,7 @@ uncaged-workflow run solve-issue --prompt "Fix bug #42"
## CLI Usage
```bash
uncaged-workflow help # Show all commands
uncaged-workflow # Print full command usage (exits with status 1)
uncaged-workflow workflow list # List registered workflows
uncaged-workflow run <name> # Start a workflow thread
uncaged-workflow thread list # List all threads
@@ -56,7 +56,7 @@ uncaged-workflow thread show <id> # Inspect a thread
uncaged-workflow skill # Agent-consumable reference docs
```
See `uncaged-workflow help` for the full command reference.
Run `uncaged-workflow` with no arguments to print usage, or `uncaged-workflow skill cli` for the full CLI skill reference.
## Development
-45
View File
@@ -1,45 +0,0 @@
import { createWorkflow, END, type RoleDefinition } from "@uncaged/workflow";
import * as z from "zod/v4";
type Roles = {
greeter: { greeting: string };
};
const greeterMetaSchema = z.object({
greeting: z.string(),
});
export const descriptor = {
description: "A simple hello world workflow",
roles: {
greeter: {
description: "Generates a greeting",
schema: {
type: "object",
properties: { greeting: { type: "string" } },
required: ["greeting"],
},
},
},
};
const greeter: RoleDefinition<Roles["greeter"]> = {
description: "Generates a greeting",
systemPrompt: "You greet the user briefly.",
extractPrompt: "Extract the greeting string produced for the user.",
schema: greeterMetaSchema,
extractRefs: null,
extractMode: "single",
};
export const run = createWorkflow<Roles>(
{
roles: { greeter },
moderator(ctx) {
return ctx.steps.length === 0 ? "greeter" : END;
},
},
{
agent: async (ctx) => `Hello, ${ctx.start.content}`,
},
);
-9
View File
@@ -1,9 +0,0 @@
{
"name": "@uncaged/workflow-examples",
"private": true,
"type": "module",
"dependencies": {
"@uncaged/workflow": "workspace:*",
"zod": "^4.0.0"
}
}
+1 -2
View File
@@ -2,8 +2,7 @@
"name": "@uncaged/workflow-monorepo",
"private": true,
"workspaces": [
"packages/*",
"examples"
"packages/*"
],
"scripts": {
"check": "bunx tsc --build && biome check .",
+10 -22
View File
@@ -1,21 +1,11 @@
import { describe, expect, test } from "bun:test";
import { formatCliUsage, runCli } from "../src/cli-dispatch.js";
import {
formatSkillDoc,
formatSkillIndex,
formatSkillTopic,
getSkillTopics,
} from "../src/skill.js";
import { formatSkillIndex, formatSkillTopic, getSkillTopics } from "../src/skill.js";
const STORAGE_ROOT = "/tmp/help-test-storage";
describe("help command", () => {
test("help returns 0", async () => {
const code = await runCli(STORAGE_ROOT, ["help"]);
expect(code).toBe(0);
});
test("no args prints usage (not red) and returns 1", async () => {
describe("runCli usage", () => {
test("no args prints usage and returns 1", async () => {
const code = await runCli(STORAGE_ROOT, []);
expect(code).toBe(1);
});
@@ -70,13 +60,6 @@ describe("--help flag on groups", () => {
});
});
describe("legacy help --skill compat", () => {
test("help --skill still works (lists topics)", async () => {
const code = await runCli(STORAGE_ROOT, ["help", "--skill"]);
expect(code).toBe(0);
});
});
describe("getSkillTopics", () => {
test("returns all topics", () => {
const topics = getSkillTopics();
@@ -128,8 +111,13 @@ describe("formatCliUsage", () => {
});
});
describe("formatSkillTopic('cli') — legacy formatSkillDoc", () => {
const doc = formatSkillDoc();
const cliSkillDoc = formatSkillTopic("cli");
if (cliSkillDoc === null) {
throw new Error("BUG: cli skill topic missing");
}
describe("formatSkillTopic('cli')", () => {
const doc = cliSkillDoc;
test("contains title", () => {
expect(doc).toContain("# uncaged-workflow CLI Reference");
@@ -0,0 +1,98 @@
import { describe, expect, test } from "bun:test";
import { createApp } from "../src/commands/serve/app.js";
function buildApp(storageRoot: string) {
const app = createApp(storageRoot);
return {
fetch: (path: string, init?: RequestInit) =>
app.fetch(new Request(`http://localhost${path}`, init)),
};
}
describe("serve /healthz", () => {
test("returns ok", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/healthz");
expect(res.status).toBe(200);
const body = (await res.json()) as { ok: boolean };
expect(body.ok).toBe(true);
});
});
describe("serve /api/workflows", () => {
test("returns empty list for missing storage", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/api/workflows");
// Registry file won't exist, should return error
expect(res.status).toBe(200);
});
});
describe("serve /api/threads", () => {
test("returns empty list for missing storage", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/api/threads");
expect(res.status).toBe(200);
const body = (await res.json()) as { threads: unknown[] };
expect(body.threads).toEqual([]);
});
test("returns 404 for missing thread", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/api/threads/nonexistent-id");
expect(res.status).toBe(404);
});
});
describe("serve /api/threads/running", () => {
test("returns empty list for missing storage", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/api/threads/running");
expect(res.status).toBe(200);
const body = (await res.json()) as { threads: unknown[] };
expect(body.threads).toEqual([]);
});
});
describe("serve /api/cas", () => {
test("returns empty list for missing storage", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/api/cas");
expect(res.status).toBe(200);
const body = (await res.json()) as { hashes: unknown[] };
expect(body.hashes).toEqual([]);
});
test("returns 404 for missing hash", async () => {
const { fetch } = buildApp("/tmp/uncaged-serve-test-nonexistent");
const res = await fetch("/api/cas/nonexistent-hash");
expect(res.status).toBe(404);
});
});
describe("serve CAS round-trip", () => {
const tmpDir = `/tmp/uncaged-serve-cas-test-${Date.now()}`;
test("put then get", async () => {
const { fetch } = buildApp(tmpDir);
const putRes = await fetch("/api/cas", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: "hello world" }),
});
expect(putRes.status).toBe(201);
const putBody = (await putRes.json()) as { hash: string };
expect(typeof putBody.hash).toBe("string");
const getRes = await fetch(`/api/cas/${putBody.hash}`);
expect(getRes.status).toBe(200);
const getBody = (await getRes.json()) as { content: string };
expect(getBody.content).toBe("hello world");
// cleanup
const delRes = await fetch(`/api/cas/${putBody.hash}`, { method: "DELETE" });
expect(delRes.status).toBe(200);
});
});
+2 -1
View File
@@ -1,12 +1,13 @@
{
"name": "@uncaged/cli-workflow",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"bin": {
"uncaged-workflow": "src/cli.ts"
},
"dependencies": {
"@uncaged/workflow": "workspace:*",
"hono": "^4.12.18",
"yaml": "^2.8.4"
},
"scripts": {
+7 -60
View File
@@ -1,29 +1,12 @@
import type { CommandEntry, DispatchFn } from "./cli-command-types.js";
import { printCliError, printCliLine, printCliWarn } from "./cli-output.js";
import { printCliError, printCliLine } from "./cli-output.js";
import { getCommandRegistry } from "./cli-registry.js";
import { formatCliUsage as formatCliUsageWithGroups } from "./cli-usage.js";
import { createCasDispatcher, dispatchGc } from "./commands/cas/index.js";
import { createCasDispatcher } from "./commands/cas/index.js";
import { createInitDispatcher } from "./commands/init/index.js";
import {
createThreadDispatcher,
dispatchFork,
dispatchKill,
dispatchLive,
dispatchPause,
dispatchPs,
dispatchResume,
dispatchRun,
dispatchThreadList,
} from "./commands/thread/index.js";
import {
createWorkflowDispatcher,
dispatchAdd,
dispatchHistory,
dispatchList,
dispatchRemove,
dispatchRollback,
dispatchShow,
} from "./commands/workflow/index.js";
import { dispatchServe } from "./commands/serve/index.js";
import { createThreadDispatcher, dispatchLive, dispatchRun } from "./commands/thread/index.js";
import { createWorkflowDispatcher } from "./commands/workflow/index.js";
import { formatSkillIndex, formatSkillTopic, getSkillTopics } from "./skill.js";
export type { CommandEntry, CommandGroup, DispatchFn } from "./cli-command-types.js";
@@ -54,15 +37,11 @@ function dispatchGroup(
return entry.handler(storageRoot, argv.slice(1));
}
function printDeprecation(oldCmd: string, newCmd: string): void {
printCliWarn(`⚠ "${oldCmd}" is deprecated, use "${newCmd}" instead`);
}
export function formatCliUsage(): string {
return formatCliUsageWithGroups(getCommandRegistry(), getSkillTopics());
}
const dispatchWorkflow = createWorkflowDispatcher({ dispatchGroup, printDeprecation });
const dispatchWorkflow = createWorkflowDispatcher({ dispatchGroup });
const dispatchThread = createThreadDispatcher({ dispatchGroup });
const dispatchCas = createCasDispatcher({ dispatchGroup });
const dispatchInit = createInitDispatcher({ dispatchGroup });
@@ -85,41 +64,15 @@ async function dispatchSkill(_storageRoot: string, argv: string[]): Promise<numb
return showSkillDocOrIndex(argv[0]);
}
async function dispatchHelp(_storageRoot: string, argv: string[]): Promise<number> {
printCliWarn('⚠ "help" is deprecated, use "skill" instead');
const skillIdx = argv.indexOf("--skill");
if (skillIdx !== -1) {
return showSkillDocOrIndex(argv[skillIdx + 1]);
}
printCliLine(formatCliUsage());
return 0;
}
const COMMAND_TABLE: Record<string, DispatchFn> = {
workflow: dispatchWorkflow,
thread: dispatchThread,
cas: dispatchCas,
init: dispatchInit,
help: dispatchHelp,
skill: dispatchSkill,
run: dispatchRun,
live: dispatchLive,
};
const DEPRECATED_ALIASES: Record<string, { newCmd: string; handler: DispatchFn }> = {
add: { newCmd: "workflow add", handler: dispatchAdd },
list: { newCmd: "workflow list", handler: dispatchList },
show: { newCmd: "workflow show", handler: dispatchShow },
remove: { newCmd: "workflow rm", handler: dispatchRemove },
ps: { newCmd: "thread ps", handler: dispatchPs },
kill: { newCmd: "thread kill", handler: dispatchKill },
pause: { newCmd: "thread pause", handler: dispatchPause },
resume: { newCmd: "thread resume", handler: dispatchResume },
threads: { newCmd: "thread list", handler: dispatchThreadList },
fork: { newCmd: "thread fork", handler: dispatchFork },
gc: { newCmd: "cas gc", handler: dispatchGc },
history: { newCmd: "workflow history", handler: dispatchHistory },
rollback: { newCmd: "workflow rollback", handler: dispatchRollback },
serve: dispatchServe,
};
export async function runCli(storageRoot: string, argv: string[]): Promise<number> {
@@ -139,12 +92,6 @@ export async function runCli(storageRoot: string, argv: string[]): Promise<numbe
return dispatch(storageRoot, rest);
}
const deprecated = DEPRECATED_ALIASES[command];
if (deprecated !== undefined) {
printDeprecation(command, deprecated.newCmd);
return deprecated.handler(storageRoot, rest);
}
printCliError(`${formatCliUsage()}\n\nerror: unknown command ${command}`);
return 1;
}
+11
View File
@@ -57,6 +57,17 @@ export function formatCliUsage(
);
lines.push("");
lines.push("Server:");
lines.push(
...formatUsageCommandLines([
{
prefix: "serve [--port N] [--host ADDR]",
description: "Start HTTP API server (default: 127.0.0.1:7860)",
},
]),
);
lines.push("");
lines.push("Reference:");
const skillTopicNames = skillTopics.map((t) => t.name).join(", ");
lines.push(
@@ -0,0 +1,20 @@
import { Hono } from "hono";
import { cors } from "hono/cors";
import { createCasRoutes } from "./routes-cas.js";
import { createThreadRoutes } from "./routes-thread.js";
import { createWorkflowRoutes } from "./routes-workflow.js";
export function createApp(storageRoot: string): Hono {
const app = new Hono();
app.use("*", cors());
app.get("/healthz", (c) => c.json({ ok: true }));
app.route("/api/workflows", createWorkflowRoutes(storageRoot));
app.route("/api/threads", createThreadRoutes(storageRoot));
app.route("/api/cas", createCasRoutes(storageRoot));
return app;
}
@@ -0,0 +1,3 @@
export { createApp } from "./app.js";
export { dispatchServe, startServer } from "./serve.js";
export type { ServeOptions } from "./types.js";
@@ -0,0 +1,56 @@
import { createCasStore, garbageCollectCas, getGlobalCasDir } from "@uncaged/workflow";
import { Hono } from "hono";
export function createCasRoutes(storageRoot: string): Hono {
const app = new Hono();
app.get("/", async (c) => {
const casDir = getGlobalCasDir(storageRoot);
const cas = createCasStore(casDir);
const hashes = await cas.list();
return c.json({ hashes });
});
app.get("/:hash", async (c) => {
const casDir = getGlobalCasDir(storageRoot);
const cas = createCasStore(casDir);
const content = await cas.get(c.req.param("hash"));
if (content === null) {
return c.json({ error: "not found" }, 404);
}
return c.json({ hash: c.req.param("hash"), content });
});
app.post("/", async (c) => {
const body = await c.req.json<{ content: string }>();
if (typeof body.content !== "string") {
return c.json({ error: "content field required" }, 400);
}
const casDir = getGlobalCasDir(storageRoot);
const cas = createCasStore(casDir);
const hash = await cas.put(body.content);
return c.json({ hash }, 201);
});
app.delete("/:hash", async (c) => {
const casDir = getGlobalCasDir(storageRoot);
const cas = createCasStore(casDir);
const hash = c.req.param("hash");
const content = await cas.get(hash);
if (content === null) {
return c.json({ error: "not found" }, 404);
}
await cas.delete(hash);
return c.json({ ok: true });
});
app.post("/gc", async (c) => {
const result = await garbageCollectCas(storageRoot);
if (!result.ok) {
return c.json({ error: result.error }, 500);
}
return c.json(result.value);
});
return app;
}
@@ -0,0 +1,46 @@
import { Hono } from "hono";
import { readTextFileIfExists } from "../../fs-utils.js";
import {
listHistoricalThreads,
listRunningThreads,
resolveThreadDataPath,
} from "../../thread-scan.js";
export function createThreadRoutes(storageRoot: string): Hono {
const app = new Hono();
app.get("/", async (c) => {
const nameFilter = c.req.query("workflow") ?? null;
const rows = await listHistoricalThreads(storageRoot, nameFilter);
return c.json({ threads: rows });
});
app.get("/running", async (c) => {
const rows = await listRunningThreads(storageRoot);
return c.json({ threads: rows });
});
app.get("/:threadId", async (c) => {
const threadId = c.req.param("threadId");
const dataPath = await resolveThreadDataPath(storageRoot, threadId);
if (dataPath === null) {
return c.json({ error: `thread not found: ${threadId}` }, 404);
}
const text = await readTextFileIfExists(dataPath);
if (text === null) {
return c.json({ error: `thread data missing: ${threadId}` }, 404);
}
const lines = text.trim().split("\n");
const records = lines.map((line) => {
try {
return JSON.parse(line) as unknown;
} catch {
return { raw: line };
}
});
return c.json({ threadId, records });
});
return app;
}
@@ -0,0 +1,55 @@
import {
getRegisteredWorkflow,
listRegisteredWorkflowNames,
readWorkflowRegistry,
} from "@uncaged/workflow";
import { Hono } from "hono";
export function createWorkflowRoutes(storageRoot: string): Hono {
const app = new Hono();
app.get("/", async (c) => {
const reg = await readWorkflowRegistry(storageRoot);
if (!reg.ok) {
return c.json({ error: reg.error.message }, 500);
}
const names = listRegisteredWorkflowNames(reg.value);
const workflows = names.map((name) => {
const entry = reg.value.workflows[name];
return {
name,
hash: entry?.hash ?? null,
timestamp: entry?.timestamp ?? null,
};
});
return c.json({ workflows });
});
app.get("/:name", async (c) => {
const reg = await readWorkflowRegistry(storageRoot);
if (!reg.ok) {
return c.json({ error: reg.error.message }, 500);
}
const name = c.req.param("name");
const entry = getRegisteredWorkflow(reg.value, name);
if (entry === null) {
return c.json({ error: `workflow not found: ${name}` }, 404);
}
return c.json({ name, ...entry });
});
app.get("/:name/history", async (c) => {
const reg = await readWorkflowRegistry(storageRoot);
if (!reg.ok) {
return c.json({ error: reg.error.message }, 500);
}
const name = c.req.param("name");
const entry = getRegisteredWorkflow(reg.value, name);
if (entry === null) {
return c.json({ error: `workflow not found: ${name}` }, 404);
}
return c.json({ name, history: entry.history });
});
return app;
}
@@ -0,0 +1,69 @@
import { err, ok, type Result } from "@uncaged/workflow";
import { serve } from "bun";
import { printCliLine } from "../../cli-output.js";
import { createApp } from "./app.js";
import type { ServeOptions } from "./types.js";
export function startServer(storageRoot: string, options: ServeOptions): void {
const app = createApp(storageRoot);
const server = serve({
fetch: app.fetch,
port: options.port,
hostname: options.hostname,
});
printCliLine(`uncaged-workflow API server listening on http://${server.hostname}:${server.port}`);
}
function parsePortValue(value: string | undefined): Result<number, string> {
if (value === undefined) {
return err("--port requires a value");
}
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 65535) {
return err(`invalid port: ${value}`);
}
return ok(parsed);
}
function parseServeArgv(argv: string[]): Result<ServeOptions, string> {
let port = 7860;
let hostname = "127.0.0.1";
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === "--port" || arg === "-p") {
const portResult = parsePortValue(argv[i + 1]);
if (!portResult.ok) {
return portResult;
}
port = portResult.value;
i++;
} else if (arg === "--host") {
const next = argv[i + 1];
if (next === undefined) {
return err("--host requires a value");
}
hostname = next;
i++;
}
}
return ok({ port, hostname });
}
export async function dispatchServe(storageRoot: string, argv: string[]): Promise<number> {
const parsed = parseServeArgv(argv);
if (!parsed.ok) {
printCliLine(`error: ${parsed.error}`);
return 1;
}
startServer(storageRoot, parsed.value);
// Keep process alive
await new Promise(() => {});
return 0;
}
@@ -0,0 +1,4 @@
export type ServeOptions = {
port: number;
hostname: string;
};
@@ -142,7 +142,7 @@ export const WORKFLOW_SUBCOMMAND_TABLE: Record<string, CommandEntry> = {
};
export function createWorkflowDispatcher(deps: WorkflowDispatchDeps) {
const { dispatchGroup, printDeprecation } = deps;
const { dispatchGroup } = deps;
return async function dispatchWorkflow(storageRoot: string, argv: string[]): Promise<number> {
const result = dispatchGroup("workflow", WORKFLOW_SUBCOMMAND_TABLE, storageRoot, argv);
if (result !== null) {
@@ -150,7 +150,6 @@ export function createWorkflowDispatcher(deps: WorkflowDispatchDeps) {
}
const sub = argv[0];
if (sub === "remove") {
printDeprecation("workflow remove", "workflow rm");
return dispatchRemove(storageRoot, argv.slice(1));
}
printCliError(`${usageText()}\n\nerror: unknown workflow subcommand: ${sub}`);
@@ -14,5 +14,4 @@ export type CmdAddSuccess = {
export type WorkflowDispatchDeps = {
dispatchGroup: DispatchGroupFn;
printDeprecation: (oldCmd: string, newCmd: string) => void;
};
-7
View File
@@ -229,10 +229,3 @@ uncaged-workflow live --latest
Bundles are immutable and identified by XXH64 hash. Re-registering a workflow with a new bundle creates a new version. Use \`workflow history\` and \`workflow rollback\` to manage versions.
`;
}
// ── Legacy compat ──────────────────────────────────────────────────────
/** @deprecated Use formatSkillTopic("cli") instead */
export function formatSkillDoc(): string {
return formatSkillCli();
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow-agent-cursor",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow-agent-hermes",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow-agent-llm",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow-template-develop",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
@@ -15,7 +15,7 @@ Run \`uncaged-workflow skill develop\` for thread ID lookup, CAS commands, and m
## Reading phase details
Each planner phase has a content-hash and title. Read full details with \`uncaged-workflow cas get <THREAD_ID> <HASH>\`.
Each planner phase has a content-hash and title. Read full details with \`uncaged-workflow cas get <HASH>\`.
The thread ID (26-char Crockford Base32) appears in the first message. If unsure, run \`uncaged-workflow thread list\`.
@@ -18,7 +18,7 @@ Run \`uncaged-workflow skill develop\` for thread ID lookup, CAS commands, and m
## Storing phase details — MANDATORY
For each phase, store its full detail text in CAS via \`uncaged-workflow cas put <THREAD_ID> '<content>'\`. The command prints a content-hash — use that as the phase identifier.
For each phase, store its full detail text in CAS via \`uncaged-workflow cas put '<content>'\`. The command prints a content-hash — use that as the phase identifier.
The thread ID (26-char Crockford Base32) appears in the first message. If unsure, run \`uncaged-workflow thread list\`.
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow-template-solve-issue",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow-util-agent",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
+1 -1
View File
@@ -27,7 +27,7 @@ import { createWorkflow, readWorkflowRegistry, executeThread } from "@uncaged/wo
| **Types** | `WorkflowDefinition`, `WorkflowFn`, `AgentFn`, `AgentBinding`, `Moderator`, `RoleDefinition`, `ThreadContext`, `LlmProvider`, `Result` shape via `ok` / `err`, `START` / `END` |
| **Bundle** | `buildDescriptor`, `extractBundleExports`, `validateWorkflowBundle`, `validateWorkflowDescriptor`, `WorkflowDescriptor`, `WorkflowRoleDescriptor` |
| **Registry** | `readWorkflowRegistry`, `writeWorkflowRegistry`, `registerWorkflowVersion`, `workflowRegistryPath`, YAML helpers |
| **CAS** | `createCasStore`, `createThreadCas`, Merkle helpers (`putStepMerkleNode`, `getContentMerklePayload`, …), `hashWorkflowBundleBytes` |
| **CAS** | `createCasStore`, Merkle helpers (`putStepMerkleNode`, `getContentMerklePayload`, …), `hashWorkflowBundleBytes` |
| **Engine** | `createWorkflow`, `executeThread`, `parseThreadDataJsonl`, fork helpers, `garbageCollectCas` |
| **Extract / LLM tools** | `llmExtract`, `reactExtract`, `createExtract`, `getExtractProvider` |
| **Agent bridge** | `workflowAsAgent` — expose a registered workflow as an agent-backed role |
+1 -7
View File
@@ -3,15 +3,9 @@ import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createCasStore, createThreadCas } from "../src/cas/cas.js";
import { createCasStore } from "../src/cas/cas.js";
import { hashString } from "../src/cas/hash.js";
describe("cas module exports", () => {
test("createThreadCas is a deprecated alias of createCasStore", () => {
expect(createThreadCas).toBe(createCasStore);
});
});
describe("createCasStore", () => {
let casDir: string;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/workflow",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
-3
View File
@@ -62,6 +62,3 @@ export function createCasStore(casDir: string): CasStore {
},
};
}
/** @deprecated Use {@link createCasStore} — CAS is global, not per-thread. */
export const createThreadCas = createCasStore;
+1 -1
View File
@@ -1,4 +1,4 @@
export { createCasStore, createThreadCas } from "./cas.js";
export { createCasStore } from "./cas.js";
export { hashString, hashWorkflowBundleBytes } from "./hash.js";
export {
createContentMerkleNode,
-1
View File
@@ -14,7 +14,6 @@ export {
type CasStore,
createCasStore,
createContentMerkleNode,
createThreadCas,
getContentMerklePayload,
hashString,
hashWorkflowBundleBytes,