Archived
app 装配 mail:channel 作首个订阅方、出站经 mail.send、mail.send 工具真发信(dryRun 只校验)、轮询工作者;上传件存取抽出共用
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
This commit is contained in:
@@ -74,12 +74,19 @@ import type { TraceService } from '@modules/trace';
|
||||
import { wireEngines } from './wiring/engines.ts';
|
||||
import { wireDeskElements } from './wiring/desk-elements.ts';
|
||||
import type { ChannelService } from '@modules/channel';
|
||||
import { createChannelRepository, createChannelService, createFileAttachmentArchive } from '@modules/channel';
|
||||
import type { MailService } from '@modules/mail';
|
||||
import {
|
||||
createChannelRepository,
|
||||
createChannelService,
|
||||
createFileAttachmentArchive,
|
||||
createSimulatedMailTransport,
|
||||
} from '@modules/channel';
|
||||
createImapSmtpProvider,
|
||||
createInboundStore,
|
||||
createMailAccountStore,
|
||||
createMailService,
|
||||
createOutboundStore,
|
||||
createPostalMimeParser,
|
||||
createWecomProvider,
|
||||
} from '@modules/mail';
|
||||
import { channelSubscriber, channelTransport, mailSendTool } from './mail-wiring.ts';
|
||||
import { readUpload, storeUpload } from './upload-store.ts';
|
||||
|
||||
import type { AppConfig } from './config.ts';
|
||||
import { createIdGenerator, systemClock } from './runtime.ts';
|
||||
@@ -107,6 +114,8 @@ export type Container = {
|
||||
readonly toolInvoker: ToolInvoker;
|
||||
readonly message: MessageService;
|
||||
readonly channel: ChannelService;
|
||||
/** 邮件收发(ADR-013):账号 / 轮询 / 订阅 / 发信 */
|
||||
readonly mail: MailService;
|
||||
readonly humanTool: HumanToolService;
|
||||
readonly interaction: InteractionService;
|
||||
readonly registry: ActivityRegistryService;
|
||||
@@ -172,9 +181,6 @@ const sfeelEvaluator = (expression: string, scope: Readonly<Record<string, unkno
|
||||
}
|
||||
};
|
||||
|
||||
/** 第一方工具实现 —— 由 app 注册进 ToolRuntime;活动只见调用契约 */
|
||||
export const firstPartyToolHandlers = new Map<string, ToolHandler>(FIRST_PARTY_TOOLS);
|
||||
|
||||
/** 集成测试的注入缝:验签与名册来源都是外界,测试给替身,不联网 */
|
||||
export type ContainerOverrides = {
|
||||
readonly tokenVerifier?: TokenVerifier;
|
||||
@@ -369,9 +375,36 @@ export async function createContainer(config: AppConfig, overrides: ContainerOve
|
||||
ids,
|
||||
});
|
||||
|
||||
/*
|
||||
* 邮件收发(ADR-013)—— L1,只要库 / 密封 / 时钟;先于工具运行时装配,`mail.send` 的处理器要闭包持它。
|
||||
* 变更经 ChangeNotifier 接到广播:轮询器不走 /api/gov/* 中间件,新来信 / 拉取出错得自己说一声。
|
||||
* 附件落 sys.upload —— 与上传路由同一段存取(8MB 上限、文本抽取、下载路由一并复用)。
|
||||
*/
|
||||
const mail = createMailService({
|
||||
accounts: createMailAccountStore(db),
|
||||
inbound: createInboundStore(db),
|
||||
outbound: createOutboundStore(db),
|
||||
attachments: {
|
||||
put: async (file) => ({
|
||||
ref: (await storeUpload(db, { name: file.name, contentType: file.contentType, bytes: file.bytes, uploadedBy: null, note: file.note })).ref,
|
||||
}),
|
||||
get: (ref) => readUpload(db, ref),
|
||||
},
|
||||
providers: { 'imap-smtp': createImapSmtpProvider(), wecom: createWecomProvider() },
|
||||
mime: createPostalMimeParser(),
|
||||
secrets,
|
||||
clock: systemClock,
|
||||
ids,
|
||||
notify: { changed: (reason) => broadcaster.publish({ kind: 'system', id: 'mail', reason }) },
|
||||
});
|
||||
|
||||
const toolRegistry = createToolRegistry(db);
|
||||
/** 第一方工具实现 —— `mail.send` 真发信,处理器在这儿闭包持 mail(放行时经 ToolInvoker 执行) */
|
||||
// 导入的自定义工具走通用 HTTP 实现:契约申报 endpoint,运行时只把那一个连接目标交出去
|
||||
const toolRuntime = createToolRuntime(firstPartyToolHandlers, httpToolHandler);
|
||||
const toolRuntime = createToolRuntime(
|
||||
new Map<string, ToolHandler>([...FIRST_PARTY_TOOLS, ['mail.send', mailSendTool(mail)]]),
|
||||
httpToolHandler,
|
||||
);
|
||||
const schemas = createSchemaValidator();
|
||||
const toolbox = createToolboxService({
|
||||
registry: toolRegistry,
|
||||
@@ -399,11 +432,13 @@ export async function createContainer(config: AppConfig, overrides: ContainerOve
|
||||
recordOutbound: (ref) => event.recordOutbound(ref),
|
||||
},
|
||||
ledger: { systemWrite: (input) => ledger.systemWrite(input) },
|
||||
mail: createSimulatedMailTransport(db, ids),
|
||||
mail: channelTransport(mail),
|
||||
archive: createFileAttachmentArchive(config.attachmentRoot),
|
||||
clock: systemClock,
|
||||
ids,
|
||||
});
|
||||
// channel 是第一个订阅方:来信归一进事件流,活动经触发器 / 监听收到;账号没绑通道的信留在运营面
|
||||
mail.subscribe('channel', channelSubscriber(channel));
|
||||
|
||||
/* ── L3/L4:交互、人类工具、活动库、放行门 ─────────────────────── */
|
||||
|
||||
@@ -775,6 +810,7 @@ export async function createContainer(config: AppConfig, overrides: ContainerOve
|
||||
toolInvoker,
|
||||
message,
|
||||
channel,
|
||||
mail,
|
||||
humanTool,
|
||||
interaction,
|
||||
registry,
|
||||
|
||||
Reference in New Issue
Block a user