/** * flow 步序推进 —— 纯逻辑,零 IO(编码规范 §5.3;语义出处 AM 05)。 * * 「flow 是活动内的**确定性范式**:业务模型 = 步序声明 —— 一条谱; * 引擎照谱执行,**零临场调度**,调度权归系统。 * 差异只有一处:下一步由**声明给定**,不由 AI 决断。 * LLM 在 flow 里可以出现,但只以『被调用的工具』出现,不是调度者。」 */ import type { FlowStep } from '@contracts'; export const STEP_STATES = ['pending', 'running', 'awaiting', 'done', 'skipped', 'failed'] as const; export type StepState = (typeof STEP_STATES)[number]; export type StepRuntime = { readonly name: string; readonly state: StepState; readonly attempt: number; readonly output?: Record; readonly error?: string; }; export type FlowPosition = { /** 步序位置 —— 三份持久状态之一(OS 09 断点续跑) */ readonly index: number; readonly steps: readonly StepRuntime[]; }; export function initialPosition(steps: readonly FlowStep[]): FlowPosition { return { index: 0, steps: steps.map((s) => ({ name: s.name, state: 'pending', attempt: 0 })) }; } /** 条件跳步:条件必须是任务数据上的**确定性谓词**,由引擎求值,零 LLM(AM 5.2) */ export type PredicateEvaluator = (expression: string, scope: Readonly>) => boolean; export type NextAction = | { readonly kind: 'run'; readonly step: FlowStep; readonly index: number } | { readonly kind: 'skip'; readonly step: FlowStep; readonly index: number; readonly reason: string } | { readonly kind: 'await'; readonly step: FlowStep; readonly index: number } | { readonly kind: 'finished' }; /** * 下一步由声明给定。异步步(人类工具步 / 等事件的步)进入 `await` —— 完成条件 = 监听。 */ export function nextAction( declaration: readonly FlowStep[], position: FlowPosition, scope: Readonly>, evaluate: PredicateEvaluator, ): NextAction { for (let i = position.index; i < declaration.length; i += 1) { const step = declaration[i]!; const runtime = position.steps[i]; if (runtime?.state === 'done' || runtime?.state === 'skipped') continue; if (runtime?.state === 'awaiting') return { kind: 'await', step, index: i }; if (step.when !== undefined && !evaluate(step.when, scope)) { return { kind: 'skip', step, index: i, reason: `条件不成立:${step.when}` }; } return { kind: 'run', step, index: i }; } return { kind: 'finished' }; } export function markStep(position: FlowPosition, index: number, patch: Partial): FlowPosition { const steps = position.steps.map((s, i) => (i === index ? { ...s, ...patch } : s)); const advanced = patch.state === 'done' || patch.state === 'skipped'; return { index: advanced ? index + 1 : index, steps }; } /* ── 失败与缺口:声明给定,不临场(AM 5.3) ────────────────────────── */ export type FailureAction = | { readonly kind: 'retry'; readonly attempt: number; readonly backoff: string } /** 转人兜底:改发人类工具卡,卡内容随声明 */ | { readonly kind: 'human'; readonly workOrder: 'execution' | 'acceptance' } /** 受阻待人:挂起,携阻因 */ | { readonly kind: 'blocked' }; /** * 「每步声明重试次数与间隔;重试仍败,按声明走下一步。 * **flow 内不得出现『由 AI 决定怎么办』的失败处理** —— 需要临场,即说明该活动不该是 flow。」 */ export function failureAction(step: FlowStep, attempt: number): FailureAction { if (attempt < step.failure.retry.times) { return { kind: 'retry', attempt: attempt + 1, backoff: step.failure.retry.backoff }; } return step.failure.fallback.kind === 'human' ? { kind: 'human', workOrder: step.failure.fallback.workOrder } : { kind: 'blocked' }; } /* ── 静态检查:谱必须全程可静态检查(AM 12.3) ────────────────────── */ export type FlowIssue = { readonly step: string; readonly message: string }; export function checkDeclaration(steps: readonly FlowStep[]): FlowIssue[] { const out: FlowIssue[] = []; const names = new Set(); for (const step of steps) { if (names.has(step.name)) out.push({ step: step.name, message: '步名重复' }); names.add(step.name); // 步序声明完整:步型 / 入参映射 / 出参写入 / 完成条件 / 失败策略齐备 if (step.failure.retry.times < 0) out.push({ step: step.name, message: '重试次数不得为负' }); if (step.kind === 'human' && step.deadline.trim() === '') { out.push({ step: step.name, message: '人类工具步必配时限 —— 无死等' }); } } return out; } /** 终态映射(AM 5.3):放行完成 = 末步验收工单「放行通过」 */ export function lastStepIsAcceptance(steps: readonly FlowStep[]): boolean { const last = steps.at(-1); return last !== undefined && last.kind === 'human' && last.workOrder === 'acceptance'; } /** 并行组:组内步无依赖、**全完成再继续** */ export function parallelGroupComplete(position: FlowPosition, members: readonly string[]): boolean { return members.every((name) => { const runtime = position.steps.find((s) => s.name === name); return runtime?.state === 'done' || runtime?.state === 'skipped'; }); }