@danceiny/gotry 0.0.1-rc.19 → 0.0.1-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/README.zh-CN.md +15 -4
- package/bin/gotry-bootstrap.js +136 -45
- package/bin/gotry-inner.js +11 -6
- package/cordis.gotry-patch.yml +19 -8
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/doctor.js +113 -19
- package/dist/capabilities/hbcli.js +28 -2
- package/dist/capabilities/session/health-watch.js +10 -3
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +2 -1
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +178 -33
- package/dist/scripts/benchmark-environment-bridge-tests.js +1480 -271
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +211 -2
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +132 -2
- package/dist/scripts/bootstrap-tests.js +30 -3
- package/dist/scripts/doctor-tests.js +55 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -1
- package/dist/scripts/hbcli-tests.js +38 -1
- package/dist/scripts/health-watch-cli.js +9 -1
- package/dist/scripts/map-tools-vendor-package-proof.js +276 -0
- package/dist/scripts/persona-surface-guard-tests.js +8 -3
- package/dist/src/benchmark-agent-conformance.js +187 -17
- package/dist/src/benchmark-environment-bridge.js +287 -102
- package/dist/src/booking-surface/dsh-planner.js +24 -8
- package/dist/src/index.js +5 -2
- package/extension/manifest.json +2 -2
- package/package.json +3 -1
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/hbcli.ts +53 -4
- package/ts/capabilities/session/health-watch.ts +9 -2
- 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 +17 -1
- package/ts/scripts/map-tools-vendor-package-proof.ts +268 -0
- package/ts/src/benchmark-agent-conformance.ts +175 -13
- package/ts/src/benchmark-environment-bridge.ts +220 -66
- package/ts/src/booking-surface/dsh-planner.ts +25 -13
- package/ts/src/index.ts +4 -2
- package/ts/src/turn-deadline.ts +4 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { homedir } from 'node:os';
|
|
4
5
|
import { join, resolve } from 'node:path';
|
|
5
6
|
import { readLatestChannelEvents } from './channel-health.js';
|
|
@@ -42,10 +43,68 @@ function probe(bin, args, timeoutMs = 8_000) {
|
|
|
42
43
|
});
|
|
43
44
|
});
|
|
44
45
|
}
|
|
46
|
+
function probeStdout(bin, args, timeoutMs = 8_000) {
|
|
47
|
+
return new Promise((resolveProbe)=>{
|
|
48
|
+
const child = spawn(bin, args, {
|
|
49
|
+
stdio: [
|
|
50
|
+
'ignore',
|
|
51
|
+
'pipe',
|
|
52
|
+
'ignore'
|
|
53
|
+
],
|
|
54
|
+
env: process.env
|
|
55
|
+
});
|
|
56
|
+
let out = '';
|
|
57
|
+
let done = false;
|
|
58
|
+
const timer = setTimeout(()=>{
|
|
59
|
+
if (!done) {
|
|
60
|
+
done = true;
|
|
61
|
+
try {
|
|
62
|
+
child.kill('SIGKILL');
|
|
63
|
+
} catch {}
|
|
64
|
+
resolveProbe(null);
|
|
65
|
+
}
|
|
66
|
+
}, timeoutMs);
|
|
67
|
+
child.on('error', ()=>{
|
|
68
|
+
if (!done) {
|
|
69
|
+
done = true;
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
resolveProbe(null);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
child.stdout?.on('data', (d)=>{
|
|
75
|
+
out += d.toString();
|
|
76
|
+
});
|
|
77
|
+
child.on('exit', (code)=>{
|
|
78
|
+
if (!done) {
|
|
79
|
+
done = true;
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolveProbe(code === 0 ? out.trim() : null);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function parseVersion(v) {
|
|
87
|
+
const m = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
88
|
+
return m ? [
|
|
89
|
+
Number(m[1]),
|
|
90
|
+
Number(m[2]),
|
|
91
|
+
Number(m[3])
|
|
92
|
+
] : null;
|
|
93
|
+
}
|
|
94
|
+
function versionAtLeast(v, min) {
|
|
95
|
+
const a = v;
|
|
96
|
+
const b = parseVersion(min);
|
|
97
|
+
if (!a || !b) return false;
|
|
98
|
+
for(let i = 0; i < 3; i++){
|
|
99
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
45
103
|
export function nodeOk(version) {
|
|
46
104
|
const [maj = '0', min = '0'] = version.split('.');
|
|
47
105
|
return Number(maj) > 22 || Number(maj) === 22 && Number(min) >= 15;
|
|
48
106
|
}
|
|
107
|
+
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3';
|
|
49
108
|
export async function runDoctorChecks(opts = {}) {
|
|
50
109
|
const repoRoot = opts.repoRoot ? resolve(opts.repoRoot) : resolve(import.meta.dirname, '..', '..');
|
|
51
110
|
const home = opts.homeDir ?? homedir();
|
|
@@ -117,22 +176,36 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
117
176
|
}
|
|
118
177
|
}
|
|
119
178
|
if (hbPresent) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
'
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
const hbCmd = hbPresent === 'hbcli(PATH)' ? 'hbcli' : hbPresent;
|
|
180
|
+
const v = parseVersion(await probeStdout(hbCmd, [
|
|
181
|
+
'--version'
|
|
182
|
+
]));
|
|
183
|
+
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
184
|
+
items.push({
|
|
185
|
+
id: 'hbcli',
|
|
186
|
+
label: 'hbcli(酒店实时源)',
|
|
187
|
+
status: 'missing',
|
|
188
|
+
detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——trade.* / search/checkAvail 会 401(issue #142)`,
|
|
189
|
+
fix: 'npm install -g staicli --registry=https://registry.npmjs.org/'
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
const whoami = await probe(hbCmd, [
|
|
193
|
+
'auth',
|
|
194
|
+
'whoami'
|
|
195
|
+
]);
|
|
196
|
+
items.push(whoami ? {
|
|
197
|
+
id: 'hbcli',
|
|
198
|
+
label: 'hbcli(酒店实时源)',
|
|
199
|
+
status: 'ok',
|
|
200
|
+
detail: `已安装且凭证有效(${hbPresent}, v${v ? v.join('.') : '?'})`
|
|
201
|
+
} : {
|
|
202
|
+
id: 'hbcli',
|
|
203
|
+
label: 'hbcli(酒店实时源)',
|
|
204
|
+
status: 'degraded',
|
|
205
|
+
detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
206
|
+
fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
136
209
|
} else {
|
|
137
210
|
items.push({
|
|
138
211
|
id: 'hbcli',
|
|
@@ -206,13 +279,22 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
206
279
|
detail: '已挂载但 calendar 未配置 username——日历工具会话中会报「未配置」',
|
|
207
280
|
fix: `npx gotry setup calendar --off(恢复默认不挂载),或在 ${calProfilePatch} 覆盖 calendar 行 config 填 username(指引: npx gotry setup calendar --status)`
|
|
208
281
|
});
|
|
282
|
+
const rootRequire = createRequire(join(repoRoot, 'package.json'));
|
|
209
283
|
const mapCandidates = [
|
|
284
|
+
join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
|
|
210
285
|
join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js'),
|
|
211
286
|
join(repoRoot, 'node_modules/dsh-map-tools/package.json'),
|
|
212
287
|
join(repoRoot, 'ts/node_modules/dsh-map-tools/package.json'),
|
|
213
288
|
join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json')
|
|
214
289
|
];
|
|
215
|
-
|
|
290
|
+
let mapHit = mapCandidates.find((p)=>existsSync(p));
|
|
291
|
+
if (!mapHit) {
|
|
292
|
+
try {
|
|
293
|
+
mapHit = rootRequire.resolve('dsh-map-tools');
|
|
294
|
+
} catch {
|
|
295
|
+
mapHit = undefined;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
216
298
|
items.push(mapHit ? {
|
|
217
299
|
id: 'map-tools',
|
|
218
300
|
label: 'dsh-map-tools(地图/路线/POI)',
|
|
@@ -223,14 +305,26 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
223
305
|
label: 'dsh-map-tools(地图/路线/POI)',
|
|
224
306
|
status: 'missing',
|
|
225
307
|
detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
|
|
226
|
-
fix: '
|
|
308
|
+
fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)'
|
|
227
309
|
});
|
|
228
310
|
const askCandidates = [
|
|
229
311
|
join(repoRoot, 'ts/dsh-runtime/vendor/deepseek-ai-dsh-tool-ask-user/package.json'),
|
|
230
312
|
join(repoRoot, 'node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
|
|
231
313
|
join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json')
|
|
232
314
|
];
|
|
233
|
-
|
|
315
|
+
let askHit = askCandidates.find((p)=>existsSync(p));
|
|
316
|
+
if (!askHit) {
|
|
317
|
+
try {
|
|
318
|
+
const reqFromDsh = createRequire(rootRequire.resolve('@deepseek-ai/dsh/lib/bin.js'));
|
|
319
|
+
askHit = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user');
|
|
320
|
+
} catch {
|
|
321
|
+
try {
|
|
322
|
+
askHit = rootRequire.resolve('@deepseek-ai/dsh-tool-ask-user');
|
|
323
|
+
} catch {
|
|
324
|
+
askHit = undefined;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
234
328
|
items.push(askHit ? {
|
|
235
329
|
id: 'ask-user',
|
|
236
330
|
label: 'dsh-tool-ask-user(结构化澄清卡)',
|
|
@@ -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 {
|
|
@@ -73,10 +73,17 @@ export function startExtensionHealthWatch(opts = {}) {
|
|
|
73
73
|
const businessProbe = opts.businessProbe ?? true;
|
|
74
74
|
const probe = opts.probe ?? (async ()=>{
|
|
75
75
|
const bridge = await import('./extension-bridge.js');
|
|
76
|
-
await bridge.getOrCreateSessionBridge({
|
|
77
|
-
keepBridge: opts.keepBridge === true
|
|
76
|
+
const created = await bridge.getOrCreateSessionBridge({
|
|
77
|
+
keepBridge: opts.keepBridge === true,
|
|
78
|
+
ports: opts.ports
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
const probeOpts = created.ok && opts.ports ? {
|
|
81
|
+
...opts,
|
|
82
|
+
ports: [
|
|
83
|
+
created.bridge.port
|
|
84
|
+
]
|
|
85
|
+
} : opts;
|
|
86
|
+
return combinedProbe(probeOpts, businessProbe);
|
|
80
87
|
});
|
|
81
88
|
const now = opts.now ?? Date.now;
|
|
82
89
|
const state = {
|
|
@@ -117,7 +117,8 @@ async function startRelay() {
|
|
|
117
117
|
if (toolNames(body).includes(TOOL)) {
|
|
118
118
|
plannerBodies.push(body);
|
|
119
119
|
const plannerIndex = plannerBodies.length;
|
|
120
|
-
const
|
|
120
|
+
const realToolMessages = (body.messages ?? []).filter((message)=>message.role === 'tool');
|
|
121
|
+
const alreadyHandedOff = realToolMessages.some((message)=>JSON.stringify(message).includes(HANDOFF));
|
|
121
122
|
const stall = !alreadyHandedOff && plannerIndex === 1 ? RELAY_STALL_MS : 0;
|
|
122
123
|
setTimeout(()=>{
|
|
123
124
|
if (!alreadyHandedOff && plannerIndex === 1) {
|
|
@@ -11,11 +11,17 @@ try {
|
|
|
11
11
|
await writeFile(p, `#!/bin/sh
|
|
12
12
|
cat <<'JSON'
|
|
13
13
|
{"candidates":[
|
|
14
|
-
{"type":"hotel","
|
|
15
|
-
{"type":"city","
|
|
16
|
-
{"type":"place","
|
|
14
|
+
{"type":"hotel","matchScore":180,"hotel":{"id":"h1","name":{"en":"Park Hyatt","zh":"柏悦酒店"},"star":5,"destinationId":"d1","latlngCoordinator":{"google":{"lat":22.31,"lng":114.16}}}},
|
|
15
|
+
{"type":"city","matchScore":250,"region":{"id":"d1","name":{"en":"Dali","zh":"大理市"},"countryCode":"CN","coordinates":{"centerLat":25.58,"centerLng":100.21}}},
|
|
16
|
+
{"type":"place","matchScore":90,"region":{"id":"r1","name":{"zh":"洱海"}},"place":{"latlngCoordinator":{"google":{"lat":25.74,"lng":100.25}}}}
|
|
17
17
|
]}
|
|
18
18
|
JSON
|
|
19
|
+
`, {
|
|
20
|
+
mode: 0o755
|
|
21
|
+
});
|
|
22
|
+
} else if (behaviour === 'echo-args') {
|
|
23
|
+
await writeFile(p, `#!/bin/sh
|
|
24
|
+
printf '{"candidates":[{"type":"city","region":{"id":"%s"}}]}' "$*"
|
|
19
25
|
`, {
|
|
20
26
|
mode: 0o755
|
|
21
27
|
});
|
|
@@ -31,6 +37,13 @@ JSON
|
|
|
31
37
|
await writeFile(p, `#!/bin/sh
|
|
32
38
|
echo 'hotelbe down' >&2
|
|
33
39
|
exit 1
|
|
40
|
+
`, {
|
|
41
|
+
mode: 0o755
|
|
42
|
+
});
|
|
43
|
+
} else if (behaviour === 'unknown-command') {
|
|
44
|
+
await writeFile(p, `#!/bin/sh
|
|
45
|
+
echo "error: unknown command 'anything'" >&2
|
|
46
|
+
exit 1
|
|
34
47
|
`, {
|
|
35
48
|
mode: 0o755
|
|
36
49
|
});
|
|
@@ -46,44 +59,80 @@ while :; do sleep 5; done
|
|
|
46
59
|
}
|
|
47
60
|
const ok = await fakeBin('hbcli-ok', 'ok');
|
|
48
61
|
const r1 = await anythingSearch({
|
|
49
|
-
keyword: '
|
|
62
|
+
keyword: '大理',
|
|
50
63
|
hbcliBin: ok
|
|
51
64
|
});
|
|
52
65
|
assert.equal(r1.verdict, 'hit');
|
|
53
66
|
assert.equal(r1.hits.length, 3);
|
|
54
67
|
assert.ok(r1.evidence.includes('hbcli-anything@'));
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
const [hotelHit, cityHit, placeHit] = r1.hits;
|
|
69
|
+
assert.equal(hotelHit.type, 'hotel');
|
|
70
|
+
assert.equal(hotelHit.name, '柏悦酒店', 'hotel i18n name 应 zh 优先');
|
|
71
|
+
assert.equal(hotelHit.hotelId, 'h1');
|
|
72
|
+
assert.equal(hotelHit.destinationId, 'd1', 'hotel 候选 destinationId 取所属目的地');
|
|
73
|
+
assert.equal(hotelHit.star, 5);
|
|
74
|
+
assert.equal(hotelHit.latitude, 22.31);
|
|
75
|
+
assert.equal(hotelHit.longitude, 114.16);
|
|
76
|
+
assert.equal(hotelHit.score, 180);
|
|
77
|
+
assert.equal(cityHit.type, 'city');
|
|
78
|
+
assert.equal(cityHit.name, '大理市', 'region i18n name 应 zh 优先');
|
|
79
|
+
assert.equal(cityHit.destinationId, 'd1');
|
|
80
|
+
assert.equal(cityHit.latitude, 25.58, 'city 坐标取 region.coordinates.centerLat');
|
|
81
|
+
assert.equal(cityHit.longitude, 100.21);
|
|
82
|
+
assert.equal(placeHit.type, 'place');
|
|
83
|
+
assert.equal(placeHit.name, '洱海');
|
|
84
|
+
assert.equal(placeHit.latitude, 25.74, 'place 坐标回退 place.latlngCoordinator');
|
|
85
|
+
console.log('1. 真实候选 wire 形状(hotel/city/place 逐字段)OK');
|
|
86
|
+
const echo = await fakeBin('hbcli-echo-args', 'echo-args');
|
|
57
87
|
const r2 = await anythingSearch({
|
|
88
|
+
keyword: '大理',
|
|
89
|
+
contentType: 'city',
|
|
90
|
+
parentDestinationId: 'd1',
|
|
91
|
+
hbcliBin: echo
|
|
92
|
+
});
|
|
93
|
+
assert.equal(r2.verdict, 'hit');
|
|
94
|
+
assert.equal(r2.hits[0].destinationId, '--json search anything 大理 --content-type city --destination-id d1', 'spawn 旗标形态(--json 前置/--destination-id 映射)应稳定,实际 ' + r2.hits[0].destinationId);
|
|
95
|
+
console.log('2. 旗标回归(--json 前置 + --destination-id 映射)OK');
|
|
96
|
+
const empty = await fakeBin('hbcli-empty', 'empty');
|
|
97
|
+
const r3 = await anythingSearch({
|
|
58
98
|
keyword: 'nothing',
|
|
59
99
|
hbcliBin: empty
|
|
60
100
|
});
|
|
61
|
-
assert.equal(
|
|
62
|
-
assert.equal(
|
|
63
|
-
console.log('
|
|
101
|
+
assert.equal(r3.verdict, 'miss');
|
|
102
|
+
assert.equal(r3.hits.length, 0);
|
|
103
|
+
console.log('3. nothing → miss OK');
|
|
64
104
|
const fail = await fakeBin('hbcli-fail', 'fail');
|
|
65
|
-
const
|
|
105
|
+
const r4 = await anythingSearch({
|
|
66
106
|
keyword: 'x',
|
|
67
107
|
hbcliBin: fail
|
|
68
108
|
});
|
|
69
|
-
assert.equal(
|
|
70
|
-
assert.equal(
|
|
71
|
-
assert.match(
|
|
72
|
-
console.log('
|
|
109
|
+
assert.equal(r4.verdict, 'error');
|
|
110
|
+
assert.equal(r4.ok, false);
|
|
111
|
+
assert.match(r4.evidence, /error/);
|
|
112
|
+
console.log('4. fail → error (降级) OK');
|
|
113
|
+
const old = await fakeBin('hbcli-old', 'unknown-command');
|
|
114
|
+
const r5 = await anythingSearch({
|
|
115
|
+
keyword: 'x',
|
|
116
|
+
hbcliBin: old
|
|
117
|
+
});
|
|
118
|
+
assert.equal(r5.verdict, 'error');
|
|
119
|
+
assert.match(r5.error ?? '', /hbcli update|gotry setup/, 'error 应带升级指引,实际 ' + r5.error);
|
|
120
|
+
assert.match(r5.evidence, /unknown command/, 'evidence 保留上游原话(溯源)');
|
|
121
|
+
console.log(`5. 旧 CLI unknown command → 升级指引 OK(${r5.error})`);
|
|
73
122
|
const hang = await fakeBin('hbcli-hang', 'hang');
|
|
74
|
-
const
|
|
123
|
+
const r6 = await anythingSearch({
|
|
75
124
|
keyword: 'x',
|
|
76
125
|
hbcliBin: hang,
|
|
77
126
|
timeoutMs: 800
|
|
78
127
|
});
|
|
79
|
-
assert.equal(
|
|
80
|
-
console.log(`
|
|
81
|
-
const
|
|
128
|
+
assert.equal(r6.verdict, 'error', 'timeout 应判 error');
|
|
129
|
+
console.log(`6. hang/timeout → error OK (latency=${r6.latencyMs}ms)`);
|
|
130
|
+
const r7 = await anythingSearch({
|
|
82
131
|
keyword: ' '
|
|
83
132
|
});
|
|
84
|
-
assert.equal(
|
|
85
|
-
console.log('
|
|
86
|
-
console.log('\nANYTHING TESTS:
|
|
133
|
+
assert.equal(r7.verdict, 'error');
|
|
134
|
+
console.log('7. empty keyword → error OK');
|
|
135
|
+
console.log('\nANYTHING TESTS: 7/7 OK(hbcli fake + wire 对齐 + 降级诚实)');
|
|
87
136
|
} finally{
|
|
88
137
|
await rm(tmp, {
|
|
89
138
|
recursive: true,
|