@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.20
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 +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +309 -30
- package/bin/gotry-inner.js +74 -23
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +22 -8
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +307 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/hbcli.js +28 -2
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +305 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
- package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +113 -2
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +175 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/hbcli-tests.js +38 -1
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +39 -16
- package/dist/src/benchmark-environment-bridge.js +316 -52
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +739 -150
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +7 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/hbcli.ts +53 -4
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/dsh-runtime/vendor/README.md +60 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
- package/ts/package.json +9 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +54 -15
- package/ts/src/benchmark-environment-bridge.ts +234 -46
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +492 -172
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +365 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { readLatestChannelEvents } from './channel-health.js';
|
|
7
|
+
function hbcliCandidates(homeDir) {
|
|
8
|
+
return [
|
|
9
|
+
'hbcli',
|
|
10
|
+
join(homeDir, '.local/bin/hbcli'),
|
|
11
|
+
join(homeDir, '.staicli/current/hbcli')
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
function probe(bin, args, timeoutMs = 8_000) {
|
|
15
|
+
return new Promise((resolveProbe)=>{
|
|
16
|
+
const child = spawn(bin, args, {
|
|
17
|
+
stdio: 'ignore',
|
|
18
|
+
env: process.env
|
|
19
|
+
});
|
|
20
|
+
let done = false;
|
|
21
|
+
const timer = setTimeout(()=>{
|
|
22
|
+
if (!done) {
|
|
23
|
+
done = true;
|
|
24
|
+
try {
|
|
25
|
+
child.kill('SIGKILL');
|
|
26
|
+
} catch {}
|
|
27
|
+
resolveProbe(false);
|
|
28
|
+
}
|
|
29
|
+
}, timeoutMs);
|
|
30
|
+
child.on('error', ()=>{
|
|
31
|
+
if (!done) {
|
|
32
|
+
done = true;
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
resolveProbe(false);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
child.on('exit', (code)=>{
|
|
38
|
+
if (!done) {
|
|
39
|
+
done = true;
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
resolveProbe(code === 0);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function nodeOk(version) {
|
|
47
|
+
const [maj = '0', min = '0'] = version.split('.');
|
|
48
|
+
return Number(maj) > 22 || Number(maj) === 22 && Number(min) >= 15;
|
|
49
|
+
}
|
|
50
|
+
export async function runDoctorChecks(opts = {}) {
|
|
51
|
+
const repoRoot = opts.repoRoot ? resolve(opts.repoRoot) : resolve(import.meta.dirname, '..', '..');
|
|
52
|
+
const home = opts.homeDir ?? homedir();
|
|
53
|
+
const env = opts.env ?? process.env;
|
|
54
|
+
const items = [];
|
|
55
|
+
const nodeVersion = process.versions.node;
|
|
56
|
+
items.push(nodeOk(nodeVersion) ? {
|
|
57
|
+
id: 'node',
|
|
58
|
+
label: 'Node 运行时',
|
|
59
|
+
status: 'ok',
|
|
60
|
+
detail: `Node ${nodeVersion}(≥22.15)`
|
|
61
|
+
} : {
|
|
62
|
+
id: 'node',
|
|
63
|
+
label: 'Node 运行时',
|
|
64
|
+
status: 'missing',
|
|
65
|
+
detail: `Node ${nodeVersion} 过旧(gotry 需 ≥22.15)`,
|
|
66
|
+
fix: '升级 Node.js 至 22.15+(https://nodejs.org)'
|
|
67
|
+
});
|
|
68
|
+
const extManifest = join(home, '.gotry', 'extension', 'manifest.json');
|
|
69
|
+
items.push(existsSync(extManifest) ? {
|
|
70
|
+
id: 'extension',
|
|
71
|
+
label: 'GoTry Session Bridge 扩展',
|
|
72
|
+
status: 'ok',
|
|
73
|
+
detail: `已就位(本地通道:${extManifest})`
|
|
74
|
+
} : {
|
|
75
|
+
id: 'extension',
|
|
76
|
+
label: 'GoTry Session Bridge 扩展',
|
|
77
|
+
status: 'missing',
|
|
78
|
+
detail: '本地通道未落位(~/.gotry/extension/manifest.json 不存在)。若你已从 Chrome 商店安装(自动更新,商店版 ID oeajpicc…),本项可忽略——会话检索可用性以运行时为准(gotry_session_search 的 verdict)。若未安装:应用商店一键装即可',
|
|
79
|
+
fix: '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd'
|
|
80
|
+
});
|
|
81
|
+
const venvPython = join(repoRoot, '.venv/bin/python');
|
|
82
|
+
const reachBin = join(repoRoot, '.venv/bin/agent-reach');
|
|
83
|
+
if (existsSync(reachBin)) {
|
|
84
|
+
items.push({
|
|
85
|
+
id: 'agent-reach',
|
|
86
|
+
label: 'Agent Reach(网页/社媒读取)',
|
|
87
|
+
status: 'ok',
|
|
88
|
+
detail: `已安装(${reachBin});渠道凭证选配体检:在对话里调 gotry_agent_reach {action:"status"}`
|
|
89
|
+
});
|
|
90
|
+
} else if (existsSync(venvPython)) {
|
|
91
|
+
items.push({
|
|
92
|
+
id: 'agent-reach',
|
|
93
|
+
label: 'Agent Reach(网页/社媒读取)',
|
|
94
|
+
status: 'degraded',
|
|
95
|
+
detail: '.venv 存在但缺 agent-reach 包——gotry_agent_reach / gotry_web_search 读页会失败',
|
|
96
|
+
fix: `npx gotry doctor --fix(或在包根执行: ${venvPython} -m pip install git+https://github.com/Panniantong/Agent-Reach.git)`
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
items.push({
|
|
100
|
+
id: 'agent-reach',
|
|
101
|
+
label: 'Agent Reach(网页/社媒读取)',
|
|
102
|
+
status: 'missing',
|
|
103
|
+
detail: '未安装——gotry_agent_reach / gotry_web_search(读网页)/ gotry_video_subtitle / gotry_github_search 全部不可用',
|
|
104
|
+
fix: 'npx gotry doctor --fix'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
let hbPresent = '';
|
|
108
|
+
for (const p of hbcliCandidates(home)){
|
|
109
|
+
if (p !== 'hbcli' && existsSync(p)) {
|
|
110
|
+
hbPresent = p;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
if (p === 'hbcli' && await probe(p, [
|
|
114
|
+
'version'
|
|
115
|
+
])) {
|
|
116
|
+
hbPresent = 'hbcli(PATH)';
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (hbPresent) {
|
|
121
|
+
const whoami = await probe(hbPresent === 'hbcli(PATH)' ? 'hbcli' : hbPresent, [
|
|
122
|
+
'auth',
|
|
123
|
+
'whoami'
|
|
124
|
+
]);
|
|
125
|
+
items.push(whoami ? {
|
|
126
|
+
id: 'hbcli',
|
|
127
|
+
label: 'hbcli(酒店实时源)',
|
|
128
|
+
status: 'ok',
|
|
129
|
+
detail: `已安装且凭证有效(${hbPresent})`
|
|
130
|
+
} : {
|
|
131
|
+
id: 'hbcli',
|
|
132
|
+
label: 'hbcli(酒店实时源)',
|
|
133
|
+
status: 'degraded',
|
|
134
|
+
detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
135
|
+
fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)'
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
items.push({
|
|
139
|
+
id: 'hbcli',
|
|
140
|
+
label: 'hbcli(酒店实时源)',
|
|
141
|
+
status: 'missing',
|
|
142
|
+
detail: '未安装——酒店检索降级静态包(公开渠道估算,非实时,仅覆盖内置场景)',
|
|
143
|
+
fix: 'npx gotry doctor --fix'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const flyaiKey = env.FLYAI_API_KEY?.trim();
|
|
147
|
+
const flyaiQuotaNote = !flyaiKey && opts.stateRoot ? await (async ()=>{
|
|
148
|
+
const ev = (await readLatestChannelEvents(opts.stateRoot)).get('flyai');
|
|
149
|
+
return ev?.state === 'down' ? `;最近一次试用达限: ${ev.at}(匿名共享池,正式 key 可解除)` : '';
|
|
150
|
+
})() : '';
|
|
151
|
+
items.push(flyaiKey ? {
|
|
152
|
+
id: 'flyai',
|
|
153
|
+
label: 'FlyAI(飞猪官方检索)',
|
|
154
|
+
status: 'ok',
|
|
155
|
+
detail: 'FLYAI_API_KEY 已配(正式 key,无试用额度限制)'
|
|
156
|
+
} : {
|
|
157
|
+
id: 'flyai',
|
|
158
|
+
label: 'FlyAI(飞猪官方检索)',
|
|
159
|
+
status: 'degraded',
|
|
160
|
+
detail: `未配 FLYAI_API_KEY——走匿名试用额度(共享,易达限;达限报 "Trial limit reached")${flyaiQuotaNote}`,
|
|
161
|
+
fix: '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY;无 key 期间机/火/酒检索请以 gotry_session_search(账号会话)为主'
|
|
162
|
+
});
|
|
163
|
+
const sidebarPkg = join(home, '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json');
|
|
164
|
+
items.push(existsSync(sidebarPkg) ? {
|
|
165
|
+
id: 'sidebar',
|
|
166
|
+
label: 'dsh-better-sidebar(侧栏工作台)',
|
|
167
|
+
status: 'ok',
|
|
168
|
+
detail: '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)'
|
|
169
|
+
} : {
|
|
170
|
+
id: 'sidebar',
|
|
171
|
+
label: 'dsh-better-sidebar(侧栏工作台)',
|
|
172
|
+
status: 'missing',
|
|
173
|
+
detail: '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)',
|
|
174
|
+
fix: 'npx gotry doctor --fix'
|
|
175
|
+
});
|
|
176
|
+
const calStatePath = join(home, '.gotry', 'calendar.json');
|
|
177
|
+
const calEnabled = (()=>{
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(readFileSync(calStatePath, 'utf-8'))?.enabled === true;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
})();
|
|
184
|
+
const calProfilePatch = join(home, '.dsh/profiles/web/cordis.patch.yml');
|
|
185
|
+
const calConfigured = calEnabled && existsSync(calProfilePatch) && (()=>{
|
|
186
|
+
try {
|
|
187
|
+
const content = readFileSync(calProfilePatch, 'utf-8');
|
|
188
|
+
return /calendar/.test(content) && /username\s*:/.test(content);
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
items.push(!calEnabled ? {
|
|
194
|
+
id: 'calendar',
|
|
195
|
+
label: 'dsh-calendar(日历工作窗口)',
|
|
196
|
+
status: 'ok',
|
|
197
|
+
detail: '默认未挂载(D-9:未配置的日历工具不进工具箱;工作窗口由访谈覆盖,不影响任何检索)'
|
|
198
|
+
} : calConfigured ? {
|
|
199
|
+
id: 'calendar',
|
|
200
|
+
label: 'dsh-calendar(日历工作窗口)',
|
|
201
|
+
status: 'ok',
|
|
202
|
+
detail: `已挂载且已配置(${calStatePath})`
|
|
203
|
+
} : {
|
|
204
|
+
id: 'calendar',
|
|
205
|
+
label: 'dsh-calendar(日历工作窗口)',
|
|
206
|
+
status: 'degraded',
|
|
207
|
+
detail: '已挂载但 calendar 未配置 username——日历工具会话中会报「未配置」',
|
|
208
|
+
fix: `npx gotry setup calendar --off(恢复默认不挂载),或在 ${calProfilePatch} 覆盖 calendar 行 config 填 username(指引: npx gotry setup calendar --status)`
|
|
209
|
+
});
|
|
210
|
+
const rootRequire = createRequire(join(repoRoot, 'package.json'));
|
|
211
|
+
const mapCandidates = [
|
|
212
|
+
join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
|
|
213
|
+
join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js'),
|
|
214
|
+
join(repoRoot, 'node_modules/dsh-map-tools/package.json'),
|
|
215
|
+
join(repoRoot, 'ts/node_modules/dsh-map-tools/package.json'),
|
|
216
|
+
join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json')
|
|
217
|
+
];
|
|
218
|
+
let mapHit = mapCandidates.find((p)=>existsSync(p));
|
|
219
|
+
if (!mapHit) {
|
|
220
|
+
try {
|
|
221
|
+
mapHit = rootRequire.resolve('dsh-map-tools');
|
|
222
|
+
} catch {
|
|
223
|
+
mapHit = undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
items.push(mapHit ? {
|
|
227
|
+
id: 'map-tools',
|
|
228
|
+
label: 'dsh-map-tools(地图/路线/POI)',
|
|
229
|
+
status: 'ok',
|
|
230
|
+
detail: `已就位(${mapHit})——地图工具可用(零 key,走 OSM/OSRM)`
|
|
231
|
+
} : {
|
|
232
|
+
id: 'map-tools',
|
|
233
|
+
label: 'dsh-map-tools(地图/路线/POI)',
|
|
234
|
+
status: 'missing',
|
|
235
|
+
detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
|
|
236
|
+
fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)'
|
|
237
|
+
});
|
|
238
|
+
const askCandidates = [
|
|
239
|
+
join(repoRoot, 'ts/dsh-runtime/vendor/deepseek-ai-dsh-tool-ask-user/package.json'),
|
|
240
|
+
join(repoRoot, 'node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
|
|
241
|
+
join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json')
|
|
242
|
+
];
|
|
243
|
+
let askHit = askCandidates.find((p)=>existsSync(p));
|
|
244
|
+
if (!askHit) {
|
|
245
|
+
try {
|
|
246
|
+
const reqFromDsh = createRequire(rootRequire.resolve('@deepseek-ai/dsh/lib/bin.js'));
|
|
247
|
+
askHit = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user');
|
|
248
|
+
} catch {
|
|
249
|
+
try {
|
|
250
|
+
askHit = rootRequire.resolve('@deepseek-ai/dsh-tool-ask-user');
|
|
251
|
+
} catch {
|
|
252
|
+
askHit = undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
items.push(askHit ? {
|
|
257
|
+
id: 'ask-user',
|
|
258
|
+
label: 'dsh-tool-ask-user(结构化澄清卡)',
|
|
259
|
+
status: 'ok',
|
|
260
|
+
detail: `已就位(${askHit})——ask_user_question 澄清卡可用(web 原生卡片;headless+TTY 用 stdio 提供方)`
|
|
261
|
+
} : {
|
|
262
|
+
id: 'ask-user',
|
|
263
|
+
label: 'dsh-tool-ask-user(结构化澄清卡)',
|
|
264
|
+
status: 'missing',
|
|
265
|
+
detail: '未解析——启动时澄清卡注入被静默剔除,模型只能散文追问(人格契约 (5) 退化文本形态)',
|
|
266
|
+
fix: '重装 @danceiny/gotry——该依赖随 dsh 闭包自带,缺失多为安装不完整'
|
|
267
|
+
});
|
|
268
|
+
items.push({
|
|
269
|
+
id: 'llm-key',
|
|
270
|
+
label: 'LLM key',
|
|
271
|
+
status: 'ok',
|
|
272
|
+
detail: '由 dsh 宿主 UI 管理——不在 doctor 体检范围(gotry 不接触、不回显凭证)'
|
|
273
|
+
});
|
|
274
|
+
const broken = items.filter((i)=>i.status !== 'ok' && i.id !== 'llm-key');
|
|
275
|
+
const summary = broken.length === 0 ? `体检通过:${items.length} 项全部就绪(可选依赖齐,LLM key 归 dsh 宿主管)。` : `体检发现 ${broken.length} 项待处理:${broken.map((i)=>`${i.label}(${i.status === 'missing' ? '未装' : '半可用'})`).join('、')}。补装:终端跑 npx gotry doctor --fix,或按各项 fix 指引逐项处理。`;
|
|
276
|
+
return {
|
|
277
|
+
ok: broken.length === 0,
|
|
278
|
+
items,
|
|
279
|
+
summary
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const fixCell = (fix)=>!fix ? '—' : /^(npx|hbcli|curl|pip|python|\$)/.test(fix) ? `\`${fix}\`` : fix;
|
|
283
|
+
export function renderDoctorReportMd(report, now = new Date()) {
|
|
284
|
+
const icon = (s)=>s === 'ok' ? '✅' : s === 'degraded' ? '⚠️' : '❌';
|
|
285
|
+
const lines = [
|
|
286
|
+
'# GoTry 依赖体检报告(doctor)',
|
|
287
|
+
'',
|
|
288
|
+
`> 生成于 ${now.toISOString()};重新生成:终端 \`npx gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
|
|
289
|
+
'',
|
|
290
|
+
'| 状态 | 依赖 | 现状 | 修复指引 |',
|
|
291
|
+
'|---|---|---|---|',
|
|
292
|
+
...report.items.map((i)=>`| ${icon(i.status)} | ${i.label} | ${i.detail} | ${fixCell(i.fix)} |`),
|
|
293
|
+
'',
|
|
294
|
+
`**结论**:${report.summary}`,
|
|
295
|
+
'',
|
|
296
|
+
'---',
|
|
297
|
+
'',
|
|
298
|
+
'- `npx gotry doctor` 随时可重跑(只读,不改任何东西);',
|
|
299
|
+
'- `npx gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
|
|
300
|
+
'- LLM key 由 dsh 宿主 UI 管理,gotry 永不体检、不回显。',
|
|
301
|
+
''
|
|
302
|
+
];
|
|
303
|
+
return lines.join('\n');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
//# sourceURL=ts/capabilities/doctor.ts
|
|
@@ -1,9 +1,76 @@
|
|
|
1
1
|
import { CircuitBreaker, withRetry } from './resilience.js';
|
|
2
2
|
import { flyaiSearch } from './flyai.js';
|
|
3
3
|
import { checkAvail, hotelRates, searchHotels } from './hbcli.js';
|
|
4
|
-
import { sessionFlightSearch } from './session-search.js';
|
|
4
|
+
import { sessionFlightSearch, sessionHotelSearch, sessionTrainSearch } from './session-search.js';
|
|
5
5
|
import { geocodePlace, getClimate, getForecast } from './weather.js';
|
|
6
6
|
import { verifyFlight } from './opensky.js';
|
|
7
|
+
import { anythingSearch } from './anything.js';
|
|
8
|
+
import { continentListUrl, countryPageUrl, extractVisaSection, fetchVisaPolicyPage, listCountryPaths, policyFactsFromMfa } from './visa-policy.js';
|
|
9
|
+
import { readUrl, reach, reachStatus } from './agent-reach.js';
|
|
10
|
+
import { githubSearch, videoSubtitle } from './agent-reach-deep.js';
|
|
11
|
+
import { sessionLogin } from './session-login.js';
|
|
12
|
+
async function fetchVisaPolicyEntries(p) {
|
|
13
|
+
const started = Date.now();
|
|
14
|
+
const fetchedAt = new Date().toISOString();
|
|
15
|
+
const countries = [];
|
|
16
|
+
const facts = [];
|
|
17
|
+
const targets = [];
|
|
18
|
+
if (p.country) {
|
|
19
|
+
targets.push({
|
|
20
|
+
continent: p.continent ?? 'yz_645708',
|
|
21
|
+
country: p.country
|
|
22
|
+
});
|
|
23
|
+
} else if (p.continent) {
|
|
24
|
+
const listUrl = continentListUrl(p.continent);
|
|
25
|
+
if (!listUrl) return {
|
|
26
|
+
ok: false,
|
|
27
|
+
via: 'visa-policy-error',
|
|
28
|
+
evidence: `[visa-policy@${fetchedAt}] 非法洲路径`,
|
|
29
|
+
latencyMs: 0,
|
|
30
|
+
facts: [],
|
|
31
|
+
countries: [],
|
|
32
|
+
error: 'invalid_continent'
|
|
33
|
+
};
|
|
34
|
+
const page = await fetchVisaPolicyPage(listUrl, p.timeoutMs);
|
|
35
|
+
if (!page.ok || !page.html) return {
|
|
36
|
+
ok: false,
|
|
37
|
+
via: 'visa-policy-error',
|
|
38
|
+
evidence: `[visa-policy@${fetchedAt}] 洲列表抓取失败: ${page.error ?? '?'}`,
|
|
39
|
+
latencyMs: 0,
|
|
40
|
+
facts: [],
|
|
41
|
+
countries: [],
|
|
42
|
+
error: page.error
|
|
43
|
+
};
|
|
44
|
+
for (const c of listCountryPaths(page.html, p.continent).slice(0, Math.max(1, Math.min(10, p.limit ?? 10))))targets.push({
|
|
45
|
+
continent: p.continent,
|
|
46
|
+
country: c
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
for (const t of targets){
|
|
50
|
+
const url = countryPageUrl(t.continent, t.country);
|
|
51
|
+
if (!url) continue;
|
|
52
|
+
const page = await fetchVisaPolicyPage(url, p.timeoutMs);
|
|
53
|
+
countries.push(t.country);
|
|
54
|
+
if (!page.ok || !page.html) continue;
|
|
55
|
+
const section = extractVisaSection(page.html);
|
|
56
|
+
if (!section) continue;
|
|
57
|
+
facts.push(...policyFactsFromMfa({
|
|
58
|
+
countryLabel: t.country,
|
|
59
|
+
countryPath: t.country,
|
|
60
|
+
section,
|
|
61
|
+
fetchedAt
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
const evidence = facts.length > 0 ? `[visa-policy:cs-mfa@${fetchedAt}] ${facts.length} 条政策事实(抓取 ${countries.length} 国)` : `[visa-policy@${fetchedAt}] 0 条(页面缺「签证入境」章节或抓取失败;不落负事实)`;
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
via: 'cs-mfa',
|
|
68
|
+
evidence,
|
|
69
|
+
latencyMs: Date.now() - started,
|
|
70
|
+
facts,
|
|
71
|
+
countries
|
|
72
|
+
};
|
|
73
|
+
}
|
|
7
74
|
const DEFAULT_HANDLERS = {
|
|
8
75
|
FLYAI_SEARCH: (p)=>flyaiSearch(p),
|
|
9
76
|
HBCLI_HOTEL_SEARCH: (p)=>searchHotels({
|
|
@@ -35,6 +102,8 @@ const DEFAULT_HANDLERS = {
|
|
|
35
102
|
timeoutMs: p.timeoutMs
|
|
36
103
|
}),
|
|
37
104
|
SESSION_FLIGHT_SEARCH: (p)=>sessionFlightSearch(p),
|
|
105
|
+
SESSION_HOTEL_SEARCH: (p)=>sessionHotelSearch(p),
|
|
106
|
+
SESSION_TRAIN_SEARCH: (p)=>sessionTrainSearch(p),
|
|
38
107
|
WEATHER_GEOCODE: (p)=>geocodePlace(p.name, {
|
|
39
108
|
count: p.count,
|
|
40
109
|
timeoutMs: p.timeoutMs
|
|
@@ -52,7 +121,24 @@ const DEFAULT_HANDLERS = {
|
|
|
52
121
|
}, p.month, {
|
|
53
122
|
timeoutMs: p.timeoutMs
|
|
54
123
|
}),
|
|
55
|
-
OPENSKY_FLIGHT_VERIFY: (p)=>verifyFlight(p)
|
|
124
|
+
OPENSKY_FLIGHT_VERIFY: (p)=>verifyFlight(p),
|
|
125
|
+
ANYTHING_SEARCH: (p)=>anythingSearch(p),
|
|
126
|
+
WEB_READ: (p)=>readUrl({
|
|
127
|
+
url: p.url,
|
|
128
|
+
timeoutMs: p.timeoutMs
|
|
129
|
+
}),
|
|
130
|
+
GITHUB_SEARCH: (p)=>githubSearch(p),
|
|
131
|
+
VIDEO_SUBTITLE: (p)=>videoSubtitle(p),
|
|
132
|
+
AGENT_REACH: (p)=>p.action === 'status' || !p.action && !p.channel ? reachStatus(p.timeoutMs) : reach({
|
|
133
|
+
channel: p.channel ?? '',
|
|
134
|
+
method: p.method ?? '',
|
|
135
|
+
args: p.args,
|
|
136
|
+
timeoutMs: p.timeoutMs
|
|
137
|
+
}),
|
|
138
|
+
SESSION_LOGIN: (p)=>sessionLogin({
|
|
139
|
+
waitMs: typeof p.waitSeconds === 'number' ? p.waitSeconds * 1000 : undefined
|
|
140
|
+
}),
|
|
141
|
+
VISA_POLICY_FETCH: (p)=>fetchVisaPolicyEntries(p)
|
|
56
142
|
};
|
|
57
143
|
const defaultIsFailure = (r)=>r?.ok === false;
|
|
58
144
|
const flyaiTransient = (msg)=>msg.length > 0 && !/Sentinel/i.test(msg) && /timeout|timed out|ECONNRESET|ECONNREFUSED|ETIMEDOUT|socket|HTTP 5/i.test(msg);
|
|
@@ -65,6 +151,15 @@ const API_BREAKER = {
|
|
|
65
151
|
failureThreshold: 3,
|
|
66
152
|
openMs: 30_000
|
|
67
153
|
};
|
|
154
|
+
const hbcliTimeout = (r)=>{
|
|
155
|
+
const msg = String((r ?? {}).error ?? '');
|
|
156
|
+
return msg.length > 0 && /timeout/i.test(msg);
|
|
157
|
+
};
|
|
158
|
+
const HBCLI_RETRY = {
|
|
159
|
+
maxAttempts: 2,
|
|
160
|
+
baseDelayMs: 300,
|
|
161
|
+
maxDelayMs: 1_000
|
|
162
|
+
};
|
|
68
163
|
const SPECS = {
|
|
69
164
|
FLYAI_SEARCH: {
|
|
70
165
|
channel: 'cli',
|
|
@@ -85,7 +180,8 @@ const SPECS = {
|
|
|
85
180
|
},
|
|
86
181
|
HBCLI_HOTEL_SEARCH: {
|
|
87
182
|
channel: 'cli',
|
|
88
|
-
retry:
|
|
183
|
+
retry: HBCLI_RETRY,
|
|
184
|
+
isRetryable: (r)=>hbcliTimeout(r),
|
|
89
185
|
breaker: {
|
|
90
186
|
failureThreshold: 3,
|
|
91
187
|
openMs: 60_000
|
|
@@ -94,7 +190,8 @@ const SPECS = {
|
|
|
94
190
|
},
|
|
95
191
|
HBCLI_HOTEL_RATES: {
|
|
96
192
|
channel: 'cli',
|
|
97
|
-
retry:
|
|
193
|
+
retry: HBCLI_RETRY,
|
|
194
|
+
isRetryable: (r)=>hbcliTimeout(r),
|
|
98
195
|
breaker: {
|
|
99
196
|
failureThreshold: 3,
|
|
100
197
|
openMs: 60_000
|
|
@@ -103,7 +200,8 @@ const SPECS = {
|
|
|
103
200
|
},
|
|
104
201
|
HBCLI_CHECK_AVAIL: {
|
|
105
202
|
channel: 'cli',
|
|
106
|
-
retry:
|
|
203
|
+
retry: HBCLI_RETRY,
|
|
204
|
+
isRetryable: (r)=>hbcliTimeout(r),
|
|
107
205
|
breaker: {
|
|
108
206
|
failureThreshold: 3,
|
|
109
207
|
openMs: 60_000
|
|
@@ -116,6 +214,18 @@ const SPECS = {
|
|
|
116
214
|
breaker: null,
|
|
117
215
|
isFailure: defaultIsFailure
|
|
118
216
|
},
|
|
217
|
+
SESSION_HOTEL_SEARCH: {
|
|
218
|
+
channel: 'browser',
|
|
219
|
+
retry: null,
|
|
220
|
+
breaker: null,
|
|
221
|
+
isFailure: defaultIsFailure
|
|
222
|
+
},
|
|
223
|
+
SESSION_TRAIN_SEARCH: {
|
|
224
|
+
channel: 'browser',
|
|
225
|
+
retry: null,
|
|
226
|
+
breaker: null,
|
|
227
|
+
isFailure: defaultIsFailure
|
|
228
|
+
},
|
|
119
229
|
WEATHER_GEOCODE: {
|
|
120
230
|
channel: 'api',
|
|
121
231
|
retry: API_RETRY,
|
|
@@ -140,6 +250,69 @@ const SPECS = {
|
|
|
140
250
|
breaker: API_BREAKER,
|
|
141
251
|
isRetryable: (_r, e)=>e != null || (_r ?? {}).verdict === 'unavailable',
|
|
142
252
|
isFailure: (r)=>r.via === 'opensky-error'
|
|
253
|
+
},
|
|
254
|
+
ANYTHING_SEARCH: {
|
|
255
|
+
channel: 'cli',
|
|
256
|
+
retry: HBCLI_RETRY,
|
|
257
|
+
isRetryable: (r)=>hbcliTimeout(r),
|
|
258
|
+
breaker: {
|
|
259
|
+
failureThreshold: 3,
|
|
260
|
+
openMs: 60_000
|
|
261
|
+
},
|
|
262
|
+
isFailure: (r)=>r.via === 'hbcli-anything-error'
|
|
263
|
+
},
|
|
264
|
+
WEB_READ: {
|
|
265
|
+
channel: 'api',
|
|
266
|
+
retry: API_RETRY,
|
|
267
|
+
breaker: API_BREAKER,
|
|
268
|
+
isFailure: (r)=>r.via === 'r.jina.ai-error'
|
|
269
|
+
},
|
|
270
|
+
GITHUB_SEARCH: {
|
|
271
|
+
channel: 'cli',
|
|
272
|
+
retry: HBCLI_RETRY,
|
|
273
|
+
isRetryable: (r)=>r.verdict === 'timeout',
|
|
274
|
+
breaker: {
|
|
275
|
+
failureThreshold: 3,
|
|
276
|
+
openMs: 60_000
|
|
277
|
+
},
|
|
278
|
+
isFailure: (r)=>{
|
|
279
|
+
const v = r.verdict;
|
|
280
|
+
return v === 'error' || v === 'timeout';
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
VIDEO_SUBTITLE: {
|
|
284
|
+
channel: 'cli',
|
|
285
|
+
retry: HBCLI_RETRY,
|
|
286
|
+
isRetryable: (r)=>r.verdict === 'timeout',
|
|
287
|
+
breaker: {
|
|
288
|
+
failureThreshold: 3,
|
|
289
|
+
openMs: 60_000
|
|
290
|
+
},
|
|
291
|
+
isFailure: (r)=>{
|
|
292
|
+
const v = r.verdict;
|
|
293
|
+
return v === 'error' || v === 'timeout';
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
AGENT_REACH: {
|
|
297
|
+
channel: 'cli',
|
|
298
|
+
retry: null,
|
|
299
|
+
breaker: null,
|
|
300
|
+
isFailure: (r)=>r.verdict === 'error'
|
|
301
|
+
},
|
|
302
|
+
SESSION_LOGIN: {
|
|
303
|
+
channel: 'browser',
|
|
304
|
+
retry: null,
|
|
305
|
+
breaker: null,
|
|
306
|
+
isFailure: defaultIsFailure
|
|
307
|
+
},
|
|
308
|
+
VISA_POLICY_FETCH: {
|
|
309
|
+
channel: 'api',
|
|
310
|
+
retry: null,
|
|
311
|
+
breaker: {
|
|
312
|
+
failureThreshold: 2,
|
|
313
|
+
openMs: 300_000
|
|
314
|
+
},
|
|
315
|
+
isFailure: defaultIsFailure
|
|
143
316
|
}
|
|
144
317
|
};
|
|
145
318
|
export function declinedObservation(effect, trace) {
|
|
@@ -150,6 +150,19 @@ export async function flyaiSearch(q) {
|
|
|
150
150
|
});
|
|
151
151
|
const latencyMs = Date.now() - started;
|
|
152
152
|
if (r.error || r.code !== 0) {
|
|
153
|
+
const upstream = `${r.stderr}\n${r.stdout}`;
|
|
154
|
+
if (/Trial limit reached|HTTP\s*429|\b429\b/.test(upstream)) {
|
|
155
|
+
return {
|
|
156
|
+
...base,
|
|
157
|
+
latencyMs,
|
|
158
|
+
ok: false,
|
|
159
|
+
via: 'flyai-error',
|
|
160
|
+
verdict: 'needs-setup',
|
|
161
|
+
evidence: `[实时API:flyai@error@${ts}] trial quota exhausted`,
|
|
162
|
+
error: `FlyAI 匿名试用额度已用尽(上游 429):${upstream.replace(/\s+/g, ' ').slice(0, 160)}`,
|
|
163
|
+
setup: 'FlyAI 试用额度达限——到 flyai.open.fliggy.com 控制台申请正式 API Key,配入环境变量 FLYAI_API_KEY 即恢复;本会话请勿重试 gotry_flyai_search,机/火/酒改走 gotry_session_search(账号会话)或 web 检索推进。'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
153
166
|
return {
|
|
154
167
|
...base,
|
|
155
168
|
latencyMs,
|
|
@@ -112,6 +112,25 @@ export async function callHbcliJson(args, opts = {}) {
|
|
|
112
112
|
}
|
|
113
113
|
return last;
|
|
114
114
|
}
|
|
115
|
+
function i18nName(v) {
|
|
116
|
+
if (!v) return '';
|
|
117
|
+
if (typeof v === 'string') return v;
|
|
118
|
+
return v.zh || v.en || v.ar || '';
|
|
119
|
+
}
|
|
120
|
+
function hotelLines(result, max = 8) {
|
|
121
|
+
const list = result?.list;
|
|
122
|
+
if (!Array.isArray(list) || list.length === 0) return '';
|
|
123
|
+
const lines = [];
|
|
124
|
+
for (const item of list.slice(0, max)){
|
|
125
|
+
const h = item;
|
|
126
|
+
const name = i18nName(h.name) || `hotel ${h.id ?? '?'}`;
|
|
127
|
+
const star = typeof h.star === 'number' && h.star > 0 ? ` ${h.star}★` : '';
|
|
128
|
+
const price = h.minPrice && typeof h.minPrice.amount === 'number' ? ` 低至 ${h.minPrice.amount}${h.minPrice.currency ? ` ${h.minPrice.currency}` : ''}` : '';
|
|
129
|
+
lines.push(`- id=${h.id ?? '?'} ${name}${star}${price}`);
|
|
130
|
+
}
|
|
131
|
+
if (list.length > max) lines.push(`…(共 ${list.length} 家,仅列前 ${max})`);
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
115
134
|
export async function searchHotels(query, opts = {}) {
|
|
116
135
|
const hbArgs = [
|
|
117
136
|
'search',
|
|
@@ -121,6 +140,8 @@ export async function searchHotels(query, opts = {}) {
|
|
|
121
140
|
'10'
|
|
122
141
|
];
|
|
123
142
|
if (query.destination) hbArgs.push('--destination-name', query.destination);
|
|
143
|
+
if (query.checkIn) hbArgs.push('--check-in', query.checkIn);
|
|
144
|
+
if (query.checkOut) hbArgs.push('--check-out', query.checkOut);
|
|
124
145
|
if (query.adults) hbArgs.push('--room-occupancies', JSON.stringify([
|
|
125
146
|
{
|
|
126
147
|
adultCount: query.adults,
|
|
@@ -129,10 +150,15 @@ export async function searchHotels(query, opts = {}) {
|
|
|
129
150
|
]));
|
|
130
151
|
const live = await callHbcliJson(hbArgs, opts);
|
|
131
152
|
if (live.via === 'hbcli-realtime') {
|
|
153
|
+
const list = live.result?.list;
|
|
154
|
+
const n = Array.isArray(list) ? list.length : 0;
|
|
155
|
+
const dateTag = query.checkIn ? `入住 ${query.checkIn}${query.checkOut ? ` → 退房 ${query.checkOut}` : ''}` : '未传日期(上游按当前窗口价)';
|
|
156
|
+
const lines = hotelLines(live.result);
|
|
157
|
+
const summary = `${query.destination}:hbcli 实时 ${n} 家(${dateTag})${lines ? `\n${lines}` : ''}\n${live.evidence}`;
|
|
132
158
|
return {
|
|
133
159
|
...live,
|
|
134
160
|
hotels: live.result,
|
|
135
|
-
summary
|
|
161
|
+
summary
|
|
136
162
|
};
|
|
137
163
|
}
|
|
138
164
|
const rawReason = live.error ?? live.via;
|
|
@@ -151,7 +177,7 @@ export async function searchHotels(query, opts = {}) {
|
|
|
151
177
|
hotels: {
|
|
152
178
|
stays: matched
|
|
153
179
|
},
|
|
154
|
-
summary: `${query.destination}:hbcli 实时源不可用(${reason}),已降级到静态包(公开渠道估算,非实时),命中 ${matched.length}
|
|
180
|
+
summary: `${query.destination}:hbcli 实时源不可用(${reason}),已降级到静态包(公开渠道估算,非实时),命中 ${matched.length} 个住宿块:\n${JSON.stringify(matched).slice(0, 1600)}`
|
|
155
181
|
};
|
|
156
182
|
}
|
|
157
183
|
return {
|