Archived
M6(4/n): 工作台五要素齐活 —— 审批 / 消息 / 纪要 / 汇报
四个 desk-* feature 自 workbench 原型 ModuleWorkspace.tsx 平移(jc-* 类名原样),
共用件落 shared/desk/module-kit;mock 数据换成服务端投影:
· desk-approval:审批点卡到审批人工位;依据快照、驳回走向(声明式)、驳回必附意见
· desk-message:通知 + 跨任务征答**就地作答**;卡面表单由 card.ts schema 驱动,
与后端回传校验同 schema;跨人卡不占任务清单,不开放任务对话流
· desk-minute:纪要维护 + 决议登记起单;正文可续、决议只增
· desk-report:四周期草案 + 度量八指标 + 确认即提交
左轨角标常驻(待批 / 未读),责任面要一眼看见什么在等我。
修一个真 bug:process.start 从不校验流程包的 callInterface,
先插工作流再由 task.start 撞活动契约 —— 失败就留下一条永远推不动的在途单。
改为**先校验后建单**(CallInterfaceViolationError),一条工作流都不留;
路由回 400 并带上缺哪个字段。已清掉这次触发出的两条孤儿单。
判据 ⑩ 纪要一环跑通:决议 → 登记触发 → 工作流 → A1 落到 BD 冯兵,
登记人即来源已记在单上;同一决议重复登记被 412 挡下。
gates: tsc / eslint / depcruise(316 模块)/ api-report / 380 测试 全绿
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -7,7 +7,9 @@ import type { Hono } from 'hono';
|
||||
import type { ApiError, ChatMessage, TaskCard, ViewBlock } from '@contracts';
|
||||
import {
|
||||
ATTRIBUTION_SIDE_LABEL,
|
||||
answerMessageRoute,
|
||||
listMessagesRoute,
|
||||
markMessageReadRoute,
|
||||
listTasksRoute,
|
||||
replyWorkOrderRoute,
|
||||
sayRoute,
|
||||
@@ -185,7 +187,55 @@ export function registerDeskRoutes(app: Hono<AppEnv>, container: Container): voi
|
||||
body: m.body,
|
||||
at: m.at,
|
||||
read: m.readAt !== undefined,
|
||||
// 跨任务征答卡随消息一并出:就地作答,不必跳进别人的任务
|
||||
...(m.card === undefined
|
||||
? {}
|
||||
: {
|
||||
card: {
|
||||
cardId: m.card.cardId,
|
||||
sourceTaskId: m.card.sourceTaskId,
|
||||
fields: (m.card.payload['fields'] as unknown[] | undefined) ?? [],
|
||||
ask: String(m.card.payload['ask'] ?? ''),
|
||||
answered: m.card.answeredAt !== undefined,
|
||||
...(m.card.rejectedReason === undefined ? {} : { rejectedReason: m.card.rejectedReason }),
|
||||
...(m.card.supersededBy === undefined ? {} : { supersededBy: m.card.supersededBy }),
|
||||
},
|
||||
}),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
app.post(markMessageReadRoute.path, async (c) => {
|
||||
const staff = await me(c.get('account'));
|
||||
if (staff === undefined) {
|
||||
const body: ApiError = { code: 'unauthenticated', message: '会话无效' };
|
||||
return c.json(body, 401);
|
||||
}
|
||||
await container.message.markRead(c.req.param('messageId'));
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* 跨任务征答的**就地作答**(OS 2.1)。
|
||||
* 「作答权限只及卡面与决策包,**不开放任务对话流**」——所以这里只收卡面回传,
|
||||
* 由 message 经统一入口写回来源任务;调用与回传全程在任务侧留痕。
|
||||
*/
|
||||
app.post(answerMessageRoute.path, async (c) => {
|
||||
const staff = await me(c.get('account'));
|
||||
if (staff === undefined) {
|
||||
const body: ApiError = { code: 'unauthenticated', message: '会话无效' };
|
||||
return c.json(body, 401);
|
||||
}
|
||||
const parsed = answerMessageRoute.body.safeParse(await c.req.json());
|
||||
if (!parsed.success) {
|
||||
const body: ApiError = { code: 'validation-failed', message: '回传不合协议' };
|
||||
return c.json(body, 400);
|
||||
}
|
||||
const answered = await container.message.answer(c.req.param('messageId'), staff.id, parsed.data.answer);
|
||||
// 跨人卡一答,来源任务就可能续跑 —— 让那边的浏览器重取投影
|
||||
if (answered.card !== undefined) {
|
||||
container.broadcaster.publish({ kind: 'task', id: answered.card.sourceTaskId, reason: 'elicitation-answer' });
|
||||
}
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user