@danceiny/gotry 0.0.1-rc.14 → 0.0.1-rc.16
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 +26 -20
- package/README.zh-CN.md +27 -21
- package/bin/gotry-bootstrap.js +331 -3
- package/cordis.gotry-patch.yml +10 -0
- package/data/airline-airports.json +39 -0
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +217 -0
- package/dist/capabilities/effect.js +293 -0
- package/dist/capabilities/fact-log.js +41 -0
- package/dist/capabilities/flyai.js +1 -1
- package/dist/capabilities/hbcli.js +43 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +90 -0
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +2 -1
- package/dist/capabilities/session/extension-bridge.js +345 -0
- package/dist/capabilities/session/extension-channel.js +93 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/health-watch.js +216 -0
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +377 -0
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +77 -1
- package/dist/capabilities/session-search.js +69 -2
- package/dist/capabilities/weather.js +1 -1
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/booking-saga-tests.js +187 -0
- package/dist/scripts/bootstrap-tests.js +49 -3
- package/dist/scripts/build-changelog.js +279 -0
- package/dist/scripts/changelog-tests.js +165 -0
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/effect-tests.js +474 -0
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/extension-tests.js +468 -0
- package/dist/scripts/fact-gate-tests.js +318 -0
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +180 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +51 -0
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/nightly-evidence-tests.js +186 -0
- package/dist/scripts/nightly-evidence.js +289 -0
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +218 -0
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/price-drift-tests.js +441 -0
- package/dist/scripts/price-drift-watch.js +491 -0
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +1 -1
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +1 -1
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke.js +159 -1
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +1 -1
- package/dist/scripts/wizard-bootstrap.js +32 -0
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +310 -0
- package/dist/src/bookable-facts.js +349 -0
- package/dist/src/booking-saga.js +153 -0
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +31 -7
- package/dist/src/engine.js +1 -1
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +322 -46
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +14 -7
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +1 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +26 -0
- package/extension/background.js +160 -0
- package/extension/content-bridge.js +41 -0
- package/extension/content-main.js +62 -0
- package/extension/manifest.json +32 -0
- package/package.json +8 -2
- package/ts/capabilities/artifacts.ts +235 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/hbcli.ts +46 -0
- package/ts/capabilities/session/benchmark.ts +4 -1
- package/ts/capabilities/session/extension-bridge.ts +354 -0
- package/ts/capabilities/session/extension-channel.ts +92 -0
- package/ts/capabilities/session/health-watch.ts +257 -0
- package/ts/capabilities/session/wizard.ts +282 -0
- package/ts/capabilities/session-login.ts +53 -2
- package/ts/capabilities/session-search.ts +68 -3
- package/ts/src/artifact-gate.ts +365 -0
- package/ts/src/bookable-facts.ts +557 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/dsh-llm.ts +40 -3
- package/ts/src/index.ts +230 -28
package/bin/gotry-bootstrap.js
CHANGED
|
@@ -4,16 +4,30 @@
|
|
|
4
4
|
* hbcli(hotelbyte-cli) → 官方 install.sh(原生二进制,~/.local/bin/hbcli)
|
|
5
5
|
* agent-reach → 官方 pip 安装 git+ upstream(包内 .venv,与 z3-solver 同址原则)
|
|
6
6
|
* flyai → 无需安装(npx 每次自拉 @fly-ai/flyai-cli)
|
|
7
|
+
* dsh-better-sidebar → dsh 宿主层插件市场组件(dshmarket.com #1 UI,18.9 万周装):
|
|
8
|
+
* dsh web 侧栏工作台(文件浏览/Markdown/Mermaid/PDF 预览)——
|
|
9
|
+
* gotry 产物(工单交付 md/行程 md)的成熟查看面(issue #25)。
|
|
10
|
+
* 宿主层安装(dsh plugin → ~/.dsh/profiles/web),不进 gotry 依赖。
|
|
11
|
+
* gotry-session-bridge → 会话检索浏览器扩展(issue #21 传输层方案 C,2026-08-29 定案):
|
|
12
|
+
* 一次性安装替代逐连接 CDP 弹窗;幂等拷贝到 ~/.gotry/extension,
|
|
13
|
+
* 最后一步是用户在 chrome://extensions 「加载已解压的扩展程序」(约 30 秒,零系统弹窗)。
|
|
7
14
|
*
|
|
8
15
|
* 用法:
|
|
9
16
|
* node bin/gotry-bootstrap.js # 显式安装(缺啥装啥;失败 exit 1)
|
|
10
17
|
* node bin/gotry-bootstrap.js --auto # postinstall 模式:CI/跳过开关检测,任何失败不挡安装(exit 0)
|
|
11
18
|
* node bin/gotry-bootstrap.js --check-only # 只探测报告,不安装(测试钩子)
|
|
19
|
+
* node bin/gotry-bootstrap.js wizard # 会话扩展 onboarding 闭环(issue #21 onboarding UX,§3.3):
|
|
20
|
+
* # 5 步编排 + 后台 health-watch 等扩展心跳,
|
|
21
|
+
* # 扩展一就位 stdout 翻绿并自动重放同 query。
|
|
22
|
+
* # 详见 docs/user-session-data-rfc.md §3.3 / RFC P3.6。
|
|
12
23
|
*
|
|
13
24
|
* 环境开关:
|
|
14
25
|
* GOTRY_SETUP_SKIP=1 全部跳过
|
|
15
26
|
* GOTRY_SETUP_HBCLI=0 跳过 hbcli
|
|
16
27
|
* GOTRY_SETUP_REACH=0 跳过 agent-reach
|
|
28
|
+
* GOTRY_SETUP_SIDEBAR=0 跳过 dsh-better-sidebar
|
|
29
|
+
* GOTRY_SETUP_EXTENSION=0 跳过会话检索扩展落位
|
|
30
|
+
* GOTRY_ONBOARDING_HEADLESS=1 wizard 子命令强制走终端面板(SSH/CI 默认探测)
|
|
17
31
|
*
|
|
18
32
|
* 契约:安装外部依赖永远不挡 gotry 本体——能力层各有降级路径(静态包/not-installed
|
|
19
33
|
* verdict),自举失败只降级体验,不产生故障。凭证(hbcli auth / agent-reach 渠道
|
|
@@ -21,7 +35,7 @@
|
|
|
21
35
|
*/
|
|
22
36
|
|
|
23
37
|
import { spawn } from 'node:child_process'
|
|
24
|
-
import { existsSync } from 'node:fs'
|
|
38
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'
|
|
25
39
|
import { homedir } from 'node:os'
|
|
26
40
|
import { join, dirname } from 'node:path'
|
|
27
41
|
import { fileURLToPath } from 'node:url'
|
|
@@ -29,6 +43,8 @@ import { fileURLToPath } from 'node:url'
|
|
|
29
43
|
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
30
44
|
const AUTO = process.argv.includes('--auto')
|
|
31
45
|
const CHECK_ONLY = process.argv.includes('--check-only')
|
|
46
|
+
const WIZARD = process.argv.includes('wizard') || process.argv.includes('--wizard')
|
|
47
|
+
const WIZARD_DRY_RUN = process.argv.includes('--dry-run')
|
|
32
48
|
|
|
33
49
|
const HBCLI_INSTALL_CMD = 'curl -fsSL https://github.com/hotelbyte-com/docs/releases/latest/download/install.sh | bash'
|
|
34
50
|
const REACH_INSTALL_URL = 'git+https://github.com/Panniantong/Agent-Reach.git'
|
|
@@ -63,7 +79,14 @@ async function setupHbcli() {
|
|
|
63
79
|
say('[gotry-setup] hbcli(hotelbyte-cli,可选酒店实时源)')
|
|
64
80
|
const candidates = ['hbcli', join(homedir(), '.local/bin/hbcli'), join(homedir(), '.staicli/current/hbcli')]
|
|
65
81
|
const present = candidates.some((p) => existsSync(p)) && (await probe(candidates[0], ['version']) || await probe(candidates[1], ['version']) || await probe(candidates[2], ['version']))
|
|
66
|
-
|
|
82
|
+
const credFile = join(homedir(), '.staicli', 'credentials.json')
|
|
83
|
+
if (present) {
|
|
84
|
+
say(' ✓ 已安装')
|
|
85
|
+
if (existsSync(credFile)) say(' ✓ 凭证已配置(自检: hbcli auth whoami)')
|
|
86
|
+
else say(' ✗ 凭证未配置——酒店检索将用内置静态包(非实时),账号配置见下方指引')
|
|
87
|
+
if (CHECK_ONLY) say(' (--check-only 只报告,不安装)')
|
|
88
|
+
return { ok: true }
|
|
89
|
+
}
|
|
67
90
|
if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
|
|
68
91
|
say(` 安装中(官方脚本): ${HBCLI_INSTALL_CMD}`)
|
|
69
92
|
const r = await run('bash', ['-c', HBCLI_INSTALL_CMD], { timeoutMs: 120_000 })
|
|
@@ -72,7 +95,11 @@ async function setupHbcli() {
|
|
|
72
95
|
if (!process.env.PATH.split(':').includes(binDir)) {
|
|
73
96
|
say(` ⚠ ${binDir} 不在当前 PATH —— gotry 工具已内建候选路径回退,无需手动处理;其他程序可用: export PATH="${binDir}:$PATH"`)
|
|
74
97
|
}
|
|
75
|
-
say(' ✓
|
|
98
|
+
say(' ✓ 安装完成——账号配置(未配时酒店检索自动用静态包,非实时):')
|
|
99
|
+
say(' · 快速试用(官方沙箱演示账号): hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo')
|
|
100
|
+
say(' · 正式接入: 向 HotelByte 申请专属 appKey/appSecret 后用同一命令替换')
|
|
101
|
+
say(' · 门户账号(权限更大,酒店搜索无必要): hbcli auth login --username <email>')
|
|
102
|
+
say(' 自检: hbcli auth whoami(api_key.configured=true 即就位)')
|
|
76
103
|
return { ok: true }
|
|
77
104
|
}
|
|
78
105
|
|
|
@@ -93,10 +120,307 @@ async function setupReach() {
|
|
|
93
120
|
return { ok: true }
|
|
94
121
|
}
|
|
95
122
|
|
|
123
|
+
async function setupSidebar() { say('[gotry-setup] dsh-better-sidebar(dsh web 侧栏工作台,产物查看面 issue #25)')
|
|
124
|
+
const installed = existsSync(join(homedir(), '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json'))
|
|
125
|
+
if (installed) { say(' ✓ 已安装(~/.dsh/profiles/web)'); return { ok: true } }
|
|
126
|
+
if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
|
|
127
|
+
const SIDEBAR_PKG = 'dsh-better-sidebar@latest'
|
|
128
|
+
// dsh CLI:本包依赖里的 @deepseek-ai/dsh 优先;解析不到走 npx 自拉(官方安装途径同款)
|
|
129
|
+
const { createRequire } = await import('node:module')
|
|
130
|
+
const require_ = createRequire(join(repoRoot, 'package.json'))
|
|
131
|
+
let launched = false
|
|
132
|
+
try {
|
|
133
|
+
const dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
|
|
134
|
+
say(` 安装中(dsh plugin → web profile): ${SIDEBAR_PKG}`)
|
|
135
|
+
const r = await run(process.execPath, [dshBin, 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000 })
|
|
136
|
+
launched = true
|
|
137
|
+
if (r.ok) {
|
|
138
|
+
say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台;工作区里的产物 md 可直接预览)')
|
|
139
|
+
return { ok: true }
|
|
140
|
+
}
|
|
141
|
+
say(` ✗ dsh plugin 安装失败(${r.error})——尝试 npx 途径`)
|
|
142
|
+
} catch { /* 本包未携带 @deepseek-ai/dsh */ }
|
|
143
|
+
if (!launched) say(' 本包未携带 @deepseek-ai/dsh,走 npx 途径安装')
|
|
144
|
+
const r2 = await run('npx', ['-y', '--package', '@deepseek-ai/dsh', 'dsh', 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000 })
|
|
145
|
+
if (!r2.ok) {
|
|
146
|
+
say(' ✗ 安装失败——不影响 gotry:产物仍可在对话里说「看看我生成的行程」经 gotry_artifacts_list/read 查看;可稍后重试: npx gotry setup')
|
|
147
|
+
return { ok: false }
|
|
148
|
+
}
|
|
149
|
+
say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台)')
|
|
150
|
+
return { ok: true }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const EXTENSION_FILES = ['manifest.json', 'background.js', 'content-main.js', 'content-bridge.js', 'README.md']
|
|
154
|
+
|
|
155
|
+
function readManifestVersion(manifestPath) {
|
|
156
|
+
try { return JSON.parse(readFileSync(manifestPath, 'utf8')).version ?? null } catch { return null }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function copyExtensionDir(srcDir, dstDir) {
|
|
160
|
+
mkdirSync(dstDir, { recursive: true })
|
|
161
|
+
for (const f of EXTENSION_FILES) {
|
|
162
|
+
const s = join(srcDir, f)
|
|
163
|
+
if (!existsSync(s)) continue
|
|
164
|
+
copyFileSync(s, join(dstDir, f))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** 会话检索扩展(issue #21 方案 C):包内 extension/ → ~/.gotry/extension 幂等落位 + 一次性加载指引 */
|
|
169
|
+
async function setupExtension() {
|
|
170
|
+
say('[gotry-setup] GoTry Session Bridge 扩展(会话检索数据面 issue #21;一次性安装,替代逐连接弹窗)')
|
|
171
|
+
const srcDir = join(repoRoot, 'extension')
|
|
172
|
+
const srcManifest = join(srcDir, 'manifest.json')
|
|
173
|
+
if (!existsSync(srcManifest)) {
|
|
174
|
+
say(` ✗ 包内未找到扩展文件(${srcDir})——不影响 gotry:会话检索降级,重装 npx gotry 可恢复`)
|
|
175
|
+
return { ok: false }
|
|
176
|
+
}
|
|
177
|
+
const srcVersion = readManifestVersion(srcManifest)
|
|
178
|
+
const dstDir = join(homedir(), '.gotry', 'extension')
|
|
179
|
+
const dstVersion = readManifestVersion(join(dstDir, 'manifest.json'))
|
|
180
|
+
if (dstVersion != null && dstVersion === srcVersion) {
|
|
181
|
+
say(` ✓ 已就位(~/.gotry/extension,v${dstVersion};若浏览器里尚未加载:chrome://extensions → 开发者模式 → 加载已解压的扩展程序 → 选 ~/.gotry/extension)`)
|
|
182
|
+
return { ok: true }
|
|
183
|
+
}
|
|
184
|
+
if (CHECK_ONLY) { say(` ✗ ${dstVersion == null ? `未安装(落位 ${dstDir})` : `待更新 v${dstVersion} → v${srcVersion},落位 ${dstDir}`}(--check-only 只报告)`); return { ok: true } }
|
|
185
|
+
try {
|
|
186
|
+
copyExtensionDir(srcDir, dstDir)
|
|
187
|
+
} catch (e) {
|
|
188
|
+
say(` ✗ 落位失败(${e.message})——不影响 gotry:会话检索降级;可手动拷贝 ${srcDir} → ${dstDir}`)
|
|
189
|
+
return { ok: false }
|
|
190
|
+
}
|
|
191
|
+
say(` ✓ 已落位 ${dstDir}(v${srcVersion};manifest 带固定 key,扩展 ID 恒为 olpgkofjhhiiiahdkkbcninhjmegghfe)`)
|
|
192
|
+
say(' 最后一步(每台浏览器一次,约 30 秒):Chrome 打开 chrome://extensions → 右上角开启「开发者模式」→「加载已解压的扩展程序」→ 选择 ~/.gotry/extension')
|
|
193
|
+
say(' 装好即生效,零系统弹窗;扩展卡片开关=总闸(与 gotry 授权闸 sessionAccess 双重控制)')
|
|
194
|
+
return { ok: true }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 调 wizard-bootstrap.ts(wizard.ts 的 CLI 入口):拿 5 步 OnboardingResult 打印到 stdout。
|
|
199
|
+
* tsx 子进程跑 wizard 全套(扩展落位 + 开 Chrome + pbcopy 真实覆写剪贴板 + osascript GUI 面板);
|
|
200
|
+
* bootstrap 不再 inline 任何 spawn——所有 UX 都走 wizard.ts,与 onboarding-tests §40 同一份代码。
|
|
201
|
+
* npm 安装态(无 ts/目录):降级 inline 跑 5 步的最小子集——只调 setupExtension + 打印路径,UI 面板/剪贴板由用户手动。
|
|
202
|
+
*/
|
|
203
|
+
async function runWizardBootstrap() {
|
|
204
|
+
const wizardScript = join(repoRoot, 'ts', 'scripts', 'wizard-bootstrap.ts')
|
|
205
|
+
const extensionDir = join(homedir(), '.gotry', 'extension')
|
|
206
|
+
if (!existsSync(wizardScript)) {
|
|
207
|
+
// npm 安装态降级:只确保扩展落位,文案照常打
|
|
208
|
+
say('[gotry-wizard] 内置 wizard 脚本缺失(已装 npm 包态)——降级手动模式:')
|
|
209
|
+
await setupExtension()
|
|
210
|
+
return { ok: true }
|
|
211
|
+
}
|
|
212
|
+
return new Promise((resolve) => {
|
|
213
|
+
const child = spawn('npx', ['--yes', 'tsx', wizardScript, '--extension-dir', extensionDir, '--source-dir', join(repoRoot, 'extension')], {
|
|
214
|
+
cwd: join(repoRoot, 'ts'),
|
|
215
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
216
|
+
})
|
|
217
|
+
let stdoutBuf = ''
|
|
218
|
+
let resolved = false
|
|
219
|
+
const finish = (r) => {
|
|
220
|
+
if (resolved) return
|
|
221
|
+
resolved = true
|
|
222
|
+
resolve(r)
|
|
223
|
+
}
|
|
224
|
+
child.stdout.on('data', (c) => {
|
|
225
|
+
stdoutBuf += c.toString('utf8')
|
|
226
|
+
let nl = stdoutBuf.indexOf('\n')
|
|
227
|
+
while (nl >= 0) {
|
|
228
|
+
const line = stdoutBuf.slice(0, nl).trim()
|
|
229
|
+
stdoutBuf = stdoutBuf.slice(nl + 1)
|
|
230
|
+
if (line.startsWith('{')) {
|
|
231
|
+
try {
|
|
232
|
+
const o = JSON.parse(line)
|
|
233
|
+
// 打印 5 步 status(wizard 自己的 print 已在子进程跑了;此处父进程再 human-friendly 一次)
|
|
234
|
+
if (Array.isArray(o.steps)) {
|
|
235
|
+
for (const s of o.steps) {
|
|
236
|
+
const tag = s.status === 'ok' ? '✅' : s.status === 'skip' ? '⏭ ' : '❌'
|
|
237
|
+
say(`[gotry-wizard] ${tag} ${s.step}: ${s.summary}`)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
say(`[gotry-wizard] platform=${o.platform}; extensionDir=${o.extensionDir}; ok=${o.ok}`)
|
|
241
|
+
finish({ ok: o.ok === true })
|
|
242
|
+
} catch {
|
|
243
|
+
say(`[gotry-wizard] ✗ wizard JSON 解析失败`)
|
|
244
|
+
finish({ ok: false })
|
|
245
|
+
}
|
|
246
|
+
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
nl = stdoutBuf.indexOf('\n')
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
child.on('exit', (code) => {
|
|
253
|
+
if (!resolved) {
|
|
254
|
+
say(`[gotry-wizard] ✗ wizard 子进程 exit ${code}(stdout 无 JSON)`)
|
|
255
|
+
finish({ ok: code === 0 })
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
child.on('error', (e) => {
|
|
259
|
+
say(`[gotry-wizard] ✗ wizard 子进程启动失败: ${e.message}`)
|
|
260
|
+
finish({ ok: false })
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 后台探活扩展心跳(wizard 闭环「装完零重跑」,§3.3):
|
|
267
|
+
* spawn tsx 跑 health-watch.ts(纯 TS 探活,run-all §40 同一份代码),接 stdout JSON 结果行。
|
|
268
|
+
* 探活期间每 5s stdout 一行 `.`,用户看见 stdout 持续推进,不卡死假象。
|
|
269
|
+
* 默认 120s 超时;若用户提前 Ctrl+C,timeoutMs 走 0 立即返回 cancelled。
|
|
270
|
+
*/
|
|
271
|
+
async function runHealthWatch() {
|
|
272
|
+
// 跨语言:bootstrap 是纯 JS,health-watch 是 TS;走 spawn npx tsx 子进程,stdout 解析。
|
|
273
|
+
// tsx 子进程路径 = <repoRoot>/ts;不在 npm 包里跑(发布面只走 src/bin,不走 wizard 自动化)
|
|
274
|
+
const watcherScript = join(repoRoot, 'ts', 'scripts', 'health-watch-cli.ts')
|
|
275
|
+
// 默认 120s / 5s,§3.3 设计值;env 可覆盖(bootstrap-tests 跑真实路径用)
|
|
276
|
+
const timeoutMs = parseInt(process.env.GOTRY_ONBOARDING_TIMEOUT_MS ?? '120000', 10) || 120_000
|
|
277
|
+
const probeIntervalMs = parseInt(process.env.GOTRY_ONBOARDING_INTERVAL_MS ?? '5000', 10) || 5_000
|
|
278
|
+
return new Promise((resolve) => {
|
|
279
|
+
const args = [watcherScript, '--timeout', String(timeoutMs), '--interval', String(probeIntervalMs), '--json']
|
|
280
|
+
// 先确认 watcher 脚本存在(npm 安装态可能未带 ts/目录——降级:走内联 node:http 短探活,避免挡用户)
|
|
281
|
+
if (!existsSync(watcherScript)) {
|
|
282
|
+
say(`[gotry-wizard] 内置探活脚本缺失(${watcherScript})——改用内置 5s 轮询`)
|
|
283
|
+
resolve(runInlineHealthWatch(timeoutMs))
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
const child = spawn('npx', ['--yes', 'tsx', ...args], {
|
|
287
|
+
cwd: join(repoRoot, 'ts'),
|
|
288
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
289
|
+
env: { ...process.env, GOTRY_ONBOARDING_HEADLESS: process.env.GOTRY_ONBOARDING_HEADLESS ?? '1' },
|
|
290
|
+
})
|
|
291
|
+
let stdoutBuf = ''
|
|
292
|
+
let heartbeatTicker = 0
|
|
293
|
+
const hbInterval = setInterval(() => {
|
|
294
|
+
heartbeatTicker += 1
|
|
295
|
+
process.stdout.write('.')
|
|
296
|
+
if (heartbeatTicker % 12 === 0) process.stdout.write(`(${Math.round(heartbeatTicker * probeIntervalMs / 1000)}s)\n`)
|
|
297
|
+
}, probeIntervalMs)
|
|
298
|
+
child.stdout.on('data', (c) => {
|
|
299
|
+
const s = c.toString('utf8')
|
|
300
|
+
stdoutBuf += s
|
|
301
|
+
// JSON 结果行以 \n 分隔;watcher 一次性 stdout 一行 JSON 退出
|
|
302
|
+
let nl = stdoutBuf.indexOf('\n')
|
|
303
|
+
while (nl >= 0) {
|
|
304
|
+
const line = stdoutBuf.slice(0, nl).trim()
|
|
305
|
+
stdoutBuf = stdoutBuf.slice(nl + 1)
|
|
306
|
+
if (line.startsWith('{')) {
|
|
307
|
+
clearInterval(hbInterval)
|
|
308
|
+
try {
|
|
309
|
+
const o = JSON.parse(line)
|
|
310
|
+
resolve({ ready: o.ready === true, attempts: o.attempts ?? 0, waitedMs: o.waitedMs ?? 0, reason: o.reason ?? 'timeout', timeoutMs })
|
|
311
|
+
} catch {
|
|
312
|
+
resolve({ ready: false, attempts: 0, waitedMs: 0, reason: 'parse-error', timeoutMs })
|
|
313
|
+
}
|
|
314
|
+
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
nl = stdoutBuf.indexOf('\n')
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
child.on('exit', (code) => {
|
|
321
|
+
clearInterval(hbInterval)
|
|
322
|
+
process.stdout.write('\n')
|
|
323
|
+
if (code !== 0 && stdoutBuf.trim() === '') {
|
|
324
|
+
resolve({ ready: false, attempts: 0, waitedMs: 0, reason: `watcher-exit-${code}`, timeoutMs })
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
child.on('error', () => {
|
|
328
|
+
clearInterval(hbInterval)
|
|
329
|
+
resolve({ ready: false, attempts: 0, waitedMs: 0, reason: 'watcher-spawn-error', timeoutMs })
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** npm 安装态(无 ts/目录)降级:内置 5s 轮询 node:http,与 health-watch 同节奏 */
|
|
335
|
+
async function runInlineHealthWatch(timeoutMs) {
|
|
336
|
+
const intervalMs = parseInt(process.env.GOTRY_ONBOARDING_INTERVAL_MS ?? '5000', 10) || 5_000
|
|
337
|
+
const ports = [8791, 8792, 8793, 8794, 8795]
|
|
338
|
+
const http = await import('node:http')
|
|
339
|
+
const startedAt = Date.now()
|
|
340
|
+
let attempts = 0
|
|
341
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
342
|
+
attempts += 1
|
|
343
|
+
for (const port of ports) {
|
|
344
|
+
const ok = await new Promise((resolve) => {
|
|
345
|
+
const req = http.request({ host: '127.0.0.1', port, path: '/status', method: 'GET', timeout: 2_000 }, (res) => {
|
|
346
|
+
if (!res.statusCode || res.statusCode >= 400) { resolve(false); return }
|
|
347
|
+
let buf = ''
|
|
348
|
+
res.setEncoding('utf8')
|
|
349
|
+
res.on('data', (c) => { buf += c })
|
|
350
|
+
res.on('end', () => {
|
|
351
|
+
try { resolve(JSON.parse(buf).extensionConnected === true) } catch { resolve(false) }
|
|
352
|
+
})
|
|
353
|
+
res.on('error', () => resolve(false))
|
|
354
|
+
})
|
|
355
|
+
req.on('error', () => resolve(false))
|
|
356
|
+
req.on('timeout', () => { req.destroy(); resolve(false) })
|
|
357
|
+
req.end()
|
|
358
|
+
})
|
|
359
|
+
if (ok) {
|
|
360
|
+
process.stdout.write('\n')
|
|
361
|
+
return { ready: true, attempts, waitedMs: Date.now() - startedAt, reason: 'ready', timeoutMs }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
process.stdout.write('.')
|
|
365
|
+
await new Promise((r) => setTimeout(r, intervalMs))
|
|
366
|
+
}
|
|
367
|
+
process.stdout.write('\n')
|
|
368
|
+
return { ready: false, attempts, waitedMs: Date.now() - startedAt, reason: 'timeout', timeoutMs }
|
|
369
|
+
}
|
|
370
|
+
|
|
96
371
|
async function main() {
|
|
372
|
+
// wizard 子命令(issue #21 onboarding UX,§3.3 / P3.6):扩展安装闭环,5 步 + 后台 health-watch。
|
|
373
|
+
// **纯 onboarding**,不调 hbcli/agent-reach/sidebar 等其他节——只跑扩展落位 + 健康探活;
|
|
374
|
+
// 想要全量安装仍走 `npx gotry setup`(无 wizard)。
|
|
375
|
+
if (WIZARD) {
|
|
376
|
+
if (WIZARD_DRY_RUN) {
|
|
377
|
+
say('[gotry-wizard] dry-run 模式(零网络零浏览器,只校验命令编排与输出形态;run-all §40 走这条)')
|
|
378
|
+
say(' 步骤: ensure-extension-files → open-chrome-extensions → clipboard-extension-path → panel-guide → watch-extension-ready')
|
|
379
|
+
say(' 平台: darwin(zh-CN stdout);headless/Linux/Windows 同理降级')
|
|
380
|
+
say(' ✅ exit 0')
|
|
381
|
+
process.exit(0)
|
|
382
|
+
}
|
|
383
|
+
// 真实路径:spawn tsx 子进程跑 wizard-bootstrap.ts(同一份 wizard.ts 代码),
|
|
384
|
+
// 拿 5 步 OnboardingResult 打印 status(ok/✅/⏭ /❌)+ 剪贴板/GUI 面板/落位实跑。
|
|
385
|
+
const wizardResult = await runWizardBootstrap()
|
|
386
|
+
if (!wizardResult.ok) {
|
|
387
|
+
say('[gotry-wizard] ✗ wizard 编排有失败步——见上方;扩展未引导到位;可手动 chrome://extensions 加载 ~/.gotry/extension')
|
|
388
|
+
process.exit(1)
|
|
389
|
+
}
|
|
390
|
+
// wizard 闭环:装完扩展就位 stdout 翻「✅ 就绪」(由 health-watch 推动)
|
|
391
|
+
say('')
|
|
392
|
+
say('[gotry-wizard] ────────────────────────────────')
|
|
393
|
+
say('[gotry-wizard] 共 3 步,约 30 秒,装完零弹窗:')
|
|
394
|
+
say('[gotry-wizard] ① Chrome 右上角开启「开发者模式」')
|
|
395
|
+
say(`[gotry-wizard] ② 点「加载已解压的扩展程序」,选这个目录(已复制到剪贴板):`)
|
|
396
|
+
say(`[gotry-wizard] ${join(homedir(), '.gotry', 'extension')}`)
|
|
397
|
+
say('[gotry-wizard] ③ 弹窗「添加扩展」点「添加」')
|
|
398
|
+
say('[gotry-wizard] ────────────────────────────────')
|
|
399
|
+
say('[gotry-wizard] 装好后**无需重跑任何命令**——下面会自动探活,扩展一就位 stdout 翻绿。')
|
|
400
|
+
say('')
|
|
401
|
+
say('[gotry-wizard] 正在后台探活扩展心跳(最长 120s;Ctrl+C 取消)...')
|
|
402
|
+
// 后台 health-watch:spawn tsx 子进程跑 health-watch.ts(纯 TS 模块,browser-only),
|
|
403
|
+
// 探活逻辑与 onboarding-tests §40 同一份代码;无 GUI 依赖。
|
|
404
|
+
const watchResult = await runHealthWatch()
|
|
405
|
+
if (watchResult.ready) {
|
|
406
|
+
say(`[gotry-wizard] ✅ 扩展就绪(等待 ${watchResult.waitedMs}ms,${watchResult.attempts} 次探活)`)
|
|
407
|
+
say('[gotry-wizard] 现在调用 gotry_session_search 即可拿到会话检索结果(零后续手工动作)。')
|
|
408
|
+
say('[gotry-wizard] 入口示例:')
|
|
409
|
+
say(' ./gotry # 启动 dsh → 用 gotry_session_search 工具')
|
|
410
|
+
say(' ./gotry session-check # 跑 sf-01..08 八条 query 双源 scorer(goal 2)')
|
|
411
|
+
process.exit(0)
|
|
412
|
+
} else {
|
|
413
|
+
say(`[gotry-wizard] ✗ ${watchResult.reason}(${watchResult.attempts} 次探活)——扩展未在 ${watchResult.timeoutMs}ms 内就绪`)
|
|
414
|
+
say('[gotry-wizard] 重新打开 chrome://extensions 确认「GoTry Session Bridge · 已启用」,再跑 `npx gotry setup wizard` 重试。')
|
|
415
|
+
process.exit(1)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
97
419
|
if (process.platform === 'win32') {
|
|
98
420
|
say('[gotry-setup] Windows 暂不支持自动安装(hbcli 上游仅 darwin/linux)。手动指引:')
|
|
99
421
|
say(` hbcli: ${HBCLI_INSTALL_CMD}(WSL);agent-reach: python -m venv .venv && .venv/Scripts/pip install ${REACH_INSTALL_URL}`)
|
|
422
|
+
say(' dsh-better-sidebar: npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest')
|
|
423
|
+
say(' GoTry Session Bridge 扩展:手动把包内 extension/ 目录拷到 %USERPROFILE%\\.gotry\\extension,再在 chrome://extensions 开发者模式「加载已解压的扩展程序」')
|
|
100
424
|
process.exit(AUTO ? 0 : 1)
|
|
101
425
|
}
|
|
102
426
|
if (AUTO && (process.env.CI || process.env.GOTRY_SETUP_SKIP === '1')) {
|
|
@@ -110,6 +434,10 @@ async function main() {
|
|
|
110
434
|
else say('[gotry-setup] hbcli:GOTRY_SETUP_HBCLI=0 跳过')
|
|
111
435
|
if (process.env.GOTRY_SETUP_REACH !== '0') results.push(await setupReach())
|
|
112
436
|
else say('[gotry-setup] agent-reach:GOTRY_SETUP_REACH=0 跳过')
|
|
437
|
+
if (process.env.GOTRY_SETUP_SIDEBAR !== '0') results.push(await setupSidebar())
|
|
438
|
+
else say('[gotry-setup] dsh-better-sidebar:GOTRY_SETUP_SIDEBAR=0 跳过')
|
|
439
|
+
if (process.env.GOTRY_SETUP_EXTENSION !== '0') results.push(await setupExtension())
|
|
440
|
+
else say('[gotry-setup] GoTry Session Bridge 扩展:GOTRY_SETUP_EXTENSION=0 跳过')
|
|
113
441
|
say('[gotry-setup] flyai:无需安装(npx 每次自拉 @fly-ai/flyai-cli,免 key)')
|
|
114
442
|
const failed = results.filter((r) => !r.ok).length
|
|
115
443
|
if (failed > 0) {
|
package/cordis.gotry-patch.yml
CHANGED
|
@@ -91,3 +91,13 @@
|
|
|
91
91
|
开页探测;返回 challenged→立即停手如实告知,绝不重试
|
|
92
92
|
绝不绕过。跨源结论各带来源标注([实时API:flyai@ts]/[会话:ctrip-flight@ts]/
|
|
93
93
|
[静态包:估算]),分桶比对直达/中转,不比裸最低价;证据链随结论必达用户。
|
|
94
|
+
(20)事实闸(可下单事实 fail-closed,issue #46):航班号/时刻/机场/价格/签证政策
|
|
95
|
+
只能来自本轮 exact-date 工具结果;exact-date 返回 miss = 「未确认/当前不可售,
|
|
96
|
+
到 D-xx 复核」,**不得**用航线页当日班期、历史航季、相邻日期或记忆填充;
|
|
97
|
+
「只验证航线存在」永不升级为「该日期有该航班」;「联程」仅用于同票保护
|
|
98
|
+
(protected_connection),分票/自助转机必须显式标红(无行李直挂/误机无保护);
|
|
99
|
+
政策只能表述为「截至 YYYY-MM-DD 的现行政策」并附复核日期,不用无条件 ✓;
|
|
100
|
+
双机场城市的航司落点不互换(FD 落 DMK、VZ 落 BKK)。
|
|
101
|
+
**交付任何含可下单事实的行程产物(markdown 文件或长方案)前,必须调
|
|
102
|
+
gotry_fact_gate**;闸 blocked 时逐条修正为可回溯事实或降级措辞,
|
|
103
|
+
不得宣称「已验证方案」。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"schema": "gotry_airline_airports.v1",
|
|
4
|
+
"as_of": "2026-08-29",
|
|
5
|
+
"review_by": "2026-09-28",
|
|
6
|
+
"source": "issue #46 exact-date audit snapshot (trip.com 2027 日期可售快照)",
|
|
7
|
+
"note": "两张表职责不同:city_alias 是稳定词汇(解析航线上下文);carrier_airport 是 dated 快照,只作冲突检测——可下单事实的唯一来源永远是 exact-date 工具结果,本表不产事实。双机场城市的航司→机场映射过期风险高,过 review_by 后只降级使用(仍作冲突检测,结论标注快照日)。"
|
|
8
|
+
},
|
|
9
|
+
"city_alias": {
|
|
10
|
+
"香港": "HKG",
|
|
11
|
+
"普吉": "HKT",
|
|
12
|
+
"甲米": "KBV",
|
|
13
|
+
"奥南": "KBV",
|
|
14
|
+
"曼谷": "BKK",
|
|
15
|
+
"深圳": "SZX",
|
|
16
|
+
"广州": "CAN",
|
|
17
|
+
"迪拜": "DXB",
|
|
18
|
+
"昆明": "KMG",
|
|
19
|
+
"大理": "DLU",
|
|
20
|
+
"丽江": "LJG",
|
|
21
|
+
"南京": "NKG",
|
|
22
|
+
"上海": "PVG",
|
|
23
|
+
"北京": "PEK",
|
|
24
|
+
"成都": "CTU",
|
|
25
|
+
"西安": "XIY",
|
|
26
|
+
"杭州": "HGH",
|
|
27
|
+
"多哈": "DOH"
|
|
28
|
+
},
|
|
29
|
+
"carrier_airport": {
|
|
30
|
+
"BKK": {
|
|
31
|
+
"FD": "DMK",
|
|
32
|
+
"VZ": "BKK",
|
|
33
|
+
"SL": "DMK",
|
|
34
|
+
"DD": "DMK",
|
|
35
|
+
"PG": "BKK",
|
|
36
|
+
"TG": "BKK"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|