minute(纪要):工作台五要素里唯一的主动动作。 纪要本身不产生责任,唯一通往责任的路是决议经登记触发起单 —— 登记人即来源。 正文可续、决议只增;同一决议不重复起单(起单一刻已有一条带来源的流在跑)。 report(汇报):日/周/月/年对任务日志的聚合投影,零手工汇报。 未提交的草案每次取回都重算 —— 汇报是投影不是账本,提交那刻才固定为事实快照。 度量八指标落在这里(§4 注:度量是只读投影,不设独立模块): 自主率 / 纠正率 / 有效自主率 / 打扰率 / 断点率 / DMN 覆盖率 / 单位成本 / 终态分布。 口径全部挂归侧标签与事件流,零埋点;单位成本的分母只认放行通过。 周窗口以周一为首日,按 UTC 切分(单组织单时区)。 FactSource 只读 memory 与 task,一行都不另记 —— 五要素同源。 gates: tsc / eslint / depcruise(305 模块)/ api-report / 377 测试 全绿 日报已在真实系统上由 A1 任务日志聚合出来,含治理干预两行 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
254 lines
8.1 KiB
TypeScript
254 lines
8.1 KiB
TypeScript
/**
|
|
* 工作台路由契约(编码规范 §8.1 / §9.1)。
|
|
* 工作台五要素:任务 / 审批 / 消息 / 纪要 / 汇报 —— 同一份过程事实的五种投影。
|
|
*/
|
|
import { z } from 'zod';
|
|
import type { RouteContract } from './http.ts';
|
|
import { sessionSchema } from '../org.ts';
|
|
import { chatMessageSchema, deliverableSchema, stageSchema, viewBlockSchema, workOrderSchema } from '../card.ts';
|
|
import { attributionSideSchema, taskRuntimeStateSchema, terminalStateSchema, workflowStateSchema } from '../terminal.ts';
|
|
import { staffIdSchema, taskIdSchema } from '../id.ts';
|
|
|
|
export const sessionRoute = {
|
|
method: 'GET',
|
|
path: '/api/session',
|
|
output: sessionSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
export const healthRoute = {
|
|
method: 'GET',
|
|
path: '/api/health',
|
|
output: z.object({
|
|
ok: z.boolean(),
|
|
version: z.string(),
|
|
db: z.enum(['up', 'down']),
|
|
}),
|
|
} as const satisfies RouteContract;
|
|
|
|
/* ── 任务要素:四区(任务列表 / 标题区 / 工作区 / 对话区) ─────────────── */
|
|
|
|
/** 任务卡 —— 清单里一条 = 一个活动的一次运行,不是一整条工作流 */
|
|
export const taskCardSchema = z.object({
|
|
id: taskIdSchema,
|
|
activity: z.string(),
|
|
title: z.string(),
|
|
runtime: taskRuntimeStateSchema,
|
|
terminal: terminalStateSchema.optional(),
|
|
/** 它在工作流上的序位 */
|
|
position: z.number().int().min(1),
|
|
total: z.number().int().min(1),
|
|
workflowSubject: z.string(),
|
|
workflowState: workflowStateSchema,
|
|
startedAt: z.string(),
|
|
});
|
|
export type TaskCard = z.infer<typeof taskCardSchema>;
|
|
|
|
export const listTasksRoute = {
|
|
method: 'GET',
|
|
path: '/api/desk/tasks',
|
|
output: z.object({ tasks: z.array(taskCardSchema) }),
|
|
} as const satisfies RouteContract;
|
|
|
|
/** 任务详情 —— 工作区与对话区的服务端投影(前端零过程状态,刷新即重建) */
|
|
export const taskDetailSchema = z.object({
|
|
card: taskCardSchema,
|
|
goal: z.string(),
|
|
budget: z.object({
|
|
steps: z.string(),
|
|
tokens: z.string(),
|
|
activeMinutes: z.string(),
|
|
humanCalls: z.string(),
|
|
}),
|
|
stages: z.array(stageSchema),
|
|
deliverables: z.array(deliverableSchema),
|
|
workOrders: z.array(workOrderSchema),
|
|
chat: z.array(chatMessageSchema),
|
|
view: z.array(viewBlockSchema),
|
|
log: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
at: z.string(),
|
|
side: attributionSideSchema,
|
|
sideLabel: z.string(),
|
|
title: z.string(),
|
|
summary: z.string().optional(),
|
|
}),
|
|
),
|
|
});
|
|
export type TaskDetail = z.infer<typeof taskDetailSchema>;
|
|
|
|
export const taskDetailRoute = {
|
|
method: 'GET',
|
|
path: '/api/desk/tasks/:taskId',
|
|
params: z.object({ taskId: taskIdSchema }),
|
|
output: taskDetailSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
/** 回传工单 —— 与后端回传校验同 schema(§9.2) */
|
|
export const replyWorkOrderRoute = {
|
|
method: 'POST',
|
|
path: '/api/desk/work-orders/reply',
|
|
body: z.object({ reply: z.record(z.string(), z.unknown()) }),
|
|
output: z.object({ ok: z.boolean(), producesCompletion: z.boolean() }),
|
|
} as const satisfies RouteContract;
|
|
|
|
/** 负责人的自由输入 —— 人事件的第二形态 */
|
|
export const sayRoute = {
|
|
method: 'POST',
|
|
path: '/api/desk/tasks/:taskId/say',
|
|
params: z.object({ taskId: taskIdSchema }),
|
|
body: z.object({ text: z.string().min(1) }),
|
|
output: chatMessageSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
/** 消息要素 */
|
|
export const listMessagesRoute = {
|
|
method: 'GET',
|
|
path: '/api/desk/messages',
|
|
output: z.object({
|
|
messages: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
kind: z.string(),
|
|
title: z.string(),
|
|
body: z.string(),
|
|
at: z.string(),
|
|
read: z.boolean(),
|
|
}),
|
|
),
|
|
}),
|
|
} as const satisfies RouteContract;
|
|
|
|
/* ── 纪要:工作台五要素里唯一的主动动作 ─────────────────────────────── */
|
|
|
|
export const minuteResolutionSchema = z.object({
|
|
id: z.string(),
|
|
text: z.string(),
|
|
ownerId: staffIdSchema.optional(),
|
|
state: z.enum(['open', 'registered']),
|
|
workflowId: z.string().optional(),
|
|
registeredById: staffIdSchema.optional(),
|
|
registeredAt: z.string().optional(),
|
|
});
|
|
|
|
export const minuteSchema = z.object({
|
|
id: z.string(),
|
|
title: z.string(),
|
|
body: z.string(),
|
|
authorId: staffIdSchema,
|
|
authorName: z.string(),
|
|
participants: z.array(z.string()),
|
|
occurredOn: z.string(),
|
|
resolutions: z.array(minuteResolutionSchema),
|
|
createdAt: z.string(),
|
|
updatedAt: z.string(),
|
|
});
|
|
export type MinuteView = z.infer<typeof minuteSchema>;
|
|
|
|
export const listMinutesRoute = {
|
|
method: 'GET',
|
|
path: '/api/minutes',
|
|
output: z.object({ minutes: z.array(minuteSchema) }),
|
|
} as const satisfies RouteContract;
|
|
|
|
export const createMinuteRoute = {
|
|
method: 'POST',
|
|
path: '/api/minutes',
|
|
body: z.object({
|
|
title: z.string().min(1),
|
|
body: z.string(),
|
|
occurredOn: z.string().min(1),
|
|
participants: z.array(staffIdSchema).default([]),
|
|
resolutions: z.array(z.object({ text: z.string().min(1), ownerId: staffIdSchema.optional() })).default([]),
|
|
}),
|
|
output: minuteSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
export const appendResolutionRoute = {
|
|
method: 'POST',
|
|
path: '/api/minutes/:minuteId/resolutions',
|
|
params: z.object({ minuteId: z.string() }),
|
|
body: z.object({ text: z.string().min(1), ownerId: staffIdSchema.optional() }),
|
|
output: minuteSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
/** 决议 → 登记起单:登记人即来源 */
|
|
export const registerResolutionRoute = {
|
|
method: 'POST',
|
|
path: '/api/minutes/resolutions/:resolutionId/register',
|
|
params: z.object({ resolutionId: z.string() }),
|
|
body: z.object({ processPackageId: z.string().min(1), data: z.record(z.string(), z.unknown()) }),
|
|
output: z.object({ workflowId: z.string(), resolutionId: z.string() }),
|
|
} as const satisfies RouteContract;
|
|
|
|
/* ── 汇报:系统代劳的自动产出 ───────────────────────────────────────── */
|
|
|
|
export const reportPeriodSchema = z.enum(['day', 'week', 'month', 'year']);
|
|
export type ReportPeriodName = z.infer<typeof reportPeriodSchema>;
|
|
|
|
export const REPORT_PERIOD_LABEL: Readonly<Record<ReportPeriodName, string>> = {
|
|
day: '日报',
|
|
week: '周报',
|
|
month: '月报',
|
|
year: '年报',
|
|
};
|
|
|
|
export const reportMetricsSchema = z.object({
|
|
autonomy: z.number(),
|
|
correction: z.number(),
|
|
effectiveAutonomy: z.number(),
|
|
interruption: z.number(),
|
|
breakpoint: z.number(),
|
|
dmnCoverage: z.number(),
|
|
unitCost: z.object({ tokens: z.number(), minutes: z.number(), humanCalls: z.number() }),
|
|
terminalMix: z.record(z.string(), z.number()),
|
|
});
|
|
|
|
export const reportSchema = z.object({
|
|
id: z.string(),
|
|
period: reportPeriodSchema,
|
|
title: z.string(),
|
|
window: z.object({ from: z.string(), to: z.string() }),
|
|
sections: z.array(
|
|
z.object({
|
|
taskId: z.string(),
|
|
title: z.string(),
|
|
activityId: z.string(),
|
|
terminalLabel: z.string().optional(),
|
|
lines: z.array(z.string()),
|
|
}),
|
|
),
|
|
metrics: reportMetricsSchema,
|
|
counts: z.object({ touched: z.number().int(), released: z.number().int(), logLines: z.number().int() }),
|
|
note: z.string().optional(),
|
|
submittedAt: z.string().optional(),
|
|
generatedAt: z.string(),
|
|
});
|
|
export type ReportView = z.infer<typeof reportSchema>;
|
|
|
|
/** 未提交的草案每次取回都重算 —— 汇报是投影,不是账本 */
|
|
export const draftReportRoute = {
|
|
method: 'GET',
|
|
path: '/api/reports/:period',
|
|
params: z.object({ period: reportPeriodSchema }),
|
|
output: reportSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
export const listReportsRoute = {
|
|
method: 'GET',
|
|
path: '/api/reports',
|
|
output: z.object({ reports: z.array(reportSchema) }),
|
|
} as const satisfies RouteContract;
|
|
|
|
/** 确认即提交,提交后不再改 */
|
|
export const submitReportRoute = {
|
|
method: 'POST',
|
|
path: '/api/reports/:reportId/submit',
|
|
params: z.object({ reportId: z.string() }),
|
|
body: z.object({ note: z.string().optional() }),
|
|
output: reportSchema,
|
|
} as const satisfies RouteContract;
|
|
|
|
export type ListMinutesOutput = z.infer<typeof listMinutesRoute.output>;
|
|
export type ListReportsOutput = z.infer<typeof listReportsRoute.output>;
|