feat(loop-engine): auto 步的卡答过了就收步

四个前提都成立才收:声明了 auto、卡发过、这一拍没有未答的卡、配方也没再发新卡。
卡还没发过就不收 —— 那才是替人签字。

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 04:47:07 +08:00
co-authored by Claude Opus 5
parent 80e9b01f84
commit 343a281af5
3 changed files with 73 additions and 1 deletions
@@ -2290,4 +2290,47 @@ describe('auto 步:配方在拍首跑', () => {
expect(h.written).toHaveLength(1);
expect(h.decided).toHaveLength(1);
});
it('卡答过了、这一步又没有 doneWhen → 收步(人已经签了,收步是照实记录)', async () => {
const h = harness({
parsed: AUTO_MODEL,
runtime: { items: [{ id: 'S1', state: 'Active', worldTouched: true }] } as never,
unanswered: [],
sfeelCondition: () => false,
steps: [
{
item: 'S1',
guide: '',
writes: [],
reads: [],
tools: [],
auto: { do: [{ kind: 'card', as: 'decide', variant: 'review', title: '结论审核', options: [{ value: 'approve', label: '确认通过' }], basis: [{ k: 'a', v: 'b' }] }] },
},
],
});
const out = await h.runner.runTurn(TASK);
expect(h.issued).toHaveLength(0);
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).toBe('Completed');
});
it('卡还没发过就不收 —— 那才是替人签字', async () => {
const h = harness({
parsed: AUTO_MODEL,
runtime: AUTO_RUNTIME,
sfeelCondition: () => false,
steps: [
{
item: 'S1',
guide: '',
writes: [],
reads: [],
tools: [],
auto: { do: [{ kind: 'card', as: 'decide', variant: 'review', title: '结论审核', options: [{ value: 'approve', label: '确认通过' }], basis: [{ k: 'a', v: 'b' }] }] },
},
],
});
const out = await h.runner.runTurn(TASK);
expect(h.issued).toHaveLength(1);
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).not.toBe('Completed');
});
});