@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.
Files changed (71) hide show
  1. package/README.md +14 -3
  2. package/README.zh-CN.md +15 -4
  3. package/bin/gotry-bootstrap.js +136 -45
  4. package/bin/gotry-inner.js +11 -6
  5. package/cordis.gotry-patch.yml +19 -8
  6. package/dist/capabilities/anything.js +62 -29
  7. package/dist/capabilities/doctor.js +113 -19
  8. package/dist/capabilities/hbcli.js +28 -2
  9. package/dist/capabilities/session/health-watch.js +10 -3
  10. package/dist/scripts/agent-planning-turn-deadline-e2e.js +2 -1
  11. package/dist/scripts/anything-tests.js +70 -21
  12. package/dist/scripts/benchmark-environment-bridge-e2e.js +178 -33
  13. package/dist/scripts/benchmark-environment-bridge-tests.js +1480 -271
  14. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +211 -2
  15. package/dist/scripts/booking-copilot-runtime-proof-tests.js +132 -2
  16. package/dist/scripts/bootstrap-tests.js +30 -3
  17. package/dist/scripts/doctor-tests.js +55 -1
  18. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  19. package/dist/scripts/hbcli-tests.js +38 -1
  20. package/dist/scripts/health-watch-cli.js +9 -1
  21. package/dist/scripts/map-tools-vendor-package-proof.js +276 -0
  22. package/dist/scripts/persona-surface-guard-tests.js +8 -3
  23. package/dist/src/benchmark-agent-conformance.js +187 -17
  24. package/dist/src/benchmark-environment-bridge.js +287 -102
  25. package/dist/src/booking-surface/dsh-planner.js +24 -8
  26. package/dist/src/index.js +5 -2
  27. package/extension/manifest.json +2 -2
  28. package/package.json +3 -1
  29. package/ts/capabilities/anything.ts +89 -38
  30. package/ts/capabilities/hbcli.ts +53 -4
  31. package/ts/capabilities/session/health-watch.ts +9 -2
  32. package/ts/dsh-runtime/vendor/README.md +60 -0
  33. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  34. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
  35. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
  36. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
  37. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  38. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
  39. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  40. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
  41. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  42. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  43. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
  44. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  45. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
  46. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
  47. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  48. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  49. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
  50. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
  51. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  52. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  53. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  54. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  55. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
  56. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  57. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  58. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  59. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  60. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  61. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
  62. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
  63. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  64. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
  65. package/ts/package.json +17 -1
  66. package/ts/scripts/map-tools-vendor-package-proof.ts +268 -0
  67. package/ts/src/benchmark-agent-conformance.ts +175 -13
  68. package/ts/src/benchmark-environment-bridge.ts +220 -66
  69. package/ts/src/booking-surface/dsh-planner.ts +25 -13
  70. package/ts/src/index.ts +4 -2
  71. package/ts/src/turn-deadline.ts +4 -0
@@ -1,4 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { createHash } from 'node:crypto';
2
3
  import { DSH_EMBEDDED_BOOKING_TOOL_NAMES, buildDshPlannerEnvironment, createDshEmbeddedBookingPlanner } from '../src/booking-surface/dsh-planner.js';
3
4
  const availability = {
4
5
  initialized: true,
@@ -343,7 +344,8 @@ const sanitizedRefPort = {
343
344
  action: {
344
345
  ...searchRun,
345
346
  factRefs: [
346
- 'draft:destination=Dubai'
347
+ 'draft:destination=Dubai',
348
+ 'draft:destination.Dubai'
347
349
  ]
348
350
  }
349
351
  }
@@ -373,9 +375,216 @@ const sanitizedDecisions = await sanitizedRef.plannerFactory(task).next({
373
375
  });
374
376
  assert.equal(sanitizedDecisions[0]?.kind, 'operation', 'sanitizer maps off-charset characters deterministically');
375
377
  assert.deepEqual(sanitizedDecisions[0].action?.factRefs, [
378
+ 'modelref:43b07dd7fc80f4a9889b6c672c450a911086293cab2ef4058b95b53559a2d100',
376
379
  'draft:destination.Dubai'
377
380
  ]);
378
381
  await sanitizedRef.close();
382
+ const collisionRawRefs = [
383
+ 'fact://a/b',
384
+ 'fact:..a?b'
385
+ ];
386
+ const collisionResults = [];
387
+ for (const [index, rawRef] of collisionRawRefs.entries()){
388
+ const collisionPlanner = await createDshEmbeddedBookingPlanner({
389
+ runPort: {
390
+ async run () {
391
+ return {
392
+ finalResponse: '',
393
+ events: [
394
+ {
395
+ type: 'tool/call',
396
+ data: {
397
+ name: 'booking_search_hotels',
398
+ arguments: JSON.stringify({
399
+ decision: {
400
+ kind: 'operation',
401
+ action: {
402
+ ...searchRun,
403
+ actionId: `action-dsh-collision-${index}`,
404
+ factRefs: [
405
+ rawRef
406
+ ]
407
+ }
408
+ }
409
+ })
410
+ }
411
+ }
412
+ ]
413
+ };
414
+ },
415
+ async close () {}
416
+ }
417
+ });
418
+ const decisions = await collisionPlanner.plannerFactory(task).next({
419
+ task,
420
+ turn: {
421
+ schemaVersion: 'booking.surface',
422
+ kind: 'user.turn',
423
+ taskId: task.taskId,
424
+ turnId: `dsh-collision-${index}`,
425
+ workspace,
426
+ request: {
427
+ text: 'Find hotels'
428
+ }
429
+ }
430
+ });
431
+ const ref = decisions[0].action?.factRefs?.[0];
432
+ assert.match(ref ?? '', /^[A-Za-z0-9][A-Za-z0-9:._-]*$/, 'unsafe refs map to the runtime-safe pattern');
433
+ collisionResults.push(ref ?? '');
434
+ await collisionPlanner.close();
435
+ }
436
+ assert.notEqual(collisionResults[0], collisionResults[1], 'distinct unsafe raw refs retain collision-resistant aliases');
437
+ assert.equal(collisionRawRefs[0].replace(/#/g, ':').replace(/[^A-Za-z0-9:._-]/g, '.'), collisionRawRefs[1].replace(/#/g, ':').replace(/[^A-Za-z0-9:._-]/g, '.'), 'the collision pair shares the legacy readable projection');
438
+ async function assertFactRefsRejected(factRefs, message) {
439
+ const planner = await createDshEmbeddedBookingPlanner({
440
+ runPort: {
441
+ async run () {
442
+ return {
443
+ finalResponse: '',
444
+ events: [
445
+ {
446
+ type: 'tool/call',
447
+ data: {
448
+ name: 'booking_search_hotels',
449
+ arguments: JSON.stringify({
450
+ decision: {
451
+ kind: 'operation',
452
+ action: {
453
+ ...searchRun,
454
+ factRefs
455
+ }
456
+ }
457
+ })
458
+ }
459
+ }
460
+ ]
461
+ };
462
+ },
463
+ async close () {}
464
+ }
465
+ });
466
+ await assert.rejects(planner.plannerFactory(task).next({
467
+ task,
468
+ turn: {
469
+ schemaVersion: 'booking.surface',
470
+ kind: 'user.turn',
471
+ taskId: task.taskId,
472
+ turnId: 'dsh-ref-rejection',
473
+ workspace,
474
+ request: {
475
+ text: 'Find hotels'
476
+ }
477
+ }
478
+ }), /planner_invalid_action/, message);
479
+ await planner.close();
480
+ }
481
+ const firstAlias = 'modelref:'.concat(createHash('sha256').update(collisionRawRefs[0], 'utf8').digest('hex'));
482
+ await assertFactRefsRejected([
483
+ collisionRawRefs[0],
484
+ firstAlias
485
+ ], 'unsafe raw ref cannot alias a same-action reserved modelref');
486
+ await assertFactRefsRejected([
487
+ collisionRawRefs[0],
488
+ collisionRawRefs[0]
489
+ ], 'repair-time duplicate factRefs are rejected by canonical validation');
490
+ await assertFactRefsRejected([
491
+ 'modelref:'.concat('a'.repeat(64))
492
+ ], 'direct modelref namespace input is reserved and rejected');
493
+ const reservedRecovery = await createDshEmbeddedBookingPlanner({
494
+ runPort: {
495
+ async run () {
496
+ return {
497
+ finalResponse: JSON.stringify({
498
+ kind: 'operation',
499
+ action: {
500
+ ...searchRun,
501
+ factRefs: [
502
+ 'modelref:'.concat('b'.repeat(64))
503
+ ]
504
+ }
505
+ }),
506
+ events: []
507
+ };
508
+ },
509
+ async close () {}
510
+ }
511
+ });
512
+ const reservedRecoveryDecision = await reservedRecovery.plannerFactory(task).next({
513
+ task,
514
+ turn: {
515
+ schemaVersion: 'booking.surface',
516
+ kind: 'user.turn',
517
+ taskId: task.taskId,
518
+ turnId: 'dsh-reserved-recovery',
519
+ workspace,
520
+ request: {
521
+ text: 'Find hotels'
522
+ }
523
+ }
524
+ });
525
+ assert.equal(reservedRecoveryDecision[0]?.kind, 'error', 'finalResponse recovery rejects direct reserved modelref aliases');
526
+ await reservedRecovery.close();
527
+ let stableRefCall = 0;
528
+ const stableRawRef = 'fact://same/raw';
529
+ const stablePlanner = await createDshEmbeddedBookingPlanner({
530
+ runPort: {
531
+ async run () {
532
+ stableRefCall += 1;
533
+ return {
534
+ finalResponse: '',
535
+ events: [
536
+ {
537
+ type: 'tool/call',
538
+ data: {
539
+ name: 'booking_search_hotels',
540
+ arguments: JSON.stringify({
541
+ decision: {
542
+ kind: 'operation',
543
+ action: {
544
+ ...searchRun,
545
+ actionId: `action-dsh-stable-${stableRefCall}`,
546
+ factRefs: [
547
+ stableRawRef
548
+ ]
549
+ }
550
+ }
551
+ })
552
+ }
553
+ }
554
+ ]
555
+ };
556
+ },
557
+ async close () {}
558
+ }
559
+ });
560
+ const stableFirst = await stablePlanner.plannerFactory(task).next({
561
+ task,
562
+ turn: {
563
+ schemaVersion: 'booking.surface',
564
+ kind: 'user.turn',
565
+ taskId: task.taskId,
566
+ turnId: 'dsh-stable-1',
567
+ workspace,
568
+ request: {
569
+ text: 'Find hotels'
570
+ }
571
+ }
572
+ });
573
+ const stableSecond = await stablePlanner.plannerFactory(task).next({
574
+ task,
575
+ turn: {
576
+ schemaVersion: 'booking.surface',
577
+ kind: 'user.turn',
578
+ taskId: task.taskId,
579
+ turnId: 'dsh-stable-2',
580
+ workspace,
581
+ request: {
582
+ text: 'Find hotels again'
583
+ }
584
+ }
585
+ });
586
+ assert.equal(stableFirst[0].action?.factRefs?.[0], stableSecond[0].action?.factRefs?.[0], 'same raw ref is stable across actions and turns');
587
+ await stablePlanner.close();
379
588
  const unauthorisedPort = {
380
589
  async run () {
381
590
  return {
@@ -458,7 +667,7 @@ const fragmentDecisions = await fragmentRef.plannerFactory(task).next({
458
667
  });
459
668
  assert.equal(fragmentDecisions[0]?.kind, 'operation', 'JSON-pointer fragment factRefs repair into the safe charset');
460
669
  assert.deepEqual(fragmentDecisions[0].action?.factRefs, [
461
- `fact:..turn_${task.lastTurnId}.request`
670
+ `modelref:c7819d3c0c375fe1bd389006338b99be890fa8e3ddd9390f243cba8b5c139d9a`
462
671
  ]);
463
672
  await fragmentRef.close();
464
673
  const truncatedPort = {
@@ -142,7 +142,137 @@ const action = (id, revision = 0, extra = {})=>({
142
142
  input: {},
143
143
  ...extra
144
144
  });
145
- let id = 0;
145
+ const repairedFactRef = `modelref:${'a'.repeat(64)}`;
146
+ {
147
+ const root = mkdtempSync(join(tmpdir(), 'gotry-booking-v2-factref-action-authority-'));
148
+ const ledger = ensureLedger(root);
149
+ const rt = new BookingCopilotTaskRuntime(ledger, {
150
+ contextRefFactory: ()=>'ctx-v2'
151
+ });
152
+ const limitedWorkspace = {
153
+ ...workspace(0),
154
+ capabilities: {
155
+ surface: 'tenant',
156
+ allowedActions: [
157
+ 'search.run'
158
+ ]
159
+ }
160
+ };
161
+ const t = rt.startTask({
162
+ ...turn('task-factref-action-authority'),
163
+ workspace: limitedWorkspace
164
+ });
165
+ const before = ledger.countEvents();
166
+ assert.throws(()=>rt.issueOperation(t.taskId, {
167
+ ...action('factref-forged-action', 0, {
168
+ factRefs: [
169
+ repairedFactRef,
170
+ 'checkout.prepare'
171
+ ]
172
+ }),
173
+ kind: 'checkout.prepare',
174
+ input: {
175
+ offerRef: 'offer-a',
176
+ offerVersionRef: 'offer-a:v1',
177
+ verifiedOfferRef: 'verified-offer-a'
178
+ }
179
+ }), /unsupported_action/, 'factRefs cannot add an action to the surface allowlist');
180
+ assert.equal(ledger.countEvents(), before, 'rejected factRef authority does not persist an operation');
181
+ ledger.close();
182
+ rmSync(root, {
183
+ recursive: true,
184
+ force: true
185
+ });
186
+ }{
187
+ const root = mkdtempSync(join(tmpdir(), 'gotry-booking-v2-factref-offer-authority-'));
188
+ const ledger = ensureLedger(root);
189
+ const rt = new BookingCopilotTaskRuntime(ledger, {
190
+ contextRefFactory: ()=>'ctx-v2'
191
+ });
192
+ const offerWorkspace = {
193
+ ...workspace(0),
194
+ visibleHotels: [
195
+ {
196
+ hotelRef: 'hotel-a',
197
+ name: 'Hotel A',
198
+ factRefs: []
199
+ }
200
+ ],
201
+ loadedOffers: [
202
+ loadedOffer('offer-a', 'hotel-a', 'offer-a:v1')
203
+ ]
204
+ };
205
+ const t = rt.startTask({
206
+ ...turn('task-factref-offer-authority'),
207
+ workspace: offerWorkspace
208
+ });
209
+ const before = ledger.countEvents();
210
+ assert.throws(()=>rt.issueOperation(t.taskId, {
211
+ ...action('factref-forged-version', 0, {
212
+ factRefs: [
213
+ repairedFactRef,
214
+ 'offer-a:v2'
215
+ ]
216
+ }),
217
+ kind: 'offer.check',
218
+ input: {
219
+ offerRef: 'offer-a',
220
+ offerVersionRef: 'offer-a:v2'
221
+ }
222
+ }), /offer_version_not_loaded/, 'factRefs cannot promote an unloaded offer version');
223
+ assert.equal(ledger.countEvents(), before, 'rejected factRef version does not persist an operation');
224
+ ledger.close();
225
+ rmSync(root, {
226
+ recursive: true,
227
+ force: true
228
+ });
229
+ }{
230
+ const root = mkdtempSync(join(tmpdir(), 'gotry-booking-v2-factref-verified-authority-'));
231
+ const ledger = ensureLedger(root);
232
+ const rt = new BookingCopilotTaskRuntime(ledger, {
233
+ contextRefFactory: ()=>'ctx-v2',
234
+ now: ()=>'2026-09-01T10:00:00.000Z'
235
+ });
236
+ const offerWorkspace = {
237
+ ...workspace(0),
238
+ visibleHotels: [
239
+ {
240
+ hotelRef: 'hotel-a',
241
+ name: 'Hotel A',
242
+ factRefs: []
243
+ }
244
+ ],
245
+ loadedOffers: [
246
+ loadedOffer('offer-a', 'hotel-a', 'offer-a:v1')
247
+ ],
248
+ selectedOfferRef: 'offer-a'
249
+ };
250
+ const t = rt.startTask({
251
+ ...turn('task-factref-verified-authority'),
252
+ workspace: offerWorkspace
253
+ });
254
+ const before = ledger.countEvents();
255
+ assert.throws(()=>rt.issueOperation(t.taskId, {
256
+ ...action('factref-forged-verified', 0, {
257
+ factRefs: [
258
+ repairedFactRef,
259
+ 'verified-offer-a'
260
+ ]
261
+ }),
262
+ kind: 'checkout.prepare',
263
+ input: {
264
+ offerRef: 'offer-a',
265
+ offerVersionRef: 'offer-a:v1',
266
+ verifiedOfferRef: 'verified-offer-a'
267
+ }
268
+ }), /offer_version_not_loaded/, 'factRefs cannot create verified-offer authority');
269
+ assert.equal(ledger.countEvents(), before, 'rejected factRef verification does not persist an operation');
270
+ ledger.close();
271
+ rmSync(root, {
272
+ recursive: true,
273
+ force: true
274
+ });
275
+ }let id = 0;
146
276
  const runtime = new BookingCopilotTaskRuntime(ensureLedger(stateRoot), {
147
277
  idFactory: (prefix)=>`${prefix}-${++id}`,
148
278
  now: ()=>'2026-09-01T10:00:00.000Z'
@@ -3997,7 +4127,7 @@ rmSync(offerRoot, {
3997
4127
  recursive: true,
3998
4128
  force: true
3999
4129
  });
4000
- console.log('BOOKING COPILOT RUNTIME PROOF: task scope, receipt binding, approval authority/one-time, recovery, ingress/SSE session OK');
4130
+ console.log('BOOKING COPILOT RUNTIME PROOF: task scope, receipt binding, factRef non-authority, approval authority/one-time, recovery, ingress/SSE session OK');
4001
4131
 
4002
4132
 
4003
4133
  //# sourceURL=ts/scripts/booking-copilot-runtime-proof-tests.ts
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { execFileSync, spawnSync } from 'node:child_process';
3
- import { mkdtempSync, readFileSync } from 'node:fs';
3
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  const repoRoot = join(import.meta.dirname, '..', '..');
@@ -76,7 +76,8 @@ const c6 = runBootstrap([
76
76
  ], {
77
77
  GOTRY_SETUP_EXTENSION: '0',
78
78
  GOTRY_ONBOARDING_TIMEOUT_MS: '600',
79
- GOTRY_ONBOARDING_INTERVAL_MS: '200'
79
+ GOTRY_ONBOARDING_INTERVAL_MS: '200',
80
+ GOTRY_ONBOARDING_BRIDGE_PORTS: '0'
80
81
  });
81
82
  assert.equal(c6.code, 1, `wizard(超时)应 exit 1,实际 ${c6.code}\n${c6.out}`);
82
83
  assert.ok(c6.out.includes('gotry-wizard'), '应输出 [gotry-wizard] 标签');
@@ -187,7 +188,33 @@ console.log('9. calendar 子命令(setup 状态面 on/off/status + doctor 三态
187
188
  assert.match(r.stderr, /gotry_doctor/, '摘要带对话内指路');
188
189
  assert.match(r.stderr, /扩展=缺/, '缺失项人话=缺(降级类=半可用)');
189
190
  }console.log('10. 启动一次性 doctor 摘要(--summary:stderr 一行/零写盘/恒 exit 0)OK');
190
- console.log('BOOTSTRAP TESTS: 10/10 OK(扩展就位 + 跳过开关 / wizard --dry-run / wizard 真实 / 扩展分发通道 / doctor 体检面 / calendar setup 状态面 / 显式跳过 + auto 跳过 + 单项跳过 / 启动摘要)');
191
+ {
192
+ const sbHome = mkdtempSync(join(tmpdir(), 'gotry-sidebar-test-'));
193
+ const prevHome = process.env.HOME;
194
+ process.env.HOME = sbHome;
195
+ try {
196
+ const { setupSidebar } = await import(bootstrap);
197
+ const r = await setupSidebar(async ()=>{
198
+ const sbPkgDir = join(sbHome, '.dsh/profiles/web/node_modules/dsh-better-sidebar');
199
+ mkdirSync(sbPkgDir, {
200
+ recursive: true
201
+ });
202
+ writeFileSync(join(sbPkgDir, 'package.json'), JSON.stringify({
203
+ name: 'dsh-better-sidebar',
204
+ version: '0.18.0'
205
+ }));
206
+ return {
207
+ ok: false,
208
+ error: 'exit 1'
209
+ };
210
+ });
211
+ assert.equal(r.ok, true, '安装器 exit 非 0 但状态已落盘 → 按落盘判成功(不误报失败)');
212
+ } finally{
213
+ if (prevHome === undefined) delete process.env.HOME;
214
+ else process.env.HOME = prevHome;
215
+ }
216
+ }console.log('11. setupSidebar 落盘状态复核(pnpm 忽略构建脚本 exit 1 不再误报)OK');
217
+ console.log('BOOTSTRAP TESTS: 11/11 OK(扩展就位 + 跳过开关 / wizard --dry-run / wizard 真实 / 扩展分发通道 / doctor 体检面 / calendar setup 状态面 / 显式跳过 + auto 跳过 + 单项跳过 / 启动摘要 / sidebar 落盘状态复核)');
191
218
 
192
219
 
193
220
  //# sourceURL=ts/scripts/bootstrap-tests.ts
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { mkdtemp, mkdir, writeFile, rm, readFile } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
4
+ import { dirname, join } from 'node:path';
5
5
  import { runDoctorChecks, renderDoctorReportMd, nodeOk } from '../capabilities/doctor.js';
6
6
  import { apply } from '../src/index.js';
7
7
  const tmp = await mkdtemp(join(tmpdir(), 'gotry-doctor-test-'));
@@ -78,6 +78,60 @@ assert.match(md, /flyai\.open\.fliggy\.com 控制台申请正式 key/, 'prose
78
78
  assert.ok(!md.includes('`到 flyai'), 'prose 类 fix 不应整体包反引号');
79
79
  assert.match(md, /LLM key/, '让渡面(LLM key)照常入表');
80
80
  console.log('5. 报告渲染 OK');
81
+ {
82
+ const hoist = await mkdtemp(join(tmpdir(), 'gotry-doctor-hoist-'));
83
+ const pkgRoot = join(hoist, 'node_modules', '@danceiny', 'gotry');
84
+ await mkdir(pkgRoot, {
85
+ recursive: true
86
+ });
87
+ await writeFile(join(pkgRoot, 'package.json'), JSON.stringify({
88
+ name: '@danceiny/gotry',
89
+ version: '0.0.1-test'
90
+ }), 'utf-8');
91
+ const stubPkg = async (dir, main)=>{
92
+ await mkdir(join(hoist, 'node_modules', dir, 'lib'), {
93
+ recursive: true
94
+ });
95
+ await writeFile(join(hoist, 'node_modules', dir, 'package.json'), JSON.stringify({
96
+ name: dir,
97
+ version: '0.0.0-test',
98
+ main
99
+ }), 'utf-8');
100
+ await writeFile(join(hoist, 'node_modules', dir, main), '// stub\n', 'utf-8');
101
+ };
102
+ await stubPkg('dsh-map-tools', 'lib/index.js');
103
+ await stubPkg('@deepseek-ai/dsh', 'lib/bin.js');
104
+ await stubPkg('@deepseek-ai/dsh-tool-ask-user', 'lib/index.js');
105
+ const rb = await runDoctorChecks({
106
+ repoRoot: pkgRoot,
107
+ homeDir: join(hoist, 'home-empty'),
108
+ env: {}
109
+ });
110
+ assert.equal(rb.items.find((i)=>i.id === 'map-tools').status, 'ok', '提升布局 map-tools 经包根解析链命中');
111
+ assert.equal(rb.items.find((i)=>i.id === 'ask-user').status, 'ok', '提升布局 ask-user 经 dsh 上下文解析链命中(误报修复)');
112
+ await rm(hoist, {
113
+ recursive: true,
114
+ force: true
115
+ });
116
+ }console.log('5b. npm 提升布局解析链(map-tools/ask-user 误报修复)OK');
117
+ {
118
+ const vend = await mkdtemp(join(tmpdir(), 'gotry-doctor-vendor-'));
119
+ const vendorEntry = join(vend, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js');
120
+ await mkdir(dirname(vendorEntry), {
121
+ recursive: true
122
+ });
123
+ await writeFile(vendorEntry, '// stub\n', 'utf-8');
124
+ const rc = await runDoctorChecks({
125
+ repoRoot: vend,
126
+ homeDir: join(vend, 'home-empty'),
127
+ env: {}
128
+ });
129
+ assert.equal(rc.items.find((i)=>i.id === 'map-tools').status, 'ok', 'vendor 副本命中(map-tools 随 tarball 分发)');
130
+ await rm(vend, {
131
+ recursive: true,
132
+ force: true
133
+ });
134
+ }console.log('5c. 随包 vendor 布局(map-tools tarball 分发面)OK');
81
135
  const smokeRoot = await mkdtemp(join(tmpdir(), 'gotry-doctor-state-'));
82
136
  const registered = [];
83
137
  const ctx = {
@@ -10,7 +10,7 @@ const SANDBOX_APP_KEY = 'hotelbyte_api_demo';
10
10
  const SANDBOX_APP_SECRET = 'hotelbyte_api_demo';
11
11
  const GUIDANCE = [
12
12
  'staicli(hbcli)账号配置指引:',
13
- ' 1. 安装 CLI(若缺):npx gotry setup(官方 install.sh~/.local/bin/hbcli)',
13
+ ' 1. 安装 CLI(若缺):npx gotry setup(npm staicli@npmjs PATH 的 hbcli)',
14
14
  ' 2. 快速试用(沙箱演示账号,来自 hotel-be 种子,user/domain/predefined_user_demo.go):',
15
15
  ' hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo',
16
16
  ' 3. 正式接入:向 HotelByte 申请专属 appKey/appSecret(hbk_*/hbs_*),替换第 2 步凭证;',
@@ -86,7 +86,44 @@ await writeFile(fallback, JSON.stringify({
86
86
  if (!argLine.includes('destination-name') || !argLine.includes('room-occupancies')) {
87
87
  throw new Error(`FAIL: v0.3.0 旗标未对齐,实际 ${argLine.slice(0, 200)}`);
88
88
  }
89
- console.log('5. v0.3.0 旗标(--destination-name/--room-occupancies)对齐 OK');
89
+ const rd = await searchHotels({
90
+ destination: '普吉',
91
+ checkIn: '2026-09-18',
92
+ checkOut: '2026-09-20'
93
+ }, {
94
+ hbcliBin: echoBin
95
+ });
96
+ const dateLine = JSON.stringify(rd);
97
+ if (!dateLine.includes('check-in') || !dateLine.includes('2026-09-18') || !dateLine.includes('check-out')) {
98
+ throw new Error(`FAIL: 日期旗标未传上游,实际 ${dateLine.slice(0, 240)}`);
99
+ }
100
+ assert.ok(rd.summary.includes('入住 2026-09-18 → 退房 2026-09-20'), 'summary 应回显日期槽位');
101
+ console.log('5. 旗标对齐(--destination-name/--room-occupancies/--check-in/--check-out)OK');
102
+ }{
103
+ const { writeFileSync, chmodSync } = await import('node:fs');
104
+ const listBin = join(tmp, 'hbcli-list');
105
+ writeFileSync(listBin, `#!/bin/sh
106
+ cat <<'JSON'
107
+ {"list":[{"id":461850557,"name":{"en":"Jumeirah Beach Hotel","zh":"朱美拉海滩酒店"},"star":5,"minPrice":{"amount":100,"currency":"USD"}}],"basic":{"sessionId":"s1"}}
108
+ JSON
109
+ `);
110
+ chmodSync(listBin, 0o755);
111
+ const rl = await searchHotels({
112
+ destination: '迪拜',
113
+ checkIn: '2026-09-18',
114
+ checkOut: '2026-09-20'
115
+ }, {
116
+ hbcliBin: listBin
117
+ });
118
+ assert.equal(rl.via, 'hbcli-realtime');
119
+ assert.ok(rl.summary.includes('实时 1 家'), 'summary 应带家数');
120
+ assert.ok(rl.summary.includes('id=461850557'), '数据行应带 hotelId(hotel-rates 入参来源)');
121
+ assert.ok(rl.summary.includes('朱美拉海滩酒店'), '数据行应带 zh 名称');
122
+ assert.ok(rl.summary.includes('5★'), '数据行应带星级');
123
+ assert.ok(rl.summary.includes('100 USD'), '数据行应带最低价');
124
+ assert.match(rl.summary, /\[实时API:hbcli@/, 'summary 应带证据链时间戳');
125
+ assert.ok(rl.hotels !== null, 'hotels 结构化产物保留(UI/程序消费)');
126
+ console.log(`5b. 实时 summary 数据行 OK:${rl.summary.split('\n')[1]}`);
90
127
  }const r4 = await searchHotels({
91
128
  destination: '普吉岛'
92
129
  }, {
@@ -15,6 +15,13 @@ function parseArgs(argv) {
15
15
  }
16
16
  if (Number.isNaN(args.timeoutMs) || args.timeoutMs < 0) args.timeoutMs = 120_000;
17
17
  if (Number.isNaN(args.intervalMs) || args.intervalMs < 1_000) args.intervalMs = 5_000;
18
+ const rawPorts = process.env.GOTRY_ONBOARDING_BRIDGE_PORTS;
19
+ if (rawPorts) {
20
+ const parsed = rawPorts.split(',').map((part)=>Number.parseInt(part.trim(), 10));
21
+ if (parsed.every((port)=>Number.isInteger(port) && port >= 0 && port <= 65535)) {
22
+ args.ports = parsed;
23
+ }
24
+ }
18
25
  return args;
19
26
  }
20
27
  async function main() {
@@ -24,7 +31,8 @@ async function main() {
24
31
  timeoutMs: args.timeoutMs,
25
32
  intervalMs: args.intervalMs,
26
33
  keepBridge: true,
27
- businessProbe
34
+ businessProbe,
35
+ ports: args.ports
28
36
  });
29
37
  process.on('SIGINT', ()=>{
30
38
  watch.cancel('SIGINT');