Archived
feat(loop-engine): 拍首跑配方 —— 确定的活由引擎做完,剩下的才问模型
liveStepOf 从 turn-runner 提纯成纯函数(带 repetitionRule 的项收了仍 Enabled, 不跳过已收掉的就整单钉在第一步 —— 这条实测栽过)。 有卡在等人时配方照样跑但只算只写:人在件上按了保存,该铺的行就铺出来。 老活动一个字不用改:剧本里没有 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:
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { liveStepOf } from '../internal/domain/live-step.ts';
|
||||
|
||||
const steps = [
|
||||
{ item: 'S1', guide: '' },
|
||||
{ item: 'S2', guide: '' },
|
||||
{ item: 'T_release', guide: '' },
|
||||
];
|
||||
|
||||
describe('liveStepOf:这一单此刻在办哪一步', () => {
|
||||
it('在场的第一个还没收掉的项', () => {
|
||||
expect(liveStepOf(steps, new Set(), [{ id: 'S1' }, { id: 'S2' }])?.item).toBe('S1');
|
||||
});
|
||||
|
||||
it('**收掉的项要跳过** —— 带 repetitionRule 的项收了之后仍是 Enabled,不跳就整单钉在第一步', () => {
|
||||
expect(liveStepOf(steps, new Set(['S1']), [{ id: 'S1' }, { id: 'S2' }])?.item).toBe('S2');
|
||||
});
|
||||
|
||||
it('在场的都收完了 → undefined', () => {
|
||||
expect(liveStepOf(steps, new Set(['S1', 'S2']), [{ id: 'S1' }, { id: 'S2' }])).toBeUndefined();
|
||||
});
|
||||
|
||||
it('在场的项没有剧本 → undefined(没写剧本的活动照旧跑)', () => {
|
||||
expect(liveStepOf(steps, new Set(), [{ id: 'X' }])).toBeUndefined();
|
||||
});
|
||||
|
||||
it('次序按 onStage 给的次序,不按 steps 的次序', () => {
|
||||
expect(liveStepOf(steps, new Set(), [{ id: 'S2' }, { id: 'S1' }])?.item).toBe('S2');
|
||||
});
|
||||
});
|
||||
@@ -2152,3 +2152,142 @@ describe('终局放行那一步:human 被拒并指向 submit', () => {
|
||||
expect(h.issued.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('auto 步:配方在拍首跑', () => {
|
||||
/** 一步的活动:那一步声明 auto */
|
||||
const AUTO_MODEL: CaseModel = {
|
||||
name: '一步的活动',
|
||||
autoComplete: false,
|
||||
items: [{ id: 'S1', name: '补数据', type: 'humanTask', discretionary: false, entryCriteria: [], exitCriteria: [] }],
|
||||
sentries: [],
|
||||
} as never;
|
||||
const AUTO_RUNTIME: CaseRuntime = { items: [{ id: 'S1', state: 'Available' }] } as never;
|
||||
|
||||
it('配方发了卡 → 这一拍一次都不问模型,状态 awaiting', async () => {
|
||||
const h = harness({
|
||||
parsed: AUTO_MODEL,
|
||||
runtime: AUTO_RUNTIME,
|
||||
sfeelCondition: () => false,
|
||||
steps: [
|
||||
{
|
||||
item: 'S1',
|
||||
guide: '',
|
||||
writes: [],
|
||||
reads: [],
|
||||
tools: [],
|
||||
auto: { do: [{ kind: 'card', as: 'execution', title: '补数据', basis: [{ k: '料号', v: 'XL2596S-5.0' }] }] },
|
||||
},
|
||||
],
|
||||
});
|
||||
const out = await h.runner.runTurn(TASK);
|
||||
expect(h.decided).toHaveLength(0);
|
||||
expect(out.status).toBe('awaiting');
|
||||
expect(h.issued.map((i) => i.workOrder)).toEqual(['execution']);
|
||||
});
|
||||
|
||||
it('配方只写不发卡、这一步因此办完 → 收步,还是不问模型', async () => {
|
||||
const h = harness({
|
||||
parsed: AUTO_MODEL,
|
||||
runtime: AUTO_RUNTIME,
|
||||
sfeelCondition: (_expr, scope) => Number(scope['quote_intake_record_rows'] ?? 0) > 0,
|
||||
viewFn: (written) => ({ quote_intake_record_rows: written.length }),
|
||||
steps: [
|
||||
{
|
||||
item: 'S1',
|
||||
guide: '',
|
||||
writes: [],
|
||||
reads: [],
|
||||
tools: [],
|
||||
doneWhen: 'quote_intake_record_rows > 0',
|
||||
auto: { do: [{ kind: 'project', into: 'quote_intake.record', from: { once: true }, fields: { pn: 'LM2596S-5.0', eau: 1000 } }] },
|
||||
},
|
||||
],
|
||||
});
|
||||
const out = await h.runner.runTurn(TASK);
|
||||
expect(h.decided).toHaveLength(0);
|
||||
expect(h.written.map((w) => w.key)).toEqual([{ pn: 'LM2596S-5.0' }]);
|
||||
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).toBe('Completed');
|
||||
});
|
||||
|
||||
it('有卡在等人交回:配方照样写,但一张新卡都不发', async () => {
|
||||
const h = harness({
|
||||
parsed: AUTO_MODEL,
|
||||
runtime: AUTO_RUNTIME,
|
||||
unanswered: [{ id: 'card-9', title: '补数据' }],
|
||||
sfeelCondition: () => false,
|
||||
viewFn: (written) => ({ n: written.length }),
|
||||
steps: [
|
||||
{
|
||||
item: 'S1',
|
||||
guide: '',
|
||||
writes: [],
|
||||
reads: [],
|
||||
tools: [],
|
||||
auto: {
|
||||
do: [
|
||||
{ kind: 'project', into: 'quote_intake.record', from: { once: true }, fields: { pn: 'A', eau: 1 } },
|
||||
{ kind: 'card', as: 'execution', title: '再来一张', basis: [{ k: 'a', v: 'b' }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await h.runner.runTurn(TASK);
|
||||
expect(h.written).toHaveLength(1);
|
||||
expect(h.issued).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('配方抛错(地址不在台账声明里)→ 交人,不问模型、不重试', async () => {
|
||||
const h = harness({
|
||||
parsed: AUTO_MODEL,
|
||||
runtime: AUTO_RUNTIME,
|
||||
sfeelCondition: () => false,
|
||||
steps: [
|
||||
{
|
||||
item: 'S1',
|
||||
guide: '',
|
||||
writes: [],
|
||||
reads: [],
|
||||
tools: [],
|
||||
auto: { do: [{ kind: 'project', into: 'nope.nope', from: { once: true }, fields: { a: 'b' } }] },
|
||||
},
|
||||
],
|
||||
});
|
||||
const out = await h.runner.runTurn(TASK);
|
||||
expect(h.decided).toHaveLength(0);
|
||||
expect(out.status).toBe('awaiting');
|
||||
expect(h.appended.some((a) => a.title.includes('nope.nope'))).toBe(true);
|
||||
});
|
||||
|
||||
it('这一步有 guide:配方跑完照常问模型(半 auto)', async () => {
|
||||
const h = harness({
|
||||
parsed: OPEN_MODEL,
|
||||
runtime: OPEN_RUNTIME,
|
||||
sfeelCondition: () => false,
|
||||
viewFn: (written) => ({ n: written.length }),
|
||||
steps: [
|
||||
{
|
||||
item: 'i1',
|
||||
guide: '把结论写实',
|
||||
writes: [],
|
||||
reads: [],
|
||||
tools: [],
|
||||
auto: { do: [{ kind: 'project', into: 'quote_intake.record', from: { once: true }, fields: { pn: 'A', eau: 1 } }] },
|
||||
},
|
||||
],
|
||||
/*
|
||||
* brief 原文这里写的是 `{ kind: 'idle', rationale: '看过了' }` ——
|
||||
* `AgentAction` 判别联合里没有 `'idle'` 这一档(全仓 grep 零命中),
|
||||
* `applyAction` 的 switch 又没有 default 分支,不认得的 kind 会让它
|
||||
* 隐式 return undefined,下一行 `applied.runtime` 当场抛 TypeError。
|
||||
* 这条用例要测的是「配方写完之后模型仍被正常问了一次」,与模型具体
|
||||
* 回了哪个动作无关 —— 换成场上已有测试反复验证过的 `finish`(收掉
|
||||
* i1,i1 没声明 manualActivation / doneWhen,收得干净)。
|
||||
*/
|
||||
action: { kind: 'finish', itemId: 'i1', rationale: '看过了' },
|
||||
});
|
||||
await h.runner.runTurn(TASK);
|
||||
expect(h.written).toHaveLength(1);
|
||||
expect(h.decided).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user