refactor(workflow-runtime): use full ThreadContext in WorkflowFn
Redefine WorkflowFn to accept a complete ThreadContext plus WorkflowRuntime dependencies, removing ThreadInput and WorkflowFnOptions. Move thread context construction into engine executeThread, update runtime loop/agent paths, and align templates/docs/tests with template-only definition exports. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -107,7 +107,7 @@ Init 生成的骨架:\`templates/\` 下放可复用定义,\`workflows/\` 下
|
|||||||
2. **编写 RoleDefinition**:为每个角色写 Zod \`schema\`,补齐 \`systemPrompt\` / \`extractPrompt\` / \`description\`。
|
2. **编写 RoleDefinition**:为每个角色写 Zod \`schema\`,补齐 \`systemPrompt\` / \`extractPrompt\` / \`description\`。
|
||||||
3. **编写 Moderator**:根据 \`ctx.steps\` 与业务状态返回下一个角色名或 \`END\`。
|
3. **编写 Moderator**:根据 \`ctx.steps\` 与业务状态返回下一个角色名或 \`END\`。
|
||||||
4. **组装 WorkflowDefinition**:在模板 \`index\` 中导出 definition(以及必要的角色 / moderator 导出)。
|
4. **组装 WorkflowDefinition**:在模板 \`index\` 中导出 definition(以及必要的角色 / moderator 导出)。
|
||||||
5. **实例化**:在 workflow 包中使用 \`createWorkflow(def, binding)\`(或项目约定的封装)绑定 **AgentFn**;**ExtractFn** 由引擎从 **workflow.yaml** 注入 \`WorkflowFnOptions\`。
|
5. **实例化**:在 workflow 包中使用 \`createWorkflow(def, binding)\`(或项目约定的封装)绑定 **AgentFn**;**ExtractFn** 由引擎从 **workflow.yaml** 注入 \`WorkflowRuntime\`。
|
||||||
6. **构建**:打包为单个 **.esm.js** bundle,使用 **uncaged-workflow add** 注册。
|
6. **构建**:打包为单个 **.esm.js** bundle,使用 **uncaged-workflow add** 注册。
|
||||||
|
|
||||||
## 4. 编码规范
|
## 4. 编码规范
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { mkdtempSync } from "node:fs";
|
|||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { createCasStore } from "@uncaged/workflow";
|
import { createCasStore } from "@uncaged/workflow";
|
||||||
import { START, type ThreadContext } from "@uncaged/workflow-runtime";
|
import { START, type AgentContext } from "@uncaged/workflow-runtime";
|
||||||
|
|
||||||
import { createLlmAdapter } from "../src/create-llm-adapter.js";
|
import { createLlmAdapter } from "../src/create-llm-adapter.js";
|
||||||
|
|
||||||
const casDir = mkdtempSync(join(tmpdir(), "wf-llm-adapter-cas-"));
|
const casDir = mkdtempSync(join(tmpdir(), "wf-llm-adapter-cas-"));
|
||||||
const testCas = createCasStore(casDir);
|
const testCas = createCasStore(casDir);
|
||||||
|
|
||||||
function makeCtx(userContent: string): ThreadContext {
|
function makeCtx(userContent: string): AgentContext {
|
||||||
return {
|
return {
|
||||||
start: {
|
start: {
|
||||||
role: START,
|
role: START,
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import {
|
|||||||
type RoleOutput,
|
type RoleOutput,
|
||||||
type RoleStep,
|
type RoleStep,
|
||||||
START,
|
START,
|
||||||
type ThreadInput,
|
type ThreadContext,
|
||||||
type WorkflowCompletion,
|
type WorkflowCompletion,
|
||||||
type WorkflowDefinition,
|
type WorkflowDefinition,
|
||||||
type WorkflowFn,
|
type WorkflowFn,
|
||||||
type WorkflowFnOptions,
|
type WorkflowRuntime,
|
||||||
} from "../types.js";
|
} from "../types.js";
|
||||||
import { mergeRefsWithContentHash } from "../util/index.js";
|
import { mergeRefsWithContentHash } from "../util/index.js";
|
||||||
|
|
||||||
@@ -57,18 +57,12 @@ async function advanceOneRound<M extends RoleMeta>(
|
|||||||
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
|
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
|
||||||
binding: AgentBinding,
|
binding: AgentBinding,
|
||||||
params: {
|
params: {
|
||||||
start: ModeratorContext<M>["start"];
|
thread: ModeratorContext<M>;
|
||||||
steps: RoleStep<M>[];
|
runtime: WorkflowRuntime;
|
||||||
options: WorkflowFnOptions;
|
|
||||||
},
|
},
|
||||||
): Promise<AdvanceOutcome<M>> {
|
): Promise<AdvanceOutcome<M>> {
|
||||||
const { start, steps, options } = params;
|
const { thread, runtime } = params;
|
||||||
const modCtx: ModeratorContext<M> = {
|
const modCtx: ModeratorContext<M> = thread;
|
||||||
threadId: options.threadId,
|
|
||||||
depth: options.depth,
|
|
||||||
start,
|
|
||||||
steps,
|
|
||||||
};
|
|
||||||
|
|
||||||
const next = def.moderator(modCtx);
|
const next = def.moderator(modCtx);
|
||||||
if (!isRoleNext(next)) {
|
if (!isRoleNext(next)) {
|
||||||
@@ -86,7 +80,7 @@ async function advanceOneRound<M extends RoleMeta>(
|
|||||||
const agentCtx: AgentContext<M> = {
|
const agentCtx: AgentContext<M> = {
|
||||||
...modCtx,
|
...modCtx,
|
||||||
currentRole: { name: next, systemPrompt: roleDef.systemPrompt },
|
currentRole: { name: next, systemPrompt: roleDef.systemPrompt },
|
||||||
cas: options.cas,
|
cas: runtime.cas,
|
||||||
};
|
};
|
||||||
|
|
||||||
const agent = agentForRole(binding, next);
|
const agent = agentForRole(binding, next);
|
||||||
@@ -97,13 +91,13 @@ async function advanceOneRound<M extends RoleMeta>(
|
|||||||
agentContent: raw,
|
agentContent: raw,
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta = await options.extract(
|
const meta = await runtime.extract(
|
||||||
roleDef.schema as unknown as z.ZodType<Record<string, unknown>>,
|
roleDef.schema as z.ZodType<Record<string, unknown>>,
|
||||||
roleDef.extractPrompt,
|
roleDef.extractPrompt,
|
||||||
extractCtx as unknown as ExtractContext,
|
extractCtx as unknown as ExtractContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentHash = await putContentBlob(options.cas, raw);
|
const contentHash = await putContentBlob(runtime.cas, raw);
|
||||||
const refs = mergeRefsWithContentHash(
|
const refs = mergeRefsWithContentHash(
|
||||||
resolveExtractedRefs(roleDef as unknown as RoleDefinition<Record<string, unknown>>, meta),
|
resolveExtractedRefs(roleDef as unknown as RoleDefinition<Record<string, unknown>>, meta),
|
||||||
contentHash,
|
contentHash,
|
||||||
@@ -133,7 +127,7 @@ async function advanceOneRound<M extends RoleMeta>(
|
|||||||
* Binds pure role definitions + moderator to runtime agents.
|
* Binds pure role definitions + moderator to runtime agents.
|
||||||
* Assign with `export const run = createWorkflow(def, binding)`.
|
* Assign with `export const run = createWorkflow(def, binding)`.
|
||||||
*
|
*
|
||||||
* Structured meta extraction is delegated to {@link WorkflowFnOptions.extract}, which the
|
* Structured meta extraction is delegated to {@link WorkflowRuntime.extract}, which the
|
||||||
* engine resolves from the workflow registry's `extract` scene.
|
* engine resolves from the workflow registry's `extract` scene.
|
||||||
*/
|
*/
|
||||||
export function createWorkflow<M extends RoleMeta>(
|
export function createWorkflow<M extends RoleMeta>(
|
||||||
@@ -141,38 +135,26 @@ export function createWorkflow<M extends RoleMeta>(
|
|||||||
binding: AgentBinding,
|
binding: AgentBinding,
|
||||||
): WorkflowFn {
|
): WorkflowFn {
|
||||||
return async function* workflowLoop(
|
return async function* workflowLoop(
|
||||||
input: ThreadInput,
|
thread: ThreadContext,
|
||||||
options: WorkflowFnOptions,
|
runtime: WorkflowRuntime,
|
||||||
): AsyncGenerator<RoleOutput, WorkflowCompletion> {
|
): AsyncGenerator<RoleOutput, WorkflowCompletion> {
|
||||||
const nowMs = Date.now();
|
if (thread.start.role !== START) {
|
||||||
const start: ModeratorContext<M>["start"] = {
|
throw new Error(`workflow loop expected start role to be ${START}`);
|
||||||
role: START,
|
}
|
||||||
content: input.prompt,
|
const maxRounds = thread.start.meta.maxRounds;
|
||||||
meta: { maxRounds: options.maxRounds },
|
let currentThread = thread as ModeratorContext<M>;
|
||||||
timestamp: nowMs,
|
|
||||||
};
|
|
||||||
|
|
||||||
const baseTs = Date.now();
|
|
||||||
let steps: RoleStep<M>[] = input.steps.map((out, i) => ({
|
|
||||||
role: out.role,
|
|
||||||
contentHash: out.contentHash,
|
|
||||||
meta: out.meta,
|
|
||||||
refs: out.refs,
|
|
||||||
timestamp: baseTs + i,
|
|
||||||
})) as RoleStep<M>[];
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (steps.length >= options.maxRounds) {
|
if (currentThread.steps.length >= maxRounds) {
|
||||||
return {
|
return {
|
||||||
returnCode: 0,
|
returnCode: 0,
|
||||||
summary: `completed: reached maxRounds (${options.maxRounds})`,
|
summary: `completed: reached maxRounds (${maxRounds})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const outcome = await advanceOneRound(def, binding, {
|
const outcome = await advanceOneRound(def, binding, {
|
||||||
start,
|
thread: currentThread,
|
||||||
steps,
|
runtime,
|
||||||
options,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (outcome.kind === "complete") {
|
if (outcome.kind === "complete") {
|
||||||
@@ -180,7 +162,10 @@ export function createWorkflow<M extends RoleMeta>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
yield outcome.output;
|
yield outcome.output;
|
||||||
steps = [...steps, outcome.step];
|
currentThread = {
|
||||||
|
...currentThread,
|
||||||
|
steps: [...currentThread.steps, outcome.step],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,10 @@ export type {
|
|||||||
RoleStep,
|
RoleStep,
|
||||||
StartStep,
|
StartStep,
|
||||||
ThreadContext,
|
ThreadContext,
|
||||||
ThreadInput,
|
|
||||||
WorkflowCompletion,
|
WorkflowCompletion,
|
||||||
WorkflowDefinition,
|
WorkflowDefinition,
|
||||||
WorkflowFn,
|
WorkflowFn,
|
||||||
WorkflowFnOptions,
|
WorkflowRuntime,
|
||||||
WorkflowResult,
|
WorkflowResult,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
export { END, START } from "./types.js";
|
export { END, START } from "./types.js";
|
||||||
|
|||||||
@@ -38,18 +38,8 @@ export type WorkflowResult = WorkflowCompletion & {
|
|||||||
rootHash: string;
|
rootHash: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Input to a workflow — prompt plus optional historical steps for fork/resume. */
|
/** Runtime dependencies passed to a workflow bundle's `run` export (engine-provided). */
|
||||||
export type ThreadInput = {
|
export type WorkflowRuntime = {
|
||||||
prompt: string;
|
|
||||||
steps: RoleOutput[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Options passed to a workflow bundle's `run` export (engine-provided). */
|
|
||||||
export type WorkflowFnOptions = {
|
|
||||||
threadId: string;
|
|
||||||
maxRounds: number;
|
|
||||||
/** Nesting depth for workflow-as-agent chains; root threads use `0`. */
|
|
||||||
depth: number;
|
|
||||||
/** Global CAS store for Merkle content blobs (role step bodies). */
|
/** Global CAS store for Merkle content blobs (role step bodies). */
|
||||||
cas: CasStore;
|
cas: CasStore;
|
||||||
/** Structured meta extraction; resolved from workflow.yaml `extract` scene by the engine. */
|
/** Structured meta extraction; resolved from workflow.yaml `extract` scene by the engine. */
|
||||||
@@ -58,8 +48,8 @@ export type WorkflowFnOptions = {
|
|||||||
|
|
||||||
/** Bundle contract — named export `run` is a function returning an AsyncGenerator. */
|
/** Bundle contract — named export `run` is a function returning an AsyncGenerator. */
|
||||||
export type WorkflowFn = (
|
export type WorkflowFn = (
|
||||||
input: ThreadInput,
|
thread: ThreadContext,
|
||||||
options: WorkflowFnOptions,
|
runtime: WorkflowRuntime,
|
||||||
) => AsyncGenerator<RoleOutput, WorkflowCompletion>;
|
) => AsyncGenerator<RoleOutput, WorkflowCompletion>;
|
||||||
|
|
||||||
/** Engine start frame: initial prompt + thread identity. */
|
/** Engine start frame: initial prompt + thread identity. */
|
||||||
@@ -81,15 +71,18 @@ export type RoleStep<M extends RoleMeta> = {
|
|||||||
};
|
};
|
||||||
}[keyof M & string];
|
}[keyof M & string];
|
||||||
|
|
||||||
/** Phase 1: Moderator decides next role. */
|
/** Thread runtime context shared by moderator/agent/extractor phases. */
|
||||||
export type ModeratorContext<M extends RoleMeta = RoleMeta> = {
|
export type ThreadContext<M extends RoleMeta = RoleMeta> = {
|
||||||
threadId: string;
|
threadId: string;
|
||||||
/** Same as `WorkflowFnOptions.depth` for the active thread. */
|
/** Nesting depth for workflow-as-agent chains; root threads use `0`. */
|
||||||
depth: number;
|
depth: number;
|
||||||
start: StartStep;
|
start: StartStep;
|
||||||
steps: RoleStep<M>[];
|
steps: RoleStep<M>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Phase 1: Moderator decides next role. */
|
||||||
|
export type ModeratorContext<M extends RoleMeta = RoleMeta> = ThreadContext<M>;
|
||||||
|
|
||||||
/** Phase 2: Agent executes — knows its role and prompt. */
|
/** Phase 2: Agent executes — knows its role and prompt. */
|
||||||
export type AgentContext<M extends RoleMeta = RoleMeta> = ModeratorContext<M> & {
|
export type AgentContext<M extends RoleMeta = RoleMeta> = ModeratorContext<M> & {
|
||||||
currentRole: {
|
currentRole: {
|
||||||
@@ -104,9 +97,6 @@ export type ExtractContext<M extends RoleMeta = RoleMeta> = AgentContext<M> & {
|
|||||||
agentContent: string;
|
agentContent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Alias — most external consumers see the agent-phase context. */
|
|
||||||
export type ThreadContext<M extends RoleMeta = RoleMeta> = AgentContext<M>;
|
|
||||||
|
|
||||||
/** Raw string output from an LLM/CLI adapter; meta is extracted by the engine. */
|
/** Raw string output from an LLM/CLI adapter; meta is extracted by the engine. */
|
||||||
export type AgentFn = (ctx: AgentContext) => Promise<string>;
|
export type AgentFn = (ctx: AgentContext) => Promise<string>;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Reference **develop** workflow template: plan phases, implement in a loop, review, test, then commit.
|
Reference **develop** workflow template: plan phases, implement in a loop, review, test, then commit.
|
||||||
|
|
||||||
Export a `WorkflowDefinition` and `createDevelopRun` so a host can bind agents/LLM and run the same graph the bundled `.esm.js` would use. Use `buildDevelopDescriptor()` when assembling `descriptor` metadata for a bundle.
|
Export a pure `WorkflowDefinition` (`developWorkflowDefinition`) and role/moderator pieces. Workflow instantiation (`createWorkflow(definition, binding)`) happens in the workflow instance layer, not in this template package.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -15,10 +15,10 @@ In this monorepo: `workspace:*` for `@uncaged/workflow-template-develop` and `@u
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createDevelopRun, developWorkflowDefinition } from "@uncaged/workflow-template-develop";
|
import { createWorkflow } from "@uncaged/workflow";
|
||||||
|
import { developWorkflowDefinition } from "@uncaged/workflow-template-develop";
|
||||||
|
|
||||||
const run = createDevelopRun(binding);
|
const run = createWorkflow(developWorkflowDefinition, binding);
|
||||||
// run(...) executes the develop moderator graph with your AgentBinding
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roles
|
## Roles
|
||||||
@@ -46,7 +46,6 @@ Also exported: role factories/meta schemas (`plannerRole`, `coderRole`, …), `D
|
|||||||
|
|
||||||
| Export | Description |
|
| Export | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `createDevelopRun` | `createWorkflow(developWorkflowDefinition, …)` factory |
|
|
||||||
| `developWorkflowDefinition` | `description`, `roles`, `developModerator` |
|
| `developWorkflowDefinition` | `description`, `roles`, `developModerator` |
|
||||||
| `developModerator` | `Moderator<DevelopMeta>` |
|
| `developModerator` | `Moderator<DevelopMeta>` |
|
||||||
| `buildDevelopDescriptor` | `buildDescriptor({ … })` for bundle metadata |
|
| `buildDevelopDescriptor` | `buildDescriptor({ … })` for bundle metadata |
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createWorkflow } from "@uncaged/workflow";
|
import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
||||||
import type { AgentBinding, WorkflowDefinition, WorkflowFn } from "@uncaged/workflow-runtime";
|
|
||||||
|
|
||||||
import { developModerator } from "./moderator.js";
|
import { developModerator } from "./moderator.js";
|
||||||
import { DEVELOP_WORKFLOW_DESCRIPTION, type DevelopMeta, developRoles } from "./roles.js";
|
import { DEVELOP_WORKFLOW_DESCRIPTION, type DevelopMeta, developRoles } from "./roles.js";
|
||||||
@@ -36,7 +35,3 @@ export const developWorkflowDefinition: WorkflowDefinition<DevelopMeta> = {
|
|||||||
roles: developRoles,
|
roles: developRoles,
|
||||||
moderator: developModerator,
|
moderator: developModerator,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createDevelopRun(binding: AgentBinding): WorkflowFn {
|
|
||||||
return createWorkflow(developWorkflowDefinition, binding);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Reference **solve-issue** workflow template: prepare a repo, delegate implementation to the **develop** workflow, then submit (e.g. open a PR).
|
Reference **solve-issue** workflow template: prepare a repo, delegate implementation to the **develop** workflow, then submit (e.g. open a PR).
|
||||||
|
|
||||||
`createSolveIssueRun` wires the `developer` role to `workflowAsAgent("develop")` by default; `binding.overrides.developer` wins if you pass one (for tests or custom hosts).
|
This package exports a pure `WorkflowDefinition` (`solveIssueWorkflowDefinition`). Workflow instantiation (`createWorkflow(definition, binding)`) and any role-specific agent wiring (for example delegating `developer` to `workflowAsAgent("develop")`) are done in the workflow instance layer.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -15,9 +15,10 @@ In this monorepo: `workspace:*` for this package and `@uncaged/workflow`.
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createSolveIssueRun, solveIssueWorkflowDefinition } from "@uncaged/workflow-template-solve-issue";
|
import { createWorkflow } from "@uncaged/workflow";
|
||||||
|
import { solveIssueWorkflowDefinition } from "@uncaged/workflow-template-solve-issue";
|
||||||
|
|
||||||
const run = createSolveIssueRun(binding);
|
const run = createWorkflow(solveIssueWorkflowDefinition, binding);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roles
|
## Roles
|
||||||
@@ -41,7 +42,6 @@ Also exported: `preparerRole`, `developerRole`, `submitterRole` and their Zod me
|
|||||||
|
|
||||||
| Export | Description |
|
| Export | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `createSolveIssueRun` | Merges `developer` override with `workflowAsAgent("develop")`, then `createWorkflow` |
|
|
||||||
| `solveIssueWorkflowDefinition` | `description`, `roles`, `solveIssueModerator` |
|
| `solveIssueWorkflowDefinition` | `description`, `roles`, `solveIssueModerator` |
|
||||||
| `solveIssueModerator` | Linear `Moderator<SolveIssueMeta>` |
|
| `solveIssueModerator` | Linear `Moderator<SolveIssueMeta>` |
|
||||||
| `buildSolveIssueDescriptor` | Descriptor helper for bundles |
|
| `buildSolveIssueDescriptor` | Descriptor helper for bundles |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|||||||
import { mkdtemp, rm } from "node:fs/promises";
|
import { mkdtemp, rm } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { createCasStore, createExtract } from "@uncaged/workflow";
|
import { createCasStore, createExtract, createWorkflow } from "@uncaged/workflow";
|
||||||
import {
|
import {
|
||||||
END,
|
END,
|
||||||
type ModeratorContext,
|
type ModeratorContext,
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from "@uncaged/workflow-runtime";
|
} from "@uncaged/workflow-runtime";
|
||||||
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
||||||
import type { DeveloperMeta } from "../src/developer.js";
|
import type { DeveloperMeta } from "../src/developer.js";
|
||||||
import { createSolveIssueRun, solveIssueModerator } from "../src/index.js";
|
import { solveIssueModerator, solveIssueWorkflowDefinition } from "../src/index.js";
|
||||||
import type { PreparerMeta, SubmitterMeta } from "../src/roles/index.js";
|
import type { PreparerMeta, SubmitterMeta } from "../src/roles/index.js";
|
||||||
import type { SolveIssueMeta } from "../src/roles.js";
|
import type { SolveIssueMeta } from "../src/roles.js";
|
||||||
|
|
||||||
@@ -123,6 +123,20 @@ const stubExtract = createExtract({
|
|||||||
model: "test",
|
model: "test",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function makeThread(prompt: string) {
|
||||||
|
return {
|
||||||
|
threadId: "01TEST000000000000000000TR",
|
||||||
|
depth: 0,
|
||||||
|
start: {
|
||||||
|
role: START,
|
||||||
|
content: prompt,
|
||||||
|
meta: { maxRounds: 20 },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
steps: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("solveIssueModerator", () => {
|
describe("solveIssueModerator", () => {
|
||||||
test("routes initial → preparer → developer → submitter → END", () => {
|
test("routes initial → preparer → developer → submitter → END", () => {
|
||||||
expect(solveIssueModerator(makeCtx(20, []))).toBe("preparer");
|
expect(solveIssueModerator(makeCtx(20, []))).toBe("preparer");
|
||||||
@@ -169,7 +183,7 @@ describe("solveIssueModerator", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createSolveIssueRun", () => {
|
describe("solveIssueWorkflowDefinition + createWorkflow", () => {
|
||||||
let restoreFetch: (() => void) | null = null;
|
let restoreFetch: (() => void) | null = null;
|
||||||
let casDir: string | undefined;
|
let casDir: string | undefined;
|
||||||
|
|
||||||
@@ -199,17 +213,13 @@ describe("createSolveIssueRun", () => {
|
|||||||
casDir = await mkdtemp(join(tmpdir(), "solve-issue-cas-"));
|
casDir = await mkdtemp(join(tmpdir(), "solve-issue-cas-"));
|
||||||
const cas = createCasStore(casDir);
|
const cas = createCasStore(casDir);
|
||||||
|
|
||||||
// Override developer so the test does not spin up a child workflow.
|
const run = createWorkflow(solveIssueWorkflowDefinition, {
|
||||||
const run = createSolveIssueRun({
|
|
||||||
agent: async () => "",
|
agent: async () => "",
|
||||||
overrides: { developer: async () => "stub-root-hash" },
|
overrides: { developer: async () => "stub-root-hash" },
|
||||||
});
|
});
|
||||||
const gen = run(
|
const gen = run(
|
||||||
{ prompt: "task", steps: [] },
|
makeThread("task"),
|
||||||
{
|
{
|
||||||
threadId: "01TEST000000000000000000TR",
|
|
||||||
maxRounds: 20,
|
|
||||||
depth: 0,
|
|
||||||
cas,
|
cas,
|
||||||
extract: stubExtract,
|
extract: stubExtract,
|
||||||
},
|
},
|
||||||
@@ -246,7 +256,7 @@ describe("createSolveIssueRun", () => {
|
|||||||
const cas = createCasStore(casDir);
|
const cas = createCasStore(casDir);
|
||||||
|
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const run = createSolveIssueRun({
|
const run = createWorkflow(solveIssueWorkflowDefinition, {
|
||||||
agent: async () => {
|
agent: async () => {
|
||||||
calls.push("default");
|
calls.push("default");
|
||||||
return "";
|
return "";
|
||||||
@@ -267,11 +277,8 @@ describe("createSolveIssueRun", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const gen = run(
|
const gen = run(
|
||||||
{ prompt: "task", steps: [] },
|
makeThread("task"),
|
||||||
{
|
{
|
||||||
threadId: "01TEST000000000000000000TR",
|
|
||||||
maxRounds: 20,
|
|
||||||
depth: 0,
|
|
||||||
cas,
|
cas,
|
||||||
extract: stubExtract,
|
extract: stubExtract,
|
||||||
},
|
},
|
||||||
@@ -288,56 +295,6 @@ describe("createSolveIssueRun", () => {
|
|||||||
expect(calls).toEqual(["submitter"]);
|
expect(calls).toEqual(["submitter"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("developer defaults to workflowAsAgent override (caller override still wins)", async () => {
|
|
||||||
const PREPARER_META: PreparerMeta = {
|
|
||||||
repoPath: "/tmp/r",
|
|
||||||
defaultBranch: "main",
|
|
||||||
conventions: null,
|
|
||||||
toolchain: { packageManager: null, testCommand: null, lintCommand: null, buildCommand: null },
|
|
||||||
};
|
|
||||||
const DEVELOPER_META: DeveloperMeta = {
|
|
||||||
branch: "feat/y",
|
|
||||||
commitSha: "def5678",
|
|
||||||
filesChanged: ["b.ts"],
|
|
||||||
summary: "more work",
|
|
||||||
};
|
|
||||||
restoreFetch = installMockChatCompletions([PREPARER_META, DEVELOPER_META]);
|
|
||||||
|
|
||||||
casDir = await mkdtemp(join(tmpdir(), "solve-issue-cas-"));
|
|
||||||
const cas = createCasStore(casDir);
|
|
||||||
|
|
||||||
let developerInvocations = 0;
|
|
||||||
const run = createSolveIssueRun({
|
|
||||||
agent: async () => "",
|
|
||||||
overrides: {
|
|
||||||
developer: async () => {
|
|
||||||
developerInvocations += 1;
|
|
||||||
return "stub-root-hash";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const gen = run(
|
|
||||||
{ prompt: "task", steps: [] },
|
|
||||||
{
|
|
||||||
threadId: "01TEST000000000000000000TR",
|
|
||||||
maxRounds: 20,
|
|
||||||
depth: 0,
|
|
||||||
cas,
|
|
||||||
extract: stubExtract,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// preparer
|
|
||||||
await gen.next();
|
|
||||||
// developer (caller override should be invoked, NOT workflowAsAgent default)
|
|
||||||
const devYield = await gen.next();
|
|
||||||
expect(devYield.done).toBe(false);
|
|
||||||
if (devYield.done) {
|
|
||||||
throw new Error("expected yield");
|
|
||||||
}
|
|
||||||
expect(devYield.value.role).toBe("developer");
|
|
||||||
expect(devYield.value.meta).toEqual(DEVELOPER_META);
|
|
||||||
expect(developerInvocations).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildSolveIssueDescriptor", () => {
|
describe("buildSolveIssueDescriptor", () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createWorkflow, workflowAsAgent } from "@uncaged/workflow";
|
import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
||||||
import type { AgentBinding, WorkflowDefinition, WorkflowFn } from "@uncaged/workflow-runtime";
|
|
||||||
|
|
||||||
import { solveIssueModerator } from "./moderator.js";
|
import { solveIssueModerator } from "./moderator.js";
|
||||||
import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, type SolveIssueMeta, solveIssueRoles } from "./roles.js";
|
import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, type SolveIssueMeta, solveIssueRoles } from "./roles.js";
|
||||||
@@ -31,22 +30,3 @@ export const solveIssueWorkflowDefinition: WorkflowDefinition<SolveIssueMeta> =
|
|||||||
roles: solveIssueRoles,
|
roles: solveIssueRoles,
|
||||||
moderator: solveIssueModerator,
|
moderator: solveIssueModerator,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the solve-issue {@link WorkflowFn}.
|
|
||||||
*
|
|
||||||
* The `developer` role always delegates to the registered `develop` workflow via
|
|
||||||
* {@link workflowAsAgent}; if the caller supplies their own `developer` override in
|
|
||||||
* `binding.overrides`, it takes precedence so tests and custom hosts can stub it.
|
|
||||||
*/
|
|
||||||
export function createSolveIssueRun(binding: AgentBinding): WorkflowFn {
|
|
||||||
const developerOverride = binding.overrides?.developer ?? workflowAsAgent("develop");
|
|
||||||
const mergedBinding: AgentBinding = {
|
|
||||||
agent: binding.agent,
|
|
||||||
overrides: {
|
|
||||||
...(binding.overrides ?? {}),
|
|
||||||
developer: developerOverride,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return createWorkflow(solveIssueWorkflowDefinition, mergedBinding);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { createCasStore, putContentMerkleNode } from "@uncaged/workflow";
|
import { createCasStore, putContentMerkleNode } from "@uncaged/workflow";
|
||||||
import { START, type ThreadContext } from "@uncaged/workflow-runtime";
|
import { START, type AgentContext } from "@uncaged/workflow-runtime";
|
||||||
|
|
||||||
import { buildAgentPrompt } from "../src/index.js";
|
import { buildAgentPrompt } from "../src/index.js";
|
||||||
|
|
||||||
function startTask(content: string): ThreadContext["start"] {
|
function startTask(content: string): AgentContext["start"] {
|
||||||
return {
|
return {
|
||||||
role: START,
|
role: START,
|
||||||
content,
|
content,
|
||||||
@@ -29,7 +29,7 @@ describe("buildAgentPrompt", () => {
|
|||||||
|
|
||||||
test("includes system prompt and full task; omits tools when there are no steps", async () => {
|
test("includes system prompt and full task; omits tools when there are no steps", async () => {
|
||||||
const cas = createCasStore(casRoot);
|
const cas = createCasStore(casRoot);
|
||||||
const ctx: ThreadContext = {
|
const ctx: AgentContext = {
|
||||||
start: startTask("fix the bug"),
|
start: startTask("fix the bug"),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
steps: [],
|
steps: [],
|
||||||
@@ -47,7 +47,7 @@ describe("buildAgentPrompt", () => {
|
|||||||
test("single step shows full content and meta, and includes tools", async () => {
|
test("single step shows full content and meta, and includes tools", async () => {
|
||||||
const cas = createCasStore(casRoot);
|
const cas = createCasStore(casRoot);
|
||||||
const onlyHash = await putContentMerkleNode(cas, "only step full body");
|
const onlyHash = await putContentMerkleNode(cas, "only step full body");
|
||||||
const ctx: ThreadContext = {
|
const ctx: AgentContext = {
|
||||||
start: startTask("user task"),
|
start: startTask("user task"),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
threadId: "01TEST000000000000000000TR",
|
threadId: "01TEST000000000000000000TR",
|
||||||
@@ -77,7 +77,7 @@ describe("buildAgentPrompt", () => {
|
|||||||
const cas = createCasStore(casRoot);
|
const cas = createCasStore(casRoot);
|
||||||
const plannerHash = await putContentMerkleNode(cas, "PLANNER_SECRET_FULL_TEXT");
|
const plannerHash = await putContentMerkleNode(cas, "PLANNER_SECRET_FULL_TEXT");
|
||||||
const coderHash = await putContentMerkleNode(cas, "last step full content");
|
const coderHash = await putContentMerkleNode(cas, "last step full content");
|
||||||
const ctx: ThreadContext = {
|
const ctx: AgentContext = {
|
||||||
start: startTask("first message full: task content here"),
|
start: startTask("first message full: task content here"),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
threadId: "01TEST000000000000000000TR",
|
threadId: "01TEST000000000000000000TR",
|
||||||
@@ -118,7 +118,7 @@ describe("buildAgentPrompt", () => {
|
|||||||
const ha = await putContentMerkleNode(cas, "HIDDEN_A");
|
const ha = await putContentMerkleNode(cas, "HIDDEN_A");
|
||||||
const hb = await putContentMerkleNode(cas, "HIDDEN_B_MIDDLE");
|
const hb = await putContentMerkleNode(cas, "HIDDEN_B_MIDDLE");
|
||||||
const hc = await putContentMerkleNode(cas, "VISIBLE_LAST");
|
const hc = await putContentMerkleNode(cas, "VISIBLE_LAST");
|
||||||
const ctx: ThreadContext = {
|
const ctx: AgentContext = {
|
||||||
start: startTask("start"),
|
start: startTask("start"),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
threadId: "01TEST000000000000000000TR",
|
threadId: "01TEST000000000000000000TR",
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ describe("executeThread", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("pre-filled ThreadInput.steps skips roles already present", async () => {
|
test("pre-filled input.steps skips roles already present", async () => {
|
||||||
restoreFetch = installMockChatCompletions([{ diff: "+ok" }]);
|
restoreFetch = installMockChatCompletions([{ diff: "+ok" }]);
|
||||||
|
|
||||||
const root = await mkdtemp(join(tmpdir(), "wf-engine-fork-"));
|
const root = await mkdtemp(join(tmpdir(), "wf-engine-fork-"));
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ export const descriptor = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export async function* run(input, options) {
|
export async function* run(thread, runtime) {
|
||||||
const cas = options.cas;
|
const cas = runtime.cas;
|
||||||
const h = await putContentMerkleNode(cas, "child-body");
|
const h = await putContentMerkleNode(cas, "child-body");
|
||||||
yield { role: "agent", contentHash: h, meta: {}, refs: [h] };
|
yield { role: "agent", contentHash: h, meta: {}, refs: [h] };
|
||||||
return { returnCode: 0, summary: "child-done:" + input.prompt };
|
return { returnCode: 0, summary: "child-done:" + thread.start.content };
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ export const descriptor = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export async function* run(input, options) {
|
export async function* run(thread, runtime) {
|
||||||
const cas = options.cas;
|
const cas = runtime.cas;
|
||||||
const h = await putContentMerkleNode(cas, "child-body");
|
const h = await putContentMerkleNode(cas, "child-body");
|
||||||
yield { role: "agent", contentHash: h, meta: {}, refs: [h] };
|
yield { role: "agent", contentHash: h, meta: {}, refs: [h] };
|
||||||
return { returnCode: 0, summary: "child-done:" + input.prompt };
|
return { returnCode: 0, summary: "child-done:" + thread.start.content };
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Re-export of {@link createWorkflow} from `@uncaged/workflow-runtime`.
|
* Re-export of {@link createWorkflow} from `@uncaged/workflow-runtime`.
|
||||||
*
|
*
|
||||||
* The runtime's `createWorkflow` already binds role definitions + agents to a workflow loop
|
* The runtime's `createWorkflow` already binds role definitions + agents to a workflow loop
|
||||||
* and delegates structured meta extraction to `WorkflowFnOptions.extract`, which the engine
|
* and delegates structured meta extraction to `WorkflowRuntime.extract`, which the engine
|
||||||
* supplies (resolved from the `extract` scene in workflow.yaml).
|
* supplies (resolved from the `extract` scene in workflow.yaml).
|
||||||
*/
|
*/
|
||||||
export { createWorkflow } from "@uncaged/workflow-runtime";
|
export { createWorkflow } from "@uncaged/workflow-runtime";
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { appendFile, mkdir } from "node:fs/promises";
|
|||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
import type {
|
import type {
|
||||||
LlmProvider,
|
LlmProvider,
|
||||||
ThreadInput,
|
RoleOutput,
|
||||||
|
ThreadContext,
|
||||||
WorkflowCompletion,
|
WorkflowCompletion,
|
||||||
WorkflowFn,
|
WorkflowFn,
|
||||||
WorkflowFnOptions,
|
WorkflowRuntime,
|
||||||
WorkflowResult,
|
WorkflowResult,
|
||||||
} from "@uncaged/workflow-runtime";
|
} from "@uncaged/workflow-runtime";
|
||||||
|
import { START } from "@uncaged/workflow-runtime";
|
||||||
import {
|
import {
|
||||||
type CasStore,
|
type CasStore,
|
||||||
getContentMerklePayload,
|
getContentMerklePayload,
|
||||||
@@ -103,7 +105,7 @@ async function finalizeAbortedThread(params: {
|
|||||||
|
|
||||||
async function maybeSupervisorHaltsThread(params: {
|
async function maybeSupervisorHaltsThread(params: {
|
||||||
workflowConfig: WorkflowConfig;
|
workflowConfig: WorkflowConfig;
|
||||||
input: ThreadInput;
|
thread: ThreadContext;
|
||||||
written: number;
|
written: number;
|
||||||
recentSupervisorSteps: readonly { role: string; summary: string }[];
|
recentSupervisorSteps: readonly { role: string; summary: string }[];
|
||||||
logger: LogFn;
|
logger: LogFn;
|
||||||
@@ -118,7 +120,7 @@ async function maybeSupervisorHaltsThread(params: {
|
|||||||
}
|
}
|
||||||
const sup = await runSupervisor({
|
const sup = await runSupervisor({
|
||||||
config: params.workflowConfig,
|
config: params.workflowConfig,
|
||||||
prompt: params.input.prompt,
|
prompt: params.thread.start.content,
|
||||||
recentSteps: params.recentSupervisorSteps,
|
recentSteps: params.recentSupervisorSteps,
|
||||||
logger: params.logger,
|
logger: params.logger,
|
||||||
});
|
});
|
||||||
@@ -143,8 +145,8 @@ async function driveWorkflowGenerator(params: {
|
|||||||
fn: WorkflowFn;
|
fn: WorkflowFn;
|
||||||
workflowName: string;
|
workflowName: string;
|
||||||
workflowConfig: WorkflowConfig;
|
workflowConfig: WorkflowConfig;
|
||||||
input: ThreadInput;
|
thread: ThreadContext;
|
||||||
bundleOptions: WorkflowFnOptions;
|
runtime: WorkflowRuntime;
|
||||||
executeOptions: ExecuteThreadOptions;
|
executeOptions: ExecuteThreadOptions;
|
||||||
dataJsonlPath: string;
|
dataJsonlPath: string;
|
||||||
threadId: string;
|
threadId: string;
|
||||||
@@ -156,8 +158,8 @@ async function driveWorkflowGenerator(params: {
|
|||||||
fn,
|
fn,
|
||||||
workflowName,
|
workflowName,
|
||||||
workflowConfig,
|
workflowConfig,
|
||||||
input,
|
thread,
|
||||||
bundleOptions,
|
runtime,
|
||||||
executeOptions,
|
executeOptions,
|
||||||
dataJsonlPath,
|
dataJsonlPath,
|
||||||
threadId,
|
threadId,
|
||||||
@@ -165,9 +167,9 @@ async function driveWorkflowGenerator(params: {
|
|||||||
cas,
|
cas,
|
||||||
stepMerkleHashes,
|
stepMerkleHashes,
|
||||||
} = params;
|
} = params;
|
||||||
const gen = fn(input, bundleOptions);
|
const gen = fn(thread, runtime);
|
||||||
let written = 0;
|
let written = 0;
|
||||||
const recentSupervisorSteps: { role: string; summary: string }[] = input.steps.map((s) => ({
|
const recentSupervisorSteps: { role: string; summary: string }[] = thread.steps.map((s) => ({
|
||||||
role: s.role,
|
role: s.role,
|
||||||
summary: JSON.stringify(s.meta),
|
summary: JSON.stringify(s.meta),
|
||||||
}));
|
}));
|
||||||
@@ -267,7 +269,7 @@ async function driveWorkflowGenerator(params: {
|
|||||||
|
|
||||||
const supervised = await maybeSupervisorHaltsThread({
|
const supervised = await maybeSupervisorHaltsThread({
|
||||||
workflowConfig,
|
workflowConfig,
|
||||||
input,
|
thread,
|
||||||
written,
|
written,
|
||||||
recentSupervisorSteps,
|
recentSupervisorSteps,
|
||||||
logger,
|
logger,
|
||||||
@@ -289,7 +291,7 @@ async function driveWorkflowGenerator(params: {
|
|||||||
export async function executeThread(
|
export async function executeThread(
|
||||||
fn: WorkflowFn,
|
fn: WorkflowFn,
|
||||||
workflowName: string,
|
workflowName: string,
|
||||||
input: ThreadInput,
|
input: { prompt: string; steps: RoleOutput[] },
|
||||||
options: ExecuteThreadOptions,
|
options: ExecuteThreadOptions,
|
||||||
io: ExecuteThreadIo,
|
io: ExecuteThreadIo,
|
||||||
logger: LogFn,
|
logger: LogFn,
|
||||||
@@ -371,10 +373,25 @@ export async function executeThread(
|
|||||||
throw new Error(registryRuntime.error);
|
throw new Error(registryRuntime.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bundleOptions: WorkflowFnOptions = {
|
const thread: ThreadContext = {
|
||||||
threadId: io.threadId,
|
threadId: io.threadId,
|
||||||
maxRounds: options.maxRounds,
|
|
||||||
depth: options.depth,
|
depth: options.depth,
|
||||||
|
start: {
|
||||||
|
role: START,
|
||||||
|
content: input.prompt,
|
||||||
|
meta: { maxRounds: options.maxRounds },
|
||||||
|
timestamp: nowMs,
|
||||||
|
},
|
||||||
|
steps: input.steps.map((out, i) => ({
|
||||||
|
role: out.role,
|
||||||
|
contentHash: out.contentHash,
|
||||||
|
meta: out.meta,
|
||||||
|
refs: out.refs,
|
||||||
|
timestamp: prefilled?.[i]?.timestamp ?? nowMs + i,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtime: WorkflowRuntime = {
|
||||||
cas: io.cas,
|
cas: io.cas,
|
||||||
extract: registryRuntime.value.extract,
|
extract: registryRuntime.value.extract,
|
||||||
};
|
};
|
||||||
@@ -383,8 +400,8 @@ export async function executeThread(
|
|||||||
fn,
|
fn,
|
||||||
workflowName,
|
workflowName,
|
||||||
workflowConfig: registryRuntime.value.workflowConfig,
|
workflowConfig: registryRuntime.value.workflowConfig,
|
||||||
input,
|
thread,
|
||||||
bundleOptions,
|
runtime,
|
||||||
executeOptions: options,
|
executeOptions: options,
|
||||||
dataJsonlPath: io.dataJsonlPath,
|
dataJsonlPath: io.dataJsonlPath,
|
||||||
threadId: io.threadId,
|
threadId: io.threadId,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type PrefilledDiskStep = {
|
|||||||
|
|
||||||
export type ExecuteThreadOptions = {
|
export type ExecuteThreadOptions = {
|
||||||
maxRounds: number;
|
maxRounds: number;
|
||||||
/** Passed to the bundle as `WorkflowFnOptions.depth`. */
|
/** Passed to the bundle thread context as `ThreadContext.depth`. */
|
||||||
depth: number;
|
depth: number;
|
||||||
signal: AbortSignal;
|
signal: AbortSignal;
|
||||||
/** Invoked after each successful yield (and outer-loop checks); used for pause/resume. */
|
/** Invoked after each successful yield (and outer-loop checks); used for pause/resume. */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import type { AgentContext, AgentFn, ThreadInput } from "@uncaged/workflow-runtime";
|
import type { AgentContext, AgentFn } from "@uncaged/workflow-runtime";
|
||||||
import { extractBundleExports } from "./bundle/index.js";
|
import { extractBundleExports } from "./bundle/index.js";
|
||||||
import { createCasStore } from "./cas/index.js";
|
import { createCasStore } from "./cas/index.js";
|
||||||
import type { ExecuteThreadIo } from "./engine/index.js";
|
import type { ExecuteThreadIo } from "./engine/index.js";
|
||||||
@@ -36,7 +36,7 @@ function resolveWorkflowAsAgentStorageRoot(options: WorkflowAsAgentOptions | nul
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an {@link AgentFn} that runs another registered workflow in a new thread,
|
* Returns an {@link AgentFn} that runs another registered workflow in a new thread,
|
||||||
* using the parent thread's initial prompt (`ctx.start.content`) as the child {@link ThreadInput.prompt}.
|
* using the parent thread's initial prompt (`ctx.start.content`) as the child prompt.
|
||||||
*/
|
*/
|
||||||
export function workflowAsAgent(
|
export function workflowAsAgent(
|
||||||
workflowName: string,
|
workflowName: string,
|
||||||
@@ -68,7 +68,7 @@ export function workflowAsAgent(
|
|||||||
return `ERROR: ${bundleExportsResult.error}`;
|
return `ERROR: ${bundleExportsResult.error}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const input: ThreadInput = {
|
const input = {
|
||||||
prompt: ctx.start.content,
|
prompt: ctx.start.content,
|
||||||
steps: [],
|
steps: [],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user