内容
<p align="center">
<img src="logo.svg" alt="openma" height="80" />
</p>
<p align="center">
<img src="https://img.shields.io/badge/Cloudflare-Workers-F38020?logo=cloudflare&logoColor=white" alt="Cloudflare Workers" />
<img src="https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript&logoColor=white" alt="TypeScript" />
<img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="Apache 2.0 License" />
<img src="https://img.shields.io/badge/Tests-passing-brightgreen" alt="Tests" />
<img src="https://img.shields.io/badge/API-Anthropic%20Compatible-blueviolet" alt="Anthropic Compatible" />
</p>
# 开放式托管代理
**开源的Claude托管代理替代方案** — 以及开源、自托管的Claude Tag风格代理的基础。
🌐 **[openma.dev](https://openma.dev)** · 📖 **[docs.openma.dev](https://docs.openma.dev)** · 💬 **[github.com/openma-ai/open-managed-agents](https://github.com/openma-ai/open-managed-agents)**
编写一个托管程序。部署后,平台运行它 — 带有会话、沙盒、工具、内存、保险箱、Slack/GitHub/Linear集成和崩溃恢复等功能开箱即用。与Claude托管代理API兼容;运行在Cloudflare Workers + Durable Objects上,或在您自己的服务器上使用`docker compose up`。
使用开放式托管代理时,您需要:
- 自托管的Claude托管代理API实现。
- 开源、自托管的Claude Tag风格工作流,带有BYOK模型凭证。
- 在您自己的部署边界下,MCP、私有工具、加密保险箱和持久会话。
比较: [Claude Tag 替代方案](https://openma.dev/claude-tag-alternative/) · [开源Claude Tag](https://openma.dev/claude-tag-open-source/) · [自托管Claude Tag](https://openma.dev/self-hosted-claude-tag/)
---
## 两种运行OMA的方式
相同的托管程序、业务逻辑和事件日志模型在两者上运行。请选择适合您的托管方式:
| | **自托管(Node)** | **Cloudflare** |
|---|---|---|
| 位置 | 您的VPS / Mac / Docker主机 / fly.io / 您的k8s | Cloudflare Workers + DO + 容器 |
| 存储 | SQLite 或 Postgres + 本地文件系统 | D1 + KV + R2 |
| 沙盒 | LocalSubprocess / LiteBox / Daytona / E2B / BoxRun | Cloudflare 沙盒(容器) |
| 运行时间 | `docker compose up`(~2分钟) | wrangler部署(~10分钟后配置) |
| 适合 | OSS用户、本地部署、无CF账户、数据驻留部署 | 边缘规模、无主机管理、已在CF上 |
**相同的SDK。** 相同的`/v1/agents` / `/v1/sessions` API。相同的控制台UI。相同的崩溃恢复语义。通过更改环境变量而不是代码,在它们之间切换。
---
## 快速入门:自托管(Docker)
```bash
git clone https://github.com/openma-ai/open-managed-agents.git
cd open-managed-agents
cp .env.example .env
# 首次启动前需要两个密钥 — 两者都在本地生成:
# BETTER_AUTH_SECRET — 签署控制台会话
# PLATFORM_ROOT_SECRET — 加密凭证、模型卡API密钥、集成令牌
# (丢失它,所有加密的行都无法读取 — 备份它)
$EDITOR .env
# BETTER_AUTH_SECRET=$(openssl rand -hex 32)
# PLATFORM_ROOT_SECRET=$(openssl rand -base64 32)
#
# 可选:ANTHROPIC_API_KEY允许第一个代理在没有模型卡的情况下运行。
# 在生产环境中,请在控制台中为每个租户添加模型卡。
# SQLite + LocalSubprocess 沙盒(默认 — 最快路径)
docker compose up -d
# 或使用Postgres后端
# docker compose -f docker-compose.postgres.yml up -d
curl localhost:8787/health
# → {"status":"ok","backends":{"db":"sqlite ..."}, ...}
open http://localhost:8787 # 控制台UI在同一端口
```
从头到尾进行烟雾测试:
```bash
AID=$(curl -s -X POST localhost:8787/v1/agents -H 'content-type: application/json' \
-d '{"name":"hello","model":"claude-sonnet-4-6","tools":[{"type":"agent_toolset_20260401"}]}' | jq -r .id)
SID=$(curl -s -X POST localhost:8787/v1/sessions -H 'content-type: application/json' \
-d "{\"agent\":\"$AID\"}" | jq -r .id)
curl -s -X POST localhost:8787/v1/sessions/$SID/events -H 'content-type: application/json' \
-d '{"events":[{"type":"user.message","content":[{"type":"text","text":"Run: uname -a"}]}]}'
```
完整的自托管指南(沙盒模式、Postgres、BoxRun、保险箱侧车、
控制台UI、操作员注意事项): **[docs.openma.dev/self-host/overview](https://docs.openma.dev/self-host/overview/)**
---
## 快速入门:Cloudflare部署
需要[Workers付费计划](https://developers.cloudflare.com/workers/platform/pricing/)(用于Durable Objects + 容器)。
```bash
git clone https://github.com/openma-ai/open-managed-agents.git
cd open-managed-agents
pnpm install
# 本地开发(不需要CF账户)— 使用模拟器进行wrangler开发
cp .dev.vars.example .dev.vars && $EDITOR .dev.vars
# 与Docker相同的双密钥设置 — PLATFORM_ROOT_SECRET启动时必填
pnpm dev
# API → http://localhost:8787
# 控制台 → http://localhost:5173
# 部署
npx wrangler login
npx wrangler kv namespace create CONFIG_KV # 粘贴到wrangler.jsonc中
# 需要的密钥(在提示时粘贴每个)
npx wrangler secret put BETTER_AUTH_SECRET # openssl rand -hex 32
npx wrangler secret put PLATFORM_ROOT_SECRET # openssl rand -base64 32 — 备份此密钥
npx wrangler secret put API_KEY # 初始引导密钥用于REST API
# 可选 — 如果您想要一个无租户的默认LLM(否则在控制台中添加模型卡)
# npx wrangler secret put ANTHROPIC_API_KEY
npm run deploy
# → https://openma.dev (或 https://managed-agents.<your-subdomain>.workers.dev 用于个人部署)
```
部署内容:
| 组件 | 功能 |
|---|---|
| **主工作器** | API路由 — 代理、会话、环境、保险箱、内存、文件 |
| **代理工作器** | 会话DO + 托管程序 + 沙盒每个环境 |
| **KV命名空间** | 代理、环境、凭证的配置存储 |
| **R2存储桶** | 容器重启时的工作空间文件持久性 |
### 创建您的第一个代理
上面的烟雾测试适用于任何部署。对于控制台驱动的流程(模型卡、保险箱、集成),请参阅 **[docs.openma.dev/quickstart](https://docs.openma.dev/quickstart)**。最小的API等效:
```bash
BASE=http://localhost:8787 # 或您的部署URL
KEY=dev-test-key # 您设置的API_KEY
AGENT=$(curl -s $BASE/v1/agents \
-H "x-api-key: $KEY" -H "content-type: application/json" \
-d '{
"name": "Coder",
"model": "claude-sonnet-4-6",
"system": "You are a helpful coding assistant.",
"tools": [{ "type": "agent_toolset_20260401" }]
}' | jq -r .id)
SESSION=$(curl -s $BASE/v1/sessions \
-H "x-api-key: $KEY" -H "content-type: application/json" \
-d "{\"agent\":\"$AGENT\"}" | jq -r .id)
# 发送一个回合并以令牌为单位流式传输回复
curl -N -X POST $BASE/v1/sessions/$SESSION/messages \
-H "x-api-key: $KEY" -H "content-type: application/json" \
-d '{"content":"Write a Python script that fetches HN top stories"}'
```
对于长期会话,请使用`GET /v1/sessions/$SESSION/events/stream` — 连接时重放历史记录,永不关闭。
---
## 架构
**元托管程序**不是代理 — 它是运行代理的平台。它定义了代理所需的一切的稳定接口,并让代理循环保持独立:
```
┌─────────────────────────────────────────────────────────┐
│ 托管程序(大脑 — 您的代码) │
│ - 读取事件、构建上下文、调用模型 │
│ - 决定如何:缓存、压缩、工具交付 │
│ - 无状态:崩溃 → 从事件日志重建 → 恢复 │
├─────────────────────────────────────────────────────────┤
│ 元托管程序(平台 — 会话DO) │
│ - 准备好可用内容:工具、技能、历史记录 │
│ - 管理生命周期:沙盒、事件、WebSocket │
│ - 崩溃恢复、凭证隔离、使用跟踪 │
├─────────────────────────────────────────────────────────┤
│ 基础设施(Cloudflare或Node自托管) │
│ - 事件日志:Durable-Object SQLite(CF)或SQLite/Pg │
│ - 沙盒:CF容器 / 子进程 / LiteBox / E2B │
│ - 存储:KV + R2(CF)或本地文件系统(自托管) │
└─────────────────────────────────────────────────────────┘
```
**平台管理**什么可用。**托管程序决定**如何将其交付给模型。
| 平台管理 | 托管程序决定 |
|---|---|
| 事件日志持久性(SQLite) | 上下文工程(过滤、排序) |
| 沙盒生命周期(容器) | 缓存策略(缓存断点) |
| 工具注册(内置 + MCP) | 压缩策略(何时压缩) |
| WebSocket广播 | 重试策略(回退、瞬态检测) |
| 崩溃恢复 | 停止条件(最大步骤、完成信号) |
| 凭证隔离(保险箱) | 系统提示构造 |
| 内存(向量搜索) | 工具交付(一次性vs.渐进式) |
---
## 编写托管程序
默认的托管程序开箱即用。当您需要自定义行为时 — 不同的缓存、压缩、上下文工程 — 请编写自己的托管程序:
```typescript
// my-harness.ts
import { defineHarness, generateText, stepCountIs } from "@open-managed-agents/sdk";
export default defineHarness({
name: "research",
async run(ctx) {
let messages = ctx.runtime.history.getMessages();
// 您的上下文工程
messages = keepOnly(messages, ["web_search", "web_fetch"]);
// 您的缓存策略
markLastN(messages, 3, { cacheControl: "ephemeral" });
// 您的循环 — 工具、沙盒、广播由平台提供
const result = await generateText({
model: ctx.model,
system: ctx.systemPrompt,
messages,
tools: ctx.tools,
stopWhen: stepCountIs(50),
onStepFinish: async ({ text }) => {
if (text) ctx.runtime.broadcast({
type: "agent.message",
content: [{ type: "text", text }],
});
},
});
await ctx.runtime.reportUsage?.(result.usage.inputTokens, result.usage.outputTokens);
},
});
```
部署:
```bash
oma deploy --harness my-harness.ts --agent agent_abc123
```
托管程序在构建时捆绑到代理工作器中。您的代码在与SessionDO相同的隔离区中运行 — 直接访问事件日志、沙盒和WebSocket广播。没有RPC,没有序列化边界。
---
## API
与[Claude托管代理API](https://docs.anthropic.com/en/docs/agents/managed-agents)兼容。相同的端点、相同的事件类型,与现有的SDK一起工作。
<details>
<summary><strong>代理</strong> — 创建和管理代理配置</summary>
```http
POST /v1/agents # 创建代理
GET /v1/agents # 列出代理
GET /v1/agents/:id # 获取代理
PUT /v1/agents/:id # 更新代理
DELETE /v1/agents/:id # 删除代理
POST /v1/agents/:id/archive # 存档代理
GET /v1/agents/:id/versions # 版本历史
GET /v1/agents/:id/versions/:version # 获取特定版本
```
</details>
<details>
<summary><strong>环境</strong> — 沙盒执行环境</summary>
```http
POST /v1/environments # 创建环境
GET /v1/environments # 列出环境
GET /v1/environments/:id # 获取环境
PUT /v1/environments/:id # 更新环境
DELETE /v1/environments/:id # 删除环境
```
</details>
<details>
<summary><strong>会话</strong> — 运行代理对话</summary>
```http
POST /v1/sessions # 创建会话
GET /v1/sessions # 列出会话
GET /v1/sessions/:id # 获取会话
POST /v1/sessions/:id # 更新会话
DELETE /v1/sessions/:id # 删除会话
POST /v1/sessions/:id/archive # 存档会话
POST /v1/sessions/:id/events # 发送事件(用户消息)
GET /v1/sessions/:id/events # 获取事件(JSON或SSE)
GET /v1/sessions/:id/events/stream # SSE流
POST /v1/sessions/:id/resources # 附加资源
GET /v1/sessions/:id/resources # 列出资源
DELETE /v1/sessions/:id/resources/:resId # 删除资源
```
</details>
<details>
<summary><strong>保险箱</strong> — 安全的凭证存储</summary>
```http
POST /v1/vaults # 创建保险箱
POST /v1/vaults/:id/credentials # 添加凭证
GET /v1/vaults/:id/credentials # 列出(隐藏凭证)
```
</details>
<details>
<summary><strong>内存存储</strong> — 持久存储;Claude托管代理内存合约</summary>
当附加到会话时,每个存储器都挂载到沙盒中的`/mnt/memory/<store_name>/`。代理使用**标准文件工具**(bash/read/write/edit/glob/grep)读取和写入它 — 没有自定义的`memory_*`工具。
R2保存真实字节(键`<store_id>/<memory_path>`);D1保存索引 + 审计,通过R2事件通知 → Cloudflare队列 → 消费者。
```http
POST /v1/memory_stores # 创建存储器
GET /v1/memory_stores # 列出存储器
GET /v1/memory_stores/:id # 检索存储器
POST /v1/memory_stores/:id/archive # 存档(单向)
DELETE /v1/memory_stores/:id # 删除存储器 + 内存 + 版本
POST /v1/memory_stores/:id/memories # 创建/更新内存 {path, content, precondition?}
GET /v1/memory_stores/:id/memories?path_prefix=&depth=N # 列出内存(元数据)
GET /v1/memory_stores/:id/memories/:mid # 检索内存(带有内容)
POST /v1/memory_stores/:id/memories/:mid # 更新内存 {path?, content, precondition?}
DELETE /v1/memory_stores/:id/memories/:mid # 删除内存
GET /v1/memory_stores/:id/memory_versions?memory_id= # 审计历史(最新)
GET /1/memory_stores/:id/memory_versions/:ver_id # 单个版本(带有快照内容)
POST /v1/memory_stores/:id/memory_versions/:ver_id/redact # 遮蔽先前版本(拒绝活动头部)
```
CAS通过`precondition: { type: "content_sha256", content_sha256 }`。每个内存容量为100KB。每个内存保留30天,最近的版本始终保留。回滚=检索版本并将其内容写入新的内存修订版(无需特殊端点)。
CLI:
```bash
oma memory stores create "用户偏好"
oma memory write <store-id> /preferences/formatting.md --content "Always use tabs."
oma memory ls <store-id> --prefix /preferences/
oma memory versions <store-id> --memory-id <mem-id>
```
</details>
<details>
<summary><strong>文件和技能</strong></summary>
```http
POST /v1/files # 上传文件
GET /v1/files/:id/content # 下载文件
POST /v1/skills # 创建技能
GET /v1/skills # 列出技能
```
</details>
---
## 内置工具
`agent_toolset_20260401` 提供:
| 工具 | 描述 |
|---|---|
| `bash` | 在沙盒中执行命令 |
| `read` | 从沙盒文件系统读取文件 |
| `write` | 写入/创建文件(自动创建目录) |
| `edit` | 文件中的外科手术式字符串替换 |
| `glob` | 查找匹配模式的文件 |
| `grep` | 使用正则表达式搜索文件内容 |
| `web_fetch` | URL → 使用Workers AI的Markdown;如果设置了`agent.aux_model`,则自动总结,否则保存到 `/workspace/.web/` |
| `web_search` | 通过Tavily API进行网络搜索(需要 `TAVILY_API_KEY`) |
| `schedule` / `cancel_schedule` / `list_schedules` | Cron样式的自唤醒,用于长时间运行的代理 |
| `browser`(需选择加入) | 无头浏览器会话 - 导航、点击、截图。 通过 `tools: [{ name: "browser", enabled: true }]` 选择加入,以便默认工具列表提示代理采用更便宜的 `web_fetch` |
派生工具根据会话配置自动生成:
| 工具 | 来源 |
|---|---|
| `call_agent_*` | 可调用代理(多代理委托) |
| `mcp__<server>__<tool>` | MCP服务器(双下划线是实际分隔符) |
(内存存储 **不** 添加特制工具 — 代理通过上述标准文件工具访问它们,如文件系统挂载在 `/mnt/memory/<store_name>/`)
---
## MCP 服务器
OMA注册任何[模型上下文协议](https://modelcontextprotocol.io)服务器附加到代理。每个上游工具以`mcp__<server>__<tool>`公开给模型(双下划线 — 复制确切名称)。每个代理最多可容纳20个服务器。
| 传输方式 | 何时使用 | 如何 |
|---|---|---|
| HTTP / SSE | 托管的MCP服务器(Linear、GitHub Copilot、Notion等) | `{"type":"url","url":"https://mcp.linear.app/mcp"}` |
| stdio | npm / PyPI MCP包,无托管端点 | `{"type":"stdio","command":"uvx","args":[...],"port":8765}` — OMA在沙盒容器内启动,与`127.0.0.1:port/sse`通信 |
凭证从不进入沙盒;输出解析器根据主机匹配并在转发时注入。
| 认证模式 | 配置为 | 刷新 |
|---|---|---|
| 无 | 无`authorization_token`,无匹配保险箱凭证 | n/a |
| 内联Bearer | 服务器条目上的`"authorization_token": "..."` | 否 |
| 保险箱静态Bearer | 会话保险箱具有静态Bearer凭证,其`mcp_server_url`匹配 | 否 |
| 保险箱OAuth | 会话保险箱具有`mcp_oauth`凭证(带有`refresh_token` + `token_endpoint`) | 是 - 在401 **和** 403(Airtable/Asana/Sentry使用403表示过期令牌)时,CAS写入新的令牌到D1,重试一次 |
```bash
# 部署
```
# 服务器连接到代理(不是会话)
curl -X PUT $BASE/v1/agents/$AGENT -H "x-api-key: $KEY" -H "content-type: application/json" \
-d '{"mcp_servers":[{"name":"linear","type":"url","url":"https://mcp.linear.app/mcp"}]}'
# 通过 Vault 绑定 OAuth 凭证
oma connect linear --vault $VAULT_ID
```
工具发现时间限制为每台服务器 15 秒;如果有一台服务器出现问题,则记录并跳过,其他服务器继续运行。完整设计:[docs.openma.dev/build/vault-and-mcp](https://docs.openma.dev/build/vault-and-mcp/).
---
## 技能
技能是指一份 `SKILL.md` 文件和相关引用文件(模板、模式、示例)。在会话开始时,平台会将技能目录下的所有内容挂载到 `/home/user/.skills/{name}/` 目录,并在沙箱中**直接内联 SKILL.md 文件的内容** —— 没有延迟读取,也没有后续的 `read` 工具调用。格式兼容 Anthropic 的 [Claude Code 技能](https://github.com/anthropics/skills).
创建技能(JSON;文件内联):
```http
POST /v1/skills
{
"files": [
{ "filename": "SKILL.md", "content": "---\nname: invoice-parser\ndescription: Parse supplier invoices.\n---\n\n# Steps\n1. ..." },
{ "filename": "schema.json", "content": "{...}" }
]
}
```
对于包含二进制文件的技能,请使用 `POST /v1/skills/upload` multipart 表单上传 `file=<my-skill.zip>`.
将技能附加到代理,使用**对象形式** —— 纯字符串数组不会绑定:
```json
{ "skills": [{ "skill_id": "skill_abc123", "type": "custom" }] }
```
代理的系统提示在会话开始时接收:
```text
<source name="skill:skill_abc123">
<skill name="invoice-parser">
{完整的 SKILL.md 内容}
</skill>
</source>
```
文件出现在 `/home/user/.skills/invoice-parser/SKILL.md` 等目录中。
四个内置技能可以直接绑定(无需上传):`xlsx`、`pdf`、`docx`、`pptx`. 可以使用 `{"skill_id":"builtin_pdf","type":"anthropic"}` 引用它们。
---
## Vaults 和出站凭证
**工具永远不会看到您的令牌。** 当沙箱发出 HTTP 请求时,出站解析器 —— 自托管的 `oma-vault` 侧车(mockttp HTTPS 代理,带有受信任的自签名 CA),Cloudflare 上的代理工作器的 `outboundByHost` 拦截器 —— 将请求主机名与会话的 Vaults 匹配,**剥离任何传入的 `Authorization`/`x-api-key`/`x-goog-api-key`**,注入真正的凭证,并转发。提示注入的代理没有任何内容可以泄露;`env | grep TOKEN` 在沙箱中返回空值。
```bash
# 创建一个 Vault 并添加一个静态的 bearer 绑定到 api.github.com
VID=$(curl -sX POST $BASE/v1/vaults -H "x-api-key: $KEY" \
-d '{"name":"github-prod"}' | jq -r .id)
curl -sX POST $BASE/v1/vaults/$VID/credentials -H "x-api-key: $KEY" -d '{
"display_name": "gh-pat",
"auth": {
"type": "static_bearer",
"token": "ghp_xxx",
"mcp_server_url": "https://api.github.com"
}
}'
# 在创建会话时绑定
curl -sX POST $BASE/v1/sessions -H "x-api-key: $KEY" \
-d "{\"agent\":\"$AGENT\",\"vault_ids\":[\"$VID\"]}"
# 在沙箱中:curl https://api.github.com/user → 200,在网络层注入 Authorization
```
三种凭证类型共享一个解析器:
| 类型 | 匹配方式 | 刷新 |
|---|---|---|
| `static_bearer` | 请求主机名与 `mcp_server_url` 匹配 | 永不 |
| `mcp_oauth` | 请求主机名与 `mcp_server_url` 匹配 | 在 401 / 403 时通过 `token_endpoint`,CAS 将新令牌写入 D1 |
| `cap_cli` | 沙箱 CLI 调用与 cap 注册表中的 `cli_id` 匹配(`gh`、`glab`、`aws` 等) | 每次 CLI 调用 |
每个 Vault 最多 20 个凭证。每次转发都会发出结构化的 `op:"mcp_proxy.forward"` 日志。完整设计:[`docs/mcp-credential-architecture.md`](docs/mcp-credential-architecture.md),[docs.openma.dev/build/vault-and-mcp](https://docs.openma.dev/build/vault-and-mcp/).
---
## 集成
将代理发布到第三方工具中,使其作为真正的队友出现 —— 被分配、提及、回复,就像其他用户一样。
### Linear
使代理成为 Linear 工作空间的成员,拥有自己的身份、头像和 `@autocomplete` 插槽。代理出现在分配下拉列表中,收到 `@mentions`,在 Agent 面板中回复,并将状态推回至正在处理的议题。
两种安装方式:
| 类型 | 何时选择 | 设置 |
|---|---|---|
| **`personal_token`**(PAT) | 单个工作空间,最快捷的方式,无 OAuth 应用 | `oma linear install-pat --workspace <slug> --pat <linear-pat>` |
| **`dedicated`**(OAuth 应用) | 多个工作空间,适当的机器人身份,OAuth 刷新 | 控制台 **Integrations → Linear → Publish agent** (向导问题每个发布回调 + webhook URL 粘贴到您自己的 Linear OAuth 应用中,网址为 `linear.app/settings/api`) |
完整的代理端攻略(何时询问人类,如何提供浏览器自动化,到底应该在 Linear 的表单中粘贴什么)位于 [`skills/openma/integrations-linear.md`](skills/openma/integrations-linear.md)。
PAT 模式下的自动驾驶 —— 让机器人通过标签/状态/项目挑选未分配的问题:
```bash
oma linear rules create <pub-id> --label triage --state Backlog --project "Inbox"
oma linear rules list <pub-id>
oma linear rules delete <rule-id>
```
查看/管理:
```bash
oma linear list # 工作空间
oma linear pubs <installation-id> # 发布(状态=live,persona,caps)
oma linear get <pub-id> # 单个发布
oma linear update <pub-id> --caps issue.read,comment.write,issue.update,…
oma linear unpublish <pub-id>
```
工作原理:
| 部分 | 功能 |
|---|---|
| **每个发布的身份** | `dedicated` 注册每个代理的 Linear OAuth 应用;`personal_token` 共享人类的 PAT(无注册应用) |
| **入站 webhook** | Linear 事件成为会话中的用户消息 —— 分配、`@mention`、评论提及、新评论在活动线程中,**代理面板**(`agentSessionCreated` / `agentSessionPrompted`,`commentReply` 用于线程继续) |
| **出站 MCP** | 代理通过 `mcp.linear.app/mcp` 使用自己的 bearer(PAT 或 OAuth 刷新)讲话,因此写入被归因于角色 |
| **能力门控** | 每个发布的允许列表(问题/评论/标签/分配/分类)限制代理可以执行的操作 |
Linear 集成位于 `packages/linear/`(提供者逻辑、webhook 签名、MCP 连线),在 `apps/integrations/src/routes/linear/publications.ts` 中具有薄的 CF 包装器。
### GitHub
给予代理自己的 GitHub 应用,拥有真正的机器人身份 —— 可以在问题上分配,在 PR 上请求审查,在其自己的 `@<slug>[bot]` 处理下发布评论。每个代理都是 GitHub 上一个单独的应用(每个发布,不是共享的市场机器人),因此凭证和审计轨迹保持隔离。
```bash
# (1) 控制台 —— 人类点击向导
Integrations → GitHub → Publish agent
# (2) CLI —— 代理驱动 openma 代表用户
oma github bind <agent-id> --env <env-id> # → 打开一次性 GitHub 应用清单流程
oma github handoff <form-token> # 替代:组织管理员的 7 天 URL 以完成
oma github list
oma github pubs <installation-id>
oma github update <pub-id> --caps pr.read,pr.review.write,issue.comment.write,…
oma github unpublish <pub-id>
```
`bind` 返回一个 `manifestStartUrl`;打开它会自动将应用清单发布到 `github.com/settings/apps/new`,并将重定向 URL + webhook URL + 推荐的权限一起内嵌。在确认后,GitHub 重定向到“安装到组织”,发布变为 `live`. 手动备用:`oma github submit <form-token> --app-id … --private-key-file … --webhook-secret …` 如果您手动注册了应用。
**参与基于标签。** 在安装 OMA 时,它会在每个选定的存储库中自动创建一个标签(默认:小写角色名称)。将标签添加到任何问题/PR 以吸引机器人在该线程上的每个后续活动;删除标签以静音。 在正文或评论中提及 `@<slug>[bot]` 是备用路径(GitHub 的 `@` 自动补全功能不包括 Bot 帐户,因此它是纯文本)。
工作原理:
| 部分 | 功能 |
|---|---|
| **每个发布的应用程序** | 每个代理通过清单流程注册自己的 GitHub 应用程序;凭证存储每个发布加密 |
| **入站 webhook** | `issues`、`issue_comment`、`pull_request`、`pull_request_review`、`pull_request_review_comment` 成为会话中的用户消息(每个 `<repo>#<num>`) |
| **出站 MCP** | 代理使用安装令牌与 GitHub 的托管 MCP 在 `api.githubcopilot.com/mcp/` 进行通信;同一个令牌也作为 `GITHUB_TOKEN` 注入沙箱 `gh` / `git` |
| **令牌轮换** | 1 小时安装令牌通过应用 JWT 在每个 webhook 分发时自动刷新 |
| **能力门控** | 每个发布的允许列表;破坏性操作(`pr.merge`、`repo.branch.delete`、`workflow.dispatch`、`release.create`、`*.delete`)需要明确选择 |
GitHub 集成位于 `packages/github/`,在 `apps/integrations/src/routes/github/` 中具有薄的 CF 包装器。
### Slack
将代理发布到 Slack 工作空间中,作为专用机器人 —— 在频道中 `@mention`、在线程中回复、加入直接消息、托管 AI 助手面板。每个频道会话:一个运行会话,每个 `(publication, channel)`,该频道中的所有事件汇聚到同一个会话 ID。
```bash
# (1) 控制台 —— 人类点击向导
Integrations → Slack → Publish agent # ↑ 打开 api.slack.com 并带有预填充的清单
# (2) CLI —— 代理驱动 openma 代表用户
oma slack publish <agent-id> --env <env-id> # → 返回 manifestLaunchUrl + formToken(60 分钟 TTL)
oma slack submit <form-token> --client-id … --client-secret … --signing-secret …
oma slack handoff <form-token> # 替代:工作空间管理员的 7 天可共享 URL
oma slack list
oma slack pubs <installation-id>
oma slack update <pub-id> --caps message.write,thread.reply,reaction.add,…
oma slack unpublish <pub-id>
```
完整的代理端攻略(清单流程注意事项、`GATEWAY_ORIGIN` HTTPS 要求、在哪里粘贴什么,MCP 切换探测)位于 [`skills/openma/integrations-slack.md`](skills/openma/integrations-slack.md)。
工作原理:
| 部分 | 功能 |
|---|---|
| **每个发布的应用程序** | 每个代理通过“从清单创建”URL流程注册为其自己的专用 Slack 应用程序 —— 自己的客户端 ID、签名密钥、机器人用户;没有共享的市场应用程序 |
| **入站 webhook** | `app_mention` / 直接消息 / 线程回复 → `direct_invocation` 信号;顶级频道帖子 → 延迟 `channel_scan_armed`(90 秒窗口);对机器人作者的消息的反应 → `reaction_on_bot_message`;`member_joined`/`member_left_channel` 对机器人 → `joined_channel` / `session_closed`;`channel_archive` / `channel_unarchive` → 关闭 / 重新打开 |
| **双令牌出站** | OAuth v2 产生机器人(`xoxb-`)和用户(`xoxp-`)令牌。`xoxp-` vault 绑定到 `mcp.slack.com/mcp` 以输入 `mcp__slack__*` 工具(搜索、历史记录、画布);`xoxb-` vault 绑定到 `slack.com/api` 以进行 `chat.postMessage`、反应等。机器人回复默认在线程中 |
| **能力门控** | 每个发布的允许列表(`message.read/write/update/delete`、`thread.reply`、`reaction.add/remove`、`user.read`、`search.read`、`canvas.write`) |
| **可恢复的安装** | 发布优先 —— 从一分钟开始,行存在,回调 + webhook URL 内置于清单中。中途失败保持可恢复,从控制台(`pending_setup` → `credentials_filled` → `awaiting_install` → `live`) |
Slack 集成位于 `packages/slack/`,在 `apps/integrations/src/routes/slack/` 中具有薄的 CF 包装器。
**操作员设置:** 集成网关需要 `GATEWAY_ORIGIN` 指向可公开访问的 HTTPS 主机 —— Slack 验证 OAuth 重定向 URL 和事件请求 URL,然后才完成安装。
---
## 项目结构
```
open-managed-agents/
├── apps/
│ ├── main/ # API 工作器(Cloudflare)—— Hono 路由、身份验证、速率限制
│ ├── main-node/ # API 工作器(Node 自托管)—— 相同的 Hono/Node 路由
│ ├── agent/ # 代理工作器 —— SessionDO + 套接字 + 沙箱
│ ├── integrations/ # 集成网关 —— Linear / GitHub / Slack OAuth + webhooks
│ ├── oma-vault/ # Vault 侧车 —— 出站身份验证头注入(每主机秘密)
│ ├── console/ # Web 仪表盘 —— React + Vite + Tailwind v4
│ ├── docs/ # 文档站点(Astro Starlight)—— 发布到 docs.openma.dev
│ └── web/ # 营销站点(Astro)—— 发布到 openma.dev
├── packages/
│ ├── cli/ # `oma` CLI —— 代理 / 会话 / 集成命令
│ ├── sdk/ # 套接字 SDK —— defineHarness, generateText 帮助函数
│ ├── api-types/ # 共享 TypeScript 类型(配置模式、事件)
│ ├── http-routes/ # 公共 REST 路由定义(共享 main + main-node)
│ ├── session-runtime/ # 套接字运行时 —— 事件日志、广播、恢复
│ ├── sandbox/ # 沙箱适配器(子进程 / litebox / daytona / e2b / boxrun)
│ ├── credentials-store/ # 加密凭证(AES-GCM 在 PLATFORM_ROOT_SECRET 下)
│ ├── model-cards-store/ # 加密模型卡 API 密钥
│ ├── vaults-store/ # Vault 定义 + 出站身份验证线路
│ ├── linear/ github/ slack/ # 提供者逻辑(OAuth、webhook 签名、MCP 线路)
│ ├── integrations-core/ # 提供者中立的持久性接口
│ └── integrations-adapters-{cf,node}/ # D1 / KV / Workers + Postgres / FS 实现
├── docs/ # 内部设计 RFC(不是面向用户的站点)
├── test/ # 单元 + 集成测试
└── scripts/ # 部署 + 维护脚本
```
---
## 配置
引导和静态安全相关的变量:
| 变量 | 必需 | 描述 |
|---|---|---|
| `PLATFORM_ROOT_SECRET` | **是** | `credentials.auth`、`model_cards.api_key_cipher` 和集成令牌的 AES-GCM 密钥。工作器在没有它的情况下拒绝启动。**备份它** —— 丢失它将使每个加密的行都不可读。使用 `openssl rand -base64 32` 生成。 |
| `BETTER_AUTH_SECRET` | **是** (生产环境) | better-auth 会话签名密钥。如果没有,会话在重启后不会存活。使用 `openssl rand -hex 32` 生成。 |
| `API_KEY` | 是 | 开发环境/首次运行的 REST API 引导密钥。一旦控制台启动,请从那里使用租户 minted 的 API 密钥。 |
| `INTEGRATIONS_INTERNAL_SECRET` | 是(如果运行 `apps/integrations`) | `apps/main` 和 `apps/integrations` 之间的共享密钥。 |
| `ANTHROPIC_API_KEY` | 否 | 如果租户未添加模型卡,则用作备用 LLM 凭证。**在生产环境中,从控制台为每个租户添加模型卡** —— 密钥在 `PLATFORM_ROOT_SECRET` 下加密存储,按租户范围限定,支持轮换,无需重新部署。如果您轮换 `PLATFORM_ROOT_SECRET`,请重新运行反向填充脚本。 |
| `ANTHROPIC_BASE_URL` | 否 | Anthropic 兼容代理的重写。 |
| `PUBLIC_BASE_URL` | 否(开发环境)/ 是(生产环境) | Cookie 域 + OAuth 重定向基址。默认为 `*` 受信任来源 —— 仅适用于本地开发。 |
| `SANDBOX_PROVIDER` | 否 | `subprocess`(默认,无隔离),`litebox`(Firecracker),`daytona`,`e2b` 或 `boxrun`. 为不受信任的代理使用隔离的后端。 |
| `TAVILY_API_KEY` | 否 | `web_search` 内置工具的后端。 |
完整列表(集成 OAuth 凭证、Postgres URL、沙箱可调参数、内存桶配置、Google 登录等)—— 请参阅 **[docs.openma.dev/reference/configuration](https://docs.openma.dev/reference/configuration/)** 和 `.env.example` / `.dev.vars.example`.
---
## 模型卡
每个租户的 LLM 凭证。代理通过设置 `agent.model = "<model_id>"` 来引用一个模型卡 —— 工作器查找模型卡并使用其 `api_key`、`base_url` 和 `headers` 对出站请求进行签名。这是全局 `ANTHROPIC_API_KEY` 环境变量的规范替代。
提供者(线路标签 → 请求形状):
| 标签 | 形状 | 典型用途 |
|---|---|---|
| `ant` | Anthropic `/v1/messages` | `api.anthropic.com` 上的 Claude |
| `ant-compatible` | Anthropic 形状,自定义 `base_url` | Bedrock 代理,自我托管的 Anthropic 兼容 |
| `oai` | OpenAI `/v1/chat/completions` | OpenAI,Azure OpenAI |
| `oai-compatible` | OpenAI 形状,自定义 `base_url` | vLLM,OpenRouter,Groq 等 |
从 **控制台 → 模型卡** 添加一个,或通过 CLI:
```bash
oma models create \
--model-id claude-prod \
--provider ant \
--model claude-sonnet-4-6 \
--api-key sk-ant-...
oma models list
```
REST:`POST /v1/model_cards`,`GET /v1/model_cards`,`POST /v1/model_cards/:id`(轮换),`DELETE /v1/model_cards/:id`. 创建运行 6 秒探测,以便坏密钥能大声报错,而不是在第一次转折时报错。
密钥在 `PLATFORM_ROOT_SECRET` 下以 AES-256-GCM 加密存储(标签 `model.cards.keys`);列表响应仅显示最后 4 位预览。如果您轮换 `PLATFORM_ROOT_SECRET`,请重新运行反向填充脚本。
---
## 测试
```bash
npm test # 单元 + 集成套件
npm run typecheck # 零错误
```
---
## 文档
用户文档站点位于 [`apps/docs`](apps/docs/)(Astro Starlight),发布到 **[docs.openma.dev](https://docs.openma.dev)**.
```bash
pnpm dev:docs # 本地预览,网址为 http://localhost:4321
pnpm build:docs # 静态构建到 apps/docs/dist/
pnpm deploy:docs # 构建 + wrangler 部署(Cloudflare Worker 静态资产)
```
`docs/` 文件夹在仓库根目录下包含 **内部设计 RFC** —— 不是面向用户的站点。
---
## 贡献
1. 复刻仓库
2. 创建功能分支 (`git checkout -b feat/amazing-feature`)
3. 运行测试 (`npm test && npm run typecheck`)
4. 提交更改
5. 打开拉取请求
---
## 许可证
[Apache 2.0](LICENSE)
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器