Skip to content

feat(#1431): 让系统提示里的「能搜网」与模型手里真有的工具一致 - #1441

Merged
jinjunnn merged 2 commits into
alphafrom
feat/1431-websearch-prompt-tool-parity
Sep 24, 2026
Merged

jinjunnn merged 2 commits into
alphafrom
feat/1431-websearch-prompt-tool-parity

Conversation

@jinjunnn

@jinjunnn jinjunnn commented Sep 24, 2026

Copy link
Copy Markdown
Owner

模型开场时被告知「你有搜网这个能力」,而它手里到底有没有这个工具是另一套逻辑决定的 —— 两边今天不是同一个判据。这个 PR 把提示那一边改成读真闸的判决,并新增一道四格闸门守住它。

Fixes #1431
Refs #1414

改了什么

#1414 勘破(docs/architecture/2026-09-23-web-tools-account-routing-recon.md §6 第 6 行)测出全仓有四处在判「能不能搜网」,其中 buildAlphaCapabilities 把结论直接写进系统提示,而它读的是 ALPHA_WEBSEARCH_DISABLE + OPENCODE_ENABLE_EXA —— 与真闸(applyWebSearchDenies 的 deny 判决 + 云 MCP 定义的形状)不同源

  • packages/ui-mac/src/main/alpha-identity.ts:buildAlphaCapabilities 的入参从「env 派生的三个布尔」换成「两条腿各自的工具表在场性」(localWebSearch / cloudWebSearch / cloudDispatch)。它不再自己判,只把在场性折成提示事实。该模块必须保持零 import(packages/ext/src/context-injection.ts 的登记簿直接 import 它;拖进 main 世界违反 ADR-006),所以判据只能住在调用点。
  • packages/ui-mac/src/main/alpha-config-injection.ts:调用点改成消费真闸自己的判决 —— 拿一个空壳 config 让 applyWebSearchDenies同一组入参判一次再读它的判决,不再手抄它的条件。云腿的在场性读新增的 cloudMcpEnabled(与写 config.mcp 那个三分支同一组输入)。顺带把同一函数里 26 行外重算一遍 Boolean(process.env.ALPHA_WEBSEARCH_DISABLE) 的那处换成已有的 killSwitch 常量。
  • packages/ui-mac/src/main/websearch-prompt-tool-parity.test.ts(新增,10 条):基线 §三 S4 / ADR-046 那条不变量的执行者,并登记进 scripts/gate-files.tsv(精确条数 10)。

行为等价:这是一次同源化重构

caps.websearch 的取值与改动前逐格相同,可代数化简验证:

改后 = (!kill && !本地deny && keyless) || (!kill && platformPays)
改前 = !kill && (keyless || platformPays)

#1411 之前「本地 deny」== platformPays,化简后两式逐字相同;#1411 之后它恒假,化简仍是 !kill && (keyless || platformPays)两种 base 上都不改任何账户态下的用户可见行为 —— packages/ext 的 identity 变体集合仍是 4 个(context-injection-inventory 快照零改动),config-injection-throat.test.ts 的 8 组 env 双向锁逐字不变。本票交付的是「结构上不可能再分叉」+ 一道能看见分叉的闸。

四格闸门是怎么判的

判官看的是生产自己写出来的两样东西,来自不同代码路径 —— 所以「它们相等」不是自指等价链:

一边 另一边
injectAlphaConfig 写到盘上的 alpha-identity.md 里有没有 web search 那一行(行本身从生产 buildAlphaIdentity差集解出,不抄措辞) 同一次注入写进 OPENCODE_CONFIG_CONTENT 的 permission deny 表(引擎 Permission.disabled 按它把工具从模型工具表里摘掉,packages/opencode/src/session/llm/request.ts:234-244)与 mcp.cloudenabled
  • ① 登出 / BYOK:云腿整条不在(没有 URL ⇒ 连 server 定义都不写),本地腿随 keyless;零 deny。
  • ② 登录(有额度):云腿 enabled:true 且未被 deny;本地腿两层 deny 都是 false(#1411 / PR #1442 的新事实),在场性 == keyless 臂 ⇒ 生产会走到的 keyless="1" 那一臂两条腿同时在模型工具表里 = ADR-046 目标态表格第二行。
  • ③ 登录(无额度):桌面侧与 ② 是同一组输入 —— 基线 §二 选定的形状就是「额度只在调用那一刻由 account 的 preauth 回答」,桌面不持有额度判据(否决 A)。所以这一格断言的是桌面对额度结构性失明:往环境里塞额度信号(ALPHA_ACCOUNT_BALANCE_FEN=0 / ALPHA_ACCOUNT_PLAN_STATUS=expired),identity 正文 / permission / mcp 必须逐字不变。诚实边界:这只拦「把额度信号塞进 env 就能改变注入」这一形态;真正的无额度现场(402 的 wire body)归 VERIFY-1,勘破 §9 第 5 条记着那一格没有真账户。
  • ④ kill-switch:两个工具 id 在顶层与 agent 两层都被钉 deny、云 server 是中和条目、提示那一行必须消失。

OPENCODE_ENABLE_EXA 不写死成某一格的常量,而是当自由变量跑两遍 —— 它的值由 main 的 applyWebSearchSovereignty 落定。把它量化掉,本闸对 #1411 前后两种 base 都成立,不是一条迎合某一刻的期望值。

#1411 / CODE-1 的分工

#1411(PR #1442)已合。它自己有一条从真 fork env → 真 RuntimeFlags → 真 webSearchEnabled → 真 Permission.disabled 的闸;本 PR 不重跑那条链,只钉住它在注入面留下的事实(代付态一个 local deny 都不产生),并要求提示那一行跟着它走。

派发时说好「按定稿的目标状态写期望值;#1411 没合就如实记为当前 base 上预期红」。它合了,所以那一格现在钉死并且真的绿 —— 而且撤掉 #1411 的变异让它当场两臂全红(下表第 4 行),所以这不是一条恒真的装饰。

变异实测(先证明这道闸测得出已知的坏)

树干净,逐次 git checkout -- 还原后复测回 10 pass。

# 变异 结果
1 把「消费真闸判决」摘掉(localWebSearch: OPENCODE_ENABLE_EXA !== "0",即退回本票要消灭的形态) 8 pass / 2 fail:④ kill-switch(keyless=1 那一臂)+ 已知的坏自检。=0 那一臂仍绿 —— 闸是精确的,不是钝的
2 模拟 CODE-1 落地(在它合入之前跑的:applyWebSearchDenies 本地腿只看 kill-switch) 本闸 10 pass / 0 fail —— parity 断言对两种 base 都成立
3 云腿也丢掉真闸判决(cloudWebSearch: platformPays) 7 pass / 3 fail:④ kill-switch 两臂 + 已知的坏自检
4 #1411 撤掉(本地腿 deny 恢复看 platformPays) 8 pass / 2 fail:② 登录(有额度) 两臂全红 —— 目标态那一格真的绑在 #1411

文件内另带两条自检:提示两行解得出、互不相同、开关翻面真的进出正文;以及一个只看 keyless flag 的假判据必须恰好kill-switch/keyless=1 那一格与工具表对不上(恒绿即本文件空对空)。

与 ADR-046 措辞的那一处差(已就地登记在闸门抬头)

ADR-046(#1432)的不变量写的是「本地 web search 此刻在不在模型工具表里」与提示那一行逐一相符。本 PR 判的是「提示那一行」⟺「那一行指得到的工具里至少有一个在场」。

#1411 之后,两种读法在生产会走到的每一格里都重合(applyWebSearchSovereignty 在非 kill-switch 分支 OPENCODE_ENABLE_EXA ??= "1" ⇒ 本地腿恒在场)。唯一还能分叉的是「代付 + 用户自己 export OPENCODE_ENABLE_EXA=0」这一臂,也就是本闸的 keyless=0 自由变量。不按字面收窄成「只看本地腿」的两条理由:①那一臂会少报 —— cloud_cloud_web_search 明明在模型手里而提示不说,等于把同一个缺陷翻到另一面;②它会让 +websearch+cloudDispatch 这个 identity 形状在 keyless=0 时不可达,打红 config 咽喉那条「生产写出的形状集合 == 登记簿变体集合」双向锁。

本地门(真实输出,worktree .worktrees/1431-code2,base origin/alpha@78e751461)

不逐条跑整条 alpha-check.sh:第 [9/14] 步会真建探针 worktree 并写共享 core.hooksPath,而此刻有多条 lane 并行(governance/local-verification-traps.md)。逐步跑同一批命令,push 时由 pre-push 钩子跑全量。

环境按陷阱文档补齐两个未追踪产物(这棵树可信的前提;少了它们 process-fence-apply / network-egress-fence 会定点恒红,而红法长得像自己改坏了):electron/{dist,path.txt} 软链主 checkout;alpha_fence.nodebun run --cwd packages/ui-mac build:fence-addon 在本树现编

[1/14] north-star                     rc=0
[2/14] frontend patch round-trip      rc=0  ✓ packages/{app,ui} == pin + SOT 补丁
[3/14] no literal NUL bytes           rc=0
[4/14] typecheck ×6 包                rc=0  contracts-consumer / ext / ui-mac / opencode / core / schema 各 0 条 error TS
[5/14] check:vendor                   rc=0
       bun test contracts-consumer    rc=0    60 pass / 0 fail
       bun test ext (地板 100)         rc=0   208 pass / 0 fail
       bun test ui-mac src (地板 3000) rc=0  5385 pass / 0 fail  (Ran 5385 tests across 387 files, 292.16s)
       bun-test-app.sh                rc=0   724 pass;5 个 alpha 判据文件 56 条逐文件精确匹配
[6/14] assert-gate-files.sh           rc=0  ✓ 227 个闸门文件全部在位;本票那一行 10 条 == 登记 10
[7/14] seed assets                    rc=0
[8/14] docs gate                      ——   本次零 Markdown 改动 ⇒ no-op(与 CI 同)
[12/14] context injection inventory   rc=0  ✓ 与快照逐字节一致(identity 仍 4 个变体)
[14/14] module size ratchet           rc=0  ✓ 三行全部 = 基线

第 [9/14](worktree bootstrap 能力)/ 10/14/ [11/14](BYOK 目录 id)/ 13/14在本机是**「未验证」第三档**(registry / GitHub API / origin/dev 取不到),按脚本设计不拦 push —— 本次运行不构成那四步的证据。

与 base fail-set 的差:同一棵 worktree、同一条命令,先在 base 78e751461 上跑一遍 = 5374 pass / 0 fail;本分支 = 5385 pass / 0 fail差 = +11 条通过、0 条失败,逐条可账:新增闸门 10 条 + alpha-identity.test.ts 多一个快照(3 → 4,补上「两条腿同时在场」这个 #1411 之后的目标态)。

模块体积棘轮:本票让 packages/ui-mac/src/main 非测试源码 +41 行(全部是说明注释)。先把两处注释收紧(+53 → +41),再按该脚本自己的指示在同一个 PR 里人手把 tree 那一行从 59813 抬到 59854 并写明理由。基线与别人的理由整份取 alpha 那一份,只重放本票这一段 —— git diff origin/alpha...HEAD -- scripts/module-size-ratchet.tsv 只多我自己那段。树下两条 file 行本票一行未动、实测均 = 基线,所以按 #1438 新加的交叉判据只抬 tree 这一行(该判据自己的闸 17 pass / 0 fail)。没有更小的写法:alpha-identity.ts 必须零 import ⇒ 判据只能住在调用点;而注释里那几行坐标(真闸在哪、引擎在哪一步摘工具、诚实边界是少报不是多报)正是下一个人判「这一行凭什么这么写」的唯一依据。

我主动没做的事

  • 没有重跑 #1411 那条链(真 fork env → 真 RuntimeFlags → 真 webSearchEnabled)。它是 CODE-1 自己的退出条件,在这里再写一遍是第二份真源;本闸只钉它在注入面留下的事实。
  • 没有修 caps.cloudDispatch。它仍然只看代付:kill-switch 下完整定义经 ARM/DEF 托管给 ext,ext 真装上兄弟工具就在,而「ext 这一 fork 装没装」注入面判不了;那一行的措辞本来就带条件(“when cloud.* tools appear in your tool list”),两种结局下都不谎报。改它要么少报要么多报,S4 也只点名 web search。已就地留注。
  • 没有镜像上游的 enableExa 代数webSearchEnabled 还认 OPENCODE_EXPERIMENTAL umbrella 与 Zen provider,main 在 fork 前判不了 ⇒ 本票的在场性只会少报(工具在而提示没说),不会多报。手抄上游那个三项 OR 正是《手写一个别人文法的替身》,不做;残留已就地登记为诚实边界。
  • 没有动 cloud-web-search.ts / server.ts(CODE-1 的边界)、没有动 ADR(CODE-3,#1432 已合)、没有跑 runtime 取证(VERIFY-1)。
  • 没有更新勘破文档 §6 第 6 行那句「判据是 !websearchDisabled && (…)」。它是一份钉了测量日期与 commit 的勘破快照(文件抬头:「勘破,不是方案」),改它等于改史料;三份勘破合并后的相对链接整理与对照仍归 #1414 的收口。

🤖 Generated with Claude Code

@jinjunnn
jinjunnn force-pushed the feat/1431-websearch-prompt-tool-parity branch 2 times, most recently from 366541e to 9dddd8d Compare September 24, 2026 04:49
jinjunnn and others added 2 commits September 24, 2026 01:00
模型开场时被告知「你有搜网这个能力」,而它手里到底有没有这个工具是另一套逻辑决定的。
`#1414` 勘破 §6 测出全仓有四处在判「能不能搜网」,其中 `buildAlphaCapabilities` 把结论
**直接写进系统提示**,而它读的是 `ALPHA_WEBSEARCH_DISABLE` + `OPENCODE_ENABLE_EXA`,
与真闸(`applyWebSearchDenies` 的 deny 判决 + 云 MCP 定义的形状)**不同源** —— 于是可以
「提示说有、工具表里其实没有」,模型要么去调一个不存在的工具,要么凭印象编答案。

- `alpha-identity.ts`:`buildAlphaCapabilities` 的入参从「env 派生的三个布尔」换成
  「两条腿各自的**工具表在场性**」。它不再自己判,只把在场性折成提示事实;本模块必须保持
  零 import(ext 的登记簿直接 import 它),所以判据只能住在调用点。
- `alpha-config-injection.ts`:调用点改成**消费真闸自己的判决** —— 拿一个空壳 config 让
  `applyWebSearchDenies` 用同一组入参判一次再读判决,不手抄它的条件。云腿在场性读新增的
  `cloudMcpEnabled`(与写 `config.mcp` 那个三分支同一组输入)。顺带把同一函数里 26 行外
  重算一遍的 `Boolean(process.env.ALPHA_WEBSEARCH_DISABLE)` 换成已有的 `killSwitch` 常量。
- 新增四格闸门 `websearch-prompt-tool-parity.test.ts`(基线 §三 S4 / ADR-046 不变量的执行者),
  登记进 `scripts/gate-files.tsv`(精确条数 10)。四格 = 登出/BYOK · 登录有额度 · 登录无额度 ·
  kill-switch;keyless flag 当自由变量跑两遍,所以本闸对 `#1411` 前后两种 base 都成立。
  代付态那一格按 ADR-046 的目标态钉死(`#1411`/PR `#1442` 已合:代付一个 local deny 都不产生
  ⇒ 两条腿同时在模型工具表里);撤掉 `#1411` 的变异实测让这一格当场两臂全红。

`caps.websearch` 的取值与改动前逐格相同(`!kill && (keyless || platformPays)`,可代数化简
验证),所以这是一次同源化重构,不改任何账户态下的用户可见行为。

Refs #1414

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`scripts/assert-module-size.sh` 量出本票让 packages/ui-mac/src/main 非测试源码 +41 行
(全部是说明注释;先把两处注释收紧过一轮,+53 → +41)。基线只能靠人手 diff 抬高,评审看得见。

起点 = 干净 origin/alpha@78e751461 实测 59813,零遗留 ⇒ 这 41 行全部是本票的。基线与别人的
理由整份取 alpha 那一份,只重放本票这一段。树下两条 file 行(ext-install-planner.ts /
ext-transaction.ts)本票一行未动、实测均 = 基线,所以按 `#1438` 新加的交叉判据只抬 tree 这一行
(该判据自己的闸 17 pass / 0 fail)。

Refs #1414

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jinjunnn
jinjunnn force-pushed the feat/1431-websearch-prompt-tool-parity branch from 9dddd8d to ed88c61 Compare September 24, 2026 05:28
@jinjunnn
jinjunnn merged commit aad2f4a into alpha Sep 24, 2026
5 of 6 checks passed
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-1414][CODE-2] 让系统提示里的「能搜网」与模型手里真有的工具一致

1 participant