chore: remove all dryRun infrastructure

dryRun no longer needed — tests use mock agents + mock fetch instead.
Removes isDryRun from WorkflowFnOptions, dryRun from ExtractConfig,
dryRunMeta from RoleDefinition, --dry-run from CLI, and all related
plumbing in engine/worker/fork/extract.

小橘 <xiaoju@shazhou.work>
This commit is contained in:
2026-05-06 14:25:44 +00:00
parent fa9163e462
commit 2482fb7e62
26 changed files with 184 additions and 102 deletions
+3 -4
View File
@@ -44,7 +44,7 @@ type ThreadInput = {
/** The bundle contract — an AsyncGenerator, not a Promise. */
type WorkflowFn = (
input: ThreadInput,
options: { isDryRun: boolean; maxRounds: number }
options: { threadId: string; maxRounds: number }
) => AsyncGenerator<RoleOutput, WorkflowResult>;
```
@@ -95,7 +95,7 @@ When using the `createRoleModerator` helper, fork is **naturally handled**:
// It sees planner already ran → routes to coder automatically
const gen = workflow(
{ prompt: "fix bug #3", steps: [{ role: "planner", content: "...", meta: {} }] },
{ isDryRun: false, maxRounds: 10 }
{ threadId: "01KQXKW18CT8G75T53R8F4G7YG", maxRounds: 10 }
);
// First yield will be coder's output, not planner's
```
@@ -225,7 +225,6 @@ No concurrency control or timeout settings in the registry — those belong to e
"parameters": {
"prompt": "Fix the login redirect bug in #3",
"options": {
"isDryRun": false,
"maxRounds": 5
}
},
@@ -271,7 +270,7 @@ No concurrency control or timeout settings in the registry — those belong to e
| `uncaged-workflow list` | List registered workflows |
| `uncaged-workflow show <name>` | Show workflow details |
| `uncaged-workflow remove <name>` | Remove a workflow |
| `uncaged-workflow run <name> [--prompt] [--dry-run] [--max-rounds]` | Start a thread |
| `uncaged-workflow run <name> [--prompt] [--max-rounds]` | Start a thread |
| `uncaged-workflow threads [name]` | List threads (optionally filter by workflow) |
| `uncaged-workflow thread <id>` | Show thread state |
| `uncaged-workflow thread rm <id>` | Delete a thread |
-2
View File
@@ -27,12 +27,10 @@ const greeter: RoleDefinition<Roles["greeter"]> = {
description: "Generates a greeting",
systemPrompt: "You greet the user briefly.",
schema: greeterMetaSchema,
dryRunMeta: { greeting: "Hello!" },
};
const extract = {
provider: { baseUrl: "http://127.0.0.1:9", apiKey: "", model: "" },
dryRun: true,
} as const;
export const run = createWorkflow<Roles>(
@@ -98,7 +98,7 @@ describe("cli fork", () => {
}
const hash = added.value.hash;
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -148,7 +148,7 @@ describe("cli fork", () => {
}
const hash = added.value.hash;
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -198,7 +198,7 @@ describe("cli fork", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -138,7 +138,7 @@ describe("cli thread commands", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -199,7 +199,7 @@ describe("cli thread commands", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -229,7 +229,7 @@ describe("cli thread commands", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -268,7 +268,7 @@ describe("cli thread commands", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -309,7 +309,7 @@ describe("cli thread commands", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
@@ -338,7 +338,7 @@ describe("cli thread commands", () => {
return;
}
const ran = await cmdRun(storageRoot, "solve-issue", "hello", false, 5);
const ran = await cmdRun(storageRoot, "solve-issue", "hello", 5);
expect(ran.ok).toBe(true);
if (!ran.ok) {
return;
+1 -2
View File
@@ -22,7 +22,7 @@ function usage(): string {
" uncaged-workflow list",
" uncaged-workflow show <name>",
" uncaged-workflow remove <name>",
" uncaged-workflow run <name> [--prompt <text>] [--dry-run] [--max-rounds N]",
" uncaged-workflow run <name> [--prompt <text>] [--max-rounds N]",
" uncaged-workflow ps",
" uncaged-workflow kill <thread-id>",
" uncaged-workflow history <name>",
@@ -111,7 +111,6 @@ async function dispatchRun(storageRoot: string, argv: string[]): Promise<number>
storageRoot,
parsed.value.name,
parsed.value.prompt,
parsed.value.dryRun,
parsed.value.maxRounds,
);
if (!result.ok) {
+1 -2
View File
@@ -15,7 +15,6 @@ export async function cmdRun(
storageRoot: string,
name: string,
prompt: string,
isDryRun: boolean,
maxRounds: number,
): Promise<Result<{ threadId: string }, string>> {
const nameOk = validateCliWorkflowName(name);
@@ -47,7 +46,7 @@ export async function cmdRun(
threadId,
workflowName: name,
prompt,
options: { isDryRun, maxRounds },
options: { maxRounds },
},
{ awaitResponseLine: false },
);
+2 -15
View File
@@ -3,20 +3,13 @@ import { err, ok, type Result } from "@uncaged/workflow";
export type ParsedRunArgv = {
name: string;
prompt: string;
dryRun: boolean;
maxRounds: number;
};
type FlagOk =
| { kind: "dry-run" }
| { kind: "prompt"; value: string }
| { kind: "max-rounds"; value: number };
type FlagOk = { kind: "prompt"; value: string } | { kind: "max-rounds"; value: number };
function parseFlagAt(argv: string[], index: number): Result<FlagOk, string> | null {
const flag = argv[index];
if (flag === "--dry-run") {
return ok({ kind: "dry-run" });
}
if (flag === "--prompt") {
const value = argv[index + 1];
if (value === undefined) {
@@ -41,7 +34,6 @@ function parseFlagAt(argv: string[], index: number): Result<FlagOk, string> | nu
export function parseRunArgv(argv: string[]): Result<ParsedRunArgv, string> {
let name: string | undefined;
let prompt = "";
let dryRun = false;
let maxRounds = 5;
let i = 0;
@@ -62,11 +54,6 @@ export function parseRunArgv(argv: string[]): Result<ParsedRunArgv, string> {
}
const flag = parsed.value;
if (flag.kind === "dry-run") {
dryRun = true;
i += 1;
continue;
}
if (flag.kind === "prompt") {
prompt = flag.value;
i += 2;
@@ -80,5 +67,5 @@ export function parseRunArgv(argv: string[]): Result<ParsedRunArgv, string> {
return err("run requires <name>");
}
return ok({ name, prompt, dryRun, maxRounds });
return ok({ name, prompt, maxRounds });
}
@@ -17,9 +17,4 @@ export const coderRole: RoleDefinition<CoderMeta> = {
"Implements the next incomplete planner phase and reports structured completion metadata.",
systemPrompt: CODER_SYSTEM,
schema: coderMetaSchema,
dryRunMeta: {
completedPhase: "phase-1",
filesChanged: [],
summary: "",
},
};
@@ -3,8 +3,12 @@ import { describe, expect, test } from "bun:test";
import { committerMetaSchema, committerRole } from "../src/committer.js";
describe("committerRole", () => {
test("dryRunMeta validates against schema", () => {
const parsed = committerMetaSchema.safeParse(committerRole.dryRunMeta);
test("committed sample validates against schema", () => {
const parsed = committerMetaSchema.safeParse({
status: "committed" as const,
branch: "feat/example",
commitSha: "abc1234",
});
expect(parsed.success).toBe(true);
});
@@ -29,9 +29,4 @@ export const committerRole: RoleDefinition<CommitterMeta> = {
description: "Creates branch, commits, and pushes when review passes.",
systemPrompt: COMMITTER_SYSTEM,
schema: committerMetaSchema,
dryRunMeta: {
status: "committed",
branch: "dry-run/placeholder",
commitSha: "0000000",
},
};
@@ -23,7 +23,4 @@ export const plannerRole: RoleDefinition<PlannerMeta> = {
description: "Breaks the task into sequential phases for the coder.",
systemPrompt: PLANNER_SYSTEM,
schema: plannerMetaSchema,
dryRunMeta: {
phases: [{ name: "phase-1", description: "placeholder", acceptance: "placeholder" }],
},
};
@@ -3,8 +3,8 @@ import { describe, expect, test } from "bun:test";
import { reviewerMetaSchema, reviewerRole } from "../src/reviewer.js";
describe("reviewerRole", () => {
test("dryRunMeta validates against schema", () => {
const parsed = reviewerMetaSchema.safeParse(reviewerRole.dryRunMeta);
test("approved sample validates against schema", () => {
const parsed = reviewerMetaSchema.safeParse({ status: "approved" as const });
expect(parsed.success).toBe(true);
});
@@ -19,5 +19,4 @@ export const reviewerRole: RoleDefinition<ReviewerMeta> = {
description: "Runs git diff checks and sets approved when the change is ready.",
systemPrompt: REVIEWER_SYSTEM,
schema: reviewerMetaSchema,
dryRunMeta: { status: "approved" },
};
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { afterEach, describe, expect, test } from "bun:test";
import {
END,
type RoleStep,
@@ -7,16 +7,78 @@ import {
validateWorkflowDescriptor,
} from "@uncaged/workflow";
import type { CoderMeta } from "@uncaged/workflow-role-coder";
import type { PlannerMeta } from "@uncaged/workflow-role-planner";
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
import { createSolveIssueRun, plannerRole, solveIssueModerator } from "../src/index.js";
import { createSolveIssueRun, solveIssueModerator } from "../src/index.js";
import type { SolveIssueMeta } from "../src/roles.js";
const DEFAULT_PHASES: PlannerMeta["phases"] = [
{ name: "phase-a", description: "Do the work", acceptance: "Done" },
];
const EXPECT_PLANNER_META: PlannerMeta = {
phases: [{ name: "phase-1", description: "placeholder", acceptance: "placeholder" }],
};
const EXPECT_CODER_META: CoderMeta = {
completedPhase: "phase-1",
filesChanged: [],
summary: "",
};
function installMockChatCompletions(sequence: ReadonlyArray<Record<string, unknown>>): () => void {
const origFetch = globalThis.fetch;
let i = 0;
const mockFetch = async (
input: Parameters<typeof fetch>[0],
init?: RequestInit,
): Promise<Response> => {
const args = sequence[i] ?? sequence[sequence.length - 1];
if (args === undefined) {
throw new Error("installMockChatCompletions: empty sequence");
}
i += 1;
void input;
const body = init?.body ? (JSON.parse(String(init.body)) as Record<string, unknown>) : {};
const tools = body.tools;
const firstTool =
Array.isArray(tools) && tools.length > 0 && tools[0] !== null && typeof tools[0] === "object"
? (tools[0] as Record<string, unknown>)
: null;
const fn =
firstTool !== null ? (firstTool.function as Record<string, unknown> | undefined) : undefined;
const toolName = typeof fn?.name === "string" ? fn.name : "extract";
return new Response(
JSON.stringify({
choices: [
{
message: {
tool_calls: [
{
type: "function",
function: {
name: toolName,
arguments: JSON.stringify(args),
},
},
],
},
},
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
};
globalThis.fetch = Object.assign(mockFetch, {
preconnect: origFetch.preconnect.bind(origFetch),
}) as typeof fetch;
return () => {
globalThis.fetch = origFetch;
};
}
function makeStart(maxRounds: number): ThreadContext<SolveIssueMeta>["start"] {
return {
role: START,
@@ -78,7 +140,6 @@ function committerStep(): RoleStep<SolveIssueMeta> {
const stubExtract = {
provider: { baseUrl: "http://127.0.0.1:9", apiKey: "", model: "test" },
dryRun: true,
} as const;
describe("solveIssueModerator", () => {
@@ -137,11 +198,20 @@ describe("solveIssueModerator", () => {
});
describe("createSolveIssueRun", () => {
test("dry-run extraction yields role dryRunMeta for planner", async () => {
let restoreFetch: (() => void) | null = null;
afterEach(() => {
restoreFetch?.();
restoreFetch = null;
});
test("structured extraction yields planner meta from mocked chat completions", async () => {
restoreFetch = installMockChatCompletions([EXPECT_PLANNER_META]);
const run = createSolveIssueRun({ agent: async () => "" }, stubExtract);
const gen = run(
{ prompt: "task", steps: [] },
{ threadId: "01TEST000000000000000000TR", isDryRun: true, maxRounds: 20 },
{ threadId: "01TEST000000000000000000TR", maxRounds: 20 },
);
const first = await gen.next();
expect(first.done).toBe(false);
@@ -149,10 +219,12 @@ describe("createSolveIssueRun", () => {
throw new Error("expected yield");
}
expect(first.value.role).toBe("planner");
expect(first.value.meta).toEqual(plannerRole.dryRunMeta);
expect(first.value.meta).toEqual(EXPECT_PLANNER_META);
});
test("per-role agent overrides default", async () => {
restoreFetch = installMockChatCompletions([EXPECT_PLANNER_META, EXPECT_CODER_META]);
const calls: string[] = [];
const run = createSolveIssueRun(
{
@@ -175,7 +247,7 @@ describe("createSolveIssueRun", () => {
);
const gen = run(
{ prompt: "task", steps: [] },
{ threadId: "01TEST000000000000000000TR", isDryRun: true, maxRounds: 20 },
{ threadId: "01TEST000000000000000000TR", maxRounds: 20 },
);
await gen.next();
expect(calls).toEqual(["planner"]);
@@ -21,7 +21,6 @@ describe("buildDescriptor", () => {
description: "Analyzes input",
systemPrompt: "You are an analyst.",
schema,
dryRunMeta: { title: "", count: 0 },
},
},
moderator: () => END,
+67 -8
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { afterEach, describe, expect, test } from "bun:test";
import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -23,9 +23,59 @@ type DemoMeta = {
coder: z.infer<typeof coderMetaSchema>;
};
function installMockChatCompletions(sequence: ReadonlyArray<Record<string, unknown>>): () => void {
const origFetch = globalThis.fetch;
let i = 0;
const mockFetch = async (
input: Parameters<typeof fetch>[0],
init?: RequestInit,
): Promise<Response> => {
const args = sequence[i] ?? sequence[sequence.length - 1];
if (args === undefined) {
throw new Error("installMockChatCompletions: empty sequence");
}
i += 1;
void input;
const body = init?.body ? (JSON.parse(String(init.body)) as Record<string, unknown>) : {};
const tools = body.tools;
const firstTool =
Array.isArray(tools) && tools.length > 0 && tools[0] !== null && typeof tools[0] === "object"
? (tools[0] as Record<string, unknown>)
: null;
const fn =
firstTool !== null ? (firstTool.function as Record<string, unknown> | undefined) : undefined;
const toolName = typeof fn?.name === "string" ? fn.name : "extract";
return new Response(
JSON.stringify({
choices: [
{
message: {
tool_calls: [
{
type: "function",
function: {
name: toolName,
arguments: JSON.stringify(args),
},
},
],
},
},
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
};
globalThis.fetch = Object.assign(mockFetch, {
preconnect: origFetch.preconnect.bind(origFetch),
}) as typeof fetch;
return () => {
globalThis.fetch = origFetch;
};
}
const demoExtract = {
provider: { baseUrl: "http://127.0.0.1:9", apiKey: "test", model: "test" },
dryRun: true,
} as const;
const demoWorkflow = createWorkflow<DemoMeta>(
@@ -35,13 +85,11 @@ const demoWorkflow = createWorkflow<DemoMeta>(
description: "Demo planner",
systemPrompt: "You are a planner.",
schema: plannerMetaSchema,
dryRunMeta: { plan: "do-it", files: ["a.ts"] },
},
coder: {
description: "Demo coder",
systemPrompt: "You are a coder.",
schema: coderMetaSchema,
dryRunMeta: { diff: "+ok" },
},
},
moderator: (ctx) => {
@@ -65,7 +113,19 @@ const demoWorkflow = createWorkflow<DemoMeta>(
);
describe("executeThread", () => {
let restoreFetch: (() => void) | null = null;
afterEach(() => {
restoreFetch?.();
restoreFetch = null;
});
test("writes RFC-001 `.data.jsonl` start + role records and `.info.jsonl` logs", async () => {
restoreFetch = installMockChatCompletions([
{ plan: "do-it", files: ["a.ts"] },
{ diff: "+ok" },
]);
const root = await mkdtemp(join(tmpdir(), "wf-engine-"));
try {
const threadId = "01KQXKW18CT8G75T53R8F4G7YG";
@@ -82,7 +142,6 @@ describe("executeThread", () => {
"demo-flow",
{ prompt: "Fix the login redirect bug in #3", steps: [] },
{
isDryRun: false,
maxRounds: 5,
signal: ac.signal,
awaitAfterEachYield: async () => {},
@@ -111,8 +170,8 @@ describe("executeThread", () => {
const params = start.parameters as Record<string, unknown>;
expect(params.prompt).toBe("Fix the login redirect bug in #3");
const opts = params.options as Record<string, unknown>;
expect(opts.isDryRun).toBe(false);
expect(opts.maxRounds).toBe(5);
expect(Object.keys(opts).sort()).toEqual(["maxRounds"]);
const role1 = JSON.parse(lines[1] ?? "{}") as Record<string, unknown>;
expect(role1.role).toBe("planner");
@@ -140,6 +199,8 @@ describe("executeThread", () => {
});
test("pre-filled ThreadInput.steps skips roles already present", async () => {
restoreFetch = installMockChatCompletions([{ diff: "+ok" }]);
const root = await mkdtemp(join(tmpdir(), "wf-engine-fork-"));
try {
const threadId = "01KQXKW18CT8G75T53R8F4G7YG";
@@ -166,7 +227,6 @@ describe("executeThread", () => {
],
},
{
isDryRun: false,
maxRounds: 5,
signal: ac.signal,
awaitAfterEachYield: async () => {},
@@ -225,7 +285,6 @@ describe("executeThread", () => {
"demo-flow",
{ prompt: "hello", steps: [] },
{
isDryRun: false,
maxRounds: 0,
signal: ac.signal,
awaitAfterEachYield: async () => {},
@@ -6,7 +6,7 @@ import {
selectForkHistoricalSteps,
} from "../src/fork-thread.js";
const sampleDataJsonl = `{"name":"demo","hash":"C9NMV6V2TQT81","threadId":"01AAA1111111111111111111","parameters":{"prompt":"hi","options":{"isDryRun":false,"maxRounds":5}},"timestamp":100}
const sampleDataJsonl = `{"name":"demo","hash":"C9NMV6V2TQT81","threadId":"01AAA1111111111111111111","parameters":{"prompt":"hi","options":{"maxRounds":5}},"timestamp":100}
{"role":"planner","content":"p","meta":{},"timestamp":101}
{"role":"coder","content":"c","meta":{},"timestamp":102}
{"role":"reviewer","content":"r","meta":{},"timestamp":103}
@@ -23,6 +23,7 @@ describe("fork-thread", () => {
expect(r.value.start.hash).toBe("C9NMV6V2TQT81");
expect(r.value.start.threadId).toBe("01AAA1111111111111111111");
expect(r.value.start.prompt).toBe("hi");
expect(r.value.start.maxRounds).toBe(5);
expect(r.value.roleSteps.length).toBe(3);
expect(r.value.roleSteps[0]?.role).toBe("planner");
});
@@ -82,5 +83,6 @@ describe("fork-thread", () => {
expect(r.value.workflowName).toBe("demo");
expect(r.value.historicalSteps.length).toBe(1);
expect(r.value.historicalSteps[0]?.timestamp).toBe(101);
expect(r.value.runOptions).toEqual({ maxRounds: 5 });
});
});
@@ -9,7 +9,6 @@ describe("RFC-001 thread JSONL shapes", () => {
parameters: {
prompt: "Fix the login redirect bug in #3",
options: {
isDryRun: false,
maxRounds: 5,
},
},
+2 -2
View File
@@ -102,7 +102,7 @@ describe("worker process", () => {
threadId,
workflowName: "demo-flow",
prompt: "hello",
options: { isDryRun: false, maxRounds: 5 },
options: { maxRounds: 5 },
});
const exitCode: number = await new Promise((resolve) => {
@@ -150,7 +150,7 @@ describe("worker process", () => {
threadId,
workflowName: "demo-flow",
prompt: "hello",
options: { isDryRun: false, maxRounds: 5 },
options: { maxRounds: 5 },
steps: [
{
role: "planner",
-2
View File
@@ -116,8 +116,6 @@ export function createWorkflow<M extends RoleMeta>(
const meta = await extractMetaOrThrow(next, raw, roleDef.schema, {
provider: extract.provider,
dryRun: extract.dryRun,
dryRunMeta: roleDef.dryRunMeta,
});
const ts = Date.now();
-3
View File
@@ -20,7 +20,6 @@ export type PrefilledDiskStep = {
};
export type ExecuteThreadOptions = {
isDryRun: boolean;
maxRounds: number;
signal: AbortSignal;
/** Invoked after each successful yield (and outer-loop checks); used for pause/resume. */
@@ -133,7 +132,6 @@ export async function executeThread(
parameters: {
prompt: input.prompt,
options: {
isDryRun: options.isDryRun,
maxRounds: options.maxRounds,
},
},
@@ -168,7 +166,6 @@ export async function executeThread(
const bundleOptions: WorkflowFnOptions = {
threadId: io.threadId,
isDryRun: options.isDryRun,
maxRounds: options.maxRounds,
};
+1 -3
View File
@@ -7,14 +7,12 @@ export async function extractMetaOrThrow<T extends Record<string, unknown>>(
roleName: string,
raw: string,
schema: z.ZodType<T>,
options: { provider: LlmProvider; dryRun: boolean; dryRunMeta: T },
options: { provider: LlmProvider },
): Promise<T> {
const result = await llmExtractWithRetry({
text: raw,
schema,
provider: options.provider,
dryRun: options.dryRun,
dryRunMeta: options.dryRunMeta,
});
if (!result.ok) {
throw new Error(
+4 -7
View File
@@ -9,7 +9,6 @@ export type ParsedThreadStartRecord = {
hash: string;
threadId: string;
prompt: string;
isDryRun: boolean;
maxRounds: number;
};
@@ -72,10 +71,9 @@ function parseStartRecordLine(firstLine: string): Result<ParsedThreadStartRecord
return err("start record missing parameters.options");
}
const optRec = options as Record<string, unknown>;
const isDryRun = optRec.isDryRun;
const maxRounds = optRec.maxRounds;
if (typeof isDryRun !== "boolean" || typeof maxRounds !== "number") {
return err("start record missing parameters.options.isDryRun or maxRounds");
if (typeof maxRounds !== "number") {
return err("start record missing parameters.options.maxRounds");
}
return ok({
@@ -83,7 +81,6 @@ function parseStartRecordLine(firstLine: string): Result<ParsedThreadStartRecord
hash,
threadId,
prompt,
isDryRun,
maxRounds,
});
}
@@ -197,7 +194,7 @@ export type ForkPlan = {
hash: string;
sourceThreadId: string;
prompt: string;
runOptions: { isDryRun: boolean; maxRounds: number };
runOptions: { maxRounds: number };
historicalSteps: ForkHistoricalStep[];
};
@@ -222,7 +219,7 @@ export function buildForkPlan(
hash: start.hash,
sourceThreadId: start.threadId,
prompt: start.prompt,
runOptions: { isDryRun: start.isDryRun, maxRounds: start.maxRounds },
runOptions: { maxRounds: start.maxRounds },
historicalSteps: selected.value,
});
}
-7
View File
@@ -7,9 +7,6 @@ export type LlmExtractArgs<T> = {
text: string;
schema: z.ZodType<T>;
provider: LlmProvider;
dryRun: boolean;
/** Returned when `dryRun` is true (ignored for live extract). */
dryRunMeta: T;
};
export type LlmError =
@@ -127,10 +124,6 @@ export function llmErrorToCause(error: LlmError): Error {
async function performLlmExtract<T>(
options: LlmExtractArgs<T> & { userContent: string },
): Promise<Result<T, LlmError>> {
if (options.dryRun) {
return ok(options.dryRunMeta);
}
const rawJsonSchema = z.toJSONSchema(options.schema) as Record<string, unknown>;
const parameters = stripJsonSchemaMeta(rawJsonSchema);
const toolName = readToolName(parameters);
+1 -4
View File
@@ -36,7 +36,6 @@ export type ThreadInput = {
/** Options passed to a workflow bundle's `run` export (engine-provided). */
export type WorkflowFnOptions = {
threadId: string;
isDryRun: boolean;
maxRounds: number;
};
@@ -82,15 +81,13 @@ export type AgentBinding = {
/** Structured extraction settings for the workflow engine. */
export type ExtractConfig = {
provider: LlmProvider;
dryRun: boolean;
};
/** Role wiring: prompts, schema, dry-run meta, and human-readable description. */
/** Role wiring: prompts, schema, and human-readable description. */
export type RoleDefinition<Meta extends Record<string, unknown>> = {
description: string;
systemPrompt: string;
schema: z.ZodType<Meta>;
dryRunMeta: Meta;
};
/**
+3 -4
View File
@@ -16,7 +16,7 @@ type RunCommand = {
threadId: string;
workflowName: string;
prompt: string;
options: { isDryRun: boolean; maxRounds: number };
options: { maxRounds: number };
steps: RoleOutput[];
/** Timestamps aligned with `steps` for `.data.jsonl` replay; length must match `steps` when non-null. */
stepTimestamps: number[] | null;
@@ -114,9 +114,8 @@ function parseRunControlPayload(rec: Record<string, unknown>): RunCommand | null
return null;
}
const optRec = options as Record<string, unknown>;
const isDryRun = optRec.isDryRun;
const maxRounds = optRec.maxRounds;
if (typeof isDryRun !== "boolean" || typeof maxRounds !== "number") {
if (typeof maxRounds !== "number") {
return null;
}
const parsedSteps = parseRunStepsPayload(rec);
@@ -136,7 +135,7 @@ function parseRunControlPayload(rec: Record<string, unknown>): RunCommand | null
threadId,
workflowName,
prompt,
options: { isDryRun, maxRounds },
options: { maxRounds },
steps: parsedSteps.steps,
stepTimestamps: parsedSteps.stepTimestamps,
forkSourceThreadId,