chore: reorganize repo — legacy packages to legacy-packages/, templates to examples/

- Move 15 old workflow-* packages to legacy-packages/ (inactive, preserved for reference)
- Rename templates/ → examples/ for clarity
- Rewrite docs/architecture.md to reflect current uwf architecture
- Active packages remain in packages/: cli-uwf, uwf-agent-hermes, uwf-agent-kit, uwf-moderator, uwf-protocol, workflow-util

小橘 🍊(NEKO Team)
This commit is contained in:
2026-05-19 07:19:40 +00:00
parent 2a3a40b9d9
commit d63d58ccb5
373 changed files with 393 additions and 203 deletions
@@ -0,0 +1,58 @@
# @uncaged/workflow-protocol
## 0.5.0-alpha.4
### Patch Changes
- f74b482: fix: correct internal dependency versions for prerelease
- f74b482: fix: use npm publish with pinned deps instead of bun publish (workspace:^ resolution bug)
## 0.5.0-alpha.3
### Patch Changes
- fix: use npm publish with pinned deps instead of bun publish (workspace:^ resolution bug)
## 0.5.0-alpha.2
### Patch Changes
- fix: correct internal dependency versions for prerelease
## 0.5.0-alpha.1
## 0.5.0-alpha.0
### Minor Changes
- feat: AgentFn<Opt> type boundary and createAgentAdapter bridging function (RFC #252)
## 0.4.5
### Patch Changes
- Add publishConfig to all packages for Gitea registry compatibility with changeset publish.
## 0.4.4
### Patch Changes
- Test changeset publish with Gitea registry.
## 0.4.3
### Patch Changes
- Include src/ in published packages so bun runtime can resolve the 'bun' exports condition.
## 0.4.2
### Patch Changes
- Fix workspace dependency resolution: use workspace:^ so published packages resolve to compatible versions instead of exact (non-existent) versions.
## 0.4.0
### Minor Changes
- Fix package exports for published packages and adopt changesets for version management.
@@ -0,0 +1,29 @@
# @uncaged/workflow-protocol
Shared workflow types, sentinel constants, and `Result` helpers.
## What This Package Does
It defines the cross-package contract for bundles and the engine: thread/step shapes, `WorkflowFn`, agent/extract contexts, descriptor types, and `CasStore` as an interface. Implementations (CAS store, CLI, extract) depend on these types so bundles stay decoupled from Node APIs.
## Key Exports
From `src/index.ts`:
- **Types:** `Result`, `CasStore`, `WorkflowRoleSchema`, `WorkflowRoleDescriptor`, `WorkflowDescriptor`, `RoleMeta`, `RoleOutput`, `StartStep`, `RoleStep`, `ThreadContext`, `ModeratorContext`, `AgentContext`, `ExtractContext`, `WorkflowCompletion`, `WorkflowResult`, `LlmProvider`, `ProviderConfig`, `ResolvedModel`, `WorkflowConfig`, `ExtractFn`, `AgentFn`, `AgentBinding`, `WorkflowRuntime`, `WorkflowFn`, `RoleDefinition`, `Moderator`, `WorkflowDefinition`, `AdvanceOutcome`
- **Constants:** `START`, `END`
- **Functions:** `ok`, `err`
## Dependencies
- **Peer:** `zod` ^4 — used in type positions for schemas (`ExtractFn`, `RoleDefinition`, etc.)
No workspace packages; this is the bottom layer.
## Usage
```typescript
import { END, START, type WorkflowFn, type ThreadContext } from "@uncaged/workflow-protocol";
```
Concrete `WorkflowFn` implementations are built with `@uncaged/workflow-runtime` (`createWorkflow`).
@@ -0,0 +1,154 @@
import { describe, expect, test } from "bun:test";
import { tableToModerator } from "../src/moderator-table.js";
import type { ModeratorContext, ModeratorTable, StartStep } from "../src/types.js";
import { END, START } from "../src/types.js";
type TestMeta = {
planner: { plan: string };
coder: { code: string };
reviewer: { approved: boolean };
};
function makeCtx(roles: (keyof TestMeta & string)[]): ModeratorContext<TestMeta> {
const steps = roles.map((role, i) => ({
role,
meta: {} as TestMeta[typeof role],
contentHash: `hash-${i}`,
refs: [],
timestamp: Date.now() + i,
}));
return {
threadId: "test-thread",
depth: 0,
bundleHash: "TESTHASH00001",
start: {
role: START,
content: "test",
meta: {},
timestamp: Date.now(),
parentState: null,
} as StartStep,
steps,
};
}
describe("tableToModerator", () => {
test("START -> role A (FALLBACK) returns A on first call", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [{ condition: "FALLBACK", role: "planner" }],
planner: [],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
expect(mod(makeCtx([]))).toBe("planner");
});
test("condition true wins over FALLBACK", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [
{
condition: {
name: "always",
description: "always true",
check: () => true,
},
role: "planner",
},
{ condition: "FALLBACK", role: "coder" },
],
planner: [],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
expect(mod(makeCtx([]))).toBe("planner");
});
test("condition false falls through to FALLBACK", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [
{
condition: {
name: "never",
description: "always false",
check: () => false,
},
role: "planner",
},
{ condition: "FALLBACK", role: "coder" },
],
planner: [],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
expect(mod(makeCtx([]))).toBe("coder");
});
test("no matching transitions returns END", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [
{
condition: {
name: "never",
description: "always false",
check: () => false,
},
role: "planner",
},
],
planner: [],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
expect(mod(makeCtx([]))).toBe(END);
});
test("multi-step: A -> FALLBACK END returns END after A", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [{ condition: "FALLBACK", role: "planner" }],
planner: [{ condition: "FALLBACK", role: END }],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
expect(mod(makeCtx(["planner"]))).toBe(END);
});
test("role not in table returns END", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [{ condition: "FALLBACK", role: "planner" }],
planner: [{ condition: "FALLBACK", role: "coder" }],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
// coder has empty transitions array
expect(mod(makeCtx(["planner", "coder"]))).toBe(END);
});
test("condition receives ctx", () => {
const table: ModeratorTable<TestMeta> = {
[START]: [
{
condition: {
name: "has-steps",
description: "checks ctx.steps",
check: (ctx) => ctx.steps.length > 0,
},
role: "coder",
},
{ condition: "FALLBACK", role: "planner" },
],
planner: [],
coder: [],
reviewer: [],
};
const mod = tableToModerator(table);
// No steps -> condition false -> FALLBACK -> planner
expect(mod(makeCtx([]))).toBe("planner");
});
});
@@ -0,0 +1,32 @@
{
"name": "@uncaged/workflow-protocol",
"version": "0.5.0-alpha.4",
"files": [
"src",
"dist",
"package.json"
],
"type": "module",
"exports": {
".": {
"bun": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./moderator-table.js": {
"bun": "./src/moderator-table.ts",
"types": "./dist/moderator-table.d.ts",
"import": "./dist/moderator-table.js"
}
},
"peerDependencies": {
"zod": "^4.0.0"
},
"devDependencies": {
"zod": "^4.0.0",
"typescript": "^5.8.3"
},
"publishConfig": {
"access": "public"
}
}
+32
View File
@@ -0,0 +1,32 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
typescript:
specifier: ^5.8.3
version: 5.9.3
zod:
specifier: ^4.0.0
version: 4.4.3
packages:
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
snapshots:
typescript@5.9.3: {}
zod@4.4.3: {}
@@ -0,0 +1,39 @@
// ── CAS thread chain nodes (RFC: CAS-based thread storage) ──────────
export type StartNodePayload = {
name: string;
hash: string;
depth: number;
/** Parent thread's head state hash at spawn time. `null` for top-level workflows. */
parentState: string | null;
};
export type StartNode = {
type: "start";
payload: StartNodePayload;
refs: string[];
};
export type StateNodePayload = {
role: string;
meta: Record<string, unknown>;
start: string;
content: string;
ancestors: string[];
compact: string | null;
timestamp: number;
/** Child thread's final state hash (workflow-as-agent). `null` when no child spawned. */
childThread: string | null;
};
export type StateNode = {
type: "state";
payload: StateNodePayload;
refs: string[];
};
export type ContentMerkleNode = {
type: "content";
payload: string;
refs: string[];
};
@@ -0,0 +1,56 @@
// ── Types ──────────────────────────────────────────────────────────
export type {
ContentMerkleNode,
StartNode,
StartNodePayload,
StateNode,
StateNodePayload,
} from "./cas-types.js";
export type {
AdapterBinding,
AdapterFn,
AdvanceOutcome,
AgentContext,
AgentFn,
CasStore,
ExtractFn,
ExtractResult,
FALLBACK,
LlmProvider,
ModeratorCondition,
ModeratorContext,
ModeratorTable,
ModeratorTransition,
ProviderConfig,
ResolvedModel,
Result,
RoleDefinition,
RoleFn,
RoleMeta,
RoleOutput,
RoleResult,
RoleStep,
StartStep,
ThreadContext,
WorkflowCompletion,
WorkflowConfig,
WorkflowDefinition,
WorkflowDescriptor,
WorkflowFn,
WorkflowGraph,
WorkflowGraphEdge,
WorkflowResult,
WorkflowRoleDescriptor,
WorkflowRoleSchema,
WorkflowRuntime,
} from "./types.js";
// ── Constants ──────────────────────────────────────────────────────
export { END, START } from "./types.js";
// ── Constructor functions ──────────────────────────────────────────
export { err, ok } from "./result.js";
@@ -0,0 +1,22 @@
import type { Moderator, ModeratorTable, RoleMeta } from "./types.js";
import { END, START } from "./types.js";
export function tableToModerator<M extends RoleMeta>(table: ModeratorTable<M>): Moderator<M> {
return (ctx) => {
const lastStep = ctx.steps.length > 0 ? ctx.steps[ctx.steps.length - 1] : null;
const currentRole: string = lastStep ? lastStep.role : START;
const transitions = (table as Record<string, (typeof table)[string]>)[currentRole];
if (!transitions) {
return END;
}
for (const transition of transitions) {
if (transition.condition === "FALLBACK" || transition.condition.check(ctx)) {
return transition.role;
}
}
return END;
};
}
@@ -0,0 +1,9 @@
import type { Result } from "./types.js";
export function ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
export function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
@@ -0,0 +1,218 @@
import type * as z from "zod/v4";
// ── Constants ──────────────────────────────────────────────────────
export const START = "__start__" as const;
export const END = "__end__" as const;
// ── Result ─────────────────────────────────────────────────────────
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
// ── CAS ────────────────────────────────────────────────────────────
export type CasStore = {
put(content: string): Promise<string>;
get(hash: string): Promise<string | null>;
delete(hash: string): Promise<void>;
list(): Promise<string[]>;
};
// ── Workflow Descriptor ────────────────────────────────────────────
export type WorkflowRoleSchema = Record<string, unknown>;
export type WorkflowRoleDescriptor = {
description: string;
systemPrompt: string;
schema: WorkflowRoleSchema;
};
/** Serializable routing edges derived from a moderator transition table. */
export type WorkflowGraphEdge = {
from: string;
to: string;
condition: string;
conditionDescription: string | null;
};
export type WorkflowGraph = {
edges: readonly WorkflowGraphEdge[];
};
export type WorkflowDescriptor = {
description: string;
roles: Record<string, WorkflowRoleDescriptor>;
graph: WorkflowGraph;
};
// ── Role & Thread ──────────────────────────────────────────────────
export type RoleMeta = Record<string, Record<string, unknown>>;
export type RoleOutput = {
role: string;
contentHash: string;
meta: Record<string, unknown>;
refs: string[];
childThread: string | null;
};
export type StartStep = {
role: typeof START;
content: string;
meta: Record<string, never>;
timestamp: number;
parentState: string | null;
};
export type RoleStep<M extends RoleMeta> = {
[K in keyof M & string]: {
role: K;
meta: M[K];
contentHash: string;
refs: string[];
timestamp: number;
};
}[keyof M & string];
export type ThreadContext<M extends RoleMeta = RoleMeta> = {
threadId: string;
depth: number;
bundleHash: string;
start: StartStep;
steps: RoleStep<M>[];
};
export type ModeratorContext<M extends RoleMeta = RoleMeta> = ThreadContext<M>;
export type AgentContext<M extends RoleMeta = RoleMeta> = ModeratorContext<M> & {
currentRole: {
name: string;
systemPrompt: string;
};
};
// ── Workflow Completion ────────────────────────────────────────────
export type WorkflowCompletion = {
returnCode: number;
summary: string;
};
export type WorkflowResult = WorkflowCompletion & {
rootHash: string;
};
// ── LLM Provider ───────────────────────────────────────────────────
export type LlmProvider = {
baseUrl: string;
apiKey: string;
model: string;
};
export type ProviderConfig = {
baseUrl: string;
apiKey: string;
};
export type ResolvedModel = {
baseUrl: string;
apiKey: string;
model: string;
};
export type WorkflowConfig = {
maxDepth: number;
supervisorInterval: number;
providers: Record<string, ProviderConfig>;
models: Record<string, string>;
};
// ── Functions ──────────────────────────────────────────────────────
/** Structured output of the extract phase (RFC v3 content Merkle + artifact refs). */
export type ExtractResult<T extends Record<string, unknown>> = {
meta: T;
contentPayload: string;
refs: string[];
};
export type ExtractFn = <T extends Record<string, unknown>>(
schema: z.ZodType<T>,
contentHash: string,
) => Promise<ExtractResult<T>>;
// ── Adapter (replaces Agent) ────────────────────────────────────────
export type RoleResult<T> = { meta: T; childThread: string | null };
export type RoleFn<T> = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise<RoleResult<T>>;
export type AdapterFn = <T>(prompt: string, schema: z.ZodType<T>) => RoleFn<T>;
/**
* Core agent function. Input is always {@link ThreadContext}, output is always string.
* `Opt` captures agent-specific structured options (required second argument).
*/
export type AgentFn<Opt> = (ctx: ThreadContext, options: Opt) => Promise<string>;
export type AdapterBinding = {
adapter: AdapterFn;
overrides: Partial<Record<string, AdapterFn>> | null;
};
// ── Workflow Runtime & Definition ──────────────────────────────────
export type WorkflowRuntime = {
cas: CasStore;
extract: ExtractFn;
};
export type WorkflowFn = (
thread: ThreadContext,
runtime: WorkflowRuntime,
) => AsyncGenerator<RoleOutput, WorkflowCompletion>;
export type RoleDefinition<Meta extends Record<string, unknown>> = {
description: string;
systemPrompt: string;
schema: z.ZodType<Meta>;
};
export type Moderator<M extends RoleMeta> = (
ctx: ModeratorContext<M>,
) => (keyof M & string) | typeof END;
export type WorkflowDefinition<M extends RoleMeta> = {
description: string;
roles: { [K in keyof M & string]: RoleDefinition<M[K]> };
table: ModeratorTable<M>;
};
// ── Declarative Moderator Table ────────────────────────────────────
export type ModeratorCondition<M extends RoleMeta> = {
name: string;
description: string;
check: (ctx: ModeratorContext<M>) => boolean;
};
export type FALLBACK = "FALLBACK";
export type ModeratorTransition<M extends RoleMeta> = {
condition: ModeratorCondition<M> | FALLBACK;
role: (keyof M & string) | typeof END;
};
export type ModeratorTable<M extends RoleMeta> = Record<
(keyof M & string) | typeof START,
ModeratorTransition<M>[]
>;
// ── Advance Outcome ────────────────────────────────────────────────
export type AdvanceOutcome<M extends RoleMeta> =
| { kind: "complete"; completion: WorkflowCompletion }
| { kind: "yield"; output: RoleOutput; step: RoleStep<M> };
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}