Archived
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
135 lines
6.3 KiB
TypeScript
135 lines
6.3 KiB
TypeScript
/**
|
|
* 路线 A 深挖:
|
|
* a) 快照(WorkflowRunState)里循环轮次到底怎么表示?
|
|
* b) resume 能不能定位「第几轮的那个 step」?(label / forEachIndex / path)
|
|
* c) 重复 resume、跨轮 resume、run 结束后 resume 会怎样?
|
|
*
|
|
* 运行:SPIKE_NO_AUTORUN=1 tsx src/probe-01-route-a-snapshot.ts
|
|
*/
|
|
import { createWorkflowStateReader } from '@mastra/core/workflows';
|
|
import { DecisionScript, Ledger, banner } from './ledger.js';
|
|
import { mastraA, storeA, _internals } from './route-a-dowhile.js';
|
|
import { resetTrace, t } from './trace.js';
|
|
|
|
_internals.setLedger(new Ledger());
|
|
_internals.setScript(new DecisionScript([]));
|
|
resetTrace();
|
|
|
|
const wf = mastraA.getWorkflow('routeA');
|
|
const rawSnap = async (runId: string) =>
|
|
(await (storeA as any).stores.workflows.loadWorkflowSnapshot({
|
|
workflowName: 'route-a',
|
|
runId,
|
|
})) as Record<string, any> | null;
|
|
|
|
banner('探针 A-1:跑到「第 2 轮 A4 挂起」,看快照');
|
|
|
|
const run = await wf.createRun();
|
|
console.log('runId =', run.runId);
|
|
|
|
let res = await run.start({ inputData: { quoteId: 'Q-probe' } });
|
|
console.log('after start: status =', res.status);
|
|
|
|
// 第 1 轮 A4
|
|
res = await run.resume({ step: ['round-body', 'A4'], resumeData: { price: 100 } });
|
|
// 第 1 轮 A5 → reject → 触发回边 E10 → 第 2 轮开始,A4 再次挂起
|
|
res = await run.resume({ step: ['round-body', 'A5'], resumeData: { verdict: 'reject' } });
|
|
console.log('after A5(reject): status =', res.status, 'suspended =', JSON.stringify((res as any).suspended));
|
|
|
|
const s2 = await rawSnap(run.runId);
|
|
console.log('\n── 原始快照 WorkflowRunState 的顶层键 ──');
|
|
console.log(Object.keys(s2 ?? {}));
|
|
console.log('\nvalue =', JSON.stringify(s2?.value));
|
|
console.log('activePaths =', JSON.stringify(s2?.activePaths));
|
|
console.log('activeStepsPath=', JSON.stringify(s2?.activeStepsPath));
|
|
console.log('suspendedPaths =', JSON.stringify(s2?.suspendedPaths));
|
|
console.log('resumeLabels =', JSON.stringify(s2?.resumeLabels));
|
|
console.log('stepExecutionPath =', JSON.stringify(s2?.stepExecutionPath));
|
|
console.log('\ncontext 的键(= 快照里保存了哪些 step 的结果):');
|
|
console.log(JSON.stringify(Object.keys(s2?.context ?? {}), null, 0));
|
|
console.log('\ncontext["round-body.A4"] =');
|
|
console.log(JSON.stringify(s2?.context?.['round-body.A4'], null, 2));
|
|
console.log('\ncontext["round-body.A5"] =');
|
|
console.log(JSON.stringify(s2?.context?.['round-body.A5'], null, 2));
|
|
console.log('\ncontext["round-body"] (循环体本身) =');
|
|
console.log(JSON.stringify(s2?.context?.['round-body'], null, 2));
|
|
|
|
banner('探针 A-2:resume 能不能指名「第 1 轮的 A4」?');
|
|
|
|
// 现在挂起的是「第 2 轮的 A4」。第 1 轮 A4 的 resumeLabel 是 A4-round-1。
|
|
const reader = createWorkflowStateReader((await wf.getWorkflowRunById(run.runId))!);
|
|
console.log('当前 resumeLabels =', JSON.stringify(reader.getResumeLabels()));
|
|
console.log('getResumeLabel("A4-round-1") =', JSON.stringify(reader.getResumeLabel('A4-round-1')));
|
|
console.log('getResumeLabel("A4-round-2") =', JSON.stringify(reader.getResumeLabel('A4-round-2')));
|
|
|
|
try {
|
|
const r = await run.resume({ label: 'A4-round-1', resumeData: { price: 111 } });
|
|
console.log('resume(label=A4-round-1) → status =', r.status);
|
|
console.log(' 报错?', JSON.stringify((r as any).error ?? null));
|
|
} catch (e) {
|
|
console.log('resume(label=A4-round-1) 抛异常:', (e as Error).message);
|
|
}
|
|
|
|
banner('探针 A-3:forEachIndex 对 dowhile 有用吗?');
|
|
try {
|
|
const r = await run.resume({
|
|
step: ['round-body', 'A4'],
|
|
resumeData: { price: 222 },
|
|
forEachIndex: 0,
|
|
});
|
|
console.log('resume(step=round-body.A4, forEachIndex=0) → status =', r.status);
|
|
console.log(' error =', JSON.stringify((r as any).error ?? null));
|
|
const snap = await rawSnap(run.runId);
|
|
console.log(
|
|
' 快照里 A4 的 output.price =',
|
|
JSON.stringify(snap?.context?.['round-body.A4']?.output?.price),
|
|
);
|
|
} catch (e) {
|
|
console.log('resume(forEachIndex=0) 抛异常:', (e as Error).message);
|
|
}
|
|
|
|
banner('探针 A-4:对「已经推进过的 step」重复 resume');
|
|
try {
|
|
const r = await run.resume({ step: ['round-body', 'A4'], resumeData: { price: 333 } });
|
|
console.log('重复 resume A4 → status =', r.status, 'error =', JSON.stringify((r as any).error ?? null));
|
|
} catch (e) {
|
|
console.log('重复 resume A4 抛异常:', (e as Error).message);
|
|
}
|
|
|
|
banner('探针 A-5:resume 一个「没有挂起」的 step');
|
|
try {
|
|
const r = await run.resume({ step: ['round-body', 'A6-sub', 'A6'], resumeData: { outcome: 'won' } });
|
|
console.log('resume 未挂起的 A6 → status =', r.status, 'error =', JSON.stringify((r as any).error ?? null));
|
|
} catch (e) {
|
|
console.log('resume 未挂起的 A6 抛异常:', (e as Error).message);
|
|
}
|
|
|
|
banner('探针 A-6:把这一单跑完,再看终态快照能否回溯轮次');
|
|
let guard = 0;
|
|
let cur = await wf.getWorkflowRunById(run.runId);
|
|
while (cur?.status === 'suspended' && guard++ < 8) {
|
|
const rd = createWorkflowStateReader(cur);
|
|
const sus = rd.getSuspendedStep()!;
|
|
const nodeId = (sus.suspendPayload as { nodeId: string }).nodeId;
|
|
const data =
|
|
nodeId === 'A4' ? { price: 77 } : nodeId === 'A5' ? { verdict: 'pass' } : { outcome: 'won' };
|
|
t('[probe]', `resume ${nodeId} @ ${JSON.stringify(sus.path)}`, data);
|
|
await run.resume({ step: sus.path, resumeData: data });
|
|
cur = await wf.getWorkflowRunById(run.runId);
|
|
}
|
|
console.log('最终 status =', cur?.status, 'result =', JSON.stringify(cur?.result));
|
|
|
|
const fin = await rawSnap(run.runId);
|
|
const finInner = (await (storeA as any).stores.workflows.loadWorkflowSnapshot({
|
|
workflowName: 'round-body',
|
|
runId: run.runId,
|
|
})) as Record<string, any> | null;
|
|
console.log('\n终态外层快照 context 的键:', JSON.stringify(Object.keys(fin?.context ?? {})));
|
|
console.log('终态外层 context["round-body"].metadata =', JSON.stringify(fin?.context?.['round-body']?.metadata));
|
|
console.log('终态内层(round-body)context 的键:', JSON.stringify(Object.keys(finInner?.context ?? {})));
|
|
console.log('终态内层 A4.suspendPayload =', JSON.stringify(finInner?.context?.['A4']?.suspendPayload));
|
|
console.log('终态内层 A4.output =', JSON.stringify(finInner?.context?.['A4']?.output));
|
|
console.log('终态内层 A4 是数组吗? ', Array.isArray(finInner?.context?.['A4']));
|
|
console.log('\n台账(我们自己记的)里第 1 轮还在吗?');
|
|
_internals.getLedger().dump('probe');
|