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:
@@ -53,9 +53,21 @@ export const taskCardSchema = z.object({
|
||||
title: z.string(),
|
||||
runtime: taskRuntimeStateSchema,
|
||||
terminal: terminalStateSchema.optional(),
|
||||
/** 它在工作流上的序位 */
|
||||
/** 它在工作流上的序位 —— **与下面的阶段进度不是一回事**:这是「第几个活动」 */
|
||||
position: z.number().int().min(1),
|
||||
total: z.number().int().min(1),
|
||||
/**
|
||||
* 活动**自己**的阶段进度 —— 进度条读的是它。
|
||||
*
|
||||
* 从前进度条读的是上面那对序位:A1 在七节点流里是 1/7,一开跑就画成 14%,
|
||||
* 跑到收口还是 14%(节点没换,序位当然不动);单活动试跑更荒唐 —— 恒 1/1,
|
||||
* 起单那一刻就满格。序位答的是「这单在整条流的哪一环」,不是「这单办到哪了」。
|
||||
*
|
||||
* 活动没声明阶段就没有这个键:界面照此不画进度条,不编一个出来。
|
||||
*/
|
||||
phase: z
|
||||
.object({ index: z.number().int().min(1), total: z.number().int().min(1), name: z.string() })
|
||||
.optional(),
|
||||
workflowSubject: z.string(),
|
||||
workflowState: workflowStateSchema,
|
||||
startedAt: z.string(),
|
||||
@@ -111,6 +123,39 @@ export const taskDetailSchema = z.object({
|
||||
file: fileDocSchema,
|
||||
}),
|
||||
),
|
||||
/**
|
||||
* 沟通记录 —— 跟客户/供应商往来的**原件**,按单归档。
|
||||
* 进来的那一封是开案的那份料(活动的入参就是它);出去的是放行后系统真发的件。
|
||||
*/
|
||||
comms: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
at: z.string(),
|
||||
direction: z.enum(['in', 'out']),
|
||||
channel: z.string(),
|
||||
party: z.string(),
|
||||
subject: z.string(),
|
||||
body: z.string(),
|
||||
attachments: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
size: z.number().int().optional(),
|
||||
ref: z.string().optional(),
|
||||
/** 基座读出来的说明:读出多少字 / 为什么读不出 —— 界面照抄,不改写 */
|
||||
note: z.string().optional(),
|
||||
readable: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
note: z.string(),
|
||||
/**
|
||||
* **未读**(2026-07-31)——「看没看过」由人按按钮说了算,不由打开页签自动清。
|
||||
* 自动清等于替人宣布「你看过了」,而他可能只是路过那个页签。
|
||||
*/
|
||||
unread: z.boolean().default(false),
|
||||
}),
|
||||
)
|
||||
.default([]),
|
||||
workOrders: z.array(workOrderSchema),
|
||||
chat: z.array(chatMessageSchema),
|
||||
view: z.array(viewBlockSchema),
|
||||
@@ -122,6 +167,17 @@ export const taskDetailSchema = z.object({
|
||||
at: z.string(),
|
||||
side: attributionSideSchema,
|
||||
sideLabel: z.string(),
|
||||
/**
|
||||
* 这一条**是谁干的** —— 日志只认三档:`owner` 负责人 · `agent` 智能体 · `system` 系统。
|
||||
*
|
||||
* 与 `side`(五种归侧)分工不同:那一维是**度量**用的(自主率要把「等外面」
|
||||
* 与「机器干的」分开);这一维是**呈现**用的 —— 人扫这一屏只问
|
||||
* 「这一步是我办的、AI 办的,还是流程自己走到的」,三档各一个颜色。
|
||||
*/
|
||||
who: z.enum(['owner', 'agent', 'system']),
|
||||
/** 类型章:**四字短语**,与对话区同词(触发事件 / 阶段结论 / 数据录入 / 员工确认 …) */
|
||||
action: z.string(),
|
||||
/** 内容,**带主语**:`任务助手:…` / `柴浩:…` —— 日志不写没头脑的流水账 */
|
||||
title: z.string(),
|
||||
summary: z.string().optional(),
|
||||
}),
|
||||
@@ -155,6 +211,74 @@ export const replyChatCardRoute = {
|
||||
output: z.object({ ok: z.boolean() }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/**
|
||||
* 标记沟通记录已读 / 未读 —— **气泡计数跟着这一下走**。
|
||||
* 不做「打开即已读」:那是替人宣布他看过了。已读还能改回未读(看了一半想留个记号)。
|
||||
*/
|
||||
export const markCommReadRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/desk/comms/:commId/read',
|
||||
params: z.object({ commId: z.string() }),
|
||||
body: z.object({ read: z.boolean() }),
|
||||
output: z.object({ ok: z.boolean(), unread: z.boolean() }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/**
|
||||
* **就地改完按下「保存」** —— 把件上那张表写回**本单草稿层**,工单不办结。
|
||||
*
|
||||
* 为什么非要立刻落库、而不是攒到提交那一下:件是**服务端投影**。
|
||||
* 人改了几格,只读那份跟着变了(那是他自己敲的),而由数据算出来的东西
|
||||
* ——类徽 A/B/C、分布条、结论横幅那条去向——全是渲染那一刻的旧值。
|
||||
* 于是屏上一半新一半旧:**人把三条料全改成 A,底下的去向还写着「去选型」**
|
||||
* (用户实测)。那不是没更新,那是界面在撒谎。
|
||||
*
|
||||
* 落库之后前端重取任务投影,服务端拿新数据重渲染整份件 —— 算出来的那些跟着一起变,
|
||||
* 去向也由同一次谓词求值重算。**放行门的位置没变**:写的仍是草稿层,
|
||||
* 合并进正式版依旧只发生在人签字那一刻。
|
||||
*
|
||||
* ── 按**地址**收,不按「当下那张卡的第几块」收(2026-08-02 用户实测逼出来)──────
|
||||
*
|
||||
* 载荷从前是 `{workOrderId, values:{'table:0': …}}`:列名由**那张工单的 `blocks[0]`**
|
||||
* 给出,于是「这份件能不能改」被绑在了「当下开着的那张卡长什么样」上。
|
||||
* 实测的下场:一单在放行门连拒五拍熔断,引擎发的是一张**没有 blocks 的熔断卡**;
|
||||
* 而屏上摆着的正是受理记录那份件,闸也在、编辑也进得去 —— 人把那条料的年用量补上、
|
||||
* 按了保存,前端找不到匹配的块,**一格都没往下送**。他要补的恰恰就是把这一单
|
||||
* 卡在那儿的那个必填格(判据 `quote_intake_record_missing = 0`),
|
||||
* 而屏上什么反应都没有 —— 界面提供了一个落不了地的编辑口。
|
||||
*
|
||||
* 现在收的是件自己说的话:每张可改的表挂着 `data-edit="域.对象"`,格子挂着
|
||||
* `data-col="台账列名"` —— 地址与列名**本来就在件里**,不必再绕一趟工单。
|
||||
* 授权面改由**活动清单的台账声明**把关(不在声明里的地址一概不写),
|
||||
* 比「当下那张卡碰巧带了哪一块」既严格又稳定。
|
||||
*
|
||||
* **改、增、删是同一件事的三种样子**(2026-08-02 用户:「编辑列表应该还能增删行」):
|
||||
* 人手改本来就是**兜 AI 的错**的那一手 —— 而错不只有「这一格填错了」,
|
||||
* 还有「这一行根本不该在」(客户点名本轮跳过的料被写了进来)与「漏了一行」。
|
||||
* 只给改格子,前两种就没有出口:那一单会一路走到放行门被判据拦住,
|
||||
* 而人明明一眼就看出问题在哪。
|
||||
*/
|
||||
export const saveTableRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/desk/tasks/:taskId/table',
|
||||
params: z.object({ taskId: taskIdSchema }),
|
||||
body: z.object({
|
||||
/** `{ '域.对象': [ { 台账列名: 值 } ] }` —— 主键列用来认行,不参与改行;台账里没有的行就是**新增** */
|
||||
tables: z.record(z.string(), z.array(z.record(z.string(), z.string()))),
|
||||
/**
|
||||
* **人在件上删掉的那几行** —— 摆主键,一行一条。
|
||||
*
|
||||
* 为什么删要单独说,而不是「`tables` 里没有的就当删了」:那种口径把**一次不完整的
|
||||
* 回传**变成一次删表 —— 件上摆的是不是整张表,服务端看不见(模版绑哪条路径是活动的事)。
|
||||
* 说出来的删才删:回传缺了什么一律当「这次没提到」,不当「要撤掉」。
|
||||
*
|
||||
* 撤的只是**本单草稿层**那一笔(`draftDrop`),正式版一个字都动不了、不抛事件 ——
|
||||
* 与 Agent 的 `drop` 同一条路子。放行门的位置没变。
|
||||
*/
|
||||
drops: z.record(z.string(), z.array(z.record(z.string(), z.string()))).optional(),
|
||||
}),
|
||||
output: z.object({ ok: z.boolean(), rows: z.number().int().min(0) }),
|
||||
} as const satisfies RouteContract;
|
||||
|
||||
/** 负责人的自由输入 —— 人事件的第二形态 */
|
||||
export const sayRoute = {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user