Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,13 @@
- [ ] 实现外部消息到 `model.Message` / `runner.Runner.Run` 的转换
- [ ] 实现 Runner Event 到文本、流式消息和卡片消息的转换
- [ ] 接入企业微信或微信相关通道
- [ ] 再接入至少一种不同 IM 通道,例如 Telegram
- [x] 接入 Telegram long polling 文本通道(Issue #31;单 Binding、Gateway Dispatch、进程内幂等)
- [ ] 接入 Telegram webhook、媒体/rich update 或其他 IM 通道
- [ ] 实现 webhook 验签、账号与租户绑定、用户身份映射
- [ ] 使用 `tenant + channel + message_id` 实现幂等去重和缓存回复
- [x] 实现单聊/群聊 Session ID 规则及跨群、跨租户隔离
- [ ] 处理消息分段、频率限制、异步回复、图片/文件、撤回和失败重试
- [x] Telegram 文本回复分段、重复投递和论坛线程路由
- [ ] 处理频率限制、异步回复、图片/文件、撤回和失败重试
- [ ] 增加重复投递、乱序、验签失败和跨租户访问测试

### 治理、安全与可观测性
Expand Down Expand Up @@ -214,21 +216,24 @@
- [x] 列出至少 8 个生产风险及对应缓解措施
- [x] 持续标注可直接复用的 tRPC-Agent-Go 能力与平台新增模块边界

> Issue #24 只完成架构、数据模型和运维文档;当前 PR 只确认下方部分 Gateway/API、进程内
> 保护和 Binding-aware identity 条目。完整 Channel/Gateway 生产能力、真实 IM/Storage
> Adapter、迁移工具和生产告警仍未实现。
> Issue #24 只完成架构、数据模型和运维文档;当前仓库另外交付了 Issue #28 的 Gateway/API
> 阶段能力和 Issue #31 的 Telegram long polling 文本 Adapter。完整 WeCom/Telegram webhook、
> rich update、持久化消息能力、迁移工具和生产告警仍未实现。

## 当前 PR 实现记录(不改变原验收要求)

> 以下内容仅索引 PR #29 当前 head 的实现和测试范围,不替代、收窄或修改上方原验收要求;
> 以下内容仅索引当前实现 PR head 和测试范围,不替代、收窄或修改上方原验收要求;
> 上方勾选仅表示该原条目已有完整证据。PR 尚未合并,当前阶段实现也不等同于全部原验收项已完成。

- `trpcservice/gateway/auth.go` 的 proof-bearing API 身份校验对应
`trpcservice/gateway/auth_test.go`;`resolver.go` 对应 `resolver_test.go`。
- 当前 PR 包含 Gateway、HTTP/SSE、进程内限流/幂等和 Channel trusted-principal 的阶段性代码,
具体边界以 `docs/docs/gateway.md` 为准。
- Issue #31 的 `trpcservice/channels/telegram` 提供单 Binding、`getMe` 身份校验、普通文本
long polling、Gateway Dispatch、进程内幂等和脱敏分段回复;具体边界以
`docs/docs/telegram.md` 为准。
- Issue #26 的 fake candidate resolver/verifier 与 proof-bearing routing 边界有独立测试,
但这不代表真实 IM Adapter、生产 webhook 或持久化能力已满足 README 原验收要求。
但这不代表 WeCom/Telegram webhook、媒体能力或持久化消息能力已满足 README 原验收要求。

## 代码目录

Expand Down
9 changes: 5 additions & 4 deletions docs/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Model Profile 和 Backend Profile,构造一个带版本、摘要和租户边
| Admin API | 租户、App、Profile、Binding 的管理、发布、回滚、审计入口 | Admin API → 控制面 Repository | 控制面模型已实现;HTTP API 为平台新增 |
| Config/Registry | 版本校验、同租户引用、Factory/Storage 注册和缓存失效 | 控制面 → Gateway/Worker 快照 | Execution Plan/快照边界已有;Issue #28 交付进程内 Runner Registry,分布式失效仍为后续工作 |
| Secret Resolver | 公开入站用不含 `tenant_id` 的 `CandidateBindingContext` 返回一次性验签 handle;验签后按固定 Tenant/Profile 作用域解析执行 Secret | Adapter/Gateway → Resolver;Resolver 不反向选租户 | Resolver 接口已有;candidate-scoped API、KMS/Secret Manager 为平台新增 |
| Channel Adapter | 解析供应商回调、校验协议、验签/解密、转换统一消息和出站回复 | IM ↔ Adapter ↔ Gateway | 包占位;真实 WeCom/Telegram Adapter 为平台新增 |
| Channel Adapter | 解析供应商回调、校验协议、验签/解密、转换统一消息和出站回复 | IM ↔ Adapter ↔ Gateway | Issue #31 已交付 Telegram long polling 普通文本;WeComTelegram webhook 和其他生产 Adapter 仍为平台新增 |
| Agent Gateway | 限流、候选绑定路由、可信租户建立、幂等记录、快照装配和队列投递 | Adapter → Gateway → Worker/Queue | Issue #28 文档契约;实现阶段交付 HTTP/API 与 Channel principal 入口,真实队列仍为后续工作 |
| Agent Worker | 消费固定执行计划,调用 Runner、Model、Tool 和 Storage,生成回复事件 | Gateway/Queue → Worker → 上游能力 | 最小 Runner spine 已有;Issue #28 交付进程内 Dispatch/HTTP,独立 Worker 为后续工作 |
| Runner/Agent/Model | Agent 编排、模型调用、Tool/MCP、Event 和 context 取消 | Worker → tRPC-Agent-Go | 直接复用;当前已有最小 LLMAgent/Runner 装配 |
Expand Down Expand Up @@ -586,6 +586,7 @@ Production architecture design (本页)
└── persistent repositories and production adapters
```

PR #25 的文档交付已完成,Issue #26 的 Channel Binding 领域与可信路由已实现;在 Issue #28
代码验收完成前,README 不勾选 Gateway 的持续服务、Registry 或 HTTP/SSE 能力,避免文档
进度掩盖工程边界。
PR #25 的文档交付已完成,Issue #26 的 Channel Binding 领域与可信路由已实现,Issue #28
交付了 Gateway 的进程内执行链,Issue #31 交付了 Telegram long polling 普通文本 Adapter;
WeCom/Telegram webhook、rich update、持久化和分布式能力仍按后续 Issue 交付,避免文档进度
掩盖工程边界。
10 changes: 6 additions & 4 deletions docs/docs/channel-binding.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Channel Binding 与可信入站路由

> 本页是 Issue #26 的实现契约。它先固定控制面模型、候选路由和可信边界,随后由
> `trpcservice/channels` 的领域模型、InMemory Repository 和 fake verifier 实现。文中没有
> 把真实企业微信/Telegram 适配器或 HTTP Gateway 误写成当前交付物。
> `trpcservice/channels` 的领域模型、InMemory Repository 和 fake verifier 实现。Telegram
> long polling 的适配器契约见 [Telegram 长轮询 Adapter](telegram.md);本页仍只定义控制面和
> trusted routing,不把协议运行时细节混入 Binding 领域模型。

## 目标与边界

Expand All @@ -22,8 +23,9 @@ Channel Binding 把一个外部 IM 账号绑定到同一租户的 Agent App。
和无拼接碰撞的单聊/群聊/线程 Runner identity;
- 使用 fake resolver/verifier 的离线集成测试。

明确不在范围内:真实供应商 SDK、企业微信 AES 解密、Telegram Bot API、HTTP Gateway、KMS/
Vault、PostgreSQL migration、消息去重/回复 Outbox、队列和生产审计持久化。
明确不在范围内:真实供应商 SDK、企业微信 AES 解密、Telegram webhook、HTTP Gateway、KMS/
Vault、PostgreSQL migration、消息去重/回复 Outbox、队列和生产审计持久化。Telegram long
polling 运行时契约见 [Telegram 长轮询 Adapter](telegram.md),不属于本 Binding 领域模型。

## 控制面模型

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ PR #25 的架构验收继续约束组件职责:Channel Adapter 负责协议适
#26 的 `VerifiedBinding` / `RoutingTarget` 是 Channel principal 的唯一可信来源;本
Issue 不重新解释请求 body/header,也不从其中拼出租户。

本 Issue 明确不实现真实 WeCom/Telegram webhook、OAuth/OIDC、KMS/Vault、Redis/SQL
本 Issue 明确不实现真实 WeCom/Telegram webhook(Telegram long polling 由 Issue #31 单独交付)、OAuth/OIDC、KMS/Vault、Redis/SQL
持久化、生产队列、Admin API、Graph/Chain/Parallel/Cycle 全量运行时或多节点一致性。
InMemory 限流、幂等、Registry 和 Session 只证明单进程契约,不能宣称跨节点生产语义。

Expand Down
3 changes: 3 additions & 0 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
- [Gateway、Execution Plan 与 HTTP/SSE](gateway.md):对齐 PR #25 架构验收与 Issue #26
可信主体,定义 Issue #28 的 Resolver、Runner Registry、Dispatch、普通/SSE API、
限流、幂等和服务生命周期契约。
- [Telegram 长轮询 Adapter](telegram.md):Issue #31 的文档先行契约,固定单 Binding、Bot
身份校验、普通文本映射、Dispatch 聚合回复和生命周期边界。

## 快速开始

Expand All @@ -42,6 +44,7 @@ cd trpc-agent-service
- [架构设计](architecture.md) — 组件拓扑、可信路由、消息链路、数据同步和多后端迁移
- [数据模型](data-model.md) — 核心表结构、Session/Event/Memory/Summary/Audit 和租户约束
- [Channel Binding](channel-binding.md) — 租户级通道绑定、候选发现与可信入站路由
- [Telegram 长轮询 Adapter](telegram.md) — 单 Binding Telegram long polling、文本映射与安全边界
- [Gateway、Execution Plan 与 HTTP/SSE](gateway.md) — 可信主体、固定执行计划、Runner Registry、
Dispatch、健康检查、优雅停机和普通/流式 API
- [运维方案](ops.md) — 发布灰度、监控审计、故障恢复、容量模型和生产风险清单
Expand Down
150 changes: 150 additions & 0 deletions docs/docs/telegram.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Telegram 长轮询 Channel Adapter

> Issue #31 的实现契约与状态记录。Telegram long polling 普通文本路径已实现并由单元、race
> 与全仓验证覆盖;Webhook、媒体/rich update、持久化和跨节点能力仍明确不在本 Issue 范围内。

## 1. 交付边界

Telegram 适配器是一个绑定级别的协议入口,不创建第二套租户或 Runner 路由。一个适配器实例
只代表一个 active Telegram Binding,运行链路固定为:

```text
Telegram getUpdates
-> Update.Message 校验和规范化
-> 已验证的 channels.RoutingTarget
-> gateway.Channel Principal
-> gateway.DispatchService
-> 完整消费脱敏 DispatchEvent
-> 聚合并分段
-> Telegram sendMessage
```

本 Issue 只实现 long polling 和普通文本消息。Webhook、媒体、命令、回调、持久化 outbox、跨
节点 polling ownership 与分布式幂等保留给后续 Issue;`WebhookPath` 仍是绑定配置中的预留字段,
不能被本适配器读取为运行模式。

## 2. 适配器边界与构造

实现包位于 `trpcservice/channels/telegram`。公开构造配置的语义如下:

| 配置 | 约束 |
| --- | --- |
| `BotToken` | 仅为运行时输入,不能写入 Binding、Plan、缓存键、日志、trace 或错误;构造成功后只由 SDK client 持有 |
| `Target` | 必须由现有 trusted boundary 创建并通过 `Validate()`;必须是 active Telegram Binding 的 RoutingTarget |
| `Dispatcher` | 使用现有 `gateway.DispatchService`;适配器不能直接调用 Runner |
| `Idempotency` | 可注入现有 `gateway.IdempotencyStore`;未注入时由适配器拥有一个进程内实例,不宣称跨进程保证 |
| `APIBaseURL` | 可选 HTTPS origin,仅用于 SDK Bot API;不从 Telegram update 或 webhook 字段读取 |
| `HTTPClient` | 可选的 SDK HTTP client,测试使用 fake/`httptest`,不要求真实凭据 |
| `PollTimeout` | 可选 long-poll timeout;采用 SDK 默认值时不自行覆盖 |
| `Workers` | 零值为 1;大于 1 必须由调用方显式配置,并由 SDK 同步处理 handler 生命周期 |
| `ErrorHook` | 只接收稳定的适配器错误类别,不接收 SDK/provider 原始错误、token 或 endpoint 凭据 |
| `Factory` | 注入式 Bot factory;生产实现才依赖 `github.com/go-telegram/bot`,测试不创建网络 client |

构造函数接收 Context,先创建带默认 update handler 的 client,再调用 `getMe`,把返回的 Bot user
ID 规范化为十进制字符串并与 `Target.ProviderAccountID` 精确比较。创建失败、`getMe` 失败或身份
不一致都 fail closed;在身份通过前不得处理任何 update。

适配器内部只保存由 `gateway.NewChannelPrincipal(Target)` 产生的 principal。Telegram update
不包含并且不能覆盖 tenant、binding、app、model、profile 或 routing hint;显示名、username 和
标题只可作为未来展示元数据,不能参与认证、session 或 Runner identity。

Bot factory 对 SDK 使用以下固定策略:

- `WithSkipGetMe`,由适配器在自己的 Context 中执行并校验 `getMe`;
- `WithDefaultHandler` 指向适配器的单 update handler;
- `WithNotAsyncHandlers`,使 `Run(ctx)` 结束时不会遗留 SDK 自己创建的异步 handler goroutine;
- `WithWorkers` 采用已验证的 worker 数量;
- 可选地设置 server URL、HTTP client 和 polling timeout;
- SDK polling error 只转换成稳定的 `polling` hook 事件,不能直接透传或记录原始错误。

## 3. 入站规范化与幂等

第一版只接受 `Update.Message` 中的普通文本:

| Telegram 字段 | Gateway 字段 | 规则 |
| --- | --- | --- |
| `Message.Text` | `InboundMessage.Content` | 必须非空;继续交给 `InboundMessage.Normalize()` 做 trim/长度校验 |
| `Message.From.ID` | `ExternalUserID` | 必须存在且非零;不能使用 username/姓名 |
| private `Message.Chat.ID` | `ConversationDirect` + `ExternalPeerID` | chat ID 是稳定会话身份 |
| group/supergroup `Message.Chat.ID` | `ConversationGroup` + `ExternalChatID` | chat ID 进入群会话身份 |
| `Message.MessageThreadID` | `ExternalThreadID` | 大于零时保留;发送回复时原样作为 forum thread |
| `Update.ID` + trusted `BindingID` | `ExternalMessageID` / `RequestID` | 使用长度前缀编码生成稳定、无碰撞的 binding-aware ID |

编辑消息、channel post、callback/inline、service update、无 sender/chat/text、未知 chat 类型和
媒体-only update 都以稳定的非敏感原因忽略或拒绝,且不得进入 Dispatch。所有合法消息先用固定
principal 调用 `IdempotencyStore.Begin`:

- pending duplicate 不再次调用 Dispatch,也不启动隐藏 retry;
- completed duplicate 重用已缓存的脱敏 DispatchEvent,并只重新发送一个聚合后的逻辑回复;
- dispatch 或发送前的处理失败释放 claim,允许调用方按既有进程内策略重新处理;
- 该 store 只保证当前进程,不能暗示跨节点、重启恢复或持久化语义。

## 4. Dispatch 与回复

适配器把规范化消息和可信 principal 交给 `DispatchService.Dispatch`,`RequestID` 使用上节生成
的稳定值,Context 原样向下传递。它必须读完事件 channel 直到关闭,不能在第一个 `message` 或
`done` 后提前退出。只拼接 `DispatchEventMessage.Text`;收到脱敏 `error`、stream 异常或空的
dispatch stream 时,发送固定的适配器级失败文本,不暴露 provider error、stack trace、Secret
或 repository 细节。

正常文本回复按 Unicode code point 切分,每段最多 4096 个 code point,并逐段调用:

```text
sendMessage(chat_id=Message.Chat.ID,
message_thread_id=Message.MessageThreadID when > 0,
text=chunk)
```

不为每个 partial event 发送 Telegram 消息,不在本 Issue 引入编辑消息、队列、退避或后台重试。
`sendMessage` 失败只通过稳定的 `send` hook 暴露;若已发送部分分段,不回滚也不启动隐式重试。

## 5. 生命周期与错误脱敏

```mermaid
sequenceDiagram
participant C as Service Context
participant A as Telegram Adapter
participant B as Bot SDK
participant G as Gateway Dispatch
participant T as Telegram API

C->>A: New(ctx, runtime token, trusted Target)
A->>B: New + getMe
B-->>A: bot user ID
A->>A: compare provider_account_id; mismatch fails closed
C->>A: Run(ctx)
A->>B: Start(ctx)
B->>T: getUpdates (long polling)
T-->>B: Update.Message
B->>A: HandleUpdate(ctx, update)
A->>G: trusted Principal + normalized InboundMessage
G-->>A: complete redacted DispatchEvent stream
A->>T: one or more sendMessage chunks
C-->>B: cancel ctx
B-->>A: stop polling and synchronous handlers
A-->>C: Run returns
```

`Run(ctx)` 是阻塞入口;Context 取消必须同时结束 SDK polling、在途 Dispatch 和 sendMessage。适配器
不创建自己的 retry goroutine,不保存 request Context,不持有 Runner lease;lease 和 event drain
由现有 Gateway contract 管理。`Close` 只关闭适配器拥有的进程内幂等 store,不能关闭调用方注入
的 store 或 HTTP client。

错误 hook 只使用 `initialization`、`polling`、`update`、`dispatch`、`send` 等稳定 operation 和
适配器 sentinel error。原始 SDK error 只能用于本地判断,不能出现在返回值、hook payload、日志、
trace 或 Telegram 回复中。

## 6. 文档与代码验收清单

README 和 MkDocs 状态应明确区分已交付与后续能力:

- [x] SDK 版本固定,Bot factory/client 可注入,`Run(ctx)` 和单 update handler 可测试;
- [x] `getMe` 身份校验、tenant/Binding/Runner 隔离、普通文本映射和 binding-aware 幂等通过测试;
- [x] Dispatch 完整消费、单逻辑回复、4096 code point 分段、forum thread 路由和失败脱敏通过测试;
- [x] cancellation、polling error、send failure、duplicate delivery 和资源生命周期通过测试;
- [x] Telegram long polling 已实现;Webhook、持久化幂等/outbox、媒体、跨节点 ownership
和其他 rich update 明确保持未勾选。

参考:[Telegram Bot API](https://core.telegram.org/bots/api)、
[getUpdates](https://core.telegram.org/bots/api#getting-updates)、
[github.com/go-telegram/bot](https://github.com/go-telegram/bot)。
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ nav:
- 架构设计: architecture.md
- 数据模型: data-model.md
- Channel Binding: channel-binding.md
- Telegram 长轮询 Adapter: telegram.md
- Gateway、Execution Plan 与 HTTP/SSE: gateway.md
- Tenant 运行时边界: tenant-runtime-boundary.md
- Agent App 模型: agent-app-model.md
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.21

require trpc.group/trpc-go/trpc-agent-go v1.11.2

require github.com/go-telegram/bot v1.23.0

require (
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-telegram/bot v1.23.0 h1:CKKQq115G/GUGBG8uuWl5uXbiBHyVjZBp/qqOLWZjJk=
github.com/go-telegram/bot v1.23.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand Down
Loading
Loading