refactor: three-phase context (Moderator/Agent/Extract) + extractPrompt + unified ExtractFn
- ModeratorContext → AgentContext → ExtractContext progressive types - ThreadContext is now alias for AgentContext - RoleDefinition adds extractPrompt field - ExtractFn = (schema, ctx: ExtractContext) => Promise<T> - createWorkflow takes ExtractFn, engine loop: moderator → agent → extract - Remove ExtractConfig, extractMetaOrThrow, extract-meta.ts 小橘 <xiaoju@shazhou.work>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { createWorkflow, END, type RoleDefinition } from "@uncaged/workflow";
|
||||
import { createExtract, createWorkflow, END, type RoleDefinition } from "@uncaged/workflow";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
type Roles = {
|
||||
@@ -26,12 +26,15 @@ export const descriptor = {
|
||||
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,
|
||||
};
|
||||
|
||||
const extract = {
|
||||
provider: { baseUrl: "http://127.0.0.1:9", apiKey: "", model: "" },
|
||||
} as const;
|
||||
const extract = createExtract({
|
||||
baseUrl: "http://127.0.0.1:9",
|
||||
apiKey: "",
|
||||
model: "",
|
||||
});
|
||||
|
||||
export const run = createWorkflow<Roles>(
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import type { ExtractFn } from "@uncaged/workflow";
|
||||
import type { ExtractContext, ExtractFn } from "@uncaged/workflow";
|
||||
import type * as z from "zod/v4";
|
||||
import { createCursorAgent, validateCursorAgentConfig } from "../src/index.js";
|
||||
|
||||
const testExtract: ExtractFn = ((_schema, _prompt) => async (_ctx) => ({
|
||||
workspace: "/tmp",
|
||||
})) as ExtractFn;
|
||||
const testExtract: ExtractFn = async <T extends Record<string, unknown>>(
|
||||
_schema: z.ZodType<T>,
|
||||
_ctx: ExtractContext,
|
||||
): Promise<T> => ({ workspace: "/tmp" }) as unknown as T;
|
||||
|
||||
describe("validateCursorAgentConfig", () => {
|
||||
test("accepts valid config", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AgentFn } from "@uncaged/workflow";
|
||||
import type { AgentFn, ExtractContext } from "@uncaged/workflow";
|
||||
import { buildAgentPrompt, type SpawnCliError, spawnCli } from "@uncaged/workflow-util-agent";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
@@ -43,13 +43,15 @@ export function createCursorAgent(config: CursorAgentConfig): AgentFn {
|
||||
|
||||
const modelFlag = resolveCursorModel(config.model);
|
||||
const timeoutMs = config.timeout > 0 ? config.timeout : null;
|
||||
const extractWorkspace = config.extract(
|
||||
cursorWorkspaceSchema,
|
||||
"From the thread context, determine the absolute filesystem path where the project/repository is located. Look for clone paths, working directories, or repo paths mentioned in previous steps.",
|
||||
);
|
||||
|
||||
return async (ctx) => {
|
||||
const { workspace } = await extractWorkspace(ctx);
|
||||
const extractCtx: ExtractContext = {
|
||||
...ctx,
|
||||
agentContent: "",
|
||||
extractPrompt:
|
||||
"From the thread context, determine the absolute filesystem path where the project/repository is located.",
|
||||
};
|
||||
const { workspace } = await config.extract(cursorWorkspaceSchema, extractCtx);
|
||||
const fullPrompt = buildAgentPrompt(ctx);
|
||||
const args = [
|
||||
"-p",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
type AgentContext,
|
||||
type AgentFn,
|
||||
err,
|
||||
type LlmProvider,
|
||||
ok,
|
||||
type Result,
|
||||
type ThreadContext,
|
||||
} from "@uncaged/workflow";
|
||||
|
||||
/** OpenAI chat completion message shape (passed to `/chat/completions`). */
|
||||
@@ -97,9 +97,9 @@ export async function chatCompletionText(options: {
|
||||
return parseAssistantText(res.value);
|
||||
}
|
||||
|
||||
/** Single-turn chat adapter: system prompt comes from {@link ThreadContext.currentRole}. */
|
||||
/** Single-turn chat adapter: system prompt comes from {@link AgentContext.currentRole}. */
|
||||
export function createLlmAdapter(provider: LlmProvider): AgentFn {
|
||||
return async (ctx: ThreadContext) => {
|
||||
return async (ctx: AgentContext) => {
|
||||
const result = await chatCompletionText({
|
||||
provider,
|
||||
messages: [
|
||||
|
||||
@@ -16,5 +16,7 @@ export const coderRole: RoleDefinition<CoderMeta> = {
|
||||
description:
|
||||
"Implements the next incomplete planner phase and reports structured completion metadata.",
|
||||
systemPrompt: CODER_SYSTEM,
|
||||
extractPrompt:
|
||||
"Extract which phase was completed, which files were changed, and a summary of the work done.",
|
||||
schema: coderMetaSchema,
|
||||
};
|
||||
|
||||
@@ -28,5 +28,7 @@ Do not attempt to fix failures yourself.`;
|
||||
export const committerRole: RoleDefinition<CommitterMeta> = {
|
||||
description: "Creates branch, commits, and pushes when review passes.",
|
||||
systemPrompt: COMMITTER_SYSTEM,
|
||||
extractPrompt:
|
||||
"Extract the commit result: committed (with branch and SHA), recoverable failure, or unrecoverable failure. Include error details and log references if applicable.",
|
||||
schema: committerMetaSchema,
|
||||
};
|
||||
|
||||
@@ -22,5 +22,7 @@ Order phases so earlier steps unblock later ones. Cover root cause, edge cases,
|
||||
export const plannerRole: RoleDefinition<PlannerMeta> = {
|
||||
description: "Breaks the task into sequential phases for the coder.",
|
||||
systemPrompt: PLANNER_SYSTEM,
|
||||
extractPrompt:
|
||||
"Extract the implementation phases from the agent's analysis. Each phase needs a name, description, and acceptance criteria.",
|
||||
schema: plannerMetaSchema,
|
||||
};
|
||||
|
||||
@@ -18,5 +18,7 @@ Only reject for blocking issues. End with your verdict.`;
|
||||
export const reviewerRole: RoleDefinition<ReviewerMeta> = {
|
||||
description: "Runs git diff checks and sets approved when the change is ready.",
|
||||
systemPrompt: REVIEWER_SYSTEM,
|
||||
extractPrompt:
|
||||
"Extract the review verdict: approved or rejected. If rejected, list the blocking issues.",
|
||||
schema: reviewerMetaSchema,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
createExtract,
|
||||
END,
|
||||
type ModeratorContext,
|
||||
type RoleStep,
|
||||
START,
|
||||
type ThreadContext,
|
||||
validateWorkflowDescriptor,
|
||||
} from "@uncaged/workflow";
|
||||
|
||||
@@ -79,7 +80,7 @@ function installMockChatCompletions(sequence: ReadonlyArray<Record<string, unkno
|
||||
};
|
||||
}
|
||||
|
||||
function makeStart(maxRounds: number): ThreadContext<SolveIssueMeta>["start"] {
|
||||
function makeStart(maxRounds: number): ModeratorContext<SolveIssueMeta>["start"] {
|
||||
return {
|
||||
role: START,
|
||||
content: "Fix the flaky login test",
|
||||
@@ -90,11 +91,10 @@ function makeStart(maxRounds: number): ThreadContext<SolveIssueMeta>["start"] {
|
||||
|
||||
function makeCtx(
|
||||
maxRounds: number,
|
||||
steps: ThreadContext<SolveIssueMeta>["steps"],
|
||||
): ThreadContext<SolveIssueMeta> {
|
||||
steps: ModeratorContext<SolveIssueMeta>["steps"],
|
||||
): ModeratorContext<SolveIssueMeta> {
|
||||
return {
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
currentRole: { name: START, systemPrompt: "" },
|
||||
start: makeStart(maxRounds),
|
||||
steps,
|
||||
};
|
||||
@@ -138,9 +138,11 @@ function committerStep(): RoleStep<SolveIssueMeta> {
|
||||
};
|
||||
}
|
||||
|
||||
const stubExtract = {
|
||||
provider: { baseUrl: "http://127.0.0.1:9", apiKey: "", model: "test" },
|
||||
} as const;
|
||||
const stubExtract = createExtract({
|
||||
baseUrl: "http://127.0.0.1:9",
|
||||
apiKey: "",
|
||||
model: "test",
|
||||
});
|
||||
|
||||
describe("solveIssueModerator", () => {
|
||||
test("routes planner → coder → reviewer → committer → END", () => {
|
||||
@@ -158,7 +160,7 @@ describe("solveIssueModerator", () => {
|
||||
});
|
||||
|
||||
test("reviewer rejects → coder retry when budget allows", () => {
|
||||
const steps: ThreadContext<SolveIssueMeta>["steps"] = [
|
||||
const steps: ModeratorContext<SolveIssueMeta>["steps"] = [
|
||||
plannerStep(),
|
||||
coderStep(),
|
||||
reviewerStep(false),
|
||||
@@ -167,7 +169,7 @@ describe("solveIssueModerator", () => {
|
||||
});
|
||||
|
||||
test("reviewer rejects → END when max rounds exhausted", () => {
|
||||
const steps: ThreadContext<SolveIssueMeta>["steps"] = [
|
||||
const steps: ModeratorContext<SolveIssueMeta>["steps"] = [
|
||||
plannerStep(),
|
||||
coderStep(),
|
||||
reviewerStep(false),
|
||||
@@ -192,7 +194,7 @@ describe("solveIssueModerator", () => {
|
||||
{ name: "p1", description: "first", acceptance: "a1" },
|
||||
{ name: "p2", description: "second", acceptance: "a2" },
|
||||
];
|
||||
const steps: ThreadContext<SolveIssueMeta>["steps"] = [plannerStep(phases), coderStep("p1")];
|
||||
const steps: ModeratorContext<SolveIssueMeta>["steps"] = [plannerStep(phases), coderStep("p1")];
|
||||
expect(solveIssueModerator(makeCtx(3, steps))).toBe(END);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type AgentBinding,
|
||||
createWorkflow,
|
||||
type ExtractConfig,
|
||||
type ExtractFn,
|
||||
type WorkflowDefinition,
|
||||
type WorkflowFn,
|
||||
} from "@uncaged/workflow";
|
||||
@@ -45,6 +45,6 @@ export const solveIssueWorkflowDefinition: WorkflowDefinition<SolveIssueMeta> =
|
||||
moderator: solveIssueModerator,
|
||||
};
|
||||
|
||||
export function createSolveIssueRun(binding: AgentBinding, extract: ExtractConfig): WorkflowFn {
|
||||
export function createSolveIssueRun(binding: AgentBinding, extract: ExtractFn): WorkflowFn {
|
||||
return createWorkflow(solveIssueWorkflowDefinition, binding, extract);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Moderator, ThreadContext } from "@uncaged/workflow";
|
||||
import type { Moderator, ModeratorContext } from "@uncaged/workflow";
|
||||
import { END } from "@uncaged/workflow";
|
||||
|
||||
import type { SolveIssueMeta } from "./roles.js";
|
||||
|
||||
function nextAfterCoder(
|
||||
ctx: ThreadContext<SolveIssueMeta>,
|
||||
ctx: ModeratorContext<SolveIssueMeta>,
|
||||
maxRounds: number,
|
||||
): (keyof SolveIssueMeta & string) | typeof END {
|
||||
const plannerStep = ctx.steps.find((s) => s.role === "planner");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ThreadContext } from "@uncaged/workflow";
|
||||
import type { AgentContext } from "@uncaged/workflow";
|
||||
|
||||
/** Builds the full agent prompt: system instructions plus summarized thread history. */
|
||||
export function buildAgentPrompt(ctx: ThreadContext): string {
|
||||
export function buildAgentPrompt(ctx: AgentContext): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(ctx.currentRole.systemPrompt);
|
||||
lines.push("");
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("buildDescriptor", () => {
|
||||
analyst: {
|
||||
description: "Analyzes input",
|
||||
systemPrompt: "You are an analyst.",
|
||||
extractPrompt: "Extract title and count from the analysis.",
|
||||
schema,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as z from "zod/v4";
|
||||
|
||||
import { createWorkflow } from "../src/create-workflow.js";
|
||||
import { executeThread } from "../src/engine.js";
|
||||
import { createExtract } from "../src/extract-fn.js";
|
||||
import { createLogger } from "../src/logger.js";
|
||||
import { END } from "../src/types.js";
|
||||
|
||||
@@ -74,9 +75,11 @@ function installMockChatCompletions(sequence: ReadonlyArray<Record<string, unkno
|
||||
};
|
||||
}
|
||||
|
||||
const demoExtract = {
|
||||
provider: { baseUrl: "http://127.0.0.1:9", apiKey: "test", model: "test" },
|
||||
} as const;
|
||||
const demoExtract = createExtract({
|
||||
baseUrl: "http://127.0.0.1:9",
|
||||
apiKey: "test",
|
||||
model: "test",
|
||||
});
|
||||
|
||||
const demoWorkflow = createWorkflow<DemoMeta>(
|
||||
{
|
||||
@@ -84,11 +87,13 @@ const demoWorkflow = createWorkflow<DemoMeta>(
|
||||
planner: {
|
||||
description: "Demo planner",
|
||||
systemPrompt: "You are a planner.",
|
||||
extractPrompt: "Extract plan text and affected files list.",
|
||||
schema: plannerMetaSchema,
|
||||
},
|
||||
coder: {
|
||||
description: "Demo coder",
|
||||
systemPrompt: "You are a coder.",
|
||||
extractPrompt: "Extract the code diff summary.",
|
||||
schema: coderMetaSchema,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { extractMetaOrThrow } from "./extract-meta.js";
|
||||
import type { ExtractFn } from "./extract-fn.js";
|
||||
import {
|
||||
type AgentBinding,
|
||||
type AgentContext,
|
||||
END,
|
||||
type ExtractConfig,
|
||||
type ExtractContext,
|
||||
type ModeratorContext,
|
||||
type RoleMeta,
|
||||
type RoleOutput,
|
||||
type RoleStep,
|
||||
START,
|
||||
type ThreadContext,
|
||||
type ThreadInput,
|
||||
type WorkflowDefinition,
|
||||
type WorkflowFn,
|
||||
@@ -21,33 +22,6 @@ function isRoleNext<M extends RoleMeta>(
|
||||
return next !== END;
|
||||
}
|
||||
|
||||
function moderatorThreadContext<M extends RoleMeta>(params: {
|
||||
threadId: string;
|
||||
start: ThreadContext<M>["start"];
|
||||
steps: RoleStep<M>[];
|
||||
roles: Pick<WorkflowDefinition<M>, "roles">["roles"];
|
||||
}): ThreadContext<M> {
|
||||
const { threadId, start, steps, roles } = params;
|
||||
const last = steps[steps.length - 1];
|
||||
if (last === undefined) {
|
||||
return {
|
||||
threadId,
|
||||
currentRole: { name: START, systemPrompt: "" },
|
||||
start,
|
||||
steps,
|
||||
};
|
||||
}
|
||||
const roleName = last.role as keyof M & string;
|
||||
const roleDef = roles[roleName];
|
||||
const systemPrompt = roleDef !== undefined ? roleDef.systemPrompt : "";
|
||||
return {
|
||||
threadId,
|
||||
currentRole: { name: roleName, systemPrompt },
|
||||
start,
|
||||
steps,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds pure role definitions + moderator to runtime agents and structured extraction.
|
||||
* Assign with `export const run = createWorkflow(def, binding, extract)`.
|
||||
@@ -55,14 +29,14 @@ function moderatorThreadContext<M extends RoleMeta>(params: {
|
||||
export function createWorkflow<M extends RoleMeta>(
|
||||
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
|
||||
binding: AgentBinding,
|
||||
extract: ExtractConfig,
|
||||
extract: ExtractFn,
|
||||
): WorkflowFn {
|
||||
return async function* workflowLoop(
|
||||
input: ThreadInput,
|
||||
options: WorkflowFnOptions,
|
||||
): AsyncGenerator<RoleOutput, WorkflowResult> {
|
||||
const nowMs = Date.now();
|
||||
const start: ThreadContext<M>["start"] = {
|
||||
const start: ModeratorContext<M>["start"] = {
|
||||
role: START,
|
||||
content: input.prompt,
|
||||
meta: { maxRounds: options.maxRounds },
|
||||
@@ -85,12 +59,11 @@ export function createWorkflow<M extends RoleMeta>(
|
||||
};
|
||||
}
|
||||
|
||||
const modCtx = moderatorThreadContext({
|
||||
const modCtx: ModeratorContext<M> = {
|
||||
threadId: options.threadId,
|
||||
start,
|
||||
steps,
|
||||
roles: def.roles,
|
||||
});
|
||||
};
|
||||
|
||||
const next = def.moderator(modCtx);
|
||||
|
||||
@@ -103,20 +76,22 @@ export function createWorkflow<M extends RoleMeta>(
|
||||
return { returnCode: 1, summary: `unknown role: ${next}` };
|
||||
}
|
||||
|
||||
const ctx: ThreadContext<M> = {
|
||||
threadId: options.threadId,
|
||||
const agentCtx: AgentContext<M> = {
|
||||
...modCtx,
|
||||
currentRole: { name: next, systemPrompt: roleDef.systemPrompt },
|
||||
start,
|
||||
steps,
|
||||
};
|
||||
|
||||
const agent = binding.overrides?.[next] ?? binding.agent;
|
||||
|
||||
const raw = await agent(ctx as unknown as ThreadContext);
|
||||
const raw = await agent(agentCtx as unknown as AgentContext);
|
||||
|
||||
const meta = await extractMetaOrThrow(next, raw, roleDef.schema, {
|
||||
provider: extract.provider,
|
||||
});
|
||||
const extractCtx: ExtractContext<M> = {
|
||||
...agentCtx,
|
||||
agentContent: raw,
|
||||
extractPrompt: roleDef.extractPrompt,
|
||||
};
|
||||
|
||||
const meta = await extract(roleDef.schema, extractCtx as unknown as ExtractContext);
|
||||
|
||||
const ts = Date.now();
|
||||
const step = {
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
import type * as z from "zod/v4";
|
||||
|
||||
import { llmExtractWithRetry } from "./llm-extract.js";
|
||||
import type { LlmProvider, ThreadContext } from "./types.js";
|
||||
import type { ExtractContext, LlmProvider } from "./types.js";
|
||||
|
||||
/**
|
||||
* Curried extract: bind a schema + prompt, get a function that extracts from ThreadContext.
|
||||
*/
|
||||
export type ExtractFn = <T extends Record<string, unknown>>(
|
||||
schema: z.ZodType<T>,
|
||||
prompt: string,
|
||||
) => (ctx: ThreadContext) => Promise<T>;
|
||||
ctx: ExtractContext,
|
||||
) => Promise<T>;
|
||||
|
||||
/**
|
||||
* Create an ExtractFn backed by an LLM provider.
|
||||
* The returned function uses the thread context (currentRole.systemPrompt + steps) as source text
|
||||
* for structured extraction.
|
||||
* Builds prompt text from {@link ExtractContext} and calls structured extraction.
|
||||
*/
|
||||
export function createExtract(provider: LlmProvider): ExtractFn {
|
||||
return <T extends Record<string, unknown>>(schema: z.ZodType<T>, prompt: string) => {
|
||||
return async (ctx: ThreadContext): Promise<T> => {
|
||||
const lines: string[] = [];
|
||||
lines.push("## Current Role");
|
||||
lines.push(ctx.currentRole.systemPrompt);
|
||||
lines.push("");
|
||||
lines.push("## Task");
|
||||
lines.push(ctx.start.content);
|
||||
lines.push("");
|
||||
if (ctx.steps.length > 0) {
|
||||
lines.push("## Thread History");
|
||||
for (const step of ctx.steps) {
|
||||
lines.push(`### ${step.role}`);
|
||||
lines.push(step.content);
|
||||
lines.push(`Meta: ${JSON.stringify(step.meta)}`);
|
||||
lines.push("");
|
||||
}
|
||||
return async <T extends Record<string, unknown>>(
|
||||
schema: z.ZodType<T>,
|
||||
ctx: ExtractContext,
|
||||
): Promise<T> => {
|
||||
const lines: string[] = [];
|
||||
lines.push(`## Role: ${ctx.currentRole.name}`);
|
||||
lines.push(ctx.currentRole.systemPrompt);
|
||||
lines.push("");
|
||||
lines.push("## Task");
|
||||
lines.push(ctx.start.content);
|
||||
lines.push("");
|
||||
if (ctx.steps.length > 0) {
|
||||
lines.push("## Thread History");
|
||||
for (const step of ctx.steps) {
|
||||
lines.push(`### ${step.role}`);
|
||||
lines.push(step.content);
|
||||
lines.push(`Meta: ${JSON.stringify(step.meta)}`);
|
||||
lines.push("");
|
||||
}
|
||||
lines.push("## Extraction Instruction");
|
||||
lines.push(prompt);
|
||||
}
|
||||
lines.push("## Agent Output");
|
||||
lines.push(ctx.agentContent);
|
||||
lines.push("");
|
||||
lines.push("## Extraction Instruction");
|
||||
lines.push(ctx.extractPrompt);
|
||||
|
||||
const text = lines.join("\n");
|
||||
const result = await llmExtractWithRetry({ text, schema, provider });
|
||||
if (!result.ok) {
|
||||
throw new Error(`extract failed: ${JSON.stringify(result.error)}`);
|
||||
}
|
||||
return result.value;
|
||||
};
|
||||
const text = lines.join("\n");
|
||||
const result = await llmExtractWithRetry({ text, schema, provider });
|
||||
if (!result.ok) {
|
||||
throw new Error(`extract failed: ${JSON.stringify(result.error)}`);
|
||||
}
|
||||
return result.value;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import type * as z from "zod/v4";
|
||||
|
||||
import { llmExtractWithRetry } from "./llm-extract.js";
|
||||
import type { LlmProvider } from "./types.js";
|
||||
|
||||
export async function extractMetaOrThrow<T extends Record<string, unknown>>(
|
||||
roleName: string,
|
||||
raw: string,
|
||||
schema: z.ZodType<T>,
|
||||
options: { provider: LlmProvider },
|
||||
): Promise<T> {
|
||||
const result = await llmExtractWithRetry({
|
||||
text: raw,
|
||||
schema,
|
||||
provider: options.provider,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(
|
||||
`Role "${roleName}": structured extraction failed after retry: ${JSON.stringify(result.error)}`,
|
||||
);
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
@@ -16,7 +16,6 @@ export {
|
||||
} from "./engine.js";
|
||||
export { type ExtractedBundleExports, extractBundleExports } from "./extract-bundle-exports.js";
|
||||
export { createExtract, type ExtractFn } from "./extract-fn.js";
|
||||
export { extractMetaOrThrow } from "./extract-meta.js";
|
||||
export {
|
||||
buildForkPlan,
|
||||
type ForkHistoricalStep,
|
||||
@@ -59,11 +58,13 @@ export { getDefaultWorkflowStorageRoot } from "./storage-root.js";
|
||||
export { createThreadPauseGate, type ThreadPauseGate } from "./thread-pause-gate.js";
|
||||
export {
|
||||
type AgentBinding,
|
||||
type AgentContext,
|
||||
type AgentFn,
|
||||
END,
|
||||
type ExtractConfig,
|
||||
type ExtractContext,
|
||||
type LlmProvider,
|
||||
type Moderator,
|
||||
type ModeratorContext,
|
||||
type RoleDefinition,
|
||||
type RoleMeta,
|
||||
type RoleOutput,
|
||||
|
||||
@@ -58,19 +58,32 @@ export type RoleStep<M extends RoleMeta> = {
|
||||
[K in keyof M & string]: { role: K; meta: M[K]; content: string; timestamp: number };
|
||||
}[keyof M & string];
|
||||
|
||||
/** Thread-scoped context passed to agents and moderator. */
|
||||
export type ThreadContext<M extends RoleMeta = RoleMeta> = {
|
||||
/** Phase 1: Moderator decides next role. */
|
||||
export type ModeratorContext<M extends RoleMeta = RoleMeta> = {
|
||||
threadId: string;
|
||||
currentRole: {
|
||||
name: string;
|
||||
systemPrompt: string;
|
||||
};
|
||||
start: StartStep;
|
||||
steps: RoleStep<M>[];
|
||||
};
|
||||
|
||||
/** Phase 2: Agent executes — knows its role and prompt. */
|
||||
export type AgentContext<M extends RoleMeta = RoleMeta> = ModeratorContext<M> & {
|
||||
currentRole: {
|
||||
name: string;
|
||||
systemPrompt: string;
|
||||
};
|
||||
};
|
||||
|
||||
/** Phase 3: Extractor runs — has agent output and extract instruction. */
|
||||
export type ExtractContext<M extends RoleMeta = RoleMeta> = AgentContext<M> & {
|
||||
agentContent: string;
|
||||
extractPrompt: 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. */
|
||||
export type AgentFn = (ctx: ThreadContext) => Promise<string>;
|
||||
export type AgentFn = (ctx: AgentContext) => Promise<string>;
|
||||
|
||||
/** Runtime agent assignment (optional per-role overrides). */
|
||||
export type AgentBinding = {
|
||||
@@ -78,15 +91,11 @@ export type AgentBinding = {
|
||||
overrides?: Partial<Record<string, AgentFn>>;
|
||||
};
|
||||
|
||||
/** Structured extraction settings for the workflow engine. */
|
||||
export type ExtractConfig = {
|
||||
provider: LlmProvider;
|
||||
};
|
||||
|
||||
/** Role wiring: prompts, schema, and human-readable description. */
|
||||
export type RoleDefinition<Meta extends Record<string, unknown>> = {
|
||||
description: string;
|
||||
systemPrompt: string;
|
||||
extractPrompt: string;
|
||||
schema: z.ZodType<Meta>;
|
||||
};
|
||||
|
||||
@@ -97,7 +106,7 @@ export type RoleDefinition<Meta extends Record<string, unknown>> = {
|
||||
* Returns the next role name or END to terminate.
|
||||
*/
|
||||
export type Moderator<M extends RoleMeta> = (
|
||||
ctx: ThreadContext<M>,
|
||||
ctx: ModeratorContext<M>,
|
||||
) => (keyof M & string) | typeof END;
|
||||
|
||||
/** Complete workflow definition as authored by users. */
|
||||
|
||||
Reference in New Issue
Block a user