@mickorz/opencode-agentic-workflow 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,650 @@
1
+ /**
2
+ * v1 脚本适配层 —— opencode-dynamic-workflows(v1)脚本体在 v2 原生运行
3
+ *
4
+ * v0.8.0 产品决策:v1 脚本(`export const meta = {...}` + 魔法全局
5
+ * `phase/agent/parallel/...` + 顶层 `return`)不改写、不迁移,flows 目录
6
+ * 内与 .mjs 流程同权装载。
7
+ *
8
+ * 形态识别:源码含 `export const meta =` 且不含 `defineWorkflow`。
9
+ * 装载:剥离 meta 语句 → 包裹为 `export default async function (...globals)`
10
+ * 模块(顶层 return/await 因此合法)→ 走既有临时文件 import 机制。
11
+ * 运行:definition.run 绑定 19 个 v1 全局到 v2 原语,语义对位:
12
+ * - agent() → core agent(),journal 记一步;返回 .output(schema 时 .structured)
13
+ * - 可恢复失败(agent 超时/schema、check 未通过)在 parallel/pipeline/
14
+ * sequence 中塌缩为 null(v1 语义);结构性错误上抛
15
+ * - checkpoint() 返回 boolean(拒绝=false 不抛);无 gate 时回落 {default}
16
+ * - verify/judgePanel:v1 实现原味移植(构建于 agent+parallel 之上)
17
+ * - fileExists 同步(v1 语义,相对 workspaceRoot/cwd);commandSuccess 异步
18
+ * - setConcurrency():v2 并发由 executor 统一管理——响亮警告 + 不生效
19
+ * - phase() → core phase()(日志 + 事件;v2 TUI 无阶段分组,步骤行=叶子调用)
20
+ *
21
+ * journal 语义:步骤不预声明(stepNames 缺省),叶子调用(agent/checkpoint/
22
+ * subflow)经 recorder.appendStep 动态记录——与 v1 树的可见节点粒度一致。
23
+ * resume = 整体重跑(无步骤前缀跳过;v1 脚本无静态步骤序)。
24
+ *
25
+ * 纪律:禁止 import OpenCode API(架构不变量,同 loader)。
26
+ */
27
+ import { exec } from "node:child_process";
28
+ import { existsSync } from "node:fs";
29
+ import path from "node:path";
30
+ import { agent as coreAgent, AgentTimeoutError, AgentSchemaError } from "../workflow/agent.js";
31
+ import { phase as corePhase } from "../workflow/phase.js";
32
+ import { GitWorktreeProvider } from "../workspace/git-worktree.js";
33
+ import { resolveFlowRef } from "./flow-index.js";
34
+ import { checkpoint as coreCheckpoint, getCheckpointGate, WorkflowCheckpointError, } from "../quality/checkpoint.js";
35
+ /** v1 全局注入参数表(顺序即包裹函数形参序) */
36
+ export const LEGACY_GLOBAL_PARAMS = [
37
+ "phase",
38
+ "agent",
39
+ "parallel",
40
+ "pipeline",
41
+ "sequence",
42
+ "fallback",
43
+ "race",
44
+ "check",
45
+ "fileExists",
46
+ "commandSuccess",
47
+ "log",
48
+ "args",
49
+ "setConcurrency",
50
+ "verify",
51
+ "judgePanel",
52
+ "retry",
53
+ "checkpoint",
54
+ "workflow",
55
+ "console",
56
+ ];
57
+ /** v1 脚本形态识别:含 export const meta 且不含 defineWorkflow */
58
+ export function detectLegacyScript(source) {
59
+ return (!source.includes("defineWorkflow") &&
60
+ /(?:^|[\n;])\s*export\s+const\s+meta\s*=/.test(source));
61
+ }
62
+ /**
63
+ * 剥离 `export const meta = {...}` 语句(字符串感知配平)。
64
+ * 返回 meta 语句原文(保留 export,随包裹模块再导出)与剩余 body。
65
+ */
66
+ export function splitLegacyMeta(source) {
67
+ const match = /export\s+const\s+meta\s*=/.exec(source);
68
+ if (match === null) {
69
+ return { ok: false, error: "legacy script must start with `export const meta = { ... }`" };
70
+ }
71
+ let i = match.index + match[0].length;
72
+ while (i < source.length && /\s/.test(source[i]))
73
+ i++;
74
+ if (source[i] !== "{") {
75
+ return { ok: false, error: "`export const meta` must be an object literal ({ ... })" };
76
+ }
77
+ // 字符串感知的花括号配平
78
+ let depth = 0;
79
+ let quote = null;
80
+ let j = i;
81
+ for (; j < source.length; j++) {
82
+ const ch = source[j];
83
+ if (quote !== null) {
84
+ if (ch === "\\") {
85
+ j++;
86
+ continue;
87
+ }
88
+ if (ch === quote)
89
+ quote = null;
90
+ continue;
91
+ }
92
+ if (ch === '"' || ch === "'" || ch === "`") {
93
+ quote = ch;
94
+ continue;
95
+ }
96
+ if (ch === "{")
97
+ depth++;
98
+ else if (ch === "}") {
99
+ depth--;
100
+ if (depth === 0) {
101
+ j++;
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ if (depth !== 0 || quote !== null) {
107
+ return { ok: false, error: "unbalanced braces in `export const meta` literal" };
108
+ }
109
+ let end = j;
110
+ if (source[end] === ";")
111
+ end++;
112
+ return {
113
+ ok: true,
114
+ metaStatement: source.slice(match.index, end),
115
+ rest: source.slice(0, match.index) + source.slice(end),
116
+ };
117
+ }
118
+ /**
119
+ * 构建可 import 的包裹模块:meta 原文再导出 + body 作为带全局形参的
120
+ * async 函数默认导出(顶层 return/await 因此合法)。
121
+ */
122
+ export function wrapLegacyModule(metaStatement, rest) {
123
+ if (/(?:^|\n)\s*import\s*[^(]/.test(rest)) {
124
+ return {
125
+ ok: false,
126
+ error: "legacy scripts cannot use static import statements (globals are injected); convert to a v2 .mjs flow if you need imports",
127
+ };
128
+ }
129
+ if (/(?:^|\n)\s*export\b/.test(rest)) {
130
+ return {
131
+ ok: false,
132
+ error: "legacy scripts may only contain `export const meta` (other exports are not supported)",
133
+ };
134
+ }
135
+ const body = `export default async function (${LEGACY_GLOBAL_PARAMS.join(", ")}) {\n${rest}\n}\n`;
136
+ return { ok: true, wrapped: `${metaStatement}\n${body}` };
137
+ }
138
+ /** 装载期 meta 校验(v1 validateMeta 对位;shape 宽松、fail-loud) */
139
+ export function validateLegacyMeta(meta) {
140
+ if (typeof meta !== "object" || meta === null) {
141
+ return { ok: false, error: "`export const meta` must evaluate to an object" };
142
+ }
143
+ const name = meta.name;
144
+ if (typeof name !== "string" || !/^[a-z][a-z0-9_]*$/i.test(name)) {
145
+ return { ok: false, error: "meta.name must be a non-empty snake_case string" };
146
+ }
147
+ const description = meta.description;
148
+ if (description !== undefined && typeof description !== "string") {
149
+ return { ok: false, error: "meta.description must be a string" };
150
+ }
151
+ return { ok: true, meta: { name, ...(description !== undefined ? { description } : {}) } };
152
+ }
153
+ /** check() 未通过(可恢复:parallel/pipeline 塌缩 null、sequence 停止返 null) */
154
+ class LegacyCheckError extends Error {
155
+ constructor(message) {
156
+ super(message);
157
+ this.name = "LegacyCheckError";
158
+ }
159
+ }
160
+ function isRecoverableFailure(error) {
161
+ return (error instanceof AgentTimeoutError ||
162
+ error instanceof AgentSchemaError ||
163
+ error instanceof LegacyCheckError);
164
+ }
165
+ /** journal 步骤 input 预览(截断,避免大 prompt 落盘膨胀) */
166
+ function previewText(value, max = 160) {
167
+ const text = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
168
+ return text.length > max ? text.slice(0, max) + "…" : text;
169
+ }
170
+ /** 叶子调用统一 journal:动态追加步骤 + 完成/失败收口 */
171
+ async function journaledStep(journal, name, input, fn) {
172
+ if (journal === undefined)
173
+ return fn();
174
+ const index = await journal.appendStep(name, previewText(input));
175
+ try {
176
+ const value = await fn();
177
+ await journal.stepCompleted(index, previewText(value));
178
+ return value;
179
+ }
180
+ catch (error) {
181
+ await journal.stepFailed(index, error);
182
+ throw error;
183
+ }
184
+ }
185
+ /** v1 agent 选项全集(超出即 fail-loud,防静默语义丢失) */
186
+ const LEGACY_AGENT_OPTION_KEYS = new Set([
187
+ "label",
188
+ "timeoutMs",
189
+ "retries",
190
+ "retryDelayMs",
191
+ "schema",
192
+ "model",
193
+ "isolation",
194
+ "agentType",
195
+ "tier",
196
+ "cwd",
197
+ ]);
198
+ const VERIFY_SCHEMA = {
199
+ type: "object",
200
+ properties: { real: { type: "boolean" }, reason: { type: "string" } },
201
+ required: ["real"],
202
+ };
203
+ const JUDGE_SCHEMA = {
204
+ type: "object",
205
+ properties: { score: { type: "number" }, reason: { type: "string" } },
206
+ required: ["score"],
207
+ };
208
+ function normalizeFanout(value, fallback, optionName) {
209
+ const count = value === undefined ? fallback : value;
210
+ if (typeof count !== "number" || !Number.isFinite(count) || !Number.isInteger(count) || count < 1) {
211
+ throw new TypeError(`${optionName} 必须是大于等于 1 的整数`);
212
+ }
213
+ return count;
214
+ }
215
+ /**
216
+ * 构造 v1 脚本的 WorkflowDefinition:run() 绑定全部全局并执行 body。
217
+ * 步骤动态 journal(叶子粒度);resume = 整体重跑(无静态步骤序)。
218
+ */
219
+ export function buildLegacyDefinition(meta, bodyFn, sourceFile) {
220
+ return {
221
+ id: meta.name,
222
+ version: "1.0.0",
223
+ ...(meta.description !== undefined ? { description: meta.description } : {}),
224
+ argsSchema: {
225
+ type: "object",
226
+ properties: { topic: { type: "string" } },
227
+ },
228
+ async run(args, ctx) {
229
+ const journal = ctx.journal;
230
+ const workspaceRoot = ctx.workspaceRoot ?? process.cwd();
231
+ let agentCount = 0;
232
+ // v1 阶段失败闸门(Bug1 对位):可恢复 agent 失败塌缩 null 继续跑,
233
+ // 登记待裁决——下一 phase() 边界或 run 终检触发终止报告;
234
+ // fallback/race 成功吸收时清除。结构性错误不走此路径(立即上抛)。
235
+ const pendingGate = [];
236
+ const gateError = () => {
237
+ const lines = pendingGate.map((g) => ` - ${g.label}: ${g.error}`).join("\n");
238
+ const detail = `阶段失败闸门触发:存在未吸收的可恢复 agent 失败——\n${lines}`;
239
+ pendingGate.length = 0;
240
+ return new Error(detail);
241
+ };
242
+ const log = (...values) => {
243
+ console.log(`[agentic-workflow] ${values.map((v) => (typeof v === "string" ? v : JSON.stringify(v) ?? String(v))).join(" ")}`);
244
+ };
245
+ const phase = (name) => {
246
+ if (pendingGate.length > 0)
247
+ throw gateError();
248
+ corePhase(name);
249
+ };
250
+ const agent = async (prompt, opts) => {
251
+ if (typeof prompt !== "string" || prompt.length === 0) {
252
+ throw new TypeError("agent(prompt, opts?) 需要非空提示词");
253
+ }
254
+ for (const key of Object.keys(opts ?? {})) {
255
+ if (!LEGACY_AGENT_OPTION_KEYS.has(key)) {
256
+ throw new TypeError(`agent() 不支持选项 "${key}"(legacy 适配层已知全集:${[...LEGACY_AGENT_OPTION_KEYS].join(", ")})`);
257
+ }
258
+ }
259
+ if (opts?.agentType !== undefined) {
260
+ console.warn(`[agentic-workflow] agent({ agentType: "${opts.agentType}" }) is ignored: ` +
261
+ `v2 has no per-call agent type (configure run-level agent via plugin options.agent)`);
262
+ }
263
+ if (opts?.tier !== undefined) {
264
+ console.warn(`[agentic-workflow] agent({ tier: "${opts.tier}" }) falls back to the run-level model: ` +
265
+ `v2 has no tier routing config (this mirrors the v1 unconfigured-tier fallback)`);
266
+ }
267
+ const label = opts?.label ?? `agent-${++agentCount}`;
268
+ const passThrough = {};
269
+ for (const key of ["model", "timeoutMs", "retries", "retryDelayMs", "schema"]) {
270
+ const value = opts?.[key];
271
+ if (value !== undefined)
272
+ passThrough[key] = value;
273
+ }
274
+ // v1 per-call worktree 隔离:独立 worktree 中执行,结束自动拆除;
275
+ // 仓库不可用(非 git 等)降级共享目录并响亮说明(v1 同款语义)
276
+ const useWorktree = opts?.isolation === "worktree";
277
+ const inner = async () => {
278
+ let handle;
279
+ if (useWorktree) {
280
+ try {
281
+ // 分支名清洗:label 可能含中文/冒号/空格(如「子workflow:middle」)
282
+ const safeLabel = label.replace(/[^A-Za-z0-9_-]+/g, "-").slice(0, 40);
283
+ handle = await new GitWorktreeProvider({ startDir: workspaceRoot }).create(`legacy-${ctx.runId}-${safeLabel}`);
284
+ console.log(`[agentic-workflow] agent "${label}" worktree 隔离:${handle.root}`);
285
+ }
286
+ catch (error) {
287
+ const reason = error instanceof Error ? error.message : String(error);
288
+ console.warn(`[agentic-workflow] agent "${label}" 的 worktree 隔离不可用,降级共享目录(${reason})`);
289
+ }
290
+ }
291
+ try {
292
+ const effectivePrompt = handle !== undefined
293
+ ? prompt +
294
+ `\n\n[工作目录] 你在一个独立的 git worktree 中(路径 ${handle.root})。` +
295
+ `全部文件操作必须在以下目录内进行,用绝对路径或先 cd:\n${handle.root}`
296
+ : prompt;
297
+ const result = await coreAgent(effectivePrompt, {
298
+ ...passThrough,
299
+ ...(handle !== undefined ? { cwd: handle.root } : {}),
300
+ });
301
+ return opts?.schema !== undefined ? result.structured : result.output;
302
+ }
303
+ finally {
304
+ if (handle !== undefined) {
305
+ await handle.dispose({ force: true }).catch(() => undefined);
306
+ }
307
+ }
308
+ };
309
+ // v1 语义:可恢复失败(超时/schema 耗尽重试)记 failed 后塌缩 null 继续,
310
+ // 登记阶段失败闸门(下一 phase() 边界或 run 终检裁决);结构性错误上抛
311
+ try {
312
+ return await journaledStep(journal, label, prompt, inner);
313
+ }
314
+ catch (error) {
315
+ if (isRecoverableFailure(error)) {
316
+ const message = error instanceof Error ? error.message : String(error);
317
+ log(`agent "${label}" 尝试失败(可恢复,塌缩 null):${message}`);
318
+ pendingGate.push({ label, error: message });
319
+ return null;
320
+ }
321
+ throw error;
322
+ }
323
+ };
324
+ const parallel = async (thunks) => {
325
+ if (!Array.isArray(thunks)) {
326
+ throw new TypeError("parallel() 期望函数数组");
327
+ }
328
+ if (thunks.some((t) => typeof t !== "function")) {
329
+ throw new TypeError("parallel() 期望函数数组而非 Promise 数组,请用 () => agent(...) 包裹");
330
+ }
331
+ const settled = await Promise.all(thunks.map(async (thunk) => {
332
+ try {
333
+ return { ok: true, value: await thunk() };
334
+ }
335
+ catch (error) {
336
+ return { ok: false, error };
337
+ }
338
+ }));
339
+ for (const item of settled) {
340
+ if (!item.ok && !isRecoverableFailure(item.error))
341
+ throw item.error;
342
+ }
343
+ return settled.map((item) => (item.ok ? item.value : null));
344
+ };
345
+ const pipeline = async (items, stages) => {
346
+ if (!Array.isArray(items))
347
+ throw new TypeError("pipeline() 期望条目数组");
348
+ if (!Array.isArray(stages) || stages.length === 0)
349
+ return [...items];
350
+ const settled = await Promise.all(items.map(async (item, index) => {
351
+ let value = item;
352
+ try {
353
+ for (const stage of stages) {
354
+ value = await stage(value, item, index);
355
+ }
356
+ return { ok: true, value };
357
+ }
358
+ catch (error) {
359
+ return { ok: false, error };
360
+ }
361
+ }));
362
+ for (const item of settled) {
363
+ if (!item.ok && !isRecoverableFailure(item.error))
364
+ throw item.error;
365
+ }
366
+ return settled.map((item) => (item.ok ? item.value : null));
367
+ };
368
+ const sequence = async (nodes) => {
369
+ if (!Array.isArray(nodes))
370
+ throw new TypeError("sequence() 期望节点函数数组");
371
+ let value;
372
+ for (const [index, node] of nodes.entries()) {
373
+ try {
374
+ value = await node(value);
375
+ }
376
+ catch (error) {
377
+ if (isRecoverableFailure(error)) {
378
+ const message = error instanceof Error ? error.message : String(error);
379
+ log(`sequence[${index}] 失败,序列停止: ${message}`);
380
+ return null;
381
+ }
382
+ throw error;
383
+ }
384
+ }
385
+ return value;
386
+ };
387
+ /**
388
+ * fallback(v1 语义原味):候选成功即清空整个失败闸门(降级成功=显式吸收,
389
+ * 否则 fallback 语义报废);「null + 候选窗口内新增登记」视为候选失败换下一个;
390
+ * 可恢复失败换候选;结构性错误上抛;全部失败返回 null(登记保留,终检裁决)。
391
+ */
392
+ const fallback = async (candidates) => {
393
+ if (!Array.isArray(candidates) || candidates.length === 0) {
394
+ throw new TypeError("fallback() 期望非空候选函数数组");
395
+ }
396
+ for (const [index, candidate] of candidates.entries()) {
397
+ const before = pendingGate.length;
398
+ let value;
399
+ try {
400
+ value = await candidate();
401
+ }
402
+ catch (error) {
403
+ if (isRecoverableFailure(error)) {
404
+ const message = error instanceof Error ? error.message : String(error);
405
+ log(`fallback[${index}] 失败,尝试下一候选: ${message}`);
406
+ continue;
407
+ }
408
+ throw error;
409
+ }
410
+ if (value === null && pendingGate.length > before) {
411
+ pendingGate.length = before;
412
+ log(`fallback[${index}] 候选返回 null 且有耗尽失败登记,视为候选失败换下一个`);
413
+ continue;
414
+ }
415
+ if (pendingGate.length > 0)
416
+ pendingGate.length = 0;
417
+ return value;
418
+ }
419
+ log("fallback 全部候选失败,返回 null");
420
+ return null;
421
+ };
422
+ /**
423
+ * race(v1 语义原味):首个真成功胜出并清空闸门(败者的耗尽失败已被吸收);
424
+ * 「null + 登记回滚」按可恢复失败计;全部分支失败返回 null;结构性错误立即上抛。
425
+ */
426
+ const race = (branches) => {
427
+ if (!Array.isArray(branches) || branches.length === 0) {
428
+ throw new TypeError("race() 期望至少一个分支");
429
+ }
430
+ return new Promise((resolve, reject) => {
431
+ let settled = false;
432
+ let failureCount = 0;
433
+ const total = branches.length;
434
+ for (const [index, branch] of branches.entries()) {
435
+ const startLen = pendingGate.length;
436
+ void branch().then((value) => {
437
+ if (value === null && pendingGate.length > startLen) {
438
+ pendingGate.length = startLen;
439
+ return { ok: false, error: new Error("候选返回 null 且有耗尽失败登记") };
440
+ }
441
+ return { ok: true, value };
442
+ }, (error) => ({ ok: false, error })).then((outcome) => {
443
+ if (settled)
444
+ return;
445
+ if (outcome.ok) {
446
+ settled = true;
447
+ log(`race[${index}] 胜出`);
448
+ if (pendingGate.length > 0)
449
+ pendingGate.length = 0;
450
+ resolve(outcome.value);
451
+ return;
452
+ }
453
+ if (!isRecoverableFailure(outcome.error)) {
454
+ settled = true;
455
+ reject(outcome.error);
456
+ return;
457
+ }
458
+ failureCount += 1;
459
+ const message = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
460
+ log(`race[${index}] 失败(${failureCount}/${total}): ${message}`);
461
+ if (failureCount === total) {
462
+ settled = true;
463
+ log("race 全部分支失败,返回 null");
464
+ resolve(null);
465
+ }
466
+ });
467
+ }
468
+ });
469
+ };
470
+ const check = async (condition, message) => {
471
+ if (typeof condition !== "function") {
472
+ throw new TypeError("check(condition, message?) 需要函数条件");
473
+ }
474
+ let passed;
475
+ try {
476
+ passed = await condition();
477
+ }
478
+ catch (error) {
479
+ const reason = error instanceof Error ? error.message : String(error);
480
+ throw new TypeError(`check 条件执行出错(不是验证未通过,是检查代码出错):${reason}`);
481
+ }
482
+ if (passed === true)
483
+ return true;
484
+ throw new LegacyCheckError(message ?? "check 验证未通过");
485
+ };
486
+ /** 同步(v1 语义):相对 workspaceRoot/cwd 的存在性 */
487
+ const fileExists = (target) => {
488
+ if (typeof target !== "string" || target.trim().length === 0)
489
+ return false;
490
+ return existsSync(path.resolve(workspaceRoot, target));
491
+ };
492
+ /** 命令退出码 0(异步;超时视为失败)——v1 语义 */
493
+ const commandSuccess = (command, timeoutMs = 30_000) => {
494
+ return new Promise((resolve) => {
495
+ if (typeof command !== "string" || command.trim().length === 0) {
496
+ resolve(false);
497
+ return;
498
+ }
499
+ exec(command, { cwd: workspaceRoot, timeout: timeoutMs, windowsHide: true }, (error) => resolve(error === null || error === undefined));
500
+ });
501
+ };
502
+ const setConcurrency = (value) => {
503
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
504
+ throw new TypeError(`setConcurrency 需要正整数,收到 ${String(value)}`);
505
+ }
506
+ console.warn(`[agentic-workflow] setConcurrency(${value}) is ignored: v2 executor concurrency is managed globally (legacy adapter)`);
507
+ };
508
+ /** 对抗式评审(v1 原味移植;每个 reviewer 一个 journal 步骤) */
509
+ const verify = async (item, opts = {}) => {
510
+ const reviewerSlots = normalizeFanout(opts.reviewers, 2, "verify() reviewers");
511
+ const threshold = opts.threshold ?? 0.5;
512
+ const lenses = opts.lens !== undefined ? (Array.isArray(opts.lens) ? opts.lens : [opts.lens]) : [];
513
+ const claim = typeof item === "string" ? item : JSON.stringify(item) ?? String(item);
514
+ const votes = (await parallel(Array.from({ length: reviewerSlots }, (_v, i) => () => agent(`Adversarially review whether the following is REAL/correct. Try to refute it; default to real=false if unsure.${lenses.length > 0 ? ` Focus lens: ${lenses[i % lenses.length]}.` : ""}\n\n${claim}`, { label: `verify ${i + 1}`, schema: VERIFY_SCHEMA }))));
515
+ const valid = votes.filter((v) => v != null);
516
+ const realCount = valid.filter((v) => v?.real === true).length;
517
+ return {
518
+ real: valid.length > 0 && realCount / valid.length >= threshold,
519
+ realCount,
520
+ total: valid.length,
521
+ votes: valid,
522
+ };
523
+ };
524
+ /** 评审团(v1 原味移植;judge 打分经 agent journal 步骤可见) */
525
+ const judgePanel = async (attempts, opts = {}) => {
526
+ const judgeSlots = normalizeFanout(opts.judges, 3, "judgePanel() judges");
527
+ const candidates = Array.isArray(attempts)
528
+ ? attempts.map((attempt, index) => ({ attempt, index })).filter((c) => c.attempt != null)
529
+ : [];
530
+ if (candidates.length === 0)
531
+ throw new TypeError("judgePanel() 需要非空候选数组");
532
+ const rubric = opts.rubric ?? "overall quality and correctness";
533
+ const scored = (await parallel(candidates.map(({ attempt, index }) => async () => {
534
+ const text = typeof attempt === "string" ? attempt : JSON.stringify(attempt) ?? String(attempt);
535
+ const judgments = (await parallel(Array.from({ length: judgeSlots }, (_v, j) => () => agent(`Score this candidate from 0 to 1 on: ${rubric}. Reply with the score.\n\nCandidate:\n${text}`, { label: `judge ${index + 1}.${j + 1}`, schema: JUDGE_SCHEMA }))));
536
+ const valid = judgments.filter((j) => j != null);
537
+ const score = valid.length > 0
538
+ ? valid.reduce((sum, v) => sum + (Number(v?.score) || 0), 0) / valid.length
539
+ : 0;
540
+ return { index, attempt, score, judgments: valid };
541
+ })));
542
+ const valid = scored.filter((s) => s != null);
543
+ if (valid.length === 0)
544
+ throw new TypeError("judgePanel() 全部候选评审失败");
545
+ // 最高均分;同分稳定取输入顺序靠前者(v1 同款)
546
+ let best = valid[0];
547
+ for (const s of valid) {
548
+ if (s.score > best.score || (s.score === best.score && s.index < best.index))
549
+ best = s;
550
+ }
551
+ return best;
552
+ };
553
+ /** 有界重试糖:直到 until 通过或耗尽,返回最后一次结果(不抛错)——v1 语义 */
554
+ const retry = async (thunk, opts = {}) => {
555
+ const attempts = Math.max(1, opts.attempts ?? 3);
556
+ let last;
557
+ for (let i = 0; i < attempts; i++) {
558
+ last = await thunk(i);
559
+ if (opts.until?.(last) === true)
560
+ return last;
561
+ }
562
+ return last;
563
+ };
564
+ /** 人工审批:批准=true / 拒绝=false(不抛);无 gate 时回落 default —— v1 语义 */
565
+ const checkpoint = async (message, opts = {}) => {
566
+ const label = opts.label ?? "checkpoint";
567
+ return journaledStep(journal, `checkpoint:${label}`, message, async () => {
568
+ if (getCheckpointGate() === undefined) {
569
+ if (opts.default !== undefined) {
570
+ log(`checkpoint(${label}) 无交互 gate,回落 default=${String(opts.default)}`);
571
+ return opts.default;
572
+ }
573
+ throw new Error(`checkpoint(${label}) 需要交互 gate 或 { default } 选项(headless 场景请传 default)`);
574
+ }
575
+ try {
576
+ await coreCheckpoint(message, { label });
577
+ return true;
578
+ }
579
+ catch (error) {
580
+ if (error instanceof WorkflowCheckpointError)
581
+ return false;
582
+ throw error;
583
+ }
584
+ });
585
+ };
586
+ /**
587
+ * 子工作流(v1 workflow 全局三形态:注册名 / 脚本路径 / {scriptPath,label})。
588
+ * 返回子流程原始返回值(对象经 JSON 还原——v1 的 spec.brief 直取语义)。
589
+ */
590
+ const workflow = async (ref, subArgs) => {
591
+ if (ctx.subflow === undefined) {
592
+ throw new Error(`workflow(${String(ref)}) 子流程需要 journalDir(ExecutionStore);未配置的 run 不提供 subflow`);
593
+ }
594
+ const resolved = resolveFlowRef(ref, sourceFile !== undefined ? [path.dirname(sourceFile)] : []);
595
+ if (!resolved.ok)
596
+ throw new Error(resolved.error);
597
+ const stepName = `subflow:${resolved.id}${resolved.label !== undefined ? `(${resolved.label})` : ""}`;
598
+ const outcome = await journaledStep(journal, stepName, subArgs, async () => {
599
+ const sub = await ctx.subflow(resolved.id, subArgs);
600
+ // v1 语义:workflow() 返回子流程的返回值本体(对象直取字段)。
601
+ // v2 subflow 契约给 output 字符串(toOutput:对象已 JSON 化)——
602
+ // 可解析则还原为对象,纯文本保持字符串。
603
+ try {
604
+ const parsed = JSON.parse(sub.output);
605
+ if (parsed !== null && typeof parsed === "object")
606
+ return parsed;
607
+ }
608
+ catch {
609
+ // 非合法 JSON:按纯文本返回
610
+ }
611
+ return sub.output;
612
+ });
613
+ return outcome;
614
+ };
615
+ const consoleShim = {
616
+ log,
617
+ info: log,
618
+ warn: (m) => log(`[warn] ${typeof m === "string" ? m : JSON.stringify(m) ?? String(m)}`),
619
+ error: (m) => log(`[error] ${typeof m === "string" ? m : JSON.stringify(m) ?? String(m)}`),
620
+ };
621
+ const globals = [
622
+ phase,
623
+ agent,
624
+ parallel,
625
+ pipeline,
626
+ sequence,
627
+ fallback,
628
+ race,
629
+ check,
630
+ fileExists,
631
+ commandSuccess,
632
+ log,
633
+ args,
634
+ setConcurrency,
635
+ verify,
636
+ judgePanel,
637
+ retry,
638
+ checkpoint,
639
+ workflow,
640
+ consoleShim,
641
+ ];
642
+ const result = await bodyFn(...globals);
643
+ // v1 run 终检:存在未吸收的可恢复失败 → 终止报告(run 判 failed)
644
+ if (pendingGate.length > 0)
645
+ throw gateError();
646
+ return result;
647
+ },
648
+ };
649
+ }
650
+ //# sourceMappingURL=legacy-script.js.map