Archived
- 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
233 lines
7.4 KiB
TypeScript
233 lines
7.4 KiB
TypeScript
/**
|
|
* DI 装配 —— `app/` 是**唯一组装层**(编码规范 §5.2)。
|
|
* 只有这里能同时认识多个模块:new 出 adapter、注入 port、把用例接起来。
|
|
*
|
|
* 跨层协作一律经端口注入:`ledger` 要发数据事件 → 注入 `event` 的出口;
|
|
* `event` 的三级消费下游(trigger L7 / message L2)在其**上**,同样经端口注入。
|
|
*/
|
|
import { Pool } from 'pg';
|
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
import type { Db } from '@shared/db.ts';
|
|
|
|
import type { EventService, TriggerMatcher } from '@modules/event';
|
|
import { createEventService, createEventStore } from '@modules/event';
|
|
import type { LedgerService } from '@modules/ledger';
|
|
import { createDraftStore, createLedgerService, createLedgerStore, DRAFT_DDL } from '@modules/ledger';
|
|
import type { OrgService, InFlightWorkProbe } from '@modules/org';
|
|
import {
|
|
createGrantRepository,
|
|
createOrgService,
|
|
createPositionRepository,
|
|
createStaffRepository,
|
|
} from '@modules/org';
|
|
import type { LlmService, ModelGateway } from '@modules/llm';
|
|
import {
|
|
createLlmInstanceRepository,
|
|
createLlmService,
|
|
createModelGateway,
|
|
createProviderInvoker,
|
|
createReplayStore,
|
|
} from '@modules/llm';
|
|
import type { MemoryService } from '@modules/memory';
|
|
import { createActivityMemoryStore, createMemoryService, createTaskMemoryStore } from '@modules/memory';
|
|
import type { ToolboxService, ToolHandler, ToolInvoker } from '@modules/toolbox';
|
|
import {
|
|
createSchemaValidator,
|
|
createToolInvoker,
|
|
createToolRegistry,
|
|
createToolReplayStore,
|
|
createToolRuntime,
|
|
createToolboxService,
|
|
} from '@modules/toolbox';
|
|
import type { MessageService } from '@modules/message';
|
|
import { createMessageRepository, createMessageService } from '@modules/message';
|
|
import type { ChannelService } from '@modules/channel';
|
|
import {
|
|
createChannelRepository,
|
|
createChannelService,
|
|
createFileAttachmentArchive,
|
|
createSimulatedMailTransport,
|
|
} from '@modules/channel';
|
|
|
|
import type { AppConfig } from './config.ts';
|
|
import { createIdGenerator, systemClock } from './runtime.ts';
|
|
import { createSecretBox, loadSecretKey } from './secret-box.ts';
|
|
|
|
export type Container = {
|
|
readonly config: AppConfig;
|
|
readonly pool: Pool;
|
|
readonly db: Db;
|
|
readonly org: OrgService;
|
|
readonly ledger: LedgerService;
|
|
readonly event: EventService;
|
|
readonly llm: LlmService;
|
|
readonly modelGateway: ModelGateway;
|
|
readonly memory: MemoryService;
|
|
readonly toolbox: ToolboxService;
|
|
readonly toolInvoker: ToolInvoker;
|
|
readonly message: MessageService;
|
|
readonly channel: ChannelService;
|
|
close(): Promise<void>;
|
|
};
|
|
|
|
/**
|
|
* 在途工作探针 —— 停用前置检查要问的是 task(L6)与 human-tool(L3),二者随 M3/M4 落地。
|
|
* 占位返回空集;M4 起接真实用例(docs/debt.md · M1-1)。
|
|
*/
|
|
const emptyInFlightProbe: InFlightWorkProbe = {
|
|
openTasksOf: async () => [],
|
|
openCardsOf: async () => [],
|
|
openApprovalsOf: async () => [],
|
|
};
|
|
|
|
/** 触发器判定 —— trigger(L7)随 M5 落地,当下恒不命中(docs/debt.md · M1-1) */
|
|
const noTriggerMatcher: TriggerMatcher = { match: async () => [] };
|
|
|
|
/** S-FEEL 求值 —— @sfeel 随 M3 落地;在此之前监听只用类型 + 关联键过滤 */
|
|
const passthroughEvaluator = (): boolean => true;
|
|
|
|
/** 第一方工具实现 —— 由 app 注册进 ToolRuntime;活动只见调用契约 */
|
|
export const firstPartyToolHandlers = new Map<string, ToolHandler>();
|
|
|
|
export async function createContainer(config: AppConfig): Promise<Container> {
|
|
const pool = new Pool({ connectionString: config.databaseUrl, max: 10 });
|
|
const db = drizzle(pool);
|
|
const ids = createIdGenerator();
|
|
const secrets = createSecretBox(loadSecretKey());
|
|
|
|
// 草稿层的建表:ledger schema 的唯一写者是 ledger adapter,DDL 在其出口给出
|
|
await pool.query(DRAFT_DDL);
|
|
|
|
const org = createOrgService({
|
|
positions: createPositionRepository(db),
|
|
staff: createStaffRepository(db),
|
|
grants: createGrantRepository(db),
|
|
inFlight: emptyInFlightProbe,
|
|
ids,
|
|
});
|
|
|
|
// message 是 event 的 `MessageFallback` 实现面 —— event(L1)不反向依赖它,由此处注入
|
|
const message = createMessageService({
|
|
repo: createMessageRepository(db),
|
|
org: {
|
|
staffOfPosition: async (positionId) => {
|
|
const { staff } = await org.listStaff();
|
|
return staff.filter((s) => s.positionId === positionId).map((s) => ({ id: s.id }));
|
|
},
|
|
governanceStaff: async () => {
|
|
const { staff } = await org.listStaff();
|
|
const governing = await Promise.all(
|
|
staff.map(async (s) => ({
|
|
s,
|
|
ok: await org.authorize(s.id, { kind: 'governance', function: 'task' }, 'govern'),
|
|
})),
|
|
);
|
|
return governing.filter((g) => g.ok).map((g) => ({ id: g.s.id }));
|
|
},
|
|
},
|
|
clock: systemClock,
|
|
ids,
|
|
});
|
|
|
|
const event = createEventService({
|
|
store: createEventStore(db, ids),
|
|
clock: systemClock,
|
|
ids,
|
|
evaluate: passthroughEvaluator,
|
|
triggers: noTriggerMatcher,
|
|
messages: message,
|
|
});
|
|
|
|
const ledger = createLedgerService({
|
|
store: createLedgerStore(pool),
|
|
drafts: createDraftStore(pool),
|
|
events: { publish: async (envelope) => void (await event.publish(envelope)) },
|
|
clock: systemClock,
|
|
ids,
|
|
});
|
|
|
|
const instances = createLlmInstanceRepository(db);
|
|
const provider = createProviderInvoker();
|
|
const llm = createLlmService({ instances, provider, secrets, clock: systemClock, ids });
|
|
const modelGateway = createModelGateway({
|
|
instances,
|
|
provider,
|
|
replay: createReplayStore(db),
|
|
secrets,
|
|
clock: systemClock,
|
|
mode: 'live',
|
|
});
|
|
|
|
const memory = createMemoryService({
|
|
tasks: createTaskMemoryStore(db),
|
|
activities: createActivityMemoryStore(db),
|
|
model: {
|
|
// 系统用途模型:叙述性小结 / 汇报生成 / 蒸馏钩子 —— 计量入系统账,不占活动预算
|
|
generate: async (input) => {
|
|
const result = await modelGateway.generate({
|
|
model: input.model,
|
|
prompt: input.prompt,
|
|
...(input.outputSchema === undefined ? {} : { outputSchema: input.outputSchema }),
|
|
label: 'system-use',
|
|
});
|
|
return { text: result.text, ...(result.object === undefined ? {} : { object: result.object }) };
|
|
},
|
|
systemModelName: async () => (await llm.systemUseBinding())?.name,
|
|
},
|
|
clock: systemClock,
|
|
ids,
|
|
});
|
|
|
|
const toolRegistry = createToolRegistry(db);
|
|
const toolRuntime = createToolRuntime(firstPartyToolHandlers);
|
|
const schemas = createSchemaValidator();
|
|
const toolbox = createToolboxService({
|
|
registry: toolRegistry,
|
|
runtime: toolRuntime,
|
|
schemas,
|
|
secrets,
|
|
clock: systemClock,
|
|
});
|
|
const toolInvoker = createToolInvoker({
|
|
registry: toolRegistry,
|
|
runtime: toolRuntime,
|
|
replay: createToolReplayStore(db),
|
|
schemas,
|
|
secrets,
|
|
clock: systemClock,
|
|
mode: 'live',
|
|
});
|
|
|
|
const channel = createChannelService({
|
|
channels: createChannelRepository(db),
|
|
events: {
|
|
ingestInbound: (input) => event.ingestInbound(input),
|
|
recordOutbound: (ref) => event.recordOutbound(ref),
|
|
},
|
|
ledger: { systemWrite: (input) => ledger.systemWrite(input) },
|
|
mail: createSimulatedMailTransport(db, ids),
|
|
archive: createFileAttachmentArchive(config.attachmentRoot),
|
|
clock: systemClock,
|
|
ids,
|
|
});
|
|
|
|
return {
|
|
config,
|
|
pool,
|
|
db,
|
|
org,
|
|
ledger,
|
|
event,
|
|
llm,
|
|
modelGateway,
|
|
memory,
|
|
toolbox,
|
|
toolInvoker,
|
|
message,
|
|
channel,
|
|
async close() {
|
|
await pool.end();
|
|
},
|
|
};
|
|
}
|