diff --git a/README_EN.md b/README_EN.md index 9541ad5a2..46901b265 100644 --- a/README_EN.md +++ b/README_EN.md @@ -57,7 +57,7 @@ AgentHub lets you collaborate with human teammates and AI agents in the same gro ## Quick Start -Minimal local bootstrap (5 steps). Requires OpenSSL, Docker, Go 1.26+, Node 22+/corepack, pnpm 10+. +Minimal local bootstrap (5 steps). Requires OpenSSL, Docker, Go 1.26+, Node 24+/corepack, pnpm 10+. ```bash cp .env.example .env && secret="$(openssl rand -hex 32)" && sed -i.bak "s/^AGENTHUB_JWT_SECRET=.*/AGENTHUB_JWT_SECRET=$secret/" .env && rm -f .env.bak && export AGENTHUB_JWT_SECRET="$secret" && unset secret # 1. Copy config and generate a random dev secret diff --git a/api/README.md b/api/README.md index 1653c41d0..e6b3171c0 100644 --- a/api/README.md +++ b/api/README.md @@ -18,10 +18,11 @@ api/ ├── README.md # API 总入口 ├── conventions.md # 命名、分页、错误、权限、版本 ├── openapi.yaml # REST API 路径总表和基础契约 +├── dispatch.md # Hub→Edge dispatch、admission、execution intent、callback ownership └── events.md # WebSocket event envelope、源码 owner、事件族和验收边界 ``` -`openapi.yaml` 只描述 REST API。WebSocket 的 envelope、frame、事件族、源码 owner 和验收边界写在 `events.md`;完整事件常量由 `edge-server/internal/adapters/adapter.go`、`hub-server/internal/ws/frame.go` 和 shared transcript tests 守住。业务解释、权威边界和架构背景写进 `docs/`,不要塞进 OpenAPI。 +`openapi.yaml` 只描述 REST API。跨 Hub→Edge 的 dispatch、admission、execution intent 与 callback ownership 语义见 `dispatch.md`。WebSocket 的 envelope、frame、事件族、源码 owner 和验收边界写在 `events.md`;完整事件常量由 `edge-server/internal/adapters/adapter.go`、`hub-server/internal/ws/frame.go` 和 shared transcript tests 守住。业务解释、权威边界和架构背景写进 `docs/`,不要塞进 OpenAPI。 ## 产品术语 diff --git a/api/conventions.md b/api/conventions.md index 853db11b0..c6acdcc45 100644 --- a/api/conventions.md +++ b/api/conventions.md @@ -2,7 +2,7 @@ 最后更新:2026-08-09 -本文定义 AgentHub REST API 和 WebSocket typed events 的通用规则。完整路径/schema 以 `api/openapi.yaml` 为准;事件合同入口见 `api/events.md`;错误码源头见 `pkg/errcode`、`edge-server/internal/errcode/` 和 `hub-server/internal/errcode/`。 +本文定义 AgentHub REST API 和 WebSocket typed events 的通用规则。完整路径/schema 以 `api/openapi.yaml` 为准;跨 Hub→Edge 的 dispatch/admission/callback ownership 合同见 `api/dispatch.md`;事件合同入口见 `api/events.md`;错误码源头见 `pkg/errcode`、`edge-server/internal/errcode/` 和 `hub-server/internal/errcode/`。 ## Principles diff --git a/api/openapi.yaml b/api/openapi.yaml index ea7502a2a..80e930438 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1373,11 +1373,19 @@ paths: operationId: decidePermission summary: Approve or deny a pending permission request. description: > - Resolves a pending tool-permission request of a run. Under Hub JWT / - multi-user mode the run must be owned by the caller; a run owned by - another Hub user and a nonexistent run are reported identically (404), - so this endpoint is not a runId existence oracle. A caller without a Hub - identity fails closed. Local single-tenant mode is unaffected. + Resolves a pending tool-permission request of a run. The legacy form + (both `controlId` and `hubTaskId` absent) keeps one-shot receiver + semantics. The modern form supplies both fields and is idempotent: an + identical retry returns the original applied receipt, a different + control for the same run/request or a changed payload for the same + control fails with 409, and a cold/evicted receipt is never invented as + success. The receipt cache is process-local, bounded, and non-durable; + capacity pressure returns 429 before applying the decision. Under Hub + JWT / multi-user mode the run must be owned by the caller and a modern + request must match the stored `hubTaskId`. A foreign run, nonexistent + run, wrong task, and missing pending request are reported identically + (404), so this endpoint is not an existence oracle. A caller without a + Hub identity fails closed. Local single-tenant mode is unaffected. x-agenthub-status: implemented x-agenthub-owner: Edge requestBody: @@ -1388,20 +1396,53 @@ paths: type: object required: [runId, requestId, decision] properties: + controlId: + type: string + description: Modern idempotency identity; must be supplied with hubTaskId. + hubTaskId: + type: string + description: Exact Hub task bound to the Edge run; must be supplied with controlId. runId: { type: string } requestId: { type: string } decision: { type: string, enum: [allow, deny] } reason: { type: string } responses: "200": - description: Decision recorded. + description: > + Decision applied. Legacy requests return `{status: ok}`; modern + requests return the applied receipt and set `deduplicated` when the + request replayed an existing receipt. + content: + application/json: + schema: + type: object + required: [code, data] + properties: + code: { type: string } + data: + type: object + required: [status] + properties: + status: { type: string } + controlId: { type: string } + hubTaskId: { type: string } + runId: { type: string } + requestId: { type: string } + decision: { type: string, enum: [allow, deny] } + applied: { type: boolean } + deduplicated: { type: boolean } "400": - description: Invalid decision payload. + description: Invalid decision payload or only one of controlId/hubTaskId supplied. "404": description: > Pending permission request not found. The same response is returned - when the run belongs to another Hub user, so a foreign runId and a - nonexistent runId are indistinguishable. + when the run belongs to another Hub user or the modern hubTaskId + does not match, so a foreign/mismatched run and a nonexistent run + are indistinguishable. + "409": + description: The control identity or run/request already has a conflicting decision receipt. + "429": + description: The bounded permission decision receipt cache is full; no decision was applied. /v1/plans/decide: post: @@ -8655,7 +8696,7 @@ components: additionalProperties: true capabilities: type: object - required: [runCallbackOwnership, directHubCallbacks] + required: [runCallbackOwnership, directHubCallbacks, permissionDecisionReceipts] properties: runCallbackOwnership: type: boolean @@ -8663,6 +8704,9 @@ components: directHubCallbacks: type: boolean description: Direct callbacks have a configured destination and current credential; no token or live reachability is exposed. + permissionDecisionReceipts: + type: boolean + description: The Edge supports idempotent modern permission decision receipts. DispatchContextMessage: type: object required: [role, content] @@ -9690,7 +9734,7 @@ components: description: True on an accepted replay of an already admitted delivery (same runId returned, no new run created). deliveryId: type: string - description: The delivery_id whose accepted receipt is being replayed; present on accepted replays. + description: On an accepted replay, echoes the current request's deliveryId; a cold replay with a different deliveryId may therefore differ from the original receipt. HubAuditEvent: type: object diff --git a/docs/api-reference.md b/docs/api-reference.md index 16e5462a8..92f4306ea 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -1,6 +1,6 @@ # AgentHub API Reference -最后更新:2026-08-16 +最后更新:2026-09-09 本文件只做 API 契约入口。旧 2041 行静态 API 参考见 [history.md](history.md),避免它和 OpenAPI / WebSocket 契约漂移。 @@ -10,6 +10,7 @@ |---|---|---| | REST 路径、schema、阶段标记 | `api/openapi.yaml` | OpenAPI YAML parse / endpoint tests | | WebSocket envelope、frame、事件族和源码 owner | [../api/events.md](../api/events.md) | WS tests / event normalizer tests | +| Hub→Edge dispatch、admission、execution intent、callback ownership | [../api/dispatch.md](../api/dispatch.md) | dispatch/runcontrol tests | | 命名、分页、错误、权限、版本 | [../api/conventions.md](../api/conventions.md) | API review / handler tests | | 架构背景和边界 | [architecture.md](architecture.md) | 架构 review | @@ -17,6 +18,7 @@ 1. 新 REST 接口先改 `api/openapi.yaml`。 2. 新 WebSocket 事件先改源码 owner,再同步 `api/events.md` 的事件族和验收边界。 -3. 通用命名、错误、分页、权限规则先改 `api/conventions.md`。 -4. 新增鉴权、session、TokenDance ID 或权限行为时,同步 [architecture/06-auth-identity.md](architecture/06-auth-identity.md) 和 `AGENTS.md` 中的边界规则。 -5. 不再在 `docs/` 里维护第二份完整 API 表。 +3. 跨 Hub→Edge 的 dispatch/admission/callback ownership 先改 `api/dispatch.md`。 +4. 通用命名、错误、分页、权限规则先改 `api/conventions.md`。 +5. 新增鉴权、session、TokenDance ID 或权限行为时,同步 [architecture/06-auth-identity.md](architecture/06-auth-identity.md) 和 `AGENTS.md` 中的边界规则。 +6. 不再在 `docs/` 里维护第二份完整 API 表。 diff --git a/docs/architecture.md b/docs/architecture.md index f659bb6e3..710256bb1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # AgentHub 架构概览 -最后更新:2026-08-29 +最后更新:2026-09-09 本文档是架构入口,只保留当前结构、边界和 owner 链接。旧长版架构说明见 [history.md](history.md)。 @@ -44,7 +44,7 @@ Mobile (fixture/boundary lane, independent RN rendering) | Mobile | `app/mobile-rn/` | RN shell、Mobile adapter、Hub viewer surface(fixture/边界验证 lane,独立渲染,非 release candidate) | | Edge | `edge-server/` | 本地项目、Thread、Run lifecycle、Runtime adapter、Artifact index | | Hub | `hub-server/` | TokenDance ID relying party、Hub session、IM、AgentTeam、同步、中继、审计 | -| API | `api/` | REST API 和 WebSocket event 契约 | +| API | `api/` | REST API、WebSocket event 与 Hub→Edge dispatch 契约 | ## 核心数据流 diff --git a/docs/architecture/01-hub-server.md b/docs/architecture/01-hub-server.md index f9f3e39a9..15724a946 100644 --- a/docs/architecture/01-hub-server.md +++ b/docs/architecture/01-hub-server.md @@ -2,7 +2,7 @@ > 子文档 | 主索引:[architecture.md](../architecture.md) > -> 最后更新:2026-08-20 +> 最后更新:2026-09-09 Hub Server(`hub-server/`)是 AgentHub 的云端控制面:TokenDance ID relying party、Hub session、IM、AgentTeam、同步、中继、审计和远程控制面。它不启动本机 Agent Runtime;执行仍由 Edge Server 和 adapter 负责。 @@ -54,6 +54,7 @@ Hub Server(`hub-server/`)是 AgentHub 的云端控制面:TokenDance ID rel |---|---| | REST path/schema | `api/openapi.yaml` | | WS frame/event families | `api/events.md` | +| Hub→Edge dispatch/admission/callback ownership | `api/dispatch.md` | | API conventions | `api/conventions.md` | | Auth/identity | [06-auth-identity.md](06-auth-identity.md) | | Deployment boundary | [05-deployment.md](05-deployment.md) | diff --git a/docs/architecture/02-edge-server.md b/docs/architecture/02-edge-server.md index c21a7ebe6..7cd8db871 100644 --- a/docs/architecture/02-edge-server.md +++ b/docs/architecture/02-edge-server.md @@ -2,7 +2,7 @@ > 子文档 | 主索引:[architecture.md](../architecture.md) > -> 最后更新:2026-09-06 +> 最后更新:2026-09-09 ## 职责 @@ -68,7 +68,7 @@ ProcessExecutor 配置 `RunTimeout`(默认 30 分钟)、`ShutdownGracePeriod `SQLiteStore`(`internal/store/sqlite_store.go`)在写入后同步持久化快照差分到 SQLite(WAL 模式,定期 checkpoint),支持崩溃恢复。SQL 连接初始化与持久化串行化独立于普通业务读面,不能用额外 SQL 读者的争用直接代替业务读取测量。 -终端状态 runs(finished/failed/cancelled/completed_with_issues)按 `TerminalTTL` 超时或 `MaxTerminalRunsPerThread` 上限自动清理,级联删除关联 diffs/artifacts/previews/items/checkpoints。Checkpoint 在 run 完成时保留,在 run 清理或所属 thread 删除时随 run 移除;这不删除工作区文件。 SQLite 后台清理有删除时同步提交;失败通过 `LastPersistError` 和日志留痕,下一周期即使没有新删除也会重试。关闭时先停止并等待后台清理/checkpoint 任务,再做最终持久化与数据库关闭。 +终端状态 runs(finished/failed/cancelled/completed_with_issues)按 `TerminalTTL` 超时或 `MaxTerminalRunsPerThread` 上限自动清理,级联删除关联 diffs/artifacts/previews/items/checkpoints;`AdmissionState == pending` 的 run 不参与该自动清理,未决 admission 的收口语义见 `api/dispatch.md`。Checkpoint 在 run 完成时保留,在 run 清理或所属 thread 删除时随 run 移除;这不删除工作区文件。 SQLite 后台清理有删除时同步提交;失败通过 `LastPersistError` 和日志留痕,下一周期即使没有新删除也会重试。关闭时先停止并等待后台清理/checkpoint 任务,再做最终持久化与数据库关闭。 `EventBus`(`internal/events/bus.go`)是基于 channel 的发布/订阅模型:4 worker 并发 observer、子 channel 缓冲(256)、gap detection(`system.gap` 事件);通过 `PersistFn` 钩子先持久化再广播。`EventLog` 是 append-only JSON-lines 事件日志(默认 50 MiB 上限,超限截断保留尾部 75%)。 @@ -108,8 +108,8 @@ ProcessExecutor 配置 `RunTimeout`(默认 30 分钟)、`ShutdownGracePeriod | 方向 | 方式 | |---|---| -| Hub -> Edge | REST callbacks + Hub WebSocket dispatch/relay | -| Edge -> Hub | 同步、状态上报 | +| Hub -> Edge | REST `POST /v1/runs` 执行 admission;WebSocket 承载适用的 relay/control frame | +| Edge -> Hub | direct callbacks、同步与状态上报 | ## Adapter 注册表 diff --git a/docs/architecture/03-runtime-adapters.md b/docs/architecture/03-runtime-adapters.md index e7c614a46..5af9c4d5a 100644 --- a/docs/architecture/03-runtime-adapters.md +++ b/docs/architecture/03-runtime-adapters.md @@ -2,7 +2,7 @@ > 子文档 | 主索引:[architecture.md](../architecture.md) > -> 最后更新:2026-08-20 +> 最后更新:2026-09-09 ## 概述 @@ -156,7 +156,7 @@ cc-switch 是可选增强,不是硬依赖。数据库缺失、不可读或无 - Desktop 文件操作必须经过 allowlist 和 typed Host API - Edge 不直接暴露文件系统给 Hub -- Hub -> Edge 的执行请求通过 REST callbacks,Edge 校验后才启动 runtime +- Hub -> Edge 的执行请求通过 REST `POST /v1/runs`(admission/execution intent 合同见 `api/dispatch.md`),Edge 校验后才启动 runtime;edge-owned run 的结果再由 Edge -> Hub callbacks 回传 ## 相关文档 diff --git a/docs/architecture/10-macro-engineering-design.md b/docs/architecture/10-macro-engineering-design.md index 8ddd44a4f..fb2bf97d5 100644 --- a/docs/architecture/10-macro-engineering-design.md +++ b/docs/architecture/10-macro-engineering-design.md @@ -2,7 +2,7 @@ > 子文档 | 主索引:[architecture.md](../architecture.md) > -> 最后更新:2026-08-29 +> 最后更新:2026-09-09 > > 状态:设计基线(Accepted)。本文定义目标架构、协议分层、可靠性/安全/可观测合同与差距路线。**本文是设计合同,不是实现完成声明**:落地状态以 GitHub Issues/PR、`AGENTS.md` 验收门禁和源码为准。 @@ -39,7 +39,7 @@ UI Workbench(chat timeline + command center) | 协议 | 角色 | 现状/目标 | 红线 | |---|---|---|---| -| 自有 REST/WS | Hub/Edge 产品控制面 SSOT | 保持 `api/openapi.yaml` + `api/events.md` | 不替换 | +| 自有 REST/WS | Hub/Edge 产品控制面 SSOT | 保持 `api/openapi.yaml` + `api/events.md` + `api/dispatch.md` | 不替换 | | ACP / Agent SDK adapter | Edge <-> coding agent runtime 进程契约 | 已有 adapter family | 只属于 Edge data plane | | MCP | agent <-> tools/data | 已有 mcp_config + Edge MCP server(8 个 canonical 工具,见 11) | tool 需要 narrow capability,不当作通用业务协议 | | A2A | agent <-> agent | 仅远程/跨设备协作引入;当前未引入(见 11) | 不替换自有 Hub/Edge 契约 | diff --git a/docs/architecture/11-protocol-capability-mapping.md b/docs/architecture/11-protocol-capability-mapping.md index 7e6fc0c04..e25bfeae3 100644 --- a/docs/architecture/11-protocol-capability-mapping.md +++ b/docs/architecture/11-protocol-capability-mapping.md @@ -1,10 +1,9 @@ # 协议能力映射:自有契约 / MCP / A2A / AG-UI(宏观 §3 P1) - > 主索引:[architecture.md](../architecture.md)。本文件是 AgentHub 自有 REST/WS 契约与三个外部协议(MCP / A2A / AG-UI)的能力对照、引入红线与评估结论 SSOT。上游基线:`docs/architecture/10-macro-engineering-design.md`(#2065,已合入)§3 协议分层表 + §9 差距路线 P1(本文即其产出)。 ## 0. 阅读约定 -- **自有契约** = AgentHub Hub Server REST + WS 事件合同(SSOT:`api/openapi.yaml` + `api/events.md`)。 +- **自有契约** = AgentHub Hub Server REST + WS 事件合同,以及 Hub→Edge dispatch/admission 语义(SSOT:`api/openapi.yaml` + `api/events.md` + `api/dispatch.md`)。 - **外部协议** = MCP、A2A、AG-UI;描述必须给来源链接或仓库内引用,「业界通常」不算证据。 - **引入判定** = 每个外部协议一节判定表;满足全部"引入条件"才允许进入产品代码,否则拒绝或推迟。 - **红线** = 不可让步的死规矩;违反即阻断合入。 @@ -12,16 +11,17 @@ ## 1. 自有 REST/WS 契约清单 -AgentHub 的产品控制面 SSOT 由两份契约文件承载,所有端(Web/Desktop/Mobile/Edge)只消费这两份契约的派生客户端。 +AgentHub 的产品控制面 SSOT 由 REST、WS 与跨通道 dispatch 契约共同承载,所有端(Web/Desktop/Mobile/Edge)只消费这些契约的派生客户端。 | 契约 | 入口文件 | 职责 | 备注 | |---|---|---|---| | REST JSON API | [`api/openapi.yaml`](../../api/openapi.yaml) | Hub + Edge 全量命令/查询端点;端点级状态标签 `x-agenthub-status: implemented/planned`;组件级另有 `contract-draft`(契约已定、实现留白,不挂在任何 operation 上) | Hub success envelope `{"code":"ok","data":...}`(见 [`api/conventions.md`](../../api/conventions.md)) | | WebSocket typed events | [`api/events.md`](../../api/events.md) | Hub/Edge 实时事件合同;at-least-once 投递、幂等语义、seq_id 语义 | SSOT 三角:`hub-server/internal/ws/frame.go` ↔ `app/shared/src/hubEvents.ts` ↔ OpenAPI `HubWebSocketFrame.type` | +| Hub→Edge dispatch/admission | [`api/dispatch.md`](../../api/dispatch.md) | Hub→Edge delivery claim/receipt、execution intent、callback ownership 与重试语义 | 与 `POST /v1/runs` 和 `edge-server/internal/runcontrol/` 同源 | | Edge EventEnvelope | [`api/events.md`](../../api/events.md) §Edge EventEnvelope | Edge stream 单调 seq + 事件 id;断线 cursor 回放 | payload 进 transcript 前脱敏 | | Hub Frame | [`api/events.md`](../../api/events.md) §Hub Frame | `{type, seq_id?, payload?}` + 31 常量 | `/client/ws` 仅 Hub-issued HS256 token | -**红线**:自有 REST/WS 契约是产品控制面唯一权威;任何外部协议不得替换、覆盖或绕过这两份契约(宏观 §3 红线列)。 +**红线**:自有 REST/WS 与 Hub→Edge dispatch 契约是产品控制面唯一权威;任何外部协议不得替换、覆盖或绕过这些契约(宏观 §3 红线列)。 ## 2. MCP:tool surface 映射与 narrow capability 红线