Archived
选择组的出口选择要走到落地:恰发一口,随放行一并确认
A6 的输出是**选择组**(negotiation: won / revise / lost),回传协议里本来就有
`selectedPorts`,human-tool 也接住了 —— 但落地那一步硬写 `selectedPorts: {}`,
选择从未抵达。结果:放行了、台账写了,却「发射输出口:(无)」,
G4 异或网关拿不到 outcome,整条流悄无声息地停住。
按 verdict 同样的路子把它串起来:人事件载荷 → 末步运行时 → 谱走完时报上去 →
落地收敛时照它发射。
gates: tsc / eslint / depcruise / 399 测试 全绿
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "rfq-20260728A@x.com",
|
||||||
|
"from": "buyer@x.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C210 RFQ-20260728A 求报价",
|
||||||
|
"body": "客户编码 C210,询价单号 RFQ-��\nPN-FULL-002 TI 年用量 9000 目标价 6.00 交期 10w",
|
||||||
|
"receivedAt": "2026-07-27T20:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -414,6 +414,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
|||||||
...(outcome.blockedReason === undefined ? {} : { reason: outcome.blockedReason }),
|
...(outcome.blockedReason === undefined ? {} : { reason: outcome.blockedReason }),
|
||||||
// 谱走完时把放行裁决带上来 —— 完成只出自放行通过
|
// 谱走完时把放行裁决带上来 —— 完成只出自放行通过
|
||||||
...(outcome.released === undefined ? {} : { released: outcome.released }),
|
...(outcome.released === undefined ? {} : { released: outcome.released }),
|
||||||
|
...(outcome.selectedPorts === undefined ? {} : { selectedPorts: outcome.selectedPorts }),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onEvent: async (taskId, event) => {
|
onEvent: async (taskId, event) => {
|
||||||
@@ -433,7 +434,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
|||||||
taskId: input.taskId,
|
taskId: input.taskId,
|
||||||
ownerId: input.ownerId,
|
ownerId: input.ownerId,
|
||||||
humanCallCap: input.humanCallCap,
|
humanCallCap: input.humanCallCap,
|
||||||
selectedPorts: {},
|
selectedPorts: input.selectedPorts,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
converged: outcome.verdict.kind === 'converged',
|
converged: outcome.verdict.kind === 'converged',
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export type AdvanceOutcome = {
|
|||||||
readonly waitingFor?: { readonly kind: 'human' | 'event' | 'time'; readonly ref: string; readonly deadline: string };
|
readonly waitingFor?: { readonly kind: 'human' | 'event' | 'time'; readonly ref: string; readonly deadline: string };
|
||||||
/** finished 时:末步验收工单的裁决决定终态 */
|
/** finished 时:末步验收工单的裁决决定终态 */
|
||||||
readonly released?: boolean;
|
readonly released?: boolean;
|
||||||
|
/** 选择组的出口选择:恰发一口,随放行一并确认 */
|
||||||
|
readonly selectedPorts?: Record<string, string>;
|
||||||
readonly blockedReason?: string;
|
readonly blockedReason?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export type StepRuntime = {
|
|||||||
readonly error?: string;
|
readonly error?: string;
|
||||||
/** 验收工单的放行裁决:pass / 驳回类别 —— 末步据此定终局 */
|
/** 验收工单的放行裁决:pass / 驳回类别 —— 末步据此定终局 */
|
||||||
readonly verdict?: string;
|
readonly verdict?: string;
|
||||||
|
/** 选择组的出口选择:恰发一口,随放行一并确认(AM 1.2) */
|
||||||
|
readonly selectedPorts?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlowPosition = {
|
export type FlowPosition = {
|
||||||
@@ -149,3 +151,8 @@ export function lastVerdictPassed(position: FlowPosition): boolean {
|
|||||||
const last = position.steps[position.steps.length - 1];
|
const last = position.steps[position.steps.length - 1];
|
||||||
return last?.verdict === 'pass';
|
return last?.verdict === 'pass';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 末步随放行确认的出口选择 —— 落地收敛时按它发射 */
|
||||||
|
export function lastSelectedPorts(position: FlowPosition): Record<string, string> {
|
||||||
|
return position.steps[position.steps.length - 1]?.selectedPorts ?? {};
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { FlowPosition } from '../domain/step-sequence.ts';
|
|||||||
import {
|
import {
|
||||||
checkDeclaration,
|
checkDeclaration,
|
||||||
fallbackFields,
|
fallbackFields,
|
||||||
|
lastSelectedPorts,
|
||||||
lastStepIsAcceptance,
|
lastStepIsAcceptance,
|
||||||
lastVerdictPassed,
|
lastVerdictPassed,
|
||||||
failureAction,
|
failureAction,
|
||||||
@@ -257,7 +258,8 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService {
|
|||||||
// 末步是验收工单时,终态由其**裁决**给出 —— 完成只出自放行通过。
|
// 末步是验收工单时,终态由其**裁决**给出 —— 完成只出自放行通过。
|
||||||
// 裁决记在末步的 runtime 上(resume 时落笔),谱走完在这里读出来。
|
// 裁决记在末步的 runtime 上(resume 时落笔),谱走完在这里读出来。
|
||||||
const released = lastStepIsAcceptance(model.steps) && lastVerdictPassed(position);
|
const released = lastStepIsAcceptance(model.steps) && lastVerdictPassed(position);
|
||||||
return { position, status: 'finished', released };
|
// 出口选择随放行一并确认,落地收敛时照它发射
|
||||||
|
return { position, status: 'finished', released, selectedPorts: lastSelectedPorts(position) };
|
||||||
}
|
}
|
||||||
case 'skip': {
|
case 'skip': {
|
||||||
await deps.memory.append({
|
await deps.memory.append({
|
||||||
@@ -295,12 +297,19 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService {
|
|||||||
});
|
});
|
||||||
// 放行裁决落笔在这一步的运行时上:谱走完时据此定终局
|
// 放行裁决落笔在这一步的运行时上:谱走完时据此定终局
|
||||||
const verdict = event.kind === 'human' ? String(event.payload['verdict'] ?? '') : '';
|
const verdict = event.kind === 'human' ? String(event.payload['verdict'] ?? '') : '';
|
||||||
|
const chosen = event.payload['selectedPorts'];
|
||||||
|
const selectedPorts =
|
||||||
|
typeof chosen === 'object' && chosen !== null ? (chosen as Record<string, string>) : undefined;
|
||||||
if (step !== undefined && event.kind === 'human') {
|
if (step !== undefined && event.kind === 'human') {
|
||||||
// 人事件的载荷是**回传协议**的形状(`{mode, workOrderId, values}`),
|
// 人事件的载荷是**回传协议**的形状(`{mode, workOrderId, values}`),
|
||||||
// 出参在 `values` 里;直接把整个载荷当出参写,落进去的会是协议壳而不是答案。
|
// 出参在 `values` 里;直接把整个载荷当出参写,落进去的会是协议壳而不是答案。
|
||||||
await writeOutput(taskId, step, replyValuesOf(event.payload));
|
await writeOutput(taskId, step, replyValuesOf(event.payload));
|
||||||
}
|
}
|
||||||
const next = markStep(position, index, verdict === '' ? { state: 'done' } : { state: 'done', verdict });
|
const next = markStep(
|
||||||
|
position,
|
||||||
|
index,
|
||||||
|
verdict === '' ? { state: 'done' } : { state: 'done', verdict, ...(selectedPorts === undefined ? {} : { selectedPorts }) },
|
||||||
|
);
|
||||||
await deps.state.save(taskId, model, next);
|
await deps.state.save(taskId, model, next);
|
||||||
return { position: next, status: 'running' };
|
return { position: next, status: 'running' };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ export function createTaskService(deps: TaskServiceDeps): TaskService {
|
|||||||
taskId,
|
taskId,
|
||||||
ownerId: task.ownerId,
|
ownerId: task.ownerId,
|
||||||
humanCallCap: task.caps.humanCalls,
|
humanCallCap: task.caps.humanCalls,
|
||||||
|
selectedPorts: outcome.selectedPorts ?? {},
|
||||||
});
|
});
|
||||||
if (!landed.converged) {
|
if (!landed.converged) {
|
||||||
// 落地未收敛:输出口不得发射,任务受阻待人(落地失败卡已发)
|
// 落地未收敛:输出口不得发射,任务受阻待人(落地失败卡已发)
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export type EngineOutcome = {
|
|||||||
readonly reason?: string;
|
readonly reason?: string;
|
||||||
/** 谱走完时:放行是否通过 —— **完成只出自放行通过**,引擎不自行宣布完成 */
|
/** 谱走完时:放行是否通过 —— **完成只出自放行通过**,引擎不自行宣布完成 */
|
||||||
readonly released?: boolean;
|
readonly released?: boolean;
|
||||||
|
/** 选择组的出口选择:恰发一口,随放行一并确认 */
|
||||||
|
readonly selectedPorts?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +59,13 @@ export type EngineOutcome = {
|
|||||||
* 放行通过的那一刻才落地:草稿合并入正式版、动作意向执行、收敛后才发射输出口。
|
* 放行通过的那一刻才落地:草稿合并入正式版、动作意向执行、收敛后才发射输出口。
|
||||||
*/
|
*/
|
||||||
export type LandingGateway = {
|
export type LandingGateway = {
|
||||||
land(input: { taskId: TaskId; ownerId: StaffId; humanCallCap: number }): Promise<{
|
land(input: {
|
||||||
|
taskId: TaskId;
|
||||||
|
ownerId: StaffId;
|
||||||
|
humanCallCap: number;
|
||||||
|
/** 选择组的出口选择 —— 恰发一口,由放行时的确认给定 */
|
||||||
|
selectedPorts: Readonly<Record<string, string>>;
|
||||||
|
}): Promise<{
|
||||||
converged: boolean;
|
converged: boolean;
|
||||||
emittedPorts: readonly string[];
|
emittedPorts: readonly string[];
|
||||||
ledgerRowsMerged: number;
|
ledgerRowsMerged: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user