/** * 工作台路由契约(编码规范 §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; 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; 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; 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; export const REPORT_PERIOD_LABEL: Readonly> = { 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; /** 未提交的草案每次取回都重算 —— 汇报是投影,不是账本 */ 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; export type ListReportsOutput = z.infer;