@danceiny/gotry 0.2.0-rc.31 → 0.2.0-rc.32
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 +63 -53
- package/bin/gotry-hbcli-release.d.ts +3 -0
- package/bin/gotry-hbcli-release.js +4 -0
- package/client/client.js +66 -21
- package/dist/capabilities/doctor.js +7 -8
- package/dist/capabilities/effect.js +45 -12
- package/dist/capabilities/flyai-config.js +12 -10
- package/dist/capabilities/flyai.js +87 -56
- package/dist/capabilities/hbcli-config.js +156 -0
- package/dist/capabilities/hbcli.js +29 -2
- package/dist/capabilities/resilience.js +6 -6
- package/dist/capabilities/session/adapters/ctrip-flight.js +42 -7
- package/dist/capabilities/session/adapters/ctrip-hotel.js +52 -3
- package/dist/capabilities/session/ctrip-city.js +186 -0
- package/dist/capabilities/session/extension-bridge.js +8 -1
- package/dist/capabilities/session/gui-flight.js +737 -0
- package/dist/capabilities/session/gui-hotel.js +413 -0
- package/dist/capabilities/session-search.js +98 -11
- package/dist/scripts/bootstrap-tests.js +23 -6
- package/dist/scripts/ctrip-city-tests.js +80 -0
- package/dist/scripts/effect-tests.js +602 -504
- package/dist/scripts/extension-tests.js +14 -8
- package/dist/scripts/flight-malformed-tests.js +1 -1
- package/dist/scripts/flyai-setup-tests.js +19 -4
- package/dist/scripts/flyai-tests.js +768 -653
- package/dist/scripts/gotry-web-api-tests.js +5 -0
- package/dist/scripts/gotry-web-client-tests.js +20 -1
- package/dist/scripts/gui-flight-live.js +27 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/hbcli-upgrade-tests.js +107 -0
- package/dist/scripts/hbcli-web-setup-tests.js +114 -0
- package/dist/scripts/hotel-date-gate-tests.js +1 -1
- package/dist/scripts/hotel-result-tests.js +169 -0
- package/dist/scripts/issue-271-liveness-tests.js +1 -1
- package/dist/scripts/session-tests.js +177 -6
- package/dist/src/gotry-web-api.js +64 -2
- package/dist/src/index.js +40 -18
- package/extension/manifest.json +2 -2
- package/package.json +2 -1
- package/ts/capabilities/flyai-config.ts +12 -10
- package/ts/capabilities/flyai.ts +91 -50
- package/ts/capabilities/hbcli-config.ts +128 -0
- package/ts/capabilities/hbcli.ts +30 -3
- package/ts/capabilities/session/adapters/ctrip-flight.ts +43 -7
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +51 -4
- package/ts/capabilities/session/ctrip-city.ts +158 -0
- package/ts/capabilities/session/extension-bridge.ts +9 -2
- package/ts/capabilities/session/gui-flight.ts +792 -0
- package/ts/capabilities/session/gui-hotel.ts +451 -0
- package/ts/capabilities/session-search.ts +115 -15
- package/ts/src/gotry-web-api.ts +51 -2
- package/ts/src/index.ts +41 -19
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.31** 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.31` (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.31**。未到 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.31`(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
|
@@ -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) → 官方版本钉扎的原生 install.sh(npm 发布暂落后)
|
|
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 万周装):
|
|
@@ -62,6 +62,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
62
62
|
import { createRequire } from 'node:module'
|
|
63
63
|
import { createHash } from 'node:crypto'
|
|
64
64
|
import { checkExtensionStatus } from './gotry-extension-status.js'
|
|
65
|
+
import { HBCLI_RELEASE_VERSION, HBCLI_INSTALL_CMD } from './gotry-hbcli-release.js'
|
|
65
66
|
|
|
66
67
|
// ── FlyAI 凭据面(纯 JS 内联;与 ts/capabilities/flyai-config.ts 成对维护,
|
|
67
68
|
// 口径以官方 @fly-ai/flyai-cli 1.0.16 解包事实为准。bootstrap 由 inner 以
|
|
@@ -164,24 +165,24 @@ function verifySecureModeInline(dir, file) {
|
|
|
164
165
|
|
|
165
166
|
/**
|
|
166
167
|
* 官方 CLI 首次运行可能先创建 ~/.flyai 为 0755(并写入 0600 device-id)。
|
|
167
|
-
* 仅对当前用户拥有、且本身不是 symlink
|
|
168
|
-
*
|
|
168
|
+
* 仅对当前用户拥有、且本身不是 symlink 的目录收紧权限。
|
|
169
|
+
* 回执所在 .gotry 同样兼容 doctor/runtime 创建的既有 0755 目录。
|
|
169
170
|
*/
|
|
170
|
-
function
|
|
171
|
-
const dir = dirname(
|
|
171
|
+
function preparePrivateDirInline(file = flyaiConfigFile()) {
|
|
172
|
+
const dir = dirname(file)
|
|
172
173
|
try {
|
|
173
174
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 })
|
|
174
175
|
const entry = lstatSync(dir)
|
|
175
|
-
if (entry.isSymbolicLink()) return { ok: false, error: '
|
|
176
|
-
if (!entry.isDirectory()) return { ok: false, error: '
|
|
176
|
+
if (entry.isSymbolicLink()) return { ok: false, error: '配置目录是符号链接,拒绝写入' }
|
|
177
|
+
if (!entry.isDirectory()) return { ok: false, error: '配置路径不是目录,拒绝写入' }
|
|
177
178
|
if (typeof process.getuid === 'function' && entry.uid !== process.getuid()) {
|
|
178
|
-
return { ok: false, error: '
|
|
179
|
+
return { ok: false, error: '配置目录不属于当前用户,拒绝写入' }
|
|
179
180
|
}
|
|
180
181
|
if ((entry.mode & 0o777) !== 0o700) chmodSync(dir, 0o700)
|
|
181
182
|
const after = lstatSync(dir)
|
|
182
|
-
if (after.isSymbolicLink() || !after.isDirectory()) return { ok: false, error: '
|
|
183
|
-
if (typeof process.getuid === 'function' && after.uid !== process.getuid()) return { ok: false, error: '
|
|
184
|
-
return verifySecureModeInline(dir,
|
|
183
|
+
if (after.isSymbolicLink() || !after.isDirectory()) return { ok: false, error: '配置目录状态发生变化,拒绝写入' }
|
|
184
|
+
if (typeof process.getuid === 'function' && after.uid !== process.getuid()) return { ok: false, error: '配置目录所有者发生变化,拒绝写入' }
|
|
185
|
+
return verifySecureModeInline(dir, file)
|
|
185
186
|
} catch (e) {
|
|
186
187
|
return { ok: false, error: e.message }
|
|
187
188
|
}
|
|
@@ -291,7 +292,7 @@ async function runSetupFlyai() {
|
|
|
291
292
|
if (Object.keys(next).length === 0) {
|
|
292
293
|
rmSync(flyaiConfigFile(), { force: true })
|
|
293
294
|
} else {
|
|
294
|
-
const configDir =
|
|
295
|
+
const configDir = preparePrivateDirInline()
|
|
295
296
|
if (!configDir.ok) { say(`[gotry-setup] 清除写入失败,旧配置保留:${configDir.error}`); return 1 }
|
|
296
297
|
const w = atomicWriteJsonInline(flyaiConfigFile(), next)
|
|
297
298
|
if (!w.ok) { say(`[gotry-setup] 清除写入失败,旧配置保留:${w.error}`); return 1 }
|
|
@@ -349,7 +350,7 @@ async function runSetupFlyai() {
|
|
|
349
350
|
return 1
|
|
350
351
|
}
|
|
351
352
|
const hadConfigFile = existsSync(flyaiConfigFile())
|
|
352
|
-
const configDir =
|
|
353
|
+
const configDir = preparePrivateDirInline()
|
|
353
354
|
if (!configDir.ok) {
|
|
354
355
|
say(`[gotry-setup] ✗ 配置目录不安全,旧配置保留:${configDir.error}`)
|
|
355
356
|
return 1
|
|
@@ -380,7 +381,8 @@ async function runSetupFlyai() {
|
|
|
380
381
|
endpointDebug: epAfter.debug,
|
|
381
382
|
at: new Date().toISOString(),
|
|
382
383
|
}
|
|
383
|
-
const
|
|
384
|
+
const receiptDir = preparePrivateDirInline(flyaiVerificationFile())
|
|
385
|
+
const receiptWrite = receiptDir.ok ? atomicWriteJsonInline(flyaiVerificationFile(), receipt) : receiptDir
|
|
384
386
|
if (!receiptWrite.ok) {
|
|
385
387
|
const rollback = hadConfigFile
|
|
386
388
|
? atomicWriteJsonInline(flyaiConfigFile(), before.data)
|
|
@@ -480,9 +482,6 @@ const WIZARD_DRY_RUN = process.argv.includes('--dry-run')
|
|
|
480
482
|
const EXT_FROM_ARG = (process.argv.find((a) => a.startsWith('--extension-from=')) ?? '').split('=')[1]
|
|
481
483
|
const EXTENSION_FROM = EXT_FROM_ARG === 'github' || EXT_FROM_ARG === 'bundled' ? EXT_FROM_ARG : process.env.GOTRY_EXTENSION_SOURCE === 'github' ? 'github' : 'bundled'
|
|
482
484
|
|
|
483
|
-
// hbcli 安装通道:npm 优先(staicli@npmjs 双轨;显式 registry 防 bnpm 404),install.sh 兜底
|
|
484
|
-
const HBCLI_NPM_INSTALL_CMD = 'npm install -g staicli --registry=https://registry.npmjs.org/'
|
|
485
|
-
const HBCLI_INSTALL_CMD = HBCLI_NPM_INSTALL_CMD
|
|
486
485
|
const REACH_INSTALL_URL = 'git+https://github.com/Panniantong/Agent-Reach.git'
|
|
487
486
|
|
|
488
487
|
let activeInstallerChild = null
|
|
@@ -631,11 +630,8 @@ function classifyHbcliWhoami(out) {
|
|
|
631
630
|
|
|
632
631
|
const say = (s) => console.log(s)
|
|
633
632
|
|
|
634
|
-
/** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖)
|
|
635
|
-
|
|
636
|
-
* 会把 literal "stored-ticket" 当 bearer 发到 hotel-be,让 trade.* /
|
|
637
|
-
* search/checkAvail 等 @permission: openapi 端点全部 401。 */
|
|
638
|
-
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3'
|
|
633
|
+
/** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖),与工具层 doctor 共用发布基线。 */
|
|
634
|
+
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? HBCLI_RELEASE_VERSION
|
|
639
635
|
|
|
640
636
|
function parseVersion(v) {
|
|
641
637
|
const m = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/)
|
|
@@ -663,38 +659,53 @@ async function hbcliVersion(bin) {
|
|
|
663
659
|
})
|
|
664
660
|
}
|
|
665
661
|
|
|
666
|
-
async function setupHbcli() {
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
say(` 升级: npm install -g staicli --registry=https://registry.npmjs.org/`)
|
|
677
|
-
return { ok: CHECK_ONLY ? true : false }
|
|
662
|
+
async function setupHbcli(opts = {}) {
|
|
663
|
+
const home = opts.homeDir ?? homedir()
|
|
664
|
+
const staicliHome = opts.homeDir ? join(home, '.staicli') : process.env.STAICLI_HOME ?? join(home, '.staicli')
|
|
665
|
+
const checkOnly = opts.checkOnly ?? CHECK_ONLY
|
|
666
|
+
say(`[gotry-setup] hbcli(hotelbyte-cli,可选酒店实时源;需 ≥ v${MIN_HBCLI_VERSION})`)
|
|
667
|
+
const candidates = ['hbcli', join(home, '.local/bin/hbcli'), join(home, '.staicli/current/hbcli')]
|
|
668
|
+
const findInstalled = async () => {
|
|
669
|
+
for (const bin of candidates) {
|
|
670
|
+
const version = await hbcliVersion(bin)
|
|
671
|
+
if (version) return { bin, version }
|
|
678
672
|
}
|
|
679
|
-
|
|
673
|
+
return null
|
|
674
|
+
}
|
|
675
|
+
const present = await findInstalled()
|
|
676
|
+
const credFile = join(staicliHome, 'credentials.json')
|
|
677
|
+
if (present && versionAtLeast(present.version, MIN_HBCLI_VERSION)) {
|
|
678
|
+
say(` ✓ 已安装(v${present.version.join('.')})`)
|
|
680
679
|
if (existsSync(credFile)) say(' ✓ 凭证已配置(自检: hbcli auth whoami)')
|
|
681
680
|
else say(' ✗ 凭证未配置——酒店检索将用内置静态包(非实时),账号配置见下方指引')
|
|
682
|
-
if (
|
|
681
|
+
if (checkOnly) say(' (--check-only 只报告,不安装)')
|
|
683
682
|
return { ok: true }
|
|
684
683
|
}
|
|
685
|
-
if (
|
|
686
|
-
say(
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
684
|
+
if (present) say(` ✗ 版本过旧(v${present.version.join('.')} < v${MIN_HBCLI_VERSION})`)
|
|
685
|
+
else say(' ✗ 未安装或无法读取版本')
|
|
686
|
+
if (checkOnly) { say(' (--check-only 只报告,不安装)'); return { ok: true } }
|
|
687
|
+
say(` ${present ? '升级' : '安装'}中(官方 staicli v${HBCLI_RELEASE_VERSION}): ${HBCLI_INSTALL_CMD}`)
|
|
688
|
+
const attemptInstall = opts.attemptInstall ?? ((command) => run('bash', ['-o', 'pipefail', '-c', command], {
|
|
689
|
+
timeoutMs: 120_000,
|
|
690
|
+
env: { ...process.env, HOME: home, STAICLI_HOME: staicliHome },
|
|
691
|
+
}))
|
|
692
|
+
const r = await attemptInstall(HBCLI_INSTALL_CMD)
|
|
693
|
+
if (!r.ok) { say(` ✗ 安装失败(${r.error})——不影响 gotry,酒店检索将用内置静态包;可稍后重试: npx @danceiny/gotry doctor --fix`); return { ok: false, error: r.error } }
|
|
694
|
+
const installed = await findInstalled()
|
|
695
|
+
if (!installed || !versionAtLeast(installed.version, MIN_HBCLI_VERSION)) {
|
|
696
|
+
const error = `安装后当前候选路径仍未找到 hbcli ≥ v${MIN_HBCLI_VERSION};检查 PATH 是否优先命中了旧版,以及 ${join(home, '.local/bin/hbcli')}`
|
|
697
|
+
say(` ✗ ${error}`)
|
|
698
|
+
return { ok: false, error }
|
|
699
|
+
}
|
|
700
|
+
const binDir = join(home, '.local/bin')
|
|
701
|
+
if (!(process.env.PATH ?? '').split(':').includes(binDir)) {
|
|
691
702
|
say(` ⚠ ${binDir} 不在当前 PATH —— gotry 工具已内建候选路径回退,无需手动处理;其他程序可用: export PATH="${binDir}:$PATH"`)
|
|
692
703
|
}
|
|
693
|
-
say(
|
|
694
|
-
say(' ·
|
|
695
|
-
say(' ·
|
|
696
|
-
say(' ·
|
|
697
|
-
say(' 自检: hbcli auth whoami
|
|
704
|
+
say(` ✓ 安装完成(v${installed.version.join('.')})——已有凭证保留;账号配置:`)
|
|
705
|
+
say(' · 打开 插件 → GoTry → HotelByte,申请沙箱账号并验证保存 App Key/App Secret')
|
|
706
|
+
say(' · 申请入口: https://hotelbyte.com/zh/guides/sandbox-verification')
|
|
707
|
+
say(' · CLI 配置: hbcli auth set-credentials --app-key <appKey> --app-secret <appSecret>')
|
|
708
|
+
say(' 自检: hbcli auth whoami;实际酒店搜索仍需账号开通可用供应商')
|
|
698
709
|
return { ok: true }
|
|
699
710
|
}
|
|
700
711
|
|
|
@@ -856,7 +867,7 @@ async function doctorChecks() {
|
|
|
856
867
|
const reachOk = existsSync(reachBin)
|
|
857
868
|
const reachLevel = reachOk ? 'ok' : existsSync(venvPython) ? 'degraded' : 'missing'
|
|
858
869
|
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 @danceiny/gotry doctor --fix' })
|
|
859
|
-
// hbcli(裸名靠 PATH 探测;绝对路径 existsSync;版本 < MIN_HBCLI_VERSION
|
|
870
|
+
// hbcli(裸名靠 PATH 探测;绝对路径 existsSync;版本 < MIN_HBCLI_VERSION 需要升级)
|
|
860
871
|
let hbBin = ''
|
|
861
872
|
for (const p of ['hbcli', join(homedir(), '.local/bin/hbcli'), join(homedir(), '.staicli/current/hbcli')]) {
|
|
862
873
|
if (p === 'hbcli') { if (await probe(p, ['version'])) { hbBin = 'hbcli(PATH)'; break } } else if (existsSync(p)) { hbBin = p; break }
|
|
@@ -864,7 +875,7 @@ async function doctorChecks() {
|
|
|
864
875
|
if (hbBin) {
|
|
865
876
|
const v = await hbcliVersion(hbBin)
|
|
866
877
|
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
867
|
-
items.push({ label: 'hbcli(酒店实时源)', ok: false, level: 'missing', detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})
|
|
878
|
+
items.push({ label: 'hbcli(酒店实时源)', ok: false, level: 'missing', detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——请升级到支持当前认证与酒店配置的版本`, fix: HBCLI_INSTALL_CMD })
|
|
868
879
|
} else {
|
|
869
880
|
// 凭证按 whoami 的 JSON 三档判定,不再看退出码(#623:whoami 无凭证也退 0,旧判法恒报「凭证有效」)
|
|
870
881
|
const cred = classifyHbcliWhoami(await probeStdout(hbBin === 'hbcli(PATH)' ? 'hbcli' : hbBin, ['--json', 'auth', 'whoami']))
|
|
@@ -873,7 +884,7 @@ async function doctorChecks() {
|
|
|
873
884
|
detail: cred.ok
|
|
874
885
|
? `已安装且凭证有效(${hbBin}, v${v ? v.join('.') : '?'}${cred.customer ? ',customer 档在用(客户邮箱验证码登录)' : ''})`
|
|
875
886
|
: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
876
|
-
fix: cred.ok ? undefined : 'hbcli auth customer-send-code --email you@mail.com 收码后 hbcli auth customer-login --email you@mail.com --code <收件码>(
|
|
887
|
+
fix: cred.ok ? undefined : 'hbcli auth customer-send-code --email you@mail.com 收码后 hbcli auth customer-login --email you@mail.com --code <收件码>(客户邮箱验证码登录,新邮箱即注册);B 端租户注册走 hbcli auth register(先 auth send-code);或在 插件 → GoTry → HotelByte 配置沙箱 App Key/App Secret',
|
|
877
888
|
})
|
|
878
889
|
}
|
|
879
890
|
} else {
|
|
@@ -1130,8 +1141,7 @@ function classifyDoctorGap(item, opts = {}) {
|
|
|
1130
1141
|
const autoSupported = doctorFixAutoSupported(platform)
|
|
1131
1142
|
if (label.startsWith('Agent Reach')) return installerEnabled('reach', env) && autoSupported ? 'auto' : 'unavailable'
|
|
1132
1143
|
// hbcli:二进制缺失(missing)= auto(setupHbcli 装;win32 → unavailable,上游无 win 安装面);
|
|
1133
|
-
// 凭证未配(degraded)= user-action(平台无关);旧版本(missing
|
|
1134
|
-
// 但安装会失败 → 结果兜底 unavailable,诚实不冒充。
|
|
1144
|
+
// 凭证未配(degraded)= user-action(平台无关);旧版本(missing)= auto,同安装器升级后复检。
|
|
1135
1145
|
if (label.startsWith('hbcli')) {
|
|
1136
1146
|
if (item.level === 'degraded') return 'user-action'
|
|
1137
1147
|
return installerEnabled('hbcli', env) && autoSupported ? 'auto' : 'unavailable'
|
|
@@ -1753,7 +1763,7 @@ if (process.argv[1] && process.argv[1].endsWith('gotry-bootstrap.js')) {
|
|
|
1753
1763
|
}
|
|
1754
1764
|
|
|
1755
1765
|
export {
|
|
1756
|
-
setupSidebar, sidebarInstalled,
|
|
1766
|
+
setupHbcli, setupSidebar, sidebarInstalled,
|
|
1757
1767
|
classifyDoctorGap, buildOnboardingPlan, onboardingSkipReason,
|
|
1758
1768
|
runOnboardingFix, promptOnboarding, renderClassifiedPlan, runOnboarding, orchestrateWebLaunch,
|
|
1759
1769
|
doctorFixAutoSupported, platformAutoUnavailableReason, installerEnabled,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Official stable hbcli baseline. npm's staicli@latest still points to 0.0.3. */
|
|
2
|
+
export const HBCLI_RELEASE_VERSION = '0.0.4'
|
|
3
|
+
export const HBCLI_INSTALL_URL = `https://github.com/hotelbyte-com/docs/releases/download/staicli-v${HBCLI_RELEASE_VERSION}/install.sh`
|
|
4
|
+
export const HBCLI_INSTALL_CMD = `curl -fsSL --connect-timeout 15 --max-time 60 ${HBCLI_INSTALL_URL} | bash -s -- --version ${HBCLI_RELEASE_VERSION}`
|
package/client/client.js
CHANGED
|
@@ -26,6 +26,15 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
saved: 'Verified and saved.', clear: 'Clear local key', cleared: 'Local key cleared. Anonymous trial is now active.',
|
|
27
27
|
retry: 'Refresh status', source: 'Source', config: 'Local configuration', env: 'Environment variable',
|
|
28
28
|
'env-debug': 'Debug environment variable', none: 'Shared trial',
|
|
29
|
+
hbcli: 'HotelByte · sandbox', appKey: 'App Key', appSecret: 'App Secret',
|
|
30
|
+
hbHint: 'Use the credentials supplied with your sandbox account. They never enter the conversation.',
|
|
31
|
+
hbRegistration: 'Apply for a sandbox account', hbUnconfigured: 'Sandbox API credentials are not configured', hbConfigured: 'Sandbox API credentials configured',
|
|
32
|
+
hbReadOnly: 'Credentials come from the launch environment, or the local configuration cannot be safely written.',
|
|
33
|
+
hbSaveFailed: 'Credentials were not saved; the previous configuration is retained.',
|
|
34
|
+
hbSaved: 'Credentials saved and the read-only hotel query completed.', hbClear: 'Clear local sandbox credentials',
|
|
35
|
+
hbCleared: 'Local sandbox API credentials cleared.', hbNoSuppliers: 'Saved · no available suppliers configured',
|
|
36
|
+
hbNoSuppliersHint: 'HotelByte must enable suppliers for your account. This result does not mean the destination has no rooms.',
|
|
37
|
+
hbMiss: 'Query verified · no hotels in this check', hbProbeHint: 'Verification makes one read-only Shanghai hotel query for tomorrow, one night and two adults.',
|
|
29
38
|
artifacts: 'GoTry artifacts', artifactsHint: 'Browse saved itineraries and planning deliveries',
|
|
30
39
|
search: 'Search artifacts', searchPlaceholder: 'Title or filename', refresh: 'Refresh',
|
|
31
40
|
empty: 'No saved artifacts in this workspace.', noMatches: 'No matching artifacts.', noSession: 'Open a conversation to browse its workspace.',
|
|
@@ -43,6 +52,15 @@ window.__ModuleLoader__.load({
|
|
|
43
52
|
saved: '验证通过,已保存。', clear: '清除本机 Key', cleared: '本机 Key 已清除,已恢复匿名试用。',
|
|
44
53
|
retry: '重新读取状态', source: '来源', config: '本机配置', env: '环境变量',
|
|
45
54
|
'env-debug': '调试环境变量', none: '共享试用',
|
|
55
|
+
hbcli: 'HotelByte(沙箱)', appKey: 'App Key', appSecret: 'App Secret',
|
|
56
|
+
hbHint: '填写开通沙箱账号后取得的凭证,内容不会进入对话。',
|
|
57
|
+
hbRegistration: '申请沙箱账号', hbUnconfigured: '尚未配置沙箱 API 凭证', hbConfigured: '已配置沙箱 API 凭证',
|
|
58
|
+
hbReadOnly: '当前凭证来自启动环境变量,或本机配置无法安全写入。',
|
|
59
|
+
hbSaveFailed: '新凭证未保存,旧配置已保留。',
|
|
60
|
+
hbSaved: '凭证已保存,只读酒店查询已完成。', hbClear: '清除本机沙箱凭证',
|
|
61
|
+
hbCleared: '本机沙箱 API 凭证已清除。', hbNoSuppliers: '已保存 · 未配置可用供应商',
|
|
62
|
+
hbNoSuppliersHint: '请联系 HotelByte 开通当前账号的供应商。这次结果不能判断目的地无房。',
|
|
63
|
+
hbMiss: '查询通道已验证 · 本次未返回酒店', hbProbeHint: '验证会只读查询上海明天入住的一晚酒店,两位成人。',
|
|
46
64
|
artifacts: 'GoTry 产物', artifactsHint: '查看已保存行程和规划交付物',
|
|
47
65
|
search: '搜索产物', searchPlaceholder: '标题或文件名', refresh: '刷新',
|
|
48
66
|
empty: '这个工作区尚无已保存产物。', noMatches: '没有匹配的产物。', noSession: '打开一个对话后即可查看其工作区产物。',
|
|
@@ -59,10 +77,12 @@ window.__ModuleLoader__.load({
|
|
|
59
77
|
return value
|
|
60
78
|
}
|
|
61
79
|
|
|
62
|
-
function
|
|
80
|
+
function SourceSettings(props) {
|
|
63
81
|
var t = props.t
|
|
82
|
+
var hotelbyte = props.provider === 'hbcli', provider = hotelbyte ? 'hbcli' : 'flyai'
|
|
64
83
|
var state = React.useState(null), status = state[0], setStatus = state[1]
|
|
65
84
|
var draft = React.useState(''), key = draft[0], setKey = draft[1]
|
|
85
|
+
var secretDraft = React.useState(''), secret = secretDraft[0], setSecret = secretDraft[1]
|
|
66
86
|
var noticeState = React.useState(''), notice = noticeState[0], setNotice = noticeState[1]
|
|
67
87
|
var errorState = React.useState(''), error = errorState[0], setError = errorState[1]
|
|
68
88
|
var busyState = React.useState(false), busy = busyState[0], setBusy = busyState[1]
|
|
@@ -72,7 +92,7 @@ window.__ModuleLoader__.load({
|
|
|
72
92
|
var controller = new AbortController()
|
|
73
93
|
lifetime.current = controller
|
|
74
94
|
setError('')
|
|
75
|
-
webRequest(
|
|
95
|
+
webRequest(provider, { signal: controller.signal }).then(function (value) {
|
|
76
96
|
if (!controller.signal.aborted) setStatus(value)
|
|
77
97
|
}).catch(function () { if (!controller.signal.aborted) setError(t('failed')) })
|
|
78
98
|
return function () { controller.abort(); lifetime.current = null }
|
|
@@ -82,14 +102,20 @@ window.__ModuleLoader__.load({
|
|
|
82
102
|
var controller = lifetime.current
|
|
83
103
|
activeRequest.current = true
|
|
84
104
|
setBusy(true); setError(''); setNotice('')
|
|
85
|
-
var candidate = key
|
|
86
|
-
setKey('')
|
|
105
|
+
var candidate = key, candidateSecret = secret
|
|
106
|
+
setKey(''); setSecret('')
|
|
87
107
|
try {
|
|
88
|
-
var
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
var payload = action === 'clear' ? { action: action } : hotelbyte
|
|
109
|
+
? { action: action, appKey: candidate, appSecret: candidateSecret } : { action: action, key: candidate }
|
|
110
|
+
var value = await webRequest(provider + '/write', { method: 'POST', signal: controller.signal,
|
|
111
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
|
|
112
|
+
if (!controller.signal.aborted) {
|
|
113
|
+
setStatus(value)
|
|
114
|
+
setNotice(hotelbyte && action === 'save' && value.verdict === 'no-suppliers' ? ''
|
|
115
|
+
: t(hotelbyte ? action === 'clear' ? 'hbCleared' : 'hbSaved' : action === 'save' ? 'saved' : 'cleared'))
|
|
116
|
+
}
|
|
91
117
|
} catch (failure) {
|
|
92
|
-
if (!controller.signal.aborted) setError(failure.message || t('saveFailed'))
|
|
118
|
+
if (!controller.signal.aborted) setError(failure.message || t(hotelbyte ? 'hbSaveFailed' : 'saveFailed'))
|
|
93
119
|
} finally {
|
|
94
120
|
activeRequest.current = false
|
|
95
121
|
if (!controller.signal.aborted) setBusy(false)
|
|
@@ -97,21 +123,32 @@ window.__ModuleLoader__.load({
|
|
|
97
123
|
}
|
|
98
124
|
if (props.view === 'summary') return t('description')
|
|
99
125
|
var disabled = busy || !status?.writable
|
|
100
|
-
var stateLabel = !status ? t('loading') : !status.configured ? t(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
126
|
+
var stateLabel = !status ? t('loading') : !status.configured ? t(hotelbyte ? 'hbUnconfigured' : 'anonymous')
|
|
127
|
+
: hotelbyte && status.verdict === 'no-suppliers' ? t('hbNoSuppliers') : hotelbyte && status.verdict === 'miss' ? t('hbMiss')
|
|
128
|
+
: status.verified ? t('verified') : t('unverified')
|
|
129
|
+
var sectionProps = { style: { display: 'grid', gap: '16px', maxWidth: '640px' } }
|
|
130
|
+
sectionProps['data-gotry-' + provider + '-settings'] = ''
|
|
131
|
+
var statusProps = { key: 'status', role: 'status', style: { display: 'grid', gap: '6px' } }
|
|
132
|
+
statusProps['data-gotry-' + provider + '-status'] = ''
|
|
133
|
+
return element('section', sectionProps, [
|
|
134
|
+
element('h3', { key: 'heading', style: { margin: 0 } }, t(provider)),
|
|
135
|
+
element('div', statusProps, [
|
|
104
136
|
element(UI.Tag, { key: 'badge', tone: status?.verified ? 'success' : 'neutral' }, stateLabel),
|
|
105
|
-
status ? element('span', { key: 'source', style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' } }, t('source') + ':' + t(status.source)) : null,
|
|
137
|
+
status ? element('span', { key: 'source', style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' } }, t('source') + ':' + t(hotelbyte && status.source === 'none' ? 'hbUnconfigured' : status.source)) : null,
|
|
106
138
|
status?.endpointDebug ? element('span', { key: 'debug' }, t('debug') + ':' + status.endpoint) : null,
|
|
107
139
|
]),
|
|
108
|
-
element('a', { key: 'console', href: 'https://flyai.open.fliggy.com/console', target: '_blank', rel: 'noopener noreferrer' }, t('console')),
|
|
109
|
-
element(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
140
|
+
element('a', { key: 'console', href: hotelbyte ? 'https://hotelbyte.com/zh/guides/sandbox-verification' : 'https://flyai.open.fliggy.com/console', target: '_blank', rel: 'noopener noreferrer' }, t(hotelbyte ? 'hbRegistration' : 'console')),
|
|
141
|
+
hotelbyte ? element('p', { key: 'probe-hint', style: { margin: 0, fontSize: '12px' } }, t('hbProbeHint')) : null,
|
|
142
|
+
hotelbyte && status?.verdict === 'no-suppliers' ? element('p', { key: 'supplier-hint', role: 'status', style: { margin: 0 } }, t('hbNoSuppliersHint')) : null,
|
|
143
|
+
element(UI.SettingsForm, { key: 'form', labels: { unavailable: t('loading'), readOnly: t(hotelbyte ? 'hbReadOnly' : 'readOnly'), saveFailed: t(hotelbyte ? 'hbSaveFailed' : 'saveFailed'), save: t('save'), saving: t('saving') },
|
|
144
|
+
state: { available: Boolean(status), writable: Boolean(status?.writable), dirty: key.trim().length > 0 && (!hotelbyte || secret.trim().length > 0), invalid: false, saving: busy, failed: false },
|
|
145
|
+
onSave: function () { void write('save') }, onDiscard: function () { setKey(''); setSecret('') } }, [
|
|
146
|
+
element(UI.SettingsSecretField, { key: 'key', id: 'gotry-' + provider + '-key', label: t(hotelbyte ? 'appKey' : 'key'), hint: t(hotelbyte ? 'hbHint' : 'keyHint'), text: key,
|
|
147
|
+
configured: Boolean(status?.configured), stateLabel: t(status?.configured ? hotelbyte ? 'hbConfigured' : 'configured' : hotelbyte ? 'hbUnconfigured' : 'anonymous'), disabled: disabled,
|
|
114
148
|
onEdit: function (value) { setKey(value); setError(''); setNotice('') } }),
|
|
149
|
+
hotelbyte ? element(UI.SettingsSecretField, { key: 'secret', id: 'gotry-hbcli-secret', label: t('appSecret'), text: secret,
|
|
150
|
+
configured: Boolean(status?.configured), stateLabel: t(status?.configured ? 'hbConfigured' : 'hbUnconfigured'), disabled: disabled,
|
|
151
|
+
onEdit: function (value) { setSecret(value); setError(''); setNotice('') } }) : null,
|
|
115
152
|
]),
|
|
116
153
|
error ? element('p', { key: 'error', role: 'alert', style: { margin: 0, color: 'var(--dsw-alias-state-error-primary)' } }, error) : null,
|
|
117
154
|
notice ? element('p', { key: 'notice', role: 'status', style: { margin: 0 } }, notice) : null,
|
|
@@ -119,11 +156,19 @@ window.__ModuleLoader__.load({
|
|
|
119
156
|
element(UI.Button, { key: 'retry', type: 'button', variant: 'outline', disabled: busy,
|
|
120
157
|
onClick: function () { setReload(function (value) { return value + 1 }) } }, t('retry')),
|
|
121
158
|
status?.configured && status.writable ? element(UI.Button, { key: 'clear', type: 'button', disabled: busy,
|
|
122
|
-
onClick: function () { void write('clear') } }, t('clear')) : null,
|
|
159
|
+
onClick: function () { void write('clear') } }, t(hotelbyte ? 'hbClear' : 'clear')) : null,
|
|
123
160
|
]),
|
|
124
161
|
])
|
|
125
162
|
}
|
|
126
163
|
|
|
164
|
+
function GoTrySettings(props) {
|
|
165
|
+
if (props.view === 'summary') return props.t('description')
|
|
166
|
+
return element('div', { style: { display: 'grid', gap: '32px' } }, [
|
|
167
|
+
element(SourceSettings, Object.assign({}, props, { key: 'flyai', provider: 'flyai' })),
|
|
168
|
+
element(SourceSettings, Object.assign({}, props, { key: 'hbcli', provider: 'hbcli' })),
|
|
169
|
+
])
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
function ArtifactBrowser(props) {
|
|
128
173
|
var t = props.t, info = props.useTabInfo(), sessionId = props.sessionId
|
|
129
174
|
var cwd = props.useSessions(function (sessions) { return sessions.byId[sessionId]?.cwd })
|
|
@@ -367,7 +412,7 @@ window.__ModuleLoader__.load({
|
|
|
367
412
|
var t = scope.locale.bind(ns)
|
|
368
413
|
scope.effect(function () { return scope.locale.register(ns, webLocale) })
|
|
369
414
|
scope.slots.inject('plugins.item', function () {
|
|
370
|
-
return scope.slots.register({ name: 'plugins.item', id: 'gotry', order: 50, label: function () { return t('title') }, locale: ns },
|
|
415
|
+
return scope.slots.register({ name: 'plugins.item', id: 'gotry', order: 50, label: function () { return t('title') }, locale: ns }, GoTrySettings)
|
|
371
416
|
})
|
|
372
417
|
scope.inject(['sidebarRightTabs'], function (sidebar) {
|
|
373
418
|
sidebar.effect(function () { return sidebar.sidebarRightTabs.register({ id: '@danceiny/gotry:artifacts', kind: 'gotry-artifacts',
|
|
@@ -20,6 +20,7 @@ import { createRequire } from 'node:module';
|
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { join, resolve } from 'node:path';
|
|
22
22
|
import { checkExtensionStatus } from '../../bin/gotry-extension-status.js';
|
|
23
|
+
import { HBCLI_RELEASE_VERSION, HBCLI_INSTALL_CMD } from '../../bin/gotry-hbcli-release.js';
|
|
23
24
|
import { readLatestChannelEvents } from './channel-health.js';
|
|
24
25
|
import { displayEndpoint, endpointFingerprint, readFlyaiVerification, resolveFlyaiEndpoint, resolveFlyaiKey, sha256Hex, } from './flyai-config.js';
|
|
25
26
|
/** hbcli 已知安装位(与 capabilities/hbcli.ts hbcliBinCandidates 同清单) */
|
|
@@ -110,10 +111,8 @@ export function nodeOk(version) {
|
|
|
110
111
|
const [maj = '0', min = '0'] = version.split('.');
|
|
111
112
|
return Number(maj) > 22 || (Number(maj) === 22 && Number(min) >= 15);
|
|
112
113
|
}
|
|
113
|
-
/** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖)
|
|
114
|
-
|
|
115
|
-
* 当 bearer 发到 hotel-be,让 trade.* / search/checkAvail 全部 401。 */
|
|
116
|
-
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3';
|
|
114
|
+
/** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖),与 CLI doctor 共用发布基线。 */
|
|
115
|
+
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? HBCLI_RELEASE_VERSION;
|
|
117
116
|
/** 单项检查:全部只读、永不抛错;env/homeDir/repoRoot 可注入(测试确定性) */
|
|
118
117
|
export async function runDoctorChecks(opts = {}) {
|
|
119
118
|
const repoRoot = opts.repoRoot ? resolve(opts.repoRoot) : resolve(import.meta.dirname, '..', '..');
|
|
@@ -149,7 +148,7 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
149
148
|
}
|
|
150
149
|
// 3. hbcli(酒店实时源;静态包自动降级,缺了不致命)
|
|
151
150
|
// 裸名 'hbcli' 靠 PATH 解析(existsSync 对裸名是 cwd 相对,无意义)——先探测再落位
|
|
152
|
-
// 版本 < MIN_HBCLI_VERSION 视为 missing
|
|
151
|
+
// 版本 < MIN_HBCLI_VERSION 视为 missing,修复走版本钉扎的官方安装器
|
|
153
152
|
let hbPresent = '';
|
|
154
153
|
for (const p of hbcliCandidates(home)) {
|
|
155
154
|
if (p !== 'hbcli' && existsSync(p)) {
|
|
@@ -167,8 +166,8 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
167
166
|
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
168
167
|
items.push({
|
|
169
168
|
id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'missing',
|
|
170
|
-
detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})
|
|
171
|
-
fix:
|
|
169
|
+
detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——请升级到支持当前认证与酒店配置的版本`,
|
|
170
|
+
fix: HBCLI_INSTALL_CMD,
|
|
172
171
|
});
|
|
173
172
|
}
|
|
174
173
|
else {
|
|
@@ -194,7 +193,7 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
194
193
|
: {
|
|
195
194
|
id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'degraded',
|
|
196
195
|
detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
197
|
-
fix: 'hbcli auth customer-send-code --email you@mail.com 收码后 hbcli auth customer-login --email you@mail.com --code <收件码>(
|
|
196
|
+
fix: 'hbcli auth customer-send-code --email you@mail.com 收码后 hbcli auth customer-login --email you@mail.com --code <收件码>(客户邮箱验证码登录,新邮箱即注册);B 端租户注册走 hbcli auth register(先 auth send-code);或在 插件 → GoTry → HotelByte 配置沙箱 App Key/App Secret',
|
|
198
197
|
});
|
|
199
198
|
}
|
|
200
199
|
}
|