@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
|
@@ -65,7 +65,7 @@ export function needsExtensionSummary(opts: { localInstalled?: boolean; home?: s
|
|
|
65
65
|
const local = opts.localInstalled ?? localExtensionInstalled(opts.home)
|
|
66
66
|
if (local) {
|
|
67
67
|
return 'GoTry Session Bridge 扩展未连接(未安装或已停用)。推荐 Chrome 应用商店一键装(自动更新) '
|
|
68
|
-
+ `${EXTENSION_STORE_URL} ;或 npx gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension`
|
|
68
|
+
+ `${EXTENSION_STORE_URL} ;或 npx @danceiny/gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension`
|
|
69
69
|
}
|
|
70
70
|
return 'GoTry Session Bridge 扩展未连接。若你已从 Chrome 商店安装(自动更新):打开 Chrome 并确认扩展已启用即可自动连接,无需再装。'
|
|
71
71
|
+ `若尚未安装:应用商店一键装 ${EXTENSION_STORE_URL} (装完零弹窗,浏览器自己当安装器)`
|
|
@@ -81,6 +81,13 @@ export const MAX_RESULT_BODY_BYTES = 8 * 1024 * 1024
|
|
|
81
81
|
|
|
82
82
|
export type ExtensionJobKind = 'search' | 'open-login' | 'cookie-names'
|
|
83
83
|
|
|
84
|
+
/** search + multiCollect(dida 推荐流):按接口类别分桶的嗅探回包体 */
|
|
85
|
+
export interface SniffBodies {
|
|
86
|
+
hotels?: string
|
|
87
|
+
recommendPrices?: string
|
|
88
|
+
realtime?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
export interface ExtensionJob {
|
|
85
92
|
jobId: string
|
|
86
93
|
kind: ExtensionJobKind
|
|
@@ -89,6 +96,12 @@ export interface ExtensionJob {
|
|
|
89
96
|
url?: string
|
|
90
97
|
/** search:等嗅探回包上限 */
|
|
91
98
|
timeoutMs?: number
|
|
99
|
+
/**
|
|
100
|
+
* search:dida 推荐流多回包收集(2026-09-11,执行环境迁浏览器客户端)——
|
|
101
|
+
* find 页加载自发 hotels + recommendPrices 两个接口,单首包语义会丢一半数据;
|
|
102
|
+
* true 时扩展按类别收齐(或超时结算)后经 bodies 回传
|
|
103
|
+
*/
|
|
104
|
+
multiCollect?: boolean
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
export interface ExtensionJobResult {
|
|
@@ -98,6 +111,10 @@ export interface ExtensionJobResult {
|
|
|
98
111
|
names?: string[]
|
|
99
112
|
/** search:NETWORK_HINTS 命中的响应原文 */
|
|
100
113
|
body?: string
|
|
114
|
+
/** search:the exact NETWORK_HINTS response URL selected for body;未命中/超时为空 */
|
|
115
|
+
url?: string
|
|
116
|
+
/** search + multiCollect:分桶回包体(见 SniffBodies) */
|
|
117
|
+
bodies?: SniffBodies
|
|
101
118
|
title?: string
|
|
102
119
|
opened?: boolean
|
|
103
120
|
timeout?: boolean
|
|
@@ -137,6 +154,26 @@ interface QueuedJob {
|
|
|
137
154
|
timer: ReturnType<typeof setTimeout> | null
|
|
138
155
|
}
|
|
139
156
|
|
|
157
|
+
/**
|
|
158
|
+
* capability 路由(2026-09-09):/jobs 轮询体里的 capabilities(扩展 SITES 键清单)
|
|
159
|
+
* 从「上报即弃」升级为**路由依据**——扩展只领自己声明的站点的 job。
|
|
160
|
+
* 背景:同机多 Chrome(日常 + 测试/多 profile)各自装着同一扩展的不同版本时,
|
|
161
|
+
* 旧版扩展会领走新站点 job 并答 ok:true 空 names(cookie-names 语义里 conf 缺失
|
|
162
|
+
* 返 []),把「未登录」伪造成普查结论。按 site ∈ capabilities 匹配后,新站点 job
|
|
163
|
+
* 只会落到认识它的扩展手里;capabilities 缺省(理论上不存在,当前扩展全部上报)
|
|
164
|
+
* 视为 match-all,向前兼容。
|
|
165
|
+
*/
|
|
166
|
+
interface ParkedPoller {
|
|
167
|
+
res: ServerResponse
|
|
168
|
+
timer: ReturnType<typeof setTimeout>
|
|
169
|
+
capabilities: Set<string> | null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function jobMatches(entry: ExtensionJob, capabilities: Set<string> | null): boolean {
|
|
173
|
+
if (capabilities == null || capabilities.size === 0) return true
|
|
174
|
+
return capabilities.has(entry.site)
|
|
175
|
+
}
|
|
176
|
+
|
|
140
177
|
function readBody(req: IncomingMessage, cap: number): Promise<string | { err: string }> {
|
|
141
178
|
return new Promise((resolve) => {
|
|
142
179
|
const chunks: Buffer[] = []
|
|
@@ -158,109 +195,56 @@ function readBody(req: IncomingMessage, cap: number): Promise<string | { err: st
|
|
|
158
195
|
})
|
|
159
196
|
}
|
|
160
197
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
export interface BridgeQueueStats {
|
|
199
|
+
queued: number
|
|
200
|
+
inFlight: number
|
|
201
|
+
parked: number
|
|
202
|
+
lastSeenMsAgo: number | null
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 传输无关的桥作业队列(2026-09-11 抽出,founder 定案:执行环境=浏览器客户端,
|
|
207
|
+
* 服务端零 Chrome)——queue/inFlight/parked 三态 + 长轮询协议处理器,
|
|
208
|
+
* 可挂两种形态:
|
|
209
|
+
* - loopback 独立桥(createSessionBridge;gotry 桌面形态,扩展与本机进程同机)
|
|
210
|
+
* - gotry-backend 鉴权面挂载(handleMountedRequest;远程扩展经 bearer 鉴权连入,
|
|
211
|
+
* 浏览器客户端即执行环境,服务器只做编排/队列)
|
|
212
|
+
*/
|
|
213
|
+
export interface BridgeJobQueue extends SessionJobHandle {
|
|
214
|
+
stats(): BridgeQueueStats
|
|
215
|
+
/** 完整桥协议(loopback 独立桥形态;/results/:jobId 路径式) */
|
|
216
|
+
handleRequest(req: IncomingMessage, res: ServerResponse): void
|
|
217
|
+
/** 挂载形态(/v1/session/bridge 前缀;/results?jobId= 查询式;bearer 鉴权由宿主模块前置) */
|
|
218
|
+
handleMountedRequest(req: IncomingMessage, res: ServerResponse): void
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface BridgeJobQueueOptions {
|
|
222
|
+
/** 信任的扩展 Origin 白名单;缺省 EXTENSION_ORIGINS(unpacked + 商店版双通道) */
|
|
223
|
+
extensionOrigins?: readonly string[]
|
|
224
|
+
now?: () => number
|
|
225
|
+
/** 长轮询 parked 定时器是否 unref(独立桥惰性形态用;gotry-backend 常驻服务无需) */
|
|
226
|
+
keepBridge?: boolean
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function createBridgeJobQueue(opts: BridgeJobQueueOptions = {}): BridgeJobQueue {
|
|
164
230
|
const extensionOrigins = new Set(opts.extensionOrigins ?? EXTENSION_ORIGINS)
|
|
165
231
|
const now = opts.now ?? Date.now
|
|
166
232
|
const queue: QueuedJob[] = []
|
|
167
233
|
const inFlight = new Map<string, QueuedJob>()
|
|
168
|
-
const parked:
|
|
234
|
+
const parked: ParkedPoller[] = []
|
|
169
235
|
let lastSeenAt = 0
|
|
170
236
|
let closed = false
|
|
171
|
-
let port = 0
|
|
172
237
|
const extensionConnected = (): boolean => lastSeenAt > 0 && now() - lastSeenAt < EXTENSION_CONNECTED_WINDOW_MS
|
|
173
238
|
|
|
174
|
-
/**
|
|
175
|
-
function
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
239
|
+
/** 领走首个 capability 匹配的 job(移入 inFlight——回包按 jobId 路由,两处都可找到) */
|
|
240
|
+
function takeMatching(capabilities: Set<string> | null): QueuedJob | null {
|
|
241
|
+
const idx = queue.findIndex((q) => jobMatches(q.job, capabilities))
|
|
242
|
+
if (idx < 0) return null
|
|
243
|
+
const [next] = queue.splice(idx, 1)
|
|
244
|
+
inFlight.set(next.job.jobId, next)
|
|
245
|
+
return next
|
|
179
246
|
}
|
|
180
247
|
|
|
181
|
-
const server = createServer((req, res) => {
|
|
182
|
-
if (closed) { res.statusCode = 503; res.end(); return }
|
|
183
|
-
const urlPath = (req.url ?? '').split('?')[0]
|
|
184
|
-
const origin = req.headers.origin
|
|
185
|
-
const isPost = req.method === 'POST'
|
|
186
|
-
const originTrusted = typeof origin === 'string' && extensionOrigins.has(origin)
|
|
187
|
-
// 网页侧请求必带 Origin;桥端点只信任白名单内的 chrome-extension:// 源(诊断 GET 面放行)
|
|
188
|
-
if (isPost && !originTrusted) {
|
|
189
|
-
res.statusCode = 403
|
|
190
|
-
res.setHeader('content-type', 'application/json')
|
|
191
|
-
res.end(JSON.stringify({ ok: false, error: 'origin 不在桥白名单' }))
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
// 心跳:扩展平时只走 /jobs 长轮询(健康只在启动探测时 ping 一次)——
|
|
195
|
-
// 一切携带白名单扩展 Origin 的请求都刷新 lastSeen,否则 45s 后误判扩展掉线;
|
|
196
|
-
// 无 Origin 的诊断 GET(/health /status)不记心跳,curl 探活不能伪造「扩展在线」
|
|
197
|
-
if (originTrusted) {
|
|
198
|
-
lastSeenAt = now()
|
|
199
|
-
}
|
|
200
|
-
const finish = (code: number, body: unknown): void => {
|
|
201
|
-
res.statusCode = code
|
|
202
|
-
res.setHeader('content-type', 'application/json')
|
|
203
|
-
res.end(JSON.stringify(body))
|
|
204
|
-
}
|
|
205
|
-
if (req.method === 'GET' && urlPath === '/health') {
|
|
206
|
-
finish(200, { ok: true, service: 'gotry-session-bridge', protocol: BRIDGE_PROTOCOL })
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
if (req.method === 'GET' && urlPath === '/status') {
|
|
210
|
-
finish(200, {
|
|
211
|
-
ok: true, port, protocol: BRIDGE_PROTOCOL,
|
|
212
|
-
extensionConnected: extensionConnected(),
|
|
213
|
-
lastSeenMsAgo: lastSeenAt > 0 ? now() - lastSeenAt : null,
|
|
214
|
-
queued: queue.length,
|
|
215
|
-
inFlight: inFlight.size,
|
|
216
|
-
parked: parked.length,
|
|
217
|
-
})
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
if (isPost && urlPath === '/jobs') {
|
|
221
|
-
const next = takeQueued()
|
|
222
|
-
if (next) { finish(200, { job: next.job }); return }
|
|
223
|
-
// 长轮询:hold ≤ JOBS_LONG_POLL_MS(必须 < MV3 SW 30s 存活窗口,每次响应都续命);
|
|
224
|
-
// 新 job 提交时即时唤醒 parked 取活者(见 submit→dispatchToParked)
|
|
225
|
-
const parkTimer = setTimeout(() => {
|
|
226
|
-
const i = parked.findIndex((p) => p.res === res)
|
|
227
|
-
if (i >= 0) parked.splice(i, 1)
|
|
228
|
-
finish(200, { job: null })
|
|
229
|
-
}, JOBS_LONG_POLL_MS)
|
|
230
|
-
// 默认桥是惰性能力:外部扩展的 parked 轮询不得在主流程结束后钉住 CLI。
|
|
231
|
-
// keepBridge wizard 反过来要靠它守住进程,因此只对默认形态 unref。
|
|
232
|
-
if (!opts.keepBridge) parkTimer.unref()
|
|
233
|
-
parked.push({ res, timer: parkTimer })
|
|
234
|
-
res.on('close', () => {
|
|
235
|
-
const i = parked.findIndex((p) => p.res === res)
|
|
236
|
-
if (i >= 0) parked.splice(i, 1)
|
|
237
|
-
clearTimeout(parkTimer)
|
|
238
|
-
})
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
if (isPost && urlPath.startsWith('/results/')) {
|
|
242
|
-
const jobId = decodeURIComponent(urlPath.slice('/results/'.length))
|
|
243
|
-
void readBody(req, MAX_RESULT_BODY_BYTES).then((body) => {
|
|
244
|
-
if (typeof body !== 'string') { finish(400, { ok: false, error: body.err }); return }
|
|
245
|
-
let parsed: ExtensionJobResult
|
|
246
|
-
try {
|
|
247
|
-
parsed = JSON.parse(body) as ExtensionJobResult
|
|
248
|
-
} catch {
|
|
249
|
-
finish(400, { ok: false, error: '回包不是 JSON' })
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
finish(200, { ok: true })
|
|
253
|
-
resolveJob(jobId, parsed)
|
|
254
|
-
})
|
|
255
|
-
return
|
|
256
|
-
}
|
|
257
|
-
finish(404, { ok: false, error: 'unknown endpoint' })
|
|
258
|
-
})
|
|
259
|
-
server.on('connection', (socket) => {
|
|
260
|
-
// server.unref() 只解开监听器;已接受的扩展长轮询 socket 仍会引用事件循环。
|
|
261
|
-
if (!opts.keepBridge) socket.unref()
|
|
262
|
-
})
|
|
263
|
-
|
|
264
248
|
/** 扩展回包:queued 与 inFlight 两处都可寻址(job 执行失败 = result.ok:false,桥自身不吞) */
|
|
265
249
|
function resolveJob(jobId: string, parsed: ExtensionJobResult): void {
|
|
266
250
|
const inFlightHit = inFlight.get(jobId)
|
|
@@ -277,10 +261,11 @@ export async function createSessionBridge(opts: SessionBridgeOptions = {}): Prom
|
|
|
277
261
|
entry.resolve({ ok: true, result: parsed })
|
|
278
262
|
}
|
|
279
263
|
|
|
280
|
-
/** 新 job 入队时即时派发给 parked 取活者(否则要白等一个 20s 长轮询周期) */
|
|
264
|
+
/** 新 job 入队时即时派发给 capability 匹配的 parked 取活者(否则要白等一个 20s 长轮询周期) */
|
|
281
265
|
function dispatchToParked(entry: QueuedJob): boolean {
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
266
|
+
const i = parked.findIndex((p) => jobMatches(entry.job, p.capabilities))
|
|
267
|
+
if (i < 0) return false
|
|
268
|
+
const [p] = parked.splice(i, 1)
|
|
284
269
|
clearTimeout(p.timer)
|
|
285
270
|
inFlight.set(entry.job.jobId, entry)
|
|
286
271
|
p.res.statusCode = 200
|
|
@@ -289,9 +274,122 @@ export async function createSessionBridge(opts: SessionBridgeOptions = {}): Prom
|
|
|
289
274
|
return true
|
|
290
275
|
}
|
|
291
276
|
|
|
292
|
-
const
|
|
277
|
+
const stats = (): BridgeQueueStats => ({
|
|
278
|
+
queued: queue.length,
|
|
279
|
+
inFlight: inFlight.size,
|
|
280
|
+
parked: parked.length,
|
|
281
|
+
lastSeenMsAgo: lastSeenAt > 0 ? now() - lastSeenAt : null,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 协议处理器工厂:prefix='' 为独立桥形态(路径式 results);prefix='/v1/session/bridge'
|
|
286
|
+
* 为挂载形态(查询式 results——gotry-backend 内核只支持精确路由,jobId 走 query)。
|
|
287
|
+
* Origin 白名单校验内置于两种形态(远程形态同样强制:网页跨域请求必带邪恶 Origin)。
|
|
288
|
+
*/
|
|
289
|
+
function makeHandler(prefix: string, resultsViaQuery: boolean, statusExtra?: () => Record<string, unknown>) {
|
|
290
|
+
return (req: IncomingMessage, res: ServerResponse): void => {
|
|
291
|
+
if (closed) { res.statusCode = 503; res.end(); return }
|
|
292
|
+
const urlPath = (req.url ?? '').split('?')[0]
|
|
293
|
+
const path = prefix && urlPath.startsWith(prefix) ? urlPath.slice(prefix.length) : urlPath
|
|
294
|
+
const origin = req.headers.origin
|
|
295
|
+
const isPost = req.method === 'POST'
|
|
296
|
+
const originTrusted = typeof origin === 'string' && extensionOrigins.has(origin)
|
|
297
|
+
// 网页侧请求必带 Origin;桥端点只信任白名单内的 chrome-extension:// 源(诊断 GET 面放行)
|
|
298
|
+
if (isPost && !originTrusted) {
|
|
299
|
+
res.statusCode = 403
|
|
300
|
+
res.setHeader('content-type', 'application/json')
|
|
301
|
+
res.end(JSON.stringify({ ok: false, error: 'origin 不在桥白名单' }))
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
// 心跳:扩展平时只走 /jobs 长轮询(健康只在启动探测时 ping 一次)——
|
|
305
|
+
// 一切携带白名单扩展 Origin 的请求都刷新 lastSeen,否则 45s 后误判扩展掉线;
|
|
306
|
+
// 无 Origin 的诊断 GET(/health /status)不记心跳,curl 探活不能伪造「扩展在线」
|
|
307
|
+
if (originTrusted) {
|
|
308
|
+
lastSeenAt = now()
|
|
309
|
+
}
|
|
310
|
+
const finish = (code: number, body: unknown): void => {
|
|
311
|
+
res.statusCode = code
|
|
312
|
+
res.setHeader('content-type', 'application/json')
|
|
313
|
+
res.end(JSON.stringify(body))
|
|
314
|
+
}
|
|
315
|
+
if (req.method === 'GET' && path === '/health') {
|
|
316
|
+
finish(200, { ok: true, service: 'gotry-session-bridge', protocol: BRIDGE_PROTOCOL })
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
if (req.method === 'GET' && path === '/status') {
|
|
320
|
+
finish(200, {
|
|
321
|
+
ok: true, protocol: BRIDGE_PROTOCOL, ...stats(),
|
|
322
|
+
extensionConnected: extensionConnected(),
|
|
323
|
+
...(statusExtra ? statusExtra() : {}),
|
|
324
|
+
})
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
if (isPost && path === '/jobs') {
|
|
328
|
+
// 轮询体携带 {extensionVersion, capabilities: SITES 键清单}——按站点 capability
|
|
329
|
+
// 路由(见 ParkedPoller 注);体缺失/非 JSON 视为 match-all,向前兼容旧扩展。
|
|
330
|
+
void readBody(req, 64 * 1024).then((body) => {
|
|
331
|
+
const pollerCaps = ((): Set<string> | null => {
|
|
332
|
+
if (typeof body !== 'string') return null
|
|
333
|
+
try {
|
|
334
|
+
const parsed = JSON.parse(body) as { capabilities?: unknown }
|
|
335
|
+
return Array.isArray(parsed.capabilities) ? new Set(parsed.capabilities.filter((c): c is string => typeof c === 'string')) : null
|
|
336
|
+
} catch {
|
|
337
|
+
return null
|
|
338
|
+
}
|
|
339
|
+
})()
|
|
340
|
+
const next = takeMatching(pollerCaps)
|
|
341
|
+
if (next) { finish(200, { job: next.job }); return }
|
|
342
|
+
// 长轮询:hold ≤ JOBS_LONG_POLL_MS(必须 < MV3 SW 30s 存活窗口,每次响应都续命);
|
|
343
|
+
// 新 job 提交时即时唤醒 parked 取活者(见 dispatchToParked)
|
|
344
|
+
const poller: ParkedPoller = { res, timer: null as unknown as ReturnType<typeof setTimeout>, capabilities: pollerCaps }
|
|
345
|
+
const parkTimer = setTimeout(() => {
|
|
346
|
+
const i = parked.findIndex((p) => p.res === res)
|
|
347
|
+
if (i >= 0) parked.splice(i, 1)
|
|
348
|
+
finish(200, { job: null })
|
|
349
|
+
}, JOBS_LONG_POLL_MS)
|
|
350
|
+
poller.timer = parkTimer
|
|
351
|
+
// 默认桥是惰性能力:外部扩展的 parked 轮询不得在主流程结束后钉住 CLI。
|
|
352
|
+
// keepBridge wizard 反过来要靠它守住进程,因此只对默认形态 unref。
|
|
353
|
+
if (!opts.keepBridge) parkTimer.unref()
|
|
354
|
+
parked.push(poller)
|
|
355
|
+
res.on('close', () => {
|
|
356
|
+
const i = parked.findIndex((p) => p.res === res)
|
|
357
|
+
if (i >= 0) parked.splice(i, 1)
|
|
358
|
+
clearTimeout(parkTimer)
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
const isResults = isPost && (resultsViaQuery ? path === '/results' : path.startsWith('/results/'))
|
|
364
|
+
if (isResults) {
|
|
365
|
+
const jobId = resultsViaQuery
|
|
366
|
+
? new URL(req.url ?? '', 'http://bridge.local').searchParams.get('jobId') ?? ''
|
|
367
|
+
: decodeURIComponent(path.slice('/results/'.length))
|
|
368
|
+
if (!jobId) { finish(400, { ok: false, error: '缺 jobId' }); return }
|
|
369
|
+
void readBody(req, MAX_RESULT_BODY_BYTES).then((body) => {
|
|
370
|
+
if (typeof body !== 'string') { finish(400, { ok: false, error: body.err }); return }
|
|
371
|
+
let parsed: ExtensionJobResult
|
|
372
|
+
try {
|
|
373
|
+
parsed = JSON.parse(body) as ExtensionJobResult
|
|
374
|
+
} catch {
|
|
375
|
+
finish(400, { ok: false, error: '回包不是 JSON' })
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
finish(200, { ok: true })
|
|
379
|
+
resolveJob(jobId, parsed)
|
|
380
|
+
})
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
finish(404, { ok: false, error: 'unknown endpoint' })
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const queueHandle: BridgeJobQueue = {
|
|
293
388
|
port: 0,
|
|
294
389
|
extensionConnected,
|
|
390
|
+
stats,
|
|
391
|
+
handleRequest: makeHandler('', false),
|
|
392
|
+
handleMountedRequest: makeHandler('/v1/session/bridge', true),
|
|
295
393
|
submit(job, submitOpts = {}) {
|
|
296
394
|
const extensionWaitMs = submitOpts.extensionWaitMs ?? DEFAULT_EXTENSION_WAIT_MS
|
|
297
395
|
const timeoutMs = submitOpts.timeoutMs ?? job.timeoutMs ?? 30_000
|
|
@@ -315,7 +413,7 @@ export async function createSessionBridge(opts: SessionBridgeOptions = {}): Prom
|
|
|
315
413
|
let waited = 0
|
|
316
414
|
const waitTimer = setInterval(() => {
|
|
317
415
|
waited += 250
|
|
318
|
-
if (
|
|
416
|
+
if (queueHandle.extensionConnected()) { clearInterval(waitTimer); return }
|
|
319
417
|
if (waited >= extensionWaitMs) {
|
|
320
418
|
clearInterval(waitTimer)
|
|
321
419
|
settle({
|
|
@@ -345,8 +443,34 @@ export async function createSessionBridge(opts: SessionBridgeOptions = {}): Prom
|
|
|
345
443
|
clearTimeout(p.timer)
|
|
346
444
|
p.res.destroy()
|
|
347
445
|
}
|
|
348
|
-
|
|
349
|
-
|
|
446
|
+
resolve()
|
|
447
|
+
}),
|
|
448
|
+
}
|
|
449
|
+
return queueHandle
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** 创 Bridge 服务(测试可直接调;生产走 getOrCreateSessionBridge 懒单例) */
|
|
453
|
+
export async function createSessionBridge(opts: SessionBridgeOptions = {}): Promise<{ ok: true; bridge: SessionJobHandle } | { ok: false; summary: string }> {
|
|
454
|
+
const ports = opts.ports ?? [...BRIDGE_PORTS]
|
|
455
|
+
const queueHandle = createBridgeJobQueue(opts)
|
|
456
|
+
let port = 0
|
|
457
|
+
|
|
458
|
+
const server = createServer((req, res) => queueHandle.handleRequest(req, res))
|
|
459
|
+
server.on('connection', (socket) => {
|
|
460
|
+
// server.unref() 只解开监听器;已接受的扩展长轮询 socket 仍会引用事件循环。
|
|
461
|
+
if (!opts.keepBridge) socket.unref()
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const bridge: SessionJobHandle = {
|
|
465
|
+
port: 0,
|
|
466
|
+
extensionConnected: queueHandle.extensionConnected,
|
|
467
|
+
submit: (job, submitOpts) => queueHandle.submit(job, submitOpts),
|
|
468
|
+
close: () =>
|
|
469
|
+
new Promise<void>((resolve) => {
|
|
470
|
+
void queueHandle.close().then(() => {
|
|
471
|
+
server.close(() => resolve())
|
|
472
|
+
server.closeAllConnections?.()
|
|
473
|
+
})
|
|
350
474
|
}),
|
|
351
475
|
}
|
|
352
476
|
|
|
@@ -375,6 +499,7 @@ export async function createSessionBridge(opts: SessionBridgeOptions = {}): Prom
|
|
|
375
499
|
// 端口被占(如另一个 gotry 实例)——试端口池下一个
|
|
376
500
|
}
|
|
377
501
|
}
|
|
502
|
+
await queueHandle.close().catch(() => { /* ignore */ })
|
|
378
503
|
return {
|
|
379
504
|
ok: false,
|
|
380
505
|
summary: `扩展桥端口池 ${ports.join('/')} 全部被占(多为并行 gotry 实例);不影响官方通道 gotry_flyai_search。关闭多余实例后重试。`,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 检索页无回包不是车道失败:与 CDP 车道同语义(到点无 hints 命中 ⇒ miss,页标题命挑战 ⇒ challenged)。
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { getOrCreateSessionBridge, needsExtensionSummary, type SessionJobHandle } from './extension-bridge.ts'
|
|
13
|
+
import { getOrCreateSessionBridge, needsExtensionSummary, type SessionJobHandle, type SniffBodies } from './extension-bridge.ts'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* needs-extension 用户门文案(D-24 自适应,issue #117):按本地通道是否落位自动跳过
|
|
@@ -43,9 +43,15 @@ function bridgeReady(b: { ok: true; bridge: SessionJobHandle } | { ok: false; su
|
|
|
43
43
|
return b.ok === true
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** 桥解析(2026-09-11):gotry-backend 服务形态注入进程内队列;桌面形态缺省走 loopback 懒单例 */
|
|
47
|
+
async function resolveBridge(injected?: SessionJobHandle): Promise<SessionJobHandleRef | { ok: false; summary: string }> {
|
|
48
|
+
if (injected) return { ok: true, bridge: injected }
|
|
49
|
+
return getOrCreateSessionBridge()
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
/** cookie-names job:票据 cookie 名存在性(名字级;协议不含值) */
|
|
47
|
-
export async function extensionCookieNames(q: { site: string; domain: string; ticketNames: string[]; timeoutMs?: number }): Promise<{ ok: true; tickets: string[] } | BridgeFailure> {
|
|
48
|
-
const bridge = await
|
|
53
|
+
export async function extensionCookieNames(q: { site: string; domain: string; ticketNames: string[]; timeoutMs?: number }, bridgeHandle?: SessionJobHandle): Promise<{ ok: true; tickets: string[] } | BridgeFailure> {
|
|
54
|
+
const bridge = await resolveBridge(bridgeHandle)
|
|
49
55
|
if (!bridgeReady(bridge)) return { ok: false, kind: 'bridge-unavailable', summary: bridge.summary }
|
|
50
56
|
const outcome = await bridge.bridge.submit(
|
|
51
57
|
{ kind: 'cookie-names', site: q.site, timeoutMs: q.timeoutMs ?? 8_000 },
|
|
@@ -58,8 +64,8 @@ export async function extensionCookieNames(q: { site: string; domain: string; ti
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/** 登录入口置前台打开(#34 纪律:标签留给用户,扩展侧不 close) */
|
|
61
|
-
export async function extensionOpenLogin(q: { site: string; url: string; timeoutMs?: number }): Promise<{ ok: true; opened: boolean } | BridgeFailure> {
|
|
62
|
-
const bridge = await
|
|
67
|
+
export async function extensionOpenLogin(q: { site: string; url: string; timeoutMs?: number }, bridgeHandle?: SessionJobHandle): Promise<{ ok: true; opened: boolean } | BridgeFailure> {
|
|
68
|
+
const bridge = await resolveBridge(bridgeHandle)
|
|
63
69
|
if (!bridgeReady(bridge)) return { ok: false, kind: 'bridge-unavailable', summary: bridge.summary }
|
|
64
70
|
const outcome = await bridge.bridge.submit({ kind: 'open-login', site: q.site, url: q.url }, { timeoutMs: q.timeoutMs ?? 15_000 })
|
|
65
71
|
if (!outcome.ok) return { ok: false, kind: outcome.reason, summary: outcome.summary }
|
|
@@ -71,26 +77,33 @@ export interface ExtensionSearchOutcome {
|
|
|
71
77
|
ok: true
|
|
72
78
|
/** NETWORK_HINTS 命中的响应原文;未命中/超时为空 */
|
|
73
79
|
body: string
|
|
80
|
+
/** Exact NETWORK_HINTS response URL selected for body;未命中/超时为空 */
|
|
81
|
+
url: string
|
|
74
82
|
/** 页标题(challenged 判定素材) */
|
|
75
83
|
title: string
|
|
76
84
|
/** 到点未见 hints 命中(站点无回包/风控拦截页) */
|
|
77
85
|
timedOut: boolean
|
|
86
|
+
/** multiCollect(dida 推荐流):分桶回包体;旧扩展/非 multiCollect 缺省 */
|
|
87
|
+
bodies?: SniffBodies
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
/** 检索 job:后台标签打开 entry,等 content hook 的嗅探回包;页无响应=timedOut(非车道失败) */
|
|
81
|
-
export async function extensionSearchJob(q: { site: string; url: string; timeoutMs?: number }): Promise<ExtensionSearchOutcome | BridgeFailure> {
|
|
82
|
-
const bridge = await
|
|
91
|
+
export async function extensionSearchJob(q: { site: string; url: string; timeoutMs?: number; multiCollect?: boolean }, bridgeHandle?: SessionJobHandle): Promise<ExtensionSearchOutcome | BridgeFailure> {
|
|
92
|
+
const bridge = await resolveBridge(bridgeHandle)
|
|
83
93
|
if (!bridgeReady(bridge)) return { ok: false, kind: 'bridge-unavailable', summary: bridge.summary }
|
|
84
94
|
const timeoutMs = q.timeoutMs ?? 30_000
|
|
85
95
|
const outcome = await bridge.bridge.submit(
|
|
86
|
-
{ kind: 'search', site: q.site, url: q.url, timeoutMs },
|
|
96
|
+
{ kind: 'search', site: q.site, url: q.url, timeoutMs, ...(q.multiCollect ? { multiCollect: true } : {}) },
|
|
87
97
|
{ timeoutMs: timeoutMs + 10_000 },
|
|
88
98
|
)
|
|
89
99
|
if (!outcome.ok) return { ok: false, kind: outcome.reason, summary: outcome.summary }
|
|
100
|
+
const bodies = outcome.result.bodies
|
|
90
101
|
return {
|
|
91
102
|
ok: true,
|
|
92
103
|
body: typeof outcome.result.body === 'string' ? outcome.result.body : '',
|
|
104
|
+
url: typeof outcome.result.url === 'string' ? outcome.result.url : '',
|
|
93
105
|
title: typeof outcome.result.title === 'string' ? outcome.result.title : '',
|
|
94
106
|
timedOut: outcome.result.timeout === true,
|
|
107
|
+
...(bodies && typeof bodies === 'object' ? { bodies } : {}),
|
|
95
108
|
}
|
|
96
|
-
}
|
|
109
|
+
}
|
|
@@ -65,7 +65,7 @@ function ensureExtensionFiles(source: string, target: string, dryRun: boolean):
|
|
|
65
65
|
if (dryRun) return { step: 'ensure-extension-files', status: 'skip', summary: '[dry-run] skip 扩展落位' }
|
|
66
66
|
try {
|
|
67
67
|
if (!existsSync(source) || !existsSync(join(source, 'manifest.json'))) {
|
|
68
|
-
return { step: 'ensure-extension-files', status: 'fail', summary: `源扩展目录缺失 manifest.json: ${source}(先跑 npx gotry setup 落位)` }
|
|
68
|
+
return { step: 'ensure-extension-files', status: 'fail', summary: `源扩展目录缺失 manifest.json: ${source}(先跑 npx @danceiny/gotry setup 落位)` }
|
|
69
69
|
}
|
|
70
70
|
mkdirSync(target, { recursive: true })
|
|
71
71
|
const required = ['manifest.json', 'background.js', 'content-main.js', 'content-bridge.js']
|
|
@@ -90,7 +90,7 @@ async function watchExtensionReady(dryRun: boolean, sourceDir: string, extension
|
|
|
90
90
|
const srcStat = statSync(sourceDir)
|
|
91
91
|
const dstStat = statSync(extensionDir)
|
|
92
92
|
if (srcStat.mtimeMs > dstStat.mtimeMs) {
|
|
93
|
-
return { step: 'watch-extension-ready', status: 'fail', summary: `扩展文件可能过期(源比落位新);重跑 npx gotry setup` }
|
|
93
|
+
return { step: 'watch-extension-ready', status: 'fail', summary: `扩展文件可能过期(源比落位新);重跑 npx @danceiny/gotry setup` }
|
|
94
94
|
}
|
|
95
95
|
const manifest = JSON.parse(await (await import('node:fs/promises')).readFile(join(extensionDir, 'manifest.json'), 'utf8'))
|
|
96
96
|
if (typeof manifest.key !== 'string' || manifest.key.length < 100) {
|