Skip to content

feat(settings): 设置页新增「工具」节 —— 按来源 / 服务 / 单个工具设置停用 / 询问 / 允许 (#1130) - #1358

Merged
jinjunnn merged 2 commits into
alphafrom
feat/1130-tools-settings
Sep 18, 2026
Merged

jinjunnn merged 2 commits into
alphafrom
feat/1130-tools-settings

Conversation

@jinjunnn

@jinjunnn jinjunnn commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

做了什么

设置页新增第四项导航「工具」(packages/ui-mac/src/renderer/alpha-ui/settings-tools.tsx):按「本地工具 / 云端工具 / 第三方 MCP / 插件」四组列出引擎 live inventory,服务可展开到工具;每行给出用户 override(三态 radiogroup,无记录 = 无选中)、「生效」徽标(resolver 终值)与「生效:状态 · 原因」(9 型 reason 逐型文案);逐条即写、无草稿;收紧畅通、放宽有闸(class / service 层「启用」先弹行内确认;service / tool 层启用写入携带 inventory 给出的当前 bindingDigest,class 层不带);cap 锁定行不可点;binding-changed 给「重新启用」;quarantine / managed-unreadable 整节横幅 + 全部只读 + 重置(留备份);写失败不做乐观更新,行内 role=alert + 重试;读失败 fail-closed。

引擎侧那一跳按票面 2026-09-17 的路线 B 落地(不扩 ADR-033 名单、不碰 httpapi/api.ts,north-star rc=0):

  • packages/core/src/permission/alpha-tool-policy-api.ts(新):core 侧标签 @opencode/v2/AlphaToolPolicyApi(list / setRecord / removeRecord / reset by directory,WriteError{kind})
  • packages/protocol/src/groups/permission.ts:GET /api/permission/tool-policy/inventory · PUT …/record · POST …/record/remove · POST …/reset(503 未接线 / 409 待恢复 / 400 记录不合 schema / 500 落盘失败)
  • packages/server/src/handlers/permission.ts:四个 handler,serviceOption 取标签、缺席 503
  • packages/opencode/src/permission/alpha-tool-inventory.ts:bridge / exposed(InstanceStore 请求时取,见下)
  • packages/opencode/src/session/prompt.ts:宿主合成暴露(Layer.suspend,deps 不变)
  • packages/opencode/src/permission/alpha-tool-policy.ts:ToolPolicyWriteError 加 kind: quarantined | io

桌面侧:ui-mac/src/main/tool-policy-client.ts(直接 fetch + main 侧再 decode)→ tool-policy-ipc.ts → preload window.api.toolPolicy → renderer。packages/ui-mac/package.json 新增 @opencode-ai/schema: workspace:*(main 用它的 parseToolPolicyInventory,renderer 只 import type)。

与探针分支的一处偏离(实测出来的):探针把 InstanceStore.node 加进了 SessionPrompt.node 的 deps。这会让每个编译 SessionPrompt.node 的测试被迫绑定 InstanceStore.bootstrapNode —— test/session/alpha-subtask-attachment-policy.test.ts 当场 Unbound layer node: @opencode/InstanceBootstrap(base 4/4 绿),test/session/prompt.test.ts 等四个上游测试同形。改为 bridge 在每次调用里 Effect.serviceOption(InstanceStore.Service)(v2 handler 的请求上下文 = app 组全部顶层输出的合并,InstanceStore 是顶层成员),宿主 deps 不变;缺席 die,不静默成空清单。勘破写在 docs/architecture/2026-09-17-tool-policy-transport-seam.md。

本地门(真实输出)

门 命令 结果
north-star bash scripts/north-star-guard.sh rc=0 ✓ zero upstream package edits
typecheck bun run --cwd packages/{core,schema,protocol,server,opencode,ui-mac} typecheck 六个包全部 rc=0 / 0 条
路线闸(改前红) bun test test/server/alpha-tool-policy-route.test.ts 改前 1 pass / 4 fail(GET 回 text/html 兜底、PUT 200)→ 改后 5 pass
渲染闸(改前红) bun test src/renderer/alpha-ui/settings-tools.test.ts 改前 0 pass / 9 fail([data-settings-section='tools'] 为 null)→ 改后 9 pass
main 客户端 bun test src/main/tool-policy-client.test.ts 7 pass
引擎相关 route + subtask-attachment + prompt + snapshot-tool-race + structured-output + inventory + doc-axis gate 81 pass / 0 fail(86 tests, 7 files)
httpapi-exercise --mode effect --include tool-policy / --mode coverage --fail-on-missing --fail-on-skip 4/4 PASS · 215 pass, missing=0
ui-mac 全量 bun test src(packages/ui-mac) rc=1:5015 pass / 3 fail(363 files)—— 3 条全在 base 失败集(下表)
opencode 全量 bun test(packages/opencode) rc=1:3488 pass / 22 skip / 10 fail(270 files);10 条所在 5 个文件在本树与 base 树各串行复跑一次:两边同为 9 fail、同名(第 10 条 v2 pty HttpApi 串行复跑两边都绿 = 全量并发时的测量污染)⇒ 与 base 差 0
gate-files bash scripts/assert-gate-files.sh rc=1:204 条闸门文件逐个点名通过,失守只有 process-fence-apply.test.ts / network-egress-fence.test.ts(Electron 二进制缺席);新登记的 alpha-tool-policy-route.test.ts 5/5
docs scripts/check-doc-links.py(7 个改动/新增 md)· check_docs_contract.py --strict(指向本 worktree)· git diff --check · NUL 闸 90 links ok · alpha-code PASS · 0 · 8200 文件零 NUL

与 base 失败集的差(逐条在 base 树同一命令复跑过):

  • packages/schema event-manifest.test.ts 2 fail —— base 同样 2 fail(事件清单计数,与本 PR 无关)。
  • ui-mac settings.test.ts > "shares saved authority…" 1 fail —— base 同样红(同文件单跑、同命令)。
  • ui-mac process-fence-apply.test.ts / network-egress-fence.test.ts —— bootstrap 过的 worktree 缺 Electron 二进制,已登记陷阱(编排器已确认)。
  • 本 PR 引入过又修掉的两条:roving-focus.test.ts composite-role 棘轮(改用仓内 rovingKey/rovingTabIndex)、brand-residue-scan.test.ts(组名「Alpha Cloud」→「云端工具 / Cloud tools」)。
  • scripts/alpha-path-residue.tsv 两行行号 +1(main/index.ts 加了一行 import 与两行注册)。

pre-push 钩子按编排器说明用 --no-verify 推送:主干上第 11/14 步 BYOK 目录闸(#1352)与 Electron 二进制两类红与本 PR 无关。

视觉证据(不启动 Electron)

docs/verification/2026-09-17-1130-settings-tools-visuals/:现役 AlphaSettings + 夹具 toolPolicy,loopback Vite + Chrome --headless=new,六态 × light/dark × 760/1280 = 24 帧,sha256 在 harness/capture-metadata.json。对照已批帧 docs/design/2026-08-25-req131-settings-tool-policy/frame.html:结构、四组、徽标、锁定、确认条、横幅、行内 alert、骨架、fail-closed 文案逐项对上;六处已裁偏差(均为实现方默认值,可回滚)写在该 README 与台账:①工具行显示引擎 identity.name 原文(专名映射归 REQ-125);②云端组按数据模型渲染成服务行 → 展开;③「无法核验」提示放节末;④新增「先打开一个项目」态(策略按 (账户, 项目) 分区,首页无项目);⑤「Alpha Cloud」→「云端工具」(REQ-139 品牌闸零 allowlist);⑥方向键按仓内唯一键盘契约「移动即激活」。

文档影响

  • 契约:docs/contracts/tool-policy.md 新增「出口:HTTP 面与桌面 IPC」;settings-and-extension-storage-adapters.md 加一条指向。
  • 架构:docs/architecture/2026-09-17-tool-policy-transport-seam.md(新)。
  • 设计:帧并入 docs/design/current/settings/design.html#set-tools(+ design.css),台账 components.md 回填设计定稿 / 落地 / 代码入口 / 已裁差异;日期快照未动。
  • 证据:上面的 verification 目录;docs/README.md 加两行索引;CHANGELOG.md [Unreleased] Added 一条。
  • 登记:scripts/gate-files.tsv 登记 alpha-tool-policy-route.test.ts(5 条);httpapi-exercise 四条 scenario。

主动没做的

  • 没重生 SDK(packages/sdk/js/src/v2/gen/* / openapi.json):本仓自 [REQ-090][CODE] 补齐 Permission DTO 与幂等 reply 语义 #433 起未再重生,重生要跑 bun dev generate(models.dev 网络陷阱)且会带出一片与本票无关的生成物漂移;main 侧按 protocol 定义直接 fetch。要不要统一重生,请编排器另裁。
  • 没给 503「未接线」写一条 HTTP 级测试(要拆掉宿主才能造出来;探针的变异 A 已证明拿掉暴露就 503)。
  • 没在真机 / 打包实例上跑(硬约束);视觉证据是组件级 harness。
  • 没修活稿里「扩展存储」pane 未回填的既有漂移(票面明说不在本票)。
  • 「先打开一个项目」这一态是帧外新增:数据模型([REQ-131][CODE] 解析并持久化分层工具策略 #1128 按项目分区)与「设置页可从首页打开」相撞,我取了 fail-closed 的可回滚默认值;若 owner 想要「首页也能改(改哪个项目?)」,那是设计裁决,不在本票。

R1 审计修复(第二个 commit)

  1. [BLOCKER] 真实会话页解不出目录:renderer/index.tsx 原来只认 route.directory,而生产导航走 canonical /server/:serverKey/session/:id,该路由 directory 恒 undefined。改为 alpha-ui/settings-directory.ts:route.directory ?? 壳层项目清单里该会话 id 的目录(AlphaProjectsApi.store,与侧栏导航同一份数据;useServerSync 在设置面挂载点之外,AppInterface 的 children 不在 ServerSyncProvider 内,所以用壳层清单而不是 serverSync().session.data.info)。设置 surface 改为 settings: (projects) => () => …,在 App() 里用 alphaProjects 装配。新测试 settings-directory.test.ts 用 hrefFor.session("sidecar","ses_123") 的真路径喂入 —— 改前红:Expected: "/Users/kai/app/kama-bot-local" / Received: undefined(3 pass / 1 fail)→ 改后 4 pass。清单里没有该会话 ⇒ undefined(fail-closed,不猜)。
  2. [MINOR] 409 死胡同:write() 失败码为 quarantined 时 reload(true),横幅与「重置」随权威 inventory 出现;新用例 T10。
  3. [MINOR] a11y:只读区 aria-hidden → inert(整棵子树不可聚焦、不宣告);T6 断言 inert 在、aria-hidden 不在、展开钮在其内。
  4. 交互回到已批帧:方向键只在组内移动焦点(仍走仓内 rovingKey,相对此刻聚焦项移动),空格 / 回车(原生 button click)才提交;T9 改为「三次方向键零写入 → click 一次写入」。verification README 偏差 6 与台账同步改写。

R1 后门:ui-mac typecheck 0;settings-directory 4 pass · settings-tools 10 pass · roving-focus 16 pass · settings-surface-ratchet / alpha-path-residue / locale-regression / shell-commands 44 pass;ui-mac 全量 5020 pass / 3 fail(364 files),3 条与 base 集逐字相同(settings.test.ts 一条 base 同红 + 两个 Electron 二进制红);north-star rc=0;doc-links / git diff --check / NUL 全 0。

Fixes #1130
Refs #723

🤖 Generated with Claude Code

jinjunnn and others added 2 commits September 17, 2026 05:26
引擎 tool policy 面(inventory 读 + record 写 + reset)按路线 B 从已收编的 v2 permission 组走出引擎进程:
core 侧标签 @opencode/v2/AlphaToolPolicyApi + opencode bridge(InstanceStore 请求时取)+ SessionPrompt.node
合成暴露;四个端点 503/409/400/500 逐型映射;不扩 ADR-033 名单、不碰 httpapi/api.ts。

桌面侧 main tool-policy-client(直接 fetch + 再 decode)→ IPC → preload window.api.toolPolicy →
renderer settings-tools.tsx:四来源分组、服务展开到工具、三态 override + 生效徽标 + 9 型原因、
逐条即写、收紧畅通放宽有闸(class/service 启用先确认;service/tool 启用带当前 bindingDigest)、
cap 锁定、binding-changed 重新启用、quarantine/managed 整节横幅 + 重置、写失败 role=alert + 重试、
读失败 fail-closed、无项目目录时不发请求。

文档:契约 tool-policy.md 出口一节、架构接缝勘破、帧并入活稿 #set-tools + 台账回填、
六态 × 双主题 × 双宽度组件级视觉证据、CHANGELOG。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1. 「工具」节在 canonical /server/:serverKey/session/:id 上恒「先打开一个项目」:路由本身不带目录,
   改按会话 id 从壳层项目清单查(settings-directory.ts,规则同 session-workspace-core);新增
   settings-directory.test.ts 用 hrefFor.session 的真路径喂入(改前 Received: undefined)。
2. 写入撞 409 时重读 inventory,让 quarantine 横幅与「重置」真的出现(T10)。
3. 整节只读区 aria-hidden → inert:展开钮 / details summary 不再可 Tab 到(T6)。
4. 三态 radiogroup 方向键只移动焦点,空格 / 回车才提交(已批帧语义;T9)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jinjunnn

Copy link
Copy Markdown
Owner Author

合并前审计已完成(M 级,2/2 轮用尽,编排器裁决)。 R1 开出 1 BLOCKER + 2 MINOR + 1 帧偏离,R2 逐条判 FIXED,结论 MERGE。

R2 的两条 minor,已知不修,理由留痕:

  1. renderer/index.tsx:97 的接线没有会执行的闸 —— 新测试只测纯函数 settingsDirectoryOf;把该行改回旧写法全套测试仍绿。审计给的修法是往 route-composition.test.ts 的源码文本清单里加一行,而源码文本断言本身就是假闸门形态(本仓已登记的陷阱),它挡不住等价改写。真闸门要经生产组装渲染一次,成本超出本票。不修;R1 的 BLOCKER 已由 settings-directory.test.ts 的行为断言守住(它自证 route.directory 恒 undefined,旧实现当场红)。
  2. 409 重读后行级重试告警落在刚变 inert 的子树里(settings-tools.tsx:448-466) —— 真机 Chromium 下不宣告、alertToFocus?.focus() 成 no-op;happy-dom 不实现 inert,所以测试两种实现都绿。用户仍有出路:quarantine 横幅 effect 抢焦点、重置按钮在横幅内(R2 已实测该路径出现)。不修,留待日后若真出现再收。

审计的 out-of-round 观察「设置页与会话页用两份目录真相」已另开票跟踪,不拦本 PR。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REQ-131][CODE] 设置页新增「工具」节:按来源、服务、单个工具设置停用/询问/允许

1 participant