@danceiny/gotry 0.2.0-rc.28 → 0.2.0-rc.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -153,7 +153,7 @@ Trust is structural, not promised:
153
153
  ## Project Status and Roadmap
154
154
  Current boundary: deterministic planning and evidence-bound read capabilities are available; booking and real-user acceptance remain outside the shipped claim. See the [architecture](docs/architecture.md) and [roadmap](docs/roadmap.md).
155
155
 
156
- **v0.2.0-rc.27** on npm (`latest`). Pre-1.0: the core loop works end to end; evaluation is still at deterministic contracts and validators, with no external scores or uplift claims. External W2A events are contract-only and inert; no real sensor bridge or consumer is active yet.
156
+ **v0.2.0-rc.28** on npm (`latest`). Pre-1.0: the core loop works end to end; evaluation is still at deterministic contracts and validators, with no external scores or uplift claims. External W2A events are contract-only and inert; no real sensor bridge or consumer is active yet.
157
157
 
158
158
  **Working today**
159
159
 
@@ -209,7 +209,7 @@ Documents ship as bilingual pairs (`x.md` English + `x.zh-CN.md` 中文); diverg
209
209
 
210
210
  **Built with**: DeepSeek Harness 0.2.0-rc.2 (root-pinned) · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 · OpenFlights · TypeScript
211
211
 
212
- **Version baseline: `v0.2.0-rc.27` (npm `latest`).** Verification gates: `scripts/run-all-tests.sh`; release flow: `scripts/publish-npm.sh`.
212
+ **Version baseline: `v0.2.0-rc.28` (npm `latest`).** Verification gates: `scripts/run-all-tests.sh`; release flow: `scripts/publish-npm.sh`.
213
213
 
214
214
  <a href="https://www.star-history.com/?repos=danceiny%2Fgotry&type=date&legend=top-left">
215
215
  <picture>
package/README.zh-CN.md CHANGED
@@ -155,7 +155,7 @@ npx @danceiny/gotry "我想从深圳休整两天,预算 3000" # headless 一
155
155
  ## 状态与路线图
156
156
  当前边界:确定性规划与有证据边界的只读能力已经可用;预订与真实用户验收仍不属于当前交付声明。见[架构权威文档](docs/architecture.zh-CN.md)与[路线图](docs/roadmap.zh-CN.md)。
157
157
 
158
- npm `latest`:**v0.2.0-rc.27**。未到 1.0:核心链路已端到端可用;评测仍停留在确定性合同与校验器阶段,无外部分数、无 uplift 声明。外部 W2A 事件仍是合同层、默认 inert;尚无真实 sensor 桥或 consumer 激活。
158
+ npm `latest`:**v0.2.0-rc.28**。未到 1.0:核心链路已端到端可用;评测仍停留在确定性合同与校验器阶段,无外部分数、无 uplift 声明。外部 W2A 事件仍是合同层、默认 inert;尚无真实 sensor 桥或 consumer 激活。
159
159
 
160
160
  **今天可用**
161
161
 
@@ -211,7 +211,7 @@ AI agent:[`AGENTS.md`](AGENTS.zh-CN.md) 是绑定契约——入场先清扫
211
211
 
212
212
  **Built with**: DeepSeek Harness 0.2.0-rc.2 (root-pinned) · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 · OpenFlights · TypeScript
213
213
 
214
- **版本基线:`v0.2.0-rc.27`(npm `latest`)。** 验证闸:`scripts/run-all-tests.sh`;发布流程:`scripts/publish-npm.sh`。
214
+ **版本基线:`v0.2.0-rc.28`(npm `latest`)。** 验证闸:`scripts/run-all-tests.sh`;发布流程:`scripts/publish-npm.sh`。
215
215
 
216
216
  <a href="https://www.star-history.com/?repos=danceiny%2Fgotry&type=date&legend=top-left">
217
217
  <picture>
@@ -891,7 +891,7 @@ async function doctorChecks() {
891
891
  // 扩展
892
892
  const extManifest = join(homedir(), '.gotry', 'extension', 'manifest.json')
893
893
  const extOk = existsSync(extManifest)
894
- items.push({ label: 'GoTry Session Bridge 扩展', ok: extOk, level: extOk ? 'ok' : 'missing', detail: extOk ? `已就位(${extManifest})` : '未安装——影响面:gotry_session_search / gotry_session_login(账号会话通道)不可用;携程机票/酒店实时检索、12306 余票、Dida 实时报价都依赖此通道。其它工具(机票 FlyAI、酒店 hbcli、地图、天气等)不受影响', fix: extOk ? undefined : '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd' })
894
+ items.push({ label: 'Stai Travel Bridge 扩展', ok: extOk, level: extOk ? 'ok' : 'missing', detail: extOk ? `已就位(${extManifest})` : '未安装——影响面:gotry_session_search / gotry_session_login(账号会话通道)不可用;携程机票/酒店实时检索、12306 余票、Dida 实时报价都依赖此通道。其它工具(机票 FlyAI、酒店 hbcli、地图、天气等)不受影响', fix: extOk ? undefined : '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd' })
895
895
  // agent-reach(.venv 装在包内)
896
896
  const venvPython = join(repoRoot, '.venv/bin/python')
897
897
  const reachBin = join(repoRoot, '.venv/bin/agent-reach')
@@ -1164,7 +1164,7 @@ function classifyDoctorGap(item, opts = {}) {
1164
1164
  return installerEnabled('hbcli', env) && autoSupported ? 'auto' : 'unavailable'
1165
1165
  }
1166
1166
  if (label.startsWith('dsh-better-sidebar')) return installerEnabled('sidebar', env) && autoSupported ? 'auto' : 'unavailable'
1167
- if (label.startsWith('GoTry Session Bridge')) return 'user-action' // 浏览器商店一键装
1167
+ if (label.startsWith('Stai Travel Bridge')) return 'user-action' // 浏览器商店一键装
1168
1168
  if (label.startsWith('FlyAI')) return 'user-action' // 上游控制台申请 key
1169
1169
  if (label.startsWith('dsh-calendar')) return 'user-action' // profile cordis.patch.yml 配置
1170
1170
  if (label.startsWith('dsh-map-tools')) return 'unavailable' // 随包 vendor,缺失即重装 gotry
@@ -1443,7 +1443,7 @@ function copyExtensionDir(srcDir, dstDir) {
1443
1443
 
1444
1444
  /** 会话检索扩展(issue #21 方案 C):包内 extension/ → ~/.gotry/extension 幂等落位 + 一次性加载指引 */
1445
1445
  async function setupExtension() {
1446
- say('[gotry-setup] GoTry Session Bridge 扩展(会话检索数据面 issue #21;一次性安装,替代逐连接弹窗)')
1446
+ say('[gotry-setup] Stai Travel Bridge 扩展(会话检索数据面 issue #21;一次性安装,替代逐连接弹窗)')
1447
1447
  const srcDir = join(repoRoot, 'extension')
1448
1448
  const srcManifest = join(srcDir, 'manifest.json')
1449
1449
  if (!existsSync(srcManifest)) {
@@ -1465,7 +1465,7 @@ async function setupExtension() {
1465
1465
  return { ok: false }
1466
1466
  }
1467
1467
  say(` ✓ 已落位 ${dstDir}(v${srcVersion};manifest 带固定 key,unpacked 扩展 ID 恒为 olpgkofjhhiiiahdkkbcninhjmegghfe)`)
1468
- say(' 推荐(免下面三步):Chrome 应用商店一键安装 GoTry Session Bridge(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd')
1468
+ say(' 推荐(免下面三步):Chrome 应用商店一键安装 Stai Travel Bridge(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd')
1469
1469
  say(' 本地加载(每台浏览器一次,约 30 秒):Chrome 打开 chrome://extensions → 右上角开启「开发者模式」→「加载已解压的扩展程序」→ 选择 ~/.gotry/extension')
1470
1470
  say(' 获取/更新本地通道扩展可走 GitHub Releases:npx @danceiny/gotry setup --extension-from=github(自动下载校验落位;手动下载: github.com/Danceiny/gotry/releases 标签 ext-*)')
1471
1471
  say(' 装好即生效,零系统弹窗;扩展卡片开关=总闸(与 gotry 授权闸 sessionAccess 双重控制)')
@@ -1478,7 +1478,7 @@ async function setupExtension() {
1478
1478
  * 任何失败显式降级 bundled 包内副本——安装外部产物永远不挡 gotry 本体。
1479
1479
  */
1480
1480
  async function setupExtensionFromGithub() {
1481
- say('[gotry-setup] GoTry Session Bridge 扩展 · GitHub Releases 下载通道(dist-manifest → tar.gz → SHA256 → key 钉扎)')
1481
+ say('[gotry-setup] Stai Travel Bridge 扩展 · GitHub Releases 下载通道(dist-manifest → tar.gz → SHA256 → key 钉扎)')
1482
1482
  const destDir = join(homedir(), '.gotry', 'extension')
1483
1483
  const cliScript = join(repoRoot, 'ts', 'scripts', 'extension-distribution-cli.ts')
1484
1484
  const releaseBase = process.env.GOTRY_EXTENSION_RELEASE_BASE // 镜像/测试基址覆盖;缺省 GitHub 官方
@@ -1509,7 +1509,7 @@ async function setupExtensionFromGithub() {
1509
1509
  if (r && r.ok) {
1510
1510
  if (r.action === 'installed') {
1511
1511
  say(` ✓ 已从 GitHub Releases 落位 ${destDir}(v${r.version}${r.previousVersion ? `,旧 v${r.previousVersion}` : ''})`)
1512
- say(' 已装过旧版的浏览器:chrome://extensions → GoTry Session Bridge 卡片 → 「重新加载」一次即生效(新装跳过)。')
1512
+ say(' 已装过旧版的浏览器:chrome://extensions → Stai Travel Bridge 卡片 → 「重新加载」一次即生效(新装跳过)。')
1513
1513
  return { ok: true }
1514
1514
  }
1515
1515
  // up-to-date(check-only 下远端更新也会走到这里,只报告不落盘)
@@ -1728,7 +1728,7 @@ if (WIZARD) {
1728
1728
  }
1729
1729
  say('')
1730
1730
  say('[gotry-wizard] ────────────────────────────────')
1731
- say('[gotry-wizard] GoTry Session Bridge 是浏览器的事——在 Chrome 应用商店一键装:')
1731
+ say('[gotry-wizard] Stai Travel Bridge 是浏览器的事——在 Chrome 应用商店一键装:')
1732
1732
  say('[gotry-wizard] https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd')
1733
1733
  say('[gotry-wizard] 装好即生效;装完后我会自动检测到(下方探活等待,最长 120s)。')
1734
1734
  say('[gotry-wizard] 全过程 gotry 不动你的剪贴板、不开你的 Chrome、不弹任何面板。')
@@ -1749,7 +1749,7 @@ if (WIZARD) {
1749
1749
  }
1750
1750
 
1751
1751
  if (process.platform === 'win32') {
1752
- say('[gotry-setup] GoTry Session Bridge 扩展:推荐 Chrome 应用商店一键安装 https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd ;本地通道:手动把包内 extension/ 目录拷到 %USERPROFILE%\\.gotry\\extension,再在 chrome://extensions 开发者模式「加载已解压的扩展程序」')
1752
+ say('[gotry-setup] Stai Travel Bridge 扩展:推荐 Chrome 应用商店一键安装 https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd ;本地通道:手动把包内 extension/ 目录拷到 %USERPROFILE%\\.gotry\\extension,再在 chrome://extensions 开发者模式「加载已解压的扩展程序」')
1753
1753
  process.exit(AUTO ? 0 : 1)
1754
1754
  }
1755
1755
  if (AUTO && (process.env.CI || process.env.GOTRY_SETUP_SKIP === '1')) {
@@ -1760,7 +1760,7 @@ if (WIZARD) {
1760
1760
  const results = []
1761
1761
  // gotry 自留面只剩扩展是否就位;hbcli/agent-reach/dsh-better-sidebar 由各自宿主生态自管。
1762
1762
  if (process.env.GOTRY_SETUP_EXTENSION !== '0') results.push(await (EXTENSION_FROM === 'github' ? setupExtensionFromGithub() : setupExtension()))
1763
- else say('[gotry-setup] GoTry Session Bridge 扩展:GOTRY_SETUP_EXTENSION=0 跳过')
1763
+ else say('[gotry-setup] Stai Travel Bridge 扩展:GOTRY_SETUP_EXTENSION=0 跳过')
1764
1764
  const failed = results.filter((r) => !r.ok).length
1765
1765
  if (failed > 0) {
1766
1766
  say(`[gotry-setup] ${failed} 项未就绪——gotry 本体不受影响;可稍后重跑: npx @danceiny/gotry setup`)
@@ -29,33 +29,6 @@
29
29
  ]
30
30
  },
31
31
  "legs": [
32
- {
33
- "id": "yn0",
34
- "note": "昆明→丽江(衔接 8.4 FD582 KMG 11:25 落地后,下午转飞丽江;M-1 工作窗口外)",
35
- "date": "8.4 下午",
36
- "services": [
37
- {
38
- "id": "MU5819",
39
- "dep": "14:30",
40
- "arr": "15:35",
41
- "price_cny": 520,
42
- "price_note": "东航,1h5m 直飞"
43
- },
44
- {
45
- "id": "8L9662",
46
- "dep": "16:40",
47
- "arr": "17:45",
48
- "price_cny": 480,
49
- "price_note": "祥鹏,1h5m 直飞"
50
- }
51
- ],
52
- "evidence": [
53
- {
54
- "tag": "[demo-reconcil:已]",
55
- "note": "8.4 FD582 → 11:25 KMG → 当天转飞丽江(已挖)"
56
- }
57
- ]
58
- },
59
32
  {
60
33
  "id": "yn1",
61
34
  "note": "深圳→丽江(直飞,丽江进大理出的开口程第一段)",
@@ -153,6 +126,36 @@
153
126
  "weekday": "any"
154
127
  }
155
128
  ],
129
+ "advisory_legs": [
130
+ {
131
+ "id": "yn0",
132
+ "advisory_reason": "本段缺 buffer_min / origin_transfer_min / dest_transfer_min 三个真值,待 founder 校准(flights_2026.json 的 f3 evidence 早已写明:yn0 衔接仍写,引擎层不挂接)。替它填数等于编造真实世界的事实(ADR-10 翻译不造数),所以只记录、不挂接引擎;三个整数补齐后把整段搬回 legs 即可参与求解(issue #635)。",
133
+ "note": "昆明→丽江(衔接 8.4 FD582 KMG 11:25 落地后,下午转飞丽江;M-1 工作窗口外)",
134
+ "date": "8.4 下午",
135
+ "services": [
136
+ {
137
+ "id": "MU5819",
138
+ "dep": "14:30",
139
+ "arr": "15:35",
140
+ "price_cny": 520,
141
+ "price_note": "东航,1h5m 直飞"
142
+ },
143
+ {
144
+ "id": "8L9662",
145
+ "dep": "16:40",
146
+ "arr": "17:45",
147
+ "price_cny": 480,
148
+ "price_note": "祥鹏,1h5m 直飞"
149
+ }
150
+ ],
151
+ "evidence": [
152
+ {
153
+ "tag": "[demo-reconcil:已]",
154
+ "note": "8.4 FD582 → 11:25 KMG → 当天转飞丽江(已挖)"
155
+ }
156
+ ]
157
+ }
158
+ ],
156
159
  "stays": [
157
160
  {
158
161
  "id": "yns1",
@@ -52,7 +52,7 @@ export const CHANNELS = [
52
52
  evidenceTag: '[会话:ctrip-flight@ts]',
53
53
  tier: 'session',
54
54
  efficiencyRank: 2,
55
- setup: '一次性装 Session Bridge 扩展 + 授权卡 + (如需)官网登录',
55
+ setup: '一次性装 Stai Travel Bridge 扩展 + 授权卡 + (如需)官网登录',
56
56
  hint: 'kind=flight',
57
57
  },
58
58
  {
@@ -64,7 +64,7 @@ export const CHANNELS = [
64
64
  evidenceTag: '[会话:ctrip-hotel@ts]',
65
65
  tier: 'session',
66
66
  efficiencyRank: 2,
67
- setup: '一次性装 Session Bridge 扩展 + 授权卡 + (如需)官网登录',
67
+ setup: '一次性装 Stai Travel Bridge 扩展 + 授权卡 + (如需)官网登录',
68
68
  hint: 'kind=hotel',
69
69
  },
70
70
  {
@@ -76,7 +76,7 @@ export const CHANNELS = [
76
76
  evidenceTag: '[会话:dida-portal@ts]',
77
77
  tier: 'session',
78
78
  efficiencyRank: 2,
79
- setup: '一次性装 Session Bridge 扩展 + 授权卡 + hotel-be portal 自动登录跳板(dida 官网由人完成登录)',
79
+ setup: '一次性装 Stai Travel Bridge 扩展 + 授权卡 + hotel-be portal 自动登录跳板(dida 官网由人完成登录)',
80
80
  hint: 'kind=dida',
81
81
  },
82
82
  {
@@ -88,7 +88,7 @@ export const CHANNELS = [
88
88
  evidenceTag: '[会话:train-12306@ts]',
89
89
  tier: 'session',
90
90
  efficiencyRank: 2,
91
- setup: '一次性装 Session Bridge 扩展(公开查询面,无需登录)',
91
+ setup: '一次性装 Stai Travel Bridge 扩展(公开查询面,无需登录)',
92
92
  hint: 'kind=train',
93
93
  },
94
94
  {
@@ -124,14 +124,14 @@ export async function runDoctorChecks(opts = {}) {
124
124
  items.push(nodeOk(nodeVersion)
125
125
  ? { id: 'node', label: 'Node 运行时', status: 'ok', detail: `Node ${nodeVersion}(≥22.15)` }
126
126
  : { id: 'node', label: 'Node 运行时', status: 'missing', detail: `Node ${nodeVersion} 过旧(gotry 需 ≥22.15)`, fix: '升级 Node.js 至 22.15+(https://nodejs.org)' });
127
- // 1. GoTry Session Bridge 扩展(账号会话通道的传输层;装在哪由 bootstrap setup 管理)
127
+ // 1. Stai Travel Bridge 扩展(账号会话通道的传输层;装在哪由 bootstrap setup 管理)
128
128
  // D-24 自适应(#117):离线只能探测本地 unpacked 通道;商店版无本地文件面——
129
129
  // missing detail 明示「商店版用户可忽略本项」,不再把商店版用户误导成本地通道缺失
130
130
  const extManifest = join(home, '.gotry', 'extension', 'manifest.json');
131
131
  items.push(existsSync(extManifest)
132
- ? { id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'ok', detail: `已就位(本地通道:${extManifest})` }
132
+ ? { id: 'extension', label: 'Stai Travel Bridge 扩展', status: 'ok', detail: `已就位(本地通道:${extManifest})` }
133
133
  : {
134
- id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'missing',
134
+ id: 'extension', label: 'Stai Travel Bridge 扩展', status: 'missing',
135
135
  detail: '本地通道未落位(~/.gotry/extension/manifest.json 不存在)。影响面:gotry_session_search / gotry_session_login(账号会话通道)不可用;携程机票/酒店实时检索、12306 余票、Dida 实时报价都依赖此通道。其它工具(机票 FlyAI、酒店 hbcli、地图、天气等)不受影响。若你已从 Chrome 商店安装(自动更新,商店版 ID oeajpicc…),本项可忽略——会话检索可用性以运行时为准(gotry_session_search 的 verdict)。若未安装:应用商店一键装即可',
136
136
  fix: '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd',
137
137
  });
@@ -9,7 +9,7 @@
9
9
  * 断路器,per-效应策略表)→ 渠道 handler(现能力层函数,零改写);
10
10
  * - mock 解译器 makeMockInterpreter:夹具回放(CI/本地无网确定性);
11
11
  * - 浏览器解译 = SESSION_* 效应(用户本人登录态的扩展桥车道,2026-08-29 起默认传输:
12
- * MV3 GoTry Session Bridge + 本地桥长轮询,零系统弹窗;ReadGuard+授权闸不变,
12
+ * MV3 Stai Travel Bridge + 本地桥长轮询,零系统弹窗;ReadGuard+授权闸不变,
13
13
  * 非 CUA 视觉点击——本仓已按零 Python 依赖与 a11y/DOM 优先判死后者,
14
14
  * docs/design/effect-interpreter.md §4)。
15
15
  *
@@ -58,17 +58,17 @@ export function localExtensionInstalled(home = homedir()) {
58
58
  * 影响面统一行:账号会话通道断 → 列表底部工具不提供;其余工具不受影响。
59
59
  * 纯函数(默认 HOME 可注入)。
60
60
  *
61
- * 双语对照:CN=本函数返回值;EN 权威见 docs/capability-onboarding.md「Session Bridge 安装」段。
61
+ * 双语对照:CN=本函数返回值;EN 权威见 docs/capability-onboarding.md「Stai Travel Bridge 安装」段。
62
62
  */
63
63
  const EXTENSION_IMPACT_LINE = '影响面:gotry_session_search / gotry_session_login(账号会话通道)不可用;携程机票/酒店实时检索、12306 余票、Dida 实时报价都依赖此通道。其它工具(机票 FlyAI、酒店 hbcli、地图、天气等)不受影响。';
64
64
  export function needsExtensionSummary(opts = {}) {
65
65
  const local = opts.localInstalled ?? localExtensionInstalled(opts.home);
66
66
  if (local) {
67
- return 'GoTry Session Bridge 扩展未连接(未安装或已停用)。推荐 Chrome 应用商店一键装(自动更新) '
67
+ return 'Stai Travel Bridge 扩展未连接(未安装或已停用)。推荐 Chrome 应用商店一键装(自动更新) '
68
68
  + `${EXTENSION_STORE_URL} ;或 npx @danceiny/gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension。`
69
69
  + EXTENSION_IMPACT_LINE;
70
70
  }
71
- return 'GoTry Session Bridge 扩展未连接。若你已从 Chrome 商店安装(自动更新):打开 Chrome 并确认扩展已启用即可自动连接,无需再装。'
71
+ return 'Stai Travel Bridge 扩展未连接。若你已从 Chrome 商店安装(自动更新):打开 Chrome 并确认扩展已启用即可自动连接,无需再装。'
72
72
  + `若尚未安装:应用商店一键装 ${EXTENSION_STORE_URL} (装完零弹窗,浏览器自己当安装器)。`
73
73
  + EXTENSION_IMPACT_LINE;
74
74
  }
@@ -2,7 +2,7 @@
2
2
  * 会话检索编排层(RFC §3.2):transport → adapter entry → networkHint 嗅探 → 解析 → ReadGuard → 证据链。
3
3
  *
4
4
  * 传输车道(2026-08-29 定案,RFC §2.2):**扩展桥为默认**——一次性安装的 MV3 扩展
5
- * (GoTry Session Bridge)在自己标签页被动嗅探 batchSearch,零 Chrome 系统弹窗;
5
+ * (Stai Travel Bridge)在自己标签页被动嗅探 batchSearch,零 Chrome 系统弹窗;
6
6
  * cdp(attach 日常 Chrome,Chrome 144+ 每连接弹权限框)降为显式后备
7
7
  * (`GOTRY_SESSION_TRANSPORT=cdp` opt-in,诊断/测试用);persistent 仅测试。
8
8
  *
@@ -41,7 +41,7 @@ function runBootstrap(extraArgs, extraEnv, bootstrapPath = bootstrap) {
41
41
  // 1. --check-only:扩展就位探测,exit 0(已不替用户管 hbcli/agent-reach/sidebar)
42
42
  const c1 = runBootstrap(['--check-only'], {});
43
43
  assert.equal(c1.code, 0, `--check-only 应 exit 0,实际 ${c1.code}\n${c1.out}`);
44
- assert.ok(c1.out.includes('Session Bridge'), '报告应含扩展就位节(issue #21 传输层方案 C)');
44
+ assert.ok(c1.out.includes('Stai Travel Bridge'), '报告应含扩展就位节(issue #21 传输层方案 C)');
45
45
  assert.ok(!c1.out.includes('hbcli'), 'setup 不再替用户管 hbcli(已让出)');
46
46
  assert.ok(!c1.out.includes('agent-reach'), 'setup 不再替用户管 agent-reach(已让出)');
47
47
  assert.ok(!c1.out.includes('dsh-better-sidebar'), 'setup 不再替用户管 dsh-better-sidebar(已让出)');
@@ -193,7 +193,7 @@ console.log('11. setupSidebar 落盘状态复核(pnpm 忽略构建脚本 exit 1
193
193
  // 只取 missing 一态;degraded 分类在 12 里用独立 item 单测,避免 label-keyed Map 碰撞。
194
194
  const onboardingItems = [
195
195
  { label: 'Node 运行时', level: 'ok', detail: 'Node 22.x', fix: undefined },
196
- { label: 'GoTry Session Bridge 扩展', level: 'missing', detail: '未安装', fix: 'https://chromewebstore...' },
196
+ { label: 'Stai Travel Bridge 扩展', level: 'missing', detail: '未安装', fix: 'https://chromewebstore...' },
197
197
  { label: 'Agent Reach(网页/社媒读取)', level: 'missing', detail: '未装配', fix: 'npx @danceiny/gotry doctor --fix' },
198
198
  { label: 'hbcli(酒店实时源)', level: 'missing', detail: '未安装', fix: 'npx @danceiny/gotry doctor --fix' },
199
199
  { label: 'FlyAI(飞猪官方检索)', level: 'degraded', detail: '未配 FLYAI_API_KEY', fix: '到 flyai 控制台申请 key' },
@@ -278,7 +278,7 @@ console.log('13. onboardingSkipReason(非 TTY/CI/benchmark/--no-onboarding/SKIP
278
278
  assert.equal(byLabel.get('Agent Reach(网页/社媒读取)').status, 'installed');
279
279
  assert.equal(byLabel.get('hbcli(酒店实时源)').status, 'installed');
280
280
  assert.equal(byLabel.get('dsh-better-sidebar(侧栏工作台)').status, 'installed');
281
- assert.equal(byLabel.get('GoTry Session Bridge 扩展').status, 'needs-user-action');
281
+ assert.equal(byLabel.get('Stai Travel Bridge 扩展').status, 'needs-user-action');
282
282
  assert.equal(byLabel.get('FlyAI(飞猪官方检索)').status, 'needs-user-action');
283
283
  assert.equal(byLabel.get('dsh-calendar(日历工作窗口)').status, 'needs-user-action');
284
284
  assert.equal(byLabel.get('dsh-map-tools(地图/路线/POI)').status, 'unavailable');
@@ -412,7 +412,7 @@ console.log('18. onboarding CLI 跳过(非 TTY / CI / GOTRY_SETUP_SKIP / GOTRY_O
412
412
  assert.ok(autoLabels.includes('hbcli(酒店实时源)'), 'hbcli missing(空 PATH)→ auto');
413
413
  assert.ok(autoLabels.includes('Agent Reach(网页/社媒读取)'), 'reach missing(fixture 无 .venv)→ auto');
414
414
  const userActionLabels = plan.userAction.map((g) => g.label);
415
- assert.ok(userActionLabels.includes('GoTry Session Bridge 扩展'), '扩展 missing(隔离 HOME)→ user-action(浏览器商店)');
415
+ assert.ok(userActionLabels.includes('Stai Travel Bridge 扩展'), '扩展 missing(隔离 HOME)→ user-action(浏览器商店)');
416
416
  assert.ok(userActionLabels.some(label => label.startsWith('FlyAI(飞猪官方检索')), 'flyai 无 key → user-action');
417
417
  assert.ok(!plan.unavailable.some((g) => g.label.startsWith('dsh-map-tools')), 'map-tools 随包就位 → 非 unavailable');
418
418
  assert.ok(!plan.unavailable.some((g) => g.label.startsWith('dsh-tool-ask-user')), 'ask-user 随包就位 → 非 unavailable');
@@ -594,7 +594,7 @@ console.log('19. onboarding --scan(临时包 fixture + 隔离 HOME + 受控 PATH
594
594
  assert.ok(visible.includes('dsh-better-sidebar(侧栏工作台)'), '渲染 sidebar 不可用行');
595
595
  assert.match(visible, /Windows[^\n]*dsh-better-sidebar/, 'sidebar 行带 Windows 原因');
596
596
  // user-action 项平台无关,仍渲染(扩展商店/flyai key/calendar)
597
- assert.ok(visible.includes('GoTry Session Bridge 扩展'), '渲染扩展 user-action 行');
597
+ assert.ok(visible.includes('Stai Travel Bridge 扩展'), '渲染扩展 user-action 行');
598
598
  assert.ok(visible.includes('FlyAI(飞猪官方检索)'), '渲染 flyai user-action 行');
599
599
  assert.ok(visible.includes('dsh-calendar(日历工作窗口)'), '渲染 calendar user-action 行');
600
600
  }
@@ -1417,7 +1417,7 @@ console.log('21. spawned installed-package inner(21a TTY-eligible 真实 prompt
1417
1417
  assert.match(unavail.get('dsh-better-sidebar(侧栏工作台)').detail, /Windows/, 'sidebar unavailable 给 win 平台具体原因');
1418
1418
  // user-action 项不受平台影响(扩展商店/flyai key/calendar profile)
1419
1419
  const ua = new Set(plan.userAction.map((g) => g.label));
1420
- assert.ok(ua.has('GoTry Session Bridge 扩展'), 'win32 扩展仍 user-action(平台无关)');
1420
+ assert.ok(ua.has('Stai Travel Bridge 扩展'), 'win32 扩展仍 user-action(平台无关)');
1421
1421
  assert.ok(ua.has('FlyAI(飞猪官方检索)'), 'win32 flyai 仍 user-action(平台无关)');
1422
1422
  assert.ok(ua.has('dsh-calendar(日历工作窗口)'), 'win32 calendar 仍 user-action(平台无关)');
1423
1423
  // darwin 计划对照:auto=3,promptable=true(不回归)
@@ -183,7 +183,7 @@ console.log('6. gotry_doctor 工具面 + 报告落盘 OK');
183
183
  // 合成 doctor items(与 bootstrap-tests §12 onboardingItems 同形态,稳定 id 锚点)。
184
184
  const repairItems = [
185
185
  { id: 'node', label: 'Node 运行时', status: 'ok', detail: 'Node 22.x' },
186
- { id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'missing', detail: '未安装', fix: 'https://chromewebstore...' },
186
+ { id: 'extension', label: 'Stai Travel Bridge 扩展', status: 'missing', detail: '未安装', fix: 'https://chromewebstore...' },
187
187
  { id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'missing', detail: '未装配', fix: 'npx @danceiny/gotry doctor --fix' },
188
188
  { id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'missing', detail: '未安装', fix: 'npx @danceiny/gotry doctor --fix' },
189
189
  { id: 'flyai', label: 'FlyAI(飞猪官方检索)', status: 'degraded', detail: '未配 FLYAI_API_KEY', fix: '到 flyai 控制台申请 key' },
@@ -209,7 +209,7 @@ function fakeBridge(env = {}) {
209
209
  return i.level === 'degraded' ? 'user-action' : (e.GOTRY_SETUP_HBCLI === '0' ? 'unavailable' : 'auto');
210
210
  if (label.startsWith('dsh-better-sidebar'))
211
211
  return e.GOTRY_SETUP_SIDEBAR === '0' ? 'unavailable' : 'auto';
212
- if (label.startsWith('GoTry Session Bridge'))
212
+ if (label.startsWith('Stai Travel Bridge'))
213
213
  return 'user-action';
214
214
  if (label.startsWith('FlyAI'))
215
215
  return 'user-action';
@@ -90,7 +90,7 @@ async function main() {
90
90
  evidence: '[会话:dida-portal-needs-extension@test]',
91
91
  latencyMs: 1,
92
92
  verdict: 'needs-extension',
93
- error: 'GoTry Session Bridge 扩展未连接',
93
+ error: 'Stai Travel Bridge 扩展未连接',
94
94
  installUrl: STORE_URL,
95
95
  installAction: 'add-to-chrome',
96
96
  }),
@@ -18,7 +18,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync,
18
18
  import { tmpdir } from 'node:os';
19
19
  import { dirname, join, resolve } from 'node:path';
20
20
  import { fileURLToPath } from 'node:url';
21
- import { CONTRADICTION_KINDS, DEFAULT_DECK_PATH, deliveredPlanMarkdown, FIXTURE_HMAC_KEY, loadPersonaDeck, parsePersonaDeck, PersonaSimError, personaSystemPrompt, poiAuditFromGateReport, promptDigest, runPersonaBatch, scrubSessionEnv, summarizeFunnel, } from './persona-sim.js';
21
+ import { CONTRADICTION_KINDS, DEFAULT_DECK_PATH, deliveredPlanMarkdown, FIXTURE_HMAC_KEY, loadPersonaDeck, parsePersonaDeck, PersonaSimError, personaSystemPrompt, poiAuditFromGateReport, promptDigest, runPersonaBatch, scrubSessionEnv, solverVerdict, summarizeFunnel, } from './persona-sim.js';
22
22
  import { enrollParticipant, exportM3Cohort, initM3Cohort, recordNps, recordPlanDelivered, recordPlanFinalized, recordPoiLock, verifyExportedEvidence, } from '../src/m3-cohort.js';
23
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
24
  const TS_ROOT = resolve(__dirname, '..');
@@ -263,6 +263,12 @@ async function main() {
263
263
  }
264
264
  assert.equal(persona.finalized, card.expected_outcome === 'plan_delivered_and_finalized');
265
265
  }
266
+ // A solver failure is an engine/data defect, not a product answer: no shipped card may end in one.
267
+ // The yunnan card used to (issue #620) until its pack's uncalibrated leg became advisory (issue #635).
268
+ for (const persona of result.personas) {
269
+ assert.notEqual(persona.solver_verdict, 'solver_error', `${persona.persona_id}: solver failed (${persona.solver_error_code})`);
270
+ }
271
+ assert.equal(result.personas.find(persona => persona.persona_id === 'yunnan-budget-student').solver_verdict, 'feasible');
266
272
  // The fixture classified every product prompt: an unclassified one means src/dsh-llm.ts
267
273
  // changed its system prompts and the harness is no longer driving the real seam.
268
274
  const calls = result.fixture_calls;
@@ -527,6 +533,18 @@ async function main() {
527
533
  solve: { answer_md: '# 候选对比\n...' },
528
534
  }), '# 候选对比\n...');
529
535
  });
536
+ await pass('a solver failure is its own verdict class and never folds into infeasible (issue #620)', () => {
537
+ const base = { calendar: { year: 2026, assertedWeekdays: {} }, profile: {}, gates: [], wishes: [] };
538
+ const verdict = (solve) => solverVerdict({ ...base, ...(solve ? { solve } : {}) });
539
+ assert.deepEqual(verdict(), { verdict: 'none', unsat: [], errorCode: null });
540
+ assert.deepEqual(verdict({ answer_md: '# 候选对比' }), { verdict: 'candidate_choice', unsat: [], errorCode: null });
541
+ assert.deepEqual(verdict({ feasible: true }), { verdict: 'feasible', unsat: [], errorCode: null });
542
+ assert.deepEqual(verdict({ feasible: false, unsat_core: ['total:budget'] }), { verdict: 'infeasible', unsat: ['total:budget'], errorCode: null });
543
+ // `feasible: false` next to a solver_error means "no verdict was produced", so it is classified
544
+ // before `feasible` is read at all, and an unsat_core can never leak into it.
545
+ assert.deepEqual(verdict({ feasible: false, solver_error: { code: 'solver_input_not_integer', message: 'yn1.bufferMin=NaN' } }), { verdict: 'solver_error', unsat: [], errorCode: 'solver_input_not_integer' });
546
+ assert.equal(verdict({ feasible: false, solver_error: {} }).errorCode, 'unknown');
547
+ });
530
548
  await pass('CLI: offline dry run exits 0 with a machine-readable synthetic result', () => {
531
549
  const run = spawnSync(TSX, ['scripts/persona-sim.ts', '--dry-run', '--format', 'json', '--persona', 'erhai-weekend-unwind'], {
532
550
  cwd: TS_ROOT,
@@ -521,7 +521,7 @@ export function deliveredPlanMarkdown(state) {
521
521
  const rendered = renderSolve(state);
522
522
  return rendered.trim() === '' || rendered === '(无求解结果)' ? null : rendered;
523
523
  }
524
- function solverVerdict(state) {
524
+ export function solverVerdict(state) {
525
525
  const solve = state.solve;
526
526
  if (!solve)
527
527
  return { verdict: 'none', unsat: [], errorCode: null };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dida 供应商门户会话面 LIVE E2E(hotel-be portal integration 迁移线;GOTRY_SESSION_LIVE=1 显式开启)。
3
3
  *
4
- * 全链:真 Chrome(加载 unpacked Session Bridge 扩展)→ 人/驱动在 dida 官网完成登录
4
+ * 全链:真 Chrome(加载 unpacked Stai Travel Bridge 扩展)→ 人/驱动在 dida 官网完成登录
5
5
  * (账密经 env 注入,gotry 永不经手)→ sessionDidaSearch 扩展车道(桥自动拉起,
6
6
  * 扩展 cookie-names 名字级登录闸 → search job 后台标签被动嗅探)→ dida SPA 页面
7
7
  * 自身发起 SearchRealTime → parseDidaRates 结构化报价。
@@ -3,7 +3,7 @@
3
3
  * 运行(在 ts/ 下):npx tsx scripts/unified-tests.ts
4
4
  */
5
5
  import assert from 'node:assert/strict';
6
- import { readFile } from 'node:fs/promises';
6
+ import { readdir, readFile } from 'node:fs/promises';
7
7
  import { join } from 'node:path';
8
8
  import { parseFlightPackToSpec, solveUnified } from '../src/unified.js';
9
9
  import { hhmmToMin } from '../src/model.js';
@@ -50,23 +50,62 @@ off.budgetCny = 9000;
50
50
  off.workWindow = undefined;
51
51
  const r4 = await solveUnified(off);
52
52
  assert.deepEqual(r4.work_window_exclusions, []); // 关掉窗口,周五班恢复可选
53
- // 5. Issue #620:yunnan-pack 的 yn0 段缺 buffer_min/origin_transfer_min/dest_transfer_min
53
+ // 5. Issue #620 / #635:云南包的 yn0 衔接段缺 buffer_min/origin_transfer_min/dest_transfer_min
54
54
  // → 旧实现 Number(undefined)=NaN → Int.val(NaN) 在 z3 WASM 里 `Assertion failed`
55
- // → 被兜底翻译成「不可行 + unsat_core:['wasm_runtime_error']」。三条红线:
56
- // ① parse 边界必须点名拒收(不默认补 0);② 绕过 parse 的 NaN spec 必须以
57
- // solver_error 返回而**绝不**是 infeasible;③ 补齐那三个字段后整包必须真能判定。
58
- const yunnanRaw = JSON.parse(await readFile(join('..', 'data', 'yunnan-pack.json'), 'utf-8'));
59
- // ① 真实数据包:yn0 的三个缺字段 → NaN → 必须是显式 solver_error,绝不是「不可行」
60
- const yunnanSpecRaw = parseFlightPackToSpec(structuredClone(yunnanRaw));
61
- yunnanSpecRaw.budgetCny = 9000;
62
- assert.ok(Number.isNaN(yunnanSpecRaw.segments[0].options[0].move.bufferMin), 'yn0 缺 buffer_min 的事实前提');
63
- const rYunnanRaw = await solveUnified(yunnanSpecRaw);
64
- assert.equal(rYunnanRaw.solver_error?.code, 'solver_input_not_integer', `yunnan 包必须以 solver_error 返回(got ${JSON.stringify(rYunnanRaw)})`);
65
- assert.ok(rYunnanRaw.solver_error.message.includes('yn0.bufferMin'), `solver_error 必须点名出问题的段与字段(got ${rYunnanRaw.solver_error.message})`);
66
- assert.equal(rYunnanRaw.feasible, false, 'solver_error 轮次没有产出可行解');
67
- assert.deepEqual(rYunnanRaw.unsat_core, undefined, '求解器失败不得伪造 unsat_core(机器 token 不进冲突位)');
68
- assert.ok(!JSON.stringify(rYunnanRaw).includes('wasm_runtime_error'), '不得再出现 wasm_runtime_error 这个伪冲突 token');
69
- // ② 最小输入:健康包里塞一个 NaN 接驳分钟 → 同样是 solver_error,点名到字段
55
+ // → 被兜底翻译成「不可行 + unsat_core:['wasm_runtime_error']」。五条红线:
56
+ // ① 出厂数据包里挂接引擎的每一段(`legs`)三字段必须是非负整数;缺真值的段只能记在
57
+ // `advisory_legs`(带理由、不挂接)——替它填数等于编造真实世界的事实(ADR-10);
58
+ // ② 出厂云南包整包可判定,yn0 不进求解(founder 2026-10-05 选「标 advisory」);
59
+ // ③ 防线仍在:缺字段的包(由测试构造)必须以 solver_error 返回而**绝不**是 infeasible;
60
+ // ④ 最小输入:健康包里塞一个 NaN 接驳分钟 → 同样是 solver_error;
61
+ // ⑤ 晋升路径:founder 给出三个真值后,把 advisory 段搬回 legs 即可整包判定。
62
+ const DATA_DIR = join('..', 'data');
63
+ const TRANSFER_FIELDS = ['buffer_min', 'origin_transfer_min', 'dest_transfer_min'];
64
+ const yunnanRaw = JSON.parse(await readFile(join(DATA_DIR, 'yunnan-pack.json'), 'utf-8'));
65
+ // ① 数据完整性:data/ 下每个带 legs 的包,挂接引擎的段三字段齐全;advisory 段自带理由且不与 legs 重名
66
+ let auditedPacks = 0;
67
+ for (const file of (await readdir(DATA_DIR)).filter(f => f.endsWith('.json')).sort()) {
68
+ const raw = JSON.parse(await readFile(join(DATA_DIR, file), 'utf-8'));
69
+ if (!Array.isArray(raw['legs']))
70
+ continue;
71
+ auditedPacks += 1;
72
+ const legs = raw['legs'];
73
+ for (const leg of legs) {
74
+ for (const field of TRANSFER_FIELDS) {
75
+ const value = leg[field];
76
+ assert.ok(typeof value === 'number' && Number.isInteger(value) && value >= 0, `${file}: leg ${String(leg['id'])}.${field} 必须是非负整数(got ${JSON.stringify(value)});缺真值的段请放进 advisory_legs,不要补编造值(issue #635)`);
77
+ }
78
+ }
79
+ const engineIds = new Set(legs.map(leg => String(leg['id'])));
80
+ for (const advisory of (raw['advisory_legs'] ?? [])) {
81
+ const reason = advisory['advisory_reason'];
82
+ assert.ok(typeof reason === 'string' && reason.trim() !== '', `${file}: advisory 段 ${String(advisory['id'])} 必须写明 advisory_reason`);
83
+ assert.ok(!engineIds.has(String(advisory['id'])), `${file}: ${String(advisory['id'])} 不能同时在 legs 与 advisory_legs`);
84
+ }
85
+ }
86
+ assert.ok(auditedPacks >= 2, `数据完整性审计应覆盖航班包与云南包(审计了 ${auditedPacks} 个)`);
87
+ // ② 出厂云南包按产品同款路径整包可判定;advisory 段 yn0 不进引擎段链,也不参与求解
88
+ const yunnanSpec = parseFlightPackToSpec(structuredClone(yunnanRaw));
89
+ assert.deepEqual(yunnanSpec.segments.map(s => s.id), ['yn1', 'yn2', 'yn3', 'yn4'], 'advisory 段 yn0 不得进引擎段链');
90
+ yunnanSpec.budgetCny = 9000;
91
+ const rYunnan = await solveUnified(yunnanSpec);
92
+ assert.equal(rYunnan.solver_error, undefined, `出厂云南包不得再有求解器失败(got ${JSON.stringify(rYunnan.solver_error)})`);
93
+ assert.equal(rYunnan.feasible, true, `出厂云南包应可行(unsat_core=${JSON.stringify(rYunnan.unsat_core)})`);
94
+ assert.deepEqual(rYunnan.legs.map(l => l.leg), ['yn1', 'yn2', 'yn3', 'yn4'], '四段全判定,yn0 不在其中');
95
+ // ③ 防线仍在:缺三个字段的包(由测试从出厂包构造)→ NaN → 必须是显式 solver_error,绝不是「不可行」
96
+ const yunnanGap = structuredClone(yunnanRaw);
97
+ for (const field of TRANSFER_FIELDS)
98
+ delete yunnanGap['legs'][0][field];
99
+ const yunnanGapSpec = parseFlightPackToSpec(yunnanGap);
100
+ yunnanGapSpec.budgetCny = 9000;
101
+ assert.ok(Number.isNaN(yunnanGapSpec.segments[0].options[0].move.bufferMin), '缺 buffer_min 的事实前提');
102
+ const rYunnanGap = await solveUnified(yunnanGapSpec);
103
+ assert.equal(rYunnanGap.solver_error?.code, 'solver_input_not_integer', `缺字段的包必须以 solver_error 返回(got ${JSON.stringify(rYunnanGap)})`);
104
+ assert.ok(rYunnanGap.solver_error.message.includes('yn1.bufferMin'), `solver_error 必须点名出问题的段与字段(got ${rYunnanGap.solver_error.message})`);
105
+ assert.equal(rYunnanGap.feasible, false, 'solver_error 轮次没有产出可行解');
106
+ assert.deepEqual(rYunnanGap.unsat_core, undefined, '求解器失败不得伪造 unsat_core(机器 token 不进冲突位)');
107
+ assert.ok(!JSON.stringify(rYunnanGap).includes('wasm_runtime_error'), '不得再出现 wasm_runtime_error 这个伪冲突 token');
108
+ // ④ 最小输入:健康包里塞一个 NaN 接驳分钟 → 同样是 solver_error,点名到字段
70
109
  const nanSpec = parseFlightPackToSpec(pack);
71
110
  nanSpec.budgetCny = 9000;
72
111
  nanSpec.segments[0].options[0].move.bufferMin = Number.NaN;
@@ -74,16 +113,18 @@ const rNan = await solveUnified(nanSpec);
74
113
  assert.equal(rNan.solver_error?.code, 'solver_input_not_integer', `NaN 接驳必须以 solver_error 返回(got ${JSON.stringify(rNan)})`);
75
114
  assert.ok(rNan.solver_error.message.includes('f1.bufferMin'), `solver_error 必须点名字段(got ${rNan.solver_error.message})`);
76
115
  assert.equal(rNan.feasible, false, 'solver_error 轮次没有产出可行解');
77
- // ③ 补齐 yn0 的三个字段(由测试提供,不改数据包:真值待 founder 校准)→ 整包真能判定,
78
- // 证明断言失败的最小输入正是这三个字段,其余四段编码本身没问题
79
- const yunnanFixed = structuredClone(yunnanRaw);
80
- Object.assign(yunnanFixed['legs'][0], {
81
- buffer_min: 60, origin_transfer_min: 30, dest_transfer_min: 30,
82
- });
83
- const yunnanSpec = parseFlightPackToSpec(yunnanFixed);
84
- yunnanSpec.budgetCny = 9000;
85
- const rYunnan = await solveUnified(yunnanSpec);
86
- assert.equal(rYunnan.solver_error, undefined, `补齐三字段后不得再有求解器失败(got ${JSON.stringify(rYunnan.solver_error)})`);
87
- assert.equal(rYunnan.feasible, true, `yunnan 整包应可行(unsat_core=${JSON.stringify(rYunnan.unsat_core)})`);
88
- assert.equal(rYunnan.legs.length, 5, '五段全判定');
89
- console.log(`TS UNIFIED TESTS: 5/5 OK(工作窗口生效,f2=VZ303,¥${r1.money_cny};#620 yunnan NaN→solver_error 点名 yn0,补齐三字段后可行 ¥${rYunnan.money_cny})`);
116
+ // ⑤ 晋升路径:三个真值由测试提供(数据包里不写——真值待 founder 校准),把 advisory 段搬回 legs 的头部
117
+ // → 五段全判定,证明「补齐三个整数」正是 yn0 从 advisory 晋升所需的全部
118
+ const yunnanPromoted = structuredClone(yunnanRaw);
119
+ const [promotedYn0] = yunnanPromoted['advisory_legs'];
120
+ delete promotedYn0['advisory_reason'];
121
+ Object.assign(promotedYn0, { buffer_min: 60, origin_transfer_min: 30, dest_transfer_min: 30 });
122
+ yunnanPromoted['legs'] = [promotedYn0, ...yunnanPromoted['legs']];
123
+ delete yunnanPromoted['advisory_legs'];
124
+ const promotedSpec = parseFlightPackToSpec(yunnanPromoted);
125
+ promotedSpec.budgetCny = 9000;
126
+ const rPromoted = await solveUnified(promotedSpec);
127
+ assert.equal(rPromoted.solver_error, undefined, `补齐三字段后不得再有求解器失败(got ${JSON.stringify(rPromoted.solver_error)})`);
128
+ assert.equal(rPromoted.feasible, true, `晋升后的云南包应可行(unsat_core=${JSON.stringify(rPromoted.unsat_core)})`);
129
+ assert.equal(rPromoted.legs.length, 5, '晋升后五段全判定');
130
+ console.log(`TS UNIFIED TESTS: 5/5 OK(工作窗口生效,f2=VZ303,¥${r1.money_cny};#620/#635 ${auditedPacks} 个包三字段齐全,出厂云南包四段可行,缺字段→solver_error 点名 yn1,晋升后五段可行)`);
@@ -384,7 +384,7 @@ export function startSessionSearchModule(options) {
384
384
  if (!queue.extensionConnected()) {
385
385
  sendJson(res, 200, {
386
386
  ok: true,
387
- suppliers: [{ supplier: 'dida-portal', loggedIn: false, reason: 'extension-not-connected', detail: '会话执行环境=管理员浏览器扩展(GoTry Session Bridge);扩展未连接桥——在管理员浏览器安装扩展并配置本服务桥地址' }],
387
+ suppliers: [{ supplier: 'dida-portal', loggedIn: false, reason: 'extension-not-connected', detail: '会话执行环境=管理员浏览器扩展(Stai Travel Bridge);扩展未连接桥——在管理员浏览器安装扩展并配置本服务桥地址' }],
388
388
  bridge: queue.stats(),
389
389
  checkedAt,
390
390
  });
package/dist/src/index.js CHANGED
@@ -1660,7 +1660,7 @@ export function apply(ctx, config, seams = {}) {
1660
1660
  description: 'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'AUTO-DETECTION FIRST: it reads ticket-cookie NAMES before anything else — if the user already logged in (on the external site) it confirms instantly WITHOUT opening any page. '
1661
1661
  + 'OPENS the site login entry in the USER\'S OWN Chrome (foreground tab, left open for the user) and waits for the user to finish logging in on the external site. '
1662
1662
  + 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). '
1663
- + 'Transport: the GoTry Session Bridge browser extension (one-time install, ZERO Chrome system dialogs). '
1663
+ + 'Transport: the Stai Travel Bridge browser extension (one-time install, ZERO Chrome system dialogs). '
1664
1664
  + `verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-extension (one-time extension install: the verdict surfaces the Chrome Web Store installUrl as a clickable link for dsh UI to render — installation is a browser concern, not gotry's). `
1665
1665
  + 'Evidence [会话:<site>-login@ts].',
1666
1666
  // D-30 第三刀(issue #112):query blob → 平铺 typed;全字段可选 → interpretArgs 容忍层
@@ -1680,7 +1680,7 @@ export function apply(ctx, config, seams = {}) {
1680
1680
  : r.verdict === 'pending'
1681
1681
  ? `登录入口已在你的 Chrome 打开;请在弹出的标签页里正常登录携程(登录由你在官网完成,不属于 gotry)。完成后说一声"继续",我再确认。gotry 只检查"是否已登录",永不收集你的账号信息。${r.evidence}`
1682
1682
  : r.verdict === 'needs-extension'
1683
- ? `需要一次性安装 GoTry Session Bridge 浏览器扩展:这是浏览器的事,gotry 不弹面板不开剪贴板——请直接在 Chrome 应用商店一键装(add-to-chrome 自动更新)。storeUrl=${EXTENSION_STORE_URL}. 装好即生效,装完后告诉我「重试」即可。${r.evidence}`
1683
+ ? `需要一次性安装 Stai Travel Bridge 浏览器扩展:这是浏览器的事,gotry 不弹面板不开剪贴板——请直接在 Chrome 应用商店一键装(add-to-chrome 自动更新)。storeUrl=${EXTENSION_STORE_URL}. 装好即生效,装完后告诉我「重试」即可。${r.evidence}`
1684
1684
  : r.verdict === 'needs-attach'
1685
1685
  ? `cdp 车道需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"(默认走扩展车道,无需此步)。${r.evidence}`
1686
1686
  : `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`;
@@ -1695,7 +1695,7 @@ export function apply(ctx, config, seams = {}) {
1695
1695
  : r.verdict === 'pending'
1696
1696
  ? '等待你在携程页面完成登录'
1697
1697
  : needsExt
1698
- ? '🧩 需装 GoTry Session Bridge 扩展(浏览器商店一键装,自动更新)'
1698
+ ? '🧩 需装 Stai Travel Bridge 扩展(浏览器商店一键装,自动更新)'
1699
1699
  : r.verdict ?? '降级';
1700
1700
  const content = [{ type: 'text', text: String(value.summary ?? '') }];
1701
1701
  // needs-extension:把商店 URL 渲成可点链接(浏览器自己当安装器,gotry 不插手)
@@ -1709,7 +1709,7 @@ export function apply(ctx, config, seams = {}) {
1709
1709
  name: 'gotry_session_search',
1710
1710
  description: routed('gotry_session_search', 'Search on the USER\'S OWN browser session (Ctrip kind="flight"(default)/"hotel"; 12306 kind="train" — public query face, no login needed). '
1711
1711
  + 'Consent gate: the FIRST call in a session asks the user via the runtime approval card; once granted it holds for the session, a refusal revokes it for the session (no repeat prompting). '
1712
- + 'Transport: GoTry Session Bridge browser extension (one-time install) — the agent side never talks to Chrome debugging, ZERO system dialogs; read-only by construction (the extension never issues requests; it only passively forwards the site\'s own search responses; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
1712
+ + 'Transport: Stai Travel Bridge browser extension (one-time install) — the agent side never talks to Chrome debugging, ZERO system dialogs; read-only by construction (the extension never issues requests; it only passively forwards the site\'s own search responses; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
1713
1713
  + 'kind="flight": from/to 中文城市名 + date YYYY-MM-DD — sniffs the site search API for structured options. Evidence [会话:ctrip-flight@ts]. '
1714
1714
  + 'kind="hotel": to=目的地中文, cityId? = the numeric city= in a hotels.ctrip.com list URL (web-search it when the destination is outside the built-in city table), checkIn?/checkOut? (YYYY-MM-DD), adults?; hotel prices are the user\'s real logged-in prices. Evidence [会话:ctrip-hotel@ts]. '
1715
1715
  + 'kind="dida": Dida supplier-portal realtime hotel rates on the employee\'s own logged-in session (hotel-be portal integration line) — no from/to needed; the portal find page\'s own requests are sniffed passively. Rates carry ratePlanId/referenceNo for the server-side booking chain. Evidence [会话:dida-portal@ts]. '
@@ -1888,7 +1888,7 @@ export function apply(ctx, config, seams = {}) {
1888
1888
  : r.verdict === 'needs-login'
1889
1889
  ? '需登录'
1890
1890
  : needsExt
1891
- ? '🧩 需装 GoTry Session Bridge 扩展(浏览器商店一键装,自动更新)'
1891
+ ? '🧩 需装 Stai Travel Bridge 扩展(浏览器商店一键装,自动更新)'
1892
1892
  : r.verdict ?? '降级';
1893
1893
  const content = [{ type: 'text', text: String(value.summary ?? '') }];
1894
1894
  if (needsExt && r.installUrl)
@@ -2172,7 +2172,7 @@ export function apply(ctx, config, seams = {}) {
2172
2172
  // 统一的状态入口来「显示状态 + 引导补装」,而不是在 not-installed 里各自摸索)----
2173
2173
  registerGuarded(defineTool({
2174
2174
  name: 'gotry_doctor',
2175
- description: 'Check optional-dependency health for ALL gotry tools (read-only by default). Coverage: GoTry Session Bridge extension / Agent Reach (.venv) / hbcli (hotel realtime) / FlyAI key + recent trial-quota exhaustion time / dsh-calendar mount state / dsh-better-sidebar / dsh-map-tools / dsh-tool-ask-user. '
2175
+ description: 'Check optional-dependency health for ALL gotry tools (read-only by default). Coverage: Stai Travel Bridge extension / Agent Reach (.venv) / hbcli (hotel realtime) / FlyAI key + recent trial-quota exhaustion time / dsh-calendar mount state / dsh-better-sidebar / dsh-map-tools / dsh-tool-ask-user. '
2176
2176
  + 'Call this when ANY gotry tool returns not-installed / needs-setup, when the user asks 「体检/依赖状态/工具为什么不可用」, or BEFORE leaning on a channel for a plan. '
2177
2177
  + 'Returns per-item status (ok/degraded/missing) with exact fix commands. '
2178
2178
  + 'Action=diagnose (default): pure read-only — never installs anything, only renders the report. '
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Stai browser extension
4
4
 
5
- Stai (formerly GoTry Session Bridge) connects supported travel-search pages to GoTry. The Chrome Web Store and unpacked builds have different extension IDs but use the same bridge contract.
5
+ Stai (formerly Stai Travel Bridge) connects supported travel-search pages to GoTry. The Chrome Web Store and unpacked builds have different extension IDs but use the same bridge contract.
6
6
 
7
7
  ## What it does
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Stai 浏览器扩展
4
4
 
5
- Stai(原名 GoTry Session Bridge)将已支持的旅行检索页面连接至 GoTry。商店版与本地加载版的扩展 ID 不同,但使用相同的桥接合同。
5
+ Stai(原名 Stai Travel Bridge)将已支持的旅行检索页面连接至 GoTry。商店版与本地加载版的扩展 ID 不同,但使用相同的桥接合同。
6
6
 
7
7
  ## 功能
8
8
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * GoTry Session Bridge — MV3 Service Worker(RFC docs/rfc/user-session-data-rfc.md §2.2 通道 C,2026-08-29 定案为 PRIMARY 传输)。
2
+ * Stai Travel Bridge — MV3 Service Worker(RFC docs/rfc/user-session-data-rfc.md §2.2 通道 C,2026-08-29 定案为 PRIMARY 传输)。
3
3
  *
4
4
  * 职责(与 Node 侧 session/extension-bridge.ts 配对,零构建纯 JS):
5
5
  * - 长轮询取活:POST /jobs(桥最多 hold 20s;每次响应/失败都重置 SW 30s 生命周期,任务秒级触达);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * GoTry Session Bridge — ISOLATED-world 桥(manifest 默认 world,document_start)。
2
+ * Stai Travel Bridge — ISOLATED-world 桥(manifest 默认 world,document_start)。
3
3
  *
4
4
  * MAIN world(content-main.js)拿不到 chrome API;本脚本监听页面里的
5
5
  * gotry-ctrip-sniff 自定义事件,把响应文本经 chrome.runtime.sendMessage 转发给
@@ -1,5 +1,5 @@
1
1
  /**
2
- * GoTry Session Bridge — MAIN-world 网络嗅探(manifest world:"MAIN",document_start)。
2
+ * Stai Travel Bridge — MAIN-world 网络嗅探(manifest world:"MAIN",document_start)。
3
3
  *
4
4
  * MAIN world 没有 chrome.runtime,只负责:hook 站点自己的 fetch/XHR,把命中
5
5
  * 嗅探面的响应文本以 CustomEvent 派发到页面里;ISOLATED 侧的 content-bridge.js
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Stai Travel Bridge",
4
- "version": "0.2.0.28",
4
+ "version": "0.2.0.29",
5
5
  "description": "Stai 连接获准的旅行检索与供应商门户:检索结果经本机或授权后端桥接;门户可触发一次性登录代填,扩展不持久保存凭据。",
6
6
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnuzBX8rT8+RZKEUip5VRA0cY056pExy9PNdN9YCiuY3dgglRbcIBhjuFJoR6EX3/MaUv82vJVyItRVrowXLTwDk4WZof0vwFgKL0zBK304PC46pUaAKTSu1PgXeL1+j5KTAGz/9a9+RnPdbj7jND5rM/MOmBHNPVPpp1NlwxRGIgF9OxHSOfXORcH4Iyte1aJSFUJbFtNS/DaE5D+KjyupPLyIqVN/dCiKnNsTCEHQB9ngeli665PFp76hkccHQQZ4sVuMLS4zBPcazUsp1kcnW+xsQoUATkODTCY+Pzk3zUVF1QOESTjRCVnbNi8tkO43RMRs2q63gX/3inFivcEwIDAQAB",
7
7
  "permissions": [
@@ -56,5 +56,5 @@
56
56
  "run_at": "document_start"
57
57
  }
58
58
  ],
59
- "version_name": "0.2.0-rc.28"
59
+ "version_name": "0.2.0-rc.29"
60
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceiny/gotry",
3
- "version": "0.2.0-rc.28",
3
+ "version": "0.2.0-rc.29",
4
4
  "description": "GoTry — 从出发到下一次出发的 AI 旅行 Agent(dsh 插件)。npm/源码入口共用锁定的 dsh runtime + README 安装路径。",
5
5
  "type": "module",
6
6
  "main": "ts/src/index.ts",
@@ -62,17 +62,17 @@ export function localExtensionInstalled(home = homedir()): boolean {
62
62
  * 影响面统一行:账号会话通道断 → 列表底部工具不提供;其余工具不受影响。
63
63
  * 纯函数(默认 HOME 可注入)。
64
64
  *
65
- * 双语对照:CN=本函数返回值;EN 权威见 docs/capability-onboarding.md「Session Bridge 安装」段。
65
+ * 双语对照:CN=本函数返回值;EN 权威见 docs/capability-onboarding.md「Stai Travel Bridge 安装」段。
66
66
  */
67
67
  const EXTENSION_IMPACT_LINE = '影响面:gotry_session_search / gotry_session_login(账号会话通道)不可用;携程机票/酒店实时检索、12306 余票、Dida 实时报价都依赖此通道。其它工具(机票 FlyAI、酒店 hbcli、地图、天气等)不受影响。'
68
68
  export function needsExtensionSummary(opts: { localInstalled?: boolean; home?: string } = {}): string {
69
69
  const local = opts.localInstalled ?? localExtensionInstalled(opts.home)
70
70
  if (local) {
71
- return 'GoTry Session Bridge 扩展未连接(未安装或已停用)。推荐 Chrome 应用商店一键装(自动更新) '
71
+ return 'Stai Travel Bridge 扩展未连接(未安装或已停用)。推荐 Chrome 应用商店一键装(自动更新) '
72
72
  + `${EXTENSION_STORE_URL} ;或 npx @danceiny/gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension。`
73
73
  + EXTENSION_IMPACT_LINE
74
74
  }
75
- return 'GoTry Session Bridge 扩展未连接。若你已从 Chrome 商店安装(自动更新):打开 Chrome 并确认扩展已启用即可自动连接,无需再装。'
75
+ return 'Stai Travel Bridge 扩展未连接。若你已从 Chrome 商店安装(自动更新):打开 Chrome 并确认扩展已启用即可自动连接,无需再装。'
76
76
  + `若尚未安装:应用商店一键装 ${EXTENSION_STORE_URL} (装完零弹窗,浏览器自己当安装器)。`
77
77
  + EXTENSION_IMPACT_LINE
78
78
  }
@@ -2,7 +2,7 @@
2
2
  * 会话检索编排层(RFC §3.2):transport → adapter entry → networkHint 嗅探 → 解析 → ReadGuard → 证据链。
3
3
  *
4
4
  * 传输车道(2026-08-29 定案,RFC §2.2):**扩展桥为默认**——一次性安装的 MV3 扩展
5
- * (GoTry Session Bridge)在自己标签页被动嗅探 batchSearch,零 Chrome 系统弹窗;
5
+ * (Stai Travel Bridge)在自己标签页被动嗅探 batchSearch,零 Chrome 系统弹窗;
6
6
  * cdp(attach 日常 Chrome,Chrome 144+ 每连接弹权限框)降为显式后备
7
7
  * (`GOTRY_SESSION_TRANSPORT=cdp` opt-in,诊断/测试用);persistent 仅测试。
8
8
  *
package/ts/src/index.ts CHANGED
@@ -1788,7 +1788,7 @@ export function apply(ctx: Context, config: Config, seams: ApplyTestSeams = {}):
1788
1788
  'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'AUTO-DETECTION FIRST: it reads ticket-cookie NAMES before anything else — if the user already logged in (on the external site) it confirms instantly WITHOUT opening any page. '
1789
1789
  + 'OPENS the site login entry in the USER\'S OWN Chrome (foreground tab, left open for the user) and waits for the user to finish logging in on the external site. '
1790
1790
  + 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). '
1791
- + 'Transport: the GoTry Session Bridge browser extension (one-time install, ZERO Chrome system dialogs). '
1791
+ + 'Transport: the Stai Travel Bridge browser extension (one-time install, ZERO Chrome system dialogs). '
1792
1792
  + `verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-extension (one-time extension install: the verdict surfaces the Chrome Web Store installUrl as a clickable link for dsh UI to render — installation is a browser concern, not gotry's). `
1793
1793
  + 'Evidence [会话:<site>-login@ts].',
1794
1794
  // D-30 第三刀(issue #112):query blob → 平铺 typed;全字段可选 → interpretArgs 容忍层
@@ -1807,7 +1807,7 @@ export function apply(ctx: Context, config: Config, seams: ApplyTestSeams = {}):
1807
1807
  : r.verdict === 'pending'
1808
1808
  ? `登录入口已在你的 Chrome 打开;请在弹出的标签页里正常登录携程(登录由你在官网完成,不属于 gotry)。完成后说一声"继续",我再确认。gotry 只检查"是否已登录",永不收集你的账号信息。${r.evidence}`
1809
1809
  : r.verdict === 'needs-extension'
1810
- ? `需要一次性安装 GoTry Session Bridge 浏览器扩展:这是浏览器的事,gotry 不弹面板不开剪贴板——请直接在 Chrome 应用商店一键装(add-to-chrome 自动更新)。storeUrl=${EXTENSION_STORE_URL}. 装好即生效,装完后告诉我「重试」即可。${r.evidence}`
1810
+ ? `需要一次性安装 Stai Travel Bridge 浏览器扩展:这是浏览器的事,gotry 不弹面板不开剪贴板——请直接在 Chrome 应用商店一键装(add-to-chrome 自动更新)。storeUrl=${EXTENSION_STORE_URL}. 装好即生效,装完后告诉我「重试」即可。${r.evidence}`
1811
1811
  : r.verdict === 'needs-attach'
1812
1812
  ? `cdp 车道需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"(默认走扩展车道,无需此步)。${r.evidence}`
1813
1813
  : `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`
@@ -1822,7 +1822,7 @@ export function apply(ctx: Context, config: Config, seams: ApplyTestSeams = {}):
1822
1822
  : r.verdict === 'pending'
1823
1823
  ? '等待你在携程页面完成登录'
1824
1824
  : needsExt
1825
- ? '🧩 需装 GoTry Session Bridge 扩展(浏览器商店一键装,自动更新)'
1825
+ ? '🧩 需装 Stai Travel Bridge 扩展(浏览器商店一键装,自动更新)'
1826
1826
  : r.verdict ?? '降级'
1827
1827
  const content: Array<{ type: 'text'; text: string }> = [{ type: 'text', text: String((value as { summary?: string }).summary ?? '') }]
1828
1828
  // needs-extension:把商店 URL 渲成可点链接(浏览器自己当安装器,gotry 不插手)
@@ -1837,7 +1837,7 @@ export function apply(ctx: Context, config: Config, seams: ApplyTestSeams = {}):
1837
1837
  description: routed('gotry_session_search',
1838
1838
  'Search on the USER\'S OWN browser session (Ctrip kind="flight"(default)/"hotel"; 12306 kind="train" — public query face, no login needed). '
1839
1839
  + 'Consent gate: the FIRST call in a session asks the user via the runtime approval card; once granted it holds for the session, a refusal revokes it for the session (no repeat prompting). '
1840
- + 'Transport: GoTry Session Bridge browser extension (one-time install) — the agent side never talks to Chrome debugging, ZERO system dialogs; read-only by construction (the extension never issues requests; it only passively forwards the site\'s own search responses; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
1840
+ + 'Transport: Stai Travel Bridge browser extension (one-time install) — the agent side never talks to Chrome debugging, ZERO system dialogs; read-only by construction (the extension never issues requests; it only passively forwards the site\'s own search responses; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
1841
1841
  + 'kind="flight": from/to 中文城市名 + date YYYY-MM-DD — sniffs the site search API for structured options. Evidence [会话:ctrip-flight@ts]. '
1842
1842
  + 'kind="hotel": to=目的地中文, cityId? = the numeric city= in a hotels.ctrip.com list URL (web-search it when the destination is outside the built-in city table), checkIn?/checkOut? (YYYY-MM-DD), adults?; hotel prices are the user\'s real logged-in prices. Evidence [会话:ctrip-hotel@ts]. '
1843
1843
  + 'kind="dida": Dida supplier-portal realtime hotel rates on the employee\'s own logged-in session (hotel-be portal integration line) — no from/to needed; the portal find page\'s own requests are sniffed passively. Rates carry ratePlanId/referenceNo for the server-side booking chain. Evidence [会话:dida-portal@ts]. '
@@ -2011,7 +2011,7 @@ export function apply(ctx: Context, config: Config, seams: ApplyTestSeams = {}):
2011
2011
  : r.verdict === 'needs-login'
2012
2012
  ? '需登录'
2013
2013
  : needsExt
2014
- ? '🧩 需装 GoTry Session Bridge 扩展(浏览器商店一键装,自动更新)'
2014
+ ? '🧩 需装 Stai Travel Bridge 扩展(浏览器商店一键装,自动更新)'
2015
2015
  : r.verdict ?? '降级'
2016
2016
  const content: Array<{ type: 'text'; text: string }> = [{ type: 'text', text: String((value as { summary?: string }).summary ?? '') }]
2017
2017
  if (needsExt && r.installUrl) content.push({ type: 'text', text: `安装链接:${r.installUrl}` })
@@ -2301,7 +2301,7 @@ export function apply(ctx: Context, config: Config, seams: ApplyTestSeams = {}):
2301
2301
  registerGuarded(defineTool({
2302
2302
  name: 'gotry_doctor',
2303
2303
  description:
2304
- 'Check optional-dependency health for ALL gotry tools (read-only by default). Coverage: GoTry Session Bridge extension / Agent Reach (.venv) / hbcli (hotel realtime) / FlyAI key + recent trial-quota exhaustion time / dsh-calendar mount state / dsh-better-sidebar / dsh-map-tools / dsh-tool-ask-user. '
2304
+ 'Check optional-dependency health for ALL gotry tools (read-only by default). Coverage: Stai Travel Bridge extension / Agent Reach (.venv) / hbcli (hotel realtime) / FlyAI key + recent trial-quota exhaustion time / dsh-calendar mount state / dsh-better-sidebar / dsh-map-tools / dsh-tool-ask-user. '
2305
2305
  + 'Call this when ANY gotry tool returns not-installed / needs-setup, when the user asks 「体检/依赖状态/工具为什么不可用」, or BEFORE leaning on a channel for a plan. '
2306
2306
  + 'Returns per-item status (ok/degraded/missing) with exact fix commands. '
2307
2307
  + 'Action=diagnose (default): pure read-only — never installs anything, only renders the report. '