@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,3 +1,12 @@
1
+ /**
2
+ * 可下单事实侧车持久化(issue #46,ADR-19):append-only JSONL 证据日志。
3
+ *
4
+ * 定位:这是**会话证据**而非用户状态——与 bridge-latency.jsonl / incidents.jsonl
5
+ * 同类(审计痕迹),不进账本投影。append-only 保历史(同 route+date 重查的旧快照
6
+ * 可回溯),读取侧经 dedupeFacts 取同 fact_id 最新。
7
+ *
8
+ * 路径:<stateRoot>/gotry-state/bookable-facts.jsonl
9
+ */
1
10
  import { appendFile, mkdir, readFile } from 'node:fs/promises';
2
11
  import { join } from 'node:path';
3
12
  import { dedupeFacts } from '../src/bookable-facts.js';
@@ -6,36 +15,41 @@ export function factLogPath(stateRoot) {
6
15
  const root = stateRoot === '.' ? process.cwd() : stateRoot;
7
16
  return join(root, 'gotry-state', FACT_LOG_NAME);
8
17
  }
18
+ /** 追加事实(永不抛错:落盘失败不阻塞检索工具主路径,返回 false 由调用方记 incident) */
9
19
  export async function appendFacts(stateRoot, facts) {
10
- if (facts.length === 0) return true;
20
+ if (facts.length === 0)
21
+ return true;
11
22
  try {
12
23
  const p = factLogPath(stateRoot);
13
- await mkdir(join(p, '..'), {
14
- recursive: true
15
- });
16
- await appendFile(p, facts.map((f)=>JSON.stringify(f)).join('\n') + '\n', 'utf-8');
24
+ await mkdir(join(p, '..'), { recursive: true });
25
+ await appendFile(p, facts.map(f => JSON.stringify(f)).join('\n') + '\n', 'utf-8');
17
26
  return true;
18
- } catch {
27
+ }
28
+ catch {
19
29
  return false;
20
30
  }
21
31
  }
32
+ /** 读注册表(去重后视图;无文件 = 空注册表,不抛) */
22
33
  export async function loadFactRegistry(stateRoot) {
23
34
  try {
24
35
  const raw = await readFile(factLogPath(stateRoot), 'utf-8');
25
36
  const facts = [];
26
- for (const line of raw.split('\n')){
37
+ for (const line of raw.split('\n')) {
27
38
  const t = line.trim();
28
- if (!t) continue;
39
+ if (!t)
40
+ continue;
29
41
  try {
30
42
  const f = JSON.parse(t);
31
- if (f.schema === 'gotry_bookable_fact.v1' && f.fact_id) facts.push(f);
32
- } catch {}
43
+ if (f.schema === 'gotry_bookable_fact.v1' && f.fact_id)
44
+ facts.push(f);
45
+ }
46
+ catch {
47
+ // 单行损坏不拖垮整本注册表(与 incidents/latency 日志同纪律)
48
+ }
33
49
  }
34
50
  return dedupeFacts(facts);
35
- } catch {
51
+ }
52
+ catch {
36
53
  return [];
37
54
  }
38
55
  }
39
-
40
-
41
- //# sourceURL=ts/capabilities/fact-log.ts
@@ -1,14 +1,80 @@
1
+ /**
2
+ * FlyAI 官方通道能力层(飞猪开放平台,data-sources.md §8 / RFC docs/rfc/user-session-data-rfc.md P0):
3
+ *
4
+ * 链路(同构 anything.ts 的 CLI spawn 模式):
5
+ * gotry capabilities/flyai.ts
6
+ * → spawn `npx -y @fly-ai/flyai-cli search-flight|search-train --origin X --destination Y --dep-date D`
7
+ * | `search-hotel --dest-name X [--key-words K][--check-in-date A --check-out-date B]`
8
+ * → 飞猪 MCP API(实时直连官方商品库)
9
+ *
10
+ * 契约(与 hbcli/weather/opensky/anything 同构,L4 不变量):
11
+ * - 只读:8 工具全只读,交易经 jumpUrl/detailUrl 由人完成(与 WriteGate 哲学同构);
12
+ * - 永不抛错:网络/超时/解析失败一律降级返回 verdict='error';
13
+ * - 证据链:成功 [实时API:flyai@ts];失败 [实时API:flyai@error@ts];
14
+ * - 匿名试用额度可用但共享易达限(2026-09-02 迪拜 session:429 Trial limit reached
15
+ * 被当通用 error,LLM 跨轮盲重试 4 次)——达限归类 verdict='needs-setup' +
16
+ * setup 指引(申请正式 key 配 FLYAI_API_KEY),工具层据此阻断盲目重试。
17
+ * FLYAI_API_KEY 已配时 env 透传,不再走试用额度。
18
+ */
1
19
  import { spawn } from 'node:child_process';
2
20
  import { closeSync, openSync, readFileSync, unlinkSync } from 'node:fs';
3
21
  import { tmpdir } from 'node:os';
4
22
  import { join } from 'node:path';
23
+ function nonEmptyText(value) {
24
+ if (typeof value !== 'string')
25
+ return undefined;
26
+ const text = value.trim();
27
+ return text || undefined;
28
+ }
29
+ function optionalText(value, allowNull = false) {
30
+ if (value === undefined)
31
+ return undefined;
32
+ if (allowNull && value === null)
33
+ return null;
34
+ return nonEmptyText(value);
35
+ }
36
+ function positiveFiniteNumber(value) {
37
+ const number = typeof value === 'number'
38
+ ? value
39
+ : typeof value === 'string' && value.trim() ? Number(value.trim()) : Number.NaN;
40
+ return Number.isFinite(number) && number > 0 ? number : undefined;
41
+ }
42
+ function positiveFiniteDuration(value) {
43
+ if (typeof value === 'number')
44
+ return positiveFiniteNumber(value);
45
+ const text = nonEmptyText(value);
46
+ if (!text)
47
+ return undefined;
48
+ const minutes = /^(\d+(?:\.\d+)?)\s*分钟$/.exec(text)?.[1];
49
+ return positiveFiniteNumber(minutes ?? text);
50
+ }
51
+ function parseTransportPrice(value) {
52
+ const raw = nonEmptyText(value);
53
+ if (!raw)
54
+ return undefined;
55
+ const bare = raw.replace(/^¥\s*/, '');
56
+ if (/^\d+(?:\.\d+)?$/.test(bare)) {
57
+ const price = Number(bare);
58
+ return Number.isFinite(price) && price > 0 ? { price } : undefined;
59
+ }
60
+ // 未鉴权态可能返回 1xxx/¥7xx;保留原值,但绝不把模糊串猜成数字。
61
+ if (/^\d+x+$/.test(bare))
62
+ return { price: 0, priceRaw: raw };
63
+ return undefined;
64
+ }
65
+ /**
66
+ * 从 CLI stdout 中提取首个完整且含 itemList 的 JSON 对象。
67
+ *
68
+ * npx/CLI 偶发在业务 JSON 前后追加提示;不能从首个 `{` 一直切到 EOF。
69
+ * 扫描器识别字符串与转义,因此 JSON 字符串里的花括号不会破坏深度计数。
70
+ */
5
71
  export function parseFlyaiItemList(stdout) {
6
72
  let start = -1;
7
73
  let depth = 0;
8
74
  let inString = false;
9
75
  let escaped = false;
10
76
  let sawIncompleteObject = false;
11
- for(let index = 0; index < stdout.length; index += 1){
77
+ for (let index = 0; index < stdout.length; index += 1) {
12
78
  const char = stdout[index];
13
79
  if (start < 0) {
14
80
  if (char === '{') {
@@ -21,243 +87,266 @@ export function parseFlyaiItemList(stdout) {
21
87
  continue;
22
88
  }
23
89
  if (inString) {
24
- if (escaped) escaped = false;
25
- else if (char === '\\') escaped = true;
26
- else if (char === '"') inString = false;
90
+ if (escaped)
91
+ escaped = false;
92
+ else if (char === '\\')
93
+ escaped = true;
94
+ else if (char === '"')
95
+ inString = false;
27
96
  continue;
28
97
  }
29
- if (char === '"') inString = true;
30
- else if (char === '{') depth += 1;
98
+ if (char === '"')
99
+ inString = true;
100
+ else if (char === '{')
101
+ depth += 1;
31
102
  else if (char === '}') {
32
103
  depth -= 1;
33
- if (depth !== 0) continue;
104
+ if (depth !== 0)
105
+ continue;
34
106
  const candidate = stdout.slice(start, index + 1);
35
107
  start = -1;
36
108
  sawIncompleteObject = false;
37
109
  try {
38
110
  const parsed = JSON.parse(candidate);
39
- if (Array.isArray(parsed.data?.itemList)) return parsed.data.itemList;
40
- } catch {}
111
+ if (Array.isArray(parsed.data?.itemList))
112
+ return parsed.data.itemList;
113
+ }
114
+ catch {
115
+ // 前缀日志可能包含成对花括号但不是 JSON;继续找下一个完整对象。
116
+ }
41
117
  }
42
118
  }
43
- throw new Error(sawIncompleteObject ? 'incomplete FlyAI JSON object' : 'no complete FlyAI itemList JSON object');
119
+ throw new Error(sawIncompleteObject
120
+ ? 'incomplete FlyAI JSON object'
121
+ : 'no complete FlyAI itemList JSON object');
44
122
  }
123
+ /**
124
+ * 子进程 stdout 走临时文件而非管道(issue #84):
125
+ * flyai CLI 异步写 stdout 后立即 process.exit();stdout 是管道时未 flush 的
126
+ * 尾部会被丢掉(实测截断在 ~7.6KB,exit=0 静默)。stdout 是文件时 Node 同步写,
127
+ * exit 不丢数据;且文件无 64KB 管道缓冲上限。close 后读文件,语义不变。
128
+ */
45
129
  function sh(cmd, args, opts) {
46
130
  const outFile = join(tmpdir(), `gotry-flyai-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.out`);
47
131
  const outFd = openSync(outFile, 'w');
48
- const child = spawn(cmd, args, {
49
- env: process.env,
50
- cwd: process.cwd(),
51
- stdio: [
52
- 'ignore',
53
- outFd,
54
- 'pipe'
55
- ]
56
- });
132
+ const child = spawn(cmd, args, { env: process.env, cwd: process.cwd(), stdio: ['ignore', outFd, 'pipe'] });
57
133
  let stderr = '';
58
134
  let error;
59
- const timer = setTimeout(()=>{
135
+ const timer = setTimeout(() => {
60
136
  try {
61
137
  child.kill('SIGKILL');
62
- } catch {}
138
+ }
139
+ catch { /* ignore */ }
63
140
  }, opts.timeoutMs);
64
- return new Promise((resolve)=>{
65
- child.stderr?.on('data', (d)=>{
66
- stderr += d.toString();
67
- });
68
- child.on('error', (e)=>{
69
- error = e.message.slice(0, 200);
70
- });
71
- child.on('close', (code)=>{
141
+ return new Promise((resolve) => {
142
+ child.stderr?.on('data', (d) => { stderr += d.toString(); });
143
+ child.on('error', (e) => { error = e.message.slice(0, 200); });
144
+ child.on('close', (code) => {
72
145
  clearTimeout(timer);
73
146
  closeSync(outFd);
74
147
  let stdout = '';
75
148
  try {
76
149
  stdout = readFileSync(outFile, 'utf8');
77
- } catch {}
150
+ }
151
+ catch { /* 子进程未启动时文件可能为空,按空 stdout 处理 */ }
78
152
  try {
79
153
  unlinkSync(outFile);
80
- } catch {}
81
- resolve({
82
- code: code ?? -1,
83
- stdout,
84
- stderr,
85
- error
86
- });
154
+ }
155
+ catch { /* ignore */ }
156
+ resolve({ code: code ?? -1, stdout, stderr, error });
87
157
  });
88
- }).finally(()=>clearTimeout(timer));
158
+ }).finally(() => clearTimeout(timer));
89
159
  }
90
160
  const YMD = /^\d{4}-\d{2}-\d{2}$/;
161
+ /** FlyAI 官方只读检索(机票/火车票/酒店) — 任何失败走降级;不抛错 */
91
162
  export async function flyaiSearch(q) {
92
163
  const started = Date.now();
93
164
  const ts = new Date().toISOString();
94
- const base = {
95
- kind: q.kind,
96
- latencyMs: 0
97
- };
98
- const argErr = (error)=>({
99
- ...base,
100
- ok: false,
101
- via: 'flyai-error',
102
- verdict: 'error',
103
- evidence: `[实时API:flyai@error@${ts}] bad args`,
104
- error
105
- });
165
+ const base = { kind: q.kind, latencyMs: 0 };
166
+ const argErr = (error) => ({ ...base, ok: false, via: 'flyai-error', verdict: 'error', evidence: `[实时API:flyai@error@${ts}] bad args`, error });
167
+ // per-kind 参数闸:机/火与酒店参数面不同,各自校验,不合法走 error 不静默容错
106
168
  let cliArgs;
107
169
  if (q.kind === 'hotel') {
108
170
  const dest = (q.destName ?? q.destination ?? '').trim();
109
- if (!dest) return argErr('hotel 需要 to/destName(目的地中文)');
110
- const badPair = (q.checkInDate ? 1 : 0) !== (q.checkOutDate ? 1 : 0) || q.checkInDate && q.checkOutDate && (!YMD.test(q.checkInDate) || !YMD.test(q.checkOutDate));
111
- if (badPair) return argErr('checkInDate/checkOutDate 须成对且为 YYYY-MM-DD(未定档期可不带日期先摸底)');
171
+ if (!dest)
172
+ return argErr('hotel 需要 to/destName(目的地中文)');
173
+ const badPair = (q.checkInDate ? 1 : 0) !== (q.checkOutDate ? 1 : 0)
174
+ || (q.checkInDate && q.checkOutDate && (!YMD.test(q.checkInDate) || !YMD.test(q.checkOutDate)));
175
+ if (badPair)
176
+ return argErr('checkInDate/checkOutDate 须成对且为 YYYY-MM-DD(未定档期可不带日期先摸底)');
112
177
  cliArgs = [
113
- 'search-hotel',
114
- '--dest-name',
115
- dest,
116
- ...q.checkInDate && q.checkOutDate ? [
117
- '--check-in-date',
118
- q.checkInDate,
119
- '--check-out-date',
120
- q.checkOutDate
121
- ] : [],
122
- ...q.keyWords ? [
123
- '--key-words',
124
- q.keyWords
125
- ] : []
178
+ 'search-hotel', '--dest-name', dest,
179
+ ...(q.checkInDate && q.checkOutDate ? ['--check-in-date', q.checkInDate, '--check-out-date', q.checkOutDate] : []),
180
+ ...(q.keyWords ? ['--key-words', q.keyWords] : []),
126
181
  ];
127
- } else {
182
+ }
183
+ else {
128
184
  const origin = (q.origin ?? '').trim();
129
185
  const destination = (q.destination ?? '').trim();
130
186
  if (!origin || !destination || !YMD.test(q.depDate ?? '')) {
131
187
  return argErr('origin/destination/depDate(YYYY-MM-DD) required');
132
188
  }
133
189
  const sub = q.kind === 'flight' ? 'search-flight' : 'search-train';
134
- cliArgs = [
135
- sub,
136
- '--origin',
137
- origin,
138
- '--destination',
139
- destination,
140
- '--dep-date',
141
- q.depDate ?? ''
142
- ];
190
+ cliArgs = [sub, '--origin', origin, '--destination', destination, '--dep-date', (q.depDate ?? '')];
143
191
  }
144
- const r = await sh(q.cliBin ?? 'npx', [
145
- '-y',
146
- '@fly-ai/flyai-cli',
147
- ...cliArgs
148
- ], {
149
- timeoutMs: q.timeoutMs ?? 30_000
192
+ const r = await sh(q.cliBin ?? 'npx', ['-y', '@fly-ai/flyai-cli', ...cliArgs], {
193
+ timeoutMs: q.timeoutMs ?? 30_000,
150
194
  });
151
195
  const latencyMs = Date.now() - started;
152
196
  if (r.error || r.code !== 0) {
197
+ // 试用额度达限(2026-09-02 迪拜 session 实况:exit 1 + MCP HTTP 429 "Trial limit
198
+ // reached")是配置问题不是瞬时故障——归 needs-setup 带补配指引,让工具层阻断
199
+ // LLM 拿同一把 429 跨轮盲重试;正式 key 经 FLYAI_API_KEY 配入后此路径不再触发。
153
200
  const upstream = `${r.stderr}\n${r.stdout}`;
154
201
  if (/Trial limit reached|HTTP\s*429|\b429\b/.test(upstream)) {
155
202
  return {
156
- ...base,
157
- latencyMs,
158
- ok: false,
159
- via: 'flyai-error',
160
- verdict: 'needs-setup',
203
+ ...base, latencyMs, ok: false, via: 'flyai-error', verdict: 'needs-setup',
161
204
  evidence: `[实时API:flyai@error@${ts}] trial quota exhausted`,
162
205
  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 检索推进。'
206
+ setup: 'FlyAI 试用额度达限——到 flyai.open.fliggy.com 控制台申请正式 API Key,配入环境变量 FLYAI_API_KEY 即恢复;本会话请勿重试 gotry_flyai_search,机/火/酒改走 gotry_session_search(账号会话)或 web 检索推进。',
164
207
  };
165
208
  }
166
- return {
167
- ...base,
168
- latencyMs,
169
- ok: false,
170
- via: 'flyai-error',
171
- verdict: 'error',
172
- evidence: `[实时API:flyai@error@${ts}] ${r.error ?? `exit ${r.code}`}`,
173
- error: r.error ?? r.stderr.replace(/\s+/g, ' ').slice(0, 200)
174
- };
209
+ return { ...base, latencyMs, ok: false, via: 'flyai-error', verdict: 'error', evidence: `[实时API:flyai@error@${ts}] ${r.error ?? `exit ${r.code}`}`, error: r.error ?? r.stderr.replace(/\s+/g, ' ').slice(0, 200) };
175
210
  }
176
211
  let items;
177
212
  try {
213
+ // main-lane 加固的扫描器:容忍 npx 前缀日志/不完整对象——data:null 语义失败
214
+ // (issue #24)的「出发日期非法」原话经 raw stdout 片段进 error 终态,语义不丢
178
215
  items = parseFlyaiItemList(r.stdout);
179
- } catch (e) {
216
+ }
217
+ catch (e) {
218
+ // 实测(2026-08-28):Sentinel 限流时 CLI exit=0 但 stdout 是 {"message":"SentinelBlockException..."}
180
219
  const raw = r.stdout.replace(/\s+/g, ' ').slice(0, 160);
181
220
  const reason = e instanceof Error ? e.message : String(e);
182
- return {
183
- ...base,
184
- latencyMs,
185
- ok: false,
186
- via: 'flyai-error',
187
- verdict: 'error',
188
- evidence: `[实时API:flyai@error@${ts}] parse failed(${reason}): ${raw}`,
189
- error: `failed to parse flyai output as JSON (${reason}): ${raw}`
190
- };
221
+ return { ...base, latencyMs, ok: false, via: 'flyai-error', verdict: 'error', evidence: `[实时API:flyai@error@${ts}] parse failed(${reason}): ${raw}`, error: `failed to parse flyai output as JSON (${reason}): ${raw}` };
191
222
  }
192
223
  if (q.kind === 'hotel') {
193
- const hotels = parseHotelItems(items);
224
+ const parsed = parseHotelItems(items);
225
+ if (parsed.malformedCount > 0)
226
+ return malformedItemListResult(q.kind, latencyMs, ts, parsed.malformedCount, items.length);
227
+ const hotels = parsed.options;
194
228
  const verdict = hotels.length > 0 ? 'hit' : 'miss';
195
- return {
196
- kind: q.kind,
197
- latencyMs,
198
- ok: true,
199
- via: 'flyai',
200
- verdict,
201
- evidence: `[实时API:flyai@${ts}] ${hotels.length}/${items.length} hotel options`,
202
- hotels
203
- };
229
+ return { kind: q.kind, latencyMs, ok: true, via: 'flyai', verdict, evidence: `[实时API:flyai@${ts}] ${hotels.length}/${items.length} hotel options`, hotels };
204
230
  }
205
- const options = parseTransportItems(items);
231
+ const parsed = parseTransportItems(items);
232
+ if (parsed.malformedCount > 0)
233
+ return malformedItemListResult(q.kind, latencyMs, ts, parsed.malformedCount, items.length);
234
+ const options = parsed.options;
206
235
  const verdict = options.length > 0 ? 'hit' : 'miss';
236
+ return { kind: q.kind, latencyMs, ok: true, via: 'flyai', verdict, evidence: `[实时API:flyai@${ts}] ${options.length}/${items.length} ${q.kind} options`, options };
237
+ }
238
+ function malformedItemListResult(kind, latencyMs, ts, malformedCount, total) {
239
+ const label = kind === 'hotel' ? 'hotel' : 'transport';
240
+ const error = `FlyAI ${label} itemList malformed: ${malformedCount}/${total} item(s) failed typed validation`;
207
241
  return {
208
- kind: q.kind,
242
+ kind,
209
243
  latencyMs,
210
- ok: true,
211
- via: 'flyai',
212
- verdict,
213
- evidence: `[实时API:flyai@${ts}] ${options.length}/${items.length} ${q.kind} options`,
214
- options
244
+ ok: false,
245
+ via: 'flyai-error',
246
+ verdict: 'error',
247
+ evidence: `[实时API:flyai@error@${ts}] ${label} itemList malformed (${malformedCount}/${total} items failed typed validation; valid siblings discarded)`,
248
+ error,
215
249
  };
216
250
  }
251
+ /** 机/火条目 → FlyaiOption;非空列表含任一不完整条目即整体 malformed */
217
252
  function parseTransportItems(items) {
218
253
  const options = [];
219
- for (const it of items){
220
- const seg = it.journeys?.[0]?.segments?.[0];
221
- if (!seg?.marketingTransportNo || !seg.depDateTime) continue;
222
- const rawPrice = it.ticketPrice ?? it.price ?? '';
223
- const numericPrice = Number(rawPrice);
254
+ let malformedCount = 0;
255
+ for (const raw of items) {
256
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
257
+ malformedCount += 1;
258
+ continue;
259
+ }
260
+ const it = raw;
261
+ const journey = Array.isArray(it.journeys) ? it.journeys[0] : undefined;
262
+ const seg = journey && typeof journey === 'object' && !Array.isArray(journey) && Array.isArray(journey.segments)
263
+ ? journey.segments[0]
264
+ : undefined;
265
+ if (!seg || typeof seg !== 'object' || Array.isArray(seg)) {
266
+ malformedCount += 1;
267
+ continue;
268
+ }
269
+ const no = nonEmptyText(seg.marketingTransportNo);
270
+ const name = optionalText(seg.marketingTransportName);
271
+ const depDateTime = nonEmptyText(seg.depDateTime);
272
+ const arrDateTime = nonEmptyText(seg.arrDateTime);
273
+ const depStation = nonEmptyText(seg.depStationName);
274
+ const arrStation = nonEmptyText(seg.arrStationName);
275
+ const durationMin = positiveFiniteDuration(seg.duration);
276
+ const rawPriceValue = it.adultPrice !== undefined
277
+ ? it.adultPrice
278
+ : it.ticketPrice !== undefined
279
+ ? it.ticketPrice
280
+ : it.price;
281
+ const parsedPrice = parseTransportPrice(rawPriceValue);
282
+ const seatClass = optionalText(seg.seatClassName);
283
+ const jumpUrl = optionalText(it.jumpUrl);
284
+ if (!no || !depDateTime || !arrDateTime || !depStation || !arrStation || durationMin === undefined || !parsedPrice
285
+ || (seg.marketingTransportName !== undefined && name === undefined)
286
+ || (seg.seatClassName !== undefined && seatClass === undefined)
287
+ || (it.jumpUrl !== undefined && jumpUrl === undefined)) {
288
+ malformedCount += 1;
289
+ continue;
290
+ }
224
291
  options.push({
225
- no: seg.marketingTransportNo,
226
- name: seg.marketingTransportName ?? '',
227
- depDateTime: seg.depDateTime ?? '',
228
- arrDateTime: seg.arrDateTime ?? '',
229
- depStation: seg.depStationName ?? '',
230
- arrStation: seg.arrStationName ?? '',
231
- durationMin: Number(seg.duration ?? 0) || 0,
232
- price: Number.isFinite(numericPrice) && numericPrice > 0 ? numericPrice : 0,
233
- priceRaw: /^\d+$/.test(rawPrice) ? undefined : rawPrice || undefined,
234
- seatClass: seg.seatClassName,
235
- jumpUrl: it.jumpUrl
292
+ no,
293
+ name: name ?? '',
294
+ depDateTime,
295
+ arrDateTime,
296
+ depStation,
297
+ arrStation,
298
+ durationMin,
299
+ price: parsedPrice.price,
300
+ priceRaw: parsedPrice.priceRaw,
301
+ seatClass: seatClass ?? undefined,
302
+ jumpUrl: jumpUrl ?? undefined,
236
303
  });
237
304
  }
238
- return options;
305
+ return { options, malformedCount };
239
306
  }
307
+ /** 酒店条目 → FlyaiHotelOption;非空列表含任一不完整条目即整体 malformed */
240
308
  function parseHotelItems(items) {
241
309
  const hotels = [];
242
- for (const it of items){
243
- if (!it.name) continue;
244
- const rawPrice = String(it.price ?? '');
245
- const bare = rawPrice.replace(/^[¥]/, '');
310
+ let malformedCount = 0;
311
+ for (const raw of items) {
312
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
313
+ malformedCount += 1;
314
+ continue;
315
+ }
316
+ const it = raw;
317
+ const name = nonEmptyText(it.name);
318
+ const star = optionalText(it.star);
319
+ const rawPrice = optionalText(it.price);
320
+ const rate = optionalText(it.rate, true);
321
+ const address = optionalText(it.address);
322
+ const poi = optionalText(it.interestsPoi);
323
+ const hotelId = optionalText(it.shId);
324
+ const jumpUrl = optionalText(it.detailUrl);
325
+ if (!name
326
+ || (it.star !== undefined && star === undefined)
327
+ || (it.price !== undefined && rawPrice === undefined)
328
+ || (it.rate !== undefined && rate === undefined)
329
+ || (it.address !== undefined && address === undefined)
330
+ || (it.interestsPoi !== undefined && poi === undefined)
331
+ || (it.shId !== undefined && hotelId === undefined)
332
+ || (it.detailUrl !== undefined && jumpUrl === undefined)) {
333
+ malformedCount += 1;
334
+ continue;
335
+ }
336
+ // 打码价如 "¥7xx" 绝不能截成数字 7(会伪装成真价)——仅全数字串才落 price
337
+ const bare = (rawPrice ?? '').replace(/^[¥]/, '');
246
338
  const numericPrice = Number(bare);
247
339
  hotels.push({
248
- name: it.name,
249
- star: it.star,
340
+ name,
341
+ star: star ?? undefined,
250
342
  price: /^\d+(\.\d+)?$/.test(bare) && numericPrice > 0 ? numericPrice : 0,
251
343
  priceRaw: rawPrice || undefined,
252
- rate: it.rate != null ? String(it.rate) : undefined,
253
- address: it.address,
254
- poi: it.interestsPoi,
255
- hotelId: it.shId,
256
- jumpUrl: it.detailUrl
344
+ rate: rate ?? undefined,
345
+ address: address ?? undefined,
346
+ poi: poi ?? undefined,
347
+ hotelId: hotelId ?? undefined,
348
+ jumpUrl: jumpUrl ?? undefined,
257
349
  });
258
350
  }
259
- return hotels;
351
+ return { options: hotels, malformedCount };
260
352
  }
261
-
262
-
263
- //# sourceURL=ts/capabilities/flyai.ts