Archived
人手改的那一手真正落得下去;界面不撒谎那几处补齐
手改是**兜 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:
@@ -15,19 +15,62 @@ export type LogLine = {
|
||||
readonly at: string;
|
||||
readonly side: AttributionSide;
|
||||
readonly sideLabel: string;
|
||||
/**
|
||||
* **这一条是谁干的** —— 日志只认三档(2026-08-02 用户定):
|
||||
* `owner` 负责人(绿)· `agent` 智能体(蓝)· `system` 系统(灰)。
|
||||
*
|
||||
* 归侧 `side` 有五种(machine / human / human-verdict / world-wait / trigger),
|
||||
* 那是**度量**的分法(自主率要把「等外面」与「机器干的」分开);
|
||||
* 而人扫这一屏时只问一件事:**这一步是我办的、AI 办的,还是流程自己走到的**。
|
||||
* 拿五种去上色,屏上就是五种深浅,而那五种对读的人不构成区别。
|
||||
*/
|
||||
readonly who: 'owner' | 'agent' | 'system';
|
||||
/**
|
||||
* **类型章:四字短语**,与对话区同词同粒度(2026-07-31 用户定)。
|
||||
*
|
||||
* 从前这一格摆的是归侧(机器产出 / 系统等待 …)—— 那是**度量的口径**,
|
||||
* 答的是「这一步该算谁的工时」;人翻日志想知道的是「这一步是什么动作」。
|
||||
* 两者不是一回事:同一个「系统等待」下面既有派工单也有发录入卡。
|
||||
* 归侧仍在(`side` / `sideLabel`),度量与配色照旧靠它,只是不再占着这一格。
|
||||
*/
|
||||
readonly action: string;
|
||||
/** 主语:任务助手 / 系统;人做的那些留空,由上层照 `actorId` 换成名字 */
|
||||
readonly actor: string;
|
||||
/** 人做的那一条是谁做的 —— 域里不认识人名(纯函数零 IO),只交出 id */
|
||||
readonly actorId: string | undefined;
|
||||
/** 内容本身,**不含主语** —— 主语由上层拼在前头(名字要查库才知道) */
|
||||
readonly title: string;
|
||||
readonly summary: string | undefined;
|
||||
readonly eventRef: string | undefined;
|
||||
readonly position: string | undefined;
|
||||
};
|
||||
|
||||
/** 骨架:只有这些种类进结构化日志(OS 8.1 列举) */
|
||||
/**
|
||||
* 骨架:哪些种类进结构化日志(OS 8.1 列举)。
|
||||
*
|
||||
* **工作日志是给人看的**(2026-07-31 用户定)——「装配技能 xxx@2.0.0」
|
||||
* 「装载案例模型(6 个计划项)」这类是引擎自己的机械动作,人看了不知道该拿它怎么办;
|
||||
* 工具调用的过程同理:**人要的是产出结果,不是调用细节**。
|
||||
* 所以骨架之外还有一道 `machinery()` 过滤(见下),两道一起才干净。
|
||||
*
|
||||
* `observation` 是**新进来的**:系统事件(一封信触发了这条流、邮件确认、
|
||||
* 人把来件标了已读)本来一条都不在日志里 —— 而那恰恰是人最想回看的
|
||||
* 「这单怎么起来的、谁在什么时候看过」。
|
||||
*
|
||||
* `action` **整类退出**(2026-07-31 第二轮,用户定)。它只有三个来源,全是机制:
|
||||
* 调工具、写草稿、技能结晶。「写入草稿:quote_intake.record 3 行(3 行有变化)」
|
||||
* 对人说不出任何事 —— 写了什么在工作成果与台账里看得见,「几行有变化」是引擎的账。
|
||||
* 从前只滤掉带 `tool` 的那一半,留着写草稿的另一半,理由是「它本身就是产出」;
|
||||
* 可产出是**台账里那几行**,不是「我写了一笔」这个动作。
|
||||
* 「为什么不行」不在这一类里:行动被拒、提交被拒、换画面失败都记在 `result`,照进。
|
||||
*/
|
||||
const SKELETON: ReadonlySet<EntryKind> = new Set<EntryKind>([
|
||||
'trigger',
|
||||
'observation',
|
||||
'engine-transition',
|
||||
'action',
|
||||
'result',
|
||||
'card-sent',
|
||||
'card-reply',
|
||||
'narration',
|
||||
'ledger-write',
|
||||
'action-intent',
|
||||
'release-verdict',
|
||||
@@ -36,32 +79,268 @@ const SKELETON: ReadonlySet<EntryKind> = new Set<EntryKind>([
|
||||
'control',
|
||||
]);
|
||||
|
||||
/**
|
||||
* 内容取原文 —— **不加前缀**。
|
||||
*
|
||||
* 从前每种各挂一个前缀(`发出:` `回传:` `写入:` `终态:` …),那是在用前缀
|
||||
* 兼职「这是什么动作」。现在动作有了自己的一格(`action`),前缀就成了重复:
|
||||
* 「[数据录入] 发出:这是位新客户…」——「发出」两个字白占地方。
|
||||
* 主语也不在这儿拼:名字要查库,域里查不了(纯函数零 IO)。
|
||||
*/
|
||||
const TEMPLATE: Readonly<Record<EntryKind, (e: MemoryEntry) => string>> = {
|
||||
observation: (e) => `观察:${e.title}`,
|
||||
thought: (e) => `思考:${e.title}`,
|
||||
action: (e) => `调用:${e.title}`,
|
||||
result: (e) => `返回:${e.title}`,
|
||||
'engine-transition': (e) => `迁移:${e.title}`,
|
||||
evaluation: (e) => `求值:${e.title}`,
|
||||
'card-sent': (e) => `发出:${e.title}`,
|
||||
'card-reply': (e) => `回传:${e.title}`,
|
||||
'ledger-write': (e) => `写入:${e.title}`,
|
||||
'action-intent': (e) => `动作意向:${e.title}`,
|
||||
'release-verdict': (e) => `放行裁决:${e.title}`,
|
||||
terminal: (e) => `终态:${e.title}`,
|
||||
'human-input': (e) => `留言:${e.title}`,
|
||||
control: (e) => `治理:${e.title}`,
|
||||
observation: (e) => e.title,
|
||||
thought: (e) => e.title,
|
||||
action: (e) => actionEntryOf(e).body,
|
||||
result: (e) => e.title,
|
||||
'engine-transition': (e) => plain(transitionOf(e.title).body),
|
||||
evaluation: (e) => e.title,
|
||||
'card-sent': (e) => e.title,
|
||||
'card-reply': (e) => e.title,
|
||||
'ledger-write': (e) => plain(e.title),
|
||||
'action-intent': (e) => e.title,
|
||||
'release-verdict': (e) => plain(e.title),
|
||||
terminal: (e) => plain(e.title),
|
||||
'human-input': (e) => e.title,
|
||||
narration: (e) => e.title,
|
||||
control: (e) => e.title,
|
||||
trigger: (e) => e.title,
|
||||
};
|
||||
|
||||
/**
|
||||
* 四字动作 —— **对话区说什么词,这里就是什么词**。
|
||||
*
|
||||
* 对齐的出处是 `chat-bubbles.tsx` 的三张表:AI 的类型章(`TAG_UI`)、
|
||||
* 系统气泡的章(`SYS_TAG`)、各卡卡头的类型名。同一件事在两屏用两个说法,
|
||||
* 人得在脑子里做一次翻译 —— 而这本来是同一件事。
|
||||
*/
|
||||
const CARD_ACTION: Readonly<Record<string, string>> = {
|
||||
elicitation: '请你补全',
|
||||
decision: '需要你定',
|
||||
entry: '数据录入',
|
||||
rating: '任务评价',
|
||||
execution: '派出工单',
|
||||
acceptance: '请你验收',
|
||||
};
|
||||
|
||||
/** 人回传:一律「员工 X X」句式 —— 点确认那一下就是**员工确认**(用户定) */
|
||||
const REPLY_ACTION: Readonly<Record<string, string>> = {
|
||||
entry: '员工确认',
|
||||
elicitation: '员工补全',
|
||||
decision: '员工决策',
|
||||
rating: '员工评价',
|
||||
execution: '员工办结',
|
||||
acceptance: '放行裁决',
|
||||
};
|
||||
|
||||
/** 叙述的三种签,与对话区 `TAG_UI` / `SAY_UI` 同词 */
|
||||
const NARRATION_ACTION: Readonly<Record<string, string>> = {
|
||||
conclusion: '阶段结论',
|
||||
guidance: '交代进展',
|
||||
criterion: '请你验收',
|
||||
};
|
||||
|
||||
/**
|
||||
* **日志里不许出现系统内的英文名**(2026-08-02 用户定)。
|
||||
*
|
||||
* 工作日志是给人看的。而引擎留痕时顺手把内部标识写了进去:
|
||||
* `manualStart:S3_confirm`、`终态:released`、`发射输出口:intake、to-alternative`——
|
||||
* 那是给排障的人看的机器话,摆在人的日志上等于没写。
|
||||
* 这里逐条换成人话;认不出的原样留着(宁可笼统,不可编)。
|
||||
*/
|
||||
const PLAIN: ReadonlyArray<{ readonly re: RegExp; readonly to: string }> = [
|
||||
/*
|
||||
* **计划项号对人是天书**:`manualStart:S3_confirm`、`开始办:T_release` ——
|
||||
* 那一格里唯一的信息是「引擎开始办某一步了」,而哪一步下一行的「完成一项」会说清。
|
||||
* 与其摆一个 id,不如只说这件事本身。
|
||||
*/
|
||||
// 老留痕里还有 `manualStart:S3_confirm` 这种(新的已经写名字了)—— 认不出名字就只说这件事
|
||||
{ re: /^manualStart[::]\s*[A-Za-z0-9_]+$/, to: '开始办这一步' },
|
||||
{ re: /^manualStart[::]\s*/, to: '开始办:' },
|
||||
// 去向那一条:口名与判据表达式都是给排障看的;人要知道的是「转到哪儿」
|
||||
{ re: /^去向已算出[::].*→\s*([a-z-]+).*$/, to: '去向已算出,随放行转入下游' },
|
||||
// 终态有两种写法:带「终态:」前缀的,与裸状态名 —— 两种都得认,漏一种就露一个英文
|
||||
{ re: /^(终态[::]\s*)?released$/, to: '这一单已放行完成' },
|
||||
{ re: /^(终态[::]\s*)?blocked$/, to: '这一单卡住了,没能落地' },
|
||||
{ re: /^(终态[::]\s*)?rejected$/, to: '这一单被退回了' },
|
||||
{ re: /^(终态[::]\s*)?exhausted$/, to: '这一单预算用尽,停下了' },
|
||||
{ re: /^裁决[::]\s*pass$/, to: '验收通过' },
|
||||
{ re: /^裁决[::]\s*redo$/, to: '验收退回重做' },
|
||||
{ re: /^放行写合并\s*/, to: '成果落账,共 ' },
|
||||
{ re: /^人工修订[::]\s*/, to: '你改的那一笔已落账:' },
|
||||
];
|
||||
|
||||
/** 把一行里的系统名换成人话 —— 认不出就原样留着 */
|
||||
function plain(title: string): string {
|
||||
for (const p of PLAIN) if (p.re.test(title)) return title.replace(p.re, p.to);
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引擎流转的措辞由引擎自己拼(`起单:xxx` / `完成:xxx` …)。
|
||||
* 认出前缀是为了两件事:动作那一格说得具体,以及**把前缀从内容里摘掉** ——
|
||||
* 章上已经写着「任务起单」,内容再来一遍「起单:」就是同一句话说两遍。
|
||||
* 认不出的一律「状态推进」,内容原样留着:宁可笼统,不可编。
|
||||
*/
|
||||
const TRANSITION: ReadonlyArray<{ readonly prefix: string; readonly action: string }> = [
|
||||
{ prefix: '起单:', action: '任务起单' },
|
||||
{ prefix: '完成:', action: '完成一项' },
|
||||
{ prefix: '自动收步:', action: '自动收步' },
|
||||
{ prefix: '立监听:', action: '立起监听' },
|
||||
{ prefix: '驳回重入:', action: '驳回重入' },
|
||||
];
|
||||
|
||||
function transitionOf(title: string): { action: string; body: string } {
|
||||
for (const t of TRANSITION) {
|
||||
if (title.startsWith(t.prefix)) return { action: t.action, body: title.slice(t.prefix.length) };
|
||||
}
|
||||
return { action: '状态推进', body: title };
|
||||
}
|
||||
|
||||
/** action 条目有两种:写草稿与调工具 —— 靠 detail 上的 `ledger` 记号分,不靠措辞 */
|
||||
function actionEntryOf(e: MemoryEntry): { action: string; body: string } {
|
||||
if (e.detail?.['ledger'] === undefined) return { action: '调用工具', body: e.title };
|
||||
const p = '写草稿:';
|
||||
return { action: '写入草稿', body: e.title.startsWith(p) ? e.title.slice(p.length) : e.title };
|
||||
}
|
||||
|
||||
function actionOf(e: MemoryEntry): string {
|
||||
const d = e.detail ?? {};
|
||||
const cardKey = String(d['k'] ?? d['mode'] ?? '');
|
||||
switch (e.kind) {
|
||||
case 'trigger':
|
||||
return '触发事件';
|
||||
case 'observation':
|
||||
return '系统事件';
|
||||
case 'engine-transition':
|
||||
return transitionOf(e.title).action;
|
||||
case 'action':
|
||||
return actionEntryOf(e).action;
|
||||
case 'narration':
|
||||
return NARRATION_ACTION[String(d['tag'] ?? '')] ?? '阶段结论';
|
||||
case 'card-sent':
|
||||
return CARD_ACTION[cardKey] ?? '发出卡片';
|
||||
case 'card-reply':
|
||||
return REPLY_ACTION[cardKey] ?? '员工回传';
|
||||
// 人在界面上的动作:点确认 = 员工确认,看来件 = 员工阅件
|
||||
case 'human-input':
|
||||
return d['act'] === 'read-comm' ? '员工阅件' : '员工确认';
|
||||
case 'ledger-write':
|
||||
return '写入台账';
|
||||
case 'result':
|
||||
return '工具产出';
|
||||
case 'action-intent':
|
||||
return '动作意向';
|
||||
case 'release-verdict':
|
||||
return '放行裁决';
|
||||
case 'terminal':
|
||||
return '任务收尾';
|
||||
case 'control':
|
||||
return '治理干预';
|
||||
case 'thought':
|
||||
return '模型思考';
|
||||
case 'evaluation':
|
||||
return '判据求值';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主语 —— 日志每一行都要有主语,不然是一串没头脑的流水账。
|
||||
*
|
||||
* 三种:**任务助手**(AI 干的)、**系统**(基座干的:起单、事件到达、收尾)、
|
||||
* 以及**具体某个人**。人名要查库,域里查不了,所以这里只交出 `actorId`,
|
||||
* 由上层换成名字;换不到就退回「员工」,不编名字。
|
||||
*/
|
||||
function actorOf(e: MemoryEntry): { actor: string; actorId: string | undefined; who: 'owner' | 'agent' | 'system' } {
|
||||
const by = e.detail?.['by'];
|
||||
const id = typeof by === 'string' && by !== '' ? by : undefined;
|
||||
switch (e.kind) {
|
||||
case 'trigger':
|
||||
case 'observation':
|
||||
case 'terminal':
|
||||
return { actor: '系统', actorId: undefined, who: 'system' };
|
||||
/*
|
||||
* **AI 办完的事要记在 AI 名下**(2026-08-02 用户:日志「要能看出人和 AI 分别干了啥」)。
|
||||
*
|
||||
* 引擎流转从前一律署「系统」,可这一类里混着两种事:
|
||||
* · 「完成一项」「开始办」——**AI 报的**(它 finish / start,引擎照办);
|
||||
* · 「起单」「自动收步」「驳回重入」—— 引擎自己做主的。
|
||||
* 全署系统,人就看不出这一单里 AI 到底干了什么、自己干了什么 ——
|
||||
* 而那正是他翻这一屏想知道的事。留痕上带 `by: 'agent'` 的从这里认。
|
||||
*/
|
||||
case 'engine-transition':
|
||||
return e.detail?.['by'] === 'agent' || /^(完成|开始办)/.test(e.title)
|
||||
? { actor: '任务助手', actorId: undefined, who: 'agent' }
|
||||
: { actor: '系统', actorId: undefined, who: 'system' };
|
||||
case 'card-reply':
|
||||
case 'human-input':
|
||||
case 'release-verdict':
|
||||
case 'control':
|
||||
return { actor: '', actorId: id, who: 'owner' };
|
||||
/*
|
||||
* 台账写入有两种,主语不同:**放行合并**是基座在人签完字之后做的一次落账
|
||||
* (记在 AI 名下会让人以为是它自作主张写了正式表);**人工修订**是人自己改的
|
||||
* (留痕带上了他的 id)。认 id,不认措辞。
|
||||
*/
|
||||
case 'ledger-write':
|
||||
return id === undefined ? { actor: '系统', actorId: undefined, who: 'system' } : { actor: '', actorId: id, who: 'owner' };
|
||||
default:
|
||||
return { actor: '任务助手', actorId: undefined, who: 'agent' };
|
||||
}
|
||||
}
|
||||
|
||||
/** 结构化日志:零 LLM,可重算,每条自带谁 · 何时 · 依据 · 留痕指针 */
|
||||
/**
|
||||
* **机械动作** —— 进得了骨架,但人看了没有用处的那些。
|
||||
*
|
||||
* 判据是「这一条能不能让人做出判断」:
|
||||
* · 装配技能 / 装载案例模型 —— 引擎每单必做,一模一样,说不出这一单的任何事;
|
||||
* · `internal` 标的观察(已在进行中 / 同拍余下作废)—— 引擎给自己看的踪迹;
|
||||
* · 人事件的到达 —— 一串卡号,而这件事上一行已经用人话说过了。
|
||||
*
|
||||
* (动作那一整类 `action` 不在这儿滤 —— 它压根不进骨架,见上面 SKELETON。)
|
||||
*
|
||||
* 不用「白名单标题」那一路:标题措辞随时会改,改一次过滤就失灵而没人发现。
|
||||
* 这里认的是 kind + detail 上的结构化标记,措辞怎么变都不影响。
|
||||
*/
|
||||
/**
|
||||
* **同一件事的复述** —— 人签完字那一刻,基座与引擎各留了一条痕,说的是同一件事。
|
||||
*
|
||||
* 实测一单的日志尾巴是这样的(用户 2026-08-02 提的「日志是给人看的」):
|
||||
* · 成果落账,共 10 行(试跑:另有 1 行只留在活动缓存) ← 放行写合并,**带得住实情**
|
||||
* · 成果已落账,交给下游 ← 发射输出口,同一件事
|
||||
* · 成果落账,共 10 行,已交下游 ← 引擎流转,还是同一件事
|
||||
* · 这一单已放行完成 ← 终态
|
||||
* 四行里有三行在说「落账了」。留痕那一侧三条都该在(排障要分得清是谁做的),
|
||||
* 但**日志这一侧只该说一遍**:留合并那一条(它是唯一说得出「几行没进正式表」的),
|
||||
* 另外两条的信息它全包含。
|
||||
*/
|
||||
const RESTATED: ReadonlyArray<RegExp> = [/^落地收敛[::]\s*合并/, /^落地收敛,发射输出口/];
|
||||
|
||||
function machinery(e: MemoryEntry): boolean {
|
||||
if (e.detail?.['internal'] === true) return true;
|
||||
if (RESTATED.some((re) => re.test(e.title))) return true;
|
||||
if (e.kind === 'thought') return true;
|
||||
if (e.kind === 'engine-transition' && e.title.startsWith('装载案例模型')) return true;
|
||||
/*
|
||||
* **人事件的到达**:对人它只是「事件到达(human):d455688a-…」一串卡号,
|
||||
* 而这件事上一行已经用人话说过了(「员工确认:写入 customer.profile」)。
|
||||
* 同一次点击记两行,后一行还是 UUID —— 对话区早就滤掉了它,日志该同一口径。
|
||||
* 数据/时间事件的到达留着:那些是外面来的,人确实要知道什么时候到的。
|
||||
*/
|
||||
if (e.kind === 'observation' && e.detail?.['kind'] === 'human') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function structuredLog(entries: readonly MemoryEntry[]): LogLine[] {
|
||||
return entries
|
||||
.filter((e) => SKELETON.has(e.kind))
|
||||
.filter((e) => SKELETON.has(e.kind) && !machinery(e))
|
||||
.map((e) => ({
|
||||
id: e.id,
|
||||
at: e.at,
|
||||
side: e.side,
|
||||
sideLabel: ATTRIBUTION_SIDE_LABEL[e.side],
|
||||
action: actionOf(e),
|
||||
...actorOf(e),
|
||||
title: TEMPLATE[e.kind](e),
|
||||
summary: typeof e.detail?.['summary'] === 'string' ? e.detail['summary'] : undefined,
|
||||
eventRef: e.eventRef,
|
||||
@@ -76,6 +355,7 @@ export function splitBySide(lines: readonly LogLine[]): Record<AttributionSide,
|
||||
human: [],
|
||||
'human-verdict': [],
|
||||
'world-wait': [],
|
||||
trigger: [],
|
||||
};
|
||||
for (const line of lines) out[line.side].push(line);
|
||||
return out;
|
||||
@@ -84,7 +364,7 @@ export function splitBySide(lines: readonly LogLine[]): Record<AttributionSide,
|
||||
/* ── 度量八指标(OS 8.3):只读投影,零埋点,统计即查询 ─────────────── */
|
||||
|
||||
export type Metrics = {
|
||||
/** 自主率 = 机器产出 ÷(机器产出 + 人产出);裁决与等待不进分母 */
|
||||
/** 自主率 = 机器产出 ÷(机器产出 + 员工动作);裁决与等待不进分母 */
|
||||
readonly autonomy: number;
|
||||
/** 纠正率 = 被纠正批次 ÷ 提交批次 */
|
||||
readonly correction: number;
|
||||
|
||||
Reference in New Issue
Block a user