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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
| ADR-031 | Accepted 2026-09-04 | 分页 clamp 可观测性(#2243 残项):**保留「夹到端点自己声明的上限」,不改 400**。实测 13 个 list handler 的信封已回传 `page.nextCursor` + `page.hasMore`(另两个 clamp 端点是 limit/offset 形态,客户端仍可推进),所以被夹短的页**可续取、不是数据丢失**;400 会把可满足的请求变成硬失败、零用户收益,并与 `repository/pagination_clamp_test.go` 钉住的 `ClampPageSize` 不变量冲突。可观测性用**文档化**补齐(`api/conventions.md` + OpenAPI `PageSize` 描述写明「超过声明上限即夹到该上限,余下部分用 nextCursor/offset 续取」),**不**新增信封字段(跨 ~16 端点的契约变更换近乎零价值)。残项:`repository/message.go GetMessagesIncrement` 的非正值分支按 `paging.go` 自己写明的「0 = no explicit limit ⇒ 把 requested 当 def 传」惯例表达,行为不变、消掉最后一处手写分支。 | Hub API | 是 |
| ADR-032 | Accepted 2026-09-04 | CI 反馈回路裁决(#2251)。**① 量化目标已达成**:`@agenthub/workbench` coverage job 从实测中位 **441s**(15 次 pre-#2300 run,自测基线而非沿用 7m31s 口径)降到 **259s**(5 次 post,**−41.3%**;把 #2300 自己 4 次 pre-merge run 并进来的 n=9 口径 −40.1%),机制在 CI 日志里验证过:vitest `tests` 桶 796.49s → 110.16s worker-seconds(−86%),达成并发度恒为 **3.86**(=job 内已无并行度可挖)。**② stretch ≤3m 不做**:单 runner 下限已是 870/3.86 + 30 = **255s**,要到 180s 得再删 ~35% 测试工作量,那是拿覆盖保障换 KPI。**③ workbench coverage 分片(`vitest --shard`)DEFERRED**:在 post-#2300 逐文件成本上重拟合 LPT+4worker 模型(N=1 标定 −2.0%),N=3/N=4 把该 job 降到 1m45s–2m26s,但 **PR 墙钟中位只从 289s → 275s(−4.8%),9 次里 4 次收益恰好为 0**——FE-only PR 已成 `frontend-required`(5/9) 与 `windows-frontend`(4/9) 的双极平局(中位差 20s);同时分片把 FE-only run 叶子数 15→19,而账号级并发上限实测 **~20**,两个重叠 FE-only PR 会 30→38(30 那一档本窗口内已饿过一次)。**触发重开**:该 job 墙钟中位 ≥ **~5m10s**(= 交叉点 ~4m09s + 60s 余量),或 ④(c) 落地之后。**④ 真正的极已换人**:(a) **`go-hub-test` 分片失衡——本 ADR 同批实施**:`internal/repository` 一个包 = hub-server 实测 418.8s race 测试成本的 **227.7s(54.4%)**,它同时是任何包粒度切分的**硬下限**;`NR % 2` 把它放在 `go list` 第 15 位(奇数)⇒ shard 2,而 shard 2 在 **14/14**(post-#2300 窗口 **18/18**)次 CI run 里都是慢的那半(中位 +119s / +2m02s,最大 +177s / 3m20s)。改成 shard 1 独占该包、shard 2 拿其余 51 个:load **310.5/108.3 → 227.7/191.1**(spread 202.3s → 36.5s),**不新增 job**;**本 PR 自己的 CI run 就是这次改动的实验,实测已回收**:`go-hub-test (1)`=**3m49s**、`(2)`=**3m28s**(spread **21s**),对照基线 shard1 2m01s–3m00s / shard2 4m41s–5m14s(中位 spread +119s)⇒ 矩阵跨度 **−62s**,落在预测的 −40…−70s 区间内;含 1 个包的那片反而成了较慢的一片(229s vs 208s),说明「测试二进制 link 成本随包数增长」这一项确实存在、量级约 20s,比 load 失衡小一个数量级,所以按 load 切是对的方向。同一 run 的 `go-edge-test` 未改:(1) 2m39s / (2) 1m48s,仍不是 `backend-required` 的极 ⇒ (b) 的「不动」判断被新数据再次支持。硬编码包路径不会静默腐烂——包被改名/移动/拆分则 shard 1 收到 0 个包,既有 0-package 守卫硬报错,`verify-ci-gates.py` 另加 3 条断言 + 2 发变异自测钉住。(b) **`go-edge-test` 保持轮转**:实测 edge 无单一支配包(最重 `internal/lifecycle` 34.57s = **24.0%**),当前 spread 29.1s load(shard1 重,与 CI「edge shard2 在 14/14 次里更快、中位 −46.5s」同向),LPT-2 只省 14.1s load,而 edge-test 从来不是 `backend-required` 的极(2m39s vs hub 4m51s)⇒ 省下的时间到不了 PR 墙钟;触发重开 = edge-test 成为 backend 极。(c) **`windows-frontend` 的 ~75s/leg 环境准备税 DEFERRED**(同样四步在 Ubuntu 上 21.5–24.0s:setup-node +28~29s、pnpm action-setup +19~22s、pnpm install +20~22s、checkout +5~6s,且 PRE→POST 还在变慢 3m54s→4m07s):只读测量量得出税额、量不出任何修法的效果,需要一次真实 CI 实验;**禁止**用砍掉 Windows leg 的 `Production build` 换那 47–50s——`desktop-linux-build` 是 `workflow_dispatch` only(43 次 run 出现 0 次),Linux `frontend-desktop` 只跑 typecheck+lint+test 不 build,那条腿是每个 PR **唯一**证明 desktop `tsc -p tsconfig.app.json && vite build` 能过的地方。(d) 两个前端杠杆**严格互补、单独都不划算**:Windows 税单独修 = **+0.0%**(`frontend-required` 变成 9/9 的极)、分片单独 = −4.8%、两个一起 = 4m49s → **3m49s(−20.5%)**、再加 `frontend-desktop` 分片重排(164s vs 138s)= **3m29s(−27.3%)**。**⑤ 测量方法学(防下次重推)**:本机 ARM64 上 `go test -short -count=1 -p 1` **不带 `-race`** 得到的成本分布与 CI 完全不同(repository 只占 9.9%、`internal/middleware` 占 31%),带上 `-race` 才复现 CI 的方向;两个模块的失衡方向都被 **28/28** 次 CI 观察独立证实 ⇒ 任何 shard 重排必须用 CI 同款 flag(`-short -race -count=1`)逐包测,命令:`go test ./... -count=1 -short -race -p 1 -parallel 1`。证据底稿 `lane-artifacts/round-73/lane-ci-REPORT.md` 与 `lane-ci-post2300-REPORT.md`。 | CI / Developer feedback loop | 是 |
| ADR-033 | Accepted 2026-09-04;landed #2315 | regenerate 的 identity 合同(#2274 B-1):**identity = task id**(服务端合同 `POST /web/agent-tasks/:id/regenerate` → `RegenerateAgentTask(userID, taskID)` 不变)。真流取证(live 栈 + 真 OIDC)证明修复前 web 把 block id 剥前缀后当 task id 发——而 block id 实为消息的 **client_msg_id**(第三个 identity 域),live 恒 404 `agent_task_not_found`、未登录 demo 对真后端发无凭据请求 401。修法三段:① **生产端补齐**——hub 两条 edge 回调路径(stream 投影 + done-final)在 agent 消息 content jsonb 里 stamp `agent_task:{"task_id":…}`,即 shared normalizer 早已解析却 0 生产者的形状(demo fixture 是唯一生产者);非 object content 不 stamp、既有 ref 不覆盖。② **transcript 写穿**——`normalizeHubMessages` 把它写成 `block.agentTaskId`(`TextTranscriptBlock` 新增可选字段)。③ **双重诚实门**——菜单条目与 regenerate effect 同时要求「端口已接线」**且** `block.agentTaskId` 存在;web 的 `onRegenerate` 随 `chatActions` fail-closed(demo/未登录端口为 undefined ⇒ 入口不渲染、零请求)。**禁止**:从 message id / client_msg_id 猜 task id、失败后静默 fallback、demo 向真 API 发请求。**历史消息无 stamp ⇒ 入口不出现**(fail-closed,不做回填迁移)。验收三层:contract(Go 单测 + live API 404/200 对照)、frontend(shared/workbench/web 单测含两个诚实门用例)、real flow(真浏览器点击:PRE 404+失败 toast / POST 200+新任务+成功 toast;demo PRE 401 请求 / POST 零请求)。证据 `lane-artifacts/round-74/b1-*-PRE/POST.json` + 截图。 | Frontend / Hub dispatch | 是 |
| ADR-034 | Accepted 2026-09-04(卫生切片 landed;语义部分 DEFERRED 待 operator) | 项目状态筛选的 vocabulary 裁决(#2274 B-6)。**事实**:Hub 的 workspace/project **没有 status 事实**——model/service/handler/openapi/migration/live DB 六处逐字一致(live 表仅 6 列);`GET /web/projects` 也不接受 status 查询;UI filter(all/running/completed/archived)**100% 前端内存过滤**;三个同名 `workspaceProjectToProjectInfo` 产 5 个输出值,与 Hub 侧对应物交集为 ∅;匹配规则还有三套互相矛盾的实现(精确表 / 精确对 / 子串 `includes('归档')`,后者与表头注释明确拒绝的做法相反)。**裁决**:不在没有 L3 事实源的前提下发明 vocabulary;收敛必须三段——L3 Hub 真 lifecycle 事实(语义由 operator 定义:running/completed/archived 各由什么派生)→ L2 唯一映射(回到 `hubDataMapping.ts`)→ L1 类型分离(`statusLabel` 与 `lifecycle: bucket|'unknown'` 拆开,`unknown` 时 chips 不渲染)。**本批已做(不需裁决)**:删 0 消费者孤儿 mapper(#2291 后唯一「消费者」是测试里一句错误注释,主机已复核 desktop 只 import 编排函数、mapper 用本地副本)+ 修正该注释 + openapi 枚举对齐 0074(run status 补 `pending_review` ×2、assignment type 补 `compete` ×3;漂移根因是 3 个 verifier 只比路由形状不比字段集/enum)+ 清 workbench vitest coverage 对已删文件的 stale exclude。**DEFERRED 触发条件**:operator 给出 lifecycle 语义定义,或 Hub 获得 status 字段;在此之前 filter 词汇保持现状但**不得**在对外文档中被表述为 Hub 事实。证据 `lane-artifacts/round-74/b6-vocabulary-survey.md`(526 行,主机复核 3/3 关键断言成立)。 | Product / Frontend / Hub | 是 |
| ADR-034 | Accepted 2026-09-05;landed [#2323](https://github.com/TokenDanceLab/AgentHub/pull/2323) | Project lifecycle 只由服务端契约定义,不从显示文案推断。**事实**:Hub 的 Project/Workspace 当前没有 lifecycle/status 字段或转换 API;`GET /web/projects` 不接受 status 筛选。**裁决**:Workbench 不提供 Running/Completed/Archived 项目筛选,也不保留基于 `ProjectInfo.status` 显示标签的本地分类链;不能为了保留旧 UI 发明后端状态机。项目搜索、选择、CRUD 与 threads/messages 保留,Task、ProjectRun、queue 和 AgentTeam 的真实状态不受影响。**后续边界**:只有产品确需 Project lifecycle 且 Hub 提供对应 schema/API/转换语义时,才引入唯一映射和类型明确的筛选;在此之前,显示标签不是 lifecycle 事实,也不要求额外的标签类型重构。该裁决取代此前“保留筛选、等待 vocabulary 裁决”的暂缓方案;原始修复与验收记录见 #2316、#2323。 | Product / Frontend / Hub | 是 |
| ADR-035 | Accepted 2026-09-04;landed #2317 | `web-v4` 键家族收敛(#2261 残项 = ADR-029 的适用范围正式扩展到 Web 自有命名空间)。**① 唯一产出点**:`webQueryKeys` 与 `hubQueryKeys`/`edgeQueryKeys` 并列住在 `queryKeys.ts`,`app/web/src` 非测试代码里的键数组字面量 **58 → 0**;**键值逐字不变**,只搬产出点,所以缓存身份与既有断言键值的测试都不动。**② 可空指针原样穿过**(`QueryKeyPointer = string|null|undefined`),**禁止**归一化成 `''`:`webHubMessagesFamily.sessionIdOf` 只判 `typeof key[2] === 'string'`,一个 `''` 会被重连补发当成真会话 id —— 那会把死键 bug 换成更糟的活 bug。**③ 失效必须打在真有生产者的键上**:据此修掉 6 类空转(联系人列表 6 处、新建群聊 1 处、登录后刷新 1 处、realtime 联系人帧 1 处、裸 `['agent-teams']` 1 处、以及一条断言了事实错误键的注释)。其中联系人列表有明确用户可见后果:该查询只有 `staleTime`、**没有 `refetchInterval`**,接受好友请求后列表会一直陈旧到窗口重新聚焦。裸 `['agent-teams']` 选择 **retarget 而非删除**——审批决定确实会改变 usageBoard 聚合的 runs,原作者意图对、键写错。**④ 无生产者的失效一律删,不留孪生键**(普查 `app/web`+`app/workbench`+`app/shared` 全量 `queryKey:` 生产点;workbench 0 处使用 react-query 键,故无隐藏生产者):Web 无通知查询 ⇒ NOTIFICATION_EVENTS 分支与事件集合整体删除;Web 只有 usageBoard ⇒ TEAM_EVENTS 6 条收敛为 1 条 family root,并删掉**只为拼死键而存在**的 teamId/teamRunId 解析;execution-targets 的 Web 孪生键删除。**⑤ 断言必须看缓存效果、不看调用**:原 `toHaveBeenCalledWith(['web-v4','execution-targets'])` 正是让 bug 活下来的护身符(键无生产者,套件却常绿);新增 `webQueryCacheTargets.test.ts` 以 `getQueryState(...).isInvalidated` 为准,**7 发变异 6 发翻红**,M6(把已删的通知分支放回去)**如实登记为不翻红**——它证明的是该分支本来就惰性,不是证明删除必要。 | Frontend | 是 |

## Archive
Expand Down
6 changes: 3 additions & 3 deletions scripts/verify/i18n-callsite-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"workbench/inspector/InspectorModeBodies.tsx": 3,
"workbench/inspector/InspectorTabChrome.tsx": 1,
"workbench/inspector/RuntimeEvidenceParts.tsx": 3,
"workbench/mockData.ts": 106,
"workbench/mockData.ts": 105,
"workbench/pages/agents/AgentEditHelpers.ts": 2,
"workbench/pages/agents/AgentInstalledParts.tsx": 9,
"workbench/pages/agents/AgentMarketHelpers.ts": 2,
Expand All @@ -54,11 +54,11 @@
"workbench/pages/docs/shared.tsx": 5,
"workbench/pages/projects/ProjectChromeViews.tsx": 1,
"workbench/pages/projects/ProjectTabViews.tsx": 10,
"workbench/pages/projects/shared.tsx": 16,
"workbench/pages/projects/shared.tsx": 10,
"workbench/pages/projects/types.ts": 23,
"workbench/pages/settings/SettingsPaneHelpers.ts": 11,
"workbench/pages/settings/SettingsPaneParts.tsx": 4,
"workbench/pages/settings/SettingsPanes.tsx": 56,
"workbench/pages/settings/SettingsPanes.tsx": 55,
"workbench/pages/settings/types.ts": 22,
"workbench/pages/tasks/TaskTableHelpers.ts": 9,
"workbench/pages/tasks/TaskTableViews.tsx": 1,
Expand Down
Loading