feat(loop-engine): analyze 动词 —— 系统组装入参、声明出参 schema、系统把结果落表

AI 在这里只是一个纯函数:不给工具、不给记忆、不多轮。
行数不由它决定 —— 出参是拿主键去 lookup 的一张查表,它漏一条那一格就是空,
required 立刻把它变成 _missing > 0,提交闸与放行门当场拦住。
出参不成形就地重问一次,再不成抛出去交人。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-29 03:54:14 +08:00
co-authored by Claude Opus 5
parent b0a3cc3a55
commit fe8de5319c
7 changed files with 228 additions and 4 deletions
@@ -135,6 +135,36 @@ export function createAutoRunner(deps: AutoRunnerDeps): {
return { wrote: localWrote, dropped: localDropped };
}
if (action.kind === 'analyze') {
const payload = expandSource(action.input.from, base).map(({ row, outer }) => {
const item: Record<string, unknown> = {};
for (const [name, expr] of Object.entries(action.input.fields)) {
item[name] = evalExpr(expr, { ...base, row, ...(outer === undefined ? {} : { outer }), computed: item });
}
return item;
});
const { object } = await deps.analyze.run({
taskId: context.taskId,
model: action.model,
instruction: action.instruction,
payload,
outputSchema: action.output,
label: `${context.stepId}#${index + 1}`,
});
/* `into` 既是出参里那个数组字段的名字,也是拍内作用域的键 —— 一处声明,两处认 */
const rows = object[action.into];
results[`result:${action.into}`] = Array.isArray(rows) ? rows : [];
await deps.memory.append({
taskId: context.taskId,
kind: 'result',
title: `已把 ${payload.length} 条参数交给模型逐条判定,拿回 ${results[`result:${action.into}`]?.length ?? 0} 条结论`,
detail: { auto: `${context.stepId}#${index + 1}`, internal: true, model: action.model },
});
/* 出参只活在拍内作用域里,不进草稿层 —— 下一笔要能看见它,所以这里也算「脏」 */
scope = { ...scope, ...results };
return { wrote: 0, dropped: 0 };
}
throw new AutoRecipeError(index, `动词尚未实现:${action.kind}`);
}