@danceiny/gotry 0.0.1-rc.20 → 0.0.1-rc.22
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 +14 -3
- package/README.zh-CN.md +15 -4
- package/bin/gotry-bootstrap.js +54 -8
- package/cordis.gotry-patch.yml +12 -5
- package/dist/capabilities/doctor.js +88 -16
- package/dist/capabilities/session/health-watch.js +10 -3
- package/dist/scripts/benchmark-environment-bridge-e2e.js +53 -15
- package/dist/scripts/benchmark-environment-bridge-tests.js +450 -28
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +211 -2
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +132 -2
- package/dist/scripts/bootstrap-tests.js +2 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +9 -1
- package/dist/scripts/map-tools-vendor-package-proof.js +276 -0
- package/dist/scripts/persona-surface-guard-tests.js +8 -3
- package/dist/src/benchmark-agent-conformance.js +155 -5
- package/dist/src/benchmark-environment-bridge.js +53 -71
- package/dist/src/booking-surface/dsh-planner.js +24 -8
- package/extension/manifest.json +2 -2
- package/package.json +2 -1
- package/ts/capabilities/session/health-watch.ts +9 -2
- package/ts/package.json +15 -7
- package/ts/scripts/map-tools-vendor-package-proof.ts +268 -0
- package/ts/src/benchmark-agent-conformance.ts +135 -4
- package/ts/src/benchmark-environment-bridge.ts +25 -40
- package/ts/src/booking-surface/dsh-planner.ts +25 -13
package/README.md
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
GoTry is an AI travel agent for **"departure to next departure."** You tell it where you want to go and why; it interviews you about your working hours and existing bookings, then hands you a **formally verified itinerary** — computed by a Z3 solver, not guessed by a model.
|
|
9
9
|
|
|
10
|
+
[](https://github.com/Danceiny/gotry/stargazers)
|
|
10
11
|
[](https://github.com/Danceiny/gotry/actions/workflows/ci.yml)
|
|
11
12
|
[](https://www.npmjs.com/package/@danceiny/gotry)
|
|
12
13
|
[](LICENSE)
|
|
13
14
|
[](https://www.npmjs.com/package/@danceiny/gotry)
|
|
14
15
|
[](docs/architecture.md)
|
|
15
16
|
|
|
16
|
-
**[What GoTry Does](#what-gotry-does)** · **[How It Works](#how-it-works)** · **[Tools](#tools)** · **[Demo](#demo)** · **[Benchmark](#how-mainstream-ai-answers-the-same-trip)** · **[Quick Start](#quick-start)** · **[Consent and Privacy](#consent-and-privacy)** · **[Trustworthy by Construction](#trustworthy-by-construction)** · **[Project Status](#project-status)** · **[Roadmap](#roadmap)** · **[For AI Agents](#for-ai-agents)** · **[Documentation](#documentation)** · **[License](#license)**
|
|
17
|
+
**[What GoTry Does](#what-gotry-does)** · **[How It Works](#how-it-works)** · **[Tools](#tools)** · **[Demo](#demo)** · **[Benchmark](#how-mainstream-ai-answers-the-same-trip)** · **[Quick Start](#quick-start)** · **[Consent and Privacy](#consent-and-privacy)** · **[Trustworthy by Construction](#trustworthy-by-construction)** · **[Project Status](#project-status)** · **[Roadmap](#roadmap)** · **[For AI Agents](#for-ai-agents)** · **[Documentation](#documentation)** · **[License](#license)** · **[Star History](#star-history)**
|
|
17
18
|
|
|
18
19
|
> **Tip for newcomers:** one command is enough to feel the difference — `npx @danceiny/gotry web`, open `http://127.0.0.1:3080`, and say *"I want three relaxing days in Dali."* The agent interviews you first; then the solver, not the model, decides what is feasible. Full walkthrough: [`docs/user-guide.md`](docs/user-guide.md).
|
|
19
20
|
|
|
@@ -194,7 +195,7 @@ Current release: **v0.0.1-rc.18** (npm `latest` and `rc` both point here; regist
|
|
|
194
195
|
|
|
195
196
|
- **Z3 solving engine** — feasibility verdicts + door-to-door whole-cost; the historical concurrency race is fixed and regression-gated
|
|
196
197
|
- **Realtime retrieval** — flights/trains/hotels (Fliggy official channel), destination/hotel catalogs, weather, live flight observation, route connectivity; realtime prices can overwrite solver prices (`GOTRY_REALTIME_PRICING=1`); exhausted FlyAI anonymous trial quota is classified `needs-setup` with key guidance (no blind retries)
|
|
197
|
-
- **Dependency doctor** — `npx gotry doctor` (CLI) / `gotry_doctor` (in-chat tool): read-only health check of optional dependencies (extension / Agent-Reach / hbcli / FlyAI key / sidebar / dsh-calendar mount / dsh-map-tools & dsh-tool-ask-user presence) with exact repair guidance; `--fix` installs; LLM keys stay with the dsh host
|
|
198
|
+
- **Dependency doctor** — `npx gotry doctor` (CLI) / `gotry_doctor` (in-chat tool): read-only health check of optional dependencies (extension / Agent-Reach / hbcli / FlyAI key / sidebar / dsh-calendar mount / dsh-map-tools & dsh-tool-ask-user presence) with exact repair guidance; map tools ship as a bundled MIT payload rather than an incompatible external npm peer; `--fix` installs; LLM keys stay with the dsh host
|
|
198
199
|
- **Account-session search** — Ctrip flights **and hotels** + 12306 trains on your Chrome (hotels 2026-09-03: real logged-in prices via passive sniffing; trains 2026-09-03: public left-ticket query; interface surfaces calibrate with the first live session); observed runs scored every landed hit 13/13 with zero write attempts, while non-hits stay explicit `miss` records — no live-availability claim beyond that
|
|
199
200
|
- **Extension install on demand** — `[GoTry Session Bridge](https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd)` is offered as a clickable link in the dsh UI when an account-session tool first needs it (one-click install + auto-update); the gotry side never runs a setup wizard
|
|
200
201
|
- **Memory & reachability** — motivation profile / wish pool / companions / travel timeline; English solve output via `GOTRY_LOCALE=en`
|
|
@@ -205,7 +206,7 @@ Current release: **v0.0.1-rc.18** (npm `latest` and `rc` both point here; regist
|
|
|
205
206
|
- **M3 Exit not closed** — engineering & distribution are ready, but real seed-user evidence (50–200 person cohort) has not been accumulated; automated tests prove contracts and formulas, not business pass
|
|
206
207
|
- **Hotel session adapters** — Ctrip-hotel / Meituan logged-in surfaces await real login-state backfill; flights are done
|
|
207
208
|
- **Interface language** — English covers the deterministic solve-output layer; the dsh host UI and dialogue surface belong to the host / calibration samples
|
|
208
|
-
- **External benchmark generalization** — every frozen external run to date remains diagnostic-only (no score, no uplift claim).
|
|
209
|
+
- **External benchmark generalization** — every frozen external run to date remains diagnostic-only (no score, no uplift claim). After the Round 10 `glm-5.3-flash` visibility diagnosis on main `c843fae` (57 empty `{}` calls), Round 11 keeps execution validation exact against frozen descriptors while exposing a flat model-facing `tools/call/errors` wire with descriptor-derived tool names and generic object arguments; the round-by-round engineering ledger lives in [`docs/evaluation/benchmark-environment-bridge.md`](docs/evaluation/benchmark-environment-bridge.md)
|
|
209
210
|
- **Booking** — nothing bookable ships today; M5 opens only through WriteGate and the booking-saga FSM
|
|
210
211
|
|
|
211
212
|
<details>
|
|
@@ -280,6 +281,16 @@ Program-level context: [`docs/gotry-master-outline.md`](docs/gotry-master-outlin
|
|
|
280
281
|
|
|
281
282
|
**MIT** — same as upstream dsh. See [LICENSE](LICENSE).
|
|
282
283
|
|
|
284
|
+
## Star History
|
|
285
|
+
|
|
286
|
+
<a href="https://www.star-history.com/?repos=danceiny%2Fgotry&type=date&legend=top-left">
|
|
287
|
+
<picture>
|
|
288
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=danceiny/gotry&type=date&theme=dark&legend=top-left" />
|
|
289
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=danceiny/gotry&type=date&legend=top-left" />
|
|
290
|
+
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=danceiny/gotry&type=date&legend=top-left" />
|
|
291
|
+
</picture>
|
|
292
|
+
</a>
|
|
293
|
+
|
|
283
294
|
---
|
|
284
295
|
|
|
285
296
|
**Built with**: DeepSeek Harness 0.1.2-alpha.3 (root-pinned) · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 · OpenFlights · TypeScript
|
package/README.zh-CN.md
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
**GoTry 是「从出发到下一次出发」的 AI 旅行 Agent**:你用一句话说想去哪、为什么想出发;它先问清楚你的工作窗口和已订资源,再用 **Z3 数学求解器**给你一份经过形式化验证的行程方案——是算出来的,不是模型猜的。
|
|
9
9
|
|
|
10
|
+
[](https://github.com/Danceiny/gotry/stargazers)
|
|
10
11
|
[](https://github.com/Danceiny/gotry/actions/workflows/ci.yml)
|
|
11
12
|
[](https://www.npmjs.com/package/@danceiny/gotry)
|
|
12
13
|
[](LICENSE)
|
|
13
14
|
[](https://www.npmjs.com/package/@danceiny/gotry)
|
|
14
15
|
[](docs/architecture.md)
|
|
15
16
|
|
|
16
|
-
**[它做什么](#它做什么)** · **[工作原理](#工作原理)** · **[工具](#工具)** · **[一段对话](#一段对话)** · **[同题横评](#与主流-ai-同题横评产品人格从哪来)** · **[快速开始](#快速开始)** · **[账号会话:授权与隐私](#账号会话授权与隐私)** · **[构造上可信](#构造上可信)** · **[状态与限制](#状态与限制)** · **[路线图](#路线图)** · **[给 AI Agent](#给-ai-agent)** · **[文档](#文档)** · **[License](#license)**
|
|
17
|
+
**[它做什么](#它做什么)** · **[工作原理](#工作原理)** · **[工具](#工具)** · **[一段对话](#一段对话)** · **[同题横评](#与主流-ai-同题横评产品人格从哪来)** · **[快速开始](#快速开始)** · **[账号会话:授权与隐私](#账号会话授权与隐私)** · **[构造上可信](#构造上可信)** · **[状态与限制](#状态与限制)** · **[路线图](#路线图)** · **[给 AI Agent](#给-ai-agent)** · **[文档](#文档)** · **[License](#license)** · **[Star History](#star-history)**
|
|
17
18
|
|
|
18
19
|
> **新读者提示**:一条命令就能感受差别——`npx @danceiny/gotry web`,打开 `http://127.0.0.1:3080`,说一句「我想去大理躺三天」。Agent 会先访谈你;然后由求解器(而非模型)判定什么可行。完整走查见 [`docs/user-guide.md`](docs/user-guide.md)。
|
|
19
20
|
|
|
@@ -143,7 +144,7 @@ npx @danceiny/gotry web
|
|
|
143
144
|
|---|---|---|
|
|
144
145
|
| Web 对话(推荐) | `npx @danceiny/gotry web` | 持续多轮规划,看推理可视化 → `:3080` |
|
|
145
146
|
| headless 一问一答 | `npx @danceiny/gotry "我想从深圳休整两天,预算 3000"` | 脚本 / CI / 定向调试 → stdout |
|
|
146
|
-
| 依赖体检 | `npx @danceiny/gotry doctor`(`--fix` 补装) | 可选渠道不好使时:体检扩展 / Agent-Reach / hbcli / FlyAI key / sidebar,逐项给精确修复指引,报告落 `gotry-state/doctor-report.md`(侧栏工作台可预览) |
|
|
147
|
+
| 依赖体检 | `npx @danceiny/gotry doctor`(`--fix` 补装) | 可选渠道不好使时:体检扩展 / Agent-Reach / hbcli / FlyAI key / sidebar / dsh-calendar / dsh-map-tools / dsh-tool-ask-user,逐项给精确修复指引,报告落 `gotry-state/doctor-report.md`(侧栏工作台可预览) |
|
|
147
148
|
|
|
148
149
|
前置:Node ≥ 22.15。LLM 凭证由 dsh 宿主 UI 配,OpenAI 兼容端点(MiniMax/中转/自建网关)走 dsh 的模型设置。首启 6–15 秒属正常冷启动;`:3080` 被占先腾端口;异常退出会留证据到 `gotry-state/incidents.jsonl`(不静默)。
|
|
149
150
|
|
|
@@ -190,7 +191,7 @@ node scripts/build-dist.mjs # 构建 JS runtime
|
|
|
190
191
|
|
|
191
192
|
- **Z3 求解引擎** —— 可行性判决 + 门到门全成本;历史并发竞态已根治并进回归闸
|
|
192
193
|
- **实时检索** —— 机票/火车/酒店(飞猪官方通道)、目的地/酒店目录、天气、航班观测、通航性校验;实时票价可覆写求解价(`GOTRY_REALTIME_PRICING=1`);飞猪匿名试用额度达限归类 `needs-setup` 并带配 key 指引(不盲重试)
|
|
193
|
-
- **依赖体检** —— `npx gotry doctor`(CLI)/ `gotry_doctor`(对话内工具):可选依赖(扩展 / Agent-Reach / hbcli / FlyAI key / sidebar)只读体检 +
|
|
194
|
+
- **依赖体检** —— `npx gotry doctor`(CLI)/ `gotry_doctor`(对话内工具):可选依赖(扩展 / Agent-Reach / hbcli / FlyAI key / sidebar / dsh-calendar / dsh-map-tools / dsh-tool-ask-user)只读体检 + 精确补装指引;地图工具以 MIT payload 随包交付,不再引入与 alpha.3 冲突的外部 npm peer;`--fix` 补装;LLM key 仍归 dsh 宿主管
|
|
194
195
|
- **账号会话检索** —— 你本人登录态查携程机票/酒店 + 12306 火车(酒/火 2026-09-03 实装:酒店为被动嗅探登录态真实价,火车为 12306 公开余票查询面;接口面随首个真会话校准);观测轮次中所有可评分 hit 全过、ReadGuard 零写,非 hit 保持显式 `miss` 记录——不作超出此口径的实时可售声明
|
|
195
196
|
- **扩展按需装** —— `[GoTry Session Bridge](https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd)` 由 dsh 宿主 UI 在账号会话工具首次需要时以可点链接给出(Chrome 商店一键装 + 自动更新);gotry 这边不跑 setup wizard、不开 chrome://extensions、不动剪贴板
|
|
196
197
|
- **记忆与触达** —— 动机画像 / 愿望池 / 同行人 / 旅行时间线;英文输出一键切换(`GOTRY_LOCALE=en`)
|
|
@@ -201,7 +202,7 @@ node scripts/build-dist.mjs # 构建 JS runtime
|
|
|
201
202
|
- **M3 Exit 未关闭** —— 工程与分发面就绪,但真实种子用户证据(50–200 人 cohort)尚未积累;自动化测试证明的是合同与公式,不是 business pass
|
|
202
203
|
- **酒店会话适配** —— 携程酒店/美团登录态面等实测回填;机票已通
|
|
203
204
|
- **界面语言** —— 英文仅覆盖求解确定性输出层;dsh 宿主界面与对话面属宿主/校准件
|
|
204
|
-
- **外部 benchmark 泛化** —— 迄今所有冻结外部运行均仅 diagnostic(无分数、无 uplift 声明)。
|
|
205
|
+
- **外部 benchmark 泛化** —— 迄今所有冻结外部运行均仅 diagnostic(无分数、无 uplift 声明)。Round 10 的 `glm-5.3-flash`(main `c843fae`)已诊断为可见性失败(57 次空 `{}` 调用);Round 11 仅把模型面对的 wire 展平为 `tools/call/errors`、descriptor 派生工具名枚举和 generic object 参数,执行时仍对冻结 descriptor 做 exact 校验;逐轮工程台账见 [`docs/evaluation/benchmark-environment-bridge.md`](docs/evaluation/benchmark-environment-bridge.md)
|
|
205
206
|
- **预订** —— 今天没有任何可下单路径;M5 只经 WriteGate 与 booking-saga 状态机启封
|
|
206
207
|
|
|
207
208
|
<details>
|
|
@@ -276,6 +277,16 @@ npx tsx scripts/evaluation-cadence-tests.ts # 确定性节奏策略/planner
|
|
|
276
277
|
|
|
277
278
|
**MIT**——与上游 dsh 一致。文本见 [LICENSE](LICENSE)。
|
|
278
279
|
|
|
280
|
+
## Star History
|
|
281
|
+
|
|
282
|
+
<a href="https://www.star-history.com/?repos=danceiny%2Fgotry&type=date&legend=top-left">
|
|
283
|
+
<picture>
|
|
284
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=danceiny/gotry&type=date&theme=dark&legend=top-left" />
|
|
285
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=danceiny/gotry&type=date&legend=top-left" />
|
|
286
|
+
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=danceiny/gotry&type=date&legend=top-left" />
|
|
287
|
+
</picture>
|
|
288
|
+
</a>
|
|
289
|
+
|
|
279
290
|
---
|
|
280
291
|
|
|
281
292
|
**Built with**: DeepSeek Harness 0.1.2-alpha.3 (root-pinned) · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 · OpenFlights · TypeScript
|
package/bin/gotry-bootstrap.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* gotry 外部依赖自举(founder 2026-08-29 指令:安装 gotry 时按上游本身的方式装好外部依赖):
|
|
4
|
-
* hbcli(hotelbyte-cli) →
|
|
4
|
+
* hbcli(hotelbyte-cli) → npm 优先(staicli@npmjs;Node ≥ 20),原生二进制 install.sh 兜底
|
|
5
5
|
* agent-reach → 官方 pip 安装 git+ upstream(包内 .venv,与 z3-solver 同址原则)
|
|
6
6
|
* flyai → 无需安装(npx 每次自拉 @fly-ai/flyai-cli)
|
|
7
7
|
* dsh-better-sidebar → dsh 宿主层插件市场组件(dshmarket.com #1 UI,18.9 万周装):
|
|
@@ -70,7 +70,9 @@ const WIZARD_DRY_RUN = process.argv.includes('--dry-run')
|
|
|
70
70
|
const EXT_FROM_ARG = (process.argv.find((a) => a.startsWith('--extension-from=')) ?? '').split('=')[1]
|
|
71
71
|
const EXTENSION_FROM = EXT_FROM_ARG === 'github' || EXT_FROM_ARG === 'bundled' ? EXT_FROM_ARG : process.env.GOTRY_EXTENSION_SOURCE === 'github' ? 'github' : 'bundled'
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
// hbcli 安装通道:npm 优先(staicli@npmjs 双轨;显式 registry 防 bnpm 404),install.sh 兜底
|
|
74
|
+
const HBCLI_NPM_INSTALL_CMD = 'npm install -g staicli --registry=https://registry.npmjs.org/'
|
|
75
|
+
const HBCLI_INSTALL_CMD = HBCLI_NPM_INSTALL_CMD
|
|
74
76
|
const REACH_INSTALL_URL = 'git+https://github.com/Panniantong/Agent-Reach.git'
|
|
75
77
|
|
|
76
78
|
/** 带超时的子进程(inherit stdio 让用户看见上游安装进度) */
|
|
@@ -99,20 +101,59 @@ function probe(cmd, args, timeoutMs = 10_000) {
|
|
|
99
101
|
|
|
100
102
|
const say = (s) => console.log(s)
|
|
101
103
|
|
|
104
|
+
/** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖)。
|
|
105
|
+
* 0.0.3 修了 portal-ticket fallback bug(issue #142):低于该版本的 hbcli
|
|
106
|
+
* 会把 literal "stored-ticket" 当 bearer 发到 hotel-be,让 trade.* /
|
|
107
|
+
* search/checkAvail 等 @permission: openapi 端点全部 401。 */
|
|
108
|
+
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3'
|
|
109
|
+
|
|
110
|
+
function parseVersion(v) {
|
|
111
|
+
const m = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/)
|
|
112
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function versionAtLeast(v, min) {
|
|
116
|
+
// v 可能是 hbcliVersion 已解析的数组,也可能是原始字符串——两者都接受
|
|
117
|
+
const a = Array.isArray(v) ? v : parseVersion(v); const b = parseVersion(min)
|
|
118
|
+
if (!a || !b) return false
|
|
119
|
+
for (let i = 0; i < 3; i++) { if (a[i] !== b[i]) return a[i] > b[i] }
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function hbcliVersion(bin) {
|
|
124
|
+
const cmd = bin === 'hbcli(PATH)' ? 'hbcli' : bin
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const child = spawn(cmd, ['--version'], { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
127
|
+
let out = ''
|
|
128
|
+
let done = false
|
|
129
|
+
const timer = setTimeout(() => { if (!done) { done = true; try { child.kill('SIGKILL') } catch { /* ignore */ } resolve(null) } }, 10_000)
|
|
130
|
+
child.on('error', () => { if (!done) { done = true; clearTimeout(timer); resolve(null) } })
|
|
131
|
+
child.stdout?.on('data', (d) => { out += d.toString() })
|
|
132
|
+
child.on('exit', (code) => { if (!done) { done = true; clearTimeout(timer); resolve(code === 0 ? parseVersion(out.trim()) : null) } })
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
102
136
|
async function setupHbcli() {
|
|
103
|
-
say(
|
|
137
|
+
say(`[gotry-setup] hbcli(hotelbyte-cli,可选酒店实时源;需 ≥ v${MIN_HBCLI_VERSION},低于会踩 issue #142 的 401)`)
|
|
104
138
|
const candidates = ['hbcli', join(homedir(), '.local/bin/hbcli'), join(homedir(), '.staicli/current/hbcli')]
|
|
105
139
|
const present = candidates.some((p) => existsSync(p)) && (await probe(candidates[0], ['version']) || await probe(candidates[1], ['version']) || await probe(candidates[2], ['version']))
|
|
106
140
|
const credFile = join(homedir(), '.staicli', 'credentials.json')
|
|
107
141
|
if (present) {
|
|
108
|
-
|
|
142
|
+
const hit = candidates.find((p) => existsSync(p)) || 'hbcli'
|
|
143
|
+
const v = await hbcliVersion(hit)
|
|
144
|
+
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
145
|
+
say(` ✗ 版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——@permission: openapi 端点(trade.* / search/checkAvail)会 401(issue #142)`)
|
|
146
|
+
say(` 升级: npm install -g staicli --registry=https://registry.npmjs.org/`)
|
|
147
|
+
return { ok: CHECK_ONLY ? true : false }
|
|
148
|
+
}
|
|
149
|
+
say(` ✓ 已安装(v${v ? v.join('.') : '?'})`)
|
|
109
150
|
if (existsSync(credFile)) say(' ✓ 凭证已配置(自检: hbcli auth whoami)')
|
|
110
151
|
else say(' ✗ 凭证未配置——酒店检索将用内置静态包(非实时),账号配置见下方指引')
|
|
111
152
|
if (CHECK_ONLY) say(' (--check-only 只报告,不安装)')
|
|
112
153
|
return { ok: true }
|
|
113
154
|
}
|
|
114
155
|
if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
|
|
115
|
-
say(` 安装中(
|
|
156
|
+
say(` 安装中(npm,staicli@npmjs): ${HBCLI_INSTALL_CMD}`)
|
|
116
157
|
const r = await run('bash', ['-c', HBCLI_INSTALL_CMD], { timeoutMs: 120_000 })
|
|
117
158
|
if (!r.ok) { say(` ✗ 安装失败(${r.error})——不影响 gotry,酒店检索将用内置静态包;可稍后重试: npx gotry setup`); return { ok: false } }
|
|
118
159
|
const binDir = join(homedir(), '.local/bin')
|
|
@@ -278,14 +319,19 @@ async function doctorChecks() {
|
|
|
278
319
|
const reachOk = existsSync(reachBin)
|
|
279
320
|
const reachLevel = reachOk ? 'ok' : existsSync(venvPython) ? 'degraded' : 'missing'
|
|
280
321
|
items.push({ label: 'Agent Reach(网页/社媒读取)', ok: reachOk, level: reachLevel, detail: reachOk ? `已安装(${reachBin})` : reachLevel === 'degraded' ? '.venv 在但缺 agent-reach 包——gotry_agent_reach / gotry_web_search 读页会失败' : '未装配——gotry_agent_reach / gotry_web_search(读网页)/ gotry_video_subtitle / gotry_github_search 全部不可用', fix: reachOk ? undefined : 'npx gotry doctor --fix' })
|
|
281
|
-
// hbcli(裸名靠 PATH 探测;绝对路径 existsSync)
|
|
322
|
+
// hbcli(裸名靠 PATH 探测;绝对路径 existsSync;版本 < MIN_HBCLI_VERSION 视为 missing,见 issue #142)
|
|
282
323
|
let hbBin = ''
|
|
283
324
|
for (const p of ['hbcli', join(homedir(), '.local/bin/hbcli'), join(homedir(), '.staicli/current/hbcli')]) {
|
|
284
325
|
if (p === 'hbcli') { if (await probe(p, ['version'])) { hbBin = 'hbcli(PATH)'; break } } else if (existsSync(p)) { hbBin = p; break }
|
|
285
326
|
}
|
|
286
327
|
if (hbBin) {
|
|
287
|
-
const
|
|
288
|
-
|
|
328
|
+
const v = await hbcliVersion(hbBin)
|
|
329
|
+
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
330
|
+
items.push({ label: 'hbcli(酒店实时源)', ok: false, level: 'missing', detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——trade.* / search/checkAvail 会 401(issue #142)`, fix: 'npm install -g staicli --registry=https://registry.npmjs.org/' })
|
|
331
|
+
} else {
|
|
332
|
+
const whoami = await probe(hbBin === 'hbcli(PATH)' ? 'hbcli' : hbBin, ['auth', 'whoami'])
|
|
333
|
+
items.push({ label: 'hbcli(酒店实时源)', ok: whoami, level: whoami ? 'ok' : 'degraded', detail: whoami ? `已安装且凭证有效(${hbBin}, v${v ? v.join('.') : '?'})` : '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)', fix: whoami ? undefined : 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)' })
|
|
334
|
+
}
|
|
289
335
|
} else {
|
|
290
336
|
items.push({ label: 'hbcli(酒店实时源)', ok: false, level: 'missing', detail: '未安装——酒店检索降级静态包(公开渠道估算,非实时,仅覆盖内置场景)', fix: 'npx gotry doctor --fix' })
|
|
291
337
|
}
|
package/cordis.gotry-patch.yml
CHANGED
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
# {ask-user-insert} ← inner 运行时填充(dsh-user-questions 服务 + ask_user_question
|
|
18
18
|
# 工具,结构化澄清卡);解析失败整块剔除
|
|
19
19
|
|
|
20
|
-
# 注:insert 里 dsh-map-tools 的 name 是占位符,bin/gotry-inner.js
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
20
|
+
# 注:insert 里 dsh-map-tools 的 name 是占位符,bin/gotry-inner.js 运行时优先重写为
|
|
21
|
+
# 随包 ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js 的绝对路径(source/package
|
|
22
|
+
# 均可用;旧 source/npm 依赖路径仅作兼容回退)。payload 缺失时该条目整块剔除,
|
|
23
|
+
# 不挡启动。地图/路线/POI 工具,零 key 走 OSM/OSRM。
|
|
24
24
|
|
|
25
25
|
# GoTry 成品人格:行为契约=产品语义
|
|
26
26
|
- id: system-prompt
|
|
@@ -52,7 +52,9 @@
|
|
|
52
52
|
直接点明(如「现在正是雨季」而非把当前月份当成远期选项);用户给的绝对日期
|
|
53
53
|
早于今天时,主动点明已过并请用户裁决(笔误还是指明年),不静默顺延;
|
|
54
54
|
周/月/节日表述一律按锚点卡换算,不自算;用户说「在 X 年内完成」时,
|
|
55
|
-
|
|
55
|
+
只推荐今天之后的时段,今天之前的一律排除;
|
|
56
|
+
澄清卡/访谈/选项里向用户列出的候选时段示例(节日/周末/月份)同样只取
|
|
57
|
+
今天之后的——已过的节日不进示例枚举,不拿过去节日当「想用的时候」的候选。
|
|
56
58
|
(9)年界断言:「元旦/新年/春节」先断年再谈——今天是 {{current_date}},
|
|
57
59
|
「2026 年内」不含 2027-01-01 元旦;农历节日先换算公历日期再判断归属年份。
|
|
58
60
|
(10)不重复问:预算/日期/窗口/出发地等已问过或用户已给的信息,会话内
|
|
@@ -119,3 +121,8 @@
|
|
|
119
121
|
(22)到达账必达:红眼/凌晨起飞、或落地当天有硬安排(上班/办事)的航段,解释方案时必须
|
|
120
122
|
显式给出到达账——当地到达时刻(含日期偏移)、时差、到达精力与落地后可用时长,并主动给
|
|
121
123
|
前一晚落脚建议;时刻表正确不等于行程可行。
|
|
124
|
+
(23)子任务等待纪律:派发后台子任务(subagent)后,它的回执 id 是子代理会话 id,
|
|
125
|
+
不是 job_output/job_kill 的后台 job id——对子代理绝不调用 job_output/job_kill
|
|
126
|
+
轮询或停止(会撞 unknown job 硬错误);子任务完成通知会自动送达当前会话,耐心做
|
|
127
|
+
别的独立步骤;要给子代理追加输入用 send_message。job_output/job_kill 只用于
|
|
128
|
+
回执明写「background job」的后台任务。
|
|
@@ -43,10 +43,68 @@ function probe(bin, args, timeoutMs = 8_000) {
|
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
+
function probeStdout(bin, args, timeoutMs = 8_000) {
|
|
47
|
+
return new Promise((resolveProbe)=>{
|
|
48
|
+
const child = spawn(bin, args, {
|
|
49
|
+
stdio: [
|
|
50
|
+
'ignore',
|
|
51
|
+
'pipe',
|
|
52
|
+
'ignore'
|
|
53
|
+
],
|
|
54
|
+
env: process.env
|
|
55
|
+
});
|
|
56
|
+
let out = '';
|
|
57
|
+
let done = false;
|
|
58
|
+
const timer = setTimeout(()=>{
|
|
59
|
+
if (!done) {
|
|
60
|
+
done = true;
|
|
61
|
+
try {
|
|
62
|
+
child.kill('SIGKILL');
|
|
63
|
+
} catch {}
|
|
64
|
+
resolveProbe(null);
|
|
65
|
+
}
|
|
66
|
+
}, timeoutMs);
|
|
67
|
+
child.on('error', ()=>{
|
|
68
|
+
if (!done) {
|
|
69
|
+
done = true;
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
resolveProbe(null);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
child.stdout?.on('data', (d)=>{
|
|
75
|
+
out += d.toString();
|
|
76
|
+
});
|
|
77
|
+
child.on('exit', (code)=>{
|
|
78
|
+
if (!done) {
|
|
79
|
+
done = true;
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolveProbe(code === 0 ? out.trim() : null);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function parseVersion(v) {
|
|
87
|
+
const m = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
88
|
+
return m ? [
|
|
89
|
+
Number(m[1]),
|
|
90
|
+
Number(m[2]),
|
|
91
|
+
Number(m[3])
|
|
92
|
+
] : null;
|
|
93
|
+
}
|
|
94
|
+
function versionAtLeast(v, min) {
|
|
95
|
+
const a = v;
|
|
96
|
+
const b = parseVersion(min);
|
|
97
|
+
if (!a || !b) return false;
|
|
98
|
+
for(let i = 0; i < 3; i++){
|
|
99
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
46
103
|
export function nodeOk(version) {
|
|
47
104
|
const [maj = '0', min = '0'] = version.split('.');
|
|
48
105
|
return Number(maj) > 22 || Number(maj) === 22 && Number(min) >= 15;
|
|
49
106
|
}
|
|
107
|
+
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3';
|
|
50
108
|
export async function runDoctorChecks(opts = {}) {
|
|
51
109
|
const repoRoot = opts.repoRoot ? resolve(opts.repoRoot) : resolve(import.meta.dirname, '..', '..');
|
|
52
110
|
const home = opts.homeDir ?? homedir();
|
|
@@ -118,22 +176,36 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
118
176
|
}
|
|
119
177
|
}
|
|
120
178
|
if (hbPresent) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
'
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
179
|
+
const hbCmd = hbPresent === 'hbcli(PATH)' ? 'hbcli' : hbPresent;
|
|
180
|
+
const v = parseVersion(await probeStdout(hbCmd, [
|
|
181
|
+
'--version'
|
|
182
|
+
]));
|
|
183
|
+
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
184
|
+
items.push({
|
|
185
|
+
id: 'hbcli',
|
|
186
|
+
label: 'hbcli(酒店实时源)',
|
|
187
|
+
status: 'missing',
|
|
188
|
+
detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——trade.* / search/checkAvail 会 401(issue #142)`,
|
|
189
|
+
fix: 'npm install -g staicli --registry=https://registry.npmjs.org/'
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
const whoami = await probe(hbCmd, [
|
|
193
|
+
'auth',
|
|
194
|
+
'whoami'
|
|
195
|
+
]);
|
|
196
|
+
items.push(whoami ? {
|
|
197
|
+
id: 'hbcli',
|
|
198
|
+
label: 'hbcli(酒店实时源)',
|
|
199
|
+
status: 'ok',
|
|
200
|
+
detail: `已安装且凭证有效(${hbPresent}, v${v ? v.join('.') : '?'})`
|
|
201
|
+
} : {
|
|
202
|
+
id: 'hbcli',
|
|
203
|
+
label: 'hbcli(酒店实时源)',
|
|
204
|
+
status: 'degraded',
|
|
205
|
+
detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
206
|
+
fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
137
209
|
} else {
|
|
138
210
|
items.push({
|
|
139
211
|
id: 'hbcli',
|
|
@@ -73,10 +73,17 @@ export function startExtensionHealthWatch(opts = {}) {
|
|
|
73
73
|
const businessProbe = opts.businessProbe ?? true;
|
|
74
74
|
const probe = opts.probe ?? (async ()=>{
|
|
75
75
|
const bridge = await import('./extension-bridge.js');
|
|
76
|
-
await bridge.getOrCreateSessionBridge({
|
|
77
|
-
keepBridge: opts.keepBridge === true
|
|
76
|
+
const created = await bridge.getOrCreateSessionBridge({
|
|
77
|
+
keepBridge: opts.keepBridge === true,
|
|
78
|
+
ports: opts.ports
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
const probeOpts = created.ok && opts.ports ? {
|
|
81
|
+
...opts,
|
|
82
|
+
ports: [
|
|
83
|
+
created.bridge.port
|
|
84
|
+
]
|
|
85
|
+
} : opts;
|
|
86
|
+
return combinedProbe(probeOpts, businessProbe);
|
|
80
87
|
});
|
|
81
88
|
const now = opts.now ?? Date.now;
|
|
82
89
|
const state = {
|
|
@@ -251,6 +251,20 @@ function toolResultPresent(body) {
|
|
|
251
251
|
function anyToolResultPresent(body) {
|
|
252
252
|
return (body.messages ?? []).some((m)=>m.role === 'tool');
|
|
253
253
|
}
|
|
254
|
+
const BRIDGE_E2E_BODY_SCHEMA = {
|
|
255
|
+
type: 'object',
|
|
256
|
+
properties: {
|
|
257
|
+
status: {
|
|
258
|
+
type: 'string'
|
|
259
|
+
},
|
|
260
|
+
payload: {
|
|
261
|
+
type: 'string'
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
required: [],
|
|
265
|
+
additionalProperties: false
|
|
266
|
+
};
|
|
267
|
+
const TERMINAL_OUTLINE = 'object{?status:string,?payload:string}';
|
|
254
268
|
async function runCase(mode, executableOverride, extraEnv = {}) {
|
|
255
269
|
const requests = [];
|
|
256
270
|
let servedToolCalls = 0;
|
|
@@ -303,7 +317,7 @@ async function runCase(mode, executableOverride, extraEnv = {}) {
|
|
|
303
317
|
mode: 0o700
|
|
304
318
|
});
|
|
305
319
|
writeFileSync(configPath, JSON.stringify({
|
|
306
|
-
schema_version: mode === 'invalid-schema' ? 'invalid' : '
|
|
320
|
+
schema_version: mode === 'invalid-schema' ? 'invalid' : 'gotry_benchmark_environment_bridge_v4',
|
|
307
321
|
enabled: true,
|
|
308
322
|
executable: mode === 'spawn-failed' ? spawnTarget : process.execPath,
|
|
309
323
|
cwd,
|
|
@@ -334,7 +348,8 @@ async function runCase(mode, executableOverride, extraEnv = {}) {
|
|
|
334
348
|
max_output_bytes: mode === 'output-truncated' ? 1_024 : 4_096,
|
|
335
349
|
terminal_output: {
|
|
336
350
|
tag: 'benchmark_terminal',
|
|
337
|
-
max_bytes: 4_096
|
|
351
|
+
max_bytes: 4_096,
|
|
352
|
+
body_schema: BRIDGE_E2E_BODY_SCHEMA
|
|
338
353
|
},
|
|
339
354
|
isolation: {
|
|
340
355
|
mode: 'host-enforced',
|
|
@@ -481,15 +496,25 @@ async function assertRuntimeContract(executableOverride) {
|
|
|
481
496
|
]
|
|
482
497
|
}).includes(TOOL));
|
|
483
498
|
const flatSchema = bridgeTool?.function?.parameters;
|
|
484
|
-
assert.
|
|
485
|
-
|
|
486
|
-
assert.deepEqual(
|
|
487
|
-
'action'
|
|
488
|
-
|
|
489
|
-
|
|
499
|
+
assert.equal(flatSchema?.type, 'object', `${target} bridge exposes an object-root wire schema`);
|
|
500
|
+
assert.equal(flatSchema?.oneOf, undefined, `${target} bridge wire has no top-level oneOf`);
|
|
501
|
+
assert.deepEqual(flatSchema?.required, [
|
|
502
|
+
'action'
|
|
503
|
+
]);
|
|
504
|
+
assert.equal(flatSchema?.additionalProperties, false);
|
|
505
|
+
assert.deepEqual(flatSchema?.properties?.action?.enum, [
|
|
506
|
+
'tools',
|
|
507
|
+
'call',
|
|
508
|
+
'errors'
|
|
509
|
+
]);
|
|
510
|
+
assert.deepEqual(flatSchema?.properties?.tool?.enum, [
|
|
511
|
+
'lookup'
|
|
490
512
|
]);
|
|
491
|
-
assert.
|
|
492
|
-
|
|
513
|
+
assert.deepEqual(flatSchema?.properties?.arguments, {
|
|
514
|
+
type: 'object',
|
|
515
|
+
additionalProperties: true,
|
|
516
|
+
description: 'action=call 时传给工具的参数对象'
|
|
517
|
+
});
|
|
493
518
|
assert.equal(enabledToolNames.some((name)=>name.startsWith('calendar_') || name.startsWith('map_')), false, `${target} benchmark projection must not expose calendar/map tools`);
|
|
494
519
|
assert.deepEqual(enabled.optionalResolutionHits, {
|
|
495
520
|
calendar: 0,
|
|
@@ -745,7 +770,8 @@ function conformanceResponse(mode, request, plannerCount) {
|
|
|
745
770
|
'b',
|
|
746
771
|
'd',
|
|
747
772
|
'f',
|
|
748
|
-
'large'
|
|
773
|
+
'large',
|
|
774
|
+
'schema'
|
|
749
775
|
].includes(mode) && plannerCount === 1;
|
|
750
776
|
if (call && !hasToolResult) return toolCall();
|
|
751
777
|
if (mode === 'f' && plannerCount === 3) return toolCall('bridge-call-retry');
|
|
@@ -754,6 +780,9 @@ function conformanceResponse(mode, request, plannerCount) {
|
|
|
754
780
|
payload: LARGE_TERMINAL_PAYLOAD
|
|
755
781
|
})}</benchmark_terminal>`);
|
|
756
782
|
}
|
|
783
|
+
if (mode === 'schema') {
|
|
784
|
+
return finalText('<benchmark_terminal>{"status":"succeeded","budget":{"total_cost":1}}</benchmark_terminal>');
|
|
785
|
+
}
|
|
757
786
|
const valid = mode === 'a' ? hasToolResult : mode === 'b' ? plannerCount >= 3 : mode === 'e' ? true : false;
|
|
758
787
|
return finalText(mode === 'c' || mode === 'd' ? 'bad benchmark body' : taggedTerminal(valid));
|
|
759
788
|
}
|
|
@@ -815,7 +844,7 @@ async function runConformanceCase(mode, executableOverride) {
|
|
|
815
844
|
const configPath = join(cwd, 'benchmark-env-config.json');
|
|
816
845
|
writeFileSync(runner, `const fs = require('node:fs'); const path = ${JSON.stringify(runnerCount)}; const count = fs.existsSync(path) ? Number(fs.readFileSync(path, 'utf8')) : 0; fs.writeFileSync(path, String(count + 1)); process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'ok', result: { marker: '${MARKER}' } }))`);
|
|
817
846
|
writeFileSync(configPath, JSON.stringify({
|
|
818
|
-
schema_version: '
|
|
847
|
+
schema_version: 'gotry_benchmark_environment_bridge_v4',
|
|
819
848
|
enabled: true,
|
|
820
849
|
executable: process.execPath,
|
|
821
850
|
cwd,
|
|
@@ -843,7 +872,8 @@ async function runConformanceCase(mode, executableOverride) {
|
|
|
843
872
|
max_output_bytes: 4_096,
|
|
844
873
|
terminal_output: {
|
|
845
874
|
tag: 'benchmark_terminal',
|
|
846
|
-
max_bytes: mode === 'large' ? 128 * 1024 : 4_096
|
|
875
|
+
max_bytes: mode === 'large' ? 128 * 1024 : 4_096,
|
|
876
|
+
body_schema: BRIDGE_E2E_BODY_SCHEMA
|
|
847
877
|
},
|
|
848
878
|
isolation: {
|
|
849
879
|
mode: 'host-enforced',
|
|
@@ -981,6 +1011,7 @@ async function assertOutputConformance(executableOverride) {
|
|
|
981
1011
|
assert.equal(a.servedToolCalls, 1, 'A exposes exactly one bridge call');
|
|
982
1012
|
assert.equal(a.runnerInvocations, 1, 'A executes the bridge subprocess exactly once');
|
|
983
1013
|
assert.ok(a.stdout.includes('<benchmark_terminal>'), 'A forwards only tagged terminal output');
|
|
1014
|
+
assert.ok(a.requests.some((request)=>JSON.stringify(request).includes(`matching exactly ${TERMINAL_OUTLINE}`)), 'A system prompt projects the exact terminal schema outline');
|
|
984
1015
|
const b = await runConformanceCase('b', executableOverride);
|
|
985
1016
|
assert.equal(b.exit, 0, 'B malformed terminal correction then valid terminal exits 0');
|
|
986
1017
|
assert.equal(b.servedToolCalls, 1, `B exposes exactly one bridge call; request shapes=${JSON.stringify(b.requests.map((request)=>({
|
|
@@ -988,6 +1019,12 @@ async function assertOutputConformance(executableOverride) {
|
|
|
988
1019
|
roles: (request.messages ?? []).map((message)=>message.role)
|
|
989
1020
|
})))}`);
|
|
990
1021
|
assert.equal(b.runnerInvocations, 1, 'B format-only correction does not rerun the bridge subprocess');
|
|
1022
|
+
assert.ok(b.requests.some((request)=>JSON.stringify(request).includes('BENCHMARK_CONFORMANCE_TERMINAL') && JSON.stringify(request).includes(`matching exactly ${TERMINAL_OUTLINE}`)), 'B terminal correction projects the same schema outline as the system prompt');
|
|
1023
|
+
const schema = await runConformanceCase('schema', executableOverride);
|
|
1024
|
+
assert.notEqual(schema.exit, 0, 'schema-invalid terminal body (extra root key) is rejected after the single correction');
|
|
1025
|
+
assert.match(schema.stderr, /benchmark terminal output unavailable \(child_conformance_failure\)/, 'schema-invalid terminal emits the stable conformance reason code');
|
|
1026
|
+
assert.equal(schema.runnerInvocations, 1, 'schema-invalid terminal still executed the bridge exactly once');
|
|
1027
|
+
assert.equal(schema.stdout.includes('<benchmark_terminal>'), false, 'schema-invalid terminal body is never released to stdout');
|
|
991
1028
|
for (const mode of [
|
|
992
1029
|
'c',
|
|
993
1030
|
'd'
|
|
@@ -1032,7 +1069,7 @@ if (packaged) {
|
|
|
1032
1069
|
try {
|
|
1033
1070
|
const configPath = join(missingServiceRoot, 'bridge.json');
|
|
1034
1071
|
writeFileSync(configPath, JSON.stringify({
|
|
1035
|
-
schema_version: '
|
|
1072
|
+
schema_version: 'gotry_benchmark_environment_bridge_v4',
|
|
1036
1073
|
enabled: true,
|
|
1037
1074
|
executable: process.execPath,
|
|
1038
1075
|
cwd: missingServiceRoot,
|
|
@@ -1061,7 +1098,8 @@ if (packaged) {
|
|
|
1061
1098
|
max_output_bytes: 4_096,
|
|
1062
1099
|
terminal_output: {
|
|
1063
1100
|
tag: 'benchmark_terminal',
|
|
1064
|
-
max_bytes: 4_096
|
|
1101
|
+
max_bytes: 4_096,
|
|
1102
|
+
body_schema: BRIDGE_E2E_BODY_SCHEMA
|
|
1065
1103
|
},
|
|
1066
1104
|
isolation: {
|
|
1067
1105
|
mode: 'host-enforced',
|