diff --git a/packages/workflow-cas/workflow-cas b/packages/workflow-cas/workflow-cas new file mode 120000 index 0000000..35f60d8 --- /dev/null +++ b/packages/workflow-cas/workflow-cas @@ -0,0 +1 @@ +/home/azureuser/repos/workflow/packages/workflow-cas \ No newline at end of file diff --git a/packages/workflow-protocol/src/index.ts b/packages/workflow-protocol/src/index.ts index 2c51b34..5574042 100644 --- a/packages/workflow-protocol/src/index.ts +++ b/packages/workflow-protocol/src/index.ts @@ -30,6 +30,7 @@ export type { Result, RoleDefinition, RoleFn, + RoleResult, RoleMeta, RoleOutput, RoleStep, diff --git a/packages/workflow-protocol/src/types.ts b/packages/workflow-protocol/src/types.ts index 8ff9643..5707d8f 100644 --- a/packages/workflow-protocol/src/types.ts +++ b/packages/workflow-protocol/src/types.ts @@ -157,7 +157,9 @@ export type AgentBinding = { // ── Adapter (replaces Agent) ──────────────────────────────────────── -export type RoleFn = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise; +export type RoleResult = { meta: T; childThread: string | null }; + +export type RoleFn = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise>; export type AdapterFn = (prompt: string, schema: z.ZodType) => RoleFn; diff --git a/packages/workflow-protocol/workflow-protocol b/packages/workflow-protocol/workflow-protocol new file mode 120000 index 0000000..0cd6c19 --- /dev/null +++ b/packages/workflow-protocol/workflow-protocol @@ -0,0 +1 @@ +/home/azureuser/repos/workflow/packages/workflow-protocol \ No newline at end of file diff --git a/packages/workflow-runtime/src/create-workflow.ts b/packages/workflow-runtime/src/create-workflow.ts index 1d16db9..e23b741 100644 --- a/packages/workflow-runtime/src/create-workflow.ts +++ b/packages/workflow-runtime/src/create-workflow.ts @@ -84,7 +84,8 @@ async function advanceOneRound( const adapter = adapterForRole(binding, next); const roleFn = adapter(roleDef.systemPrompt, roleDef.schema as z.ZodType>); - const meta = await roleFn(modCtx as unknown as ThreadContext, runtime); + const result = await roleFn(modCtx as unknown as ThreadContext, runtime); + const meta = result.meta; const refsFromMeta = resolveExtractedRefs( roleDef as unknown as RoleDefinition>, @@ -110,7 +111,7 @@ async function advanceOneRound( contentHash: step.contentHash, meta: step.meta, refs: step.refs, - childThread: null, + childThread: result.childThread, }, step, }; diff --git a/packages/workflow-runtime/src/index.ts b/packages/workflow-runtime/src/index.ts index ef2184d..cea50cf 100644 --- a/packages/workflow-runtime/src/index.ts +++ b/packages/workflow-runtime/src/index.ts @@ -20,6 +20,7 @@ export type { Result, RoleDefinition, RoleFn, + RoleResult, RoleMeta, RoleOutput, RoleStep, diff --git a/packages/workflow-runtime/src/types.ts b/packages/workflow-runtime/src/types.ts index c171028..e0ac6e8 100644 --- a/packages/workflow-runtime/src/types.ts +++ b/packages/workflow-runtime/src/types.ts @@ -24,6 +24,7 @@ export type { Result, RoleDefinition, RoleFn, + RoleResult, RoleMeta, RoleOutput, RoleStep, diff --git a/packages/workflow-runtime/workflow-runtime b/packages/workflow-runtime/workflow-runtime new file mode 120000 index 0000000..5bfb638 --- /dev/null +++ b/packages/workflow-runtime/workflow-runtime @@ -0,0 +1 @@ +/home/azureuser/repos/workflow/packages/workflow-runtime \ No newline at end of file diff --git a/packages/workflow-template-develop/bundle-entry.ts b/packages/workflow-template-develop/bundle-entry.ts index 17860a5..d178f2d 100644 --- a/packages/workflow-template-develop/bundle-entry.ts +++ b/packages/workflow-template-develop/bundle-entry.ts @@ -4,10 +4,8 @@ * All roles use cursor-agent with workspace auto-extracted from context. */ import { createCursorAgent } from "@uncaged/workflow-agent-cursor"; -import { putContentNodeWithRefs } from "@uncaged/workflow-cas"; -import type { AdapterFn, AgentContext, AgentFnResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime"; import { createWorkflow } from "@uncaged/workflow-runtime"; -import type * as z from "zod/v4"; +import { wrapAgentAsAdapter } from "@uncaged/workflow-util-agent"; import { buildDevelopDescriptor, developWorkflowDefinition } from "./src/index.js"; function requireEnv(name: string): string { @@ -43,19 +41,6 @@ const agent = createCursorAgent({ llmProvider, }); -function wrapAgentAsAdapter(agentFn: (ctx: AgentContext) => Promise): AdapterFn { - return (prompt: string, schema: z.ZodType) => { - return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise => { - const agentCtx: AgentContext = { ...ctx, currentRole: { name: "agent", systemPrompt: prompt } }; - const result = await agentFn(agentCtx); - const output = typeof result === "string" ? result : result.output; - const contentHash = await putContentNodeWithRefs(runtime.cas, output, []); - const extracted = await runtime.extract(schema as z.ZodType>, contentHash); - return extracted.meta as T; - }; - }; -} - const adapter = wrapAgentAsAdapter(agent); const wf = createWorkflow(developWorkflowDefinition, { adapter, overrides: null }); diff --git a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts index 62dc7c3..4daa5b7 100644 --- a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts +++ b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts @@ -11,6 +11,7 @@ import { createWorkflow, END, type ModeratorContext, + type RoleResult, type RoleStep, START, type ThreadContext, @@ -112,13 +113,13 @@ function makeThread(prompt: string) { function createSequenceAdapter(sequence: ReadonlyArray>): AdapterFn { let i = 0; return (_prompt: string, _schema: z.ZodType) => { - return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise => { + return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise> => { const meta = sequence[i] ?? sequence[sequence.length - 1]; if (meta === undefined) { throw new Error("createSequenceAdapter: empty sequence"); } i += 1; - return meta as T; + return { meta: meta as T, childThread: null }; }; }; } @@ -130,9 +131,9 @@ function createTrackingAdapter( meta: Record, ): AdapterFn { return (_prompt: string, _schema: z.ZodType) => { - return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise => { + return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise> => { calls.push(name); - return meta as T; + return { meta: meta as T, childThread: null }; }; }; } diff --git a/packages/workflow-template-solve-issue/bundle-entry.ts b/packages/workflow-template-solve-issue/bundle-entry.ts index 2efd8db..deb775d 100644 --- a/packages/workflow-template-solve-issue/bundle-entry.ts +++ b/packages/workflow-template-solve-issue/bundle-entry.ts @@ -5,11 +5,9 @@ * developer → workflow-as-agent (delegates to "develop" workflow) */ import { createHermesAgent } from "@uncaged/workflow-agent-hermes"; -import { putContentNodeWithRefs } from "@uncaged/workflow-cas"; import { workflowAsAgent } from "@uncaged/workflow-execute"; -import type { AdapterFn, AgentContext, AgentFnResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime"; import { createWorkflow } from "@uncaged/workflow-runtime"; -import type * as z from "zod/v4"; +import { wrapAgentAsAdapter } from "@uncaged/workflow-util-agent"; import { buildSolveIssueDescriptor, solveIssueWorkflowDefinition } from "./src/index.js"; function optionalEnv(name: string): string | null { @@ -20,19 +18,6 @@ function optionalEnv(name: string): string | null { return value; } -function wrapAgentAsAdapter(agentFn: (ctx: AgentContext) => Promise): AdapterFn { - return (prompt: string, schema: z.ZodType) => { - return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise => { - const agentCtx: AgentContext = { ...ctx, currentRole: { name: "agent", systemPrompt: prompt } }; - const result = await agentFn(agentCtx); - const output = typeof result === "string" ? result : result.output; - const contentHash = await putContentNodeWithRefs(runtime.cas, output, []); - const extracted = await runtime.extract(schema as z.ZodType>, contentHash); - return extracted.meta as T; - }; - }; -} - const hermesAgent = createHermesAgent({ model: optionalEnv("WORKFLOW_HERMES_MODEL"), timeout: optionalEnv("WORKFLOW_HERMES_TIMEOUT") diff --git a/packages/workflow-util-agent/package.json b/packages/workflow-util-agent/package.json index e668f0a..a2b702e 100644 --- a/packages/workflow-util-agent/package.json +++ b/packages/workflow-util-agent/package.json @@ -14,6 +14,7 @@ "test": "bun test" }, "dependencies": { - "@uncaged/workflow-runtime": "workspace:*" + "@uncaged/workflow-runtime": "workspace:*", + "@uncaged/workflow-cas": "workspace:*" } } diff --git a/packages/workflow-util-agent/src/index.ts b/packages/workflow-util-agent/src/index.ts index 042d418..323c90a 100644 --- a/packages/workflow-util-agent/src/index.ts +++ b/packages/workflow-util-agent/src/index.ts @@ -1,3 +1,4 @@ export { buildAgentPrompt, buildThreadInput } from "./build-agent-prompt.js"; export type { SpawnCliConfig, SpawnCliError, SpawnCliResult } from "./spawn-cli.js"; export { spawnCli } from "./spawn-cli.js"; +export { wrapAgentAsAdapter } from "./wrap-agent-as-adapter.js"; diff --git a/packages/workflow-util-agent/src/wrap-agent-as-adapter.ts b/packages/workflow-util-agent/src/wrap-agent-as-adapter.ts new file mode 100644 index 0000000..1af09cb --- /dev/null +++ b/packages/workflow-util-agent/src/wrap-agent-as-adapter.ts @@ -0,0 +1,31 @@ +import { putContentNodeWithRefs } from "@uncaged/workflow-cas"; +import type { + AdapterFn, + AgentContext, + AgentFnResult, + RoleResult, + ThreadContext, + WorkflowRuntime, +} from "@uncaged/workflow-runtime"; +import type * as z from "zod/v4"; + +/** + * Wraps a legacy AgentFn into an AdapterFn. + * The agent produces a string (or { output, childThread }); the adapter + * stores the output in CAS, runs extract, and returns typed meta + childThread. + */ +export function wrapAgentAsAdapter( + agentFn: (ctx: AgentContext) => Promise, +): AdapterFn { + return (prompt: string, schema: z.ZodType) => { + return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise> => { + const agentCtx: AgentContext = { ...ctx, currentRole: { name: "agent", systemPrompt: prompt } }; + const result = await agentFn(agentCtx); + const output = typeof result === "string" ? result : result.output; + const childThread = typeof result === "string" ? null : result.childThread; + const contentHash = await putContentNodeWithRefs(runtime.cas, output, []); + const extracted = await runtime.extract(schema as z.ZodType>, contentHash); + return { meta: extracted.meta as T, childThread }; + }; + }; +} diff --git a/packages/workflow-util-agent/tsconfig.json b/packages/workflow-util-agent/tsconfig.json index 1187cda..6013afa 100644 --- a/packages/workflow-util-agent/tsconfig.json +++ b/packages/workflow-util-agent/tsconfig.json @@ -6,5 +6,5 @@ "composite": true }, "include": ["src/**/*.ts"], - "references": [{ "path": "../workflow-runtime" }] + "references": [{ "path": "../workflow-runtime" }, { "path": "../workflow-cas" }] } diff --git a/packages/workflow-util/workflow-util b/packages/workflow-util/workflow-util new file mode 120000 index 0000000..2ab661e --- /dev/null +++ b/packages/workflow-util/workflow-util @@ -0,0 +1 @@ +/home/azureuser/repos/workflow/packages/workflow-util \ No newline at end of file