Archived
fix(loop-engine): 「自动收步」留痕去掉 internal —— machinery() 挡下的第五处同病
R10:评审挖出第五个同病的 —— turn-runner.ts 那条「自动收步:…卡已交回」用的是
SKELETON 里就有的 kind: 'engine-transition',但 detail.internal: true 让它照样
被 machinery() 的第一刀滤掉,永远进不了工作日志。保留 item / auto 两个键,只删 internal。
新增回归测试:跑真实的 turn-runner.ts,捕获这一笔留痕喂给 structuredLog 全程,
断言可见且归系统。
顺带修实一条名不副实的既有用例:task-memory.test.ts:453 那条看着像在覆盖
「自动收步」这件事,但 entry() 工厂不设默认 detail,原入参是 {},根本没走到
machinery() 的 internal 过滤 —— 补上生产环境真实的 detail 形状(item / 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:
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { TaskId } from '@contracts';
|
||||
import { deriveSide, structuredLog } from '@modules/memory';
|
||||
import type { EntryKind, GovernanceMark, MemoryEntry, VersionStamp } from '@modules/memory';
|
||||
import { createTurnRunner, echoTitleOf, handsOff, resendVerdict, sameGist, stalledTurns, toolInputHash, unmetWhy, answeredCardFact, earlyFinishTitleOf } from '../internal/usecase/turn-runner.ts';
|
||||
import type { TurnRunnerDeps } from '../internal/usecase/turn-runner.ts';
|
||||
import type { PendingAcceptance } from '../port.ts';
|
||||
@@ -14,6 +16,30 @@ import type { CaseRuntime } from '../internal/domain/lifecycle.ts';
|
||||
|
||||
const TASK = 't-1' as TaskId;
|
||||
|
||||
/** 合成的信封字段 —— 与 `task-memory.test.ts` 的 `entry()` 工厂同一套写法 */
|
||||
const STAMP: VersionStamp = { contractVersion: '1', businessModelVersion: '1', skillVersions: [] };
|
||||
const GOVERNANCE: GovernanceMark = { masked: false, learnable: true };
|
||||
|
||||
/**
|
||||
* **把捕获到的一笔真实留痕包成完整 `MemoryEntry`,喂给 `structuredLog` 验收**(2026-08-29,R10)。
|
||||
* 与 `auto-runner.test.ts` 的同名助手同一套写法 —— 见那边的注释。
|
||||
*/
|
||||
function asLogEntry(captured: { kind: string; title: string; detail: Record<string, unknown> | undefined }): MemoryEntry {
|
||||
const kind = captured.kind as EntryKind;
|
||||
return {
|
||||
id: 'e-1',
|
||||
taskId: TASK,
|
||||
seq: 1,
|
||||
at: '2026-08-29T00:00:00.000Z',
|
||||
kind,
|
||||
side: deriveSide(kind),
|
||||
title: captured.title,
|
||||
stamp: STAMP,
|
||||
governance: GOVERNANCE,
|
||||
...(captured.detail === undefined ? {} : { detail: captured.detail }),
|
||||
};
|
||||
}
|
||||
|
||||
/** 空案例模型:必做项全无 + autoComplete → 一进 runTurn 就「判齐」 */
|
||||
const READY_MODEL: CaseModel = { name: '空案', autoComplete: true, items: [], sentries: [] } as never;
|
||||
const READY_RUNTIME: CaseRuntime = { items: [] } as never;
|
||||
@@ -38,7 +64,7 @@ type Harness = {
|
||||
/** 每次推帧时「已写下几行」—— 帧推在行动之前的话,它是 0 */
|
||||
readonly writesAtPush: number[];
|
||||
/** 这一拍写下的全部留痕 —— 断言「被拒/无事发生」走这里 */
|
||||
readonly appended: Array<{ kind: string; title: string }>;
|
||||
readonly appended: Array<{ kind: string; title: string; detail: Record<string, unknown> | undefined }>;
|
||||
/** 真被调到的工具名 —— 断言「拦没拦住调用」走这里 */
|
||||
readonly toolCalls: string[];
|
||||
/** 真立起来的监听 —— 断言「拦没拦住立监听」走这里 */
|
||||
@@ -174,8 +200,8 @@ function harness(
|
||||
declared: async () => over.declaredListeners ?? [{ eventType: 'data', keyFilter: {} }],
|
||||
} as never,
|
||||
memory: {
|
||||
append: async (entry: { kind: string; title: string }) => {
|
||||
appended.push({ kind: entry.kind, title: entry.title });
|
||||
append: async (entry: { kind: string; title: string; detail?: Record<string, unknown> }) => {
|
||||
appended.push({ kind: entry.kind, title: entry.title, detail: entry.detail });
|
||||
},
|
||||
loadActivityMemory: async () => [],
|
||||
window: async () => over.trail ?? [],
|
||||
@@ -2311,6 +2337,17 @@ describe('auto 步:配方在拍首跑', () => {
|
||||
const out = await h.runner.runTurn(TASK);
|
||||
expect(h.issued).toHaveLength(0);
|
||||
expect(out.runtime.items.find((i) => i.id === 'S1')?.state).toBe('Completed');
|
||||
/*
|
||||
* **配方留痕真的到得了工作日志**(2026-08-29,R10,spec §5.9 验收)。
|
||||
* 上一轮(Task 12)只测过 actorOf 这个纯函数;这里把「自动收步」这一笔捕获到的
|
||||
* 真实 {kind, title, detail} 喂给 structuredLog 全程 —— 从前它带着 `internal: true`,
|
||||
* 会被 `machinery()` 挡在骨架之外,`actorOf` 判得再对也没用。
|
||||
*/
|
||||
const settled = h.appended.find((e) => e.title.startsWith('自动收步:'));
|
||||
expect(settled).toBeDefined();
|
||||
const lines = structuredLog([asLogEntry(settled!)]);
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(lines[0]?.who).toBe('system');
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@@ -2037,7 +2037,13 @@ export function createTurnRunner(deps: TurnRunnerDeps): LoopEngineService {
|
||||
kind: 'engine-transition',
|
||||
/* 标题别以「完成」开头 —— `task-log.actorOf` 按 /^(完成|开始办)/ 认 AI,而这一步是引擎收的 */
|
||||
title: `自动收步:${current.item} —— 卡已交回,这一步到此为止`,
|
||||
detail: { internal: true, item: current.item, auto: current.item },
|
||||
/*
|
||||
* **不再标 internal**(2026-08-29,R10)—— 从前的 `internal: true` 会被
|
||||
* `task-log.ts` 的 `machinery()` 挡下,这一笔永远进不了工作日志。
|
||||
* `engine-transition` 本就在骨架里,标题也不匹配「完成/开始办」的正则,
|
||||
* 只要不被 `machinery()` 滤掉,它就会正确地归系统 —— 不必藏成内部痕。
|
||||
*/
|
||||
detail: { item: current.item, auto: current.item },
|
||||
});
|
||||
await deps.state.save(taskId, model, parsed, settled);
|
||||
/* `Navigation.kind` 没有 'progressed' 这一档(只有 proceed/breakpoint/converged,
|
||||
|
||||
Reference in New Issue
Block a user