@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.
- package/README.md +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
package/dist/src/z3-shared.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
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(()=>
|
|
13
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/extension/README.md
CHANGED
|
@@ -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`(仅本机回环)。
|
package/extension/background.js
CHANGED
|
@@ -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
|
|
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
|
|
96
|
+
if (r.ok) { activePort = p; return true }
|
|
58
97
|
} catch { /* 无人监听,试下一个端口 */ }
|
|
59
98
|
}
|
|
60
|
-
return
|
|
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
|
-
|
|
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(
|
|
122
|
+
await fetch(url, {
|
|
67
123
|
method: 'POST',
|
|
68
|
-
headers:
|
|
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 =
|
|
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 (
|
|
156
|
-
|
|
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(
|
|
253
|
+
const r = await fetch(`${bridgeBase()}/jobs`, {
|
|
159
254
|
method: 'POST',
|
|
160
|
-
headers:
|
|
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
|
-
|
|
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
|
-
|
|
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)
|