Archived
M3(2/5): human-tool 与 release —— 卡协议与放行门
human-tool(L3): - 三规矩机检:调用必附上下文(裸调用拒发)/ 回传即留痕 / 完成只出自放行通过 - 工单两模式 + 对话卡族的形态校验与回传校验;驳回必附意见;补全卡「没法填」旁路; 决策卡自定义收尾项必附理由;已答不得再答(只增不改) - 到达面两路(负责人 → 任务对话流;他人 → 系统消息);驳回重答 = 新卡实例、原卡闭合 - 调人预算由基座在环外强制;落地失败卡 = 执行工单的 preset release(L4): - 落地预检五项(入参 schema / 工具可用与授权 / 连接可达 / 目标预校验 / 数据冲突) - 预检全过才发验收工单,卡面即已验证的动作清单;不过即打回环内,不发卡 - 放行写合并 + 幂等落地(任务 × 意向);落地收敛才发射输出口; 受阻走落地失败卡且 emittedPorts 恒空;已成动作留存不回滚 215 个测试通过,门禁五道全绿 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
co-authored by
Claude Opus 5
parent
146e9bbfad
commit
561a2474ae
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* human-tool 存储 adapter —— Drizzle 只准出现在 adapter(编码规范 §5.3)。
|
||||
*/
|
||||
import { and, asc, eq, isNull, sql } from 'drizzle-orm';
|
||||
import type { ChatCard, StaffId, TaskId, WorkOrder } from '@contracts';
|
||||
import { chatCardSchema, workOrderSchema } from '@contracts';
|
||||
import type { Db } from '@shared/db.ts';
|
||||
import type { CardStore } from '../../port.ts';
|
||||
import { chatCardTable, workOrderTable } from './human-tool-schema.ts';
|
||||
|
||||
export function createCardStore(db: Db): CardStore {
|
||||
return {
|
||||
async saveWorkOrder(taskId, order, recipientId) {
|
||||
await db.insert(workOrderTable).values({
|
||||
id: order.id,
|
||||
taskId,
|
||||
recipientId,
|
||||
mode: order.mode,
|
||||
title: order.title,
|
||||
payload: order,
|
||||
at: new Date(order.at),
|
||||
done: order.done ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
async getWorkOrder(id) {
|
||||
const rows = await db.select().from(workOrderTable).where(eq(workOrderTable.id, id)).limit(1);
|
||||
const row = rows[0];
|
||||
if (row === undefined) return undefined;
|
||||
const order = workOrderSchema.parse({
|
||||
...(row.payload as Record<string, unknown>),
|
||||
...(row.done === null ? {} : { done: row.done }),
|
||||
});
|
||||
return { taskId: row.taskId as TaskId, order, recipientId: row.recipientId as StaffId };
|
||||
},
|
||||
|
||||
async listWorkOrders(taskId, openOnly) {
|
||||
const conditions = [eq(workOrderTable.taskId, taskId)];
|
||||
if (openOnly) conditions.push(isNull(workOrderTable.done));
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(workOrderTable)
|
||||
.where(and(...conditions))
|
||||
.orderBy(asc(workOrderTable.at));
|
||||
return rows.map((row) =>
|
||||
workOrderSchema.parse({
|
||||
...(row.payload as Record<string, unknown>),
|
||||
...(row.done === null ? {} : { done: row.done }),
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
async markWorkOrderDone(id, done) {
|
||||
await db.update(workOrderTable).set({ done }).where(eq(workOrderTable.id, id));
|
||||
},
|
||||
|
||||
async saveChatCard(taskId, card, recipientId) {
|
||||
await db.insert(chatCardTable).values({
|
||||
id: card.id,
|
||||
taskId,
|
||||
recipientId,
|
||||
kind: card.k,
|
||||
payload: card,
|
||||
at: new Date(card.at),
|
||||
});
|
||||
},
|
||||
|
||||
async getChatCard(id) {
|
||||
const rows = await db.select().from(chatCardTable).where(eq(chatCardTable.id, id)).limit(1);
|
||||
const row = rows[0];
|
||||
if (row === undefined) return undefined;
|
||||
return {
|
||||
taskId: row.taskId as TaskId,
|
||||
card: chatCardSchema.parse(row.payload),
|
||||
recipientId: row.recipientId as StaffId,
|
||||
};
|
||||
},
|
||||
|
||||
async listChatCards(taskId) {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(chatCardTable)
|
||||
.where(eq(chatCardTable.taskId, taskId))
|
||||
.orderBy(asc(chatCardTable.at));
|
||||
return rows.map((row) => chatCardSchema.parse(row.payload));
|
||||
},
|
||||
|
||||
async updateChatCard(card: ChatCard) {
|
||||
await db.update(chatCardTable).set({ payload: card }).where(eq(chatCardTable.id, card.id));
|
||||
},
|
||||
|
||||
async closeChatCard(id, supersededBy, reason) {
|
||||
await db.update(chatCardTable).set({ supersededBy, rejectedReason: reason }).where(eq(chatCardTable.id, id));
|
||||
},
|
||||
|
||||
/** 调人次数 = 发出的工单 + 对话卡 —— 打扰率与预算的计数面(OS 8.3) */
|
||||
async humanCallCount(taskId) {
|
||||
const orders = await db
|
||||
.select({ n: sql<number>`count(*)::int` })
|
||||
.from(workOrderTable)
|
||||
.where(eq(workOrderTable.taskId, taskId));
|
||||
const cards = await db
|
||||
.select({ n: sql<number>`count(*)::int` })
|
||||
.from(chatCardTable)
|
||||
.where(eq(chatCardTable.taskId, taskId));
|
||||
return (orders[0]?.n ?? 0) + (cards[0]?.n ?? 0);
|
||||
},
|
||||
|
||||
async openCardsOf(staffId) {
|
||||
const orders = await db
|
||||
.select({ id: workOrderTable.id, title: workOrderTable.title })
|
||||
.from(workOrderTable)
|
||||
.where(and(eq(workOrderTable.recipientId, staffId), isNull(workOrderTable.done)));
|
||||
const cards = await db
|
||||
.select({ id: chatCardTable.id, kind: chatCardTable.kind })
|
||||
.from(chatCardTable)
|
||||
.where(and(eq(chatCardTable.recipientId, staffId), isNull(chatCardTable.supersededBy)));
|
||||
return [
|
||||
...orders.map((o) => ({ cardId: o.id, title: o.title })),
|
||||
...cards.map((c) => ({ cardId: c.id, title: `${c.kind} 卡` })),
|
||||
];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type { WorkOrder };
|
||||
Reference in New Issue
Block a user