@danceiny/gotry 0.2.0-rc.31 → 0.2.0-rc.33

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 (128) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/bin/gotry-bootstrap.js +63 -53
  4. package/bin/gotry-hbcli-release.d.ts +3 -0
  5. package/bin/gotry-hbcli-release.js +4 -0
  6. package/client/client.js +66 -21
  7. package/dist/capabilities/doctor.js +7 -8
  8. package/dist/capabilities/effect.js +45 -12
  9. package/dist/capabilities/flyai-config.js +12 -10
  10. package/dist/capabilities/flyai.js +87 -56
  11. package/dist/capabilities/hbcli-config.js +156 -0
  12. package/dist/capabilities/hbcli.js +29 -2
  13. package/dist/capabilities/resilience.js +6 -6
  14. package/dist/capabilities/session/adapters/ctrip-flight.js +42 -7
  15. package/dist/capabilities/session/adapters/ctrip-hotel.js +52 -3
  16. package/dist/capabilities/session/bridge-ledger.js +168 -67
  17. package/dist/capabilities/session/ctrip-city.js +186 -0
  18. package/dist/capabilities/session/extension-bridge.js +148 -47
  19. package/dist/capabilities/session/gui-flight.js +949 -0
  20. package/dist/capabilities/session/gui-hotel.js +470 -0
  21. package/dist/capabilities/session-search.js +102 -14
  22. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +129 -2
  23. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +53 -5
  24. package/dist/scripts/booking-copilot-gateway-chat-adapter-tests.js +233 -0
  25. package/dist/scripts/booking-copilot-planner-live-probe.js +1 -0
  26. package/dist/scripts/booking-copilot-startup-proof-tests.js +1 -1
  27. package/dist/scripts/bootstrap-tests.js +23 -6
  28. package/dist/scripts/bridge-ledger-tests.js +20 -18
  29. package/dist/scripts/ctrip-city-tests.js +80 -0
  30. package/dist/scripts/effect-tests.js +602 -504
  31. package/dist/scripts/extension-tests.js +14 -8
  32. package/dist/scripts/fixtures/tenant-runaway-worker-fixture.mjs +73 -0
  33. package/dist/scripts/flight-malformed-tests.js +1 -1
  34. package/dist/scripts/flyai-setup-tests.js +19 -4
  35. package/dist/scripts/flyai-tests.js +768 -653
  36. package/dist/scripts/gotry-web-api-tests.js +5 -0
  37. package/dist/scripts/gotry-web-client-tests.js +20 -1
  38. package/dist/scripts/gui-flight-live.js +27 -0
  39. package/dist/scripts/hbcli-tests.js +1 -1
  40. package/dist/scripts/hbcli-upgrade-tests.js +107 -0
  41. package/dist/scripts/hbcli-web-setup-tests.js +114 -0
  42. package/dist/scripts/hotel-date-gate-tests.js +1 -1
  43. package/dist/scripts/hotel-result-tests.js +169 -0
  44. package/dist/scripts/issue-271-liveness-tests.js +1 -1
  45. package/dist/scripts/mutation-check.js +153 -0
  46. package/dist/scripts/nightly-evidence.js +4 -111
  47. package/dist/scripts/session-tests.js +211 -6
  48. package/dist/scripts/tenant-bridge-org-tests.js +1264 -0
  49. package/dist/scripts/tenant-capacity-probe.js +263 -0
  50. package/dist/scripts/tenant-compute-isolation-tests.js +707 -0
  51. package/dist/scripts/tenant-egress-credentials-tests.js +822 -0
  52. package/dist/scripts/tenant-executor-binding-tests.js +203 -0
  53. package/dist/scripts/tenant-prompt-partition-tests.js +265 -0
  54. package/dist/scripts/tenant-seam-adversarial-tests.js +957 -0
  55. package/dist/scripts/tenant-seam-quota-tests.js +383 -0
  56. package/dist/scripts/tenant-seam-test-kit.js +292 -0
  57. package/dist/scripts/tenant-state-copies-tests.js +607 -0
  58. package/dist/scripts/tenant-state-drill-tests.js +298 -0
  59. package/dist/scripts/tenant-state-drill-worker.js +164 -0
  60. package/dist/scripts/tenant-state-durable-conformance.js +101 -0
  61. package/dist/scripts/tenant-state-handoff-tests.js +854 -0
  62. package/dist/scripts/tenant-state-lease-conformance.js +142 -0
  63. package/dist/scripts/tenant-state-lease-worker.js +28 -0
  64. package/dist/scripts/tenant-state-router-tests.js +464 -0
  65. package/dist/scripts/tenant-state-test-kit.js +92 -0
  66. package/dist/scripts/tenant-state-tests.js +564 -0
  67. package/dist/src/backend/modules/booking-executor.js +61 -6
  68. package/dist/src/backend/modules/session-search.js +136 -39
  69. package/dist/src/booking-surface/dsh-llm-gateway-chat.js +434 -0
  70. package/dist/src/booking-surface/dsh-planner.js +150 -19
  71. package/dist/src/booking-surface/index.js +1 -0
  72. package/dist/src/booking-surface/managed-dsh-run-port.js +121 -7
  73. package/dist/src/booking-surface/server.js +193 -80
  74. package/dist/src/booking-surface/startup.js +153 -0
  75. package/dist/src/booking-surface/tenancy.js +188 -0
  76. package/dist/src/gotry-web-api.js +64 -2
  77. package/dist/src/index.js +70 -18
  78. package/dist/src/llm-price-table.js +148 -0
  79. package/dist/src/tenant-seam/index.js +8 -0
  80. package/dist/src/tenant-seam/llm-broker.js +508 -0
  81. package/dist/src/tenant-seam/process-governor.js +281 -0
  82. package/dist/src/tenant-seam/quotas.js +360 -0
  83. package/dist/src/tenant-seam/seam.js +92 -0
  84. package/dist/src/tenant-state/durable-store.js +158 -0
  85. package/dist/src/tenant-state/epoch-copies.js +422 -0
  86. package/dist/src/tenant-state/errors.js +108 -0
  87. package/dist/src/tenant-state/ids.js +101 -0
  88. package/dist/src/tenant-state/index.js +11 -0
  89. package/dist/src/tenant-state/lease-store.js +203 -0
  90. package/dist/src/tenant-state/tenant-lease.js +150 -0
  91. package/dist/src/tenant-state/tenant-router.js +650 -0
  92. package/extension/manifest.json +2 -2
  93. package/package.json +5 -1
  94. package/ts/capabilities/flyai-config.ts +12 -10
  95. package/ts/capabilities/flyai.ts +91 -50
  96. package/ts/capabilities/hbcli-config.ts +128 -0
  97. package/ts/capabilities/hbcli.ts +30 -3
  98. package/ts/capabilities/session/adapters/ctrip-flight.ts +43 -7
  99. package/ts/capabilities/session/adapters/ctrip-hotel.ts +51 -4
  100. package/ts/capabilities/session/bridge-ledger.ts +192 -78
  101. package/ts/capabilities/session/ctrip-city.ts +158 -0
  102. package/ts/capabilities/session/extension-bridge.ts +185 -67
  103. package/ts/capabilities/session/gui-flight.ts +1022 -0
  104. package/ts/capabilities/session/gui-hotel.ts +505 -0
  105. package/ts/capabilities/session-search.ts +130 -18
  106. package/ts/src/booking-surface/dsh-llm-gateway-chat.js +434 -0
  107. package/ts/src/booking-surface/dsh-planner.ts +169 -19
  108. package/ts/src/booking-surface/index.ts +1 -0
  109. package/ts/src/booking-surface/managed-dsh-run-port.ts +150 -7
  110. package/ts/src/booking-surface/server.ts +215 -84
  111. package/ts/src/booking-surface/startup.ts +155 -0
  112. package/ts/src/booking-surface/tenancy.ts +242 -0
  113. package/ts/src/gotry-web-api.ts +51 -2
  114. package/ts/src/index.ts +67 -19
  115. package/ts/src/llm-price-table.ts +180 -0
  116. package/ts/src/tenant-seam/index.ts +9 -0
  117. package/ts/src/tenant-seam/llm-broker.ts +520 -0
  118. package/ts/src/tenant-seam/process-governor.ts +343 -0
  119. package/ts/src/tenant-seam/quotas.ts +433 -0
  120. package/ts/src/tenant-seam/seam.ts +142 -0
  121. package/ts/src/tenant-state/durable-store.ts +171 -0
  122. package/ts/src/tenant-state/epoch-copies.ts +491 -0
  123. package/ts/src/tenant-state/errors.ts +128 -0
  124. package/ts/src/tenant-state/ids.ts +108 -0
  125. package/ts/src/tenant-state/index.ts +12 -0
  126. package/ts/src/tenant-state/lease-store.ts +266 -0
  127. package/ts/src/tenant-state/tenant-lease.ts +175 -0
  128. package/ts/src/tenant-state/tenant-router.ts +743 -0
@@ -699,7 +699,7 @@ async function main() {
699
699
  __resetRateLimiterForTest();
700
700
  const cookiePoll = claimOnce(lane.port, null);
701
701
  await waitForParkedCount(lane.port, 1);
702
- const search = sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 3_000 });
702
+ const search = sessionFlightSearch({ from: '上海', to: '丽江', fromCityCode: 'SHA', toCityCode: 'LJG', date: '2026-12-01', timeoutMs: 3_000 });
703
703
  const cookieClaim = await cookiePoll;
704
704
  assert.equal(cookieClaim.job?.kind, 'cookie-names');
705
705
  // 商店版先候在桥上;它不得接走另一 Origin 已验证登录态后的 search。
@@ -751,7 +751,7 @@ async function main() {
751
751
  __resetRateLimiterForTest();
752
752
  const cookiePoll = claimOnce(lane.port, null, undefined, ['ctrip-flight'], EXTENSION_ORIGIN, clientA);
753
753
  await waitForParkedCount(lane.port, 1);
754
- const search = sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 3_000, auditPath: shapeAudit });
754
+ const search = sessionFlightSearch({ from: '上海', to: '丽江', fromCityCode: 'SHA', toCityCode: 'LJG', date: '2026-12-01', timeoutMs: 3_000, auditPath: shapeAudit });
755
755
  const cookieClaim = await cookiePoll;
756
756
  assert.equal(cookieClaim.job?.kind, 'cookie-names');
757
757
  otherPoll = claimOnce(lane.port, (job) => {
@@ -820,7 +820,7 @@ async function main() {
820
820
  })();
821
821
  try {
822
822
  __resetRateLimiterForTest();
823
- const result = await sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 3_000 });
823
+ const result = await sessionFlightSearch({ from: '上海', to: '丽江', fromCityCode: 'SHA', toCityCode: 'LJG', date: '2026-12-01', timeoutMs: 3_000 });
824
824
  assert.equal(result.verdict, 'error');
825
825
  assert.equal(result.transportShape, 'sniff_timeout');
826
826
  assert.match(result.error ?? '', /嗅探超时|未收到.*回包/);
@@ -859,7 +859,7 @@ async function main() {
859
859
  })();
860
860
  try {
861
861
  __resetRateLimiterForTest();
862
- const result = await sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 3_000 });
862
+ const result = await sessionFlightSearch({ from: '上海', to: '丽江', fromCityCode: 'SHA', toCityCode: 'LJG', date: '2026-12-01', timeoutMs: 3_000 });
863
863
  assert.equal(result.verdict, 'error');
864
864
  assert.equal(result.transportShape, failedKind === 'search' ? 'search_job_unavailable' : 'precheck_unavailable');
865
865
  assert.doesNotMatch(JSON.stringify(result), /PRIVATE_ROUTE_DATE_COOKIE_VALUE/);
@@ -882,12 +882,18 @@ async function main() {
882
882
  const blockers = [];
883
883
  try {
884
884
  for (const port of [8791, 8792, 8793, 8794, 8795]) {
885
- const s = createServer().listen(port, '127.0.0.1');
886
- await new Promise((r) => s.once('listening', () => r()));
887
- blockers.push({ close: () => new Promise((r) => s.close(() => r())) });
885
+ const s = createServer();
886
+ const owned = await new Promise((resolve, reject) => {
887
+ s.once('listening', () => resolve(true));
888
+ s.once('error', (error) => error.code === 'EADDRINUSE' ? resolve(false) : reject(error));
889
+ s.listen(port, '127.0.0.1');
890
+ });
891
+ // An active user bridge already occupies this port; never stop it.
892
+ if (owned)
893
+ blockers.push({ close: () => new Promise((r) => s.close(() => r())) });
888
894
  }
889
895
  await __resetSessionBridgeForTest();
890
- const r = await sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01' });
896
+ const r = await sessionFlightSearch({ from: '上海', to: '丽江', fromCityCode: 'SHA', toCityCode: 'LJG', date: '2026-12-01' });
891
897
  assert.equal(r.verdict, 'error', `端口池全占应 error(环境故障),实 ${r.verdict}:${r.error}`);
892
898
  assert.ok(r.error?.includes('端口池'), '错误面必须点明端口池环境问题');
893
899
  assert.ok(!r.error?.includes('一次性安装'), 'error 路径不应诱导用户去装扩展');
@@ -0,0 +1,73 @@
1
+ // A worker for the managed port's JSON-lines protocol that misbehaves on request (AaaS threat S-17 drill).
2
+ // Every runaway mode bounds itself, so a governor that fails costs a red test and not a dead machine.
3
+ import { spawn } from 'node:child_process'
4
+ import { writeFileSync } from 'node:fs'
5
+ import { createInterface } from 'node:readline'
6
+
7
+ const MIB = 1024 * 1024
8
+
9
+ if (process.env.TENANT_FIXTURE_PID_FILE) writeFileSync(process.env.TENANT_FIXTURE_PID_FILE, String(process.pid))
10
+ // TENANT_FIXTURE_NO_LIFECYCLE=1: a worker that never says who it is, so nothing can attach a governor to it.
11
+ if (process.env.TENANT_FIXTURE_NO_LIFECYCLE !== '1') {
12
+ // TENANT_FIXTURE_LIE_PID=1: it names a process that is not its own, so a sampler that trusts it would watch nothing.
13
+ const reported = process.env.TENANT_FIXTURE_LIE_PID === '1' ? 2_147_000_000 : process.pid
14
+ process.stdout.write(`${JSON.stringify({ lifecycle: 'worker_started', workerPid: reported, parentPid: process.ppid })}\n`)
15
+ }
16
+
17
+ const hold = []
18
+ const input = createInterface({ input: process.stdin, crlfDelay: Infinity })
19
+
20
+ function reply(value) {
21
+ process.stdout.write(`${JSON.stringify(value)}\n`)
22
+ }
23
+
24
+ input.on('line', (line) => {
25
+ const request = JSON.parse(line)
26
+ // TENANT_FIXTURE_MODE lets a planner, which sends its own prompt text, drive the same behavior.
27
+ const mode = process.env.TENANT_FIXTURE_MODE ?? request.prompt
28
+ if (mode === 'ok') {
29
+ reply({ id: request.id, ok: true, result: { finalResponse: 'ok', events: [{
30
+ nodeOptions: process.env.NODE_OPTIONS ?? null,
31
+ execArgv: process.execArgv,
32
+ ambient: { LLM_BASE_URL: process.env.LLM_BASE_URL ?? null, LLM_API_KEY: process.env.LLM_API_KEY ?? null, DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ?? null, DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL ?? null },
33
+ }], notifications: [] } })
34
+ } else if (mode === 'memory') {
35
+ // Off-heap memory: the V8 heap cap cannot see a Buffer, so only the process-tree sampler can stop this one.
36
+ let total = 0
37
+ const timer = setInterval(() => {
38
+ hold.push(Buffer.alloc(16 * MIB, 1))
39
+ total += 16 * MIB
40
+ if (total >= 1536 * MIB) clearInterval(timer)
41
+ }, 10)
42
+ } else if (mode === 'memory-in-child') {
43
+ // The runaway is a descendant, not the worker: the tree is what is limited.
44
+ const child = spawn(process.execPath, ['-e', `
45
+ const hold = []; let total = 0
46
+ const timer = setInterval(() => { hold.push(Buffer.alloc(${16 * MIB}, 1)); total += ${16 * MIB}; if (total >= ${1536 * MIB}) clearInterval(timer) }, 10)
47
+ `], { stdio: 'ignore' })
48
+ reply({ id: request.id, ok: true, result: { finalResponse: 'spawned', events: [{ childPid: child.pid }], notifications: [] } })
49
+ } else if (mode === 'cpu') {
50
+ // Blocks this thread's event loop; the check comes from outside. Gives up by itself after 30 s.
51
+ const end = Date.now() + 30_000
52
+ while (Date.now() < end) { /* spin */ }
53
+ } else if (mode === 'heap') {
54
+ // JS heap growth: the V8 ceiling in NODE_OPTIONS must end this long before the machine notices.
55
+ const objects = []
56
+ while (process.memoryUsage().heapUsed < 900 * MIB) objects.push({ filler: 'x'.repeat(1000) })
57
+ reply({ id: request.id, ok: true, result: { finalResponse: 'heap-not-limited', events: [], notifications: [] } })
58
+ } else if (mode === 'flood') {
59
+ // One line that never ends, written as fast as the pipe takes it, up to 256 MiB.
60
+ const chunk = 'x'.repeat(64 * 1024)
61
+ let written = 0
62
+ const write = () => {
63
+ while (written < 256 * MIB) {
64
+ written += chunk.length
65
+ if (!process.stdout.write(chunk)) { process.stdout.once('drain', write); return }
66
+ }
67
+ }
68
+ write()
69
+ }
70
+ })
71
+
72
+ process.on('SIGTERM', () => process.exit(143))
73
+ setInterval(() => {}, 1_000)
@@ -90,7 +90,7 @@ async function main() {
90
90
  const expectVerdict = async (label, body, title, expected, extra) => {
91
91
  __resetRateLimiterForTest();
92
92
  testCase = { body, title };
93
- const r = await sessionFlightSearch({ from: '上海', to: '丽江', date: '2026-12-01', timeoutMs: 5_000 });
93
+ const r = await sessionFlightSearch({ from: '上海', to: '丽江', fromCityCode: 'SHA', toCityCode: 'LJG', date: '2026-12-01', timeoutMs: 5_000 });
94
94
  await check(label, () => {
95
95
  assert.equal(r.verdict, expected, `verdict 期望 ${expected},实 ${r.verdict} (${r.error ?? ''})`);
96
96
  if (extra)
@@ -39,7 +39,7 @@ chmodSync(fakeVerifier, 0o700);
39
39
  // that the npm package exposes for `gotry setup ...` dispatch.
40
40
  const packageFixture = join(sandbox, 'package-shaped');
41
41
  mkdirSync(join(packageFixture, 'bin'), { recursive: true });
42
- for (const file of ['gotry-inner.js', 'gotry-bootstrap.js', 'gotry-flyai-verification.js', 'gotry-extension-status.js', 'gotry-process-liveness.js', 'gotry-runtime-resolution.js']) {
42
+ for (const file of ['gotry-inner.js', 'gotry-bootstrap.js', 'gotry-hbcli-release.js', 'gotry-flyai-verification.js', 'gotry-extension-status.js', 'gotry-process-liveness.js', 'gotry-runtime-resolution.js']) {
43
43
  copyFileSync(join(repoRoot, 'bin', file), join(packageFixture, 'bin', file));
44
44
  }
45
45
  writeFileSync(join(packageFixture, 'package.json'), JSON.stringify({ type: 'module', version: 'test' }) + '\n');
@@ -133,7 +133,7 @@ assert.ok(symlinkRun.stdout.includes('配置目录不安全'));
133
133
  assert.ok(!existsSync(join(symlinkTarget, 'config.json')), 'symlink target must not receive credentials');
134
134
  console.log('1c. symlink .flyai rejected without following target OK');
135
135
  // The TypeScript config writer shares the same compatibility rule; the
136
- // verification receipt writer remains separately strict on ~/.gotry.
136
+ // verification receipts use the same safe directory migration.
137
137
  const tsConfigHome = mkdtempSync(join(sandbox, 'ts-config-'));
138
138
  writeOfficialFlyaiDir(tsConfigHome, 'device-ts');
139
139
  const tsSave = saveFlyaiKey('sk-ts-config-key-1234', { homeDir: tsConfigHome, env: {} });
@@ -203,15 +203,30 @@ assert.notEqual(verifyFail.status, 0);
203
203
  assert.deepEqual(readFileSync(configPath(verifyFailHome)), verifyBefore);
204
204
  assert.ok(verifyFail.stdout.includes('配置未改动'));
205
205
  console.log('4. bad JSON + verifier failure preserve old config bytes/key OK');
206
+ // Existing doctor/runtime state must be secured without losing its contents.
207
+ const legacyHome = mkdtempSync(join(sandbox, 'legacy-receipt-'));
208
+ mkdirSync(join(legacyHome, '.gotry'), { mode: 0o755 });
209
+ chmodSync(join(legacyHome, '.gotry'), 0o755);
210
+ writeFileSync(join(legacyHome, '.gotry', 'doctor-report.md'), 'existing report');
211
+ const legacyRun = runPackageEntry(legacyHome, ['setup', 'flyai', '--stdin'], `${key}\n`);
212
+ assert.equal(legacyRun.status, 0, legacyRun.stdout + legacyRun.stderr);
213
+ assert.equal(mode(join(legacyHome, '.gotry')), 0o700);
214
+ assert.equal(mode(receiptPath(legacyHome)), 0o600);
215
+ assert.equal(readFileSync(join(legacyHome, '.gotry', 'doctor-report.md'), 'utf8'), 'existing report');
216
+ console.log('5a. existing 0755 state directory secured without losing contents OK');
206
217
  // 5. receipt write failure is visible and rolls back the newly attempted key.
207
218
  const receiptFailHome = mkdtempSync(join(sandbox, 'receipt-fail-'));
208
219
  writeConfig(receiptFailHome, { FLYAI_API_KEY: oldKey, keep: 'yes' });
209
- mkdirSync(join(receiptFailHome, '.gotry'), { mode: 0o755 });
210
- chmodSync(join(receiptFailHome, '.gotry'), 0o755);
220
+ const receiptTarget = join(receiptFailHome, 'outside-state');
221
+ mkdirSync(receiptTarget, { mode: 0o755 });
222
+ chmodSync(receiptTarget, 0o755);
223
+ symlinkSync(receiptTarget, join(receiptFailHome, '.gotry'), 'dir');
211
224
  const receiptFail = run(receiptFailHome, ['setup', 'flyai', '--stdin'], `${key}\n`);
212
225
  assert.notEqual(receiptFail.status, 0);
213
226
  assert.ok(receiptFail.stdout.includes('验证回执写入失败'));
214
227
  assert.equal(JSON.parse(readFileSync(configPath(receiptFailHome), 'utf8')).FLYAI_API_KEY, oldKey);
228
+ assert.equal(mode(receiptTarget), 0o755);
229
+ assert.ok(!existsSync(join(receiptTarget, 'flyai-verification.json')));
215
230
  console.log('5. receipt write failure reports non-success and preserves old key OK');
216
231
  // The verifier may echo its input credentials. Public diagnostics must not.
217
232
  for (const failure of ['leak-403', 'leak-exit']) {