@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 +2 -2
- package/README.zh-CN.md +2 -2
- package/bin/gotry-bootstrap.js +9 -9
- package/data/yunnan-pack.json +30 -27
- package/dist/capabilities/channel-registry.js +4 -4
- package/dist/capabilities/doctor.js +3 -3
- package/dist/capabilities/effect.js +1 -1
- package/dist/capabilities/session/extension-bridge.js +3 -3
- package/dist/capabilities/session-search.js +1 -1
- package/dist/scripts/bootstrap-tests.js +6 -6
- package/dist/scripts/doctor-tests.js +2 -2
- package/dist/scripts/gotry-backend-kernel-tests.js +1 -1
- package/dist/scripts/persona-sim-tests.js +19 -1
- package/dist/scripts/persona-sim.js +1 -1
- package/dist/scripts/session-dida-live-e2e.js +1 -1
- package/dist/scripts/unified-tests.js +71 -30
- package/dist/src/backend/modules/session-search.js +1 -1
- package/dist/src/index.js +6 -6
- package/extension/README.md +1 -1
- package/extension/README.zh-CN.md +1 -1
- package/extension/background.js +1 -1
- package/extension/content-bridge.js +1 -1
- package/extension/content-main.js +1 -1
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/ts/capabilities/session/extension-bridge.ts +3 -3
- package/ts/capabilities/session-search.ts +1 -1
- package/ts/src/index.ts +6 -6
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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>
|
package/bin/gotry-bootstrap.js
CHANGED
|
@@ -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: '
|
|
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('
|
|
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]
|
|
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 应用商店一键安装
|
|
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]
|
|
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 →
|
|
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]
|
|
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]
|
|
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]
|
|
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`)
|
package/data/yunnan-pack.json
CHANGED
|
@@ -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: '一次性装
|
|
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: '一次性装
|
|
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: '一次性装
|
|
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: '一次性装
|
|
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.
|
|
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: '
|
|
132
|
+
? { id: 'extension', label: 'Stai Travel Bridge 扩展', status: 'ok', detail: `已就位(本地通道:${extManifest})` }
|
|
133
133
|
: {
|
|
134
|
-
id: 'extension', label: '
|
|
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
|
|
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「
|
|
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 '
|
|
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 '
|
|
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
|
-
* (
|
|
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('
|
|
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: '
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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: '
|
|
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('
|
|
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: '
|
|
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
|
|
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
|
|
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
|
-
// ①
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
//
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
assert.equal(
|
|
89
|
-
|
|
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: '会话执行环境=管理员浏览器扩展(
|
|
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
|
|
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
|
-
? `需要一次性安装
|
|
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
|
-
? '🧩 需装
|
|
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:
|
|
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
|
-
? '🧩 需装
|
|
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:
|
|
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. '
|
package/extension/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Stai browser extension
|
|
4
4
|
|
|
5
|
-
Stai (formerly
|
|
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
|
|
package/extension/background.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Stai Travel Bridge",
|
|
4
|
-
"version": "0.2.0.
|
|
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.
|
|
59
|
+
"version_name": "0.2.0-rc.29"
|
|
60
60
|
}
|
package/package.json
CHANGED
|
@@ -62,17 +62,17 @@ export function localExtensionInstalled(home = homedir()): boolean {
|
|
|
62
62
|
* 影响面统一行:账号会话通道断 → 列表底部工具不提供;其余工具不受影响。
|
|
63
63
|
* 纯函数(默认 HOME 可注入)。
|
|
64
64
|
*
|
|
65
|
-
* 双语对照:CN=本函数返回值;EN 权威见 docs/capability-onboarding.md「
|
|
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 '
|
|
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 '
|
|
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
|
-
* (
|
|
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
|
|
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
|
-
? `需要一次性安装
|
|
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
|
-
? '🧩 需装
|
|
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:
|
|
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
|
-
? '🧩 需装
|
|
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:
|
|
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. '
|