@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,18 +1,284 @@
1
+ /**
2
+ * Z3 WASM 运行时单例(README Known limitations「Z3 WASM race」的根治面)。
3
+ *
4
+ * 历史:engine/journey/unified 三模块各自 `init().Context('main')`:
5
+ * - 一进程内并存 2-3 份独立 WASM 实例(内存放大;系统压力下 2GB 堆分配失败的 OOM 形态);
6
+ * - 候选求解用 Promise.all 并发多候选共享同一 Context,z3-solver 的 async API
7
+ * (Asyncify)不允许并发 unwind,交错即栈损坏 → `memory access out of bounds`
8
+ * (run-all §1 重试止血的真因;rc.4 曾试统一单例但残留自建 Context 触发 mismatch 被回滚)。
9
+ *
10
+ * #227 补充:
11
+ * - getZ3 必须在第一个 await 之前缓存 Promise,否则冷启动并发会创建多个 Context 对象;
12
+ * - z3-solver 5.2.0 high-level FinalizationRegistry 的 cleanup 直接调用 low-level
13
+ * `dec_ref`/`*_dec_ref`,不经过 GoTry `withZ3`。V8 判定对象不可达的时机保持不变;
14
+ * GoTry 只在 Z3 会话活跃时把实际 native cleanup 推迟到会话 settle 后同步 drain,
15
+ * 防止 GC cleanup 与 check()/AST 构造并发碰同一 WASM heap。
16
+ *
17
+ * 现在的口径:三模块全部 import 本模块——单一 WASM 实例 + 单一 Context + 会话级
18
+ * 互斥(await chain 占链)。低层 cleanup 队列是局部包装,不替换全局 FinalizationRegistry,
19
+ * 不主动 free 仍可达对象;显式 release/GC finalizer 仍由 z3-solver 的所有权语义决定。
20
+ */
21
+ import { AsyncLocalStorage } from 'node:async_hooks';
22
+ const MAX_DEFERRED_CLEANUPS = parseCleanupQueueLimit();
23
+ const ASYNC_LOW_LEVEL_CALLS = new Set([
24
+ 'simplify', 'simplify_ex', 'eval_smtlib2_string',
25
+ 'tactic_apply', 'tactic_apply_ex',
26
+ 'solver_check', 'solver_check_assumptions', 'solver_get_consequences', 'solver_cube',
27
+ 'algebraic_roots', 'algebraic_eval',
28
+ 'fixedpoint_query', 'fixedpoint_query_relations', 'fixedpoint_query_from_lvl',
29
+ 'optimize_check', 'polynomial_subresultants',
30
+ ]);
31
+ const nestedSession = new AsyncLocalStorage();
32
+ const cleanupWrappedFunctions = new Set();
33
+ const asyncWrappedFunctions = new Set();
34
+ const nativeCleanupCalls = {};
35
+ const deferredCleanupCalls = {};
36
+ const drainedCleanupCalls = {};
37
+ const deferredCleanups = [];
38
+ const nativeIdleWaiters = [];
1
39
  let z3Promise = null;
40
+ let z3SessionActive = false;
41
+ let activeNativeCalls = 0;
42
+ let z3Poisoned = null;
43
+ let maxDeferredQueueDepth = 0;
44
+ const lifecycleStats = {
45
+ initAttempts: 0,
46
+ contextsCreated: 0,
47
+ concurrentDecRefEnabled: 0,
48
+ sessionsStarted: 0,
49
+ sessionsSettled: 0,
50
+ };
51
+ /** 求解互斥链:后到会话排队等前一会话完全 settle(成败皆放行,不吞异常) */
2
52
  let chain = Promise.resolve();
53
+ function incCounter(counters, name) {
54
+ counters[name] = (counters[name] ?? 0) + 1;
55
+ }
56
+ function parseCleanupQueueLimit() {
57
+ const raw = process.env.GOTRY_Z3_CLEANUP_QUEUE_LIMIT ?? '2000000';
58
+ const value = Number(raw);
59
+ if (!Number.isInteger(value) || value <= 0)
60
+ throw new Error(`GOTRY_Z3_CLEANUP_QUEUE_LIMIT must be a positive integer: ${raw}`);
61
+ return value;
62
+ }
63
+ function poisonZ3(error, fatalNative = false) {
64
+ const err = error instanceof Error ? error : new Error(String(error));
65
+ if (!z3Poisoned)
66
+ z3Poisoned = { error: err, fatalNative };
67
+ else
68
+ z3Poisoned.fatalNative = z3Poisoned.fatalNative || fatalNative;
69
+ return err;
70
+ }
71
+ function poisonMessage() {
72
+ return z3Poisoned ? z3Poisoned.error.message : null;
73
+ }
74
+ function hasFatalNativePoison() {
75
+ return z3Poisoned ? z3Poisoned.fatalNative : false;
76
+ }
77
+ function callNativeCleanup(name, invoke, fromDrain) {
78
+ try {
79
+ invoke();
80
+ if (fromDrain)
81
+ incCounter(drainedCleanupCalls, name);
82
+ return null;
83
+ }
84
+ catch (error) {
85
+ const err = poisonZ3(error, isFatalZ3Error(error));
86
+ // This wrapper can be reached from z3-solver's FinalizationRegistry cleanup.
87
+ // Never throw from the cleanup call-site; surface the poison at the next controlled withZ3 entry.
88
+ return err;
89
+ }
90
+ }
91
+ function deferNativeCleanup(cleanup) {
92
+ deferredCleanups.push(cleanup);
93
+ incCounter(deferredCleanupCalls, cleanup.name);
94
+ if (deferredCleanups.length > maxDeferredQueueDepth)
95
+ maxDeferredQueueDepth = deferredCleanups.length;
96
+ if (deferredCleanups.length > MAX_DEFERRED_CLEANUPS) {
97
+ poisonZ3(new Error(`Z3 cleanup queue overflow (${deferredCleanups.length}/${MAX_DEFERRED_CLEANUPS})`), false);
98
+ }
99
+ }
100
+ function beginNativeCall() {
101
+ activeNativeCalls += 1;
102
+ }
103
+ function finishNativeCall() {
104
+ activeNativeCalls -= 1;
105
+ if (activeNativeCalls < 0) {
106
+ activeNativeCalls = 0;
107
+ poisonZ3(new Error('Z3 native async call counter underflow'));
108
+ }
109
+ if (activeNativeCalls === 0) {
110
+ for (const resolve of nativeIdleWaiters.splice(0))
111
+ resolve();
112
+ if (!z3SessionActive && !hasFatalNativePoison())
113
+ drainDeferredCleanups();
114
+ }
115
+ }
116
+ async function waitForNativeIdle() {
117
+ if (activeNativeCalls === 0)
118
+ return;
119
+ await new Promise(resolve => nativeIdleWaiters.push(resolve));
120
+ }
121
+ function trackNativePromise(name, promise) {
122
+ return promise.then(value => value, error => {
123
+ if (isFatalZ3Error(error))
124
+ poisonZ3(error, true);
125
+ throw error;
126
+ }).finally(() => finishNativeCall());
127
+ }
128
+ function drainDeferredCleanups() {
129
+ let firstError = null;
130
+ while (deferredCleanups.length > 0) {
131
+ const cleanup = deferredCleanups.pop();
132
+ const err = callNativeCleanup(cleanup.name, cleanup.invoke, true);
133
+ if (!err)
134
+ continue;
135
+ if (!firstError)
136
+ firstError = err;
137
+ if (hasFatalNativePoison()) {
138
+ deferredCleanups.push(cleanup);
139
+ break;
140
+ }
141
+ }
142
+ return firstError;
143
+ }
144
+ function installZ3RuntimeGuards(Z3) {
145
+ for (const name of Object.keys(Z3)) {
146
+ if (ASYNC_LOW_LEVEL_CALLS.has(name)) {
147
+ const original = Z3[name];
148
+ if (typeof original === 'function' && !original.__gotryNativeAsyncWrapped) {
149
+ const wrapped = (...args) => {
150
+ beginNativeCall();
151
+ let result;
152
+ try {
153
+ result = original(...args);
154
+ }
155
+ catch (error) {
156
+ if (isFatalZ3Error(error))
157
+ poisonZ3(error, true);
158
+ finishNativeCall();
159
+ throw error;
160
+ }
161
+ if (result instanceof Promise)
162
+ return trackNativePromise(name, result);
163
+ finishNativeCall();
164
+ return result;
165
+ };
166
+ Object.defineProperty(wrapped, '__gotryNativeAsyncWrapped', { value: true });
167
+ Z3[name] = wrapped;
168
+ asyncWrappedFunctions.add(name);
169
+ }
170
+ }
171
+ if (name !== 'dec_ref' && !name.endsWith('_dec_ref'))
172
+ continue;
173
+ const original = Z3[name];
174
+ if (typeof original !== 'function' || original.__gotryCleanupQueueWrapped)
175
+ continue;
176
+ const wrapped = (...args) => {
177
+ incCounter(nativeCleanupCalls, name);
178
+ const invoke = () => original(...args);
179
+ if (hasFatalNativePoison() || z3SessionActive || activeNativeCalls > 0) {
180
+ deferNativeCleanup({ name, args, invoke });
181
+ return undefined;
182
+ }
183
+ return callNativeCleanup(name, invoke, false);
184
+ };
185
+ Object.defineProperty(wrapped, '__gotryCleanupQueueWrapped', { value: true });
186
+ Z3[name] = wrapped;
187
+ cleanupWrappedFunctions.add(name);
188
+ }
189
+ }
190
+ /** Initialize one shared Context and install the local low-level cleanup queue. */
191
+ export async function initZ3Context(loadInit) {
192
+ lifecycleStats.initAttempts += 1;
193
+ const api = await loadInit();
194
+ installZ3RuntimeGuards(api.Z3);
195
+ const ctx = api.Context('main');
196
+ lifecycleStats.contextsCreated += 1;
197
+ if (typeof api.Z3.enable_concurrent_dec_ref !== 'function') {
198
+ throw new Error('z3-solver low-level API missing enable_concurrent_dec_ref; refusing unsafe Z3 context');
199
+ }
200
+ api.Z3.enable_concurrent_dec_ref(ctx.ptr);
201
+ lifecycleStats.concurrentDecRefEnabled += 1;
202
+ return ctx;
203
+ }
3
204
  export async function getZ3() {
205
+ // 延迟到首次实际求解才加载:避免 dsh 加载 GoTry 模块时启动 WASM worker。
206
+ // Promise 必须在第一个 await 之前写入缓存,防冷启动并发创建多 Context。
4
207
  if (!z3Promise) {
5
- const { init } = await import('z3-solver');
6
- z3Promise = (async ()=>(await init()).Context('main'))();
208
+ z3Promise = (async () => {
209
+ const { init } = await import('z3-solver');
210
+ return initZ3Context(init);
211
+ })().catch(error => {
212
+ z3Promise = null;
213
+ throw error;
214
+ });
7
215
  }
8
216
  return z3Promise;
9
217
  }
218
+ /** Z3 求解会话互斥门:全程独占 WASM 实例,防 Asyncify 并发 unwind(栈损坏根因)。 */
10
219
  export async function withZ3(label, session) {
220
+ const outer = nestedSession.getStore();
221
+ if (outer)
222
+ throw new Error(`nested withZ3 is forbidden (${outer} -> ${label})`);
11
223
  const z3 = await getZ3();
12
- const turn = chain.then(()=>session(z3));
13
- chain = turn.then(()=>undefined, ()=>undefined);
224
+ const turn = chain.then(() => nestedSession.run(label, () => runZ3Session(label, z3, session)));
225
+ // 链推进到本会话 settlement(吞掉的只是链上对前序错误的转发,异常仍从本会话返回值抛出)
226
+ chain = turn.then(() => undefined, () => undefined);
14
227
  return turn;
15
228
  }
16
-
17
-
18
- //# sourceURL=ts/src/z3-shared.ts
229
+ async function runZ3Session(label, z3, session) {
230
+ if (poisonMessage())
231
+ throw new Error(`Z3 context is poisoned; refusing new solve (${poisonMessage()})`);
232
+ lifecycleStats.sessionsStarted += 1;
233
+ z3SessionActive = true;
234
+ let value;
235
+ let sessionFailed = false;
236
+ let sessionError;
237
+ let cleanupError = null;
238
+ try {
239
+ value = await session(z3);
240
+ }
241
+ catch (error) {
242
+ sessionFailed = true;
243
+ sessionError = error;
244
+ }
245
+ finally {
246
+ await waitForNativeIdle();
247
+ if (sessionFailed && isFatalZ3Error(sessionError))
248
+ poisonZ3(sessionError, true);
249
+ z3SessionActive = false;
250
+ cleanupError = hasFatalNativePoison() ? null : drainDeferredCleanups();
251
+ lifecycleStats.sessionsSettled += 1;
252
+ }
253
+ if (sessionFailed) {
254
+ if (cleanupError && typeof sessionError === 'object' && sessionError !== null) {
255
+ ;
256
+ sessionError.z3CleanupError = cleanupError;
257
+ }
258
+ throw sessionError;
259
+ }
260
+ if (cleanupError)
261
+ throw cleanupError;
262
+ if (poisonMessage())
263
+ throw new Error(`Z3 context is poisoned after ${label}; refusing result reuse (${poisonMessage()})`);
264
+ return value;
265
+ }
266
+ function isFatalZ3Error(error) {
267
+ const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
268
+ return /memory access out of bounds|corrupted (?:its )?heap memory|RuntimeError: Aborted|abort\(Runtime error/i.test(text);
269
+ }
270
+ export function getZ3LifecycleStats() {
271
+ return {
272
+ ...lifecycleStats,
273
+ cleanupWrappedFunctions: [...cleanupWrappedFunctions].sort(),
274
+ asyncWrappedFunctions: [...asyncWrappedFunctions].sort(),
275
+ cleanupQueueLimit: MAX_DEFERRED_CLEANUPS,
276
+ activeNativeCalls,
277
+ nativeCleanupCalls: { ...nativeCleanupCalls },
278
+ deferredCleanupCalls: { ...deferredCleanupCalls },
279
+ drainedCleanupCalls: { ...drainedCleanupCalls },
280
+ maxDeferredQueueDepth,
281
+ currentDeferredQueueDepth: deferredCleanups.length,
282
+ poisoned: poisonMessage(),
283
+ };
284
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  ## 它做什么 / 不做什么
6
6
 
7
+ - ✅ 检索范围(2026-09-09 起):携程机票/酒店、12306 余票、**Dida 供应商门户实时价**(`portal.dida.com`;站点 job 经桥按站点 capability 路由,扩展只被动嗅探页面自身请求)。
7
8
  - ✅ 检索:你(或 agent)发起会话检索时,后台标签页打开携程机票列表页,被动嗅探页面自己发出的 `batchSearch` 响应,文本回传给本机的 gotry。收尾自动关闭自己开的标签。
8
9
  - ✅ 登录检查:只读登录票据 cookie 的**名字**(cticket/uid/uname/passport),判断「是否已登录」这个布尔事实。
9
10
  - ✅ 登录引导:把登录入口页**置前台**打开,登录由你在携程官网完成。
@@ -26,7 +27,7 @@
26
27
  不经商店审核,版本化 tarball + SHA256 校验,与 npm 发版节奏解耦,更新比商店快:
27
28
 
28
29
  ```bash
29
- npx gotry setup --extension-from=github # 自动下载校验并落位 ~/.gotry/extension
30
+ npx @danceiny/gotry setup --extension-from=github # 自动下载校验并落位 ~/.gotry/extension
30
31
  ```
31
32
 
32
33
  (或手动:从 [github.com/Danceiny/gotry/releases](https://github.com/Danceiny/gotry/releases) 标签 `ext-*` 下载 `gotry-session-bridge.tar.gz` 解压到任意固定位置。)
@@ -36,7 +37,7 @@ npx gotry setup --extension-from=github # 自动下载校验并落位 ~/.gotry
36
37
  ### 方式三:npm 包内副本本地加载(离线确定性)
37
38
 
38
39
  ```bash
39
- npx gotry setup # 把包内扩展落位到 ~/.gotry/extension
40
+ npx @danceiny/gotry setup # 把包内扩展落位到 ~/.gotry/extension
40
41
  ```
41
42
 
42
43
  再按方式二的 `chrome://extensions` 三步加载该目录。
@@ -47,4 +48,4 @@ npx gotry setup # 把包内扩展落位到 ~/.gotry/extension
47
48
 
48
49
  - 商店版扩展 ID 固定为 `oeajpiccmonococjcegddlooeeohlbgd`(商店用自己的签名 key,与 unpacked 版 ID 不同,二者都是同一扩展,本机 gotry 桥双通道同信);
49
50
  - 本地加载(unpacked)版扩展 ID 应为固定的 `olpgkofjhhiiiahdkkbcninhjmegghfe`(manifest 带 `key`,跨机器稳定)——若不一致,说明 manifest 被改动过,不要加载;
50
- - gotry 侧运行 `npx gotry` 后发起一次会话检索,扩展图标应出现(无需点击);桥状态可看 `curl http://127.0.0.1:8791/status`(仅本机回环)。
51
+ - gotry 侧运行 `npx @danceiny/gotry` 后发起一次会话检索,扩展图标应出现(无需点击);桥状态可看 `curl http://127.0.0.1:8791/status`(仅本机回环)。
@@ -10,6 +10,15 @@
10
10
  * cookie-names → chrome.cookies 只读票据 cookie **名字**(值即取即弃,永不离开扩展——红线)。
11
11
  * - 物理只读(ReadGuard 扩展车道形态):本 SW 绝不向站点发任何请求——检索请求由站点自己的
12
12
  * 页面代码发出,我们只「导航 + 被动转发 NETWORK_HINTS 命中响应」;写不是被禁止的行为,是不存在的原语。
13
+ *
14
+ * 桥地址(2026-09-11,founder 定案:执行环境=浏览器客户端,服务端零 Chrome):
15
+ * 扩展**完全不暴露任何配置**给员工。hotel-be portal 在员工已登录的页面里通过
16
+ * content-bridge.js 派发 `gotry-join` 消息({bridgeUrl, token}),SW 缓存到
17
+ * `joinTicket` 模块变量;员工从未看见 URL / token / API host 等技术字眼。
18
+ * SW 进程重启(约每 30s 节律)→ ticket 丢失 → 下次员工打开 portal 页面自动重发 → 静默恢复。
19
+ *
20
+ * 桌面形态(loopback 端口池)保留向后兼容:无 join ticket 时回退到 127.0.0.1 探测,
21
+ * 这是 gotry 桌面开发场景(gotry CLI 同机形态),与员工日常产品路径无关。
13
22
  */
14
23
 
15
24
  'use strict'
@@ -34,6 +43,13 @@ const SITES = {
34
43
  domain: '12306.cn',
35
44
  ticketNames: [],
36
45
  },
46
+ // Dida 供应商门户(2026-09-09 实装;hotel-be portal integration 迁移线):
47
+ // 员工登录态(HttpOnly 会话 cookie 只读名字,值即取即弃);检索走 portal.dida.com
48
+ // 后台标签 + 被动嗅探 portal-webapi 实时价
49
+ 'dida-portal': {
50
+ domain: 'dida.com',
51
+ ticketNames: ['CN_M_DidaTravel'],
52
+ },
37
53
  }
38
54
 
39
55
  /** 检索 URL 白名单(per-site;search job 只允许开各自站点域,其余一律拒) */
@@ -41,31 +57,71 @@ const SITE_SEARCH_PREFIXES = {
41
57
  'ctrip-flight': 'https://flights.ctrip.com/',
42
58
  'ctrip-hotel': 'https://hotels.ctrip.com/',
43
59
  'train-12306': 'https://kyfw.12306.cn/',
60
+ 'dida-portal': 'https://portal.dida.com/',
44
61
  }
45
62
 
63
+ /**
64
+ * join ticket:由 hotel-be portal 通过 content-bridge 派发,SW 缓存;
65
+ * 模块级变量,SW 重启即清空(下次 portal 页派发即恢复,无需持久化)。
66
+ * 形态:{ bridgeUrl, token, issuedAt, expiresAt }
67
+ */
68
+ let joinTicket = null
46
69
  let activePort = null
47
70
  let polling = false
48
71
 
72
+ function isJoinFresh(t) {
73
+ if (!t || typeof t.bridgeUrl !== 'string' || !t.bridgeUrl) return false
74
+ if (t.expiresAt && Number.isFinite(Date.parse(t.expiresAt)) && Date.parse(t.expiresAt) < Date.now()) return false
75
+ return true
76
+ }
77
+
49
78
  function sleep(ms) {
50
79
  return new Promise((resolve) => setTimeout(resolve, ms))
51
80
  }
52
81
 
53
- async function detectPort() {
82
+ async function detectBridge() {
83
+ // 远程 join ticket 优先:portal 派发则连远程
84
+ if (isJoinFresh(joinTicket)) {
85
+ try {
86
+ const r = await fetch(`${joinTicket.bridgeUrl.replace(/\/+$/, '')}/health`, {
87
+ headers: { authorization: `Bearer ${joinTicket.token ?? ''}`, 'x-gotry-bridge': 'v1' },
88
+ })
89
+ if (r.ok) return true
90
+ } catch { /* 远程暂不可达;若也无可用 loopback 则继续重试 */ }
91
+ }
92
+ // 桌面形态兜底:loopback 端口池扫描
54
93
  for (const p of BRIDGE_PORTS) {
55
94
  try {
56
95
  const r = await fetch(`http://127.0.0.1:${p}/health`, { headers: { 'x-gotry-bridge': 'v1' } })
57
- if (r.ok) return p
96
+ if (r.ok) { activePort = p; return true }
58
97
  } catch { /* 无人监听,试下一个端口 */ }
59
98
  }
60
- return null
99
+ return false
100
+ }
101
+
102
+ function bridgeBase() {
103
+ if (isJoinFresh(joinTicket)) return joinTicket.bridgeUrl.replace(/\/+$/, '')
104
+ return activePort != null ? `http://127.0.0.1:${activePort}` : null
105
+ }
106
+
107
+ function bridgeHeaders(withJson) {
108
+ const h = { 'x-gotry-bridge': 'v1' }
109
+ if (withJson) h['content-type'] = 'application/json'
110
+ if (isJoinFresh(joinTicket) && joinTicket.token) h['authorization'] = `Bearer ${joinTicket.token}`
111
+ return h
61
112
  }
62
113
 
63
114
  async function postResult(jobId, result) {
64
- if (activePort == null || !jobId) return
115
+ const base = bridgeBase()
116
+ if (base == null || !jobId) return
117
+ // 远程桥挂载在 gotry-backend 精确路由内核上,jobId 走 query;loopback 独立桥保持路径式
118
+ const url = isJoinFresh(joinTicket)
119
+ ? `${base}/results?jobId=${encodeURIComponent(jobId)}`
120
+ : `${base}/results/${encodeURIComponent(jobId)}`
65
121
  try {
66
- await fetch(`http://127.0.0.1:${activePort}/results/${encodeURIComponent(jobId)}`, {
122
+ await fetch(url, {
67
123
  method: 'POST',
68
- headers: { 'content-type': 'application/json', 'x-gotry-bridge': 'v1' },
124
+ headers: bridgeHeaders(true),
69
125
  body: JSON.stringify(result),
70
126
  })
71
127
  } catch { /* 桥可能已退场;Node 侧超时兜底 */ }
@@ -81,6 +137,41 @@ async function ticketNames(site) {
81
137
  .filter((n) => conf.ticketNames.includes(n))
82
138
  }
83
139
 
140
+ /** dida 推荐流多回包分类(与 Node 侧 mergeDidaRatesBodies 对账,run-all §38 防漂移断言守住) */
141
+ function classifyDidaSniff(url) {
142
+ if (/SearchHomepageRecommendHotels/i.test(url)) return 'hotels'
143
+ if (/SearchHomepageRecommendPrices/i.test(url)) return 'recommendPrices'
144
+ if (/HotelPriceAPI\/SearchRealTime/i.test(url)) return 'realtime'
145
+ return null
146
+ }
147
+
148
+ /** multiCollect(dida 推荐流):hotels+recommendPrices 齐即结算;超时带回已见分桶(诚实缺桶) */
149
+ function waitSniffMulti(tabId, timeoutMs) {
150
+ return new Promise((resolve) => {
151
+ const bodies = {}
152
+ let settled = false
153
+ let title = ''
154
+ const finish = () => {
155
+ if (settled) return
156
+ settled = true
157
+ clearTimeout(timer)
158
+ chrome.runtime.onMessage.removeListener(handler)
159
+ resolve({ ok: true, kind: 'search', bodies, title, timeout: !(bodies.hotels && bodies.recommendPrices) })
160
+ }
161
+ const handler = (msg, sender) => {
162
+ if (!sender || !sender.tab || sender.tab.id !== tabId) return
163
+ if (msg && msg.type === 'gotry-page') { title = String(msg.title ?? title); return }
164
+ if (msg && msg.type === 'gotry-sniff') {
165
+ const cls = classifyDidaSniff(String(msg.url ?? ''))
166
+ if (cls && !bodies[cls]) bodies[cls] = String(msg.body ?? '')
167
+ if (bodies.hotels && bodies.recommendPrices) finish()
168
+ }
169
+ }
170
+ const timer = setTimeout(finish, Math.max(Number(timeoutMs) || 30_000, 5_000))
171
+ chrome.runtime.onMessage.addListener(handler)
172
+ })
173
+ }
174
+
84
175
  /** 后台标签等嗅探回包;超时带回页标题(供 Node 侧 CHALLENGE_RE 判定),收尾关自己的标签 */
85
176
  function waitSniff(tabId, timeoutMs) {
86
177
  return new Promise((resolve) => {
@@ -93,14 +184,14 @@ function waitSniff(tabId, timeoutMs) {
93
184
  settled = true
94
185
  clearTimeout(timer)
95
186
  chrome.runtime.onMessage.removeListener(handler)
96
- resolve({ ok: true, kind: 'search', body: String(msg.body ?? ''), title })
187
+ resolve({ ok: true, kind: 'search', body: String(msg.body ?? ''), url: String(msg.url ?? ''), title })
97
188
  }
98
189
  }
99
190
  const timer = setTimeout(() => {
100
191
  if (settled) return
101
192
  settled = true
102
193
  chrome.runtime.onMessage.removeListener(handler)
103
- resolve({ ok: false, kind: 'search', timeout: true, title })
194
+ resolve({ ok: false, kind: 'search', timeout: true, url: '', title })
104
195
  }, Math.max(Number(timeoutMs) || 30_000, 5_000))
105
196
  chrome.runtime.onMessage.addListener(handler)
106
197
  })
@@ -136,7 +227,9 @@ async function handleJob(job) {
136
227
  return
137
228
  }
138
229
  const tab = await chrome.tabs.create({ url: job.url, active: false })
139
- const result = await waitSniff(tab.id, job.timeoutMs)
230
+ const result = job.multiCollect
231
+ ? await waitSniffMulti(tab.id, job.timeoutMs)
232
+ : await waitSniff(tab.id, job.timeoutMs)
140
233
  await chrome.tabs.remove(tab.id).catch(() => { /* 用户先关了,无妨 */ })
141
234
  await postResult(jobId, result)
142
235
  return
@@ -152,12 +245,14 @@ async function loop() {
152
245
  polling = true
153
246
  try {
154
247
  for (;;) {
155
- if (activePort == null) activePort = await detectPort()
156
- if (activePort == null) { await sleep(RETRY_MS); continue }
248
+ if (bridgeBase() == null) {
249
+ const up = await detectBridge()
250
+ if (!up) { await sleep(RETRY_MS); continue }
251
+ }
157
252
  try {
158
- const r = await fetch(`http://127.0.0.1:${activePort}/jobs`, {
253
+ const r = await fetch(`${bridgeBase()}/jobs`, {
159
254
  method: 'POST',
160
- headers: { 'content-type': 'application/json', 'x-gotry-bridge': 'v1' },
255
+ headers: bridgeHeaders(true),
161
256
  body: JSON.stringify({
162
257
  extensionVersion: chrome.runtime.getManifest().version,
163
258
  capabilities: Object.keys(SITES),
@@ -172,8 +267,24 @@ async function loop() {
172
267
  }
173
268
  }
174
269
 
270
+ /**
271
+ * portal content-bridge 派发入口(2026-09-11):hotel-be portal 在员工已登录的
272
+ * 页面里通过 content-bridge.js 发 `gotry-join` 消息,SW 静默接收并缓存 ticket。
273
+ * 员工从未看见这些消息;任何外部源都可发,但 ticket 必须由 hotel-be 后端签发,
274
+ * Node 侧通过 Bearer 鉴权校验 ticket 内 token 才认账。
275
+ */
276
+ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
277
+ if (msg && msg.type === 'gotry-join' && msg.ticket && typeof msg.ticket.bridgeUrl === 'string') {
278
+ joinTicket = msg.ticket
279
+ activePort = null
280
+ sendResponse && sendResponse({ ok: true })
281
+ return false
282
+ }
283
+ return false
284
+ })
285
+
175
286
  // SW 冷启动 + 闹钟兜底双保险;loop() 幂等
176
287
  if (chrome.runtime.onStartup) chrome.runtime.onStartup.addListener(() => { void loop() })
177
288
  chrome.alarms.create('gotry-bridge-poll', { periodInMinutes: 0.5 })
178
289
  chrome.alarms.onAlarm.addListener((a) => { if (a.name === 'gotry-bridge-poll') void loop() })
179
- void loop()
290
+ void loop()
@@ -6,11 +6,26 @@
6
6
  * Service Worker(onMessage 按 sender.tab.id 归属到对应 search job)。
7
7
  *
8
8
  * 附带页标题快报(gotry-page):SW 在嗅探超时/挑战判定(CHALLENGE_RE)时用它。
9
+ *
10
+ * Join 投递(2026-09-11,hotelbyte 产品定案):hotel-be portal 在员工已登录的
11
+ * 页面里挂一段 `window.__gotryJoinTicket = {...}` 即可,本脚本负责:
12
+ * - 页面派发 ticket → SW 静默接收(员工全程不接触 URL/token);
13
+ * - ticket 未注入 → 静默 no-op(扩展安静等下次 portal 页加载);
14
+ * - portal 自己负责派发的合规性(token 由 portal 后端签发,扩展不验签,
15
+ * Node 侧 gotry-backend 经 Bearer 校验——信任链在 hotel-be ↔ gotry-backend 之间)。
9
16
  */
10
17
  ;(function () {
11
18
  if (window.__gotrySniffBridge) return
12
19
  window.__gotrySniffBridge = true
13
20
 
21
+ function dispatchJoinTicket() {
22
+ try {
23
+ var t = window.__gotryJoinTicket
24
+ if (!t || typeof t.bridgeUrl !== 'string' || !t.bridgeUrl) return
25
+ chrome.runtime.sendMessage({ type: 'gotry-join', ticket: t }).catch(function () { /* SW 重启中 */ })
26
+ } catch { /* 不抛 */ }
27
+ }
28
+
14
29
  window.addEventListener('gotry-ctrip-sniff', function (ev) {
15
30
  try {
16
31
  var d = (ev && ev.detail) || {}
@@ -33,9 +48,16 @@
33
48
  }).catch(function () { /* SW 侧无等待者,常态丢弃 */ })
34
49
  } catch { /* 环境异常不抛 */ }
35
50
  }
51
+ // 页面派发新票据时重取(2026-09-11,hotel-be 侧 C16 落地配套):
52
+ // portal 的票据是**异步**取回的(fetch bridgeTicket),通常晚于 DOMContentLoaded,
53
+ // 上面那次一次性读取会错过;hotel-be 每次写入/刷新 window.__gotryJoinTicket 都会
54
+ // 在 window 上派发该事件 → 这里重取一次即可(不需要轮询,也不引入任何配置面)。
55
+ window.addEventListener('gotry-join-ticket-available', dispatchJoinTicket)
56
+
36
57
  if (document.readyState === 'loading') {
37
- window.addEventListener('DOMContentLoaded', sendPage, { once: true })
58
+ window.addEventListener('DOMContentLoaded', function () { sendPage(); dispatchJoinTicket() }, { once: true })
38
59
  } else {
39
60
  sendPage()
61
+ dispatchJoinTicket()
40
62
  }
41
63
  })()
@@ -24,11 +24,17 @@
24
24
  var HOTEL_HINT_RE = /hotels\.ctrip\.com\/(hotels\/api|domestic\/pc\/api)|GetHotelListBySOA|GetHotelListByCity|HotelSearch|hotelsearch|restapi\/soa2\/\d+\/fetchHotelList/i
25
25
  /** 火车(2026-09-03 实装):12306 余票查询 XHR(负载均衡变体 queryG/Z/A/U 全命中) */
26
26
  var TRAIN_HINT_RE = /leftTicket\/query/i
27
+ /** Dida 供应商门户(2026-09-09 实装):portal-webapi 实时价/价格监控
28
+ * (与 Node 侧 DIDA_NETWORK_HINTS 对账,run-all §38 防漂移断言守住) */
29
+ var DIDA_HINT_RE = /portal-webapi\.dida\.com\/HotelPriceAPI\/SearchRealTime|portal-webapi\.dida\.com\/HotelPriceAPI\/SearchMonitor|portal-webapi\.dida\.com\/HotelRecommendAPI\/SearchHomepageRecommendHotels|portal-webapi\.dida\.com\/HotelRecommendAPI\/SearchHomepageRecommendPrices|portal-webapi\.dida\.com\/PopularDestinationAPI\/SearchHotels|portal-webapi\.dida\.com\/PopularDestinationAPI\/SearchHotelPrices/i
30
+ /** 形状嗅探签名(dida 实时价信封;与 Node 侧 looksLikeDidaRatesBody 签名一致) */
31
+ var DIDA_BODY_SIG_RE = /"HotelPriceList"|"RatePlanList"/
27
32
  /** 形状嗅探(酒店页兜底;与 Node 侧 looksLikeHotelListBody 签名一致) */
28
33
  var HOTEL_BODY_SIG_RE = /"hotelList"|"hotelMatchInfos"|"hotelName"/
29
34
  var HOTEL_BODY_MAX = 2_000_000
30
35
  var isHotelPage = /(^|\.)hotels\.ctrip\.com$/.test(location.hostname)
31
36
  var isTrainPage = /(^|\.)12306\.cn$/.test(location.hostname)
37
+ var isDidaPage = /(^|\.)portal\.dida\.com$/.test(location.hostname)
32
38
 
33
39
  function dispatch(url, body) {
34
40
  try {
@@ -42,6 +48,9 @@
42
48
  function trainPageWants(url) {
43
49
  return isTrainPage && TRAIN_HINT_RE.test(url)
44
50
  }
51
+ function didaPageWants(url) {
52
+ return isDidaPage && DIDA_HINT_RE.test(url)
53
+ }
45
54
 
46
55
  var origFetch = window.fetch
47
56
  if (typeof origFetch === 'function') {
@@ -51,7 +60,7 @@
51
60
  var url = ''
52
61
  if (typeof input === 'string') url = input
53
62
  else if (input && typeof input.url === 'string') url = input.url
54
- var urlHint = FLIGHT_HINT_RE.test(url) || hotelPageWants(url) || trainPageWants(url)
63
+ var urlHint = FLIGHT_HINT_RE.test(url) || hotelPageWants(url) || trainPageWants(url) || didaPageWants(url)
55
64
  var isPost = false
56
65
  try { isPost = String((init && init.method) || (input && input.method) || 'GET').toUpperCase() === 'POST' } catch { /* ignore */ }
57
66
  var capOk = true
@@ -63,7 +72,8 @@
63
72
  var clone = res.clone()
64
73
  clone.text().then(function (t) {
65
74
  try {
66
- if (urlHint || (t.length <= HOTEL_BODY_MAX && HOTEL_BODY_SIG_RE.test(t))) dispatch(url, t)
75
+ var responseUrl = typeof res.url === 'string' ? res.url : ''
76
+ if (urlHint || (t.length <= HOTEL_BODY_MAX && HOTEL_BODY_SIG_RE.test(t)) || (t.length <= HOTEL_BODY_MAX && DIDA_BODY_SIG_RE.test(t))) dispatch(responseUrl, t)
67
77
  } catch { /* 嗅探失败不影响站点自身 */ }
68
78
  }).catch(function () { /* 流不可读则跳过 */ })
69
79
  }
@@ -85,14 +95,15 @@
85
95
  xhr.addEventListener('load', function () {
86
96
  try {
87
97
  if (!xhr.__gotryUrl) return
88
- var urlHint = FLIGHT_HINT_RE.test(xhr.__gotryUrl) || hotelPageWants(xhr.__gotryUrl) || trainPageWants(xhr.__gotryUrl)
98
+ var urlHint = FLIGHT_HINT_RE.test(xhr.__gotryUrl) || hotelPageWants(xhr.__gotryUrl) || trainPageWants(xhr.__gotryUrl) || didaPageWants(xhr.__gotryUrl)
89
99
  if (!urlHint && !(isHotelPage && xhr.__gotryMethod === 'POST')) return
90
100
  var body = ''
91
101
  if (xhr.responseType === '' || xhr.responseType === 'text') body = xhr.responseText
92
102
  else if (xhr.responseType === 'json') body = JSON.stringify(xhr.response)
93
103
  else return
94
104
  if (!body) return
95
- if (urlHint || (body.length <= HOTEL_BODY_MAX && HOTEL_BODY_SIG_RE.test(body))) dispatch(xhr.__gotryUrl, body)
105
+ var responseUrl = typeof xhr.responseURL === 'string' ? xhr.responseURL : ''
106
+ if (urlHint || (body.length <= HOTEL_BODY_MAX && HOTEL_BODY_SIG_RE.test(body)) || (body.length <= HOTEL_BODY_MAX && DIDA_BODY_SIG_RE.test(body))) dispatch(responseUrl, body)
96
107
  } catch { /* 嗅探失败不影响站点 */ }
97
108
  })
98
109
  return origSend.apply(this, arguments)