diff --git a/llmdoc/architecture.mdx b/llmdoc/architecture.mdx new file mode 100644 index 0000000..f13dfe2 --- /dev/null +++ b/llmdoc/architecture.mdx @@ -0,0 +1,63 @@ +--- +description: Watt 的整体定位、规范真源、模块边界、核心数据流与部署形态;用于首次进入仓库或判断跨域影响面。 +kind: architecture +relations: + related: + - contracts/events-and-results.mdx + - contracts/auth-and-errors.mdx + - contracts/htbp-and-platform-apis.mdx + - operations/topology-and-provisioning.mdx +code: + paths: + - Docs/*.md + - DOD.md + - LOOP.md + - pnpm-workspace.yaml + - packages/*/package.json + - packages/gateway/src/index.ts + - packages/gateway/wrangler.jsonc +--- + +# Watt 整体架构 + +## 项目定位与真源 + +Watt 是运行在 Cloudflare 上的 Agent 基础设施,不是新的 Agent framework。任意 harness 通过开放协议接入组织的事件、上下文、工具、长任务和模型渠道;平台负责身份、路由、持久化、审计与管理入口。 + +规范优先级是:`Docs/Proto.md` 定接口与横切契约,`Docs/Architecture.md` 定模块和拓扑,`Docs/Plugin.md` 定扩展边界,`Docs/Vision.md` 定产品目标;`DOD.md` 定可重跑验收,`LOOP.md` 定开发纪律。实现与规范冲突时,先澄清或修改规范,再对齐实现。 + +## 模块与所有权 + +| 模块 | 责任 | 主要运行位置与持久化 | +|---|---|---| +| Event Gateway | 渠道规约、发布、订阅、会话粘性、留痕、出站 | gateway Worker、Queues、Router DO、D1 | +| Agent Runtime | 定义、实例、派生、生命周期、harness | Agents SDK Durable Object;heavy runtime 预留给 Container/Sandbox | +| Context Layer | object、structured、vector 上下文与 namespace mount | R2、D1、Vectorize、Context Registry DO | +| Tool Layer | 工具发现、虚拟化、调用与授权执行点 | tool-bridge Worker;gateway 提供鉴权代理 | +| Auth | Principal、Policy、委托链衰减、身份映射 | gateway PEP、D1 Policy、Identity Mapper | +| Scheduler | cron 与 publish、agent、script 三类动作 | Scheduler DO;script 使用一次性 isolate | +| Task | 跨时长工作与 human checkpoint | Cloudflare Workflows 与 TaskStore | +| Model Provider | provider 注册、默认路由与模型调用 | Provider D1、AI Gateway 或兼容上游 | +| Observability | usage、metrics、audit | Analytics Engine 与 D1 | +| Management | Dashboard、manage agent、Watt CLI 三个对等入口 | 全部复用 Platform API,不设管理旁路 | +| Plugin System | 扩展注册、认证、探活与生命周期 | Plugin Registry、独立 Worker 或外部 HTTP 服务 | + +Registry 类接口遵循 `List/Get/Write/Update` 的共同动词;Provider 类接口按领域定义。Agent 对平台的逻辑消费面是一棵 HTBP 树:tools、context、platform 三个分支。物理上 gateway 自持 platform/context,tool-bridge 承载 tools,gateway 的 tools 代理负责统一身份和 `Authorizer.Check`;物理分治不能变成逻辑旁路。 + +## 核心数据流 + +入站渠道先 Verify/Decode 为统一 Event,再经 EventBus、Queues 与 Router DO 路由。带 session 的订阅由稳定 instance key 粘到同一 Agent DO;Agent 访问 Context、Tool 或 Platform API 时携带调用者和派生链身份,每个入口都执行衰减式授权。Agent 不直接向 IM SDK 发送消息,而是发布 `outbound.message`,由 channel adapter/plugin 统一发送。 + +Agent-to-Agent 仍走 Event Bus。带 correlationId 的 `agent.result` 与 `agent.failed` 定向回送等待方,不进入普通订阅匹配。Task 的 human checkpoint 发布标准事件,渠道把它渲染为交互卡片;用户动作经过 `task://` 的 `signal` 授权后恢复 Workflow。 + +## 扩展与管理边界 + +Plugin 分为 context-provider、tool-provider、channel-adapter、agent-harness。模型渠道只是 ModelProviderRegistry 数据,不是 Plugin。Plugin 只能实现声明的接口并以受限 plugin token 回调平台;新增渠道或工具不得要求修改 gateway 的领域硬编码。 + +Dashboard、manage agent 与 CLI 必须调用同一套 Platform API,并产生相同的授权与审计语义。前端为了体验增加的查询 filter 可以扩展既有接口,但不能引入只有某个管理入口可用的专用后门。 + +## 部署形态 + +仓库是 pnpm monorepo。gateway、tool-bridge 和独立 plugin Worker 组成边缘数据面;Durable Objects、Workflows、Queues、D1、KV、R2、Vectorize 和 Analytics Engine 按模块 ownership 绑定。Dashboard 静态产物由 gateway 同域分发;CLI 是远程 Platform API 客户端。 + +具体契约从 `contracts/` 检索,运行时语义从 `runtime/` 检索,部署与开发流程从 `operations/` 检索,飞书与 Plugin 从 `integrations/` 检索,Dashboard 从 `dashboard/` 检索。V3 不维护手写 index 或 startup 阅读顺序,使用 `llmdoc tree/index/search/context/show` 渐进检索。 diff --git a/llmdoc/architecture/modules-and-flows.md b/llmdoc/architecture/modules-and-flows.md deleted file mode 100644 index c34b1ad..0000000 --- a/llmdoc/architecture/modules-and-flows.md +++ /dev/null @@ -1,97 +0,0 @@ -# 模块边界与数据流(M1~M11) - -> 真源:`Docs/Architecture.md`(模块边界)、`Docs/Plugin.md`(插件契约)。接口契约查 [../reference/proto-map.md](../reference/proto-map.md)。 - -## 模块表(M1~M11) - -注意:实际编号为 M1~M11(含 M11 Plugin System)。Architecture M2~M11 与 Proto §2~§11 编号一一对齐。 - -| # | 模块 | 职责 | 宿主 | 持久化 | 接口面 | -|---|---|---|---|---|---| -| M1 | Event Gateway | 事件统一入口/出口与路由,异构来源规约成 `Event` | Ingress Worker(`fetch`/`email`)+ Channel DO + Queues + Router DO | Queues(缓冲/重试/DLQ)、Router DO SQLite(订阅表/Session Mapper)、D1(EventStore,建议 30 天) | ChannelAdapter / ChannelRegistry / EventBus / EventStore | -| M2 | Agent Runtime | Agent 定义/实例/生命周期/派生/协议接入 | Agents SDK(每实例一 DO = Light)+ Containers/Sandbox(Heavy) | DO `this.state`/`this.sql`;容器 scale-to-zero 无持久态 | AgentRegistry / AgentRuntime / AgentEndpoint | -| M3 | Context Layer | 多来源上下文的带命名空间统一读写面 | Context Registry DO + 内置 Provider + Plugin | R2(object)、D1(structured)、Vectorize+Workers AI embeddings(vector);Registry SQLite + `this.schedule` TTL 回收 | ContextProvider / ContextRegistry | -| M4 | Tool Layer | 统一工具发现/调用面;**整棵 HTBP 树的宿主** | tool-bridge(Worker) | 无独立存储(ToolMount 注册表 DO 内聚);上游凭证在网关侧 Secrets | ToolProvider / ToolRegistry | -| M5 | Auth | Principal/Policy/访问判定(衰减式委托链) | Worker 中间件(PEP 分布在各模块入口) | D1(策略表)+ KV(authz-cache/tenants) | Authorizer / PolicyStore / IdentityMapper | -| M6 | Scheduler | Cron/延迟触发;action = publish/agent/script | Scheduler DO(`this.schedule` alarm);script 走一次性 isolate(Dynamic Worker Loader) | DO alarm + SQLite;script 本体存 `context://automations/` | Scheduler | -| M7 | Task | 跨小时/跨天长任务 + 人类检查点 | Cloudflare Workflows | Workflows 引擎持久化(step.do / sleep ≤365 天 / waitForEvent) | TaskManager | -| M8 | Model Provider | 模型渠道注册/默认路由/fallback/计量 | AI Gateway(统一 LLM 流量入口) | D1(Provider 配置);密钥经 Secrets | ModelProviderRegistry / ModelRouter | -| M9 | Observability | 用量/费用/缓存/性能/审计 | AI Gateway analytics + Workers Analytics Engine | Analytics Engine(时序)+ D1(审计明细) | Metrics / AuditLog | -| M10 | Management | Dashboard + Manage Agent + Watt CLI 三对等入口 | gateway Static Assets 同域托管的 React Router 7 SPA(`ssr:false`,Tailwind v4 + shadcn/ui,16 视图全 CRUD + manage 对话,R36)+ 内置 Agent(manage/*)+ npm `@tokenroll/watt` | 无自有存储(纯 Platform API 客户端) | 复用各模块接口(HTBP 绑定),无管理旁路 | -| M11 | Plugin System | 一切扩展的注册与生命周期 | Registry DO + Worker 部署(或外部 HTTP 服务) | PluginRegistry DO | PluginRegistry / PluginLifecycle | - -M10 Dashboard 的 manage 对话链路(R36):Spawn 建会话(**不带 expect**,避免 harness 空跑一次 LLM)→ 每轮 `Send{expect}` → 1.5s 轮询 `EventStore.List{correlationId}` 取 `agent.result`/`agent.failed` → 渲染 `payload.output`(70s 兜底超时;选型见 [../memory/decisions/dashboard-rr7-stack.md](../memory/decisions/dashboard-rr7-stack.md))。 - -## 两个全局模式 - -1. **三大 Registry**:AgentRegistry(M2) / ToolRegistry(M4) / ContextRegistry(M3),统一 `List/Get/Write/Update` 四动词(Proto §0.4);其余配置类注册表(ChannelRegistry、PolicyStore、Scheduler、ModelProviderRegistry、PluginRegistry)同构。 -2. **单一 HTBP 消费面**:Agent 对平台的全部消费收敛为一棵 HTBP 树——`/htbp/tools/...`、`/htbp/context/...`、`/htbp/platform/...`,由 tool-bridge(M4)统一服务。Tool 与 Context 的 Agent 访问面融入 HTBP,不各自另设协议。HTBP 翻译层:ContextProvider 四动词是 Provider 侧 SPI,HTBP 是 Agent 侧消费面,Tool Gateway 在中间翻译;每个 namespace = `/htbp/context/` 一个节点。 - -## 入站→路由→Agent→出站数据流 - -1. **入站**:渠道回调统一入口 `POST /channels//inbound`,Ingress Worker 按 `ChannelConfig.adapter` 分发 → Verify → Decode → 规约成 `Event`;email 走 `email()` handler;外部系统持 Bearer 直接调 `EventBus.Publish`(`source.kind='webhook'`)。 -2. **路由**:按订阅规则(三类订阅:AgentDefinition 声明式 / 渠道生命周期自动 / 手工)经 Queues + Router DO 分发给 Agent 实例/Task/外部 webhook。 -3. **会话粘性**:`Event.session`(如 `feishu:chat:`)经 Session Mapper 稳定映射到实例 ID,同会话事件恒达同一 DO。 -4. **Agent 消费平台**:经 HTBP 或 MCP(`watt-platform` MCP server)访问 Context/Tool/Spawn;平台驱动 Agent 经 `AgentEndpoint.OnEvent`;每次调用带 Agent Token(实例 ID/派生链/on-behalf principal)。 -5. **判定**:每个入口 PEP(Tool Gateway 全 HTBP 子树、Context Registry、Event Gateway、Agent Runtime)调 `Authorizer.Check`;有效权限 = Principal ∩ Agent ∩ 派生链祖先,**只衰减不放大**。 -6. **出站**:Agent 不直连 IM API,而是 Publish 出站 Event(`type:"outbound.message"`)→ 对应 ChannelAdapter 的 `Send` 投递;审计/限流/权限在同一点收敛。 -7. **Agent-to-Agent**:Agent 间接力复用同一 Event Bus(`source.kind='agent'`);完成经 `agent.result`/`agent.failed` 定向回送(Proto §3.4,correlationId+超时)。 - -## Human-in-the-loop 链路(Case 1/2 的确认环) - -Task 挂起并 Publish `task.checkpoint`(含 prompt + 通知目标)→ 内置路由渲染成带按钮的 IM 卡片(按钮内嵌 `{taskId, checkpoint, decision}`)→ 用户点击产生 `im.action` → 平台校验点击者权限 `Check(task://, 'signal')` → 调 `TaskManager.Signal` → Workflows `waitForEvent` 恢复。适配层把点分事件名净化(`.`→`-`,Workflows 事件 type 禁 `.`),详见 [../reference/proto-map.md](../reference/proto-map.md) §3.4 节。 - -## M10 Watt CLI 命令表 - -> 原文标注"**节选核心命令**",非完整枚举。做 CLI 完备性核对时以 M10 命令族为上界、DOD 各 Phase 子命令为最小验收集。 - -| 命令 | 背后接口 | -|---|---| -| `watt login` / `watt whoami` | OAuth 2.1 device flow → user token(Proto §6.5);whoami 解码 claims | -| `watt status` | `Metrics.Query` + 各模块 Health 汇总 | -| `watt agent list/get/spawn/send/terminate/tree` | AgentRegistry.* + AgentRuntime.*(tree = `ListInstances(tree)`) | -| `watt context ls/cat/put/patch/mount/unmount` | ContextProvider 四动词 + ContextRegistry.* | -| `watt tool ls/describe/call/mount` | ToolProvider.List/Get/Call + ToolRegistry.* | -| `watt task list/get/run/signal/cancel` | TaskManager.*(signal = 人类检查点的命令行确认路径) | -| `watt cron list/create/trigger/rm` | Scheduler.* | -| `watt event tail/get/subs` | EventStore.List/Get(tail = 轮询)+ EventBus.ListSubscriptions | -| `watt channel list/set/connect` | ChannelRegistry.*;connect = 本地承载 push 型长连接(如飞书 WSClient),开发期替代 Container 宿主 | -| `watt provider list/add/set-default` | ModelProviderRegistry.* | -| `watt policy list/add/rm` | PolicyStore.* | -| `watt plugin register/list/health` | PluginRegistry.* + PluginLifecycle.Health | -| `watt metrics query` / `watt audit list` | Metrics.Query / AuditLog.List | - -DOD 各 Phase 子命令合并清单(最小验收集):Phase 0 `status`;Phase 1 `login/whoami/policy list|add|rm/audit list`;Phase 2 `event tail|get|subs/channel list|set`;Phase 3 `context ls|cat|put|patch|mount|unmount`;Phase 4 `agent list|spawn|send|tree/tool ls|call|mount/provider list`;Phase 5 `task list|run|signal|cancel/cron list|create|trigger`;Phase 6 `channel connect/metrics query/status/plugin register/audit list`;Phase 7 复用以上作 E2E 驱动器。 - -认证:`watt login` 走 OAuth 2.1 device flow,token 缓存 `~/.watt/credentials`;非交互场景支持 `WATT_TOKEN` 环境变量;权限走 Proto §6.4c 纯 principal 路径。 - -## Plugin 四类契约摘要(Plugin.md) - -Watt Plugin = 实现某一层纯接口的可注册部署单元。三步:挑类型(=挑接口)→ 任意语言实现 HTTP 契约 → `PluginRegistry` 注册 manifest。 - -| kind | 实现接口 | 能力 | 例子 | -|---|---|---|---| -| `context-provider` | ContextProvider 四动词(+可选 Search/Watch/Delete,Proto §4.1) | 新上下文来源挂 `context://` | 飞书文档、mem0、Notion | -| `tool-provider` | ToolProvider List/Get/Call(Proto §5.1) | 新工具挂 `/htbp/tools/` | 上游 MCP server、内部 API | -| `channel-adapter` | ChannelAdapter Verify/Decode/Encode/Send(push 型可豁免 Verify/Decode,capabilities 声明 `push`,Proto §2.1) | 新事件渠道 | 飞书、Slack、Email | -| `agent-harness` | AgentEndpoint OnEvent/Describe(Proto §3.3) | 新 Agent 运行方式 | Flue、Claude Agent SDK 容器 | - -要点: -- **模型渠道不是 Plugin**——只是 `ModelProviderRegistry.Write` 的一条数据。 -- 能力边界:Plugin 只能 (a) 响应平台对接口的调用;(b) 用 plugin token 回调平台,scope 严格等于 manifest 的 `requiredGrants`。 -- 注册流程:探活(healthPath)→ 抓 `~help` 契约校验(方法集与 `interfaceVersion` 不符则拒绝挂载)→ 挂载;响应含 `platformBaseUrl`/`jwksUrl`/`pluginToken`。健康检查连续失败标 unhealthy 并告警,**不自动注销**。实现状态(R27):探活已实现(失败拒绝注册);`~help` 契约校验延后(doc-gaps #30)。 -- 双向认证:平台→Plugin 用 `platform-token`(JWT,平台公钥验签)或 `bearer`(静态密钥经 Secrets);Plugin→平台用 plugin token。 -- 版本:`interfaceVersion` = `/v`;同 major 向后兼容;未知字段必须忽略。 -- 部署形态:平台内 Worker(推荐)/ 外部 HTTP 服务 / Container(agent-harness 需完整 OS)。 - -## 附B 部署拓扑与资源引导顺序 - -资源清单:workers `watt-gateway`(M1+M5+Platform API)/ `watt-toolbridge`(M4)/ `watt-plugins/*`;DO:AgentInstance、AgentRegistry、ToolRegistry、ChannelSession、EventRouter、ContextRegistry、SchedulerHub、PluginRegistry;containers `agent-heavy-*`;workflows `watt-task`;storage:R2(context-objects/artifacts)、D1(policies/providers/audit/events)、KV(authz-cache/tenants)、Vectorize(context-index);AI Gateway;pages `watt-dashboard`;cli `watt-cli`。 - -**引导顺序(bootstrap)**: -1. 部署 Workers/DO/Workflows。 -2. 写入初始 admin principal + 种子 Policy(Proto §6.5c)。 -3. 注册内置 Provider/Adapter(object/structured/vector Context Provider、builtin Tool Provider、各 IM Adapter)。 -4. 写内置 AgentDefinition(`manage/*` 系列)。 -5. 挂载工具树与 Context namespace。 -6. Dashboard 可用。 diff --git a/llmdoc/contracts/auth-and-errors.mdx b/llmdoc/contracts/auth-and-errors.mdx new file mode 100644 index 0000000..8ec000e --- /dev/null +++ b/llmdoc/contracts/auth-and-errors.mdx @@ -0,0 +1,69 @@ +--- +description: CallContext、Policy 与 Agent 委托链授权、Token 引导、SecretStore 和 WattError 的规范边界。 +kind: reference +relations: + requires: + - architecture.mdx + related: + - runtime/agents-and-tools.mdx + - operations/credentials-and-recovery.mdx +code: + paths: + - Docs/Proto.md + - packages/shared/src/error.ts + - packages/core/src/auth/** + - packages/core/src/authz/** + - packages/gateway/src/authz/** + - packages/gateway/src/http/auth.ts + - packages/gateway/src/http/errors.ts + - packages/gateway/src/http/oauth.ts + - packages/gateway/src/secrets/** +--- + +# 授权、凭据与错误契约 + +## CallContext 与身份 + +CallContext 携带最终受益人 principal、roles、可选 AgentChainRef 和 traceId。principal 可以是 user、service 或定义级 agent;实例身份只放在 token claims 的 `agent_inst` 与 CallContext.agent 中,不能拿实例 ID 充当最终受益人。 + +HTTP 绑定下 CallContext 只通过 `X-Watt-Context` 传输,body 不复制。渠道身份先经 IdentityMapper 解析;纯 user/service token 没有 agent 链时只做 principal Policy 判定。 + +## 衰减式授权 + +授权不变量是: + +```text +allow = P(principal) ∩ P(current agent definition) ∩ P(each ancestor/system segment) +``` + +Policy 先按 subject、resource 与 action 求 principal 许可,deny 优先。带 agent claims 时再检查当前 AgentDefinition.grants,并沿 chain 检查祖先 definition。`cron:` 只有 script action 把 job grants 作为额外上限;publish/agent action 不追加该段上限;job 已删除或禁用时拒绝。 + +gateway 的 Authorizer 通过 AgentDefLoader 惰性装载 `claims.agent_def`,避免空索引把所有 agent 主体误拒。cron script 则以当前 job 自足播种 cronJobs。任何新的 PEP 只要接收含 agent/cron 链的 claims,就必须确保判定点能解析链上引用的数据,不能把空索引当成正常运行态。 + +Agent 工具调用还要同时满足 toolScopes 前缀、principal Policy 和 AgentDefinition.grants;管理员直调只能证明工具可用,不能证明 agent 主体有权调用。 + +## Token 与引导 + +JWT 使用非对称 Ed25519 签名并通过 JWKS 暴露公钥,签验由 jose 完成。JWT 私钥只来自部署期 secret,不进入 SecretStore。替换 JWT 签名私钥会吊销所有由旧 key 签发的 user、agent 与 plugin token,属于破坏性轮换。 + +人类登录走 RFC 8628 device flow。RFC 定义的 authorize/token 端点返回 OAuth 错误形状;平台自有的 approve 动作仍返回 WattError。非交互环境通过 `WATT_TOKEN` 提供已签 user token。 + +Root Key 是只保存 SHA-256 摘要的长期引导凭据,明文只在创建时展示一次,不能作为 Bearer 调平台接口。它只能调用 root exchange 换发 admin user token;换发使用当前 JWT 私钥,不触发签名根轮换。 + +SecretStore 用专用 AES-256-GCM 根密钥加密运行时 secret,AAD 绑定 secret 名称,密文放 KV。`resolveSecret` 先查 Worker env,再查 SecretStore;JWT 私钥明确排除,避免信任根循环。List/Get 管理面只返回元数据,永不回显明文。 + +## WattError + +WattError 只有七个 code:`not_found`、`permission_denied`、`invalid_argument`、`conflict`、`unavailable`、`rate_limited`、`internal`。body 顶层就是 `{code,message,retryable}`,没有 `{error: ...}` 信封。 + +| HTTP | code | +|---|---| +| 400 | `invalid_argument` | +| 401、403 | `permission_denied` | +| 404 | `not_found` | +| 409 | `conflict` | +| 429 | `rate_limited` | +| 500 | `internal` | +| 501、503 | `unavailable` | + +只有 `rate_limited`、`unavailable`、`internal` 可以标 retryable。调用方只对 429 或 5xx 且 retryable 为 true 的响应做指数退避。OAuth RFC 端点是明确例外,不得为了统一外观把 OAuth error 包成 WattError。 diff --git a/llmdoc/contracts/events-and-results.mdx b/llmdoc/contracts/events-and-results.mdx new file mode 100644 index 0000000..d327737 --- /dev/null +++ b/llmdoc/contracts/events-and-results.mdx @@ -0,0 +1,68 @@ +--- +description: Event 信封、入站规约、去重、HITL 事件以及 Agent correlation 结果回传的现行契约。 +kind: reference +relations: + requires: + - architecture.mdx + related: + - runtime/agents-and-tools.mdx + - runtime/tasks-and-scheduler.mdx +code: + paths: + - Docs/Proto.md + - packages/core/src/event/** + - packages/core/src/eventbus/** + - packages/core/src/agent/correlation.ts + - packages/core/src/agent/routing.ts + - packages/gateway/src/event/** + - packages/gateway/src/agent/agent-correlation.ts +--- + +# Event 与结果回传契约 + +## Event 信封 + +Event 是一切入站、出站和平台内部消息的统一信封。核心字段包括平台生成的 `id` 与 `traceId`、`source`、点分类型名、可选 `session`、可选 `principal` 与 `channelUser`、可选 `dedupeKey`、`payload`、可选截断审计原文 `raw`、`occurredAt`。 + +- 整个序列化信封不得超过 128 KB。大内容先写 Context 或 artifact,再在 payload 中放引用。 +- ChannelAdapter Decode 必须给出业务发生时刻;Publish 若收到 `occurredAt` 就保留,缺省时才补平台接收时刻。 +- `channelUser` 存在且 `principal` 缺省时,平台调用 IdentityMapper 补齐;调用方已提供的 principal 不被覆盖。未映射渠道身份归到 `user:anonymous`。 +- 支持重投的渠道必须提供 dedupeKey。当前实现的默认幂等窗口是 24 小时,窗口内返回原 eventId,不重复留痕或投递。 +- channel-adapter plugin 以 plugin token 发布 IM 事件时,可以保留自身规约出的 `source.kind='im'`;普通 Platform API 发布不会借此伪造渠道主体。 + +## 订阅与实例键 + +EventBus 支持 AgentDefinition 声明、渠道生命周期自动建立和手工建立三类订阅。Agent sink 的 `instanceBy` 决定实例键: + +| 模式 | 实例键语义 | +|---|---| +| `singleton` | 一个 definition 全局一个实例 | +| `event` | 每个 event 一个实例 | +| `session` | 同一 event.session 稳定映射到同一实例 | + +`instanceBy='session'` 但事件没有 session 时返回 `invalid_argument`,不得静默退化为 singleton 或 event。类型匹配支持 `*` 全通配;前缀通配必须保留点分边界。 + +Publish 的持久化与 Queue 投递不是一个跨服务事务。实现先留痕再投递;Queue 失败时 best-effort 删除刚写的事件并返回可重试错误。调用方必须按 WattError 的 retryable 语义重试。 + +## 出站与 HITL + +出站事件类型固定为 `outbound.message`,payload 描述 channel、target、文本/blocks/actions 以及可选 replyTo。Agent 只发布事件;consumer 根据 channel adapter/plugin 完成 Encode 与 Send。 + +Task 进入 human checkpoint 时发布 `task.checkpoint`,内置 system subscriber 生成带 signal 的出站卡片。渠道点击 Decode 为 `im.action`;只有 `Authorizer.Check(task://, 'signal')` 允许时才调用 TaskManager.Signal。系统 subscriber 不占用户订阅表。 + +## Agent 结果协议 + +`Send` 或带 expect 的 `Spawn` 建立 correlation。结果事件是 `agent.result` 或 `agent.failed`;correlationId 只允许字母、数字、下划线和连字符,长度上限 80。 + +六条规则共同构成结果协议: + +1. 带 correlationId 的结果定向投递等待方,不走普通订阅匹配。 +2. 派生 Spawn 的结果自动回送父实例。 +3. 超时由平台代发 `agent.failed`,reason 为 `timeout`,不隐式重跑模型。 +4. 等待中的实例被终止时,平台代发 reason 为 `terminated`。 +5. 等待方先消失时,结果丢弃并写审计,EventStore 仍保留事件。 +6. 同一 correlationId 只有首个结果生效;后续结果丢弃并审计。 + +AgentCorrelation DO 以 `pending → delivering → settled` 三态交付。只有等待方确认接收后才 settle;失败时回滚到 pending 并让 Queue 重试。平台自产的 timeout/terminated 结果直接投递 waiter,避免被公共去重管道误吞。 + +ExpectSpec 可带 JSON Schema。当前校验支持 `type/properties/required/items/enum` 子集,默认最多尝试三次;每次失败把违规摘要反馈给模型,耗尽后发送 reason 为 `invalid_output` 的失败事件。 diff --git a/llmdoc/contracts/htbp-and-platform-apis.mdx b/llmdoc/contracts/htbp-and-platform-apis.mdx new file mode 100644 index 0000000..d174e62 --- /dev/null +++ b/llmdoc/contracts/htbp-and-platform-apis.mdx @@ -0,0 +1,81 @@ +--- +description: HTBP 三分支、Context/Tool 调用形状、Registry 动词、响应信封与 Plugin 传输的精确查询参考。 +kind: reference +relations: + requires: + - architecture.mdx + - contracts/auth-and-errors.mdx + related: + - runtime/agents-and-tools.mdx + - integrations/feishu-and-plugins.mdx +code: + paths: + - Docs/Proto.md + - Docs/Plugin.md + - packages/gateway/src/http/context-routes.ts + - packages/gateway/src/http/routes.ts + - packages/gateway/src/http/tools-proxy.ts + - packages/gateway/src/tools/** + - packages/toolbridge/vendor/** + - packages/cli/src/** +--- + +# HTBP 与 Platform API + +## 树与绑定 + +Agent 的逻辑入口是 `/htbp`: + +- `/htbp/tools//...`:工具发现与调用;物理宿主是 tool-bridge,gateway 做租户同步、路径转换与授权。 +- `/htbp/context//...`:Context Provider 四动词与 mount 解析;gateway 自持。 +- `/htbp/platform/`:agent、task、scheduler、event、policy、provider、plugin、metrics、audit、secret 等管理接口;gateway 自持。 + +所有消费面都用 Bearer token。工具 `~help` 需要认证,并按逐节点 read 授权裁剪;Context namespace 的 `~help` 当前在认证中间件之前开放,这是实现边界,不应外推成所有 `~help` 都公开。tool-bridge 提供 `~help` 与 `~skill`;Platform/Context 并非每个节点都完整实现 `~skill`。 + +## 调用形状 + +Platform 与 Context 的方法调用使用节点级 POST: + +```json +{"tool":"Write","arguments":{"name":"example"}} +``` + +`opts` 作为一个整体字段传递,不得平铺。Tool 调用不同:工具名位于 URL end-path,body 只有 arguments 信封: + +```text +POST /htbp/tools// +{"arguments":{...}} +``` + +gateway 按 mount 类型归一化:HTTP provider 收裸参数,MCP 与 builtin provider 收协议信封。调用方不得同时兼容多种猜测形状;解析失败应立即报错。 + +## Registry 与 Provider + +Registry 默认四动词是 List、Get、Write(upsert)、Update(patch),Delete 由领域按需提供。Page 的 cursor 是可选能力;多个当前 Registry 只实现 limit/filter 并返回 `{items}`,不能因为接口类型含 cursor 就假定已支持翻页。 + +ContextProvider 的必需方法是 List、Get、Update、Write,可选 Search、Watch、Delete。namespace mount 负责最长前缀 Resolve、readOnly 与 TTL;unmount 只移除 mount,不保证删除 provider 数据。TTL 以到期时刻含等为过期边界。 + +Context 成功响应形状由 gateway route tests 锁定: + +| 操作 | 响应 | +|---|---| +| Provider List | 裸 Page | +| Provider Get | `{entry}` | +| Provider Write/Update | `{meta}` | +| ContextRegistry Write | `{mount}` | + +CLI 与 Dashboard 必须精确解包这些形状,禁止 `body.meta ?? body` 一类双形态兜底。 + +ToolProvider 提供 List/Get/Call。ToolSpec 声明 effect、可选 scope、confirm 与 skill;有 scope 时它就是授权 action,否则 action 为 `invoke`。ToolRegistry 的 mount 可以 prefix、rename、hide 与覆盖描述,但不能绕过 gateway PEP。 + +## 平台接口族 + +平台接口按模块分为:ChannelRegistry/EventBus/EventStore、AgentRegistry/AgentRuntime、ContextRegistry、ToolRegistry、Authorizer/PolicyStore/IdentityMapper、Scheduler、TaskManager、ModelProviderRegistry/ModelRouter、Metrics/AuditLog、PluginRegistry/PluginLifecycle、SecretStore。CLI、Dashboard 与 manage agent 都只是这些接口的消费者。 + +EventStore 的查询 filter 包括类型、渠道、会话、时间以及 correlationId;manage chat 依赖 correlationId 精确轮询结果。AgentRuntime 的 Interrupt 仍是非 MVP 预留,不能把类型中的存在理解为已交付能力。Proto 的动态 Task orchestration 同样是非规范性预留。 + +## Plugin 传输 + +Plugin 必须暴露 healthPath 与 `~describe`,接口版本使用 `/v`,同 major 保持向后兼容,未知字段必须忽略。平台调用 Plugin 使用 platform token;Plugin 回调平台使用 scope 不超过 requiredGrants 的 plugin token。 + +当前 PluginRegistry.Write 会在落库和签 token 前做 health probe;对 `binding:` endpoint 视为平台内能力而跳过 HTTP 探活。方法集与 interfaceVersion 的 `~help/~describe` 契约校验尚未实现,注册成功目前不能证明 Plugin 的全部方法形状正确,集成验收仍需真实调用覆盖。 diff --git a/llmdoc/dashboard/architecture.mdx b/llmdoc/dashboard/architecture.mdx new file mode 100644 index 0000000..8968931 --- /dev/null +++ b/llmdoc/dashboard/architecture.mdx @@ -0,0 +1,50 @@ +--- +description: Dashboard 的 React Router SPA、同域静态分发、API client 边界以及 manage chat 的 correlation 轮询协议。 +kind: architecture +relations: + requires: + - architecture.mdx + - contracts/htbp-and-platform-apis.mdx + - contracts/events-and-results.mdx + related: + - runtime/agents-and-tools.mdx +code: + paths: + - packages/dashboard/** + - packages/gateway/src/index.ts + - packages/gateway/src/http/routes.ts + - packages/gateway/src/event/event-store.ts + - scripts/build-deploy.mjs +--- + +# Dashboard 架构 + +## 技术与分发 + +Dashboard 使用 React Router framework mode 的纯 SPA、Tailwind 和 vendored shadcn UI。构建产物写入静态 client 目录,再由 gateway Static Assets 同域托管;前端不拥有服务端状态或独立数据库。 + +路由覆盖 metrics、events、audit、agents、manage chat、tasks、cron、context、tools、policies、providers、channels、plugins、secrets 与 settings 等管理域。导航数量和具体组件由 routes/nav 源码决定,llmdoc 不保存视图计数快照。 + +## API client 边界 + +`app/lib/api` 是 Dashboard 的 Platform API 适配层。domain modules 复用 core request/error 处理并精确解包 gateway route 的唯一响应形状;组件不直接猜 response envelope。共享 wrapper 属于契约代码,修改时同时对照 gateway route 和 CLI 的真实消费方,不能只相信调查报告或旧 mock。 + +Dashboard、CLI 与 manage agent 是同一管理面的三个入口。新增 Dashboard 操作时必须确认已有 Platform API 和 Auth/Audit 语义;若只有前端能做,或前端通过专用未授权 endpoint 完成,就产生了管理旁路。 + +认证状态保存在浏览器 session/local UI 所需的最小范围,Bearer token 只用于 API 请求。Root Key 只提交给 root exchange 换 token,不作为一般 Authorization header,也不持久化回显。 + +## Manage chat + +新会话先 Spawn manage definition,但不带 expect;带 expect 的 Spawn 会把空输入送进 harness,造成一次无意义模型调用。每一轮用户消息再 Send 并携带 expect,得到 correlationId。 + +前端按 correlationId 轮询 EventStore.List,直到出现 `agent.result` 或 `agent.failed`,再渲染 result payload.output 或失败信息。EventStore 的 correlationId filter 是正式查询能力,避免前端拉取宽结果集后自行筛 payload。超时只终止前端等待,不改写平台 correlation 的首结果语义。 + +当前方案是有界轮询而不是 WebSocket/SSE;选择它是为了复用 EventStore 和同域 HTTP。若未来改推送,仍必须保持 correlationId、首结果胜出和失败事件的协议,不得新增第二套同步结果端点。 + +## 构建与维护边界 + +- React Router SPA 构建仍解析 server runtime 依赖,这些包必须在 production dependencies。 +- Tailwind 指令需要 Biome 的 Tailwind parser;build、类型生成和 vendored UI 目录不进入常规 lint。 +- 新增 `app/lib` 路径后用 git status/check-ignore 确认未被模板遗留的 `lib/` 规则吞掉。 +- API contract tests 用 gateway 形状 fixture;宽容 fallback 会让 mock 全绿而真实请求静默错配。 +- 前端 route 可以调整展示,但权限裁剪、resource URI、retryable 与错误 code 只能来自平台合同。 diff --git a/llmdoc/guides/phase-gate-workflow.md b/llmdoc/guides/phase-gate-workflow.md deleted file mode 100644 index 326af18..0000000 --- a/llmdoc/guides/phase-gate-workflow.md +++ /dev/null @@ -1,51 +0,0 @@ -# Phase 关门标准流程(Phase Gate Workflow) - -> 适用场景:某 Phase 的 DoD 全部勾选后,正式宣布该 Phase 关门之前。实测:Phase 0 关门(2026-07-02 Round 3)、Phase 1 关门(2026-07-02 Round 7),证据见 `PROGRESS.md` 对应轮次。 - -## 流程五步 - -### ① 重跑全部 DoD 命令,记证据 - -- 该 Phase 在 `DOD.md` 中的**每一条** DoD 命令都要在关门轮重新跑一遍(不吃历史证据)。 -- 主 assistant 亲自跑,每条记一行结果(命令 + exit code / 关键输出),入 `PROGRESS.md` 关门轮。 - -### ② 质量关口 Workflow:4 维 review → 对抗核查 - -- **4 维并行 review**:correctness / contract(与 Proto 契约一致性) / ops / test-quality,各维独立派 review agent(Phase 0/1 曾含渗透性安全维度共 5 维,Round 10 起按用户指示去掉该维度)。 -- **逐条对抗核查**:每条 BLOCKER/MAJOR finding 再派一个核查 agent,prompt 要求**"先假设这是误报去求证"**——拿着 finding 去读源码/规范原文,只有证伪失败才确认成立。 -- MINOR 不阻塞关门,记入 backlog(PROGRESS 遗留节)。 - -### ③ 确认项全修 + 复核 - -- 确认成立的 BLOCKER/MAJOR **全部修复**后才能关门。 -- 修复后主 assistant **亲自重跑**受影响的验证命令(verify / deploy / curl 等),不信任 worker 自报。 - -### ④ Docs 漂移回查(宪法优先) - -- review 中发现的规范缺口/矛盾/实现与规范的偏差,先修 `Docs/` 再改实现(宪法优先)。 -- 同步闭环 `llmdoc/memory/doc-gaps.md` 对应条目状态。 -- Phase 0 实例:修了 Proto §0.2(401/501 规范性补充)、§6.4c(引用错位 + cron 系统段规则)、Architecture 附B(watt- 前缀实名)。 - -### ⑤ PROGRESS 入账 + llmdoc 沉淀 - -- `PROGRESS.md` 写关门轮记录:DoD 重跑证据、finding 清单与修复、Docs 回写、MINOR 遗留。 -- 触发 llmdoc 沉淀:更新 `must/current-state.md`(阶段翻页 + 关门证据摘要)、相关 reference/guides、`memory/decisions/` 新决策。 - -## 实测经验(Phase 0,2026-07-02) - -- 5 维并行 + 逐条对抗核查**有效滤掉误报**:共 12 个 agents,7 条 MAJOR 对抗核查后 7/7 确认成立(评审期间另有 1 条 501 码问题已先行解决)。 -- 对抗核查 prompt 的关键措辞是要求核查者**先假设误报去求证**,避免核查者顺着原 finding 的叙事确认偏误。 -- 关门轮不做新功能,只做重跑、修复、回写三类动作。 - -## 实测经验(Phase 1,2026-07-02 Round 7) - -- **确认项修复可并行**:按"互不冲突的文件集"把修复拆给多个 worker(本轮 4 个)并行执行,主 assistant 复核 diff 并收口。 -- **跨包契约改动派发时必须列全消费方**:gateway List 返回形状变更(→ `{items}`)时漏列了 CLI 消费方 `cli/audit.ts`——worker 无权限修界外文件,且 CLI 侧测试 mock 掩盖了错配,只能靠主 assistant 收口时发现。拆任务时对每个契约变更显式枚举所有消费方(含测试 mock),要么划进同一 worker 的文件集,要么明确留给主 assistant 收口。 -- **关门轮 DoD 重跑可结合线上全链**:Phase 1 用真实部署跑通 device flow 全链(login → approve → whoami)作为 DoD 项 3 的关门证据,比只跑单测更硬。 - -## 实测经验(Phase 2/3,2026-07-03 Round 10/13) - -- **finding 先聚类找共同根因**:Phase 3 关门 vector provider 三条独立 MAJOR(截断丢数据 / List 违约 / 最终一致)同根——权威数据放错存储层;对症是"D1 sidecar"一个架构动作而非三个补丁。修复派发前先按文件/子系统聚类 finding,判断补丁 vs 重构。 -- **对抗核查连续四个 Phase 0 误报**:其价值不在滤误报,而在修正严重度、补齐修复所需代码事实、比对 doc-gaps 识别"已声明的实现自由"(核查 prompt 里加 doc-gaps/PROGRESS 声明比对指令有效)。 -- **验收指令置于派发 prompt 末尾显眼处**:worker idle ≠ done——曾有 worker 源码就位但测试未写即 idle,需 SendMessage 追要。派发时明确"验收命令跑绿并报告后才算完成"。 -- **禁宽容解析**:跨包响应形状解不到就报错(`body.meta ?? body` 型双形态兜底曾把错配从测试期一路掩盖到线上冒烟之后);形状唯一真源 = gateway 路由测试,CLI mock 照抄(toolchain-pitfalls §34)。 diff --git a/llmdoc/guides/toolchain-pitfalls.md b/llmdoc/guides/toolchain-pitfalls.md deleted file mode 100644 index 33982bc..0000000 --- a/llmdoc/guides/toolchain-pitfalls.md +++ /dev/null @@ -1,368 +0,0 @@ -# 工具链安装与测试配置的坑 - -> 适用场景:在本机安装依赖、配置 vitest-pool-workers 测试、调整 TS 工程、跑 wrangler provision/deploy 时。§1~5 为 Round 1(Phase 0 骨架)、§6~11 为 Round 2(资源 provision + 部署)、§12~14 为 Round 3(Phase 0 关门)、§15~20 为 Round 4~6(Phase 1 Auth)、§21~25 为 Round 7(Phase 1 关门)、§26~29 为 Round 9/10(Phase 2 Event Gateway + 关门)、§30~34 为 Round 12/13(Phase 3 Context Layer + 关门)、§36~40 为 Round 14~18(Phase 4 Tool+Agent + 关门)、§41~45 为 Round 19~21(Phase 5 Task+Scheduler)、§46~53 为 Round 23~32(Phase 6/7 + E2E)、§54~61 为 Round 33(可用性冲刺:plugin 化 + worktree 并行)、§62~64 为 Round 34(维护轮:飞书 agent 工具授权修复)、§65 为 R35 后信任根轮换断链修复、§66~68 为 Round 36(Dashboard RR7 重构)实测踩坑与已验证解法。 - -## 1. 大二进制下载超时(npm registry) - -- 现象:`@cloudflare/workerd-darwin-arm64`(约 32MB)经默认 npm registry 在本机反复 `UND_ERR_SOCKET` 超时。 -- 解法:`pnpm install --registry https://registry.npmmirror.com`。 - -## 2. pnpm 11.9 的 build 门禁 - -- workerd / esbuild / sharp 的 postinstall 需在 `pnpm-workspace.yaml` **同时**配置 `onlyBuiltDependencies` + `allowBuilds`。 -- 缺一则报 `ERR_PNPM_IGNORED_BUILDS`,workerd 二进制不落地(症状:wrangler/测试起不来)。 - -## 3. vitest-pool-workers 0.18(Vitest 4 时代)的新 API - -- 旧写法已移除:`defineWorkersConfig`(from `@cloudflare/vitest-pool-workers/config`)。 -- 新写法: - - ```ts - import { defineConfig } from 'vitest/config'; - import { cloudflareTest } from '@cloudflare/vitest-pool-workers'; - - export default defineConfig({ - plugins: [cloudflareTest({ wrangler: { configPath } })], - }); - ``` - -- 类型引用:`/// `。 -- 版本锚点:wrangler 锁 4.107.0 devDependency,对齐 vitest-pool-workers 0.18 捆绑版本(见 [../must/current-state.md](../must/current-state.md))。 - -## 4. TS 工程决策:noEmit + `.ts` 扩展名导入 - -- **不用** `tsc -b` / composite——与 `.ts` 扩展名导入冲突。 -- 采用:全局 `noEmit` + `allowImportingTsExtensions`;每包类型检查跑 `tsc --noEmit`;Node 26 原生 strip types 直接执行 `.ts`。 -- 坑:残留的 `dist/*.test.js` 会被 vitest 误抓——`dist/` 必须 gitignore 且不留旧产物。 - -## 5. Cloudflare 凭据验证 - -- 只用 `wrangler whoami`;`/user/tokens/verify` 对 Account-scoped token 必然误报。详见 [../must/current-state.md](../must/current-state.md) 凭据一节(此处仅交叉引用,不重复)。 - -## 6. wrangler 配 `routes` 后 workers.dev 默认关闭 - -- 现象:wrangler.jsonc 加了 `routes`(custom domain)后再 deploy,workers.dev 子域返回 404。 -- 原因:配置 `routes` 时 wrangler 默认把 workers.dev 子域关掉。 -- 解法:显式加 `"workers_dev": true` 保双 URL(custom domain + workers.dev 并存)。 - -## 7. wrangler env-token 模式的 stdout 污染与 `--json` 支持不齐 - -- env-token 模式(`CLOUDFLARE_API_TOKEN` 环境变量)下,**每条命令的 stdout 都会先打一段 whoami banner**,直接污染 `--json` 输出——不能天真 `JSON.parse(stdout)`。 -- 且 `--json` 支持不齐:`d1 list` / `kv namespace list` / `vectorize list` 支持;**`queues list` / `r2 bucket list` 不支持**。 -- 脚本判断资源是否存在的稳妥做法:对 list 输出做**资源名 substring 匹配**(`scripts/provision.mjs` 即此方案)。 - -## 8. JSONC 注入陷阱(wrangler.jsonc 回填) - -- 现象:向末尾带行注释的 wrangler.jsonc 追加属性后,wrangler 的 JSONC parser 报 `CommaExpected`。 -- 原因:逗号被补在了注释之后。逗号必须补在**最后一个真实 JSON token 后**(注释之前/之外),不能跟在行注释后面。 -- 隐蔽点:**爆点在 test 阶段而非 typecheck**(vitest-pool-workers 加载 wrangler 配置时才解析),typecheck 全绿也可能带着坏配置。 - -## 9. 新部署 Worker 的边缘传播窗口 - -- 现象:`wrangler deploy` 成功后立即 curl,首击可能 500 `error code: 1104`。 -- Round 7 补充:deploy 后**首批 curl 还可能命中旧 isolate**(观测到 404 纯文本 / 旧版本 body)——验证新行为前等几秒或重测一次,勿凭首击结果下结论。 -- 解法:验证脚本必须带重试(`scripts/smoke.ts` 已内置 5 次重试)。 - -## 10. Vectorize 绑定在本地测试环境 - -- vitest-pool-workers / miniflare 对 Vectorize 绑定只打 WARNING,不报错。 -- 本地测试**无需**从 wrangler.jsonc 注释掉 Vectorize 绑定。 - -## 11. 本机网络:ISP DNS 污染 `watt.pdjjq.org` - -- 现象:本机解析 `watt.pdjjq.org` 得到假 IP → TLS reset;CF 边缘本身正常(非平台问题)。 -- workaround:本机验证/E2E 用 workers.dev URL(`watt-gateway.shuaiqijianhao.workers.dev`),或 curl 加 `--doh-url https://1.1.1.1/dns-query`。 - -## 12. Biome 强制单行 import - -- Biome formatter 会把 import 语句压成单行;手工拆成多行会在 `pnpm verify`(lint 阶段)fail。 -- 写代码时直接保持单行 import,或改完跑一次 `biome format --write` 再 verify。 - -## 13. `scripts/lib/*.mjs` 在 typecheck 范围外 - -- `scripts/tsconfig.json` 只 `include: ["smoke.ts"]`(且 `checkJs: false`),`scripts/lib/*.mjs` 不进 typecheck。 -- 后果:`.mjs` 脚本库的类型错误 verify 抓不到。 -- 约定:新脚本库保持 `.mjs`(接受无类型检查),或者写 `.ts` 时**必须**主动加进 `scripts/tsconfig.json` 的 include。 - -## 14. provision 幂等的金标准检查 - -- 金标准 = **重跑 `pnpm provision` 后 `wrangler.jsonc` 字节级一致**(跑前跑后 MD5 对比)。 -- 仅看输出全 [exists] 不够——回填逻辑仍可能重写 marker 段造成 diff/绑定漂移;MD5 一致才证明真正幂等。 -- 关联坑:给某类绑定加 per-binding 新字段(如 D1 的 `migrations_dir`)时,必须同步改 provision 的 bindingsBlock 生成逻辑,否则重跑会把新字段抹掉(MD5 检查即为此设)。 - -## 15. 块注释内的 `*/` 字面提前闭合(oxc PARSE_ERROR) - -- 现象:在 `/* ... */` 块注释里写含 `*/` 的字面文本(如描述通配符 `agent_*/chain`),注释提前闭合 → oxc 报 PARSE_ERROR,**且报错行号定位到别处,极具误导性**。 -- 解法:块注释内避免 `*/` 序列(改写为 `agent_* / chain` 或用行注释 `//`)。遇到定位不明的 PARSE_ERROR,先全文搜块注释里的 `*/`。 - -## 16. vitest-pool-workers 跑 D1 migrations 的接线三件套 - -缺一即测试里 D1 无表(`no such table`): - -1. vitest config 顶层 `await readD1Migrations(migrationsDir)` 读出 migrations; -2. 经 `miniflare.bindings`(如 `TEST_MIGRATIONS`)注入测试环境; -3. 测试 setup 里 `applyD1Migrations(env.DB, env.TEST_MIGRATIONS)`; -4. 另需 `declare global { interface Cloudflare { Env: ... } }`(或对应 Env 声明)让 `env.TEST_MIGRATIONS` 过 typecheck。 - -## 17. coverage 产物污染 lint - -- `coverage/` 目录不 gitignore 且不在 biome 排除里,会被 lint 抓到爆上百错。 -- 解法:`.gitignore` 加 `coverage/`,且 biome 配置 `files.ignore`(或等效排除)同步加。 - -## 18. `pnpm --filter X test -- --coverage` 不透传 - -- `--coverage` 经 pnpm `--` 透传到 vitest 会丢失,静默不生效。 -- 解法:把 `--coverage`(及覆盖率门禁)直接写进该包 package.json 的 `test` 脚本,不靠命令行透传。 - -## 19. 脚本 stdout 纯净化模式(供 `$()` 捕获) - -- 场景:脚本输出会被 shell `$()` 或管道捕获时(如 `sign-admin-token.mjs` 输出 token),任何子进程/日志混入 stdout 都会污染捕获值。 -- 模式:脚本内所有子进程(`wrangler` 等)的 stdout 显式重定向到 stderr(`stdio: ['ignore','inherit'→2,'inherit']` 或 pipe 后写 `process.stderr`),日志一律 `console.error`;**只有最终目标值走 stdout**。 - -## 20. `wrangler secret put` 后的传播窗口 - -- secret put 成功返回后,边缘约有 ~15s 传播窗口,期间线上仍用旧值(或无值)。 -- 验证脚本在 put 后 `sleep 15`(或重试探测)再断言,否则假失败。 - -## 21. pnpm `--filter` 以 package.json name 为准,且空匹配 exit 0 - -- 包名以各包 `package.json` 的 `name` 字段为准,不是目录名:CLI 包是 **`watt-cli`** 不是 `@watt/cli`。 -- 更危险的是:**filter 匹配不到任何包时 exit 0**,命令"成功"但什么都没跑——测试假通过。 -- 派任务 / 写脚本前先核对包名(`pnpm ls -r --depth -1`)。 - -## 22. 本机验证脚本的 base_url 缺省 - -- 本机 fetch 线上的验证脚本,base_url 缺省应取 workers.dev(`watt-gateway.shuaiqijianhao.workers.dev`),留环境变量覆盖(如 `WATT_JWKS_BASE_URL`)。 -- **勿直接复用 `.env` 的 `WATT_BASE_URL`**——它指向被本机 DNS 污染的 `watt.pdjjq.org`(见 §11),脚本会假失败。 - -## 23. vitest-pool-workers 同一测试文件内共享 isolate - -- 同一个 test 文件的所有用例跑在同一个 workerd isolate 里:**模块级单例(如 seed 的 once-guard Promise)跨用例存活**。 -- 后果:凡是 beforeEach 里 clearDb 的测试文件,种子引导会被 once-guard 短路,后续用例的种子/授权失真。 -- 解法:clearDb 的同时 reset 单例(gateway 导出 `resetSeedGuardForTests` 即为此设)。 - -## 24. Hono matcher 在首个请求后锁定 - -- Hono app 处理过第一个请求后 route matcher 即构建锁定,之后再 `app.get()` 追加路由会报 **"matcher already built"**。 -- 测试里要动态挂路由的,必须在任何 fetch 之前于模块顶层注册。 - -## 25. 认证中间件先于 notFound 执行(501 占位的注册位置) - -- notFound 兜底在中间件链末端:若规范树 501 占位靠 notFound 判前缀实现,请求会先被认证中间件拦成 401。 -- §11.3a 语义要求 501 优先于 401——占位路由必须**显式注册在认证中间件之前**(gateway `index.ts` 即此做法)。 - -## 26. gateway src 直接 import zod 在 workerd 解析失败 - -- 现象:gateway 源码直接 `import { z } from 'zod'`,typecheck 绿但 workerd 运行时报 `Cannot find package 'zod'`。 -- 原因:zod 被 pnpm workspace hoisting 到别处,gateway 包解析不到。 -- 解法:gateway 内用手写 type guard,或需要 zod schema 时**经 `@watt/core` 导出**(core 是 zod 的直接依赖方)。 - -## 27. TextDecoder `fatal` 须同时给 `ignoreBOM`(TS2345) - -- 现象:`new TextDecoder('utf-8', { fatal: true })` 在本工程 lib 类型下报 TS2345。 -- 解法:同时给两个选项:`{ fatal: true, ignoreBOM: false }`(或显式 true),否则 typecheck 红。 - -## 28. 本机代理:workers.dev 直连偶发超时(Round 10 起) - -- 现象:本机直连 `watt-gateway.shuaiqijianhao.workers.dev` 也开始偶发超时(此前只有 watt.pdjjq.org DNS 污染,见 §11);CF 边缘本身正常。 -- 解法:验证命令带 `https_proxy=http://127.0.0.1:7890`;**Node 脚本**(如 `sign-admin-token.mjs`)不认 env proxy,需另加 `NODE_USE_ENV_PROXY=1`。 - -## 29. 多 teammate 共享工作树的并行修复纪律 - -- typecheck 红时先判断归属:用 `git show HEAD:` 对比基线,确认是自己的在途改动还是他人的——不要见红就修别人的文件。 -- **禁用 `git stash`**:会连带他人在途改动一起 stash 掉,恢复时产生冲突/丢改动。 - -## 30. R2 list 默认不返回 customMetadata - -- `bucket.list()` 结果对象缺省不含 customMetadata——用 customMetadata 承载 meta 时,list 出来 meta 全空(Round 12 线上冒烟才暴露)。 -- 解法:`bucket.list({ include: ['customMetadata'] })`。**本包 workers-types 未收录该字段**,需类型拓宽(as 或局部接口扩展)才过 typecheck。 - -## 31. DO RPC 联合返回类型经 type guard 后 narrow 成 never - -- DO RPC 方法返回联合类型(如 `Mount | WattError`)时,调用侧过 `isWattError()` guard 后另一分支被 narrow 成 `never`(Cloudflare RPC types 的已知问题——RPC stub 包装破坏了判别式收窄)。 -- 解法:guard 之后显式 `as` 标注目标类型,勿指望自动收窄。 - -## 32. Vectorize 变更异步最终一致,read-after-write 不可靠 - -- Vectorize upsert/delete 是**异步最终一致**:写后立即 query/getByIds 可能读不到(或读到旧值),List 语义无法可靠实现。 -- 解法:**不要把权威数据放 Vectorize metadata**——需要 read-after-write 语义就上 D1 sidecar(权威数据在 D1,Vectorize 只存 embedding+引用;gateway vector provider 即此架构,见 doc-gap #27①)。 - -## 33. KV namespace list 在 https_proxy 下超时 → provision 误判走 create - -- 现象:带 `https_proxy` 环境跑 `pnpm provision`,`kv namespace list` 可能超时/空结果,脚本判定资源不存在走 create,报 already exists 假失败(Round 12 实测)。 -- 解法:**跑 provision 前 unset 代理**(provision 走 CF API 不需要本机代理;代理只在 curl workers.dev 验证时用,见 §28)。 - -## 34. CLI/服务端响应形状以 gateway 路由测试为真源,禁双形态兜底 - -- 教训:CLI 与 gateway "双方各按自己理解写",独立 mock 全绿但线上错配——Phase 3 连出三次线上 bug(put 缺必填 contentType / cat 未解包 `{entry}` / put·patch 双形态兜底掩盖漂移)。 -- 纪律:响应形状**唯一真源 = gateway 路由测试锁定的形状**(Get→`{entry}`、Write/Update→`{meta}`、List→裸 Page、管理面 Write→`{mount}`);CLI mock 必须照抄真源并在文件头声明出处;**禁止写"两种形状都能解"的兜底**——它只会把契约漂移从测试期推迟到线上。 - -## 35. AI 绑定触发 vitest-pool-workers 远程代理会话 - -- 现象:wrangler.jsonc 有 `"ai"` 绑定时,vitest-pool-workers 会尝试为 AI 起远程代理会话,多账户非交互环境必失败(`user account selection unavailable`)。 -- 解法:vitest.config 里 `remoteBindings: false`;vector provider 等对 AI 的调用走依赖注入 fake(测试从不读 env.AI),真实 embeddings 验证留部署后冒烟(Round 12 实测)。 - -## 36. agents 包传递依赖 core-js-pure 的 build script 阻塞 vitest - -- 现象:装 `agents`(Agents SDK)后,其传递依赖 `core-js-pure` 带 postinstall build script,被 pnpm 门禁 ignore → vitest 的 depsStatusCheck 检出 ignored build 并阻塞测试启动。 -- 解法:`pnpm-workspace.yaml` 的 `allowBuilds` 显式声明 `core-js-pure: false`(明确"不构建也没关系",消除 ignored 状态),vitest 恢复运行。 - -## 37. ai SDK 版本必须跟 agents 的 peer 走 - -- 现象:`agents@0.17.3` 的 peer 是 `ai@^6`;直接装最新 `ai@7` 会 peer 冲突(安装期报错或运行时行为漂移)。 -- 纪律:装 Vercel AI SDK 前先查 `agents` 当前版本的 peerDependencies,锁对应大版本(本仓库 ai@6 + @ai-sdk/anthropic@3,决策见 memory/decisions/model-call-sdk.md)。升级 agents 时同步核对 ai peer。 - -## 38. HTBP tools call 的请求形状契约(§34 的请求形状对偶) - -- 上游 tool-bridge 契约:**工具名走 URL end-path**(`...//`),**body 是 `{arguments}` 信封**;http adapter 会把 body 整包转发给目标端点。 -- 教训:CLI 曾把 `{tool,arguments}` 发到节点级 URL——对 http/mcp provider 必炸,但线上 echo 服务"什么都吞"恰好掩盖(Round 18 BLOCKER)。 -- 修法:CLI call 拼 end-path + `{arguments}` 信封;gateway 代理按 provider 归一化——**http 拆信封发裸参数、mcp/builtin 透传**。 -- 测试纪律:**fake 必须按节点 type 分派、忠实上游各 provider 语义**——echo 型"什么都吞"的测试替身是契约漂移的天然掩盖器(与 §34 同根:真源只能有一个)。 - -## 39. 系统代发事件与外部事件共用消费管道时的去重误杀 - -- 现象:correlation 超时代发 agent.failed 走与外部事件相同的消费/去重管道,**去重判定把自产事件当重复吞掉**——failed 永远到不了 waiter(Round 18 BLOCKER,§3.4 规则 3/4 根因)。 -- 纪律:系统代发路径**要么直投**(本仓库解法:AgentCorrelation DO 直投 waiter,绕开 routeResult 去重)、**要么显式标记绕开去重**;不要让自产事件裸走公共去重面。 -- 关联修法:routeResult 投递改三态 peek(delivering)→deliver→confirm,失败 rollback + msg.retry,投递成功才 settle。 - -## 40. R2 条件写用 `.etag`,不是 `.httpEtag` - -- R2 对象的 `httpEtag` 带双引号(HTTP 头格式),传给 `onlyIf: { etagMatches }` 会匹配失败/报错;条件写必须用**裸 `.etag`** 字段(gateway `src/context/providers/object.ts` 即此写法)。 - -## 41. 本地 Workflows 实例 hibernate 时 `status()` 仍报 running - -- 现象:vitest-pool-workers 里 Workflows 实例在 `step.waitForEvent` 处 hibernate,`instance.status()` 仍报 `'running'`——靠 `waitForStatus('waiting')` 的断言永远等不到。 -- 解法:测试断言以 **TaskStore 状态表为真源**(`waiting_human` 由引擎步骤落库,是平台对外可查的权威态),轮询状态表而非 instance.status(gateway `test/workflow-task.test.ts` 的 `waitForCheckpoint` 即此写法)。 - -## 42. step.do / waitForEvent 泛型 `T extends Rpc.Serializable` 的类型约束 - -- bare `unknown` 不满足约束;递归 JSON 类型(`Json = ... | Json[]` 类)会触发 **TS2589 深实例化超限**。 -- 解法:事件 payload 用**一层深的 FlatRecord**(`{ [k: string]: string|number|boolean|null }`,见 `watt-task-workflow.ts` L80-92)——顶层判定够用;嵌套结构的全量不在 Workflow 侧读,存 TaskStore 时用 `unknown` 承载。 - -## 43. agents 包无 cron 解析纯函数导出 - -- `agents/schedule` 只导出给 LLM 生成用的 zod schema(cron 当 `z.string()` 透传);内部 `getNextCronTime` 依赖第三方 cron-schedule 库但**未 re-export 为纯函数**。 -- 后果:core(零运行时依赖)需要 cron 解析时无现成导出可用——自实现分钟级五段子集(`core/src/task/cron.ts`,子集边界与拒绝面在文件头声明)。 - -## 44. Dynamic Worker Loader:本地无绑定 + env 字段 structured clone 拒收 - -- ① **本地 vitest-pool-workers 无 LOADER 绑定**(wrangler worker-loader binding 线上 open beta——DJJ 账户已开通实证;本地 workerd 未在 pool-workers 暴露)——依赖 LOADER 的路径必须做成**可注入降级**(ScriptRunner 抽象:生产真 isolate / 测试 fake,见 `gateway/src/scheduler/script-runner.ts` 与 [../memory/decisions/scheduler-script-runner.md](../memory/decisions/scheduler-script-runner.md))。 -- ② **loader 的 `env` 字段走 structured clone**:plain object 的闭包函数和 **RpcTarget 实例都会被拒**(线上部署冒烟两次实测 "could not be cloned")。能力 binding 必须经 **entrypoint RPC 调用参数**传入——Cap'n Web 只在 RPC 边界把 RpcTarget 转 stub;故脚本入口约定 `run(watt)` 参数注入(script-runner.ts 文件头有完整迭代记录)。 - -## 45. getAgentByName 的 stub 直接传 runInDurableObject,勿 cast 成实例类 - -- `getAgentByName` 返回 DO stub;传给 `runInDurableObject(stub, fn)` 时直接用 stub 类型即可,fn 参数里才是真实 instance。 -- 把 stub cast 成实例类会撞类型冲突(如 `name` 属性:实例类是 `string`,stub 包装后是 `Promise`)——与 §31 的 RPC 包装破坏收窄同源(gateway `test/scheduler-hub.test.ts` 即正确写法)。 - -## 46. ai@6 的 `result.usage` 只算最后一步——多步 tool loop 计量必须取 `totalUsage` - -`generateText` 带 tools + `stopWhen: stepCountIs(N)` 时跑多步;`result.usage` 语义是"最后一步的用量"、`result.totalUsage` 才是全步累计(ai@6.0.219 类型定义原文)。取 usage 会系统性漏账前面所有 step 的 token(manage/* 对话恰是大头)。无 tools 单步时二者相等,零回归。锁定测试:fake fetchImpl 两步应答(tool_use→end_turn)断言 usage=两步之和(`test/anthropic-caller-usage.test.ts`)。 - -## 47. DO `idFromName` 对任意名字隐式创建幽灵实例——投递面必须先查索引 - -`idFromName(<拼错的 id>)` 不报错,直接创建一个 INITIAL_STATE 的新 DO(AgentInstance 缺省 harness=echo、不在实例索引里)。Send 直投这种幽灵会**静默回显**而非报错——R27 实测把 `r27-gate` 误写成 `manage/cron#r27-gate`,send accepted、onEvent ok、无任何事件留痕,排查耗时显著(tail 日志才看出 11ms onEvent 不可能调过模型)。修复模式:对外的投递入口先查权威索引(correlation 实例表),未 Spawn → not_found。教训通用:**任何 idFromName 消费面都要考虑"名字不存在"不是错误而是隐式创建**。 - -## 48. vitest-pool-workers 0.18(Vite 插件 API)没有 `fetchMock` 导出 - -`import { fetchMock } from 'cloudflare:test'` 得 undefined(dist 里根本没有该实现)。测试里拦截 Worker 出站 fetch 的替代:被测模块暴露 `setFetchForTests(fetchImpl)` 模块级钩子(对齐 resetSeedGuardForTests 习惯)——SELF.fetch 与测试代码共享同一 isolate 的模块态,钩子对路由生效(plugin-registry 注册探活即此模式)。 - -## 49. 飞书 WSClient(node-sdk 1.68.0)构造参数有未导出的 `onReady`/`onError` 回调 - -`start()` 是 async 且内部自持重连,正常路径永不返回也永不抛——把它包进只在同步 throw 时 reject 的 Promise 是死代码(连接后永不 settle,外层监督循环不可达)。SDK 全部终态放弃路径(鉴权失败/重连耗尽/autoReconnect 关)都必然 `safeInvoke('onError')`(lib/index.js L89250/89264 等)——正确写法:构造参数传 `onError: reject`(类型未导出,经 Record 注入),并 `Promise.resolve(start()).catch(reject)` 接住异步 rejection 防 unhandledRejection。SDK 自愈型断线走 onReconnecting,不 settle。 - -## 50. node --experimental-strip-types 不支持 constructor parameter properties - -`constructor(readonly x: number)` 在 strip-only 模式抛 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(参数属性是需要代码生成的 TS 语法,不是纯类型注解)。scripts/ 下直接 node 跑的 .ts 文件要用显式字段声明 + 构造函数赋值。enum、namespace 同理。 - -## 51. script 的 watt.publish 发 outbound.message 会触发出站 Check——注入的 Authorizer 必须播种 cronJobs - -event-bus publish ① 对 type=outbound.message 做 Check(event:///,'write'),claims 带 cron: 链段;平台 newAuthorizer 的 cronJobs 索引恒空 → core authorize 步骤 3 查无此 job → 误判 "cron job disabled/deleted"(R28 E2E-6 实测:job 明明 enabled)。修复模式 = script 侧传本 job 播种的判定包装(cronJobs: {[job.id]: job})。通用教训:**凡 claims 带链段(cron:/instance)的调用路径,判定点必须能解析链段对应的数据面**——用平台默认 Authorizer 前先查它的索引是不是空的。另注意 grants 前缀通配要显式星号(`event://*`;`event://` 是精确匹配,match.ts §6.2)。 - -## 52. E2E 长跑最常见 FAIL 源 = admin token 1h 过期 - -`sign-admin-token.mjs` 签发的 token TTL 1h;六条 E2E 串行 + 部署间隔很容易越界,症状是脚本中途 401("invalid or expired token")——不是代码问题。纪律:**每次全量 e2e 前重签**(需双身份时 `--rotate --extra user:staff=staff` 同轮换双签——两次 --rotate 会互相吊销)。另一个模式:E2E 脚本的清理必须有 `process.on('exit')` 兜底(断言失败即 process.exit(1) 会跳过顺序清理——e2e-6 曾因此可能遗留每天 09:00 真实触发的 CronJob);注意 exit 钩子引用的 let 变量声明须在顶层 await 之前(TDZ)。 - -## 53. 平台 Authorizer 空索引对带 agent_def 的 claims 同样误拒(§51 的 agent 面) - -newAuthorizer 的 agentDefs/cronJobs/instances 恒空——claims 带 agent_def 时 core authorize 步骤 2 查无此 def 即 deny "agent definition not found"。agent 主体(如 lurker 出站)要过 Check 必须播种本 def(`agentDefs: {[def.name]: def}`)走 core authorize,审计单独补。与 §51(cron 链段)同根:**判定点必须能解析 claims 引用的数据面**。(R33 系统性收口:Authorizer 接 AgentDefLoader 惰性播种,见 memory/decisions/agent-spawn-revive-and-defloader.md。) - -## 54. 同账户 worker→worker 经 workers.dev URL 被平台拦截(收 404 非 1042) - -同账户下 gateway 经 HTTPS 调自家 plugin 的 workers.dev URL(探活/Send)一律收 **404**(不是文档所述的 1042 错误码,极易误判为路由没注册)。同账户 plugin 互调必须走 **service binding**(`binding:` 形态),跨账户才走 HTTPS(R33 实测,决策见 memory/decisions/plugin-outbound-dispatcher.md)。 - -## 55. workers.dev 境内被干扰:境内回调方(飞书等)3s 握手必超时 - -飞书事件订阅对 workers.dev URL 的回调验证必然超时失败。回调面必须挂 **custom domain**(如 `watt-feishu.pdjjq.org`);注意 Universal SSL 只覆盖一级子域,勿用二级子域(证书不覆盖)。 - -## 56. agent worktree(.claude/worktrees/*)嵌套 biome.jsonc 干扰 biome check - -worktree 内嵌套的 biome.jsonc 会让根目录 `biome check .` 报错(根 biome 配置已 ignore `**/.claude`);而在 worktree 内跑 `biome check .` 则命中 0 文件 exit 1——worktree 内验证要用**显式文件路径**,或回主仓库根跑。 - -## 57. 并行 worktree 基线漂移:worker 首步必须核对 HEAD - -worktree 可能从旧 commit 切出(创建时机早于最新 main)——并行 worker 首步必须 `git rev-parse HEAD` 对照 main 并 fast-forward,否则在过期基线上开发(R33 P1/P2 均踩)。 - -## 58. zsh 无 `${!var}` 间接展开且管道右侧仍执行——空值写进 wrangler secret - -zsh 对 `${!var}` 报 bad substitution,但**管道右侧的 `wrangler secret put` 照常执行**,把空串静默写进 secret。批量 put 必须 `bash -c` 执行,且**逐个校验值非空**后再 put。 - -## 59. `.env` 值位为注释的"假非空"(R27 §同类再现) - -`FEISHU_ENCRYPT_KEY=<空格+#注释>` 这种行,grep 判"行存在且等号后有内容"会误判已配——判非空必须**先 strip 行内注释**再看长度。 - -## 60. 新 worktree 的 per-package node_modules 不全 - -在 worktree 里 `pnpm add` 只补 touched 包的 node_modules,其余包缺依赖——跑 esbuild/vitest 前先全量 `pnpm install`。 - -## 61. 飞书验签是纯 sha256 拼接非 HMAC;bot 收非 @ 群消息取决于权限 - -- 签名 = `sha256(timestamp + nonce + encrypt_key + body)`,直接拼接**无分隔符、非 HMAC**——按 HMAC 实现永远验不过。 -- bot 能否收到**非 @ 的群消息**取决于应用是否有「接收群聊中所有消息」权限——缺该权限则只有 @ 消息可达,lurker 的 scratch 上下文永远 0 条。 - -## 62. agent 主体工具访问是两关授权:policy(步骤 1)+ def grants(步骤 2),缺一即拒 - -- §6.4c 对 agent 主体是两关判定,且**步骤 1 先拦时步骤 2 的缺口完全不可见**——只补 policy 就宣布修好,下一次调用会撞第二层拒绝(R34 实测:飞书群 lurker 调 tool://test 先后被两关各拒一次)。 -- deny 文案可区分卡在哪关:步骤 1 = **"principal not permitted"**(主体无 allow policy)、步骤 2 = **"agent definition grant exceeded"**(def grants 不含该资源)。 -- 排查顺序:先 `watt policy`(查主体对资源的 allow)→ 再 `watt agent get `(查 def 的 grants 与 toolScopes 现值——**源码种子只证初始值,线上现值必须 CLI 实查**)。 -- 修复纪律:**一次盘双侧**——补 policy 的同轮核对 def grants(及 toolScopes 前缀约束),三闸门齐了再走真实主体路径验证(admin 旁路对授权修复零证明力,admin 种子策略 allow-all)。 - -## 63. AgentRegistry 的 def Write 是整体覆盖——编排脚本重跑会冲掉部署侧 toolScopes/grants - -- `watt setup feishu` 第③步与 `e2e-3` 都内联 `toolScopes:[]` 的 def 字面(`lurker.ts`/`setup.ts` 默认值),重跑即把部署侧手工注入的 toolScopes/grants **整体冲回默认**(R34 线上 def 的 toolScopes 漂移即此源头)。 -- 运行中实例靠 spawn 时的旧快照**暂不受影响**(实例行为≠def 现值),但 re-Spawn 按当前 def 重新快照——冲掉即丢。 -- 运维纪律:修复只跑需要的子步骤(如 pluginToken 重签只单跑 `watt plugin register channel-feishu`,勿重跑完整 setup);改 def 前先 `watt agent get` 备份现值。 - -## 64. CLI `--json` 是全局选项,必须放在子命令之前 - -- `watt event tail ... --json` 尾部的 `--json` 被**静默忽略**(正确:`watt --json event tail ...`)——轮询解析不到 JSON,R34 曾空转 60s 误判「无出站」。 -- 轮询类验证首轮就零输出时,先怀疑命令形状再怀疑系统。(与 §7 的 banner 污染是不同坑:那是 wrangler,这是 watt CLI 的选项位置语义。) - -## 65. 信任根轮换后 pluginToken 静默失效——标准诊断与修复流程 - -信任根轮换(admin `--rotate` 或 R35 Root Key 改造这类换根动作)会连坐吊销 pluginToken,症状是**入站消息静默消失**(plugin 收到飞书回调但 Publish 401 被丢弃,飞书侧收 502;平台无任何入站留痕)。标准流程(R35 后实测跑通): - -1. 查 `watt-events` 最近入站时间戳——判定"消息没进来"而非"进来了没回"。 -2. 打 plugin `/healthz`——排除 worker 本身挂掉。 -3. challenge 握手探测——排除验签/路由层问题。 -4. 用 `FEISHU_VERIFICATION_TOKEN` 构造**假 `im.message.receive_v1`**(用假 chat_id 防打扰真实群)打 `/webhook/event`,看 Publish 环节状态——**401 即 pluginToken 死**。 -5. 单跑 `watt plugin register channel-feishu` 重签 + `wrangler secret put` 到 plugin worker(stdin 直 put)——**勿跑完整 setup feishu**(其 def Write 会冲掉部署侧 toolScopes/grants,§63)。 -6. 假事件重放收 `{"ok":true}` + D1 落库确认,再以真人群消息双向验证收口。 - -通用教训:**分段假事件探测能把"静默断链"精确定位到具体环节**(握手/验签/Publish 各自独立可测);凡换信任根,主动核查全部派生凭据(pluginToken 等)而非等线上断链。 - -## 66. gitignore 的 Python 模板 `lib/` 规则静默吞掉 `packages/*/app/lib/` - -- 根 `.gitignore` 沿用的 Python 模板含裸 `lib/` 规则——匹配**任意层级**的 lib 目录,R36 曾把 dashboard 的 `app/lib/`(整个 api 层)静默排除在版本控制外,本地全绿但文件从未入库。 -- 解法:收窄为 `/lib/`(仅根目录);用 `git check-ignore -v ` 验证具体文件命中哪条规则。 -- 通用教训:新增深层目录后 `git status` 看不到预期新文件时,先怀疑 gitignore 模板遗产规则。 - -## 67. RR7 SPA 模式(`ssr:false`)构建仍需 `@react-router/node` + `isbot` 在 dependencies - -- React Router 7 framework mode 即使 `ssr: false`,`react-router build` 仍会解析 server runtime——dependencies 缺 `@react-router/node` 与 `isbot` 时报 **"Could not determine server runtime"** 构建失败(devDependencies 不够)。 -- `react-router typegen` 同理依赖这两个包。 - -## 68. biome 对 Tailwind v4 指令与 RR7 产物目录的配置要求 - -- Tailwind v4 的 CSS 指令(`@theme`/`@utility` 等)会被 biome CSS 解析器报错——需配 `css.parser.tailwindDirectives: true`。 -- RR7 的构建产物 `build/` 与类型生成目录 `.react-router/` 必须加进 biome `files` 排除,否则 check 扫产物噪声爆炸。 -- shadcn 的 `app/components/ui/` 按 vendored 代码排除不 lint(与 toolbridge vendor 同一纪律)。 diff --git a/llmdoc/index.md b/llmdoc/index.md deleted file mode 100644 index 06b707f..0000000 --- a/llmdoc/index.md +++ /dev/null @@ -1,68 +0,0 @@ -# llmdoc 全局文档地图 - -> Watt:构建在 Cloudflare 上的 Agent Infra 平台,由持续开发 loop 按 DOD Phase 0~7 增量实现(**Phase 0~7 全部关门 + Phase 6 ② 采证闭环(R33 webhook plugin 真实群消息)——项目全部 Done**,转入维护/可用性迭代;R33 完成可用性冲刺:飞书 plugin 化 / htbp 工具注入 / SecretStore / CLI npm 化 / watt init / dashboard 配置页)。规格真源在 `Docs/`,本目录是其检索层与过程记忆。启动阅读顺序见 [startup.md](startup.md)(此处不重复)。 - -## 目录用途 - -| 目录 | 用途 | -|---|---| -| `must/` | 每轮必读的小文档(执行契约 + 当前状态) | -| `overview/` | 项目定位、验收基准、路线图 | -| `architecture/` | 模块边界、数据流、全局模式 | -| `guides/` | 单一工作流指南(随实现轮次生长) | -| `reference/` | 稳定查表事实(接口契约地图、外部事实) | -| `memory/` | 过程记忆:`decisions/`(决策记录)、`reflections/`(流程反思,reflector 维护)、`doc-gaps.md`(Docs 缺口台账) | - -## 文档清单 - -- [must/loop-contract.md](must/loop-contract.md) — 每轮循环执行契约浓缩:四条纪律、成熟框架优先表、五步流程、tool-bridge 上游通道、止损规则。 -- [must/current-state.md](must/current-state.md) — 当前状态快照:Phase 进度、源码/部署现状、本机工具链、凭据状态与空缺(随轮次更新)。 -- [overview/project-overview.md](overview/project-overview.md) — 项目定位、六个 User Case、Phase 0~7 路线图、全局 Done 五条。 -- [architecture/modules-and-flows.md](architecture/modules-and-flows.md) — M1~M11 模块表、三大 Registry 与单一 HTBP 消费面、数据流、HITL 链路、Dashboard manage 对话链路、CLI 命令表、Plugin 四类契约、引导顺序。 -- [guides/toolchain-pitfalls.md](guides/toolchain-pitfalls.md) — 本仓库工具链与 wrangler 部署的坑:registry 超时、pnpm build 门禁、vitest-pool-workers 新 API(含 D1 migrations 接线、同文件共享 isolate、AI 绑定 remoteBindings)、TS noEmit、workers_dev 默认关闭、--json banner 污染、JSONC 注入、边缘传播重试(含旧 isolate 首击)、本机 DNS 污染 workaround、Biome 单行 import、块注释 `*/` 字面、coverage 排除、--coverage 不透传、脚本 stdout 纯净化、secret 传播窗口、provision 幂等金标准、pnpm --filter 空匹配假通过、验证脚本 base_url 缺省、Hono matcher 锁定、501 占位注册位置、gateway 直接 import zod 解析失败、TextDecoder fatal+ignoreBOM、本机代理(workers.dev 超时→https_proxy)、共享工作树并行修复纪律、R2 list include customMetadata、DO RPC 联合类型 narrow-to-never、Vectorize 最终一致需 D1 sidecar、provision 前 unset 代理、响应形状真源禁双形态兜底、core-js-pure build 门禁阻塞 vitest、ai SDK 跟 agents peer 锁版本、HTBP call 请求形状契约(end-path+{arguments} 信封、fake 按节点 type 分派)、系统代发事件绕开去重管道、R2 条件写用 .etag 非 .httpEtag、本地 Workflows hibernate 时 status 报 running(断言以状态表为真源)、step.do/waitForEvent 泛型 Serializable 约束(FlatRecord 解 TS2589)、agents 包无 cron 纯函数导出、Dynamic Worker Loader 本地无绑定+env structured clone 拒收(能力经 RPC 参数注入)、getAgentByName stub 勿 cast 实例类、ai@6 多步 usage 取 totalUsage、idFromName 幽灵 DO(投递先查索引)、pool-workers 0.18 无 fetchMock(*ForTests 钩子)、飞书 WSClient onError 构造回调(settle 语义)、strip-only 无参数属性、script 出站 Check 播种 cronJobs、e2e token 1h 过期与 exit 兜底清理、Authorizer 空索引对 agent_def claims 误拒(播种 agentDefs)、同账户 workers.dev 互调被拦(404,走 service binding)、workers.dev 境内干扰须 custom domain(一级子域)、worktree 嵌套 biome/基线漂移 ff/node_modules 不全、zsh 间接展开空值写 secret、.env 注释假非空、飞书验签纯 sha256 与「接收群聊中所有消息」权限、agent 工具两关授权(policy+def grants 缺一即拒,deny 文案分层)、def Write 整体覆盖冲掉部署侧 toolScopes/grants(编排脚本只跑所需子步骤)、watt CLI --json 全局选项须前置、信任根轮换后 pluginToken 静默失效标准诊断修复(查入站时间戳→healthz→假事件探测→401 即重签单跑 plugin register)、gitignore 模板 `lib/` 规则吞 app/lib(收窄 `/lib/`)、RR7 SPA 构建需 @react-router/node+isbot 在 dependencies、biome tailwindDirectives 与 RR7 产物目录/shadcn vendored 排除。 -- [guides/phase-gate-workflow.md](guides/phase-gate-workflow.md) — Phase 关门标准流程:重跑 DoD 记证据 → 4 维质量关口 + 对抗核查 → 修复复核 → Docs 漂移回查 → PROGRESS 入账与 llmdoc 沉淀;附 Phase 0/1 实测经验(并行修复拆分、跨包契约消费方枚举、线上全链重跑)。 -- [reference/proto-map.md](reference/proto-map.md) — Proto.md 章节检索地图 + 四大横切契约(Event 信封/CallContext/§6.4c 判定/WattError 含 401/501 补充与裸 body)+ §6.5d device flow + §3.4 六条路由规则 + HTBP 树 + 24 个接口面。 -- [reference/external-facts.md](reference/external-facts.md) — Cloudflare 原语选型、外部仓库归属(含 Flue 勘误)、模型渠道双路径与易错点、飞书 webhook plugin 主路径要点(验签/权限/回调域名境内约束)。 -- [memory/doc-gaps.md](memory/doc-gaps.md) — Docs 缺口台账(P1/P2 需修 Docs,P3 仅记录)。 -- [memory/decisions/feishu-websocket-channel.md](memory/decisions/feishu-websocket-channel.md) — 飞书走 WS push 型不走 webhook(2026-07-02;**已被 feishu-plugin-webhook 取代**)。 -- [memory/decisions/root-key-bootstrap.md](memory/decisions/root-key-bootstrap.md) — Root Key 持久引导凭据:摘要存储+仅展示一次+换发制,与 JWT 轮换解耦(2026-07-04 R35)。 -- [memory/decisions/dashboard-rr7-stack.md](memory/decisions/dashboard-rr7-stack.md) — Dashboard 技术栈 RR7 framework mode(ssr:false)+Tailwind v4+shadcn(兼容既有 assets 分发管道);manage 对话走轮询 event List{correlationId} 非新增同步端点;Spawn 建会话不带 expect(2026-07-04 Round 36)。 -- [memory/decisions/feishu-plugin-webhook.md](memory/decisions/feishu-plugin-webhook.md) — 飞书转正为独立 channel-adapter plugin + 自持 webhook 回调主路径,WS connect 降 dev-only;custom domain 解境内干扰(2026-07-04 Round 33)。 -- [memory/decisions/plugin-outbound-dispatcher.md](memory/decisions/plugin-outbound-dispatcher.md) — 通用出站分发器:adapter→channel-→binding:/HTTPS §11.4 Send;同账户 workers.dev 互调被拦(404)是选 service binding 的决定性理由(2026-07-04 Round 33)。 -- [memory/decisions/secretstore-runtime-keys.md](memory/decisions/secretstore-runtime-keys.md) — SecretStore 密钥 runtime 化:AES-256-GCM + 专用 WATT_SECRET_ENCRYPTION_KEY(不从 JWT 派生)+ AAD=名字 + 永不回显 + resolveSecret env→KV(2026-07-04 Round 33)。 -- [memory/decisions/agent-spawn-revive-and-defloader.md](memory/decisions/agent-spawn-revive-and-defloader.md) — re-Spawn 复活 terminated(重置态+按当前 def 重快照,Send 不复活)+ Authorizer 接 AgentDefLoader 收口 agent 主体 PEP 误拒(2026-07-04 Round 33)。 -- [memory/decisions/flue-attribution.md](memory/decisions/flue-attribution.md) — Flue=withastro/flue 勘误确认(gh 核实)。 -- [memory/decisions/resource-naming-and-provision.md](memory/decisions/resource-naming-and-provision.md) — 云资源 `watt-` 前缀、D1 多库拆分、Vectorize 1024 维 bge-m3、`pnpm provision` 幂等方案(2026-07-02 Round 2)。 -- [memory/decisions/bare-watterror-body.md](memory/decisions/bare-watterror-body.md) — 错误 body = 裸 WattError 无信封;401/501 复用 7 码不扩容(2026-07-02 Round 3)。 -- [memory/decisions/auth-implementation.md](memory/decisions/auth-implementation.md) — Phase 1 Auth 选型:Ed25519+jose+JWKS、私钥生命周期与轮换签发模式、device grants 存 KV、OAuth 端点 WattError 豁免边界、CLI 未认证退出码分层、KV 判定缓存跳过(2026-07-02 Round 5/6)。 -- [memory/decisions/model-call-sdk.md](memory/decisions/model-call-sdk.md) — 模型调用 SDK = Vercel AI SDK(ai@6 + @ai-sdk/anthropic@3,锁 v6 对齐 agents peer);排除 pi SDK(Node-only 跑不进 workerd);单次 generateText,schema 重试留 llm.ts;workerd 用 createAnthropic 工厂 + baseURL 补 /v1(2026-07-03)。 -- [memory/decisions/task-workflow-instance-id.md](memory/decisions/task-workflow-instance-id.md) — taskId 即 Workflows instanceId(Write 时 create({id:taskId}),Signal/Cancel 直取),免映射表;List/Get 投影仍靠 TaskStore(2026-07-03 Round 20)。 -- [memory/decisions/scheduler-script-runner.md](memory/decisions/scheduler-script-runner.md) — cron script 的 ScriptRunner 可注入抽象(生产 LOADER 真 isolate / 测试 fake 同一 Check 路径);watt binding 经 RPC 参数注入;能力最小面 watt.publish;claims cron 链段以本 job 播种自足(2026-07-03 Round 21)。 -- [memory/reflections/2026-07-02-round1-scaffold.md](memory/reflections/2026-07-02-round1-scaffold.md) — Round 1 worker 派发反思:prompt 三件套模板、Reflection Handoff、派发前 proto-map 自查指令契约细节。 -- [memory/reflections/2026-07-02-round7-phase1-gate.md](memory/reflections/2026-07-02-round7-phase1-gate.md) — Round 7 关门轮反思:派发检查清单(filter 名对照 package.json name、跨包契约改动先 grep 全部消费方、文件集预留连带改动报告出口)、独立 mock 掩盖跨包错配、deploy 后旧 isolate 误判、worker 有理偏离评审建议的良性案例。 -- [memory/reflections/2026-07-03-round10-phase2-gate.md](memory/reflections/2026-07-03-round10-phase2-gate.md) — Round 10 关门轮反思:并行 worker 共享工作树的类型契约耦合污染(git worktree 隔离验证自救)、4 维评审裁剪不降质(0 误报)、对抗核查价值=修严重度+补证据链、注释与死测试合谋锁 bug、代理环境 NODE_USE_ENV_PROXY 与 token 1h 有效期。 -- [memory/reflections/2026-07-04-round27-phase6-gate.md](memory/reflections/2026-07-04-round27-phase6-gate.md) — Round 27 关门轮反思:验证命令 exit code 勿经管道采集、biome 排错先 --diagnostic-level=error、幽灵实例排查靠物理量对账(11ms≠模型调用)、.env 存在≠有值(grep 断言非空)、对抗核查连续五 Phase 0 误报(codeFacts 精确到行→主 assistant 直修无返工)。 -- [memory/reflections/2026-07-03-round13-phase3-gate.md](memory/reflections/2026-07-03-round13-phase3-gate.md) — Round 13 关门轮反思:宽容解析是契约漂移温床("mock 全绿线上坏"四连击→解析失败就报错)、评审 finding 聚类找共同根因再决定补丁 vs 重构(vector 三 MAJOR 同根=D1 sidecar 一个架构动作)、对抗核查连续四 Phase 0 误报(价值=修严重度+补代码事实+doc-gaps 比对滤实现自由)、worker idle≠done(验收指令置 prompt 末尾)。 -- [memory/reflections/2026-07-04-round33-usability-sprint.md](memory/reflections/2026-07-04-round33-usability-sprint.md) — R33 可用性冲刺反思:worktree 基线漂移与并行纪律、实测逼出的三个架构修正。 -- [memory/reflections/2026-07-04-round34-feishu-tool-authz.md](memory/reflections/2026-07-04-round34-feishu-tool-authz.md) — R34 维护轮反思:调查报告须区分代码路径事实 vs 运行时数据事实(两处推断被 `watt agent get` 证伪)、两关授权一次修双侧 + 真实主体路径验证、轮换连坐只跑所需子步骤、`--json` 全局选项须前置。 -- [memory/reflections/2026-07-04-feishu-plugintoken-outage.md](memory/reflections/2026-07-04-feishu-plugintoken-outage.md) — 飞书 pluginToken 信任根轮换连坐静默断链反思:分段假事件探测定位(握手/验签/Publish 各环节独立可测)+ 单步重签修复 + 真人 e2e 验证收口。 - -## 检索路由 - -- 查**接口契约/事件 schema/错误码/判定算法** → [reference/proto-map.md](reference/proto-map.md)(再按章节号进 `Docs/Proto.md`)。 -- 查**模块归属/宿主/持久化/数据流/Plugin 类型/CLI 命令** → [architecture/modules-and-flows.md](architecture/modules-and-flows.md)。 -- 查 **Cloudflare 原语限制/外部仓库/模型渠道/飞书方案** → [reference/external-facts.md](reference/external-facts.md)。 -- 查**验收标准/Phase 范围/E2E 判据** → [overview/project-overview.md](overview/project-overview.md)(再进 `DOD.md`)。 -- 查**每轮怎么干/何时止损/该不该造轮子** → [must/loop-contract.md](must/loop-contract.md)。 -- 查**凭据/工具链/当前进度** → [must/current-state.md](must/current-state.md)。 -- **装依赖/配测试/改 TS 工程/wrangler provision 与 deploy 踩坑** → [guides/toolchain-pitfalls.md](guides/toolchain-pitfalls.md)。 -- **某 Phase DoD 全勾、准备关门** → [guides/phase-gate-workflow.md](guides/phase-gate-workflow.md)。 -- 发现 Docs 自相矛盾或缺口 → 先查 [memory/doc-gaps.md](memory/doc-gaps.md) 是否已记录。 -- 历史决策为什么这么定 → `memory/decisions/`。 - -## 外部真源(不在 llmdoc 内) - -- `Docs/{Vision,Architecture,Proto,Plugin,Reference}.md` — 规范宪法。 -- `DOD.md` — 验收法官;`LOOP.md` — 执行契约原文。 -- `.env` — 凭据真源(已 gitignore);`PROGRESS.md` — 进度账本(首轮建立)。 -- `.llmdoc-tmp/investigations/` — 临时调查缓存,复用前须验证。 diff --git a/llmdoc/integrations/feishu-and-plugins.mdx b/llmdoc/integrations/feishu-and-plugins.mdx new file mode 100644 index 0000000..28f93a3 --- /dev/null +++ b/llmdoc/integrations/feishu-and-plugins.mdx @@ -0,0 +1,71 @@ +--- +description: Plugin 类型与认证、通用 outbound dispatcher、飞书 webhook 主路径和 WS dev-only 备用路径的当前架构。 +kind: architecture +relations: + requires: + - contracts/events-and-results.mdx + - contracts/auth-and-errors.mdx + - contracts/htbp-and-platform-apis.mdx + related: + - operations/topology-and-provisioning.mdx + - operations/credentials-and-recovery.mdx +code: + paths: + - Docs/Plugin.md + - packages/gateway/src/event/plugin-sender.ts + - packages/gateway/src/plugin/** + - packages/gateway/migrations-providers/0004_plugin_registrations.sql + - packages/plugin-feishu/src/** + - packages/plugin-feishu/wrangler.jsonc + - packages/cli/src/connect.ts + - packages/cli/src/setup.ts +--- + +# 飞书与 Plugin 集成 + +## Plugin 边界 + +Watt Plugin 是实现某一层纯接口的可注册部署单元: + +| kind | 接口与用途 | +|---|---| +| context-provider | ContextProvider,挂载新的 `context://` namespace | +| tool-provider | ToolProvider,挂载新的 HTBP tools 子树 | +| channel-adapter | Verify/Decode/Encode/Send,接入 IM、email 或 webhook | +| agent-harness | AgentEndpoint,接入新的运行方式 | + +平台到 Plugin 使用 platform token 或声明的 bearer;Plugin 回调平台使用 plugin token,其 scope 不得超过 manifest.requiredGrants。Plugin 响应必须忽略未知字段,并在同一个 interface major 内保持向后兼容。 + +注册时外部 HTTPS endpoint 先过 health probe,`binding:` endpoint 视为平台内部能力。当前注册流程尚未校验 `~help/~describe` 的方法全集与 interfaceVersion,因此 Plugin 的真实兼容性还需要契约测试或注册后的最小调用验证。 + +## 通用出站分发 + +consumer 收到 `outbound.message` 后,不按渠道写硬编码 sender。分发器从 ChannelConfig.adapter 推导 plugin id `channel-`,允许 settings.pluginId 覆盖,再查 PluginRegistry。 + +同账户 Plugin 通过 `binding:` service binding 调用;跨账户/第三方走 HTTPS 的 Plugin 传输信封。请求带 platform token、稳定 request id 与超时;网络和可重试服务错误让 Queue 重投,业务拒绝留痕后 ack。Plugin Send 必须把 request id 用作幂等依据,避免 Queue 重投造成重复消息。 + +gateway 不持有渠道 SDK 或渠道 app secret。每个 channel-adapter Plugin 自持凭据、webhook endpoint 与编码细节;新增渠道只注册 Plugin 与 ChannelConfig,不修改 consumer 的领域分支。 + +## 飞书主路径 + +飞书生产入站是独立 channel-adapter Worker 的 webhook: + +```text +Feishu callback + → challenge / signature / optional decrypt + → Decode + mention expansion + → plugin token Publish Event + → EventBus / Agent +``` + +出站由 gateway 的通用 dispatcher 经 service binding 调 Plugin 的 Encode/Send。FEISHU 凭据只属于 plugin Worker。签名算法是 `sha256(timestamp + nonce + encrypt_key + body)` 的直接拼接,无分隔符且不是 HMAC;加密 key 未配置时仍需 verification token 校验,不能无验证接受回调。 + +飞书回调有短握手时限,且 workers.dev 从中国境内可能不可达,因此生产 webhook 使用可在回调方网络访问的一级 custom domain。可达性必须从飞书实际回调验证;开发机通过代理访问成功不构成证据。 + +bot 是否接收未 @ 的群消息由飞书应用的“接收群聊中所有消息”权限决定。缺权限时 lurker 只能积累 @ 消息,属于上游事件不可见,不是 Context 或 Agent bug。 + +## WS 备用路径 + +`watt channel connect` 的 WebSocket push 方案只用于开发和诊断。飞书 Node SDK 的 WSClient 不能运行在 Workers isolate,必须由本地 CLI 或完整 Node/Container 宿主维持;它不再是生产主路径。 + +WS supervisor 以 SDK 的 onError 作为终态失败信号并负责重建;`start()` 在正常自重连期间不会返回。WS Decode 与 webhook Decode 共享 core channel 语义,仍要生成 session、channelUser、dedupeKey,并以 plugin 主体 Publish,不能因 dev-only 身份绕过 EventBus/Auth。 diff --git a/llmdoc/memory/decisions/agent-spawn-revive-and-defloader.md b/llmdoc/memory/decisions/agent-spawn-revive-and-defloader.md deleted file mode 100644 index 8818b2c..0000000 --- a/llmdoc/memory/decisions/agent-spawn-revive-and-defloader.md +++ /dev/null @@ -1,30 +0,0 @@ -# 决策:re-Spawn 复活 terminated 实例 + Authorizer 接 AgentDefLoader - -- 日期:2026-07-04(Round 33,线上实测逼出的两个架构修正) -- 状态:已定案(Proto §3.2 已补充复活语义) - -## 决定 1:re-Spawn 复活 terminated 实例 - -- 对已 terminated 的实例再次 Spawn(同 instanceKey)→ **复活**:重置运行态 + **按当前 AgentDefinition 重新快照**(harness/model/toolScopes/systemPrompt 全部取最新 def)。 -- **Send 不复活**:Send 到 terminated/未 Spawn 实例仍是 not_found(R27 幽灵 DO 防护语义不变,pitfalls §47)。 -- Proto §3.2 已回写该语义(宪法先行)。 - -### 理由 - -- terminated 实例占着 instanceKey,原语义下该 key 永久报废——重建同名实例只能换 key,违反"同名恒路由同一实例"的直觉。 -- **顺带解决实例快照永不追随 def 更新的问题**:实例 state 在首次 Spawn 时快照 def,此后 def Update 对既有实例无效;re-Spawn 重新快照给了一条显式的"刷新到最新 def"路径。 -- 复活入口限定 Spawn(显式管理动作),Send(数据面投递)不触发——防止拼错 id 的投递静默复活/新建实例。 - -## 决定 2:Authorizer 接 AgentDefLoader - -- 平台 `newAuthorizer` 增 **AgentDefLoader**:core authorize 步骤 2 需要 `claims.agent_def` 对应的 def 时**惰性加载播种**(原实现恒传空 agentDefs 索引)。 - -### 理由 - -- 历史空索引导致**一切 agent 主体在步骤 2 被误拒**("agent definition not found")——lurker 出站、htbp 工具 Check 等全部 PEP 面对 agent 主体系统性失效;此前只能各调用点手工播种(pitfalls §51/§53 同类坑)。 -- AgentDefLoader 在判定点统一收口"判定点必须能解析 claims 引用的数据面",替代逐点绕道;lurker 出站原有的播种绕道保留(行为等价)。 - -## 影响 - -- R33 实证:lurker 主体全链过 PEP(audit `tool://test/echo/get-uuid` read+invoke allow)。 -- 相关坑:toolchain-pitfalls §47(幽灵 DO)、§51/§53(空索引误拒同根)。 diff --git a/llmdoc/memory/decisions/auth-implementation.md b/llmdoc/memory/decisions/auth-implementation.md deleted file mode 100644 index 95269fb..0000000 --- a/llmdoc/memory/decisions/auth-implementation.md +++ /dev/null @@ -1,37 +0,0 @@ -# 决策:Phase 1 Auth 实现选型与边界(2026-07-02,Round 5/6) - -> 涉及 Proto §6(Auth)、§6.5d(device flow)。实现位置见 [../../must/current-state.md](../../must/current-state.md) 源码现状节。 - -## 1. JWT 选型:Ed25519 + jose + JWKS - -- **决策**:user/admin token 用 Ed25519 非对称签名,库选 jose,公钥经 `/.well-known/jwks.json` 公开。 -- **理由**:Proto §11.2 语境反推需非对称——Plugin/外部方需独立验签,不能共享对称密钥;jose 是 Workers 环境标准库,原生 WebCrypto。 -- 实现约束:`packages/core/src/auth/jwt.ts` 密钥全部注入(纯逻辑,测试用 fixture 密钥经 miniflare.bindings 注入)。 - -## 2. 私钥生命周期 - -- 生成(内存,`scripts/gen-jwt-keys.mjs`)→ 管道进 `wrangler secret put WATT_JWT_PRIVATE_JWK`(不落盘)→ **put 后不可取回**。 -- 后果:验收/运维需要签 admin token 时不能"取私钥再签",只能走**轮换模式**——`scripts/sign-admin-token.mjs` 生成新密钥对、put 新私钥、同进程用内存私钥直接签 token(顺带解 bootstrap 鸡生蛋:没有 token 就签不了第一个 token)。 -- 注意 secret 传播窗口 ~15s(见 [../../guides/toolchain-pitfalls.md](../../guides/toolchain-pitfalls.md) §20)。 - -## 3. Device grants 存 KV(watt-tenants)而非 D1 - -- **决策**:device flow 的 grant 状态存 KV,`expirationTtl` 对齐 `expires_in`,双索引(device_code 键 + user_code 键)。 -- **理由**:grant 是短命(默认 600s)自过期状态,KV 的 TTL 语义天然贴合;进 D1 要自己扫过期行,且无查询/关联需求。不新增 migration。 - -## 4. OAuth 端点错误形状边界:RFC 裸形状 vs WattError - -- `/oauth/device/authorize` 与 `/oauth/token`:**豁免 WattError**,遵循 RFC 8628/OAuth 错误形状(如 `{error:"authorization_pending"}` HTTP 400)——OAuth 客户端生态按 RFC 形状解析,包 WattError 会破坏互操作。 -- `/oauth/device/approve`:**仍走 WattError**——它不是 RFC 端点,而是平台管理动作(admin 带 token 调用),归平台错误契约。 -- 边界判据:**RFC 定义的端点按 RFC;平台自有动作按 WattError**。已回写 Proto §6.5d。 - -## 5. CLI 未认证语义分层 - -- **本地无 token**(`WATT_TOKEN` 未设且 `~/.watt/credentials.json` 不存在)→ exit 2,提示 `watt login`——用户侧配置问题,不发请求。 -- **服务端 401**(有 token 但过期/无效)→ exit 1——服务端判定结果。 -- 理由:脚本/CI 可凭退出码区分"没配"和"配错",两者修复路径不同。 - -## 6. KV 判定缓存:Phase 1 有意跳过 - -- §6.4c 允许"实现可用 KV 缓存各段结果"。Phase 1 只有步骤 1(user token 无 agent 链),判定就是一次 D1 查询,缓存收益小而失效逻辑(Policy 变更)成本高。 -- 代码注释已声明跳过;`watt-authz-cache` KV 已 provision,留待 agent 链判定(Phase 4/5)真实多段查询时启用。 diff --git a/llmdoc/memory/decisions/bare-watterror-body.md b/llmdoc/memory/decisions/bare-watterror-body.md deleted file mode 100644 index c0c92d6..0000000 --- a/llmdoc/memory/decisions/bare-watterror-body.md +++ /dev/null @@ -1,22 +0,0 @@ -# 决策:错误响应 body = 裸 WattError(无信封) - -- 日期:2026-07-02(Round 3,Phase 0 关门质量关口) -- 状态:已实施并回写 Proto(`pnpm verify` 绿 + 线上 curl 验证) - -## 决策 - -1. **HTTP 错误响应 body 就是裸 `WattError` 对象**(`{code,message,retryable}`),依据 Proto §11.3。**决不使用 `{error:...}` 信封**或任何其他包裹结构。 -2. **7 码不扩容**。规范外场景复用现有码,已回写为 Proto §0.2 规范性补充: - - 未认证 **401 → `permission_denied`**; - - 未实现 **501 → `unavailable`**。 - -## 来源 - -- Phase 0 质量关口 contract 维度 finding:gateway 占位实现曾用 `{error: WattError}` 信封 + 规范外码 `unimplemented`,与 Proto §11.3 / §0.2 冲突。 -- 处置:实现改裸体 + `unavailable`;Proto §0.2 回写两条规范性补充(宪法优先,先修 Docs 再对齐实现)。 -- 关联:`llmdoc/memory/doc-gaps.md` #17(已闭环);契约细节见 `llmdoc/reference/proto-map.md` 横切契约四。 - -## 对后续实现的约束 - -- 所有模块(gateway、HTBP 树、platform 端点)的错误路径统一返回裸 WattError;测试断言 body 顶层就是 `code` 字段。 -- 新增错误场景先查 7 码能否覆盖,不得私加扩展码。 diff --git a/llmdoc/memory/decisions/dashboard-rr7-stack.md b/llmdoc/memory/decisions/dashboard-rr7-stack.md deleted file mode 100644 index 6f12f75..0000000 --- a/llmdoc/memory/decisions/dashboard-rr7-stack.md +++ /dev/null @@ -1,26 +0,0 @@ -# 决策:Dashboard 技术栈 = RR7 framework mode SPA + Tailwind v4 + shadcn/ui;manage 对话走轮询 event List - -- 日期:2026-07-04(Round 36) -- 状态:已实现上线(packages/dashboard 原地重建,旧 src/ 已删) - -## 决定 - -1. **技术栈**:React Router 7 framework mode(`ssr: false` 纯 SPA)+ Tailwind v4 + shadcn/ui(23 组件 vendored 进 `app/components/ui`,不 lint)。16 视图全 CRUD + manage 对话,与 CLI 十六命令族对齐。 -2. **manage 对话链路**:Spawn 建会话 → 每轮 `Send{expect}` → 前端 1.5s 轮询 `EventStore.List{correlationId}` 取 `agent.result`/`agent.failed` → 渲染 `payload.output`(70s 兜底超时,sessionStorage 会话恢复)。 -3. **Spawn 建会话不带 expect**:expect 只在每轮 Send 时带。 - -## 理由 - -1. **RR7 + SPA 产物**:Cloudflare 对 React Router 7 有官方一等支持;`ssr:false` 产物是纯静态 `build/client`,落 dist/ 后**完全兼容既有 gateway assets 同域托管分发管道**(R35 单域化 + build:deploy 模板改写逻辑零调整)。shadcn 是代码进仓而非依赖,可随「电力控制台」主题任意定制。 -2. **轮询 event List 而非新增同步端点**:investigator 三方案对比(A=EventStore List 增 `correlationId` filter、B=gateway 新增同步等待端点、C=WebSocket/SSE 推送)。选 A——manage 对话「零后端改动可跑通但前端筛 payload 会漏」,把 correlationId 收口为一等查询路径只需 **EventStore.List filter 十行改动**(json_extract payload,Proto §2.4 先行增补 + 2 测试),B/C 都要新协议面与连接管理,投资回报不成比例。 -3. **Spawn 不带 expect**:view-b worker 实测发现 Spawn 带 expect 会触发 harness 对空输入**空跑一次 LLM**(gateway `src/agent/agent-runtime.ts:182-186`:spawn 时 expect 存在即投递一次 OnEvent)——建会话改为不带 expect 规避。 - -## 遗留 - -- agent waiter 无 `'none'` 类:manage 对话每轮 Send 的 self-waiter 回投会**多跑一次 LLM**(成本冗余,投资回报低暂不做)。 -- `/oauth/root/token` 不在 CORS 白名单:同域托管无碍;跨源 vite dev 时 Root Key 登录被拦,开发期用 token 粘贴路径。 - -## 关联 - -- [../../guides/toolchain-pitfalls.md] §66~68(gitignore `lib/` 吞 app/lib、RR7 SPA 构建依赖、biome Tailwind 指令与产物排除)。 -- [root-key-bootstrap.md](root-key-bootstrap.md)——dashboard 登录页的 Root Key 换发路径即 R35 该决策的 WEB 消费面。 diff --git a/llmdoc/memory/decisions/feishu-plugin-webhook.md b/llmdoc/memory/decisions/feishu-plugin-webhook.md deleted file mode 100644 index 3bc7d1a..0000000 --- a/llmdoc/memory/decisions/feishu-plugin-webhook.md +++ /dev/null @@ -1,27 +0,0 @@ -# 决策:飞书转正为独立 channel-adapter plugin + 自持 webhook 回调主路径 - -- 日期:2026-07-04(Round 33) -- 状态:已定案并采证(Phase 6 ② 真实群消息闭环即经此路径) -- 取代:[feishu-websocket-channel.md](feishu-websocket-channel.md)(WS push 型降为 dev-only 备用) - -## 决定 - -1. 飞书接入从「WS push 型 + CLI 本地长驻(`watt channel connect`)」**转正为独立 channel-adapter plugin**:新包 `packages/plugin-feishu`,部署为独立 Worker `watt-plugin-feishu`。 -2. **入站主路径 = plugin 自持 webhook 回调**(`/webhook/event`:challenge 握手、验签、AES 解密、decode、mentions 展开,以 pluginToken 调平台 Publish);出站经 §11.4 Encode/Send 面(gateway 经 service binding 调入)。 -3. FEISHU_* 凭据由 plugin worker 自持,移出 gateway。 -4. 回调面挂 **custom domain `watt-feishu.pdjjq.org`**(Universal SSL 只盖一级子域)。 -5. WS `channel connect` 保留为 **dev-only** 备用路径,不再是主路径。 - -## 理由 - -1. **用户要求可独立发行**:channel adapter 作为 watt-plugins/* 独立包/独立 Worker,可单独部署、单独发布,是 Plugin.md 生态的第一个真实实例。 -2. **境内 workers.dev 被干扰**:飞书回调方 3s 握手必超时——用 custom domain 解决后 webhook 路径完全可用,原「免公网回调 URL」的 WS 优势不再是必需。 -3. **彻底摆脱本地长驻**:WS 方案要求 CLI 进程本机常开(Node SDK 不能跑 Workers isolate),入站可用性绑死在开发机上;webhook plugin 是纯 Workers 常驻,无人值守。 - -## 影响 - -- Phase 6 ② 采证经此路径闭环(真实群 @watt 收到回复,events/audit 双留痕)。 -- 出站分发经通用 plugin-sender(见 [plugin-outbound-dispatcher.md](plugin-outbound-dispatcher.md));gateway `feishu-sender.ts` 已删除。 -- `watt setup feishu` 幂等五步负责签 pluginToken + put plugin secrets + 注册。 -- 飞书后台「事件发送至开发者服务器」须保持 `https://watt-feishu.pdjjq.org/webhook/event`。 -- 验签细节与权限事实见 toolchain-pitfalls §61 与 [../../reference/external-facts.md](../../reference/external-facts.md)。 diff --git a/llmdoc/memory/decisions/feishu-websocket-channel.md b/llmdoc/memory/decisions/feishu-websocket-channel.md deleted file mode 100644 index 94963a1..0000000 --- a/llmdoc/memory/decisions/feishu-websocket-channel.md +++ /dev/null @@ -1,28 +0,0 @@ -# 决策:飞书渠道走 WebSocket 长连接(push 型),不走 webhook - -- 日期:2026-07-02 -- 状态:**已被取代**(2026-07-04 Round 33:主路径改为独立 channel-adapter plugin + 自持 webhook 回调,见 [feishu-plugin-webhook.md](feishu-plugin-webhook.md);WS `channel connect` 降为 dev-only 备用。以下原文保留作历史依据) -- 原状态:已定案(Phase 6 实现依据;DOD §8/§9 已按此写入) - -## 决定 - -飞书 ChannelAdapter 采用**长连接 push 型**接入(飞书后台订阅方式选"使用长连接接收事件",无需配置 inbound URL),不走 webhook 回调。webhook 型(`FEISHU_WEBHOOK_URL`/`FEISHU_VERIFICATION_TOKEN`)保留为备用路径。 - -## 理由 - -1. 免公网回调 URL 与验签/加密配置,开发与部署都更简单;`FEISHU_ENCRYPT_KEY` 在 WS 方案下非必需。 -2. 与 Plugin 契约兼容:push 型 ChannelAdapter 可豁免 Verify/Decode(capabilities 声明 `push`,Plugin.md §2 / Proto §2.1),规约义务(session/channelUser/dedupeKey=event_id)在 Adapter 内自行完成,以 plugin token 调 `EventBus.Publish`。 -3. 出站路径(Encode/Send,飞书 REST API 含 actions 卡片)已于 2026-07-02 实测通过(测试群 "Tipsy Agent Infra")。 - -## 宿主约束(关键实现注意) - -`@larksuiteoapi/node-sdk` 的 WSClient 是 Node SDK,**不能跑在 Workers isolate**。连接进程宿主: - -- 生产:Container(M2 Heavy Runtime)。 -- 开发期:由 CLI 承载——`watt channel connect feishu-main` 在本地维持长连接并把事件转发进 `EventBus.Publish`。 - -## 影响 - -- Phase 0 的 `POST /channels//inbound` webhook 入口仅为通用 ChannelAdapter 保留占位,飞书不用。 -- Phase 6 单测须覆盖 WS 断线重连与事件去重(dedupeKey=event_id)。 -- 相关文档:[../../reference/external-facts.md](../../reference/external-facts.md)、[../../must/current-state.md](../../must/current-state.md)。 diff --git a/llmdoc/memory/decisions/flue-attribution.md b/llmdoc/memory/decisions/flue-attribution.md deleted file mode 100644 index 629b764..0000000 --- a/llmdoc/memory/decisions/flue-attribution.md +++ /dev/null @@ -1,24 +0,0 @@ -# 决策/勘误确认:Flue 真身为 withastro/flue - -- 日期:2026-07-03(gh 核实;memory 线索最初记录更早) -- 状态:已确认;Docs 回写待办(doc-gaps P1-1) - -## 结论 - -- **Flue = `withastro/flue`**(公开,Apache-2.0,描述 "The sandbox agent framework",与 Watt 中 Flue 的 sandbox agent harness 定位一致)。 -- **`TokenRollAI/flue` 不存在**:`gh search repos flue --owner TokenRollAI` 返回空。 -- Docs 原文(`LOOP.md` §0/§2.1、`DOD.md` §6、`Docs/Reference.md`)把 Flue 与 tool-bridge/HTBP 并列归为 TokenRollAI **属勘误**。 - -## 核实方式 - -gh CLI 只读核查(账户 `Disdjj`,scopes 含 repo): - -- `withastro/flue`:存在,公开,默认分支 `main`。 -- `TokenRollAI/tool-bridge`:存在,**私有**,可访问,默认分支 `main`(LOOP §2.1 "gh 已有访问权"属实)。 -- `TokenRollAI/HTBP`:存在,公开。 -- `TokenRollAI/flue`:不存在。 - -## 行动约束 - -- M4 及 harness 相关轮次 clone/引用 Flue 时一律指向 `withastro/flue`,**勿按 Docs 原文路径操作**。 -- Docs 回写(所有 Flue 引用改指 `withastro/flue`)记录在 [../doc-gaps.md](../doc-gaps.md) P1-1,待某轮顺手完成后销账。 diff --git a/llmdoc/memory/decisions/model-call-sdk.md b/llmdoc/memory/decisions/model-call-sdk.md deleted file mode 100644 index 6b3461c..0000000 --- a/llmdoc/memory/decisions/model-call-sdk.md +++ /dev/null @@ -1,42 +0,0 @@ -# 决策:模型调用 SDK = Vercel AI SDK(ai@6 + @ai-sdk/anthropic@3) - -> 2026-07-03。取代此前 `@anthropic-ai/sdk` 选型;再取代更早的手拼 HTTP。 - -## 决定 - -`packages/gateway/src/agent/harness/anthropic-caller.ts` 的模型调用用 **Vercel AI SDK**: -`ai` + `@ai-sdk/anthropic` 的 `createAnthropic({ apiKey, baseURL, fetch })` + `generateText`。 - -## 为什么不是 `@anthropic-ai/sdk` - -- **供应商中立**:换 provider(OpenAI 兼容 / 其它中转)只改工厂函数,调用点 `generateText` 不动。用户明确要"兼容更多 provider"。 -- **workerd 兼容**:`ai` 是纯 JS、无 native build、无 Vercel 基建依赖,官方支持 Cloudflare Workers;vitest-pool-workers 下全套单测通过(323 passed)。 - -## 为什么不是 pi SDK(`@earendil-works/pi-coding-agent`) - -评估后排除:它是**完整的编码 agent 会话框架**(自带 session/工具执行/事件流),且 **Node.js only**(依赖文件系统会话存储、`~/.pi` 配置目录、`process.cwd()`),文档对非 Node 集成只推荐子进程 RPC——**在 workerd isolate 里起不来**,且与已落地的 Cloudflare Agents SDK(`AgentInstance`)正面冲突。持久化 Agent / agent loop 仍归 Agents SDK,不引 pi。 - -## 版本锁定(关键约束) - -**ai@6(6.0.219)+ @ai-sdk/anthropic@3(3.0.92)**,勿升 ai@7。 -原因:`agents@0.17.3`(Agent Runtime 框架)的 peer 是 `ai@^6.0.0`;装 ai@7 会触发 peer 冲突。`@ai-sdk/anthropic` 的 dist-tag `ai-v6` = 3.0.92(配 ai@6),`latest`(4.x) 配 ai@7。zod peer `^3.25.76 || ^4.1.8`,本仓库 zod@4.1.11 满足。 - -## workerd 适配要点 - -- **必须用 `createAnthropic()` 工厂显式传 apiKey/baseURL**(从 env binding 取);禁用裸 `import { anthropic }`——Workers 无 `process.env`,裸 provider 会静默读 env 失败。 -- **baseURL 需含 `/v1`**:`createAnthropic` 缺省 baseURL 是 `https://api.anthropic.com/v1`,其后拼 `/messages`。中转期望 `${根}/v1/messages`,故 caller 内 `withV1Suffix` 把中转根(`https://llm.fantacy.live`)补成 `${根}/v1`。 - -## 边界(LOOP 纪律 4) - -- 此 harness = **单次调用**(`generateText`,文本进/文本出);**schema 校验重试留在 `llm.ts`**(`validateAgentOutput` + `shouldRetry` 循环),保留 Proto §3.4「携带违规反馈退回重发」协议语义,不交给 SDK 的 `generateObject` 黑盒重试。故 schema 路径不依赖中转对 glm-5.2 的原生结构化输出。 -- 多轮工具调用循环(Phase 5+ deep-research 等)走 Agents SDK `AIChatAgent` / Claude Agent SDK / Flue,禁止在此文件自增 loop。 - -## 体积 - -gateway worker 打包 gzip 491.86 KiB(Total Upload 2788.92 KiB),远低于 Workers 免费档 3 MiB / 付费 10 MiB 门槛。 - -## 未验证(留 @llm 真实测试 / team-lead) - -SDK 真实网络往返 + 中转对 `generateText`/glm-5.2 的响应形状(`{ text }` 提取)——需 `LLM_TESTS=1 ANTHROPIC_API_KEY=` 冒烟。单测用 fake caller 不触网络。 - -相关:[[flue-attribution]](Flue = withastro/flue,Phase 5+ agent loop 候选)。 diff --git a/llmdoc/memory/decisions/plugin-outbound-dispatcher.md b/llmdoc/memory/decisions/plugin-outbound-dispatcher.md deleted file mode 100644 index 1a81a05..0000000 --- a/llmdoc/memory/decisions/plugin-outbound-dispatcher.md +++ /dev/null @@ -1,24 +0,0 @@ -# 决策:通用出站分发器(plugin-sender)替换渠道硬编码 - -- 日期:2026-07-04(Round 33) -- 状态:已定案(gateway `src/event/plugin-sender.ts`;feishu-sender 已删) - -## 决定 - -consumer 的 outbound.message 投递不再按渠道硬编码,统一走通用分发器: - -1. **寻址约定**:channel 的 `adapter` → plugin id **`channel-`**(channel `settings.pluginId` 可覆盖)→ 查 PluginRegistry。 -2. **双形态投递**:plugin endpoint 是 `binding:` 前缀 → 经 **service binding** 调用;否则走 HTTPS,POST §11.4 `{"tool":"Send"}` 信封。 -3. **调用契约**:带 platform-token 认证 + `X-Watt-Request-Id` 幂等键 + 10s 超时;retryable 失败走 msg.retry 重投(幂等由请求 id 保证)。 - -## 理由 - -1. **同账户 workers.dev 互调被平台拦截是决定性理由**:R33 实测 gateway 经 HTTPS 调同账户 `watt-plugin-feishu.workers.dev` 的探活/Send 一律收 **404**(非文档所述 1042 错误码)——同账户 plugin 只能走 service binding;HTTPS 形态留给跨账户/第三方 plugin。 -2. 渠道硬编码(原 feishu-sender)与 Plugin.md 的 channel-adapter 契约冲突:新渠道要改 gateway 源码;分发器 + registry 后新增渠道只需注册 plugin。 -3. `channel-` 命名约定让零配置场景开箱即用,settings.pluginId 覆盖保留灵活性。 - -## 影响 - -- gateway 不再持有任何渠道 SDK/凭据(FEISHU_* 移出);wrangler 增 service binding `FEISHU_PLUGIN`。 -- deploy 顺序:plugin worker 必须先于 gateway 部署(binding 目标先存在)。 -- 相关坑:toolchain-pitfalls §54(同账户 404)、§55(workers.dev 境内干扰)。 diff --git a/llmdoc/memory/decisions/resource-naming-and-provision.md b/llmdoc/memory/decisions/resource-naming-and-provision.md deleted file mode 100644 index 8070d60..0000000 --- a/llmdoc/memory/decisions/resource-naming-and-provision.md +++ /dev/null @@ -1,26 +0,0 @@ -# 决策:云资源命名、D1 多库拆分与 provision 方案 - -- 日期:2026-07-02(Round 2,Phase 0) -- 状态:已实施(资源已真实创建,`pnpm verify` / smoke 回归绿) - -## 1. 统一 `watt-` 前缀 - -- **决策**:所有云资源统一加 `watt-` 前缀(D1/KV/R2/Queue/Vectorize 全部)。 -- **偏离**:附B 原文 KV namespace 无前缀。 -- **理由**:该 Cloudflare 账户内有大量其他项目资源,无前缀会混淆,且不利于脚本按名匹配。 -- **后续**:与附B 原文的偏差可在 Phase 0 关门时回写附B(见 `PROGRESS.md` Round 2 遗留)。 - -## 2. D1 采用多库而非单库 - -- **决策**:四个独立 D1 库——`watt-policies` / `watt-providers` / `watt-audit` / `watt-events`。 -- **理由**:对应附B 四条目分属不同模块(policies→M5、providers→M8、audit→M9、events→M1),按 ownership 边界拆库,避免跨模块共享 schema。 - -## 3. Vectorize 维度与模型 - -- **决策**:`watt-context-index`,1024 维,cosine,embedding 用 `@cf/baai/bge-m3`。 -- **理由**:中文 IM 场景,bge-m3 多语言召回表现好;1024 维为该模型原生输出维度。 - -## 4. provision 入口与幂等 - -- **入口**:`pnpm provision`(`scripts/provision.mjs`),幂等可重跑——先 list 判存在(资源名 substring 匹配,因 `--json` 支持不齐 + banner 污染,见 [../../guides/toolchain-pitfalls.md](../../guides/toolchain-pitfalls.md) §7),不存在才 create。 -- **wrangler.jsonc 回填**:走 marker 段(脚本只改标记区间,不动手写配置)。 diff --git a/llmdoc/memory/decisions/root-key-bootstrap.md b/llmdoc/memory/decisions/root-key-bootstrap.md deleted file mode 100644 index c40f7fb..0000000 --- a/llmdoc/memory/decisions/root-key-bootstrap.md +++ /dev/null @@ -1,19 +0,0 @@ -# 决策:Root Key 持久引导凭据(仅展示一次,换发制) - -- 日期:2026-07-04(Round 35) -- 状态:已实现上线(Proto §6.5e 规范先行) - -## 决定 - -新增一把持久引导凭据 **Root Key**(`wrk_` + 32B base64url):平台只存 SHA-256 摘要(gateway secret `WATT_ROOT_KEY_HASH`),明文在生成时**仅展示一次**(set-root-key.mjs / watt init 收尾)。Root Key 不能直接调用任何接口,唯一用途是经 `POST /oauth/root/token` **换发** §6.5a 形状的 admin user token(TTL 缺省 7d、钳 30d;成功/失败都写 `platform://auth` `root-exchange` 审计)。 - -## 理由 - -1. 消解引导死角:此前 admin token(1h/7d)过期后唯一自救 = 轮换 JWT 私钥重签 → **吊销全部存量 token 连坐 pluginToken** → 还要重跑 setup feishu(又会触发 §63 def 覆盖坑)。Root Key 换发用当前私钥签名,与轮换完全解耦。 -2. 摘要比对天然常数时间;明文不落盘/不落库/不回显,泄露时重跑 set-root-key 覆写即失效(存量 JWT 不受影响自然过期)。 -3. 消费面覆盖 TUI/WEB:`watt login --root`(stdin)、dashboard Settings 密码框。 - -## 关联 - -- [../../guides/toolchain-pitfalls.md] §63(def Write 整体覆盖)——Root Key 上线后轮换频率骤降,该坑触发面同步收窄。 -- Proto §6.5c′(init 本地签发首 token)仍保留:init 现在同时产出 Root Key,两者互补(首 token 立即可用,Root Key 管长期)。 diff --git a/llmdoc/memory/decisions/scheduler-script-runner.md b/llmdoc/memory/decisions/scheduler-script-runner.md deleted file mode 100644 index 345e756..0000000 --- a/llmdoc/memory/decisions/scheduler-script-runner.md +++ /dev/null @@ -1,37 +0,0 @@ -# 决策:ScriptRunner 可注入抽象 + watt binding 经 RPC 参数注入 - -> 2026-07-03(Round 21,Phase 5 Scheduler/M6 script action)。 - -## 决定 - -cron script action(Proto §7:一次性隔离 isolate 执行)的执行器抽象为可注入接口 `ScriptRunner`: - -- **生产 = LoaderScriptRunner**:Dynamic Worker Loader(`env.LOADER.load`)起真 isolate,`globalOutbound: null` 禁网,凭证不进脚本运行时。 -- **测试 = fake runner 注入**:本地 vitest-pool-workers **无 LOADER 绑定**(线上 open beta,DJJ 账户已开通实证)——fake 走**同一 watt binding + 同一 Authorizer.Check 路径**,能力表与鉴权语义在本地即可验证,只有"真 isolate 起得来"留部署冒烟。 - -## watt binding 注入方式(两次线上失败迭代出的硬约束) - -loader 的 `env` 字段走 **structured clone**:plain object 闭包函数、RpcTarget 实例都被拒("could not be cloned" 线上实测两次)。Cap'n Web 只在 **RPC 边界**把 RpcTarget 转 stub——故 watt binding(`WattBindingRpc extends RpcTarget`)经 **entrypoint RPC 调用参数**传入,脚本入口约定: - -```js -export default class extends WorkerEntrypoint { - async run(watt) { return watt.publish({ type: '...', payload: {...} }); } -} -``` - -详见 toolchain-pitfalls §44 与 `script-runner.ts` 文件头。 - -## 能力表与鉴权 - -- **最小面 = `watt.publish` 一个能力**(满足 DoD"查桩指标→Publish 出站事件";未来扩 metrics.read 等按同一 Check 门控接线)。 -- 每次 publish 过 `platform://event` 'manage' 的 Check(与平台 event Publish 同权面)。 -- claims 构造:principal=job.createdBy + IdentityMapper **实时 roles** + chain=`[cron:]`;authorize 的 `cronJobs` 索引**直接以本 job 播种**(本 job 就是链上唯一 cron 段,上限=job.action.grants,无需外查 Scheduler.Get,自足)。 - -## 代价与边界 - -- fake runner 验不了 isolate 边界本身(禁网、structured clone 语义)——真 isolate 行为只有部署冒烟覆盖,本地绿不等于 loader 路径通。 -- 脚本内容承载当前只支持 structured provider(`context://automations/`);Scheduler Write 不做 grants≤createdBy 静态校验(§7 推迟运行时,见 doc-gaps #29⑧)。 - -真源:`packages/gateway/src/scheduler/script-runner.ts`(文件头注释 = 完整设计声明);doc-gaps #29⑥⑦。 - -相关:[[task-workflow-instance-id]](同 Phase 的 Task 侧决策)。 diff --git a/llmdoc/memory/decisions/secretstore-runtime-keys.md b/llmdoc/memory/decisions/secretstore-runtime-keys.md deleted file mode 100644 index 231f88b..0000000 --- a/llmdoc/memory/decisions/secretstore-runtime-keys.md +++ /dev/null @@ -1,24 +0,0 @@ -# 决策:密钥 runtime 化——SecretStore(AES-256-GCM + 专用加密根密钥) - -- 日期:2026-07-04(Round 33) -- 状态:已定案并线上实证(default provider 的 LLM_RELAY_KEY 仅存 KV,模型调用成功) - -## 决定 - -1. 平台运行时密钥(provider secretRef、plugin 凭据等)不再只能走 wrangler secret,新增 **SecretStore**:`POST /htbp/platform/secret` 四动词(set/list/rm/…),**永不回显值**;CLI `watt secret`(值走 stdin)。 -2. 加密:**AES-256-GCM**,根密钥为**专用 `WATT_SECRET_ENCRYPTION_KEY`**(gateway wrangler secret);**AAD = 密钥名字**(防密文换位重放);密文存 **KV_TENANTS 的 `secret:` 前缀**。 -3. 读取链:**resolveSecret env 优先 → KV 回退**——env 里已配的(如 ANTHROPIC_API_KEY)不受影响,KV 是补充面。 -4. **明确排除项**:`keys.ts` 的 JWT 私钥不进 SecretStore——它是信任根,若经 SecretStore 读取会形成信任根循环(解密 SecretStore 需要的信任面又依赖它自己)。 - -## 理由 - -1. **不从 JWT 私钥派生加密密钥**:JWT 私钥有轮换语义(--rotate),派生会导致每次轮换毁掉全部已存密文;专用 key 让"签名轮换"与"密文根"解耦。 -2. AAD 绑定名字:同一根密钥下,A 名下的密文不能被搬到 B 名下解出(GCM 认证失败)。 -3. 永不回显 + 值走 stdin:密钥不落 shell history / 日志 / API 响应。 -4. runtime 可写(不需 redeploy)是 `watt init` 向导与 dashboard 配置页的前提。 - -## 影响 - -- gateway 新增 secret `WATT_SECRET_ENCRYPTION_KEY`(deploy-all secrets 检查已收录)。 -- ModelProvider 的 secretRef 解析接 resolveSecret(default provider kv-relay 即实证)。 -- dashboard SecretsView / ProvidersView(secretRef 下拉)消费此面。 diff --git a/llmdoc/memory/decisions/task-workflow-instance-id.md b/llmdoc/memory/decisions/task-workflow-instance-id.md deleted file mode 100644 index e4de28c..0000000 --- a/llmdoc/memory/decisions/task-workflow-instance-id.md +++ /dev/null @@ -1,25 +0,0 @@ -# 决策:taskId 即 Workflows instanceId(免映射表) - -> 2026-07-03(Round 20,Phase 5 Task/M7)。 - -## 决定 - -TaskManager.Write 生成 taskId 后,直接以它作为 Cloudflare Workflows 的实例 id 创建: -`env.WATT_TASK.create({ id: taskId, params })`。Signal / Cancel 经 `env.WATT_TASK.get(taskId)` -直取实例——**不维护 taskId↔instanceId 映射表**。 - -## 为什么 - -- Workflows 的 `create({ id })` 接受调用方指定 id,且 `get(id)` 可按 id 直取——平台原语天然支持外部主键,映射表是纯冗余。 -- 免掉一张映射表 = 免掉一处写入原子性问题(Write 时"建 task 行 + 建实例 + 写映射"三步的部分失败面收窄为两步)。 -- Signal/Cancel/Get 的路径全部单跳:拿 taskId 就能到实例,无需先查表。 - -## 代价与边界 - -- taskId 必须满足 Workflows instance id 的字符集/长度约束(当前 UUID 形态天然满足);若未来 taskId 形态变化需重核。 -- Workflows 实例本身不暴露自定义查询——List/Get 的过滤与投影仍靠 TaskStore(watt-events 库 `tasks` 表,migrations-events/0002);`instance.status()` 仅在 Get 合成时叠加引擎侧执行态。本地 hibernate 时 status 报 running 的坑见 toolchain-pitfalls §41。 -- 同 id 重复 create 会撞实例已存在——Write 侧以 taskId 唯一生成保证不重入。 - -真源:`packages/gateway/src/task/watt-task-workflow.ts` 文件头 + `migrations-events/0002_task_store.sql` 归属说明;doc-gaps #29①。 - -相关:[[scheduler-script-runner]](同 Phase 的 Scheduler 侧决策)。 diff --git a/llmdoc/memory/doc-gaps.md b/llmdoc/memory/doc-gaps.md deleted file mode 100644 index de088d0..0000000 --- a/llmdoc/memory/doc-gaps.md +++ /dev/null @@ -1,53 +0,0 @@ -# 文档缺口清单(doc-gaps) - -> 归并自 bootstrap 阶段五份调查报告(`.llmdoc-tmp/investigations/`)。优先级:P1 = 应修 Docs(影响执行正确性/会踩坑);P2 = 应修 Docs(一致性/完整性);P3 = 记录即可(已知留白/实现自由/非 MVP)。处理后请更新状态列。 - -## P1 — 需修 Docs(易踩坑) - -| # | 缺口 | 位置 | 处置 | 状态 | -|---|---|---|---|---| -| 1 | **Flue 归属勘误**:早期讨论误将 Flue 归为 TokenRollAI,实为 `withastro/flue`;`TokenRollAI/flue` 不存在 | Docs/Reference.md | **已核实无需回写**:Reference.md §1 已含勘误声明("早期讨论曾误将 flue 归于 TokenRollAI…本文以 withastro/flue 为准");LOOP/DOD/Architecture 等仅提 "Flue" 名称、无错误归属。决策记录 [decisions/flue-attribution.md](decisions/flue-attribution.md) | ✅ 已解决(2026-07-02 Round 1 间隙核实) | -| 2 | **Cloudflare 凭据验证判据**:token 为 Account-scoped,`/user/tokens/verify` 会误报 Invalid,脚本用它作健康检查会误判凭据失效阻塞 Phase 0 | DOD.md §9 | **已回写**:DOD §9 已知外部前置补一行"凭据验证判据"。同时在 must/current-state.md 有记录 | ✅ 已回写(2026-07-02) | - -## P2 — 需修 Docs(一致性/完整性) - -| # | 缺口 | 位置 | 处置 | 状态 | -|---|---|---|---|---| -| 3 | Architecture §0 概览表 M10 落地只写 "Pages + Agent 实例",遗漏第三入口 Watt CLI(正文与附B 均有) | Docs/Architecture.md §0 | 需修 Docs:§0 表补 CLI 或加注"见 M10 正文" | 待回写 | -| 4 | M10 命令表自称"节选",无完整 CLI 命令真源;DOD 靠各 Phase 分散验收。核对策略:M10 命令族为上界、DOD 为下界 | Docs/Architecture.md M10 | 需修 Docs:补一份完整 `watt` 命令↔Proto 接口映射表 | 待回写 | -| 5 | Reference.md 未覆盖 4 个实现层 npm 依赖(`@larksuiteoapi/node-sdk`/`jose`/`Hono`/`zod`)的落点定义 | Docs(实现层文档) | 需修 Docs(实现层补齐;Reference 可不改)。当前落点已记 reference/external-facts.md | 待回写 | -| 6 | **Proto §6.4c 步骤 2 交叉引用错位**:正文写"见 e",但 §6.4 只有 a–d;纯人类判定实际在 §6.5b | Docs/Proto.md §6.4c | 需修 Docs:改引用为 §6.5b | ✅ 已回写(2026-07-02:§6.4c 步骤 2 改"见 §6.5b") | -| 7 | **cron grants 疑似矛盾**:`CronJob.action` 仅 `script` 分支有 `grants`,但 §6.4c 步骤 3 要求系统段 `cron:` 取 `action.grants` 作上限——`kind:'agent'`/`'publish'` 的 job 缺省行为(空集 deny?不追加段?)未定义 | Docs/Proto.md §6.4c / §7 | 需修 Docs:明确非 script 动作的系统段规则 | ✅ 已回写(2026-07-02:§6.4c 步骤 3 明确 script 取 grants、agent/publish 不追加上限、禁用/删除→deny) | -| 17 | **inbound 占位错误码超出 WattError 7 码**(Round 1,需决策):gateway 占位曾返回 501 + 规范外 code `unimplemented` | Docs/Proto.md §0.2 / packages/gateway | 已决策:7 码不扩容;Proto §0.2 增补"未认证 401→permission_denied""未实现 501→unavailable"两条规范性补充;实现与测试同步改为 `unavailable`,verify 绿 + 部署后 curl 验证 501/unavailable | ✅ 已解决(2026-07-02) | - -## P3 — 仅记录(已知留白/实现自由/非 MVP) - -| # | 缺口 | 处置 | -|---|---|---| -| 8 | `E2E_FEISHU_ADMIN_OPEN_ID`/`E2E_FEISHU_EMPLOYEE_OPEN_ID` 空——E2E-4 已明确降级为 API 模拟身份,真实双账号后补 | 仅记录(已接受,不阻塞) | -| 18 | **Event dedupe 时间窗口径不一**(Round 4):phase1 调研报告建议 5min,实现取 24h(`packages/core/src/event/dedupe.ts`,可注入参数,注释已声明理由:覆盖渠道重投最坏窗)。回写 Docs 时统一口径为 24h 默认 | 待回写 Proto §1(低优先,实现已自声明) | -| 19 | **Event principal 补齐语义**(Round 4):Proto 未明确平台补齐 principal 时是否覆盖调用方显式传入值。实现选「已有 principal 不覆写,仅缺省且有 channelUser+resolver 时补齐」(`normalizeEvent`)。需 Docs 定夺 | 待回写 Proto §1 | -| 20 | **链段实例→def 解析接口缺失**(Round 4):§6.4c 步骤 3「实例 ID 段取其 agent_def.grants」,但 Proto 未定义由实例 ID 反查 agent_def 的接口(claims 只带当前段)。实现以注入 `instances: Record` map 建模;真实取数应来自 AgentRuntime 实例目录(Phase 4 落地时定) | 待回写 Proto §3/§6(Phase 4 前须收口) | -| 9 | `OPENAI_API_KEY` 与 `ANTHROPIC_API_KEY` 同 key 复用——E2E-5"新渠道"语义可能不足,待 Phase 6 确认 | 仅记录 | -| 10 | 非交互 CI 如何拿 CLI token(device flow 需人工) | ✅ 已解决(2026-07-02 Round 6:Proto §6.5d 规范性补充——device flow 三端点 + 非交互用 `WATT_TOKEN` env(sign-admin-token.mjs 离线签发),实现与集成验收已通过) | -| 11 | `ExpectSpec.schema` 重试次数 N 无默认上下限(Proto §3.2);`ExpectSpec.timeoutMs` 缺省值与 Workflows 24h 默认/365 天上限关系未收口(§3.4);Event `dedupeKey` 去重时间窗无基线(§1/§2.3) | **实现已声明,待回写 Proto §3.4**:schema 重试 maxAttempts=3 已在 #28④ 声明;checkpoint waitForEvent 超时实现声明 **human 10min / agent 5min**(`watt-task-workflow.ts`,显式短超时避开 24h 默认挂起,Round 20 收口);dedupeKey 时间窗见 #18(24h) | -| 12 | `PrincipalRef` 与 `agent:` 前缀边界含糊(§0.3:`agent:` 可作 principal 时与 `agent` 链字段语义重叠未澄清) | 仅记录 | -| 13 | external harness 走 MCP 的传输绑定半缺口(§3.1 `protocol:'mcp'` vs §11.3/§11.4 未展开 agent-harness 的 MCP 细节) | 仅记录 | -| 14 | `TaskDefinition`/`checkpoints` 声明来源未定义(checkpoint 名由部署的 Workflows 代码定义,属"代码部署产物"边界外,可能有意留白) | ✅ 已解决(2026-07-03 Round 20/21):checkpoints 由**部署模板代码声明**——`DEPLOYED_TEMPLATES` in `watt-task-workflow.ts`,ListDefinitions kind='deployed' 返回(实现声明+测试锁定),符合"代码部署产物"边界 | -| 15 | `AgentRuntime.Interrupt` 标注为非 MVP(Dashboard 干预预留,六 Case 不依赖)——实现优先级可缓 | 仅记录 | -| 16 | Proto §8.1 动态编排全节为非规范性预留(additive-only)——后续若见相关代码应视为超前实现 | 仅记录 | -| 21 | **HTBP 节点 `~help`/`~skill` 延后**(Phase 1 关门,2026-07-02):Proto §11.3a 要求每个 HTBP 节点响应 `GET ~help`/`~skill`,Phase 1 的 platform 子树未实现。已决策统一延后:platform 子树最小 ~help 随 Phase 3 Help DSL parser 落地;通用 ~help 生成归上游 tool-bridge(Phase 4,见 loop-contract §2.1 上游通道)。未知路径当前由 gateway notFound 兜底返回 404/501 裸 WattError | 仅记录(有意延后,已入 PROGRESS 关门记录) | -| 22 | **Page cursor 分页延后**(Phase 1 关门,2026-07-02):§0.2 Page 含可选 cursor;Phase 1 PolicyStore.List 返回 `{items}` 省略 cursor(limit 默认 50、上限 200 已按 §6.2 实现),cursor 分页留待数据量需要时的后续 Phase 补 | 仅记录(cursor 为可选字段,省略不违反契约) | -| 23 | **instanceBy='session' 但 event.session 缺失行为未定义**(Round 8):Proto §2.3 未定义该组合。实现取显式 `invalid_argument` 错误、不静默 fallback(`packages/core/src/eventbus/instance-key.ts` 注释声明理由),由订阅建立时保证 session 存在。另两项实现声明:type 通配 `"*"` 全通配合法;`"im.*"` 前缀含点故不匹配裸 `"im"` | 待回写 Proto §2.3(低优先,实现已自声明+测试锁定) | -| 24 | **occurredAt 的 §2.3 Omit vs §2.1 Decode 义务矛盾**(Round 10 关门):§2.3 Publish 参数为 `Omit`(类型上不含 occurredAt),但 §2.1 Decode 义务字段又要求填 occurredAt(渠道侧发生时刻)。已按"调用方已提供则保留、缺省才补接收时刻"实现(core normalizeEvent),并在 Proto §2.3 加规范性澄清(2026-07-03) | ✅ 已回写 Proto §2.3 | -| 25 | **Phase 2 实现声明簇**(Round 10 关门,均已代码注释声明+测试锁定):① Platform API Publish 无条件规约 `source.kind='webhook'`(Phase 2 无 agent token 豁免面;**已收口**:2026-07-04 Round 27,channel-adapter plugin 主体(pluginToken)自报 kind='im' 予以保留,规范性补充已回写 Proto §2.3);② §1.1 system subscriber 的 outbound.message 投递不走出站 Check(系统行为非 Agent 出站);③ im.action→Signal 桩的权限校验点 Check(task://,'signal') 留 Phase 5 与 TaskManager 一并落地(**已落地**:2026-07-03 Round 20,consumer 换真实 TaskSignaler + Check(task://,'signal'),principal 从 event.principal 取、缺省拒绝);④ DLQ 命名 `watt-events-dlq`(仅队列存在,consumer/重放工具留 Phase 6 可观测轮);⑤ webhook adapter session 形状 `webhook::`(scope 退化,与 §1 `::` 的对齐留 doc 漂移候选);⑥ publish 的 queue.send 失败补偿为 best-effort 删留痕(删失败留 console.error,严格原子需 DO/事务);⑦ im.bot_joined payload 形状 §1.1 未定义,实现按 `{channel:string}` 建模 | 仅记录(Phase 4/5/6 对应项到期收口) | -| 26 | **Phase 3 core context 实现声明簇**(Round 11,均代码注释+测试锁定):① TTL 边界含等(`nowMs >= expiresMs` 即回收);② URI 解析:`context://ns` 与尾斜杠均 path="",空 ns / 非 context scheme → invalid_argument;③ 最长前缀匹配按段边界(`feedback/bugs` 不误配 `feedback/bugsy`);④ applyPatch:显式 `content:undefined` 视同未提供(允许替换为空串);⑤ 携带 ifVersion 但条目不存在 → conflict(not_found 由 requireExisting 单独负责);⑥ ~help 每 cmd 行仅附 `scope ` 属性行(q/h/body/returns 等可选项 Round A 未输出) | 仅记录(Proto §4 未明确处的实现取舍) | -| 27 | **Phase 3 关门实现声明簇**(Round 13,均代码注释+测试锁定):① vector provider = D1 sidecar 架构(权威数据在 DB_CONTEXT entries 表与 structured 同表、以 mounts 唯一键防 namespace 撞名;Vectorize 只存 embedding+引用);② unmount(Registry.Delete)只卸载不清 provider 数据、TTL 过期才物理清理(§4.2 "到期整个 namespace 回收"语义按此实现,清理 best-effort);③ ~help 免认证(§11.3a 渐进发现精神,规范未定 ~help 鉴权层级);④ readOnly mount 写动词拒绝码 = 403 permission_denied;⑤ 并发窗口残余声明:R2 onlyIf 收窄到 put 调用内、head-put 间仍非严格原子;vector 的 Vectorize upsert 与 D1 写非原子(D1 为准,孤儿向量由 namespace filter 掩蔽);⑥ 成功响应信封:消费面 Get→{entry}、Write/Update→{meta}、List→裸 Page;管理面 Write→{mount}——形状由 gateway 测试锁定,CLI 精确解包(漂移曾三次致线上 bug);⑦ ~skill 端点仍缺(#21 范围,随 tool-bridge Phase 4) | 仅记录(Phase 4+ 到期收口;⑥ 建议某轮统一信封并回写 Proto §11.3a) | -| 28 | **Phase 4 实现声明簇**(Round 14~18,均代码注释+测试锁定):① harness 由 model 声明推导(AgentRegistry entry.className 恒 AgentInstance,echo/llm 分派看 model);② correlation 宿主 = 独立 DO `AGENT_CORRELATION`(AgentCorrelation),代发/回送**直投 waiter**(绕开 routeResult 去重自吞,见 toolchain-pitfalls §39)+ settled 三态(pending/delivering/settled,投递成功才 settle);③ ToolRegistry/AgentRegistry/ModelProvider 均挂 watt-providers 库(migrations-providers 0001~0003);④ ExpectSpec.schema 校验 = JSON Schema **五关键字子集**(type/properties/required/items/enum,声明子集范围),重试 **maxAttempts=3** → invalid_output(收口 #11 的实现自由声明);⑤ 模型调用超时 60s(AbortSignal.timeout);⑥ terminated 实例行 **TERMINATED_TTL 7d** sweep;⑦ HTBP tools call 请求形状契约:工具名走 URL end-path、body 是 `{arguments}` 信封,代理按 provider 归一化(http 拆信封/mcp·builtin 透传,见 toolchain-pitfalls §38)——建议回写 Proto §5/§11;⑧ ModelProvider resolveDefault 尚未接 harness(死代码,backlog);⑨ Architecture M4 树宿主边界矛盾**已回写**(commit 22960b2:逻辑单一入口、物理分治——gateway 自持 platform/context,tool-bridge 承载 tools,gateway 代理层做 Check PEP) | 仅记录(⑦ 为 Docs 回写候选;⑧ Phase 5+ 顺手接;⑨ 已解决) | -| 29 | **Phase 5 实现声明簇**(Round 19~21,均代码注释+测试锁定):① **taskId 即 Workflows instanceId**(Write 时 `WATT_TASK.create({id:taskId})`,Signal/Cancel 经 get(taskId) 直取,免映射表——决策 [decisions/task-workflow-instance-id.md](decisions/task-workflow-instance-id.md));② checkpoint waitForEvent 超时 **human 10min / agent 5min**(`watt-task-workflow.ts`,收口 #11),超时按平台代发 failed 语义处理;③ TaskStore 挂 **watt-events** 库(migrations-events/0002,归属理由见 migration 头注释:Task 生命周期与 Event Gateway 紧耦合、providers 是静态定义簇、避免单表新库);④ **disabled cron job 仍可手动 Trigger**(补跑/调试面;§7 未限,enabled 只关到点自动触发);⑤ **publish action 亦发 cron.completed**(§7 只要求 script/agent 必发;统一三 action 留痕面);⑥ script 能力表**最小面 = watt.publish**,每次调用过 platform://event 'manage' Check(cron 链段,上限=job.action.grants——决策 [decisions/scheduler-script-runner.md](decisions/scheduler-script-runner.md));⑦ script 的 watt binding 经 **RPC 调用参数**注入而非 loader env(env 字段 structured clone 连 RpcTarget 也拒收,toolchain-pitfalls §44);⑧ Scheduler Write **不做 grants≤createdBy 静态校验**(§7 推迟运行时判定,运行时每次 Check 兜底) | 仅记录(②为 Proto §3.4 回写候选;后续 Phase 到期收口) | -| 30 | **PluginRegistry.Write 注册契约校验的 ~help/~describe 面延后**(Round 27 关门):Proto §11.1/§11.4b 要求注册时「探活 + ~help」契约校验。探活面已实现(R27:外部 endpoint GET healthPath 不通 → invalid_argument 拒绝注册,不落库不签 pluginToken;`binding:` 前缀跳过);~help/~describe 方法集校验依赖 Help DSL 校验生态(同 #21 路线),延后至 tool-bridge ~help 生成收口后 | 仅记录(探活已实现;~help 校验延后声明) | -| 31 | **Phase 6 R27 关门实现声明簇**(均代码注释+测试锁定):① 飞书出站投递 retryable 语义(网络/token 失效→重投 max_retries=3→DLQ;业务拒绝→留痕 ack)+ 飞书 create message `uuid`=event.id 服务端去重;② consumer systemPublish 带确定性 dedupeKey(`system:checkpoint:<源事件id>`)+ findByDedupeKey 窗内短路(重投不重复下发卡片);③ AgentRuntime.Send 先查实例索引,未 Spawn → not_found(idFromName 幽灵 DO 防护);④ 内置种子(plugin/manage defs)为「get 不存在才 write」——不覆写管理员修改;⑤ CLI connect 优先 `WATT_PLUGIN_TOKEN`(plugin 主体 Publish,kind='im' 保留),回落 user token 仅调试;⑥ connectFeishu 经 SDK onError settle(终态放弃→supervisor 退避重建);⑦ pluginToken 复用 user token TTL(1h)且无轮换端点——§11.2 /plugin/token 未实现(MINOR backlog) | 仅记录(后续 Phase 到期收口) | - -## 报告事实漂移订正(勿改 Docs) - -- `FEISHU_ENCRYPT_KEY`:delivery 报告曾记"空",followup 复查 `.env` 为非空。以当前 `.env` 为准。 diff --git a/llmdoc/memory/reflections/2026-07-02-round1-scaffold.md b/llmdoc/memory/reflections/2026-07-02-round1-scaffold.md deleted file mode 100644 index 82805eb..0000000 --- a/llmdoc/memory/reflections/2026-07-02-round1-scaffold.md +++ /dev/null @@ -1,33 +0,0 @@ -# Round 1 — Phase 0 monorepo 骨架搭建(worker 派发流程反思) - -## Task -- 主 assistant 派 `llmdoc:worker` 完成 Phase 0 monorepo 骨架(DoD 项),一轮闭环成功。 - -## Expected vs Actual -- 预期:worker 按 prompt 完成骨架并通过验收命令。 -- 实际:一次成功;worker 额外主动汇报了一处契约偏离(占位错误码 501/unimplemented 超出 Proto §0.2 的 7 码集合),未悄悄糊过去。 - -## What Went Wrong -- 派发 prompt 本身"违宪":主 assistant 在任务指令中指定的占位错误码(unimplemented/501)与 Proto §0.2 错误码集合冲突。靠 worker 主动汇报才暴露,而非派发前拦截。 -- 环境损耗(一句话摘要,细节见 [../../guides/toolchain-pitfalls.md](../../guides/toolchain-pitfalls.md)):npm registry 超时与 pnpm build 门禁消耗 worker 大量轮次。 - -## Root Cause -- 派发前只核对了 DoD 与验收命令,未把 prompt 中出现的契约细节(错误码、事件名等)对照 [../../reference/proto-map.md](../../reference/proto-map.md) 快查一遍——默认"指令即正确",但指令与 Docs 同样可能冲突。 - -## Missing Docs or Signals -- 缺一条派发前检查规则:"给 worker 的指令中所有契约细节须先过 proto-map 自查"。 -- 缺工具链踩坑 guide(新 CF 依赖 / npm registry / pnpm 门禁),本轮已确认需要 `guides/toolchain-pitfalls.md`。 - -## What Worked(保持) -- **高约束 prompt 模板**:写死「精确 DoD 项原文 + 选型约束表 + 验收命令」三件套,worker 一轮闭环。 -- **Reflection Handoff 段落**:worker 结构化汇报契约偏离而非静默妥协——应作为后续派 worker 的模板必备要求。 -- **不信任声称的绿**:主 assistant 亲自重跑 `pnpm verify`(LOOP §3 要求),本次与 worker 汇报一致;成本低,每轮保持。 - -## Promotion Candidates -- `guides/`(新建 dispatch-worker 指南或并入现有派发章节):worker prompt 三件套 + 要求 Reflection Handoff 段落。 -- `must/loop-contract.md` 五步流程第 3 步补一句:派发前对照 proto-map 自查指令中的契约细节。 -- `guides/toolchain-pitfalls.md`:npm registry 超时、pnpm build 门禁、新 CF 依赖前置阅读(细节由 recorder 落盘)。 - -## Follow-up -- 下轮派 worker 前:prompt 里的错误码/事件名/接口面先过 proto-map;模板中固定要求 Reflection Handoff 段落。 -- 请 recorder 落盘 `guides/toolchain-pitfalls.md` 并在 index.md 挂载。 diff --git a/llmdoc/memory/reflections/2026-07-02-round7-phase1-gate.md b/llmdoc/memory/reflections/2026-07-02-round7-phase1-gate.md deleted file mode 100644 index 01809f2..0000000 --- a/llmdoc/memory/reflections/2026-07-02-round7-phase1-gate.md +++ /dev/null @@ -1,39 +0,0 @@ -# Round 7 — Phase 1 关门轮(并行 worker 修复 13 项 finding 的流程反思) - -## Task -- 质量关口确认的 13 项 finding(1 BLOCKER + 8 MAJOR + 4 MINOR)按"互不冲突文件集"拆 4 个 worker 并行修复,主 assistant 收口 + DoD 重跑,Phase 1 正式关门。 - -## Expected vs Actual -- 预期:4 个 worker 各自在授权文件集内闭环,验收命令直接可用。 -- 实际:总体顺利关门,但暴露 3 处派发缺陷(验收命令包名错误、跨包消费方遗漏、文件集必然交叉),均靠 worker 主动上报而非派发前拦截。 - -## What Went Wrong -- **验收命令包名错误**:主 assistant 下发的命令写了 `@watt/cli`,实际 package.json 的 `name` 是 `watt-cli`。pnpm filter 空匹配 exit 0,会造成假通过——幸被一个 worker 发现上报,主 assistant 随即向其他 worker 广播纠正。 -- **跨包契约改动漏列消费方**:gateway List 返回形状 records/cursor→items 的任务只列了 policy.ts 消费方,漏了 cli/audit.ts;负责 worker 无权限修,且 CLI 测试用独立 mock,错配下测试仍绿(线上会 TypeError)。worker 上报"跨界依赖",主 assistant 收口改 audit.ts。 -- **授权文件集必然交叉**:seed once-guard 的连带影响使两个 worker 在 gateway/test/device-flow.test.ts 上必然相遇。worker-platform 越界加了一行 `resetSeedGuardForTests()` 并在结论中显式报告不可兼得性——越界但透明,属良性处理。 -- **部署后立即断言险些误判**:deploy 后首批 curl 命中旧 isolate(Hono 默认 404 纯文本),险些判修复无效;等传播窗口后重测全部符合契约。 - -## Root Cause -- 派发前没有核对 package.json 的 `name` 字段——filter 名是验收命令里的高危细节,空匹配静默成功使错误不可见。 -- 改返回形状时只凭记忆列消费方,未 grep 全部消费点;独立 mock 的测试对跨包契约漂移**无检测力**(mock 与实现同步错、测试绿但集成断)。 -- 文件集划分假设"互不冲突"总能成立,未给必然连带改动预留出口。 - -## Missing Docs or Signals -- 缺派发前检查项:"验收命令中的 pnpm filter 名须与各 package.json `name` 逐一核对"(空匹配 exit 0 是静默陷阱,可考虑 filter 失配时报错的守护写法)。 -- 缺规则:"跨包契约(返回形状/字段名)改动派发前必须 grep 全部消费方列入任务"。 -- 缺检测手段:跨包契约漂移需要契约测试或共享 fixture,独立 mock 不够。 - -## What Worked(保持) -- **worker 主动上报越界/跨界而非静默处理**:三次派发缺陷全部由 worker 的 Reflection Handoff 暴露,广播纠正机制有效。 -- **越界的透明出口**:worker-platform "有理有据越界 + 显式报告不可兼得性"是正确姿势——边界划分应预留"必然连带改动须显式报告"的出口,而非绝对禁止越界。 -- **worker 对评审建议的合理偏离**:占位路由注册在认证中间件之前而非评审建议的 app.notFound(否则 501 被 401 先拦),worker 说明结构性矛盾并给替代方案,主 assistant 采纳。评审 fix 建议是最小修复参考,不是机械执行的指令。 -- **线上断言等传播窗口重测**:deploy 后先等边缘传播/重测两次再下结论,避免误判。 - -## Promotion Candidates -- 派发 worker 检查清单(并入 dispatch 相关 guide):① filter 名对照 package.json `name`;② 跨包契约改动先 grep 全部消费方;③ 文件集划分预留"连带改动显式报告"出口。 -- `guides/toolchain-pitfalls.md`:pnpm filter 空匹配 exit 0 假通过;deploy 后旧 isolate 传播窗口。 -- 测试策略:跨包契约点考虑契约测试或共享 fixture(doc-gap 候选)。 - -## Follow-up -- 下次跨包契约改动:派发前 grep 消费方全集,或直接由单一 worker 端到端负责该契约。 -- worker prompt 模板固化:"遇必然连带改动,允许最小越界但必须在结论显式报告"。 diff --git a/llmdoc/memory/reflections/2026-07-03-round10-phase2-gate.md b/llmdoc/memory/reflections/2026-07-03-round10-phase2-gate.md deleted file mode 100644 index 9670b41..0000000 --- a/llmdoc/memory/reflections/2026-07-03-round10-phase2-gate.md +++ /dev/null @@ -1,39 +0,0 @@ -# Round 10 — Phase 2 关门轮(并行修复交叉污染 + 评审维度裁剪的流程反思) - -## Task -- 质量关口 Workflow(4 维 review + 对抗核查,19 agents)确认的 15 MAJOR 按主题拆 4 个 worker 并行修复,主 assistant 收口 + DoD 线上重跑,Phase 2 正式关门。 - -## Expected vs Actual -- 预期:4 个 worker 各自文件集内闭环,typecheck/测试独立可验证。 -- 实际:全部修复成功关门,但共享工作树并行导致 worker 间在途半成品互相污染验证信号;线上冒烟中途 token 过期 + 代理环境脚本坑各浪费一次排查。 - -## What Went Wrong -- **并行 worker 交叉污染**:4 个 worker 共享同一工作树,worker A 的在途半成品(consumer.ts 扩 ConsumerDeps/import zod 未完成)让 worker B/C 的 typecheck 和 gateway 测试全红。B/C 被迫自救:用 `git worktree` 隔离验证、用 `git show HEAD:` 判断报错归属是自己还是他人在途改动。 -- **注释把错误设计写成正确意图**:CLI tail 的"+1ms 避免重复"注释本身就是 bug 成因(跳过同毫秒事件),审阅时被注释描述带偏;配合一条死测试(守卫条件恒假、断言永不执行)把 bug 锁进了绿灯。 -- **token 有效期撞上长关门轮**:admin token 1h 过期,关门轮跨多小时,线上冒烟中途 401——重签即可,但 sign-admin-token.mjs 在代理环境需 `NODE_USE_ENV_PROXY=1`,不知道这点浪费了一次排查。 - -## Root Cause -- 文件集"互不冲突"只保证了**写入不冲突**,没保证**验证信号不污染**——类型契约耦合(一人扩接口、另一人的测试要消费该接口)使共享工作树下的 typecheck/测试红灯无法归属。 -- review 测试时默认"断言存在即有检测力",没有先问"这个断言有没有可能永不执行"(守卫条件、桩的抛错时机都可能让断言死掉);注释被当作意图真源而非嫌疑对象。 -- 长流程未预检 token 剩余有效期;Node 20+ fetch 不读 env 代理变量这一事实未进任何 guide。 - -## What Worked(保持) -- **评审维度裁剪不降质**:按用户要求去掉渗透性安全维(避免模型拒答),4 维(correctness/contract/ops/test-quality)+ "常规工程质量问题照常报"的措辞足够——15 MAJOR 全部确认、0 误报,质量未因裁维下降。phase-gate guide 已同步改 4 维。 -- **对抗核查连续三个 Phase 误报率 0**(Round 3 / 7 / 10 全数确认成立):说明 review agent 的 finding 证据链质量高(带 file:line + 规范原文)。对抗核查的价值已不在滤误报,而在**修正严重度 + 产出修复所需的完整代码事实链**——保留它,但预期要调整。 -- **worker 自救手段可复用**:git worktree 隔离验证 + `git show HEAD:` 判错误归属,应写进并行派发的 prompt 模板。 - -## Missing Docs or Signals -- 缺派发规则:并行 worker 的文件集之间若存在**类型契约耦合**,要么划进同一 worker,要么 prompt 里明示"你的 typecheck/测试可能因他人在途改动而红,以隔离验证(git worktree)为准"。 -- 缺收口规则:主 assistant 收口时统一跑 verify 收敛格式(biome --write)与残留,而非依赖各 worker 自扫。 -- 缺 review 检查项:审测试先问"断言有没有可能永不执行";注释与实现矛盾时以实现行为为准,注释本身可能是 bug 的书面化。 -- 缺工具链事实:sign-admin-token.mjs(Node fetch)在代理环境需 `NODE_USE_ENV_PROXY=1`;admin token 1h 有效期,长流程中途需预备重签。 - -## Promotion Candidates -- `guides/phase-gate-workflow.md`(并行修复章节):① 类型契约耦合的文件集划分规则;② prompt 明示"红灯可能来自他人在途改动,以 git worktree 隔离验证为准";③ 主 assistant 收口统一 biome --write + verify。 -- `guides/phase-gate-workflow.md`(评审章节):4 维裁剪的实测结论(0 误报、质量不降)已改,补"对抗核查价值 = 修严重度 + 补证据链,非滤误报"。 -- `guides/toolchain-pitfalls.md`:`NODE_USE_ENV_PROXY=1` 代理坑;admin token 1h 有效期与长流程重签。 -- review 检查项(并入 phase-gate 的 review prompt 模板):"死测试探测——守卫恒假/桩抛错时机使断言永不执行"。 - -## Follow-up -- 下次并行派发前:按类型契约耦合(不只是写入冲突)划文件集;prompt 模板加隔离验证条款。 -- 请 recorder 把 NODE_USE_ENV_PROXY / token 有效期两条落进 toolchain-pitfalls。 diff --git a/llmdoc/memory/reflections/2026-07-03-round13-phase3-gate.md b/llmdoc/memory/reflections/2026-07-03-round13-phase3-gate.md deleted file mode 100644 index 84fa158..0000000 --- a/llmdoc/memory/reflections/2026-07-03-round13-phase3-gate.md +++ /dev/null @@ -1,37 +0,0 @@ -# Round 13 — Phase 3 关门轮(宽容解析养漂移 + finding 聚类找共同根因) - -## Task -- Phase 3 关门:质量关口 Workflow(4 维 review + 对抗核查,20 agents)确认 16 MAJOR(去重后 9 个独立问题),2 worker 文件集互斥并行全修,DoD 线上复验后正式关门。 - -## Expected vs Actual -- 预期:Round 12 冒烟修完三个跨包错配后,CLI↔gateway 形状已对齐。 -- 实际:关门评审又揪出第四个(CLI put/patch 解包 `{entry}` 但服务端返 `{meta}`)——冒烟时"人类模式回退字符串"的双形态兜底恰好掩盖;另发现 vector provider 三条独立 MAJOR 同根,需架构级修复而非三个补丁。 - -## What Went Wrong -- **"mock 全绿线上坏"四连击**:Phase 3 冒烟揪出三个跨包错配(contentType 必填缺省 / cat 未解包 `{entry}` / R2 list 缺 include customMetadata),关门评审补第四个(put/patch 解 `{entry}` vs 服务端返 `{meta}`)。前三个靠线上全链暴露,第四个连线上冒烟都没暴露——因为 CLI 写了"两种形状都能解"的兜底,解不到就回退字符串,人类模式下输出看着正常。 -- **架构级 finding 被报成三个独立问题**:vector provider 的 2048 截断丢数据 / List unavailable 违反 §4.1 / Vectorize 最终一致使 read-after-write 不可靠,评审报为三条 MAJOR;实际同一根因——**权威数据放错了存储层**(全文/version/metadata 塞进 Vectorize)。对症是一个架构动作"D1 sidecar"(权威数据入 DB_CONTEXT,Vectorize 只存 embedding+引用),一举修三条,还顺带解锁 metadata-only Update 不重算 embedding。 -- **worker 空转**:p3-providers(Round 12)idle 时源文件已就位但测试未写,若按 idle 即收口会漏验收。主 assistant 用 SendMessage 补要求"验收命令跑绿再报告"后完成。 - -## Root Cause -- **"宽容解析"是漂移的温床**:双形态兜底把契约错配从"第一次运行就炸"推迟到"静默产出错误结果"。mock 各写各的挡不住错配(Phase 1/2 已知),而兜底连线上冒烟这道最后防线也废掉了——解不到就应报错。 -- 评审 agent 逐文件/逐维度产 finding,天然倾向报症状不报病灶;三条症状共享根因时,逐条打补丁会把错误架构固化。 -- worker 的 idle 状态与"完成"没有必然联系;派发 prompt 中验收指令埋在中部,不够醒目。 - -## What Worked(保持) -- **对抗核查连续四个 Phase 误报率 0**(Round 3/7/10/13):关键在 review agent 的证据链质量(file:line + 规范原文)。核查的价值稳定在三件事:修正严重度、补足修复所需代码事实、识别"已声明的实现自由"(非 bug)——本轮核查 prompt 新加了 doc-gaps 台账比对指令,有效滤掉了已声明项。 -- **finding 聚类→找共同根因→再决定补丁还是重构**:vector 三条聚类后一个 D1 sidecar 动作全消,新增 36 测试;若逐条补丁只会在错误的存储层上叠创可贴。 -- **根治纪律已落地**(pitfalls §34):响应形状唯一真源 = gateway 路由测试锁定的形状(Get→`{entry}`、Write/Update→`{meta}`、List→裸 Page);CLI mock 照抄真源并在文件头声明出处;禁双形态兜底。本轮修复即按此执行(删兜底、mock 全对齐、补 headers 断言)。 - -## Missing Docs or Signals -- 缺元规则成文:**宽容解析(fallback/双形态/静默回退)是契约漂移的温床——解析失败就报错,让错配在第一次运行就炸**。§34 记了具体纪律,元规则本身适用于所有跨包边界(不只 CLI↔gateway)。 -- 缺评审收口步骤:对抗核查确认后、派发修复前,先做 finding 聚类找共同根因,再决定补丁 vs 重构。 -- 缺派发规则:"验收命令跑绿再报告,idle≠done"要放 prompt 末尾显眼处;主 assistant 见 worker idle 先核对验收产物再收口。 - -## Promotion Candidates -- `must/loop-contract.md` 或 `guides/phase-gate-workflow.md`:元规则"解析失败就报错,禁宽容兜底"(§34 是其在 CLI↔gateway 的实例)。 -- `guides/phase-gate-workflow.md`(评审章节):① 修复派发前先聚类 finding 找共同根因;② 核查 prompt 保留 doc-gaps 比对指令(本轮实测有效)。 -- worker 派发 prompt 模板:验收指令置于末尾显眼处 + "idle≠done,跑绿验收命令再报告"。 - -## Follow-up -- 请 recorder 把"聚类找根因"与"验收指令置尾"两条并入 phase-gate guide 的评审/派发章节。 -- 下次跨包消费面新增时:CLI 侧先抄 gateway 路由测试的响应 fixture,再写解析代码——顺序颠倒就是本轮四连击的起点。 diff --git a/llmdoc/memory/reflections/2026-07-04-feishu-plugintoken-outage.md b/llmdoc/memory/reflections/2026-07-04-feishu-plugintoken-outage.md deleted file mode 100644 index 55e5afd..0000000 --- a/llmdoc/memory/reflections/2026-07-04-feishu-plugintoken-outage.md +++ /dev/null @@ -1,42 +0,0 @@ -# 2026-07-04 — 飞书群消息静默中断:R35 信任根轮换连坐 pluginToken(R34 遗留验证收口) - -## Task -- 用户报障「给飞书群发消息无回复」。排查确认消息根本没进平台(watt-events 最近入站 im.message 停在 UTC 14:14),逐段探测定位到 pluginToken 401 失效,单步重签修复,真人群消息 e2e 闭环。 - -## Expected vs Actual -- 预期:R34 末尾重签的 pluginToken 应当有效,飞书入站链路可用。 -- 实际:该 pluginToken 在 R35 Root Key 改造期间信任根再次轮换时被连坐吊销——R34 反思 Follow-up 里「新 pluginToken 未经真人群消息验证」的遗留项,实际上遗留的就是一个已经坏掉的 token。入站中断完全静默:飞书侧只见回调 502,平台侧无任何告警。 - -## What Went Wrong -- **信任根轮换后没有立即验证依赖凭据**:R35 的 Root Key 改造属于「顺带」轮换信任根,轮换连坐 pluginToken 的规律 R34 已知,但没有形成「轮换 → 立即重签 + secret put + 假事件探测」的强制动作,而是被动等「下次真人消息」暴露——结果暴露方式就是线上事故。 -- **pluginToken 失效无告警面**:飞书回调收到 502 后只会重试/放弃,watt 平台侧 Publish 401 没有任何主动信号。「入站流量归零」这类物理量目前无人监控。 -- **R34 遗留项被 R35 的改动直接作废而未察觉**:遗留台账记录的是「待验证」,但后续轮次改动信任根时没有回查该遗留项是否已被连坐失效——遗留项的前提条件变化时台账不会自动报警。 - -## What Worked(保持) -- **诊断先查数据真源**:第一步直接看 watt-events 最近入站时间戳 vs 当前时间,立刻判定「消息没进来」(而非「进来了没回复」),比翻日志快一个量级,排查方向一次定准。 -- **分段探测法**:healthz(worker 活着)→ verification token challenge 握手(验签配置对)→ 用 .env 的 FEISHU_VERIFICATION_TOKEN 构造假 im.message.receive_v1 事件(假 chat_id,避免打扰真实群)POST 到 `https://watt-feishu.pdjjq.org/webhook/event` → 返回 502 "platform Publish failed: HTTP 401 invalid or expired token",一次探测把断点精确暴露在 Publish 的 pluginToken 上。假事件同时覆盖「全链含 Publish + D1 落库」两个证明面。 -- **最小面修复(复用 R34 教训)**:只单跑 `npx @tokenroll/watt --json plugin register channel-feishu ...` 重签(严格不跑完整 `setup feishu`,避免 pitfalls §63 def 整体 Write 冲掉线上 grants);pluginToken 经 stdin 直接 `wrangler secret put` 到 watt-plugin-feishu,不落盘不回显;等 15s 传播窗口后假事件重放返回 `{"ok":true}` 且落库。 -- **真人闭环采证**:用户发两条 @watt 消息均入站(17:31:23/17:31:32)且均有出站回复(17:31:46/17:32:10)——R34 遗留的「入站面未采证」就此收口。 - -## Root Cause -- pluginToken 的信任链挂在 JWT 签发密钥上,信任根任何轮换(含 Root Key 改造这类非轮换目的的改动)都会连坐吊销全部在途 pluginToken,且失效表现为下游回调 502 的静默故障。已知规律(R34)没有升级为轮换后的强制运维步骤,验证被推迟到「自然流量」。 - -## 顺带发现(current-state 过时项) -- npm `@tokenroll/watt` 已真实发布 v0.1.1(npx 可直接跑),current-state 里「npm 未真发布」已过时。 -- `~/.watt/credentials.json` 的 admin token 为 7d 长票且仍有效(R35 root key 换发产物)。 - -## Missing Docs or Signals -- 缺轮换 runbook:信任根轮换(不论动机)后的强制三步——重签 pluginToken → stdin secret put → 假事件探测验证(不得以「等真人消息」代替)。 -- 缺告警面:pluginToken 失效静默,平台侧 Publish 401 / 入站流量归零无信号(可考虑 healthz 扩展带 token 自检,或入站水位监控)。 -- 缺排障套路沉淀:飞书入站链路分段探测法(healthz → challenge → 假事件 → D1 落库)目前只在本反思里。 - -## Promotion Candidates -- `must/current-state.md`:更新 npm 已发布 v0.1.1、admin token 7d 长票现状;pluginToken 已于本次重签。 -- `guides/`(运维/轮换 runbook):信任根轮换连坐纪律——轮换后立即重签 pluginToken + secret put + 假事件探测,静默失效不可依赖自然流量暴露。 -- `guides/`(排障):飞书入站分段探测法四步(数据真源时间戳判定 → healthz → challenge → 假 chat_id 事件全链探测),假事件是覆盖 Publish 面的最小无扰探针。 -- `reference/external-facts.md` 或 pitfalls:pluginToken 失效在飞书侧的表征 = 回调 502,`platform Publish failed: HTTP 401` 即信任根连坐信号。 - -## Follow-up -- 请 recorder 更新 current-state(npm 发布 / 7d admin token / pluginToken 重签),并按 Promotion Candidates 落轮换 runbook 与排障指南。 -- 评估 pluginToken 失效的主动信号方案(healthz 自检或入站水位告警),避免第三次静默中断。 -- 后续任何触碰信任根的改动(密钥轮换、root key、签发逻辑)在 DoD 里显式包含 pluginToken 重签 + 假事件验证。 diff --git a/llmdoc/memory/reflections/2026-07-04-round27-phase6-gate.md b/llmdoc/memory/reflections/2026-07-04-round27-phase6-gate.md deleted file mode 100644 index 75be713..0000000 --- a/llmdoc/memory/reflections/2026-07-04-round27-phase6-gate.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -标题: Round 27(Phase 6 关门轮)流程反思 -日期: 2026-07-04 ---- - -# Round 27 反思(Phase 6 关门轮) - -## 1. 验证命令的 exit code 被管道吞掉,差点误报 verify 全绿 - -`pnpm verify 2>&1 | tail -30`、`... | grep -E "Tests"` 这类写法的 exit code 是 tail/grep 的,不是 verify 的——本轮第一次全量 verify 实际上 lint 失败(5 个格式错误),但因为只看了 tail 输出里的测试计数就当"全绿"继续推进,直到第二次带 `echo "exit: $?"` 的运行才暴露。**规矩:判定绿/红的命令必须显式采集 exit code**(`cmd > log 2>&1; echo "exit: $?"` 或 zsh `${pipestatus}`),测试计数只是佐证不是判据。历史轮次的"exit 0"证据若采集方式同样经管道,可靠性存疑。 - -## 2. biome 的 "Found N errors" 与逐条输出里的 `!`(warning)不是一回事 - -排查 lint 红时,`grep -B2 -A8 "×"` 抓到的前几屏全是 `!` 前缀的 warning(noNonNullAssertion),误当成 error 去修了三个无辜文件(后又撤销)。真正的 5 个 error 要用 `--diagnostic-level=error` 过滤才现形(全是本轮新文件的格式/导入排序)。**规矩:biome 排错先 `--diagnostic-level=error`**;warning 面(当前 35 条)是历史容忍区,勿顺手扩大改动面。 - -## 3. Send 拼错 instanceId 静默回显——"11ms 的 onEvent 不可能调过模型"这类物理不可能性是最快的定位器 - -DoD ④ 复验时 send 到 `manage/cron#r27-gate`(正确 id 是 spawn 返回的 `r27-gate`),accepted:true 但 cron 不建、无 agent.failed、tokensToday=0。三层排查(event tail → wrangler tail → 带 expect 重发看 output)最后靠 `{"echo":{...}}` 输出才确认命中幽灵 echo DO。两个教训:① **spawn 响应里的 instanceId 才是真源**,别按"definition#key"想当然拼;② 排查静默失败时先对账**物理量**(耗时/token/留痕行数)——11ms 的 onEvent 直接排除了"模型调用失败"整类假设。该缺陷本身已修(Send 未 Spawn → not_found,commit 2b8de74)。 - -## 4. `.env` 变量"存在"≠"有值"——掩码检查误判了两个空 open_id - -用 `sed 's/=.*/=/'` 掩码查看 .env 时,`KEY=`(空值)也显示 ``,据此误判 E2E open_id 已配置并写进了流程假设,浪费了一次 MapIdentity 调用(还留下了 channelUserId='' 的脏行要清)。**规矩:检查凭据存在性用 `grep -c "^KEY=.\+"`(断言非空)**,掩码展示另做。 - -## 5. 关门轮的质量关口 workflow 继续 0 误报(连续第五个 Phase),对抗核查的价值锚点不变 - -12/12 MAJOR 确认成立、1 条降级 MINOR、0 驳回。本轮新增价值:对抗核查给出的 codeFacts 精确到"修复要改哪几行、消费方有谁、测试补哪个 reset 钩子",主 assistant 直接照单实现(用户要求不派 worker),7 簇修复无一返工。派发核查 prompt 里的三要素保持有效:先假设误报、对照规范原文、比对 doc-gaps/PROGRESS 已声明的实现自由。 diff --git a/llmdoc/memory/reflections/2026-07-04-round33-usability-sprint.md b/llmdoc/memory/reflections/2026-07-04-round33-usability-sprint.md deleted file mode 100644 index 3971f54..0000000 --- a/llmdoc/memory/reflections/2026-07-04-round33-usability-sprint.md +++ /dev/null @@ -1,51 +0,0 @@ -# Round 33 — 可用性冲刺(六期并行 worktree 纪律 + 实测逼出的架构修正) - -## Task -- 用户四痛点驱动的六期并行冲刺:P1 飞书 plugin 化、P2 HTBP 工具注入、P3 SecretStore、P4 CLI npm 化、P5 `watt init` 向导、P6 dashboard 配置页。4 个 worker worktree 并行(3 批次流水),主 assistant 只做合并/冲突/部署/线上验证;最终飞书真实群 @watt 闭环采证(Phase 6 ② 关门)。 - -## Expected vs Actual -- 预期:六期各自 worktree 内闭环,单测全绿即可合并部署。 -- 实际:六期全落地且 `pnpm verify` 1233 全绿,但——① P1/P2 基线漂移险些在旧代码上开工;② 线上实测(真实主体 + 真实链路)逼出三个单测全绿盖不住的架构修正;③ 两条外部环境硬事实(workers.dev 同账户互调被拦、境内被干扰)只有部署后线上失败才暴露。 - -## What Went Wrong -- **worktree 基线漂移**:P1/P2 两个 worktree 都从 f71d8cc(Phase 5 时代)切出,而 main 已在 Phase 7 关门之后。P2 自查发现并自愈(ff 到 main);P1 是主 assistant 收到 P2 报告后主动 SendMessage 预警才纠正。若未发现,两期会在缺失两个 Phase 改动的基线上开发。 -- **单测全绿 ≠ 可用——三个实测逼出的架构修正**: - 1. 终止实例后 session 永久死锁:terminated 行挡住重生,同 session 再也拉不起实例 → 增补 **re-Spawn 复活语义**(Proto §3.2)。 - 2. Authorizer 恒传空 agentDefs(Phase 1 残留)→ 一切 agent 主体在判定步骤 2 被误拒。关键信号早就存在:**lurker 出站当初为绕它直调 core**——绕道本身是坏味道标记,当时没系统性收口,债务一直潜伏到本轮真实主体全链过 PEP 才炸。 - 3. policy `tool://test/*` 不匹配根路径 `tool://test`(~help 根即列表语义)→ 通配语义修正。 -- **外部环境事实两次靠线上失败才定**:同账户 workers.dev 互调被平台拦截(收 404,无文档直说);workers.dev 境内被干扰(飞书回调 3s 握手超时,**用户截图反馈才暴露**)。两次都发生在部署后。 -- **token 生命周期未纳入长回合计划**:admin token 1h 中途过期,且轮换连坐 pluginToken(重签 admin 会吊销 plugin 主体),排查/重签打断流水。 -- **零散返工**:`git add -A` 在含 ignored 嵌套仓库的目录把 worktree 当 embedded repo 收进 index(需 `git rm --cached` 补救);@feishu/@llm 真实资源验证在多问题连环排查中超出"每轮每 tag 一次"预算。 - -## Root Cause -- worktree 创建时默认"当前 HEAD 即最新",派发 prompt 没有基线自检指令——漂移只能靠 worker 自觉或事后发现。 -- 平台层判定链的历史 workaround(绕 Authorizer 直调 core)从未被登记为债务:绕道消除了症状也消除了报警信号,直到新调用方走正路才复现。 -- 线上验证长期只走 happy path + 管理员主体,"真实主体(agent/plugin token)+ 真实链路(PEP 全链)"没有进入常规验证面;外部平台行为(互调拦截、境内可达性)无文档可查,只能实测。 -- 长回合规划只排任务批次,没排凭据生命周期。 - -## What Worked(保持) -- **3 批次流水 + 文件所有权互斥清单写进 prompt**:六期冲突全部是"两侧新增同位置"型(union 保留即解),零语义冲突——所有权划分有效。 -- **主 assistant 角色收敛**:只做合并/冲突/部署/线上验证,不下场写码,六期节奏未互相阻塞。 -- **P3~P6 的基线自检指令**:吸取 P1/P2 教训后,派发 prompt 内置「首步 `git rev-parse HEAD` 对照 main」,四期全部顺利——同轮内闭环验证了该指令有效。 -- **跨 agent 即时横向广播**:P2 报告漂移后主 assistant 立刻 SendMessage 预警 P1,把一个 agent 的坑变成全体的免疫。 -- **用户侧回环是不可替代的验证面**:境内可达性问题任何自动化探测(本机在代理后)都测不出,用户截图是唯一暴露渠道。 - -## Missing Docs or Signals -- 缺派发规则:worktree 派发 prompt 必须内置基线自检首步(rev-parse 对照 main,不一致先 ff/rebase 再开工)。 -- 缺债务纪律:**"绕过平台层"的 workaround 是债务标记,见到就开票**(backlog/doc-gaps),不许只留在代码注释里。 -- 缺部署前检查清单条目:跨 worker 调用形态(同账户 workers.dev 互调被拦 → 用 service binding)与境内可达性(workers.dev 被干扰 → 自定义域名)。 -- 缺长回合规划项:开局盘点凭据有效期(admin token 1h + 轮换连坐 pluginToken),预排重签点。 -- 缺 git 事实:含 ignored 嵌套仓库时 `git add -A` 的 embedded repo 陷阱。 -- 缺预算声明惯例:真实资源验证(@feishu/@llm)在排查型场景多次触发可接受,但需在 PROGRESS 声明超额原因。 - -## Promotion Candidates -- `guides/`(并行派发相关,或 phase-gate-workflow 的派发模板):① worktree 基线自检首步指令;② 跨 agent 坑即时横向广播;③ 文件所有权互斥清单 + 主 assistant 只做合并/部署/验证的分工模式(本轮实证零语义冲突)。 -- `must/loop-contract.md` 或 `guides/`:**绕过平台层的 workaround = 债务开票义务**;线上验证须覆盖"真实主体 + 真实链路"而非仅管理员 happy path。 -- `reference/external-facts.md`:同账户 workers.dev 互调被拦(用 service binding);workers.dev 境内被干扰(自定义域名,Universal SSL 只盖一级)——两条已属稳定外部事实。 -- `guides/toolchain-pitfalls.md`:`git add -A` 嵌套仓库坑;长回合开局盘点 token 生命周期(1h + 轮换连坐 pluginToken)。 - -## Follow-up -- 把「基线自检首步」固化进并行派发 prompt 模板(本轮 P3~P6 文本可直接复用)。 -- 对现存平台层绕道做一次 grep 盘点(已知:lurker 出站绕道本轮保留),逐条开票进 backlog。 -- 部署前检查清单增补两条环境事实条目;请 recorder 按上述 Promotion Candidates 落稳定文档。 -- R33 MINOR backlog 跟进时优先处理 admin token 短命/轮换连坐的运维摩擦(init 的 7d token 或独立 plugin 签名面)。 diff --git a/llmdoc/memory/reflections/2026-07-04-round34-feishu-tool-authz.md b/llmdoc/memory/reflections/2026-07-04-round34-feishu-tool-authz.md deleted file mode 100644 index e6e860f..0000000 --- a/llmdoc/memory/reflections/2026-07-04-round34-feishu-tool-authz.md +++ /dev/null @@ -1,43 +0,0 @@ -# Round 34 — 维护轮:飞书群 agent 工具链路修复(运行时数据推断被证伪 + 两关授权一次修双侧) - -## Task -- 用户截图报障:真实飞书群 @watt 用工具取 uuid 失败——先「"test" 工具树 permission denied」后「503」。主 assistant 派 investigator 出报告(`.llmdoc-tmp/investigations/agent-htbp-test-tree-denied.md`),随后线上修复(零代码改动,全部是策略/def/挂载配置)+ 真实群端到端验证通过。 - -## Expected vs Actual -- 预期:调查报告定位根因 → 按报告一次修复 → 注入验证即绿。 -- 实际:报告的**代码路径结论全部正确**(deny 在 §6.4c 步骤 1、503 是 httpbin 桩经 toolbridge 兜 500),但**两处运行时数据推断被实测证伪**;第一次修复后真实验证暴露第二层拒绝(`agent definition grant exceeded`),补 grants 后第二次注入才闭环(真 uuid 投递进飞书群)。 - -## What Went Wrong -- **investigator 把运行时数据推断写成了结论(两处证伪)**: - 1. 报告断言线上 def 有 `grants: tool://*`(依据是源码种子 `LURKER_SCRIBE_DEF`)——实际线上 def grants 只有 `event://*` write,源码种子≠线上现状。教训→**源码种子只能证明「初始值」,不能证明「现值」**;线上 def/policy/mount 全是 D1/KV/DO 运行时数据,报告里必须归入「Gaps/未验证」而非结论(本报告对 test 树 mount 恰好做对了,对 def grants 没做到——同一报告内双标)。 - 2. 报告由 deny 文案措辞反推「test 已在 toolScopes 内(前缀过了)」——实际线上 def `toolScopes` 已被某次 def 整体 Write 冲回 `[]`,只是**运行中实例的旧快照**还留着 test。教训→文案推断只能证明「本次请求走到了哪个分支」,不能证明持久化配置的现值;且 spawn 快照语义(R33 已知:实例快照不追随 def)意味着「实例行为」与「def 现值」本就允许不一致。 -- **主 assistant 复用报告前未对运行时断言实查**:`watt agent get lurker/scribe` 一条命令就能同时证伪上述两处(grants 与 toolScopes),成本秒级,却在第一次修复后才靠线上真实回复暴露。教训→**报告里凡是「线上态」断言,动手修复前先 CLI 实查一遍**(agent get / policy list / tool get),把「读报告」和「查现场」当两个必做步骤。 -- **「修一层就宣布修好」的诱惑**:§6.4c 是两关判定(步骤 1 principal policy + 步骤 2 def grants),步骤 1 先拦时步骤 2 的缺口完全不可见。第一次只补了 policy,若当时直接宣布修复完成,用户下次 @watt 仍会失败。教训→**两关(多关)授权模型的修复必须一次盘双侧**:补 policy 的同时核对 def grants(以及 toolScopes 前缀约束),三个闸门逐一对照后再验证。 -- **CLI `--json` 位置错误导致 60s 空转误判**:`watt event tail ... --json` 尾部的 `--json` 被忽略(它是全局选项,必须放子命令前:`watt --json event tail ...`),轮询解析不到 JSON 输出,空转 60s 一度误判「无出站」。教训→全局选项前置是本 CLI 的硬语法;轮询类验证若首轮就零输出,先怀疑命令形状再怀疑系统。 - -## Root Cause -- 调查报告体例没有强制区分两类事实:**代码路径事实**(源码可证,静态可靠)vs **运行时数据事实**(线上 D1/KV/DO 态,源码取不到)。investigator 无线上凭据时,后者只能是假设,但报告语气未降级。 -- 真实路径(agent 主体全链过 PEP)此前只在 R33 末尾走通过一次,两关授权的「另一关被前一关遮蔽」特性没有形成修复 checklist。 -- def 整体 Write 覆盖语义(`setup feishu`/`e2e-3` 会把部署侧 toolScopes/grants 冲回默认)是本轮 toolScopes 漂移的源头——幂等脚本的子步骤有破坏性副作用,此前无警示。 - -## What Worked(保持) -- **真实路径验证 loop**:API 注入 @消息到真实群(非 admin 旁路)是唯一能证明 agent 主体授权的手段——第一次验证「失败」本身就是产出(暴露第二层拒绝)。admin 路径 `watt tool call` 只证明了 503 侧修复(挂载换 httpbingo),对授权侧零证明力(admin 种子策略 allow-all)。 -- **轮换连坐的最小面处理**:admin token 过期 → `--rotate` 连坐 pluginToken;本轮**没有**重跑完整 `watt setup feishu`(其第③步 def Write 会冲掉刚修好的 toolScopes/grants),而是单跑 `watt plugin register channel-feishu` 重签 + 重 put secret。教训(正面)→幂等编排脚本的子步骤有副作用时,运维修复只跑需要的那一步。 -- 报告的代码路径部分(文件/行号索引、两关判定顺序、503 透传形状)修复时全程直接复用,零返工——**「源码可证」的部分 investigator 是可靠的**,问题只出在越界推断。 - -## Missing Docs or Signals -- 缺 investigator 报告体例约束:运行时数据断言必须显式标注「未验证」并给出验证命令(如 `watt agent get`),不得以源码种子代替线上现值。 -- 缺修复 checklist:两关授权类 deny 的修复项 =「policy + def grants + toolScopes 三闸门同轮核对」+「真实主体路径验证」。 -- 缺运维警示:`setup feishu`/`e2e-3` 的 def 整体 Write 会冲掉部署侧 toolScopes/grants(PROGRESS 已记遗留:建议 merge 语义或文档警示)。 -- 缺 CLI 事实:`--json` 是全局选项须前置(pitfalls 已有 `--json banner 污染`条目,可就近增补位置语义)。 - -## Promotion Candidates -- `guides/`(investigator 派发模板或调查报告体例):**报告须两栏事实——代码路径事实(行号可证)/运行时数据事实(标未验证+附验证命令)**;主 assistant 复用前对后者逐条 CLI 实查。 -- `guides/` 或 `must/loop-contract.md`:多关授权修复纪律——修一关必核对全部关;验证必走真实主体路径(admin 旁路对授权修复零证明力)。 -- `guides/toolchain-pitfalls.md`:① CLI 全局选项 `--json` 必须置于子命令前,尾部被忽略(轮询零输出先查命令形状);② `setup feishu` 等编排脚本的 def Write 是整体覆盖,会冲掉部署侧 toolScopes/grants——运维修复只单跑所需子步骤(`watt plugin register`)。 -- `reference/` 或 architecture:agent 工具访问三闸门模型(toolScopes 前缀约束 → §6.4c 步骤 1 policy → 步骤 2 def grants)+ spawn 快照不追随 def 的排障含义(实例行为≠def 现值)。 - -## Follow-up -- 真人在群里 @watt 一条,验证入站 webhook 的新 pluginToken(本轮注入走 admin Publish 旁路,入站面未采证——PROGRESS 已记遗留)。 -- 请 recorder 按 Promotion Candidates 落稳定文档;investigator 派发 prompt 增加「运行时断言标注未验证」硬性条款。 -- 后续给 `watt setup feishu` 的 def 步骤加 merge 语义或 `--skip-def` 旗标(R34 遗留)。 diff --git a/llmdoc/memory/reflections/2026-07-04-round36-dashboard-refactor.md b/llmdoc/memory/reflections/2026-07-04-round36-dashboard-refactor.md deleted file mode 100644 index 7aab30b..0000000 --- a/llmdoc/memory/reflections/2026-07-04-round36-dashboard-refactor.md +++ /dev/null @@ -1,42 +0,0 @@ -# Round 36 — Dashboard 全量重构(5 worker 共享工作树并行 + 骨架契约与快照漂移教训) - -## Task -- Dashboard 全量重构:主 assistant 先落骨架(含共享 `platform.ts` 等 wrapper),再按视图族拆 5 个 worker(view-a~e)在**同一工作树**并行实现,各族文件集互斥,主 assistant 收口跑全包门禁。 - -## Expected vs Actual -- 预期:骨架期预置的共享 wrapper 可信,worker 照用;并行期各自文件集独立推进,收口一次全绿。 -- 实际:五族零文件冲突、全包门禁收口顺利,但——① 骨架 `platform.ts` 里一处凭调查报告写的响应形状与 gateway 真源不符,被 view-e 双证发现;② 并行期两起「快照漂移」(跨 worker 复制旧版模式、基于旧快照的门禁转发);③ gitignore 遗留规则静默吞掉整个新建 api 层,收尾自查才暴露。 - -## What Went Wrong -- **骨架期埋契约错误的放大效应**:主 assistant 在骨架 `platform.ts` 里凭调查报告写 `registerPlugin` 响应形状(`{plugin, pluginToken?}`),与 gateway 真源(`{registration: {...}}`)不符。view-e 以 gateway `routes.ts` + `cli/src/plugin.ts` 双证发现,因 `platform.ts` 属禁改文件,在自己文件里正确重实现并上报。教训→**骨架期预置的 wrapper 必须逐个对照真源**,「先写个大概等 worker 用时再校」会让错误形状被下游信任;worker 面对禁改文件里的错误,正确动作 = 新文件重实现 + 上报,而非沉默绕过或擅改禁改文件。 -- **并行 worker 的「快照漂移」二连**: - 1. view-b 复制了 `view-c-parts.tsx` 早期版本的 JsonField 写法(useMemo + biome-ignore),而 view-c 期间已重构成纯函数式,view-b 的旧写法在新 biome 配置下报错。教训→**跨 worker 复制模式前以当前磁盘态为准**,重新 Read 目标文件,不用记忆里的版本。 - 2. view-d 全包扫描报出的 C/E 族门禁问题,实际 C/E 在收到转发前已自修完——扫描基于旧快照。教训→转发门禁问题清单时**注明扫描时间**;接收方先复核现状再动手修,避免对已修问题二次施工。 -- **gitignore 遗留规则的静默吞档**:Python 模板遗留的 `lib/` 规则把整个新建 api 层排除在 git 外,直到 view-e 收尾自查 `git check-ignore` 才暴露。教训→**新目录结构落地时立即 `git status` 核对新文件全部可见**,不要等到提交时。 - -## Root Cause -- 骨架期对「共享契约代码」与「占位代码」没有区分对待:wrapper 是全体 worker 的信任锚点,其正确性标准应等同真源,但落骨架时按占位代码的松标准写了。 -- 共享工作树并行的本质是「无隔离快照」:任何 worker 对磁盘的读取都可能是他人改动前/后的瞬时态,跨族复制与全包扫描两个动作天然携带过期风险,此前无「以当前态为准 / 标注扫描时间」的纪律。 -- 项目由 Python 模板起步,语言栈切换后 gitignore 未做全量清查,遗留规则与新目录命名(`lib/`)撞车。 - -## What Worked(保持) -- **全包门禁 vs 文件集隔离的约定**:多 worker 共享工作树时 `pnpm typecheck`/biome 全包必然互相看到在途红——各 worker 以「本人文件集零 error」自证,主 assistant 收口时跑全包。本轮五族零文件冲突,约定运转良好。 -- **有理偏离的良性案例**:view-b 实测 `agent-runtime.ts` Spawn 带 expect 会触发 harness 空跑一次 LLM,主动偏离任务书(改为不带 expect)并写明证据与回退选项。派发词给了「新会话=Spawn 带 expect」的错误细节,worker 用运行时事实纠正——正面例证了**验收指令给意图、不给实现细节**。 -- **Docs 先行的低摩擦实践**:EventStore correlationId filter 十行改动,按「Docs 是宪法」先改 Proto §2.4(附动机)再改码,总成本几分钟——小改动走宪法流程并不慢,不必为省事跳过。 -- view-e 的双证发现路径(gateway 真源 + CLI 消费方互证)与「重实现 + 上报」的处置,可作为 worker 面对上游错误的标准动作。 - -## Missing Docs or Signals -- 缺骨架期纪律:共享 wrapper/契约代码落笔时必须逐个对照真源(gateway routes + 一个真实消费方双证),不得以调查报告转述代替。 -- 缺共享工作树并行纪律:① 跨 worker 复制模式前重新 Read 当前磁盘态;② 转发门禁清单注明扫描时间、接收方先复核现状;③ 各 worker 以本人文件集零 error 自证 + 主 assistant 收口全包(本轮已实证,应固化成文)。 -- 缺项目起步检查项:语言栈/模板切换后全量清查 gitignore;新目录落地即 `git status` 核对可见性。 -- 缺派发模板正面条款:验收指令给意图不给实现细节,worker 有权以运行时证据偏离并上报(view-b 案例可引)。 - -## Promotion Candidates -- `guides/`(并行派发相关):共享工作树多 worker 的三条纪律(复制以当前态为准 / 门禁转发注明扫描时间 / 文件集自证 + 收口全包)+ 骨架期共享契约代码的真源双证要求。 -- `guides/` 或派发模板:worker 面对禁改文件中的错误的标准动作(新文件重实现 + 上报);验收指令给意图不给实现细节。 -- `guides/toolchain-pitfalls.md`:① Python 模板遗留 `lib/` gitignore 规则会吞新建目录,新结构落地即 `git status`/`git check-ignore` 核对;② agent-runtime Spawn 带 expect 触发 harness 空跑一次 LLM。 - -## Follow-up -- 请 recorder 按 Promotion Candidates 落稳定文档;并行派发 prompt 模板增补共享工作树三纪律与骨架真源双证条款。 -- 清查根 gitignore 中 Python 模板遗留规则,删除或改为精确路径,避免再吞新目录。 -- 对骨架期其余 `platform.ts` wrapper 做一次逐个对照真源的回扫(本轮只确证了 `registerPlugin` 一处错误)。 diff --git a/llmdoc/meta.json b/llmdoc/meta.json new file mode 100644 index 0000000..bd731bd --- /dev/null +++ b/llmdoc/meta.json @@ -0,0 +1,51 @@ +{ + "schema": "llmdoc.meta/v3", + "baseline": { + "revision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5", + "verifiedAt": "2026-08-25T08:11:36.456Z" + }, + "documents": { + "architecture.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "contracts/auth-and-errors.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "contracts/events-and-results.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "contracts/htbp-and-platform-apis.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "dashboard/architecture.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "integrations/feishu-and-plugins.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "operations/credentials-and-recovery.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "operations/development-workflow.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "operations/toolchain.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "operations/topology-and-provisioning.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "runtime/agents-and-tools.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + }, + "runtime/tasks-and-scheduler.mdx": { + "validatedRevision": "da1f7050afa1c68209fdcfcf3c21da96289be3b5" + } + }, + "convergence": { + "capturedAt": "2026-08-25T08:11:10Z", + "source": "init", + "documentCount": 12, + "totalEstimatedTokens": 6554 + } +} diff --git a/llmdoc/must/current-state.md b/llmdoc/must/current-state.md deleted file mode 100644 index 178df71..0000000 --- a/llmdoc/must/current-state.md +++ /dev/null @@ -1,129 +0,0 @@ -# 当前项目状态快照 - -> 本文档随轮次更新。最后更新:2026-07-04(R36 Dashboard 全量重构:RR7 + shadcn 16 视图全 CRUD + manage 对话;**Phase 0~7 全关门 + Phase 6 ② 已采证——项目全部 Done**)。 - -## 阶段 - -- 规格真源:`Docs/{Vision,Architecture,Proto,Plugin,Reference}.md` + `DOD.md`(验收)+ `LOOP.md`(执行契约)。 -- **Phase 0~7 全部关门**(0~5 于 R3/7/10/13/18/22;6 于 R27;7 于 R32 六条 E2E 全绿)。**Phase 6 ② @feishu 入站真实群消息已采证**(R33:webhook plugin 主路径,真实群 @watt 收到回复,用户截图 + events/audit 双留痕)→ **DOD §0 五条全局 Done 全部成立,项目全部 Done**,转入维护/可用性迭代。 -- **R33 可用性冲刺新增面**(六期并行 + 线上实测增补,详见 PROGRESS Round 33 与 4 个新决策记录): - - `packages/plugin-feishu` — 独立 Worker `watt-plugin-feishu`(第一个 watt-plugins/* 实例):自持 webhook 回调(验签/AES 解密/decode/mentions 展开/Publish)+ §11.4 Encode/Send 面 + 凭据自持;custom domain `watt-feishu.pdjjq.org`。决策见 [../memory/decisions/feishu-plugin-webhook.md](../memory/decisions/feishu-plugin-webhook.md)。 - - gateway `src/event/plugin-sender.ts` — **通用出站分发器**(adapter→`channel-`(settings.pluginId 可覆盖)→PluginRegistry→`binding:` service binding / HTTPS §11.4 Send);**feishu-sender 已删,FEISHU_* 移出 gateway**。决策见 [../memory/decisions/plugin-outbound-dispatcher.md](../memory/decisions/plugin-outbound-dispatcher.md)。 - - htbp 三工具(`harness/htbp-tools.ts`:htbp_help/skill/call,scope 前缀约束 + Check PEP + deny 回喂;tools-proxy 抽取 `tools/tool-invoker.ts`)+ `AgentDefinition.systemPrompt`(Proto §3.1 先行);spawn 落 toolScopes/systemPrompt 进 state。 - - **SecretStore** — `platform://secret` 四动词永不回显;AES-256-GCM + `WATT_SECRET_ENCRYPTION_KEY`;resolveSecret env 优先→KV 回退链。决策见 [../memory/decisions/secretstore-runtime-keys.md](../memory/decisions/secretstore-runtime-keys.md)。 - - CLI 改名 **`@tokenroll/watt`**(tsup bundle dist/bin.js + `release:cli`;**npm 已真实发布 v0.1.1,`npx -y @tokenroll/watt` 可直接运行**)+ 新命令族 `watt init`(TUI 九步向导)/ `watt setup feishu`(幂等五步)/ `watt secret`(三命令,值走 stdin)。 - - dashboard 三配置视图(SecretsView / ChannelsView(feishu plugin 状态卡)/ ProvidersView(secretRef 下拉))。 - - lurker 接真实 LLM(default provider caller + state.toolScopes 工具 + scratch 上下文进 system);**re-Spawn 复活 terminated 实例**(Proto §3.2 已补,Send 不复活);**Authorizer 接 AgentDefLoader**(agent 主体 PEP 误拒系统性收口)。决策见 [../memory/decisions/agent-spawn-revive-and-defloader.md](../memory/decisions/agent-spawn-revive-and-defloader.md)。 -- **R33 遗留清单**:~~npm publish 未真执行~~(**已收口**:`@tokenroll/watt` v0.1.1 已真实发布,`npx -y @tokenroll/watt` 可直接运行);`watt init` 真实账户全程未跑(交互式 TUI,建议前缀 watt-init-test);飞书 app 缺「接收群聊中所有消息」权限(群上下文只累积 @ 消息);未配 ENCRYPT_KEY(明文+token 校验模式,建议后台生成后 secret put 到 plugin);~~admin token 1h 短命且轮换连坐 pluginToken~~(**R35 已缓解**:Root Key 换发 7d admin token;pluginToken 连坐仍在,标准修复流程见 pitfalls §65);~~httpbin.org 桩工具不稳~~(**R34 已收口**:test/echo get-uuid 已 remount 到 httpbingo.org/uuid)。 -- 历史 backlog:Phase 5(script 能力表已扩 watt.queryMetric,模板 def 已种子化——基本收口,见 doc-gaps #29);Phase 4 的 13 MINOR + Phase 3 的 14 MINOR + Phase 2 的 17 MINOR + R27 的 19 MINOR + R32 的 16 MINOR,维护态顺手修;实现声明簇见 doc-gaps #25~#28/#31。 -- Phase 路线:0 骨架/部署管道 → 1 Auth+Event 信封 → 2 Event Gateway → 3 Context Layer → 4 Tool+Agent Runtime → 5 Task+Scheduler → 6 飞书+Observability+Management → 7 六条 E2E 验收(详见 [../overview/project-overview.md](../overview/project-overview.md))。 -- 上游 tool-bridge:分支 `feat/watt-builtin-and-tool-semantics`(56ab13b,已 push 未开 PR/未合 main)——ToolSpec effect/scope/confirm 语义字段 + builtin 节点类型。Watt 侧以 vendored 方式消费(见下)。 - -## 源码现状(Round 36 后,测试共 **1292 passed / 1 skipped**:shared 6 + dashboard 71 + core 411 + plugin-feishu 47 + cli 175 + gateway 582+1skip;core 覆盖率 100%,门禁挂 verify) - -Phase 6 新增面(R23~R27,详表见 PROGRESS 对应轮):gateway `src/audit/`(AuditStore + Authorizer wrapper 单点 writeAudit,watt-audit 0001)、`src/metrics/`(usage 表 D1+AE 双写 + Metrics.Query + /htbp/platform/metrics)、`src/plugin/`(PluginRegistry watt-providers 0004 + 内置 webhook/feishu 种子 + Write 注册探活)、`src/agent/manage/`(manage/cron·platform 种子 def + scheduler 工具绑定)、CORS 中间件;core `src/channel/feishu.ts`(WS 事件 decode / 卡片 encode);cli `channel connect`(WSClient + supervisor 重连,**R33 起降为 dev-only 备用**)、`metrics query`、`plugin register|list|health`、`status` 汇总;**packages/dashboard**(R36 全量重构,见下条目)。Phase 7 新增面:`scripts/e2e/{lib,index,e2e-1..6}.ts`(`pnpm e2e`;E2E_LLM/E2E_FEISHU 门控);echo def 种子化;harness 接 default provider('default' 哨兵);script watt.queryMetric;模板真实化;lurker/scribe 潜伏 agent;sign-admin-token --extra 多身份。 - -- `packages/shared` — `WattError`(规范 7 码,裸 body 契约见 [../memory/decisions/bare-watterror-body.md](../memory/decisions/bare-watterror-body.md))。 -- `packages/core`(@watt/core,平台核心纯逻辑,零 Cloudflare 依赖): - - `src/authz/` — `authorize()` §6.4c 四步判定 + subject 匹配 + 工具动作映射。 - - `src/event/` — Event 信封(128KB 上限、DedupeStore、normalizeEvent occurredAt 保留语义、eventInputSchema)。 - - `src/auth/` — `jwt.ts`(jose Ed25519)+ `device-flow.ts`(RFC 8628)。 - - `src/eventbus/` — 订阅匹配/instanceKey/inbound/outbound/hmac 纯逻辑(详见 Round 8 记录与 doc-gap #23)。 - - `src/context/` — Context Layer 纯逻辑(types/resolve/ttl/verbs/help,实现声明 doc-gaps #26)。 - - `src/agent/` — `types.ts`(AgentDefinition(**R33 增 systemPrompt**)/SpawnRequest/ExpectSpec/AgentResult/AgentFailed zod)、`correlation.ts`、`routing.ts`(§3.4 六条规则纯判定)、`expect-schema.ts`(JSON Schema 五关键字子集,schema 重试 maxAttempts=3 → invalid_output)、`model-provider.ts`。Spawn 幂等键复用 eventbus resolveInstanceKey。 - - `src/tools/` — `types.ts`(toolMountSchema §5.2:mcp/http/builtin + virtualize 四项)。 - - `src/task/` — `types.ts`(TaskInfo 7 态/TaskDetail/SignalRequest/SchedulerCronJob)、`signal.ts`、`event-names.ts`、`cron.ts`(五段分钟级子集手写解析,toolchain-pitfalls §43)、`checkpoint.ts`。 - - `src/channel/feishu.ts` — 飞书事件 decode / 卡片 encode 纯逻辑(webhook 与 WS 共用)。 -- `packages/toolbridge` — **vendored** 上游 `TokenRollAI/tool-bridge` worker 源码 **@56ab13b**(vendor src/worker + ASSETS patch + patch 守卫脚本;升级流程见该包 README.md)。部署为独立 Worker `watt-toolbridge`。 -- **`packages/plugin-feishu`(R33 新增)** — 独立 Worker `watt-plugin-feishu`,第一个 watt-plugins/* 实例:`/webhook/event` 自持回调(challenge 握手 + 验签(纯 sha256 拼接非 HMAC,pitfalls §61)+ AES 解密 + decode + mentions 展开 + 以 pluginToken 调平台 Publish)+ §11.4 Encode/Send 面(gateway 经 service binding 调入)+ FEISHU_* 凭据自持。 -- **`packages/dashboard`(R36 全量重构)** — React Router 7 framework mode SPA(`ssr:false`)+ Tailwind v4 + shadcn/ui(23 组件,`app/components/ui` 视为 vendored 不 lint):**16 视图**(Overview/Metrics/Events/Audit/Agents/Manage Chat/Tasks/Cron/Context/Tools/Policies/Providers/Channels/Plugins/Secrets/Settings)**全 CRUD + manage 对话**,与 CLI 十六命令族对齐(init/login/channel connect 三个纯本地命令除外)。`app/` 结构 = `routes/` + `lib/api/{core,types,platform,+各 domain 文件}`(barrel export *)+ `components/ui`;测试 71 个纯 api 契约测试;产物 `build/client` → dist/,gateway assets 同域托管管道零改动。选型与 manage 对话轮询方案见 [../memory/decisions/dashboard-rr7-stack.md](../memory/decisions/dashboard-rr7-stack.md)。 -- `packages/gateway`(Hono Worker,入口 export fetch/queue + 四个 DO 类): - - `src/authz/` `src/event/` `src/context/` `src/http/{auth,errors,oauth,routes,context-routes,inbound}` — Phase 1~3 面(详见 doc-gaps #25/#26/#27 与 PROGRESS Round 5~13)。**R36:EventStore.List filter 增 `correlationId`**(json_extract payload;Proto §2.4 先行增补,dashboard manage 对话轮询 agent.result 用)。 - - `src/agent/` — `agent-instance.ts`(AgentInstance = Agents SDK Agent 类,DO;**R33:spawn 落 toolScopes/systemPrompt 进 state;re-Spawn 复活 terminated 并按当前 def 重新快照**)、`harness/`(echo + llm;模型调用经 Vercel AI SDK ai@6 + @ai-sdk/anthropic@3,AbortSignal.timeout(60s);**htbp-tools.ts 三工具 R33**;`lurker.ts` 潜伏 harness R31/**R33 LLM 化**)、`agent-correlation.ts`(直投 waiter + 三态 settle + 超时 alarm 代发)、`agent-registry.ts`、`agent-runtime.ts`(Spawn 幂等 / Send 先查索引防幽灵 / Terminate cascade / ListInstances tree)、`model-provider.ts`(0003,set-default)。**Authorizer 经 AgentDefLoader 惰性播种 claims.agent_def(R33)**。 - - `src/tools/` — `tool-registry.ts`(watt-providers 0001)+ `tool-invoker.ts`(R33 从 tools-proxy 抽取,htbp 工具与代理共用)。 - - `src/task/` — `watt-task-workflow.ts`(WattTaskWorkflow = Workflows WorkflowEntrypoint,deep-research N=3 fan-in + auto-delivery-lite 两模板真实化 R29/30;taskId 即 instanceId 免映射;checkpoint waitForEvent 超时 human 10min/agent 5min)、`task-store.ts`、`task-manager.ts`(七动词)、`task-events.ts`。HITL 闭环:consumer 真实 TaskSignaler + Check(task://,'signal')。 - - `src/scheduler/` — `scheduler-hub.ts`(SchedulerHub = Agents SDK Agent DO)、`actions.ts`(publish/agent/script 三 action 双留痕)、`script-runner.ts`(可注入抽象,[../memory/decisions/scheduler-script-runner.md](../memory/decisions/scheduler-script-runner.md))、`scheduler-manager.ts`。script 能力面 = watt.publish + watt.queryMetric(R28)。 - - `src/event/plugin-sender.ts`(**R33,替换已删的 feishu-sender**)— 通用出站分发器:adapter→`channel-`(settings.pluginId 可覆盖)→PluginRegistry→binding:/HTTPS 双形态 §11.4 Send(platform-token + X-Watt-Request-Id 幂等 + 10s 超时 + retryable 重投)。 - - `src/secret/`(R33)— SecretStore(AES-256-GCM + AAD=名字 + KV_TENANTS `secret:` 前缀)+ `/htbp/platform/secret` 四动词永不回显 + resolveSecret env→KV 回退链(keys.ts JWT 私钥明确排除)。 - - `src/http/tools-proxy.ts` — /htbp/tools 代理:认证→Check PEP→service binding TOOLBRIDGE 转发→call 信封归一化→syncTenantTree。 - - `src/event/agent-deliverer.ts` — consumer agent sink 真实投递;routeResult 三态 peek→deliver→confirm。 - - migrations:`migrations/0001~0002`(watt-policies)、`migrations-events/0001~0002`(watt-events)、`migrations-context/0001`、`migrations-providers/0001~0004`、`migrations-audit/0001`。 -- `packages/cli`(包名 **`@tokenroll/watt`**,R33 改名 + tsup bundle)— **十六命令族**:`status` / `login` / `whoami` / `policy` / `audit` / `event` / `channel`(connect 降 dev-only)/ `context` / `tool` / `agent` / `provider` / `task` / `cron` / **`init`(TUI 九步:auth→问答→provision TS 移植→模板渲染→migrations→信任根三 secret→同进程本地签首 admin token→deploy→SecretStore 写可选密钥;`--resume`/`--resign-admin`)** / **`setup feishu`(幂等五步)** / **`secret`(三命令,值走 stdin)**。部署产物随包(build:deploy esbuild 3 worker + 模板 + migrations + dashboard dist;tarball 684K)。token 顺序 `WATT_TOKEN` env > `~/.watt/credentials.json`(0600)。 - -### 关键契约(Round 18 锁定,后续轮增补) - -- **HTBP tools call 请求形状**:工具名走 URL end-path、body 是 `{arguments}` 信封;gateway 代理按 provider 归一化(toolchain-pitfalls §38)。 -- **correlation settled 三态**:pending → delivering → settled;投递成功才 settle,失败 rollback + retry。 -- **模型调用超时 60s**;**TERMINATED_TTL 7d**;**re-Spawn 复活 terminated(Send 不复活,幽灵防护不变)**。 -- http mount 的 providerConfig 必须是上游 HttpEndpointConfig 形状 `{endpoints:[...]}`。 -- Context 响应信封形状真源 = gateway 路由测试,CLI 精确解包禁双形态兜底(doc-gap #27⑥)。 -- **taskId 即 Workflows instanceId**([../memory/decisions/task-workflow-instance-id.md](../memory/decisions/task-workflow-instance-id.md));Task 状态权威态在 TaskStore 状态表(toolchain-pitfalls §41)。 -- **出站分发契约(R33)**:adapter → plugin id `channel-`(settings.pluginId 可覆盖);同账户走 `binding:`,跨账户 HTTPS `{"tool":"Send"}`([../memory/decisions/plugin-outbound-dispatcher.md](../memory/decisions/plugin-outbound-dispatcher.md))。 -- **密钥引用契约(R33)**:`secretRef` 经 resolveSecret env 优先→SecretStore KV 回退;secret 永不回显。 - -## 部署现状 - -- **三 Worker 拓扑(R33)**:`watt-toolbridge` → `watt-plugin-feishu`(channel-adapter plugin,凭据自持;**custom domain `watt-feishu.pdjjq.org`**——workers.dev 境内被干扰,飞书回调必超时)→ `watt-gateway`(service binding `TOOLBRIDGE` + **`FEISHU_PLUGIN`**)。**deploy 顺序必须 toolbridge→plugin-feishu→gateway**(service binding 目标先存在)——`pnpm deploy:all` 已编排 + 内置 secrets 检查。另有 Pages `watt-dashboard`。 -- watt-gateway 双 URL: - - `https://watt-gateway.shuaiqijianhao.workers.dev` — 本机验证首选;本机直连偶发超时,验证命令带 `https_proxy=http://127.0.0.1:7890`,Node 脚本另加 `NODE_USE_ENV_PROXY=1`(toolchain-pitfalls §28)。 - - `https://watt.pdjjq.org` — CF 边缘正常,本机 ISP DNS 污染持续;`.env` 的 `WATT_BASE_URL` 指向它,本机验证脚本勿直接复用。 -- secrets(R33 重排): - - gateway:`WATT_JWT_PRIVATE_JWK` + `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL` + **`WATT_SECRET_ENCRYPTION_KEY`(R33,SecretStore 专用)**。**FEISHU_* 已移出 gateway**。 - - plugin worker(watt-plugin-feishu):`FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_VERIFICATION_TOKEN` / `WATT_PLUGIN_TOKEN` / `WATT_BASE_URL`。**未配 FEISHU_ENCRYPT_KEY**(`.env` 该值位是注释即空——明文+token 校验模式,pitfalls §59)。 - - put 后 ~15s 传播窗口。 -- **运维事实(R33 实测)**:admin token TTL **1h**;`--rotate` 轮换会**连坐 pluginToken**(同一信任根签发——轮换后只需单跑 `watt plugin register channel-feishu` 重签 + 重 put plugin secret,**勿重跑完整 setup feishu**——其③步 def Write 会冲掉部署侧 toolScopes/grants,pitfalls §63);`sign-admin-token.mjs` 需 `WATT_JWKS_BASE_URL`(jwksBase 已参数化);**default model provider = kv-relay**(secretRef `LLM_RELAY_KEY` 存 SecretStore KV,回退链线上实证)。 -- **运维事实(R34 线上配置修复,零代码改动)**: - - test/echo 树 `get-uuid` 已换 **`https://httpbingo.org/uuid`**(httpbin.org 不稳,R33 遗留收口);test/watt 与 finance/e2e4 仍是 postman-echo 不动。 - - 线上 lurker/scribe def 现为 `toolScopes:["test"]` + grants 含 `tool://test`/`tool://test/*`(read,invoke)——**与代码字面(lurker.ts/setup.ts 默认值)不同步**,重跑 setup feishu/e2e-3 会整体冲掉(pitfalls §63)。 - - 两条策略已入库:`lurker-tool-test-root`(agent:lurker/scribe → tool://test)+ `lurker-tool-test-sub`(→ tool://test/*),均 read,invoke allow(两关授权修复,pitfalls §62)。 - - pluginToken 已于 R34 随 admin token `--rotate` 轮换重签并重 put 到 watt-plugin-feishu;~~入站 webhook 的新 pluginToken 尚未经真人群消息验证~~(**已收口**:R34 的 pluginToken 又被 R35 信任根轮换连坐吊销,本轮重签后已经假事件探测 + 真人群消息双向验证,见下)。 -- **运维事实(2026-07-04 深夜,飞书入站断链修复,零代码改动)**: - - **断链根因**:R34 重签的 pluginToken 被 R35 Root Key 改造的信任根轮换**连坐吊销**——用户群消息全部在 plugin Publish 步骤 **401 静默丢弃**(飞书侧回调收 502),入站彻底断链。 - - **修复**:单跑 `npx @tokenroll/watt plugin register channel-feishu` 重签 pluginToken(manifest 与线上一致:kind=channel-adapter、interface-version=channel-adapter/v1、endpoint=binding:FEISHU_PLUGIN、health-path=/healthz、grants=[{resources:["event://"],actions:["write"]}])+ stdin 直 put 到 watt-plugin-feishu;**勿跑完整 setup feishu**(pitfalls §63)。 - - **验证**:假事件探测(FEISHU_VERIFICATION_TOKEN 构造 im.message.receive_v1 + 假 chat_id)`{"ok":true}` + 真人群消息双向验证通过(两条 @watt 入站均有出站回复)。标准诊断流程沉淀为 **pitfalls §65**。 - - **admin token 现状**:`~/.watt/credentials.json` 存有 **7d admin token**(R35 Root Key `POST /oauth/root/token` 换发产物),日常运维直接可用,无需每次重签。 -- **`pnpm deploy:all` 内置五库 migrations**:`d1 migrations apply {watt-policies,watt-events,watt-context,watt-providers,watt-audit} --remote`(幂等)。 -- Queue `watt-events` consumer 已绑;DLQ `watt-events-dlq` 已建。 -- `scripts/smoke.ts` 内置 5 次重试(toolchain-pitfalls §9)。 - -## 云资源(已真实创建,`pnpm provision` 幂等可重跑) - -| 类型 | 资源 | -|---|---| -| D1 ×5 | `watt-policies`(0001~0002) / `watt-providers`(0001~0004) / `watt-audit`(0001) / `watt-events`(0001~0002) / `watt-context`(0001) | -| KV ×2 | `watt-authz-cache`(判定缓存,未用) / `watt-tenants`(device grant + toolbridge 租户树配置 + **SecretStore `secret:` 前缀,R33**) | -| R2 ×2 | `watt-context-objects` / `watt-artifacts` | -| Queue ×2 | `watt-events`(producer+consumer) / `watt-events-dlq` | -| Vectorize ×1 | `watt-context-index`(1024 维,bge-m3,cosine) | -| Workers AI | AI 绑定(bge-m3 embedding) | -| DO ×5 | `EVENT_ROUTER` / `CONTEXT_REGISTRY` / `AGENT_INSTANCE` / `AGENT_CORRELATION` / `SCHEDULER_HUB` | -| Workflows ×1 | `watt-task`(binding WATT_TASK,class WattTaskWorkflow) | -| Worker Loader | `LOADER`(worker_loaders binding;线上 open beta,DJJ 账户已开通实证) | -| Worker ×3 | `watt-gateway` / `watt-toolbridge` / **`watt-plugin-feishu`(R33,custom domain `watt-feishu.pdjjq.org`)** | -| Analytics Engine | `AE_METRICS`(watt_metrics dataset;本地 no-op 见 pitfalls) | -| Pages ×1 | `watt-dashboard`(https://watt-dashboard-4tn.pages.dev) | - -命名/多库/维度决策见 [../memory/decisions/resource-naming-and-provision.md](../memory/decisions/resource-naming-and-provision.md)。 - -## 本机工具链(已实测,全绿) - -| 工具 | 版本/状态 | -|---|---| -| node | v26.4.0 | -| pnpm | 11.9.0 | -| wrangler | 4.107.0(devDependency 锁定,对齐 vitest-pool-workers 0.18 捆绑版本) | -| gh | 已登录账户 `Disdjj`,scopes 含 `repo`(可 clone/push 私有 tool-bridge) | - -## 凭据状态(`.env`,已 gitignore;不记录任何秘密值) - -- **Cloudflare**:`CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_API_TOKEN` 均存在且 token 有效(Account-scoped,账户 DJJ)。 - - ⚠️ 验证只用 `wrangler whoami`;`/user/tokens/verify` 对 Account API Token 必然误报,勿作判据。 -- **模型**:双路径已实测通——① 中转直连 `https://llm.fantacy.live`(Anthropic Messages 格式);② AI Gateway custom provider(`glm-5.2` / `minimax-m3`)。**当前线上 default provider = kv-relay(secretRef LLM_RELAY_KEY 存 SecretStore KV)**。易错点见 [../reference/external-facts.md](../reference/external-facts.md)。@llm 真实测试每轮每 tag 一次(LOOP 纪律 3)。 -- **飞书**:**主路径 = webhook plugin**([../memory/decisions/feishu-plugin-webhook.md](../memory/decisions/feishu-plugin-webhook.md);WS `channel connect` 降 dev-only 备用);入站/出站均已真实采证;`E2E_FEISHU_TEST_CHAT_ID` 已在 `.env`;`FEISHU_ENCRYPT_KEY` **实为空**(值位是注释)。飞书后台需保持"事件发送至开发者服务器"=`https://watt-feishu.pdjjq.org/webhook/event`。 -- **空缺项**:`E2E_FEISHU_ADMIN_OPEN_ID` / `E2E_FEISHU_EMPLOYEE_OPEN_ID`(入站身份未映射 → user:anonymous 属 §6.3 正确语义,不阻塞);`E2E_WEBHOOK_SINK_URL`(可选);飞书 app「接收群聊中所有消息」权限(缺则 lurker 群上下文只累积 @ 消息)。 - -## 外部仓库可达性(已用 gh 核实) - -- `TokenRollAI/tool-bridge`:私有,可访问,默认分支 `main`;Watt 分支 `feat/watt-builtin-and-tool-semantics`(56ab13b)已 push 未合。 -- `TokenRollAI/HTBP`:公开。 -- Flue = `withastro/flue`(公开);`TokenRollAI/flue` **不存在**([../memory/decisions/flue-attribution.md](../memory/decisions/flue-attribution.md))。 diff --git a/llmdoc/must/loop-contract.md b/llmdoc/must/loop-contract.md deleted file mode 100644 index 165eed8..0000000 --- a/llmdoc/must/loop-contract.md +++ /dev/null @@ -1,57 +0,0 @@ -# 每轮循环执行契约(浓缩自 LOOP.md + DOD §10 约定) - -> 每轮必读。真源:`LOOP.md`、`DOD.md`。本文只做高密度浓缩,冲突时以真源为准。 - -## 四条不可违背纪律(LOOP §0) - -1. **Docs 是宪法**:`Docs/Proto.md`/`Architecture.md`/`Plugin.md` 是规范真源;实现偏离先改 Docs(写理由)再写码;Docs 自相矛盾先修 Docs。 -2. **DOD 是验收法官**:只有 DOD 勾选项算进度;勾选唯一依据是可重跑命令及其输出。 -3. **不伪造进度**:测试失败就报失败;跳过步骤明说;`@llm`/`@feishu` 测试真实消耗资源,每轮每 tag 最多跑一次。 -4. **成熟框架优先,拒绝造轮子**:要"从头写"基础设施时停下先找成熟方案;表外新需求先派 investigator 调研,确认无方案并在 PROGRESS.md 写明理由后才允许手写。 - -## 成熟框架优先表(全表,LOOP §0) - -| 要写的东西 | 用现成的 | -|---|---| -| Agent 实例/状态/调度/WS | Cloudflare Agents SDK(`Agent` 类、`this.state`/`this.sql`/`this.schedule`),不手写 DO 状态机 | -| 长任务/重试/human-in-loop | Cloudflare Workflows(`step.do`/`waitForEvent`),不自造持久化执行引擎 | -| Agent harness | Flue / Agents SDK / Claude Agent SDK(Reference §4),不自写 agentic loop | -| 工具网关/HTBP 树 | tool-bridge 现有实现优先复用;私有仓库 `TokenRollAI/tool-bridge`,缺功能直接改它,不另起平行网关 | -| MCP server/client | Agents SDK 的 McpAgent + `workers-oauth-provider`,不手写协议 | -| 飞书收发 | `@larksuiteoapi/node-sdk`(WSClient + REST),不手写 wss/验签 | -| JWT/JWKS | `jose`,不手写签名验签 | -| HTTP 路由/中间件 | Hono,不手写 router | -| CLI 框架 | commander/citty/clipanion 择一,不手写 argv 解析 | -| 校验/Schema | zod(含 JSON Schema 互转),不手写 validator | -| 模型调用 | 官方 SDK(@anthropic-ai/sdk 等)经 AI Gateway,不手拼 HTTP | - -注意:文档中 "Flue" 归属勘误——真身为 `withastro/flue`,`TokenRollAI/flue` 不存在(见 [../memory/decisions/flue-attribution.md](../memory/decisions/flue-attribution.md))。 - -## 每轮五步流程 - -1. **取 context(llmdoc 优先)**:`llmdoc/index.md` → `startup.md` → MUST 文件;读/建 `PROGRESS.md`;按任务读相关 guides 与 reflections;探索现状派 `investigator` 产 scratch report(`.llmdoc-tmp/`),不在主上下文大面积翻代码。 -2. **定目标(每轮一个 DoD 项)**:从 PROGRESS.md + 当前 Phase 挑一个未勾选 DoD 项作唯一目标,按 Phase 顺序不跳;依赖外部凭据先核对 `.env` 与 DOD §9,缺则记 blocker 换下一项。 -3. **实现(默认并行)**:界限清晰的实现派 worker(给精确 Proto 章节号+验收命令);多互不依赖子任务同消息并行(冲突改文件用 `isolation: worktree`);Phase 关门前跑 review→对抗核查;单点小改动直接自己做。派 subagent 三要求:精确路径与章节号、结构化结论、产出验证后才算数。 -4. **验证(按序全满足)**:① 本项 DoD 命令通过(test-first 先看红过);② 回归 `pnpm verify` 全绿;③ 契约核对 + CLI 同步生长 + 造轮子自查(新写 >100 行无框架基础设施代码对照纪律 4 表);④ 涉部署项 `pnpm deploy:all` + `smoke.ts` 或 `watt` 命令在 `WATT_BASE_URL` 验证;⑤ 证据入账 PROGRESS.md 并勾 DOD。 -5. **收尾沉淀(每轮必做)**:更新 `PROGRESS.md`(末尾追加 `## Round — <日期>`:目标/动作/验证/勾选/沉淀/遗留 六行);durable knowledge 跑 `/llmdoc:update`;流程坑派 `reflector`;Phase 关门时重跑全部 DoD+回归、整体沉淀、回查 Docs 漂移。 - -## tool-bridge 上游通道判断标准(LOOP §2.1) - -Watt Tool Gateway(M4)缺能力**首选去上游 tool-bridge 补齐**,不在 Watt 内 workaround 或平行实现: - -- **通用网关能力**(新节点类型、`~help` 生成、虚拟化、租户、协议细节)→ 上游 tool-bridge。 -- **Watt 特有语义**(Auth 联动、platform 子树接口、Watt Context 子树代理)→ Watt 侧 adapter/配置。 -- 拿不准 → 倾向上游。 - -流程:`gh repo clone TokenRollAI/tool-bridge`(私有,gh 已有访问权)→ 开分支实现(遵循其 Vitest + `src/worker/tb/` adapter 模式),跑通自身测试后 push → Watt 侧引用更新版 → 回到本轮 DoD 项验证 → PROGRESS.md 单独记一行上游改动。 - -## 硬性止损规则 - -- **同一 DoD 项连续 3 轮未闭环**:停止重试,在 PROGRESS.md 记 blocker,换下一项,请人工介入。 -- Cloudflare 凭据验证只用 `wrangler whoami`,不用 `/user/tokens/verify`(Account API Token 会误报 Invalid,详见 [current-state.md](current-state.md))。 - -## DOD 全局约定要点 - -- CLI 增量策略:`watt` CLI 不是独立 Phase,随各 Phase 生长;从 Phase 1 起作为集成验证默认驱动;CLI 是纯 Platform API 客户端,"存在管理旁路"视为缺陷。 -- 每 Phase 通用验收五条:契约一致 / 单元测试(Vitest + `@cloudflare/vitest-pool-workers`)/ 集成测试穿透 / 可部署(`wrangler deploy` + `scripts/smoke.ts`)/ 回归不破坏。 -- E2E 断言协议事实(事件序列、状态迁移、权限判定、数据落点),不断言 LLM 文本内容。 diff --git a/llmdoc/operations/credentials-and-recovery.mdx b/llmdoc/operations/credentials-and-recovery.mdx new file mode 100644 index 0000000..78c2c01 --- /dev/null +++ b/llmdoc/operations/credentials-and-recovery.mdx @@ -0,0 +1,66 @@ +--- +description: Root Key、JWT signing key、SecretStore 与 plugin token 的安全操作边界,以及飞书入站静默中断的分段恢复流程。 +kind: guide +relations: + requires: + - contracts/auth-and-errors.mdx + - integrations/feishu-and-plugins.mdx + related: + - operations/topology-and-provisioning.mdx +code: + paths: + - packages/gateway/src/authz/keys.ts + - packages/gateway/src/http/oauth.ts + - packages/gateway/src/secrets/** + - packages/gateway/src/plugin/** + - packages/plugin-feishu/src/** + - packages/cli/src/login.ts + - packages/cli/src/plugin.ts + - packages/cli/src/secret.ts + - scripts/set-root-key.mjs + - scripts/sign-admin-token.mjs +--- + +# 凭据与恢复 + +## 日常凭据选择 + +人类交互登录优先走 device flow;无人值守脚本使用显式 `WATT_TOKEN`。长期自救用 Root Key 换发 admin token,不通过轮换 JWT 签名私钥续期。Root Key 明文只在生成时展示一次,平台只保存摘要;泄露时重新设置 Root Key,只影响后续换发,不主动吊销已有 JWT。 + +运行时 provider/plugin secret 优先放 SecretStore 或 Worker secret。SecretStore 管理写入从 stdin 接值,管理 API 永不回显;JWT 私钥与 SecretStore 加密根密钥始终是部署期 secret,不能存回 SecretStore。 + +## 识别破坏性轮换 + +替换 `WATT_JWT_PRIVATE_JWK` 会让旧 JWKS key 消失,从而同时吊销 user、agent 和 plugin token。`watt init --resign-admin` 或离线签 admin token 的 rotate 模式都属于此类操作。执行前列出所有派生凭据消费者;执行后不能只验证新 admin token。 + +Root Key exchange 使用当前 signing key 签 token,本身不轮换 signing key。重新设置 Root Key 只替换摘要,也不应与 JWT 轮换混为一谈。 + +## signing key 轮换后 + +1. 用新 admin token 验证 whoami 与 JWKS。 +2. 对每个外部 Plugin 重新执行 PluginRegistry.Write/register,取得新 plugin token。 +3. 通过 stdin 把 token 写回对应 Plugin Worker secret,等待传播窗口。 +4. 对每个 Plugin 走一次最小真实回调或回调等价探针,证明它不仅 health 通过,而且能带 token 调 Platform API。 +5. 查询 EventStore/AuditLog,确认新主体、权限与落库链路。 + +只为重签 plugin token 时不要重跑完整 setup。某些 setup 步骤会整体 Write AgentDefinition,可能覆盖运维时加入的 grants/toolScopes。先查询现值,再只跑需要的 register 与 secret put。 + +## 飞书入站静默中断 + +症状通常是用户消息无回复,而平台没有对应入站事件。按数据面从外到内定位: + +1. 查询 EventStore 最近的飞书入站时间,先区分“没进平台”和“进了但没回复”。 +2. 调 Plugin healthz,确认 Worker 和基础配置存活。 +3. 发送 challenge,验证 webhook 路由与 verification/encryption 配置。 +4. 使用测试 chat id 构造最小 `im.message.receive_v1` 假事件,避免打扰真实群,同时覆盖 Decode → plugin Publish。 +5. 若 Publish 返回 401,重新 register 取得 plugin token,并经 stdin 更新 Plugin Worker secret。 +6. 等传播后重放假事件,确认 success 与 D1 落库;最后让真实用户在群里完成一次双向验证。 + +healthz 通常不验证 plugin token 的 Publish 能力,不能作为最终证据。假事件应使用无效目标或隔离测试群,且不得把 verification token、app secret、chat id 或完整请求体写进日志、PROGRESS 或 llmdoc。 + +## 失败处理 + +- secret put 后失败先考虑传播窗口,再查值是否为空、变量展开是否正确、目标 Worker 是否正确。 +- 401 先区分 JWT signing key 轮换、token 自然过期和 scope 不足;不要一律 rotate。 +- agent 工具拒绝同时核对 Policy、definition grants、toolScopes;plugin Publish 拒绝核对 requiredGrants 与 token scope。 +- 修复结束后记录可重跑的无秘密验证命令和协议事实,不记录实际 token、URL、资源 UUID 或在线配置快照。 diff --git a/llmdoc/operations/development-workflow.mdx b/llmdoc/operations/development-workflow.mdx new file mode 100644 index 0000000..c7bb57a --- /dev/null +++ b/llmdoc/operations/development-workflow.mdx @@ -0,0 +1,63 @@ +--- +description: 本仓库从检索、契约对齐、实现、评审到可重跑验证的工作流,以及并行代理与运行时事实的证据纪律。 +kind: guide +relations: + requires: + - architecture.mdx + related: + - operations/toolchain.mdx + - operations/topology-and-provisioning.mdx +code: + paths: + - LOOP.md + - DOD.md + - PROGRESS.md + - Docs/*.md + - package.json + - packages/*/package.json +--- + +# 开发与验证工作流 + +## 开始任务 + +1. 先按 llmdoc Retrieval Gate 用 search、context、tree、index 或 show 之一缩小知识面;进入新子系统时重新过 gate。 +2. 阅读对应的 Docs 规范和 DOD 验收,不从旧 PROGRESS 记录或源码注释反推契约。 +3. 非平凡计划或修改先与用户对齐;每轮只承诺一个清晰目标和可重跑验收。 +4. 不熟悉的当前状态交给 investigator,稳定文档只由 recorder 写;临时报告放 `.llmdoc-tmp/`,复用前验证。 + +## 实现纪律 + +- Docs 是契约真源。发现规范矛盾或需要扩接口时先改 Docs,再改实现与测试。 +- DOD 是进度证据。只有能重跑的命令与输出能支撑完成状态;不以 agent 自报、测试数量或历史轮次代替。 +- 优先使用 Cloudflare Agents SDK、Workflows、tool-bridge、jose、Hono、zod 和模型 SDK 等已有抽象。准备手写状态机、协议栈或 agent loop 前先证明现有框架无法承担。 +- 绕过 gateway PEP、registry 或统一 invoker 的 workaround 必须开债务票;代码旁路会消除报警信号,不能只留一条注释。 +- 修改接口 shape、字段名或语义时,先搜索所有生产消费方、CLI、Dashboard、脚本和测试 mock。消费方要么同一任务收口,要么显式留给主代理。 +- 接口解析只接受唯一真源 shape。禁止双形态、fallback 字符串或“尽量解析”,让漂移在第一次集成运行时失败。 + +## 调查与排障证据 + +调查报告必须区分: + +- 代码路径事实:可以由当前源码、规范和测试证明,给出路径。 +- 运行时数据事实:D1、KV、DO、线上 definition/policy/mount/token 状态,必须标“未验证”并附查询命令。 + +源码 seed 只证明初始值,不代表当前持久化状态;错误文案只证明请求走到哪个分支,不证明配置现值。动手修复前逐条重新查询运行态。授权类修复必须走真实主体和真实链路验证,管理员 happy path 对 agent/plugin 权限没有证明力。 + +排查多条 finding 时先按子系统、文件和数据 ownership 聚类,找共同根因,再决定补丁还是重构。注释也可能固化错误假设;测试审阅先确认断言确实执行、fake 忠实模拟真实 provider、失败不会被 guard 或宽容解析吞掉。 + +## 并行工作 + +- worktree worker 开工先核对 HEAD 与目标基线;基线落后先更新再写。 +- 文件所有权不仅考虑写冲突,也考虑类型契约耦合。共享工作树中各 worker 对自己的文件集验收,主代理最后跑全包门禁。 +- 跨 worker 复制模式前重新读取当前磁盘态;转发门禁问题标明扫描时间,接收者先复核是否仍存在。 +- worker 遇到授权范围外但必需的连带改动时,最小处理并明确上报;禁改共享契约错误时可在自己的边界内正确实现并报告根因。 +- prompt 给目标、契约章节和验收,不把未经证明的实现细节当命令;验收命令放在末尾,idle 不等于 done。 + +## 验证与收尾 + +按风险依次跑:目标测试 → 受影响包 typecheck/lint → 全仓 verify → 必要的部署/smoke/真实主体 E2E。服务端 route tests 是 API shape 真源,CLI 与 Dashboard fixtures 对齐它。 + +判定命令成败必须读取原命令 exit code。经过 `tail`、`grep` 等管道时启用 pipefail 或单独保存日志,不能把下游命令的 exit 0 当作 verify 成功。Biome 排错先只看 error 级别,避免顺手修历史 warning 扩大范围。 + +完成后更新 PROGRESS/DOD 所需证据;产生可复用架构或流程知识时运行 llmdoc update。阶段关门要重新跑该阶段全部 DoD,并进行 correctness、contract、ops、test-quality 四维 review;BLOCKER/MAJOR 先按“假设误报”对照规范和代码复核,确认项修完并复验后才关门。 diff --git a/llmdoc/operations/toolchain.mdx b/llmdoc/operations/toolchain.mdx new file mode 100644 index 0000000..e99cc5c --- /dev/null +++ b/llmdoc/operations/toolchain.mdx @@ -0,0 +1,70 @@ +--- +description: pnpm、TypeScript、Vitest Workers pool、Wrangler、R2/Vectorize/DO 与共享 worktree 的高价值排障规则。 +kind: guide +relations: + related: + - operations/development-workflow.mdx + - operations/topology-and-provisioning.mdx + - runtime/tasks-and-scheduler.mdx +code: + paths: + - package.json + - pnpm-workspace.yaml + - biome.jsonc + - tsconfig.json + - packages/*/package.json + - packages/*/tsconfig.json + - packages/*/vitest.config.ts + - packages/*/wrangler.jsonc + - scripts/** +--- + +# 工具链排障 + +## pnpm 与构建 + +- pnpm filter 按 package.json 的 `name`,不是目录名;空匹配也可能 exit 0。派发或写脚本前用 workspace list 核对包名。 +- workerd、esbuild 等 postinstall 必须在 workspace build allowlist 中明确允许或明确跳过;ignored build 会让 Vitest 在真正运行前失败。 +- 仓库采用 `noEmit` 与 `.ts` 扩展名导入,包级 typecheck 用 `tsc --noEmit`。直接由 Node strip-types 执行的脚本不能使用 parameter properties、enum、namespace 等需要代码生成的 TypeScript 语法。 +- `scripts/lib/*.mjs` 不自动进入 TypeScript 检查;新增脚本要么保持小而直接,要么改为 TS 并显式加入 scripts tsconfig。 +- 依赖版本需要满足 Cloudflare Agents SDK 的 peer range;不要在知识文档锁死某个精确版本,以当前 package manifest 和 lockfile 为准。 + +## Vitest Workers pool + +- D1 migration 测试要同时完成读取 migration、注入 miniflare binding、setup 应用和 Env 类型声明;缺一步就会出现 `no such table`。 +- 同一测试文件共享 workerd isolate,模块级 once guard 会跨用例存活。beforeEach 清数据库时也要 reset 对应 guard。 +- Hono matcher 在首次请求后锁定;动态测试路由必须在任何 fetch 前注册。 +- AI binding 可能触发远程代理会话;不需要真实 AI 的单测关闭 remote bindings,并经依赖注入 fake。 +- 当前 Workers pool 不提供可靠的全局 fetchMock;需要拦截出站 fetch 时用明确的 `set...ForTests` 注入钩子,并在 teardown 复原。 +- Workflow 在 waitForEvent hibernate 时 runtime status 可能仍是 running,断言 TaskStore 的业务态。 + +## Cloudflare 数据原语 + +- R2 list 默认不一定带 customMetadata,需要显式 include;条件写匹配裸 etag,不用 HTTP ETag。 +- Vectorize 写入最终一致,不能用作 List、version 或 read-after-write 的权威层;使用 D1 sidecar。 +- DO `idFromName` 会为任意名字返回 stub。对外数据面先查权威索引,再取 stub。 +- DO RPC 包装可能破坏联合类型收窄;通过 WattError guard 后必要时显式断言成功分支类型。 +- Dynamic Worker Loader 的 env 必须可 structured-clone,平台能力经 RPC 参数传入;本地没有 Loader binding 时用显式 fake,不能声称验证了真实 isolate。 + +## Wrangler 与部署脚本 + +- 有 routes 时若仍需 workers.dev,wrangler 配置必须显式开启;不要依赖默认值。 +- Wrangler 子命令的 `--json` 支持不统一,认证 banner 也可能污染 stdout;自动化优先解析受控格式或按精确资源名判断。 +- 写 secret 的脚本先验证值非空并去掉 `.env` 行内注释。不要在 zsh 使用不支持的间接展开后继续管道写入,否则右侧命令可能收到空串。 +- 被 shell 捕获的脚本只把最终值写 stdout,所有日志和子进程输出写 stderr。 +- deploy、secret 和 DNS/route 传播后有限重试;首击旧 isolate 不足以判失败。 +- Cloudflare Account-scoped token 用 `wrangler whoami` 验证,不用 user-token verify endpoint。 + +## API 与前端构建 + +- gateway 源码要直接声明所用依赖,不能依赖 workspace 偶然 hoist;运行时解析失败常被 typecheck 漏掉。 +- Tool call 的 URL/body 形状和 Context response envelope 以 gateway route tests 为真源,fake 必须按 provider 类型模拟真实语义。 +- React Router framework SPA 仍需要 server runtime build 依赖;依赖必须位于 production dependencies。Tailwind 指令需要 Biome 的 Tailwind parser,build 与类型生成目录从 lint 排除,vendored UI 目录按 vendored 代码处理。 +- 新建 `app/lib` 等目录后立即用 git status 与 check-ignore 核验;模板遗留的裸 `lib/` 规则会匹配任意层级。 + +## 共享工作树 + +- typecheck 红先用 `git diff`/`git show HEAD:` 判断是否来自自己的在途改动,不修其他 worker 的文件。 +- 不对共享工作树使用 stash;它会卷入其他人的未提交修改。 +- worktree 创建后核对基线,并完整安装 workspace 依赖;只在一个包里 add 依赖可能让其他包的 node_modules 不完整。 +- `git add -A` 前确认目录中没有被当作 embedded repository 的工作树或 vendor checkout。 diff --git a/llmdoc/operations/topology-and-provisioning.mdx b/llmdoc/operations/topology-and-provisioning.mdx new file mode 100644 index 0000000..d5cdc7c --- /dev/null +++ b/llmdoc/operations/topology-and-provisioning.mdx @@ -0,0 +1,67 @@ +--- +description: Cloudflare 资源 ownership、命名、绑定、provision 幂等与部署先后顺序;用于改 wrangler 或部署脚本前评估影响。 +kind: architecture +relations: + requires: + - architecture.mdx + related: + - operations/credentials-and-recovery.mdx + - integrations/feishu-and-plugins.mdx +code: + paths: + - packages/*/wrangler.jsonc + - packages/gateway/migrations*/** + - scripts/provision.mjs + - scripts/deploy-all.mjs + - scripts/build-deploy.mjs + - scripts/smoke.ts + - pnpm-workspace.yaml +--- + +# 拓扑与资源引导 + +## Worker 与绑定关系 + +部署数据面由 gateway、tool-bridge 和独立 plugin Worker 组成。gateway 绑定 tool-bridge 与同账户 plugin 的 service binding,承担 Platform API、Event、Context、Auth、Task、Scheduler、Observability 与静态 Dashboard 分发;tool-bridge 承担 HTBP tools;plugin Worker 自持渠道或 provider 凭据。 + +同账户 Worker 之间优先使用 service binding。把同账户 workers.dev URL 当内部 RPC 可能收到平台级 404,且绕开 binding 的类型与部署依赖。跨账户或第三方 Plugin 才使用 HTTPS endpoint。 + +部署顺序必须先满足 binding target:tool-bridge → plugin Workers → gateway。D1 migrations 在 gateway 发布前应用,Dashboard 静态产物在 gateway build 前生成。`deploy:all` 是顺序真源,单独 deploy 某包时调用者负责满足同样的前置关系。 + +## 存储 ownership + +| 资源 | 所属数据 | +|---|---| +| policies D1 | Policy 与 IdentityMapper | +| providers D1 | Tool、Agent、Model、Plugin registry | +| events D1 | EventStore 与 TaskStore 生命周期投影 | +| context D1 | structured Context、vector sidecar 权威元数据 | +| audit D1 | 授权与操作审计明细 | +| tenants KV | device grants、租户树配置、加密 SecretStore 记录 | +| authz-cache KV | 预留判定缓存;不能假定已启用 | +| context R2 | object Context 与 artifacts | +| Vectorize | embedding 与引用,不是权威正文存储 | +| Queues | Event 分发、重试和 DLQ | +| Durable Objects | Router、Registry、Agent instance/correlation、Scheduler coordination | +| Workflows | Task 的持久化执行 | +| Analytics Engine | 聚合 metrics;审计真源仍是 D1 | + +资源名使用统一 `watt-` 前缀,避免与同账户其他项目冲突。D1 按模块 ownership 分库,新增表优先进入负责该生命周期的现有库;跨模块方便不是共享 schema 的理由。 + +## Context 存储边界 + +object provider 用 R2;structured provider 用 D1;vector provider 采用 D1 sidecar 保存正文、version 与 metadata,Vectorize 只保存 embedding/引用。Vectorize 最终一致,不能承担 read-after-write、List 或乐观并发的权威语义。R2 条件写使用裸 `etag`,不是带引号的 HTTP ETag。 + +ContextRegistry 的 unmount 只移除 namespace mount;provider 数据不承诺同步删除。TTL 回收与孤儿对象清理由各 provider 的维护路径完成,跨 R2/D1/Vectorize 不存在单事务。 + +## Provision 幂等 + +`scripts/provision.mjs` 先 list 再按资源名判断是否 create,并只改 wrangler JSONC 的 marker 区间。Cloudflare CLI 的 JSON 输出支持不一致,且认证 banner 可能污染 stdout,因此脚本不能假设所有 list 都是纯 JSON。 + +幂等验收不是“命令显示 exists”,而是连续重跑后 wrangler 配置字节级不变。新增 binding 字段时必须同步修改 marker 生成逻辑,否则下一次 provision 会抹掉手写字段。JSONC 注入逗号必须落在真实 JSON token 后,不能附在行注释后。 + +## 部署验证 + +Worker deploy、secret put 和路由更新都有传播窗口;首个请求可能命中旧 isolate。验证脚本使用有限重试并同时检查响应内容与 exit code。配置 custom domain 时显式决定是否保留 workers.dev;面向中国境内回调方的 endpoint 需要从回调方网络验证可达性,开发机代理成功不能替代该证据。 + +任何真实部署验证都应使用可回收测试资源,失败路径也注册 exit handler 清理 cron、task、mount 或临时配置。不要把实际账号 ID、资源 UUID、域名或当前在线配置写进本知识面;这些由 wrangler 配置、部署输出与运行时 API 查询。 diff --git a/llmdoc/overview/project-overview.md b/llmdoc/overview/project-overview.md deleted file mode 100644 index fad1fe9..0000000 --- a/llmdoc/overview/project-overview.md +++ /dev/null @@ -1,45 +0,0 @@ -# Watt 项目总览 - -> 真源:`Docs/Vision.md`(定位与验收基准)、`DOD.md`(Phase 划分与全局 Done)。 - -## 定位 - -Watt 是一个廉价、可扩展、协议开放的**云上 Agent 基础设施(Agent Infra)**——不是又一个 Agent 框架,而是框架之下那一层:让任意 harness(Flue / Claude / OpenAI SDK / 自研)通过开放协议(MCP / HTBP / HTTP)接入组织的事件流、上下文与工具,长期自治运行。底座绑定 Cloudflare,空闲近零成本。 - -分层原则:每层由纯接口定义,实现(内置或 Plugin)都是 Provider;层间只经接口交互,无旁路。模块与数据流详见 [../architecture/modules-and-flows.md](../architecture/modules-and-flows.md)。 - -## 六个 User Case(Vision §3,硬性验收基准) - -| # | 场景 | 要点 | 覆盖模块 | -|---|---|---|---| -| 1 | 自动交付需求 | webhook 收 bug 反馈 → Triage 查重登记 → 定位 → Coding Agent(Container)修复 → QA/Review 接力 → PR/CI → **人类确认上线**(checkpoint 卡片→Signal 恢复)→ 回写 Context 置 fixed | M1/M2/M3/M4/M7/M5 | -| 2 | Deep Research | 飞书提问 → Master 出方案卡片等确认 → **Spawn N 个 subagent**(带 expect)各自 websearch → `agent.result` fan-in 汇总回群(超时者平台代发 `agent.failed`) | M1/M2/M4/M7 | -| 3 | 群聊记录 | 机器人入群**只记录不回复**(长驻 DO 空闲零计费),写入带 TTL 的临时 Context namespace;被 @ 时基于积累 context 立即回答 | M1/M2/M3 | -| 4 | 权限控制 | 同一财务 Agent,CEO 可用工具、普通员工在 Tool Layer 调用点被 Auth 拒绝后礼貌拒答;判定主体 = **(调用者 Principal, Agent, 资源)** 三元组 | M5/M4/M1 | -| 5 | Provider 管理 | Admin 看 7 天 token 用量/费用/缓存命中率 → 新增模型渠道并设为默认 | M10/M8/M9/M5 | -| 6 | 定时任务 | 对 Manage Agent 说"每天发 token 日报到飞书群" → 脚本存 `context://automations` → Scheduler 发布 `action=script` cron → 每日 isolate 执行 → 出站 webhook | M10/M6/M9/M1/M3 | - -## Phase 0~7 路线图(DOD) - -| Phase | 内容 | 要点 | -|---|---|---| -| 0 | 工程骨架与部署管道 | pnpm monorepo、`watt-gateway` 骨架、wrangler 绑定占位、`pnpm verify`/`deploy:all`/`smoke.ts`、CLI 骨架(`watt status`) | -| 1 | Auth 内核 + Event 信封 | Proto §0/§1/§6 落地:JWT 三类 token、`Authorizer.Check` 四步算法、PolicyStore+KV 缓存、种子 Policy、WattError↔HTTP 中间件 | -| 2 | Event Gateway(M1) | Ingress、EventBus(Queues+Router DO)、EventStore、ChannelRegistry、内置 webhook Adapter、§1.1 HITL 内置路由 | -| 3 | Context Layer(M3) | ContextRegistry(挂载/TTL/Resolve)、object/structured/vector 三内置 Provider、HTBP Context 子树 | -| 4 | Tool Layer + Agent Runtime(M4+M2) | tool-bridge 集成(缺能力改上游)、Agent Spawn/Send/§3.4 六条路由规则、echo/LLM harness、Model Provider 最小版。首个消耗真实 token 的测试(tag `@llm`) | -| 5 | Task + Scheduler(M7+M6) | Workflows 适配(事件名净化/归并/超时)、deep-research 与 auto-delivery-lite 模板、cron 三种 action、HITL 全链路接通 | -| 6 | 飞书 + Observability + Management | 飞书 WS 长连接 Adapter(tag `@feishu`)、IdentityMapper 映射、Metrics/AuditLog、manage/* Agent、Dashboard 最小版、CLI 完备性核对 | -| 7 | E2E 验收 | 六条 E2E(真实部署+真实飞书+真实模型,`pnpm e2e`,CLI `--json` 驱动);**E2E 通过 = 项目 Done**,此后进入维护态 | - -## 全局 Done(DOD §0,五条同时成立) - -1. 六个 User Case 的 E2E 全部通过(真实 Cloudflare + 真实飞书)。 -2. 每个 Phase 的 DoD 全勾选且依据可重跑命令。 -3. `pnpm verify` 一键绿。 -4. 从零部署 30 分钟内可复现。 -5. Watt CLI 覆盖全部管理面(M10 命令表 + 六条 E2E 以 `--json` CLI 驱动断言)。 - -## 成功标准补充(Vision §5) - -六 Case 走通无旁路;新增 Context/工具/IM 来源 = 实现 Plugin 接口 + 注册;纯 HTTP fetch 的 Agent 能经 HTBP 发现并使用全部工具/Context;空闲月成本近零、成本随用量线性;每层可"对话/界面/命令行"三入口管理同一套接口。 diff --git a/llmdoc/reference/external-facts.md b/llmdoc/reference/external-facts.md deleted file mode 100644 index 32b1494..0000000 --- a/llmdoc/reference/external-facts.md +++ /dev/null @@ -1,56 +0,0 @@ -# 外部事实:Cloudflare 原语、外部仓库、模型渠道、飞书方案 - -> 真源:`Docs/Reference.md`(平台原语与外部项目)、`DOD.md` §9(已知外部前置实测)、followup 调查(gh 核实)。 - -## Cloudflare 原语选型表(Reference §3) - -| 原语 | 用于 | 关键事实 | -|---|---|---| -| Agents SDK(`agents` 包) | Agent Runtime 基座(M2) | `Agent` 每实例一 DO(内嵌 SQLite),同名 ID 恒路由同一实例;`routeAgentRequest()`;生命周期钩子 `onStart/onRequest/onConnect/onMessage/onEmail/onStateChanged/...`;`this.schedule()` 持久化调度 | -| Durable Objects | 长驻 Agent/会话协调点 | 全局唯一单线程 + 就地 SQLite(10GB/对象 paid);Alarms;WS Hibernation;**空闲零计费**("廉价"依据)。限制:CPU 默认 30s(可配至 5min)、单值 ≤2MB | -| Workflows | Task(M7) | `step.do`(自动重试)、`step.sleep`(≤365 天)、`step.waitForEvent`;步骤 ≤10k,step 输出 ≤1MiB | -| Queues | 事件缓冲 + DLQ(M1) | 消息 ≤128KB(Event 信封上限依据)、5k msg/s/队列 | -| Cron Triggers | Scheduler(M6) | 分钟粒度、UTC;配置传播 ≤15min | -| R2 | Context 对象存储(M3) | S3 兼容,**零出口流量费**,$0.015/GB-月 | -| KV | 判定缓存/租户查找(M5/M4) | 全球最终一致;1 write/s/key | -| D1 | 策略/配置/审计/事件留痕 | Serverless SQLite,30 天 PITR,10GB/库 | -| Vectorize | vector Context Provider(M3) | ≤1536 维、10M 向量/索引 | -| Workers AI / AI Gateway | Model Provider + Observability(M8/M9) | 模型路由、缓存、fallback、token/费用分析 | -| Containers / Sandbox SDK | Heavy Runtime(M2) | `Container` 类以 DO 形式绑定,scale-to-zero,按 10ms 计费 | -| Email Routing / Workers / Service | 邮件渠道 | 入站 `email()` handler;出站需 Email Service onboard 发信域 | -| McpAgent + workers-oauth-provider | MCP 上游接入 | 每 MCP session = 一个 McpAgent = 一个 DO;`serve("/mcp")` Streamable HTTP;OAuth 2.1(DCR + PKCE) | - -## 外部仓库归属(gh 已核实,2026-07-03) - -| 仓库 | 状态 | 说明 | -|---|---|---| -| `TokenRollAI/tool-bridge` | **私有**,可访问(gh 账户 `Disdjj` 含 repo scope),默认分支 `main` | Tool Layer 参考实现/网关;线上 tool-bridge.fantacy.live;缺能力改上游(LOOP §2.1) | -| `TokenRollAI/HTBP` | 公开,默认分支 `main` | Tool Layer 协议契约(Draft,仅文档);RFC-0001 在 `docs/rfcs/RFC-0001-htbp-core.md` | -| `withastro/flue` | 公开,"The sandbox agent framework",Apache-2.0 | **Flue 真身**。Docs(LOOP/DOD/Reference)把 Flue 归为 TokenRollAI **属勘误**;`TokenRollAI/flue` 不存在。决策记录见 [../memory/decisions/flue-attribution.md](../memory/decisions/flue-attribution.md) | - -HTBP 与 tool-bridge 二分:HTBP = 协议契约,tool-bridge = 参考实现;"MCP 在上游供给,HTBP 在下游供 Agent 消费";tool-bridge 的 mount 节点桥接 Context。 - -## 模型渠道(DOD §9 已实测,两条路径均通) - -1. **中转直连**:`https://llm.fantacy.live`(Anthropic Messages 格式,用 `ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL`)。 -2. **AI Gateway custom provider**:`https://gateway.ai.cloudflare.com/v1//watt-gateway/custom-tipsy/messages`;`glm-5.2` 与 `minimax-m3` 均通;请求头需 `cf-aig-authorization`(gateway 开了 authentication)+ `x-api-key`。M8 规范路径直接用 gateway,Case 5 的 analytics/缓存指标有原生数据源。 - -**模型调用 SDK = Vercel AI SDK**(`ai` + `@ai-sdk/anthropic` 的 `createAnthropic`),非 `@anthropic-ai/sdk`。理由:供应商中立(换 provider 只改工厂)、workerd 兼容。决策记录见 [../memory/decisions/model-call-sdk.md](../memory/decisions/model-call-sdk.md)。gateway 消费面在 `packages/gateway/src/agent/harness/anthropic-caller.ts`(单次 `generateText`,schema 校验重试留 `llm.ts`)。 - -**custom provider 三个易错点**: -1. 模型 ID 必须**小写**(`glm-5.2`,不是 `GLM-5.2`)。 -2. custom provider 调用要加 **`custom-` 前缀**(`custom-tipsy`)。 -3. 该 provider 的 base_url **已含 `/v1`**,路径写 `/messages`——写 `/v1/messages` 会 404。 - -## 飞书渠道方案要点(2026-07-04 R33 更新:webhook plugin 主路径) - -- **主路径(R33 定案,Phase 6 ② 采证路径)**:独立 channel-adapter plugin `watt-plugin-feishu`(`packages/plugin-feishu`)自持 webhook 回调——challenge 握手 / 验签 / AES 解密 / decode / mentions 展开,以 pluginToken 调 `EventBus.Publish`;出站 §11.4 Encode/Send 面由 gateway 经 service binding 调入。决策记录:[../memory/decisions/feishu-plugin-webhook.md](../memory/decisions/feishu-plugin-webhook.md)。 -- **回调域名境内约束**:workers.dev 境内被干扰,飞书回调 3s 握手必超时——回调面必须挂 custom domain(现为 `watt-feishu.pdjjq.org`;Universal SSL 只盖一级子域,勿用二级)。飞书后台「事件发送至开发者服务器」须保持 `https://watt-feishu.pdjjq.org/webhook/event`。 -- **验签事实**:签名 = `sha256(timestamp + nonce + encrypt_key + body)` 纯拼接,**无分隔符、非 HMAC**(toolchain-pitfalls §61)。 -- **权限事实**:bot 能否收到**非 @ 的群消息**取决于应用「接收群聊中所有消息」权限——缺则只有 @ 消息可达(lurker 群上下文无法累积)。 -- **WS 长连接 push 型降为 dev-only 备用**(`watt channel connect`,`@larksuiteoapi/node-sdk` WSClient 是 Node SDK 不能跑 Workers isolate);原决策 [../memory/decisions/feishu-websocket-channel.md](../memory/decisions/feishu-websocket-channel.md) 已被取代。 -- 出站(Encode/Send 含 actions 卡片,飞书 REST API)已实测通过;测试群 "Tipsy Agent Infra" chat_id 已入 `.env`。 - -## 实现层 npm 依赖(不在 Reference.md 范围,来自 LOOP 成熟框架表) - -`@larksuiteoapi/node-sdk`(飞书)、`jose`(JWT/JWKS)、`Hono`(HTTP 路由)、`zod`(校验/JSON Schema 互转)、`ai` + `@ai-sdk/anthropic`(模型调用;**版本锁 ai@6 + @ai-sdk/anthropic@3**,与 `agents@0.17.3` 的 `ai@^6` peer 一致,勿升 ai@7)、commander/citty/clipanion 择一(CLI)。Reference.md 定位为平台原语层,不覆盖这些库——落点缺口已记入 [../memory/doc-gaps.md](../memory/doc-gaps.md)。 diff --git a/llmdoc/reference/proto-map.md b/llmdoc/reference/proto-map.md deleted file mode 100644 index f546ab6..0000000 --- a/llmdoc/reference/proto-map.md +++ /dev/null @@ -1,166 +0,0 @@ -# Proto.md 契约检索地图与横切契约 - -> 实现轮次查接口契约的第一站。真源:`Docs/Proto.md`(~1050 行)。所有接口方法均为异步(返回 `Promise`,文档省略)。模块归属查 [../architecture/modules-and-flows.md](../architecture/modules-and-flows.md)。 - -## 章节地图(§ → 主题 → 一句话) - -| § | 主题 | 一句话 | -|---|---|---| -| §0 | 通用约定 | URI 体系、通用类型、CallContext、CRUD 四动词全局基线 | -| §0.1 | 资源 URI | 一切可授权资源统一 `://` URI(context/tool/agent/agent-instance/event/task/cron/model/plugin/platform) | -| §0.2 | 通用类型 | `URI`/`Timestamp`/`Page`/`ListOptions`/`WattError` + HTTP 映射(含 401/501 规范性补充)+ 重试规则 | -| §0.3 | CallContext | principal/roles/agent 链/traceId,传输层隐式携带 | -| §0.4 | CRUD 动词 | Registry 类 = List/Get/Write(upsert)/Update(patch);Delete 可选;Provider 类由领域定 | -| §1 | Event 信封 | 一切进出平台的消息统一为 `Event`;≤128KB;出站复用信封 `type:"outbound.message"` | -| §1.1 | 规范化事件(HITL) | `task.checkpoint` + `im.action` + 两条内置路由规则闭合人类确认环 | -| §2 | Event Gateway | §2.1 ChannelAdapter(Verify/Decode/Encode/Send,webhook 型 vs push 型);§2.2 ChannelRegistry;§2.3 EventBus(三类订阅);§2.4 EventStore | -| §3 | Agent Runtime | §3.1 AgentRegistry(AgentDefinition:runtime light/heavy/external、grants、subscriptions);§3.2 AgentRuntime(Spawn/Send/... + ExpectSpec);§3.3 AgentEndpoint(仅 OnEvent+Describe);§3.4 结果回传协议(规范性) | -| §4 | Context Layer | §4.1 ContextProvider 四动词+可选 Search/Watch/Delete;§4.2 ContextRegistry(NamespaceMount + Resolve + Delete 卸载) | -| §5 | Tool Layer | §5.1 ToolProvider(List/Get/Call;ToolSpec 含 effect/scope/confirm/skill);§5.2 ToolRegistry(ToolMount + 虚拟化 prefix/rename/hide/describeOverride) | -| §6 | Auth | §6.1 Authorizer(Check/CheckBatch,"只衰减"公式);§6.2 PolicyStore(deny 优先);§6.3 IdentityMapper;§6.4 Token claims 与判定细则;§6.5 User Token 与 admin bootstrap(§6.5d CLI device flow) | -| §7 | Scheduler | Cron CRUD + Trigger;action = publish/agent/script | -| §8 | Task | TaskManager + 7 态状态机 + Signal + checkpoint;§8.1 动态编排为**非规范性预留**(当前不实现) | -| §9 | Model Provider | ModelProviderRegistry(渠道 CRUD)+ ModelRouter.Route(内部路由,endpoint 是网络地址非资源 URI) | -| §10 | Observability | Metrics.Query + AuditLog List/Get | -| §11 | Plugin System | §11.1 PluginRegistry(PluginManifest);§11.2 PluginLifecycle(Health/Describe);§11.3 传输绑定(HTBP 默认 / MCP);§11.4 Plugin 传输契约(规范性) | -| 附 | 追溯矩阵 | 接口方法 ↔ User Case 1-6 对应表 | - -## 横切契约一:Event 信封(§1) - -字段:`id`(平台生成全局唯一)、`source: EventSource`(`kind` = im/webhook/email/cron/agent/system,可选 `channel`/`ref`)、`type`(开放集合点分命名:`im.message`/`im.mention`/`im.action`/`im.bot_joined`/`webhook.received`/`cron.fired`/`cron.completed`/`agent.message`/`agent.result`/`agent.failed`/`task.checkpoint`/`email.received`)、`session?`(会话粘性键 `"::"`,由 Decode 生成)、`principal?`(平台补齐)、`channelUser?{channel,userId}`、`dedupeKey?`(支持重投的渠道 Decode 必填)、`payload: unknown`、`raw?`(审计用可截断)、`occurredAt`、`traceId`。 - -- **尺寸**:序列化总尺寸 ≤128 KB(对齐 Queues 上限)。超限大内容先落 Context/R2,payload 以 `{ "$ref": "" }` 引用;raw 超限强制截断并在 metadata 标 `truncated`。 -- **去重**:相同 `dedupeKey` 的重复 Publish 在时间窗内幂等返回原 `eventId`(窗长未定量,见 doc-gaps)。 -- **出站**:`type:"outbound.message"`,payload = `OutboundMessage{channel,target,content:MessageContent,replyTo?}`;`MessageContent{text?(Markdown 子集)/blocks?/actions?:ActionButton[]}`;`ActionButton{id,label,signal?}`——signal 存在则点击后平台自动转 `TaskManager.Signal`。 - -### HITL 规范化事件(§1.1) - -- `task.checkpoint`(`TaskCheckpointPayload{taskId/checkpoint/prompt/options(approve|reject|custom)[]/notify{channel,target}}`):Task 进入 `waiting_human` 时由引擎 Publish。 -- `im.action`(`ImActionPayload{actionId/signal?}`):用户点按钮时 Decode 产出。 -- 内置路由规则(system subscriber,不占用户订阅表):① `task.checkpoint` → 按 notify 构造带 actions 卡片出站;② `im.action` 且有 signal → 校验 `Check(context, task://, 'signal')` 后调 `TaskManager.Signal(taskId,{checkpoint,decision})`。 - -## 横切契约二:CallContext(§0.3) - -``` -CallContext { - principal: PrincipalRef // 最终受益人;无人类受益人的系统任务允许 agent:/service: 作 principal - roles: string[] // IdentityMapper.Resolve 产物 - agent?: AgentChainRef // { instanceId; chain: string[] } chain 为根→当前链段(实例 ID 或系统段 "cron:") - traceId: string -} -PrincipalRef = "user:alice" | "service:ci" | "agent:finance"(定义级) -``` - -要点:实例身份走 claims 的 `agent_inst`,**不作 principal**。HTTP 绑定下 CallContext 一律只经 `X-Watt-Context` 头承载,body 不重复;冲突以 header 为准(§3.3、§11.4a)。 - -## 横切契约三:Authorizer.Check 判定算法(§6.4c,逐条) - -判定公式(§6.1):`allow = P(principal) ∩ P(agent 定义 grants) ∩ P(链上每个祖先)`;权限只能沿派生链**衰减**。 - -对 `(resource, action)` 四步: -1. **求 principal 许可**:以 claims 的 `sub` + `roles` 匹配的 Policy 集判定(**deny 优先**)。 -2. **求 Agent 定义上限**:`AgentRegistry.Get(agent_def).grants` 是否覆盖;claims 无 `agent_*` 段(纯人类/服务调用,**见 §6.5b**——原文错引"见 e"已于 2026-07-02 修正)时**跳过步骤 2/3**。 -3. **沿 chain 逐段重复步骤 2**:实例 ID 段取其 `agent_def.grants`;系统段 `cron:` 规则(2026-07-02 回写明确)——`action.kind='script'` 取 `action.grants`;**`kind='agent'`/`'publish'` 不追加上限段**;job 已禁用/删除 → 该环空集 → **deny**。 -4. **全部环节允许才 allow**。实现可用 KV 缓存各段结果;Policy/grants 变更使缓存失效。 - -补充规则: -- **subject 匹配(§6.4b)**:`user:/service:`→claims.sub;`role:`→claims.roles 含之;`agent:`→claims.agent_def;`agent-instance:`→claims.agent_inst;`*`→任意。 -- **工具动作映射(§6.4d)**:`ToolSpec.scope` 存在则 action = 该 scope 字符串(如 `finance.read`);否则 action = `"invoke"`。 -- **user token(§6.5b)**:`CallContext.agent` 缺省时只执行步骤 1。 -- **AccessDecision**:`allow` + `reason?`(deny 时可读解释)+ `obligations?:('require_confirm'|'audit_verbose')[]`。 - -### CLI 设备授权(§6.5d,2026-07-02 规范性补充) - -`watt login` 走 RFC 8628 Device Authorization Grant 最小子集,三端点挂平台根: - -- `POST /oauth/device/authorize`(无认证)→ `{device_code, user_code, verification_uri, expires_in(默认 600s), interval(默认 5s)}`;user_code 8 位大写字母数字。 -- 确认:Dashboard 页面;Dashboard 未上线前允许 admin 用已有 token 调 `POST /oauth/device/approve` `{user_code, principal}` 代替。 -- `POST /oauth/token` `{grant_type:"urn:ietf:params:oauth:grant-type:device_code", device_code}` → 未确认 `{error:"authorization_pending"}`(HTTP 400,RFC 8628 §3.5 形状);已确认 → `{access_token, token_type:"Bearer", expires_in}`。 -- **错误形状豁免**:OAuth 端点整体在 WattError 契约之外,遵循 RFC 裸 OAuth 错误形状(approve 是平台管理动作,**仍走 WattError**——边界见 [../memory/decisions/auth-implementation.md](../memory/decisions/auth-implementation.md))。 -- 非交互环境(CI/loop)不走 device flow:直接以 `WATT_TOKEN` 环境变量提供 token(本地用平台私钥离线签发,见 `scripts/sign-admin-token.mjs`)。 - -## 横切契约四:WattError(§0.2) - -7 码:`not_found`/`permission_denied`/`invalid_argument`/`conflict`/`unavailable`/`rate_limited`/`internal`。字段 `code`/`message`(面向 LLM/人)/`retryable:boolean`。 - -HTTP 映射(规范性):not_found→404、permission_denied→403、invalid_argument→400、conflict→409、rate_limited→429、unavailable→503、internal→500。 - -规范性补充(2026-07-02 Phase 0 关门回写 §0.2): -- **未认证 401 → code `permission_denied`**(裸 WattError body)。 -- **未实现 501 → code `unavailable`**(裸 WattError body)。 -- 7 码不扩容,401/501 复用现有码。决策见 [../memory/decisions/bare-watterror-body.md](../memory/decisions/bare-watterror-body.md)。 - -**错误 body 形状(§11.3)**:HTTP 错误响应 body 就是**裸 WattError 对象**(`{code,message,retryable}`),**没有 `{error:...}` 信封**。 - -重试约束:`retryable=true` 仅允许出现在 `rate_limited`/`unavailable`/`internal`;平台对 429 与 5xx 且 retryable=true 按指数退避重试。 - -## §3.4 结果回传协议:六条路由规则 - -两个事件 payload: -- `agent.result`:`AgentResultPayload{correlationId/instanceId/output(≤1 MiB,更大放 Context 用 artifacts 引)/artifacts?:URI[]}`。 -- `agent.failed`:`AgentFailedPayload{correlationId/instanceId/reason('error'|'timeout'|'terminated'|'rejected'|'invalid_output')/error?:WattError}`。 - -correlationId:由 `Send`/`Spawn` 的 expect 生成或透传;缺省平台生成并回传;字符集 `[A-Za-z0-9_-]`、长度 ≤80,违规 → `invalid_argument`。 - -六条规则: -1. **定向回送**:带 correlationId 的 result/failed 不进通用订阅匹配,直投等待方(Send 调用者的 OnEvent / Task 步骤的 waitForEvent)。 -2. **派生自动回送**:`Spawn(expect)` 的子实例结果自动回送父实例。 -3. **超时代发**:`expect.timeoutMs` 到期,平台代发 `agent.failed(reason='timeout')`(幂等:真实结果后到则丢弃记审计);平台不隐式重试。 -4. **终止即失败**:等待中实例被 Terminate(含级联),未完成 correlation 代发 `agent.failed(reason='terminated')`。 -5. **等待方先消失**:到达的 result/failed 丢弃记审计(EventStore 照常留痕)。 -6. **结果去重**:同一 correlationId 首个结果生效,其后丢弃记审计。 - -`ExpectSpec`(§3.2):`correlationId?/timeoutMs?/schema?`——schema 为 JSON Schema 约束 output 形状,回送前校验,不符退回子实例重试最多 N 次(N 实现声明),仍失败 → `agent.failed(reason='invalid_output')`。 - -**Workflows 适配(规范映射)**:Workflows waitForEvent 事件 type 仅 `[A-Za-z0-9_-]`、≤100 字符、禁 `.`。`agent.result`/`agent.failed` 进入 Workflows **归并为同一 type** `agent-result-`,payload 带 `status:'result'|'failed'`;人类确认同理 `task-signal-`。waitForEvent 默认超时 24h、上限 365 天——Task 引擎在 waiting_human/waiting_event 检查点必须显式设超时并捕获转 checkpoint 超时语义。净化只发生在 Workflows 适配层,平台层保持点分。 - -## HTBP 树与四动词签名 - -树三分支(§11.3a):`/htbp/tools//...`、`/htbp/context//...`、`/htbp/platform/`(agent/task/scheduler/event/...)。 - -- 元端点:`GET .../~help`(Help DSL,方法名即 cmd)、`GET .../~skill`(指南);根 `GET /htbp/~help` 渐进发现整棵树。Plugin 侧另有 `GET {base}/~describe`(必须)与 healthPath(必须,§11.4b)。 -- 方法调用:`POST <节点路径>`,body `{"tool":"","arguments":{...}}`;`opts` 作整体对象传递**不平铺**(§11.4a)。 -- 认证:`Authorization: Bearer `;List/~help 按权限裁剪。 -- 错误:HTTP 状态码 + body 为**裸 WattError**(无信封,§11.3)。 -- MCP 绑定(§11.3b):`watt-platform` MCP server(Streamable HTTP `/mcp`),方法名 `_`(如 `context_list`、`agent_spawn`);Context 条目同时以 MCP resource 暴露(对应 Watch);OAuth 2.1。 - -**Context 四动词(§4.1 ContextProvider)**: -- `List(path, opts?): Page` — namespace 内相对前缀,浅层+分页。 -- `Get(path): ContextEntry` — 含内容。 -- `Update(path, patch: ContextPatch): ContextEntryMeta` — 部分更新;不存在 → not_found。 -- `Write(path, entry: ContextEntryInput): ContextEntryMeta` — 创建或整体替换。 -- 可选(capability 声明于 Describe):`Search?/Watch?/Delete?`。 -- 乐观并发经 `ifVersion`,不匹配 → conflict。 - -**Tool 三动词(§5.1 ToolProvider)**:`List(opts): Page`(可见性裁剪由 Tool Gateway 做,Provider 不感知调用者)、`Get(toolName): ToolSpec`(~help 数据源)、`Call(toolName, args): ToolResult{ok/content/error?}`。`ToolMeta{name(虚拟名)/summary/effect:'read'|'write'|'destructive'}`;`ToolSpec` 另含 `inputSchema/outputSchema?/scope?/confirm?/skill?`。 - -## 24 个接口面清单 - -| 接口 | 关键方法 | 所属 | -|---|---|---| -| ChannelAdapter | Verify(RawInbound)→bool;Decode(RawInbound)→Partial[];Encode(OutboundMessage)→RawOutbound;Send(RawOutbound)→SendReceipt | §2.1(Plugin) | -| ChannelRegistry | List/Get/Write/Update → ChannelConfig | §2.2 | -| EventBus | Publish(Omit)→{eventId};Subscribe→{subscriptionId};Unsubscribe;ListSubscriptions | §2.3 | -| EventStore | List(filter type/channel/session/时间)→Page;Get(eventId) | §2.4 | -| AgentRegistry | List/Get(name)/Write/Update → AgentDefinition | §3.1 | -| AgentRuntime | Spawn(SpawnRequest)→{instance,correlationId?};Send(id,event,expect?)→{accepted,correlationId?};Status;Interrupt(非 MVP);Terminate(id,{cascade?});ListInstances(opts&{tree?}) | §3.2 | -| AgentEndpoint | OnEvent(event,ctx)→{accepted};Describe()→{name,harness,capabilities,healthy} | §3.3(Agent 侧最小义务) | -| ContextProvider | List/Get/Update/Write + 可选 Search/Watch/Delete | §4.1(Plugin) | -| ContextRegistry | List/Get(namespace)/Write(mount)/Update/Delete(卸载)/Resolve(uri)→{provider,path} | §4.2 | -| ToolProvider | List→Page;Get→ToolSpec;Call→ToolResult | §5.1(Plugin) | -| ToolRegistry | List/Get(mountPath)/Write/Update → ToolMount(含 virtualize) | §5.2 | -| Authorizer | Check(AccessRequest)→AccessDecision;CheckBatch(List 裁剪用) | §6.1 | -| PolicyStore | List/Get/Write/Update → Policy | §6.2 | -| IdentityMapper | Resolve(channel,channelUserId)→{principal,roles};ResolvePrincipal(principal)→{roles} | §6.3 | -| Scheduler | List/Get/Write/Update/Trigger(jobId)→{eventId}/Delete → CronJob | §7 | -| TaskManager | List/Get→TaskDetail;ListDefinitions;Write({definition,input?,taskId?})→TaskInfo;Update(taskId,{note?});Cancel;Signal(taskId,{checkpoint,decision,payload?}) | §8 | -| ModelProviderRegistry | List/Get/Write/Update → ModelProvider | §9 | -| ModelRouter | Route({model,agent?})→{provider,model,endpoint}(内部接口) | §9 | -| Metrics | Query(MetricQuery)→MetricSeries[] | §10 | -| AuditLog | List(filter principal/agent/resource/decision)→Page;Get | §10 | -| PluginRegistry | List/Get/Write(manifest)→PluginRegistration/Update → PluginManifest | §11.1 | -| PluginLifecycle | Health()→{healthy,detail?};Describe()→{kind,interfaceVersion,capabilities,listDefaults?} | §11.2 | - -注记:Registry 四动词(§0.4)统一适用于 AgentRegistry / ToolRegistry / ChannelRegistry / PolicyStore / Scheduler / ModelProviderRegistry / PluginRegistry / ContextRegistry(后者另有 Delete/Resolve)。 - -已知契约缺口(ExpectSpec 未定量、dedupeKey 时间窗等)见 [../memory/doc-gaps.md](../memory/doc-gaps.md)。§6.4c 交叉引用错位、cron grants 矛盾、501 错误码三项已于 2026-07-02 回写 Proto 闭环。 diff --git a/llmdoc/runtime/agents-and-tools.mdx b/llmdoc/runtime/agents-and-tools.mdx new file mode 100644 index 0000000..4a7ab6d --- /dev/null +++ b/llmdoc/runtime/agents-and-tools.mdx @@ -0,0 +1,61 @@ +--- +description: Agent definition/instance 生命周期、工具注入与授权、correlation 宿主以及模型 harness 的运行时不变量。 +kind: architecture +relations: + requires: + - contracts/events-and-results.mdx + - contracts/auth-and-errors.mdx + - contracts/htbp-and-platform-apis.mdx + related: + - runtime/tasks-and-scheduler.mdx +code: + paths: + - packages/core/src/agent/** + - packages/core/src/tools/** + - packages/gateway/src/agent/** + - packages/gateway/src/tools/** + - packages/gateway/src/http/tools-proxy.ts + - packages/gateway/migrations-providers/0001_tool_registry.sql + - packages/gateway/migrations-providers/0002_agent_registry.sql + - packages/gateway/migrations-providers/0003_model_providers.sql +--- + +# Agent 与工具运行时 + +## Definition、Instance 与宿主 + +AgentRegistry 保存可变的 AgentDefinition;每个 light instance 是 Agents SDK 的一个 Durable Object。definition 描述 harness/model、grants、subscriptions、toolScopes 与 systemPrompt,instance state 保存 Spawn 时的运行快照。修改 definition 不会热改已经运行的实例。 + +Spawn 的 instanceKey 是幂等键:相同 definition 与 key 返回同一实例。terminated 实例再次 Spawn 会复活、重置运行态并按当前 definition 重新快照;Send 绝不复活或隐式创建实例。对外 Send 在调用 `idFromName` 前必须查实例索引,因为 Durable Objects 会为任意名字生成 stub,直接调用会制造不在索引中的“幽灵实例”。 + +definition Write 是整体 upsert,不是 merge。任何 setup/e2e/运维脚本重写 definition 前都必须读取并保留线上 grants、toolScopes 与 systemPrompt;否则下一次 re-Spawn 会把覆盖后的定义重新快照进实例。 + +AgentCorrelation 使用独立 Durable Object 管理 expect、超时与首结果胜出,具体交付状态见 [Event 与结果回传](../contracts/events-and-results.mdx)。AgentInstance 只处理事件与 harness,不自行实现一套等待协议。 + +## Harness 与模型 + +内置 harness 包括 echo、LLM、lurker 与 manage 类能力;heavy/external harness 通过 AgentEndpoint/Plugin 契约接入。LLM 调用由供应商中立的 Vercel AI SDK 完成,Workers 环境必须显式传入 apiKey、baseURL 与 fetch,不能依赖 `process.env`。 + +无工具时模型调用是单步 `generateText`;带工具时由 SDK 的 tools 与 step-count stop condition 承担多步循环,平台不手写 agentic loop。多步计量必须使用 SDK 的全步骤累计 usage,不能只取最后一步。模型网络错误不由平台隐式重试;ExpectSpec.schema 的违规反馈重试由 llm harness 单独控制。 + +ModelProviderRegistry 保存 provider 配置与默认项,harness 经 resolveDefault/secretRef 解析调用凭据。provider 是配置数据,不是 Plugin。换 provider 应局限在 provider factory/route,不能把供应商 API 细节扩散到 AgentInstance。 + +## 工具发现与三闸门 + +AgentDefinition.toolScopes 是可见工具树前缀。运行时按 scopes 注入 `htbp_help`、`htbp_skill`、`htbp_call`: + +1. 路径必须落在 toolScopes 前缀内;含 `://` 的历史 platform scope 不生成 HTBP 工具。 +2. principal 的 Policy 必须允许目标 resource/action。 +3. AgentDefinition.grants 必须覆盖同一 resource/action,祖先链上限也必须允许。 + +help/skill 使用 read,call 使用 ToolSpec.scope 或默认 invoke。任一闸门失败都返回可解释错误给模型,不得用管理员 token 旁路。修工具授权问题时一次核对 toolScopes、Policy、definition grants,并用真实 agent 主体链路验收;源码 seed 只能证明初始默认值,不能代表持久化运行态。 + +gateway 的 tool-invoker 是 HTBP tools 代理与 Agent 注入工具的共同执行核心,负责 mount 解析、provider 形状归一和授权。新增工具调用路径必须复用它,避免出现一条受 PEP 管理、另一条直连 provider 的双轨语义。 + +## 运行时边界 + +- Terminate 可以级联子实例,并为未完成 correlation 发送 terminated 失败;terminated 记录按保留期清理。 +- definition 与 instance 快照允许暂时不一致;排障时必须分别查询,两者不能互相代替。 +- AgentRuntime.Interrupt 是规范预留,当前不能作为已实现的可靠控制面。 +- schema validator 只实现明确子集;需要更完整 JSON Schema 时应扩展 core validator 与契约测试,而不是在调用点临时宽容解析。 +- 所有工具调用与 Agent 派生必须携带原 principal 和完整 chain,权限只能衰减。 diff --git a/llmdoc/runtime/tasks-and-scheduler.mdx b/llmdoc/runtime/tasks-and-scheduler.mdx new file mode 100644 index 0000000..0b723d8 --- /dev/null +++ b/llmdoc/runtime/tasks-and-scheduler.mdx @@ -0,0 +1,51 @@ +--- +description: Cloudflare Workflows Task、TaskStore 权威态、checkpoint 事件映射以及 Scheduler script isolate 的现行设计。 +kind: architecture +relations: + requires: + - contracts/events-and-results.mdx + - contracts/auth-and-errors.mdx + related: + - runtime/agents-and-tools.mdx + - operations/topology-and-provisioning.mdx +code: + paths: + - Docs/Proto.md + - packages/core/src/task/** + - packages/gateway/src/task/** + - packages/gateway/src/scheduler/** + - packages/gateway/migrations-events/0002_task_store.sql +--- + +# Task 与 Scheduler 运行时 + +## Task 与 Workflow + +TaskManager 把长任务交给 Cloudflare Workflows,使用 step.do、sleep 和 waitForEvent 获得持久化重试与挂起,不自造工作流引擎。TaskDefinition 是部署代码中的模板声明;Proto 的动态 orchestration 是非规范性预留,当前不应作为可用能力。 + +taskId 同时作为 Workflows instanceId。Write 生成 taskId 后直接 `create({id: taskId})`,Signal/Cancel 用同一个 id 取 Workflow,无额外映射表。TaskStore 保存可查询的七态投影、checkpoint 与输出,是 List/Get 以及本地测试的权威状态;Workflow runtime 的 `instance.status()` 只作为执行态补充,本地 hibernate 时它仍可能报告 running。 + +平台事件名保持点分;Workflows 适配层把它们净化为允许的字符集。`agent.result` 与 `agent.failed` 都映射到 `agent-result-`,payload 用 status 区分;human signal 映射到 `task-signal-`。净化不得泄漏回平台事件类型。 + +waitForEvent 必须显式设置超时并捕获超时异常。当前模板基线是 human checkpoint 十分钟、agent result 五分钟,模板可按业务覆盖;超时转换为平台失败/checkpoint 语义并留痕,不能让 Workflow 无解释地 failed。 + +## Scheduler + +SchedulerHub 是 Agents SDK Durable Object,持久化 cron 定义并用 schedule/alarm 触发。CronJob action 有 publish、agent、script 三种:触发先记录 `cron.fired`,执行结束都记录 `cron.completed`,包括 publish action,以便统一观测。 + +enabled 控制自动到点触发;手工 Trigger 仍允许执行 disabled job,作为补跑与调试面。删除或禁用的 job 在授权链里不能继续提供 cron grants。 + +## ScriptRunner 与隔离 + +script action 通过可注入 ScriptRunner 执行:生产使用 Dynamic Worker Loader 创建一次性 isolate,测试使用 fake runner 但复用同一授权和能力 binding。Loader 不存在时必须明确失败,不能在生产静默退化为同进程执行。 + +Loader 的 env 走 structured clone,闭包和 RpcTarget 不能直接塞入 env。平台能力以 WorkerEntrypoint RPC 的 `run(watt)` 参数传入,让 RPC 边界把 RpcTarget 转换成 stub。脚本拿不到平台凭据或任意网络,只能调用 watt binding 暴露的能力。 + +当前 watt binding 包含 publish 与只读 metrics。每次调用都以 `createdBy` 为 principal、实时解析 roles,并把 `cron:` 放进 chain;Authorizer 以当前 job.action.grants 作为该链段上限。Scheduler Write 不做“grants 小于等于创建者当前权限”的静态校验,运行时每次 Check 才决定,因此创建者后来被收权时 job 会自然失效。 + +## 验证边界 + +- 本地 fake runner 能验证能力表与 Auth,不能证明真实 isolate 禁网、structured-clone 或 Loader 开通状态;部署冒烟必须覆盖真实 Loader。 +- Workflows 测试等待 TaskStore 状态,不轮询 runtime status 猜测 waiting。 +- signal 必须用触发者 principal 做 `task://` 授权;admin CLI 直调不能替代普通主体或渠道动作的验证。 +- `step.do` 与 waitForEvent 的 payload 应使用可序列化的浅层结构;递归 unknown/JSON 泛型会触发 RPC 类型深实例化问题,完整对象留在 TaskStore。 diff --git a/llmdoc/startup.md b/llmdoc/startup.md deleted file mode 100644 index 27e8bbb..0000000 --- a/llmdoc/startup.md +++ /dev/null @@ -1,15 +0,0 @@ -# 启动阅读顺序 - -## MUST(每轮按序读) - -1. [must/loop-contract.md](must/loop-contract.md) — 本轮怎么干:纪律、流程、验证标准、止损规则。 -2. [must/current-state.md](must/current-state.md) — 现在在哪:Phase 进度、凭据、工具链、空缺项。 - -## 升级提示(按需读非 MUST 文档) - -- 首次接触项目或需要 Phase/E2E 全貌 → [overview/project-overview.md](overview/project-overview.md)。 -- 本轮要实现某接口、查事件 schema/错误码/判定算法 → [reference/proto-map.md](reference/proto-map.md)。 -- 本轮涉及模块边界、Plugin、CLI 命令、部署拓扑 → [architecture/modules-and-flows.md](architecture/modules-and-flows.md)。 -- 本轮碰 Cloudflare 原语限制、外部仓库、模型调用、飞书 → [reference/external-facts.md](reference/external-facts.md)。 -- 发现 Docs 疑似矛盾/缺口 → 先查 [memory/doc-gaps.md](memory/doc-gaps.md)。 -- 追问某决策缘由 → `memory/decisions/` 对应记录。