人手改的那一手真正落得下去;界面不撒谎那几处补齐

手改是**兜 AI 的错**的那一手 —— 这一轮把它从「摆在那儿但落不了地」做成真的:

件上就地改
· 保存**按地址收**,不看当下那张卡:载荷从前借道工单的 `blocks[i]` 取列名,
  于是熔断卡(没有 blocks)在屏上时,人补的那一格一个字都送不出去 ——
  而他要补的恰恰是把这一单卡住的那个必填格。授权面改由活动清单的台账声明把关。
· **能增删行**:错不止「这一格填错了」,还有「这一行不该在」与「漏了一行」。
  删挂记号不当场抹掉(看得见、可后悔),且**删要说出来**(`drops`)——
  「没回传就是删了」会把一次不完整的回传变成一次删表。
· **必填没填按不下提交**:与放行门 L1 同一个数同一套算法;闸上摆出差在哪一行哪一格。
· 只写真改了的那几行 —— 改一格却留「修订 4 行」的痕,那句话是假的。

判据与门
· 裁量判据带谓词时**谓词说了算**:「选填缺口已补齐」基座数得出来,
  人填好的目标价它照样判「客户未提供」的毛病由此消掉。
· 验收卡判据**读时现算** —— 人在成果件上补一格,判据跟着变。
· 放行门认「**办完了没有**」不认「有人写过」:从前写个 `false` 就把门推开,
  于是一单没打标就进了放行门。三个数全由基座现数。
· 拒回把「活没干完」说成「数或行对不上」的毛病:判据点名的表一行都没写时直说那一步没办。
  **指向两扇打不开的门,比不指还糟。**

界面不撒谎
· 日志只有三色:负责人绿 · 智能体蓝 · 系统灰(归侧五种是度量的分法,不是给人看的)。
· 验收收口答两件事:落了什么(按**交付物**点名,一份成果的几张表并成一行)、
  往哪儿去(转出哪一口、带过去哪些字段);一个口都没发就直说到此为止。
· 签完到收尾之间那一段有自己的话「字已签下,正在落地」—— 中间态本来就该说出来,只是要说对。
· 消息不再被内部事件刷屏:只有从通道进来的件才谈得上「无人认领」。
· 建客户公司与联系人**一次确认落两行**;联系人编码自增不问人;卡面摆表的人读名。

A1 三案例端到端全 released:全 A 类直通 12 拍 / 客户点名跳一条 10 拍 / 新客户缺必填 14 拍。
五道门禁全绿(971 passed)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
This commit is contained in:
2026-08-02 13:41:44 +08:00
co-authored by Claude Opus 5
parent 94f1c7a4ef
commit 259192f736
228 changed files with 27850 additions and 2847 deletions
+87 -10
View File
@@ -9,6 +9,7 @@
* 活动与工具的解耦就在②③之间:槽位只声明工具**名**,注册、连接值、白名单全在这里。
*/
import type { Container } from './container.ts';
import { installRealActivities } from './seed-activities.ts';
import { installSlots } from './seed-mvp-slots.ts';
import { seedMvpFlow } from './seed-mvp-flow.ts';
@@ -16,16 +17,27 @@ import { seedMvpFlow } from './seed-mvp-flow.ts';
const HISTORY_TOOL = {
name: 'crm.history-price-read',
displayName: '历史价查询',
version: '1.0.0',
description: '读取历史价格情报:某 PN 最近一次报价距今天数、行情是否平稳、生命周期状态',
input: { type: 'object', required: ['pn'], properties: { pn: { type: 'string' } } },
version: '1.1.0',
description:
'读取历史价格情报:某 PN 最近一次报价距今天数、行情是否平稳、生命周期状态、上次成交单价。' +
'一次可查一批 —— 传 pns 数组,出参走 rows(一单几十条料不必一条一调)。',
input: {
type: 'object',
properties: {
pn: { type: 'string', description: '单条查询' },
pns: { type: 'array', items: { type: 'string' }, description: '批量查询(优先于 pn)' },
},
},
output: {
type: 'object',
properties: {
pn: { type: 'string' },
historyDays: { type: 'integer' },
marketStable: { type: 'boolean' },
lifecycle: { type: 'string' },
hit: { type: 'boolean' },
lastPrice: { type: 'number' },
rows: { type: 'array', description: '批量查询的逐条结果' },
},
},
effect: 'read' as const,
@@ -53,6 +65,57 @@ const MAIL_TOOL = {
connectionKeys: ['endpoint'],
};
/**
* 逐条打标 —— **纯工具**(活动私有作用域,`ownerActivityId` 定死是谁的)。
*
* 它是三分类规则 P-03 的**求值器**:同输入必同结论、零方差零 token、命中规则行随出参留痕。
* 「能算的不问 LLM」的具体形态就是这样一枚工具 —— 不是让模型照着规则表判,
* 而是把规则表做成工具,模型只负责把判据取齐。
*
* 出参键名与台账列同名(`type_tag` / `basis`):拿回来直接 write,不必让模型改名 ——
* 那一步交给模型做,它会漏。
*/
const CLASSIFY_TOOL = {
name: 'quote-intake.classify',
displayName: '料号三分类打标',
version: '1.0.0',
description:
'按三分类规则 P-03 逐条打标(A 历史价≤90天且行情稳 / B 无历史·过期·波动 / C EOL·缺货·要替代)。' +
'一次可打一批 —— 传 rows(每行含 pn 与三项判据),出参走 results。',
input: {
type: 'object',
properties: {
rows: {
type: 'array',
description: '批量:每行 { pn, history_days, market_stable, lifecycle }',
items: { type: 'object' },
},
history_days: { type: 'integer', description: '单条:距上次报价天数' },
market_stable: { type: 'boolean', description: '单条:行情是否稳定' },
lifecycle: { type: 'string', description: '单条:生命周期状态' },
},
},
output: {
type: 'object',
properties: {
results: { type: 'array', description: '批量结果:每行 { pn, type_tag, play, basis, rule }' },
type_tag: { type: 'string' },
play: { type: 'string' },
basis: { type: 'string' },
rule: { type: 'string', description: '命中的规则行 —— 留痕靠它' },
},
},
effect: 'read' as const,
errors: [{ code: 'no-match', retryable: false, message: '三分类规则无命中行' }],
idempotent: true,
timeoutMs: 5_000,
retries: 0,
connectionKeys: [] as string[],
// 纯工具:活动私有作用域 —— 别的活动调不到,白名单也给不了
pure: true,
ownerActivityId: 'quote-intake',
};
/**
* 活动槽位的占位产出 —— **编排层跑通期专用的纯工具**。
*
@@ -108,7 +171,8 @@ const PLACEHOLDER_TOOL = {
/** 白名单:逐槽位授权 —— 谁能调什么,治理台说了算,活动代码里看不到这张表 */
const ALLOWLIST: Readonly<Record<string, readonly string[]>> = {
'rfq-intake': [HISTORY_TOOL.name, PLACEHOLDER_TOOL.name],
// A1 已是真活动:白名单里摘掉占位工具 —— 它一在,模型就会拿它糊弄过去
'quote-intake': [HISTORY_TOOL.name],
'alt-selection': [HISTORY_TOOL.name, PLACEHOLDER_TOOL.name],
'sourcing-inquiry': [HISTORY_TOOL.name, MAIL_TOOL.name, PLACEHOLDER_TOOL.name],
'cost-pricing': [HISTORY_TOOL.name, PLACEHOLDER_TOOL.name],
@@ -147,13 +211,20 @@ export async function seedMvp(container: Container): Promise<void> {
log(`① LLM 实例已绑定:deepseek-v4-flash · deepseek-v4-pro${apiKey === undefined ? '(环境无密钥 —— 两条都是未绑定,去治理台补)' : ''}`);
/* ② 功能工具注册两审 + 逐槽位白名单 */
for (const contract of [HISTORY_TOOL, MAIL_TOOL, PLACEHOLDER_TOOL]) {
for (const contract of [HISTORY_TOOL, MAIL_TOOL, CLASSIFY_TOOL, PLACEHOLDER_TOOL]) {
const verdict = await container.toolbox.register({
contract,
samples: [
{
// 样例集一份喂三件工具:并集覆盖各家必填
input: { pn: 'LM358DR', to: 'x@y.com', subject: '询价' },
// 样例集一份喂四件工具:并集覆盖各家必填
input: {
pn: 'LM358DR',
to: 'x@y.com',
subject: '询价',
history_days: 30,
market_stable: true,
lifecycle: '在产',
},
expectShape: { type: 'object' },
},
],
@@ -167,11 +238,17 @@ export async function seedMvp(container: Container): Promise<void> {
for (const [activityId, tools] of Object.entries(ALLOWLIST)) {
await container.toolbox.setAllowlist(activityId, tools);
}
log('② 功能工具已注册并入白名单(读类 1 · 写类 1 · 占位纯工具 1,逐槽位授权)');
log('② 功能工具已注册并入白名单(读类 1 · 写类 1 · 纯工具 2,逐活动授权)');
/* ③ 六个活动槽位经安装通道装入 —— 声明物化把四域十八对象建出来 */
/*
* ③ 活动装入 —— 两批:
* · 真活动包经**安装通道读文件**装(系统外开发、安装即用);
* · 尚未开发的槽位照旧从常量表装(契约就绪、实现留白),让编排层能整条走通。
* 两批都走同一道安装两审,声明物化顺带把台账表建出来。
*/
await installRealActivities(container);
await installSlots(container);
log('③ 六个活动槽位已装入(契约就绪,实现留白)');
log('③ 活动已装入(真活动包 1 · 槽位 6:契约就绪、实现留白)');
/* ④ 流程包 1:1 + 触发器 + 通道 */
await seedMvpFlow(container);