M2: 基座能力 —— memory / toolbox / channel / message

- memory   任务记忆四性质(只追加·信封盖戳·治理先行·归侧)、活动记忆四层与条目三件套、
           任务日志两级投影(结构化零 LLM / 叙述性小结不入事实库)、度量八指标只读投影
- toolbox  注册两审(静态审 + 试运行审)、效应等级决定运行时待遇、契约演进只增不破坏、
           白名单与纯工具私有作用域、ToolInvoker(两缝之二,live/record/replay)
- channel  通道实例配置、入站归一(解析 → 系统写 → 数据事件)、出站发送与关联记录登记
- message  通知 / 跨任务征答(就地作答 · 驳回重答只增不改)/ 死信 / 无主兜底

app:message 接上 event 的 MessageFallback;派发器按目标 ID 取 advisory lock 串行消费 +
消费幂等表;/api/ingest 模拟邮件通道(mock 只在外界)

已验证:无主 RFQ 邮件入站 → 归属解析 no-match → 事件落库 → 兜底落 OP 岗位的消息要素
门禁五道全绿;150 个单元测试 + 1 个 PG 集成测试

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
JCAI Main Agent
2026-07-27 01:16:52 +08:00
co-authored by Claude Opus 5
parent 14d4fa046a
commit f0444f1417
73 changed files with 7488 additions and 31 deletions
+41
View File
@@ -0,0 +1,41 @@
/**
* module: message —— 对外类型(编码规范 §10.1)。
*/
import type { Envelope, StaffId } from '@contracts';
import type { MessageKind, SystemMessage } from './internal/domain/message-kind.ts';
export type { Envelope, MessageKind, SystemMessage };
export declare class MessageNotAnswerableError extends Error {
readonly messageId: string;
constructor(messageId: string);
}
export type SendNotice = {
readonly staffId: StaffId;
readonly title: string;
readonly body: string;
readonly eventRef?: string;
};
export type SendElicitation = {
readonly staffId: StaffId;
readonly title: string;
readonly body: string;
readonly cardId: string;
readonly sourceTaskId: string;
readonly payload: Record<string, unknown>;
};
export type MessageService = {
inbox(staffId: StaffId, options?: {
unreadOnly?: boolean;
limit?: number;
}): Promise<SystemMessage[]>;
markRead(messageId: string): Promise<void>;
notify(input: SendNotice): Promise<SystemMessage>;
/** 跨人卡以系统消息送达对方,就地作答 —— 任务要素永远只装「我负责的任务」(OS 2.1) */
sendElicitation(input: SendElicitation): Promise<SystemMessage>;
answer(messageId: string, staffId: StaffId, answer: Record<string, unknown>): Promise<SystemMessage>;
/** 驳回重答 = 新发一条系统消息(新卡实例),原卡闭合 —— 只增不改 */
rejectAndReask(messageId: string, reason: string): Promise<SystemMessage>;
/** 死信:重试超限 → 系统通知送达相关人的消息要素与治理台 */
deadLetter(envelope: Envelope, error: string): Promise<void>;
/** 无主兜底:相关人 = 通道实例配置的默认认领岗位 */
unowned(envelope: Envelope, fallbackPosition: string | undefined): Promise<void>;
};
+11
View File
@@ -0,0 +1,11 @@
/**
* module: message —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
*/
export type { MessageKind, MessageService, SendElicitation, SendNotice, SystemMessage } from './contract.ts';
export { MessageNotAnswerableError } from './contract.ts';
export type { Clock, IdGenerator, MessageRepository, PositionResolver } from './port.ts';
export type { MessageServiceDeps } from './internal/usecase/message-service.ts';
export { createMessageService } from './internal/usecase/message-service.ts';
export { canAnswer, MESSAGE_KIND_LABEL, MESSAGE_KINDS, pendingAnswerCount, unreadCount, } from './internal/domain/message-kind.ts';
export { createMessageRepository } from './internal/adapter/message-store.ts';
export { messageTable } from './internal/adapter/message-schema.ts';
+29
View File
@@ -0,0 +1,29 @@
/**
* module: message —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
*/
import type { StaffId } from '@contracts';
import type { SystemMessage } from './internal/domain/message-kind.ts';
export type Clock = {
now(): Date;
};
export type IdGenerator = {
next(): string;
};
export type MessageRepository = {
list(staffId: StaffId, unreadOnly: boolean, limit: number): Promise<SystemMessage[]>;
get(messageId: string): Promise<SystemMessage | undefined>;
insert(message: SystemMessage): Promise<SystemMessage>;
markRead(messageId: string, at: Date): Promise<void>;
markAnswered(messageId: string, at: Date, answer: Record<string, unknown>): Promise<void>;
supersede(messageId: string, byMessageId: string, reason: string): Promise<void>;
};
/** 岗位解析 —— app 注入 `org`(L1):无主兜底要把消息落到默认认领岗位上的人 */
export type PositionResolver = {
staffOfPosition(positionId: string): Promise<Array<{
id: StaffId;
}>>;
/** 治理台收件人(任务管理岗)—— 死信同时送治理台 */
governanceStaff(): Promise<Array<{
id: StaffId;
}>>;
};