- 人工智能
- AI Agent
- 多智能体
- 后端
- 前端
【免费下载链接】relay
Relay 是一个多智能体协作平台,通过统一的界面进行协作开发、代码审查、任务管理,将多个 AI Agent 组织成一个真正的团队。
Relay(OfficeClaw)的多智能体协作平台将后端 HTTP API 服务封装为独立的 npm 包@openjiuwen/relay-api-server,它向外导出服务入口与编程式接入接口,并通过"Provider 模块 + 环境变量"的方式按需加载存储、证据、调度、认证、目录等能力实现。本文以 packages/api/README.md 为骨架,结合仓库源码与测试,系统讲解该包的安装方式、导出结构、Provider 模块化机制、SQLite 提供方接入实践以及编程式启动的完整用法,帮助你理解如何基于该包二次集成或定制 Relay 的 API 服务。
一、包定位:Relay 运行时的后端 API 入口
@openjiuwen/relay-api-server是 OfficeClaw 运行时使用的后端 API 服务包(OfficeClaw API server package)。按 packages/api/README.md 的描述,它导出 OfficeClaw 运行时所需的服务器入口点(server entry points),并依赖共享契约包(shared contracts)与核心运行时包(core runtime package)。
在仓库 monorepo 结构中,该包的依赖关系可以在 packages/api/package.json 中得到印证:
@openjiuwen/relay-core:核心运行时,提供OfficeClawProviderPlugin、ProviderPluginRegistry等插件契约;@openjiuwen/relay-api-server-contracts:API 服务契约包,定义IMessageStore、IThreadStore、EvidenceProvider、SchedulerProvider等端口接口;@openjiuwen/relay-shared:共享类型与工具(如createRedisClient、SessionStore、Agent 注册表等)。
三者均以workspace:*形式引入,说明该包必须工作在 pnpm workspace 中。包本身还内置了 Fastify、Socket.IO、ioredis、puppeteer、nodemailer、imapflow、sharp 等大量依赖,覆盖了消息、会话、调度、推送、音视频、连接器等后端能力。
二、安装与基本用法
按 README 给出的安装方式,在应用或运行时项目中执行:
pnpm add @openjiuwen/relay-api-server安装后有两个可用的导出子路径(exports):
@openjiuwen/relay-api-server:主入口,对应 src/index.ts 编译产物dist/index.js,导出main()、createApiModule()、createAuthModule、createStorageModule、createCatalogModule等;@openjiuwen/relay-api-server/server:编程式服务器工厂入口,对应 src/server.ts 编译产物dist/server.js,导出createOfficeClawServer()。
此外,packages/api/package.json 中还注册了两个 bin 命令:
office-claw-api-serverrelay-api-server
二者都指向./dist/cli.js,也就是 src/cli.ts 的编译产物。CLI 入口非常轻量,仅仅调用main()并在出错时输出[api] Fatal error后以退出码 1 结束进程:
import { main } from './index.js'; main().catch((err) => { console.error('[api] Fatal error:', err); process.exit(1); });这意味着安装该包后,你既可以通过pnpm dev/node dist/cli.js直接启动服务,也可以在宿主代码中编程式拉起服务器。
三、Provider 模块化架构:能力按需加载
README 的核心信息在于:存储(storage)、证据(evidence)、调度(scheduler)、认证(auth)、指标(metrics)与目录(catalog)等实现并不内置于 API 包,而是通过 Provider 模块加载。你需要单独安装 Provider 包,并用对应的OFFICE_CLAW_*_PROVIDER_MODULES环境变量告诉运行时去哪里加载它们。
这种"端口 + 适配器"的设计在源码中有清晰的对应实现。每一个领域都有一份独立的module.ts,统一遵循"注册内置 Provider → 按环境变量加载外部模块 → 解析激活 Provider ID → bootstrap → 创建具体能力"的模式:
| 能力域 | 模块文件 | 激活 ID 环境变量 | 模块列表环境变量 | 默认 Provider |
|---|---|---|---|---|
| 存储 | src/storage/module.ts | OFFICE_CLAW_STORAGE_PROVIDER | OFFICE_CLAW_STORAGE_PROVIDER_MODULES | 有 Redis 用redis,否则MEMORY_STORE=1用memory,否则直接抛错(fail-closed) |
| 证据 | src/evidence/module.ts | OFFICE_CLAW_EVIDENCE_PROVIDER | OFFICE_CLAW_EVIDENCE_PROVIDER_MODULES | noop |
| 调度 | src/scheduler-persistence/module.ts | OFFICE_CLAW_SCHEDULER_PROVIDER | OFFICE_CLAW_SCHEDULER_PROVIDER_MODULES | noop |
| 认证 | src/auth/module.ts | OFFICE_CLAW_AUTH_PROVIDER | OFFICE_CLAW_AUTH_PROVIDER_MODULES | no-auth |
| 目录 | src/catalog/module.ts | OFFICE_CLAW_CATALOG_PROVIDER | OFFICE_CLAW_CATALOG_PROVIDER_MODULES | file |
几个值得注意的源码细节:
- 模块列表支持逗号分隔:以存储模块为例,storage/module.ts 中的
parseModuleSpecifiers()会把OFFICE_CLAW_STORAGE_PROVIDER_MODULES按逗号切分并过滤空项,因此一个变量可以加载多个 Provider 模块。 - 激活 Provider 与模块分离:
OFFICE_CLAW_*_PROVIDER决定"用哪一个",OFFICE_CLAW_*_PROVIDER_MODULES决定"从哪些模块注册可用项";如果激活的 ID 未注册,Registry 的get()会抛出形如Storage provider 'xxx' not found. Registered: [...]的明确错误(见 storage/storage-provider-registry.ts)。 - 证据与调度默认 noop:未配置时证据与调度 Provider 为
noop,此时证据索引与 TaskRunnerV2 调度器不会真正工作。在 src/index.ts 中,调度模块初始化为noop时会被判定为不可用(scheduler provider is noop抛错并被捕获为非致命警告),这说明生产环境必须接入真实的调度 Provider。 - README 中提到的 metrics:README 将 metrics 与 storage、evidence 等并列。从当前仓库源码结构看,
storage、evidence、scheduler、auth、catalog五个模块均有对应的module.ts与OFFICE_CLAW_*_PROVIDER_MODULES环境变量可查证;metrics 相关的 Provider 模块文件未在packages/api/src下发现,如需启用可参考同族环境变量的命名规律自行接入 Provider 包。
Provider 模块的导出契约
每个外部 Provider 模块被加载后,Registry 会从模块的命名空间中收集候选 Provider。以证据 Registry(src/evidence/provider-registry.ts)为例,它依次检查default、evidenceProvider具名导出以及evidenceProviders数组导出,只要命中的对象满足{ id: string; createEvidenceServices: Function }形状即视为有效 Provider。存储 Registry(src/storage/storage-provider-registry.ts)还支持部分 Provider:只实现部分 store 的 Provider 会被wrapPartialProvider包装,用默认 Provider 补齐缺失的 store。这为"只替换某个存储"的定制场景提供了很大灵活性。
四、实战:接入 SQLite 证据与调度 Provider
README 给出的完整示例是使用@openjiuwen/relay-storage-sqlite包来获得 SQLite 证据(evidence)与调度(scheduler)能力:
pnpm add @openjiuwen/relay-storage-sqliteOFFICE_CLAW_EVIDENCE_PROVIDER=sqlite OFFICE_CLAW_EVIDENCE_PROVIDER_MODULES=@openjiuwen/relay-storage-sqlite/evidence OFFICE_CLAW_SCHEDULER_PROVIDER=sqlite OFFICE_CLAW_SCHEDULER_PROVIDER_MODULES=@openjiuwen/relay-storage-sqlite/scheduler4.1 Provider 包的结构
该包对应仓库中的 packages/sqlite-adapter/api 目录,包名为@openjiuwen/relay-storage-sqlite(见 package.json)。它通过exports暴露三个子路径:
.:主入口,导出evidenceProvider、schedulerProvider与approvalRecordStorageProvider;./evidence:映射到dist/evidence/sqlite.js,导出id: 'sqlite'的evidenceProvider;./scheduler:映射到dist/scheduler/sqlite.js,导出id: 'sqlite'的schedulerProvider;./authorization:提供createSqliteApprovalRecordStore等审批记录存储能力。
子路径导出正是 README 示例中.../evidence与.../scheduler能作为模块说明符被直接加载的原因——OFFICE_CLAW_EVIDENCE_PROVIDER_MODULES指向的字符串本质上就是一个可被import()的模块说明符。
4.2 证据 Provider 的底层实现
evidence/sqlite.ts 中的evidenceProvider调用createMemoryServices()创建一套 SQLite 支撑的证据服务,包括:
SqliteEvidenceStore:证据文档与关系(edges)的存储;IndexBuilder:对docsRoot下的文档建索引;VectorStore与EmbeddingService:基于sqlite-vec的向量检索;stats()能力:直接查询evidence_docs与edges表统计文档数、边数以及最近更新时间。
从 src/index.ts 可以看到,API 服务在启动时会调用证据模块的indexBuilder.rebuild()重建证据索引(失败仅告警不阻断启动),随后通过markThreadDirty/accumulateSummaryDelta/flushDirtyThreads每 30 秒把脏线程摘要增量刷入索引。
4.3 调度 Provider 的底层实现
scheduler/sqlite.ts 中的schedulerProvider用better-sqlite3打开数据库文件,并设置三项 pragma:
db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); db.pragma('busy_timeout = 5000');随后返回一组 SQLite 持久化对象,供 API 服务的TaskRunnerV2调度器使用:
RunLedger:任务运行台账;GlobalControlStore:全局治理开关;EmissionStore:发射(投递)记录;PackTemplateStore:打包模板;DynamicTaskStore:动态任务持久化。
需要留意的是,SQLite 证据与调度默认使用同一个数据库文件路径:API 服务在 src/index.ts 中通过env.EVIDENCE_DB ?? <monorepoRoot>/data/evidence.sqlite同时注入给证据模块与调度模块,即两套能力共享一个evidence.sqlite数据库(调度侧的建表依赖applyMigrations完成)。
4.4 环境变量默认值如何被铺开
仓库的启动脚本已经把这些默认值内置。例如:
- scripts/start-dev.sh 默认
OFFICE_CLAW_STORAGE_PROVIDER_MODULES=@openjiuwen/relay-storage-sqlite、OFFICE_CLAW_APPROVAL_RECORD_PROVIDER=sqlite-approval-records; - scripts/start-windows.ps1 为 Windows 运行时设置了
OFFICE_CLAW_EVIDENCE_PROVIDER=sqlite、OFFICE_CLAW_EVIDENCE_PROVIDER_MODULES=@openjiuwen/relay-storage-sqlite/evidence、OFFICE_CLAW_SCHEDULER_PROVIDER=sqlite及对应的_MODULES变量。
这解释了 README 示例的完整落地路径:安装 Provider 包 → 设置环境变量 → 启动 API 服务即自动加载 SQLite 证据与调度能力。
五、模块加载器:workspace 内如何解析 Provider 模块
外部 Provider 模块说明符(如@openjiuwen/relay-storage-sqlite/evidence)是通过moduleLoader动态import()的。默认的加载器定义在 src/utils/workspace-module-loader.ts:它先尝试标准import(specifier),如果抛出ERR_MODULE_NOT_FOUND,则扫描当前项目根下packages目录中的全部package.json,把@openjiuwen/*包名映射到实际目录,再通过pathToFileURL()定位exports中对应的子路径实现文件。
这正是本仓库 monorepo 部署形态的支撑点:当 Provider 包尚未发布到 npm registry 时,@openjiuwen/relay-storage-sqlite/evidence也能通过 workspace 扫描被正确解析并加载。createStorageModule、createEvidenceModule、createAuthModule等都接收可选的moduleLoader参数,测试与下游宿主可以注入自定义加载器。
六、编程式接入:createOfficeClawServer 与启动选项
6.1 服务器工厂
@openjiuwen/relay-api-server/server子路径导出的createOfficeClawServer()(见 src/server.ts)是最常用的编程式接入点,返回{ start, close }:
import { createOfficeClawServer } from '@openjiuwen/relay-api-server/server'; const server = await createOfficeClawServer({ port: 3004, host: '127.0.0.1', redisUrl: 'redis://127.0.0.1:6379', memoryStore: false, }); const url = await server.start(); // 例如 http://127.0.0.1:3004 // ... 退出前调用 await server.close();OfficeClawServerOptions支持以下字段,工厂会把它们写入对应环境变量后再启动(见 src/server.ts):
| 选项 | 写入的环境变量 | 说明 |
|---|---|---|
port | API_SERVER_PORT | 监听端口,默认3004 |
host | API_SERVER_HOST | 监听地址,默认127.0.0.1 |
redisUrl | REDIS_URL | Redis 连接串,提供后自动启用 Redis 存储与租约 |
memoryStore | MEMORY_STORE=1 | 开启内存存储模式(无 Redis 时) |
projectRoot | OFFICE_CLAW_CONFIG_ROOT | 项目根目录,用于解析 Agent 配置 |
plugins | globalThis.__clowder_extra_plugins | 注入额外的OfficeClawProviderPlugin,可注册自定义 Agent 提供方 |
runtimeEnvStore | — | 运行时环境存储,供启动时 bootstrap 解析 |
启动完成后返回的 URL 由 src/server-lifecycle.ts 依据API_SERVER_PORT/API_SERVER_HOST拼接,close()则通过_stopForProgrammatic()触发优雅关停。
6.2 更细粒度的 ApiStartOptions
若你不需要server.ts的封装,可以直接调用主入口的main(options)(即createApiModule(options)),它支持更细粒度的扩展点(见 src/index.ts):
storageModule/catalogModule:注入自定义模块以替换存储后端或 Agent 配置加载;hooks.afterAppCreated:在全局认证钩子注册前挂载自定义认证中间件;hooks.afterInit:拿到完整的ApiInitContext(env、cwd、Fastify 实例、stores、services、modules);hooks.afterAuthMiddleware:注册 JWTrequest.auth注入;hooks.afterSocketManagerCreated:为 WebSocket 附加认证逻辑;skipFeatures:跳过指定功能组的路由注册。FeatureName的可选值为terminal、ppt、connector、scheduler、memory、session、push、uploads、tts。
6.3 启动阶段的内建行为
main()会依次完成大量初始化,理解这些行为有助于排障与定制:
- Fastify 实例:
bodyLimit为 100MB,注册 Cookie、CORS、WebSocket 插件;日志序列化器会脱敏token、apiKey、secret等查询参数; - 全局安全:注册全局认证钩子、安全响应头、统一错误处理器与
GET /health健康检查(返回{ status: 'ok', timestamp }); - 存储层:按
OFFICE_CLAW_STORAGE_PROVIDER创建messageStore、threadStore、taskStore、backlogStore、memoryStore等(消息/线程/任务/积压均支持*_TTL_SECONDS过期); - Agent 编排:从 catalog 读取 Agent 配置,通过
ProviderPluginRegistry按config.provider创建对应的AgentService(内置插件见 src/config/plugins/builtin-providers.ts,涵盖 anthropic、openai、google、dare、opencode、antigravity、relayclaw、acp、a2a),并维护AgentRouter用于消息路由; - 多实例保护:配置了 Redis 时通过
ApiInstanceLease获取 API 命名空间租约,若租约被抢占或监听失败,进程会拒绝启动并退出,防止同一 Redis 命名空间出现多个活实例; - 收尾任务:启动时清理孤儿 invocation(
StartupReconciler)、全局会话 reaper 定时扫描卡在sealing状态的会话、预热 provider profiles、检测可用 CLI 客户端、启动 GitHub 评审 watcher 与 CI/CD 轮询器、启动连接器网关; - 优雅关停:收到 SIGTERM/SIGINT 后依次记录审计日志、触发 Redis BGSAVE、关闭存储 Provider、停止 watcher、关闭 SocketManager 与 Fastify,最后释放租约。
七、验证与测试
仓库为该包的接入方式提供了直接可读的测试证据:
- test/server-entrypoint.test.js 验证
createOfficeClawServer({ port, host, memoryStore })暴露start/close方法,并验证注入的runtimeEnvStore会被注册到 bootstrap 解析器; - test/auth-module.test.js 覆盖
OFFICE_CLAW_AUTH_PROVIDER+OFFICE_CLAW_AUTH_PROVIDER_MODULES的 Provider 注册、缺失 Provider 报错与 bootstrap 行为; - test/auth-external-provider-e2e.test.js 以
OFFICE_CLAW_AUTH_PROVIDER=demo-static、OFFICE_CLAW_AUTH_PROVIDER_MODULES=@examples/demo-auth-provider演示了外部认证 Provider 的端到端接入。
这些测试同时佐证了 README 的 Provider 加载方式:_PROVIDER指定激活 ID,_PROVIDER_MODULES指向模块说明符,二者缺一不可。
八、总结
@openjiuwen/relay-api-server作为 Relay 的官方后端 API 服务包,把"服务入口"与"能力实现"解耦:服务骨架内置在包中(Fastify、认证、路由、Agent 编排、优雅关停等),而存储、证据、调度、认证、目录等具体能力全部由 Provider 模块按需接入。接入时只需两条规则:
- 安装对应的 Provider 包(如
@openjiuwen/relay-storage-sqlite),并通过其exports子路径指向具体的 Provider 实现; - 设置
OFFICE_CLAW_<能力域>_PROVIDER指定激活项,设置OFFICE_CLAW_<能力域>_PROVIDER_MODULES指定模块加载说明符。
需要更深入的实现细节时,建议按以下路径阅读源码:包契约与启动流程看 src/index.ts,编程式接入看 src/server.ts,各能力域模块看 src/storage/module.ts、src/evidence/module.ts、src/scheduler-persistence/module.ts、src/auth/module.ts、src/catalog/module.ts,Provider 实现参考 packages/sqlite-adapter/api/src/evidence/sqlite.ts 与 packages/sqlite-adapter/api/src/scheduler/sqlite.ts。
- 人工智能
- AI Agent
- 多智能体
- 后端
- 前端
【免费下载链接】relay
Relay 是一个多智能体协作平台,通过统一的界面进行协作开发、代码审查、任务管理,将多个 AI Agent 组织成一个真正的团队。
相关推荐
CloudForest回归分析指南:L1回归和梯度提升树的应用技巧
CloudForest回归分析指南:L1回归和梯度提升树的应用技巧 CloudForest是一个基于Go语言实现的决策树集成学习库,提供了L1回归和梯度提升树等
机器学习3步掌握GPT-SoVITS:从5秒语音到专业级语音克隆的完整攻略
3步掌握GPT SoVITS:从5秒语音到专业级语音克隆的完整攻略 你是否曾为语音合成的高门槛而却步?是否觉得需要数小时录音和复杂训练才能获得一个像样的语音模型
语音音频人工智能大模型微调模型推理服务本地部署bytebuffer.js:终极JavaScript二进制数据处理库,让ArrayBuffer与Buffer操作如虎添翼
bytebuffer.js:终极JavaScript二进制数据处理库,让ArrayBuffer与Buffer操作如虎添翼 bytebuffer.js是一个快速且
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考