三面拆分⑭:宽进收敛 —— 起单不卡缺项,进活动受理归一,信息不全派补全单
用户定的逻辑:「调用活动,输入数据可以不定或者为空;进入活动后受理归一,
信息不全派补全单。」把收敛点从门口挪进活动 —— 三道门口闸全撤:
· 触发装配:缺必填**不再入死信**(AssemblyResult 的 missing 改 partial,带上已装配的);
装到多少带多少照样起单。死信只留给真错误(勾了确认没指定人这类配置错误)。
· process.start:撤掉调用接口缺项闸 —— 从前缺一个字段整条流都起不来,
现实是客户来信本来就常缺客户码,拦在门外等于要求外界替系统把信息备齐。
· task.start(替代旧 AM 12.1「缺项不得启动」):缺必填的任务落成「等人」,
缺的字段做成**补全卡**(elicitation,标签取 schema 的 title)发到负责人的
任务对话流,并武装 human 监听(IntakeDesk 端口;不占活动的人卡预算)。
答复只收契约声明过的键,合并进 data;齐了或答「没法填」才转 running 开跑;
答一半就就缺的部分再派一张卡。等人期间谱一步不走、不扣步数。
· 测试台预检改成**信息**不是闸:灯灭不拦起单,文案直说
「抽不出的键由受理补全单找人收齐」。
5 条受理归一单测 + 全链实测:投一封只写「想问下工控板价格」的信(零键)→
流照起 → A1 落负责人工位、等人、补全卡在对话流 → 答卡(customerCode/rfqRef)→
任务醒、合并、谱跑 3 步到验收。修一处接线错误:监听 deadline 要 ISO-8601
时长(P2D)不是时刻,传错整个补全监听静默没装上。
trigger/task/process 三份 CONTRACT.md 与产品设计.md 同步(宽进纪律成文)。
五闸绿;契约快照随本提交入库。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -70,10 +70,13 @@ describe('一封信 → 调用入参', () => {
|
||||
expect(out.data['rfqRef']).toBe('RFQ-2607-002');
|
||||
});
|
||||
|
||||
it('抽不出来就如实报缺哪个 —— 不静默起一张空单', () => {
|
||||
it('抽不全:如实列出缺哪个,但**带着已有的照样能起** —— 宽进,收敛在活动', () => {
|
||||
const out = assemble('随便问问,有没有便宜的板子');
|
||||
expect(out.kind).toBe('missing');
|
||||
if (out.kind !== 'missing') return;
|
||||
expect(out.kind).toBe('partial');
|
||||
if (out.kind !== 'partial') return;
|
||||
expect(out.fields).toEqual(expect.arrayContaining(['customerCode', 'rfqRef']));
|
||||
// 缺的键不以 undefined 占位 —— 下游按「键在不在」判缺不缺
|
||||
expect('customerCode' in out.data).toBe(false);
|
||||
expect(out.data['subject'], '抽得到的照带').toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,15 +152,7 @@ async function assembleFromMail(
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
if (assembled.kind === 'missing') {
|
||||
const wanted = channel.extract
|
||||
.filter((r) => assembled.fields.includes(r.key))
|
||||
.map((r) => `${r.key} 要匹配 /${r.regex}/`);
|
||||
throw new Error(
|
||||
`这封信里抽不出 ${assembled.fields.join('、')} —— 通道「${channel.name}」的抽键规则没命中` +
|
||||
(wanted.length > 0 ? `(${wanted.join(';')})` : ''),
|
||||
);
|
||||
}
|
||||
// 宽进:抽不全也照起 —— 缺的键由活动受理归一、补全单收齐(与线上事件起单同一条路)
|
||||
return assembled.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import type { TriggerService } from '@modules/trigger';
|
||||
|
||||
import { evaluate as evalSfeel, evaluateCondition, evaluateUnaryTest } from '@sfeel';
|
||||
import type { StaffId, TaskId } from '@contracts';
|
||||
import { staffIdSchema } from '@contracts';
|
||||
import { cardIdSchema, staffIdSchema } from '@contracts';
|
||||
import { createAgentRunner } from '../agent-runner.ts';
|
||||
import { createMastraAgentRunner } from '../mastra-agent-runner.ts';
|
||||
import type { Clock, IdGenerator } from '../runtime.ts';
|
||||
@@ -772,6 +772,32 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
|
||||
},
|
||||
},
|
||||
interaction: { setPhase: async (taskId, phase) => void (await deps.interaction.setPhase(taskId, phase)) },
|
||||
/*
|
||||
* 受理补全台(宽进):缺必填 → 补全卡进任务对话流 + 武装 human 监听等答复。
|
||||
* 不占活动的人卡预算 —— 补全发生在活动开跑之前,是受理的事,不是活动的打扰。
|
||||
*/
|
||||
intake: {
|
||||
askCompletion: async (input) => {
|
||||
const card = {
|
||||
id: cardIdSchema.parse(ids.next()),
|
||||
at: clock.now().toISOString(),
|
||||
k: 'elicitation' as const,
|
||||
ask: `受理这一单还缺几个信息 —— 补上,流程才跑得下去;实在没法填就写明原因。`,
|
||||
fields: input.fields.map((f) => ({ key: f.key, label: f.label })),
|
||||
};
|
||||
await deps.humanTool.issueChatCard(
|
||||
{ taskId: input.taskId as never, ownerId: input.ownerId as never, humanCallCap: 1_000_000 },
|
||||
card,
|
||||
);
|
||||
await listeners.register({
|
||||
taskId: input.taskId as never,
|
||||
eventType: 'human',
|
||||
keyFilter: { cardId: card.id },
|
||||
// 时限是 ISO-8601 **时长**(P2D = 两天),不是时刻 —— 到点抛时间事件,无死等
|
||||
deadline: 'P2D',
|
||||
});
|
||||
},
|
||||
},
|
||||
clock,
|
||||
ids,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user