@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.17

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 (238) hide show
  1. package/README.md +51 -13
  2. package/README.zh-CN.md +26 -12
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +357 -8
  5. package/bin/gotry-inner.js +442 -60
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +15 -1
  10. package/data/airline-airports.json +39 -0
  11. package/dist/capabilities/agent-reach-deep.js +1 -1
  12. package/dist/capabilities/agent-reach.js +1 -1
  13. package/dist/capabilities/anything.js +1 -1
  14. package/dist/capabilities/artifacts.js +1 -1
  15. package/dist/capabilities/effect.js +293 -0
  16. package/dist/capabilities/fact-log.js +41 -0
  17. package/dist/capabilities/flyai.js +20 -6
  18. package/dist/capabilities/hbcli.js +43 -1
  19. package/dist/capabilities/incident-log.js +1 -1
  20. package/dist/capabilities/model-override.js +18 -0
  21. package/dist/capabilities/opensky.js +1 -1
  22. package/dist/capabilities/resilience.js +90 -0
  23. package/dist/capabilities/session/action-cache.js +1 -1
  24. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  25. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  26. package/dist/capabilities/session/benchmark.js +2 -1
  27. package/dist/capabilities/session/extension-bridge.js +358 -0
  28. package/dist/capabilities/session/extension-channel.js +93 -0
  29. package/dist/capabilities/session/extension-distribution.js +234 -0
  30. package/dist/capabilities/session/extract.js +1 -1
  31. package/dist/capabilities/session/golden-score.js +92 -0
  32. package/dist/capabilities/session/health-watch.js +216 -0
  33. package/dist/capabilities/session/read-guard.js +1 -1
  34. package/dist/capabilities/session/static-flight-golden.js +137 -0
  35. package/dist/capabilities/session/transport.js +1 -1
  36. package/dist/capabilities/session/wizard.js +147 -0
  37. package/dist/capabilities/session-consent.js +1 -1
  38. package/dist/capabilities/session-login.js +83 -1
  39. package/dist/capabilities/session-search.js +111 -2
  40. package/dist/capabilities/weather.js +168 -46
  41. package/dist/data/session-golden-20.json +25 -0
  42. package/dist/data/sf-golden-manifest.json +102 -0
  43. package/dist/data/sf-static-routes.json +91 -0
  44. package/dist/scripts/action-cache-tests.js +1 -1
  45. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  46. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  47. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  48. package/dist/scripts/agent-reach-tests.js +1 -1
  49. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  50. package/dist/scripts/anything-tests.js +1 -1
  51. package/dist/scripts/async-collect.js +1 -1
  52. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  53. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  54. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  55. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  56. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  57. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  58. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  59. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  60. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  61. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  62. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  63. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  64. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  65. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  66. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  67. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  68. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  69. package/dist/scripts/booking-saga-tests.js +1 -1
  70. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  71. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  72. package/dist/scripts/bootstrap-tests.js +59 -3
  73. package/dist/scripts/build-changelog.js +279 -0
  74. package/dist/scripts/changelog-tests.js +165 -0
  75. package/dist/scripts/companion-tests.js +1 -1
  76. package/dist/scripts/diff-test.js +1 -1
  77. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  78. package/dist/scripts/dsh-runtime-closure.js +150 -0
  79. package/dist/scripts/effect-tests.js +474 -0
  80. package/dist/scripts/engine-run.js +1 -1
  81. package/dist/scripts/engine-tests.js +1 -1
  82. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  83. package/dist/scripts/evaluation-contract-tests.js +574 -0
  84. package/dist/scripts/extension-distribution-cli.js +35 -0
  85. package/dist/scripts/extension-distribution-tests.js +341 -0
  86. package/dist/scripts/extension-tests.js +573 -0
  87. package/dist/scripts/fact-gate-tests.js +318 -0
  88. package/dist/scripts/flyai-tests.js +1 -1
  89. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  90. package/dist/scripts/hbcli-tests.js +1 -1
  91. package/dist/scripts/health-watch-cli.js +51 -0
  92. package/dist/scripts/i18n-tests.js +1 -1
  93. package/dist/scripts/incident-tests.js +1 -1
  94. package/dist/scripts/journey-tests.js +1 -1
  95. package/dist/scripts/ledger-tests.js +1 -1
  96. package/dist/scripts/ledger-workflow-crash.js +1 -1
  97. package/dist/scripts/memory-capture-tests.js +1 -1
  98. package/dist/scripts/memory-decay-tests.js +1 -1
  99. package/dist/scripts/memory-metrics.js +1 -1
  100. package/dist/scripts/memory-value-report.js +1 -1
  101. package/dist/scripts/model-override-e2e.js +176 -0
  102. package/dist/scripts/nightly-evidence-tests.js +67 -4
  103. package/dist/scripts/nightly-evidence.js +59 -3
  104. package/dist/scripts/nudge-digest.js +1 -1
  105. package/dist/scripts/onboarding-tests.js +186 -0
  106. package/dist/scripts/opensky-check.js +1 -1
  107. package/dist/scripts/opensky-tests.js +1 -1
  108. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  109. package/dist/scripts/price-drift-tests.js +441 -0
  110. package/dist/scripts/price-drift-watch.js +491 -0
  111. package/dist/scripts/probe-poi-tests.js +1 -1
  112. package/dist/scripts/product-metrics.js +1 -1
  113. package/dist/scripts/publish-preverify.js +40 -4
  114. package/dist/scripts/realtime-pricing-tests.js +1 -1
  115. package/dist/scripts/replay-async.js +1 -1
  116. package/dist/scripts/replay-real.js +1 -1
  117. package/dist/scripts/replay.js +1 -1
  118. package/dist/scripts/session-attach-diagnose.js +1 -1
  119. package/dist/scripts/session-attach-poc.js +1 -1
  120. package/dist/scripts/session-benchmark.js +1 -1
  121. package/dist/scripts/session-extract-tests.js +1 -1
  122. package/dist/scripts/session-login.js +1 -1
  123. package/dist/scripts/session-tests.js +70 -20
  124. package/dist/scripts/sf-live-benchmark.js +338 -0
  125. package/dist/scripts/sf-live-cli-tests.js +21 -0
  126. package/dist/scripts/sf-soft-score-tests.js +108 -0
  127. package/dist/scripts/sf-summary.js +93 -0
  128. package/dist/scripts/skeleton-check.js +1 -1
  129. package/dist/scripts/skeleton-integration-test.js +1 -1
  130. package/dist/scripts/skills-contract-tests.js +1 -1
  131. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  132. package/dist/scripts/smoke.js +154 -33
  133. package/dist/scripts/state-cli-tests.js +1 -1
  134. package/dist/scripts/state-cli.js +1 -1
  135. package/dist/scripts/static-golden-tests.js +299 -0
  136. package/dist/scripts/time-eval-tests.js +1 -1
  137. package/dist/scripts/travel-timeline-tests.js +1 -1
  138. package/dist/scripts/unified-tests.js +1 -1
  139. package/dist/scripts/weather-tests.js +694 -44
  140. package/dist/scripts/z3-race-tests.js +1 -1
  141. package/dist/src/artifact-gate.js +310 -0
  142. package/dist/src/benchmark-agent-conformance.js +370 -0
  143. package/dist/src/benchmark-environment-bridge.js +384 -0
  144. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  145. package/dist/src/benchmark-tool-isolation.js +124 -0
  146. package/dist/src/bookable-facts.js +349 -0
  147. package/dist/src/booking-saga.js +1 -1
  148. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  149. package/dist/src/booking-surface/canonical-schema.js +113 -0
  150. package/dist/src/booking-surface/contracts-v2.js +89 -0
  151. package/dist/src/booking-surface/contracts.js +46 -0
  152. package/dist/src/booking-surface/dsh-planner.js +453 -0
  153. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  154. package/dist/src/booking-surface/error-codes.js +94 -0
  155. package/dist/src/booking-surface/index.js +15 -0
  156. package/dist/src/booking-surface/profile.js +68 -0
  157. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  158. package/dist/src/booking-surface/runtime.js +351 -0
  159. package/dist/src/booking-surface/server-v2.js +334 -0
  160. package/dist/src/booking-surface/server.js +302 -0
  161. package/dist/src/booking-surface/startup.js +159 -0
  162. package/dist/src/booking-surface/validation-v2.js +319 -0
  163. package/dist/src/booking-surface/validation.js +809 -0
  164. package/dist/src/bridge.js +1 -1
  165. package/dist/src/companions.js +1 -1
  166. package/dist/src/contracts.js +1 -1
  167. package/dist/src/dsh-llm.js +1 -1
  168. package/dist/src/engine.js +1 -1
  169. package/dist/src/evaluation-cadence.js +234 -0
  170. package/dist/src/evaluation-contracts.js +906 -0
  171. package/dist/src/i18n.js +1 -1
  172. package/dist/src/index.js +234 -60
  173. package/dist/src/journey.js +1 -1
  174. package/dist/src/loop.js +1 -1
  175. package/dist/src/memory-capture.js +1 -1
  176. package/dist/src/memory-decay.js +1 -1
  177. package/dist/src/memory-utility.js +1 -1
  178. package/dist/src/mock-llm.js +1 -1
  179. package/dist/src/model.js +1 -1
  180. package/dist/src/realtime-pricing.js +14 -7
  181. package/dist/src/slot-spec.js +1 -1
  182. package/dist/src/state-ledger.js +2 -1
  183. package/dist/src/time-anchor.js +1 -1
  184. package/dist/src/tool-budget.js +136 -0
  185. package/dist/src/tool-packet.js +1 -1
  186. package/dist/src/travel-slots.js +1 -1
  187. package/dist/src/travel-timeline.js +1 -1
  188. package/dist/src/unified.js +1 -1
  189. package/dist/src/wish-pool.js +1 -1
  190. package/dist/src/z3-shared.js +1 -1
  191. package/extension/README.md +50 -0
  192. package/extension/background.js +160 -0
  193. package/extension/content-bridge.js +41 -0
  194. package/extension/content-main.js +62 -0
  195. package/extension/manifest.json +32 -0
  196. package/package.json +291 -12
  197. package/schemas/booking.surface.v1.schema.json +927 -0
  198. package/schemas/booking.surface.v2.schema.json +61 -0
  199. package/ts/capabilities/fact-log.ts +54 -0
  200. package/ts/capabilities/flyai.ts +16 -3
  201. package/ts/capabilities/hbcli.ts +46 -0
  202. package/ts/capabilities/session/benchmark.ts +4 -1
  203. package/ts/capabilities/session/extension-bridge.ts +377 -0
  204. package/ts/capabilities/session/extension-channel.ts +95 -0
  205. package/ts/capabilities/session/extension-distribution.ts +264 -0
  206. package/ts/capabilities/session/golden-score.ts +139 -0
  207. package/ts/capabilities/session/health-watch.ts +257 -0
  208. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  209. package/ts/capabilities/session/wizard.ts +140 -0
  210. package/ts/capabilities/session-login.ts +63 -2
  211. package/ts/capabilities/session-search.ts +105 -3
  212. package/ts/capabilities/weather.ts +141 -52
  213. package/ts/package.json +3 -3
  214. package/ts/src/artifact-gate.ts +365 -0
  215. package/ts/src/benchmark-agent-conformance.ts +448 -0
  216. package/ts/src/benchmark-environment-bridge.ts +348 -0
  217. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  218. package/ts/src/benchmark-tool-isolation.ts +166 -0
  219. package/ts/src/bookable-facts.ts +557 -0
  220. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  221. package/ts/src/booking-surface/canonical-schema.js +113 -0
  222. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  223. package/ts/src/booking-surface/contracts.ts +380 -0
  224. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  225. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  226. package/ts/src/booking-surface/error-codes.ts +101 -0
  227. package/ts/src/booking-surface/index.ts +12 -0
  228. package/ts/src/booking-surface/profile.ts +42 -0
  229. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  230. package/ts/src/booking-surface/runtime.ts +483 -0
  231. package/ts/src/booking-surface/server-v2.ts +247 -0
  232. package/ts/src/booking-surface/server.ts +324 -0
  233. package/ts/src/booking-surface/startup.ts +196 -0
  234. package/ts/src/booking-surface/validation-v2.ts +205 -0
  235. package/ts/src/booking-surface/validation.ts +453 -0
  236. package/ts/src/index.ts +161 -14
  237. package/ts/src/state-ledger.ts +1 -0
  238. package/ts/src/tool-budget.ts +165 -0
@@ -0,0 +1,90 @@
1
+ export function backoffDelayMs(policy, failedAttempt) {
2
+ const raw = policy.baseDelayMs * 2 ** (Math.max(1, failedAttempt) - 1);
3
+ return Math.min(raw, policy.maxDelayMs);
4
+ }
5
+ const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
6
+ export async function withRetry(fn, policy, onBackoff) {
7
+ const sleep = policy.sleep ?? defaultSleep;
8
+ const maxAttempts = Math.max(1, policy.maxAttempts);
9
+ let attempts = 0;
10
+ let backoffMs = 0;
11
+ for(;;){
12
+ attempts += 1;
13
+ let result = null;
14
+ let error = null;
15
+ try {
16
+ result = await fn();
17
+ } catch (e) {
18
+ error = e;
19
+ }
20
+ const retryable = policy.isRetryable?.(result, error) ?? (error != null || result?.ok === false);
21
+ if (attempts < maxAttempts && retryable) {
22
+ const delay = backoffDelayMs(policy, attempts);
23
+ onBackoff?.(attempts, delay);
24
+ backoffMs += delay;
25
+ await sleep(delay);
26
+ continue;
27
+ }
28
+ return {
29
+ result,
30
+ error,
31
+ attempts,
32
+ backoffMs
33
+ };
34
+ }
35
+ }
36
+ export class CircuitBreaker {
37
+ o;
38
+ consecutiveFailures = 0;
39
+ openedAt = 0;
40
+ probeInFlight = false;
41
+ constructor(o){
42
+ this.o = o;
43
+ }
44
+ threshold() {
45
+ return Math.max(1, this.o.failureThreshold);
46
+ }
47
+ now() {
48
+ return (this.o.now ?? Date.now)();
49
+ }
50
+ state() {
51
+ if (this.openedAt > 0 && this.now() - this.openedAt >= this.o.openMs) return 'half-open';
52
+ if (this.openedAt > 0) return 'open';
53
+ return 'closed';
54
+ }
55
+ canAttempt() {
56
+ const s = this.state();
57
+ if (s === 'closed') return {
58
+ allowed: true,
59
+ state: s
60
+ };
61
+ if (s === 'open') return {
62
+ allowed: false,
63
+ state: s
64
+ };
65
+ if (this.probeInFlight) return {
66
+ allowed: false,
67
+ state: s
68
+ };
69
+ this.probeInFlight = true;
70
+ return {
71
+ allowed: true,
72
+ state: s
73
+ };
74
+ }
75
+ onSuccess() {
76
+ this.consecutiveFailures = 0;
77
+ this.openedAt = 0;
78
+ this.probeInFlight = false;
79
+ }
80
+ onFailure() {
81
+ this.probeInFlight = false;
82
+ this.consecutiveFailures += 1;
83
+ if (this.openedAt > 0 || this.consecutiveFailures >= this.threshold()) {
84
+ this.openedAt = this.now();
85
+ }
86
+ }
87
+ }
88
+
89
+
90
+ //# sourceURL=ts/capabilities/resilience.ts
@@ -117,4 +117,4 @@ export function actionCachePath(stateRoot) {
117
117
  }
118
118
 
119
119
 
120
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/action-cache.ts
120
+ //# sourceURL=ts/capabilities/session/action-cache.ts
@@ -80,4 +80,4 @@ export function parseBatchSearch(body) {
80
80
  }
81
81
 
82
82
 
83
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/adapters/ctrip-flight.ts
83
+ //# sourceURL=ts/capabilities/session/adapters/ctrip-flight.ts
@@ -70,4 +70,4 @@ export function extractListings(entries, opts = {}) {
70
70
  }
71
71
 
72
72
 
73
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/adapters/meituan-local.ts
73
+ //# sourceURL=ts/capabilities/session/adapters/meituan-local.ts
@@ -228,6 +228,7 @@ export function evaluateDoubleSource(input) {
228
228
  }
229
229
  if (!session) return baseEvaluation('source_unavailable', 'no_spend_stop');
230
230
  if (session.verdict === 'needs-attach') return baseEvaluation('waiting_attach', 'no_spend_waiting_user');
231
+ if (session.verdict === 'needs-extension') return baseEvaluation('waiting_extension', 'no_spend_waiting_user');
231
232
  if (session.verdict === 'needs-login') return baseEvaluation('waiting_login', 'no_spend_waiting_user');
232
233
  if (!official || official.verdict !== 'hit' || session.verdict !== 'hit') {
233
234
  return baseEvaluation('source_unavailable', 'no_spend_stop');
@@ -249,4 +250,4 @@ export function evaluateDoubleSource(input) {
249
250
  }
250
251
 
251
252
 
252
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/benchmark.ts
253
+ //# sourceURL=ts/capabilities/session/benchmark.ts
@@ -0,0 +1,358 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ export const BRIDGE_PORTS = [
4
+ 8791,
5
+ 8792,
6
+ 8793,
7
+ 8794,
8
+ 8795
9
+ ];
10
+ export const BRIDGE_PROTOCOL = 'session-bridge.v1';
11
+ export const EXTENSION_ID = 'olpgkofjhhiiiahdkkbcninhjmegghfe';
12
+ export const EXTENSION_ORIGIN = `chrome-extension://${EXTENSION_ID}`;
13
+ export const EXTENSION_ID_STORE = 'oeajpiccmonococjcegddlooeeohlbgd';
14
+ export const EXTENSION_ORIGIN_STORE = `chrome-extension://${EXTENSION_ID_STORE}`;
15
+ export const EXTENSION_STORE_URL = `https://chromewebstore.google.com/detail/gotry-session-bridge/${EXTENSION_ID_STORE}`;
16
+ export const EXTENSION_ORIGINS = [
17
+ EXTENSION_ORIGIN,
18
+ EXTENSION_ORIGIN_STORE
19
+ ];
20
+ export const EXTENSION_CONNECTED_WINDOW_MS = 45_000;
21
+ export const JOBS_LONG_POLL_MS = 20_000;
22
+ export const DEFAULT_EXTENSION_WAIT_MS = 6_000;
23
+ export const MAX_RESULT_BODY_BYTES = 8 * 1024 * 1024;
24
+ function readBody(req, cap) {
25
+ return new Promise((resolve)=>{
26
+ const chunks = [];
27
+ let size = 0;
28
+ let done = false;
29
+ req.on('data', (c)=>{
30
+ if (done) return;
31
+ size += c.length;
32
+ if (size > cap) {
33
+ done = true;
34
+ req.removeAllListeners('data');
35
+ resolve({
36
+ err: `body 超 ${cap} 字节上限(防失控回包)`
37
+ });
38
+ return;
39
+ }
40
+ chunks.push(c);
41
+ });
42
+ req.on('end', ()=>{
43
+ if (!done) {
44
+ done = true;
45
+ resolve(Buffer.concat(chunks).toString('utf8'));
46
+ }
47
+ });
48
+ req.on('error', ()=>{
49
+ if (!done) {
50
+ done = true;
51
+ resolve({
52
+ err: 'body 读取失败'
53
+ });
54
+ }
55
+ });
56
+ });
57
+ }
58
+ export async function createSessionBridge(opts = {}) {
59
+ const ports = opts.ports ?? [
60
+ ...BRIDGE_PORTS
61
+ ];
62
+ const extensionOrigins = new Set(opts.extensionOrigins ?? EXTENSION_ORIGINS);
63
+ const now = opts.now ?? Date.now;
64
+ const queue = [];
65
+ const inFlight = new Map();
66
+ const parked = [];
67
+ let lastSeenAt = 0;
68
+ let closed = false;
69
+ let port = 0;
70
+ const extensionConnected = ()=>lastSeenAt > 0 && now() - lastSeenAt < EXTENSION_CONNECTED_WINDOW_MS;
71
+ function takeQueued() {
72
+ const next = queue.shift();
73
+ if (next) inFlight.set(next.job.jobId, next);
74
+ return next ?? null;
75
+ }
76
+ const server = createServer((req, res)=>{
77
+ if (closed) {
78
+ res.statusCode = 503;
79
+ res.end();
80
+ return;
81
+ }
82
+ const urlPath = (req.url ?? '').split('?')[0];
83
+ const origin = req.headers.origin;
84
+ const isPost = req.method === 'POST';
85
+ const originTrusted = typeof origin === 'string' && extensionOrigins.has(origin);
86
+ if (isPost && !originTrusted) {
87
+ res.statusCode = 403;
88
+ res.setHeader('content-type', 'application/json');
89
+ res.end(JSON.stringify({
90
+ ok: false,
91
+ error: 'origin 不在桥白名单'
92
+ }));
93
+ return;
94
+ }
95
+ if (originTrusted) {
96
+ lastSeenAt = now();
97
+ }
98
+ const finish = (code, body)=>{
99
+ res.statusCode = code;
100
+ res.setHeader('content-type', 'application/json');
101
+ res.end(JSON.stringify(body));
102
+ };
103
+ if (req.method === 'GET' && urlPath === '/health') {
104
+ finish(200, {
105
+ ok: true,
106
+ service: 'gotry-session-bridge',
107
+ protocol: BRIDGE_PROTOCOL
108
+ });
109
+ return;
110
+ }
111
+ if (req.method === 'GET' && urlPath === '/status') {
112
+ finish(200, {
113
+ ok: true,
114
+ port,
115
+ protocol: BRIDGE_PROTOCOL,
116
+ extensionConnected: extensionConnected(),
117
+ lastSeenMsAgo: lastSeenAt > 0 ? now() - lastSeenAt : null,
118
+ queued: queue.length,
119
+ inFlight: inFlight.size,
120
+ parked: parked.length
121
+ });
122
+ return;
123
+ }
124
+ if (isPost && urlPath === '/jobs') {
125
+ const next = takeQueued();
126
+ if (next) {
127
+ finish(200, {
128
+ job: next.job
129
+ });
130
+ return;
131
+ }
132
+ const parkTimer = setTimeout(()=>{
133
+ const i = parked.findIndex((p)=>p.res === res);
134
+ if (i >= 0) parked.splice(i, 1);
135
+ finish(200, {
136
+ job: null
137
+ });
138
+ }, JOBS_LONG_POLL_MS);
139
+ if (!opts.keepBridge) parkTimer.unref();
140
+ parked.push({
141
+ res,
142
+ timer: parkTimer
143
+ });
144
+ res.on('close', ()=>{
145
+ const i = parked.findIndex((p)=>p.res === res);
146
+ if (i >= 0) parked.splice(i, 1);
147
+ clearTimeout(parkTimer);
148
+ });
149
+ return;
150
+ }
151
+ if (isPost && urlPath.startsWith('/results/')) {
152
+ const jobId = decodeURIComponent(urlPath.slice('/results/'.length));
153
+ void readBody(req, MAX_RESULT_BODY_BYTES).then((body)=>{
154
+ if (typeof body !== 'string') {
155
+ finish(400, {
156
+ ok: false,
157
+ error: body.err
158
+ });
159
+ return;
160
+ }
161
+ let parsed;
162
+ try {
163
+ parsed = JSON.parse(body);
164
+ } catch {
165
+ finish(400, {
166
+ ok: false,
167
+ error: '回包不是 JSON'
168
+ });
169
+ return;
170
+ }
171
+ finish(200, {
172
+ ok: true
173
+ });
174
+ resolveJob(jobId, parsed);
175
+ });
176
+ return;
177
+ }
178
+ finish(404, {
179
+ ok: false,
180
+ error: 'unknown endpoint'
181
+ });
182
+ });
183
+ server.on('connection', (socket)=>{
184
+ if (!opts.keepBridge) socket.unref();
185
+ });
186
+ function resolveJob(jobId, parsed) {
187
+ const inFlightHit = inFlight.get(jobId);
188
+ if (inFlightHit) {
189
+ inFlight.delete(jobId);
190
+ if (inFlightHit.timer) clearTimeout(inFlightHit.timer);
191
+ inFlightHit.resolve({
192
+ ok: true,
193
+ result: parsed
194
+ });
195
+ return;
196
+ }
197
+ const idx = queue.findIndex((q)=>q.job.jobId === jobId);
198
+ if (idx < 0) return;
199
+ const [entry] = queue.splice(idx, 1);
200
+ if (entry.timer) clearTimeout(entry.timer);
201
+ entry.resolve({
202
+ ok: true,
203
+ result: parsed
204
+ });
205
+ }
206
+ function dispatchToParked(entry) {
207
+ const p = parked.shift();
208
+ if (!p) return false;
209
+ clearTimeout(p.timer);
210
+ inFlight.set(entry.job.jobId, entry);
211
+ p.res.statusCode = 200;
212
+ p.res.setHeader('content-type', 'application/json');
213
+ p.res.end(JSON.stringify({
214
+ job: entry.job
215
+ }));
216
+ return true;
217
+ }
218
+ const bridge = {
219
+ port: 0,
220
+ extensionConnected,
221
+ submit (job, submitOpts = {}) {
222
+ const extensionWaitMs = submitOpts.extensionWaitMs ?? DEFAULT_EXTENSION_WAIT_MS;
223
+ const timeoutMs = submitOpts.timeoutMs ?? job.timeoutMs ?? 30_000;
224
+ const full = {
225
+ jobId: randomUUID(),
226
+ ...job
227
+ };
228
+ return new Promise((resolve)=>{
229
+ if (closed) {
230
+ resolve({
231
+ ok: false,
232
+ reason: 'bridge-unavailable',
233
+ summary: '扩展桥已关闭'
234
+ });
235
+ return;
236
+ }
237
+ const entry = {
238
+ job: full,
239
+ resolve: null,
240
+ timer: null
241
+ };
242
+ const settle = (outcome)=>{
243
+ if (entry.timer) clearTimeout(entry.timer);
244
+ const i = queue.findIndex((q)=>q.job.jobId === full.jobId);
245
+ if (i >= 0) queue.splice(i, 1);
246
+ inFlight.delete(full.jobId);
247
+ resolve(outcome);
248
+ };
249
+ entry.resolve = settle;
250
+ entry.timer = setTimeout(()=>{
251
+ settle({
252
+ ok: false,
253
+ reason: 'timeout',
254
+ summary: `扩展未在 ${timeoutMs}ms 内回包(标签页无嗅探命中或扩展已停用)`
255
+ });
256
+ }, timeoutMs);
257
+ if (!dispatchToParked(entry)) queue.push(entry);
258
+ let waited = 0;
259
+ const waitTimer = setInterval(()=>{
260
+ waited += 250;
261
+ if (bridge.extensionConnected()) {
262
+ clearInterval(waitTimer);
263
+ return;
264
+ }
265
+ if (waited >= extensionWaitMs) {
266
+ clearInterval(waitTimer);
267
+ settle({
268
+ ok: false,
269
+ reason: 'extension-not-connected',
270
+ summary: `GoTry Session Bridge 扩展未连接(未安装或已停用)。一次性安装(装完零弹窗):推荐 Chrome 应用商店一键装(自动更新) ${EXTENSION_STORE_URL} ;或 npx gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension`
271
+ });
272
+ }
273
+ }, 250);
274
+ });
275
+ },
276
+ close: ()=>new Promise((resolve)=>{
277
+ closed = true;
278
+ const voided = {
279
+ ok: false,
280
+ reason: 'bridge-unavailable',
281
+ summary: '扩展桥关闭,任务作废(零花费)'
282
+ };
283
+ for (const q of queue.splice(0)){
284
+ if (q.timer) clearTimeout(q.timer);
285
+ q.resolve(voided);
286
+ }
287
+ for (const [, q] of inFlight){
288
+ if (q.timer) clearTimeout(q.timer);
289
+ q.resolve(voided);
290
+ }
291
+ inFlight.clear();
292
+ for (const p of parked.splice(0)){
293
+ clearTimeout(p.timer);
294
+ p.res.destroy();
295
+ }
296
+ server.close(()=>resolve());
297
+ server.closeAllConnections?.();
298
+ })
299
+ };
300
+ const listenAt = (p)=>new Promise((resolve, reject)=>{
301
+ const onError = (e)=>{
302
+ server.off('listening', onListening);
303
+ reject(e);
304
+ };
305
+ const onListening = ()=>{
306
+ server.off('error', onError);
307
+ resolve();
308
+ };
309
+ server.once('error', onError);
310
+ server.once('listening', onListening);
311
+ server.listen(p, '127.0.0.1');
312
+ });
313
+ for (const p of ports){
314
+ try {
315
+ await listenAt(p);
316
+ if (!opts.keepBridge) {
317
+ server.unref();
318
+ }
319
+ port = server.address().port;
320
+ bridge.port = port;
321
+ return {
322
+ ok: true,
323
+ bridge
324
+ };
325
+ } catch {}
326
+ }
327
+ return {
328
+ ok: false,
329
+ summary: `扩展桥端口池 ${ports.join('/')} 全部被占(多为并行 gotry 实例);不影响官方通道 gotry_flyai_search。关闭多余实例后重试。`
330
+ };
331
+ }
332
+ let singleton = null;
333
+ export async function getOrCreateSessionBridge(opts = {}) {
334
+ if (singleton) return singleton;
335
+ const created = await createSessionBridge(opts);
336
+ const result = created.ok ? {
337
+ ok: true,
338
+ bridge: created.bridge
339
+ } : {
340
+ ok: false,
341
+ summary: created.summary
342
+ };
343
+ singleton = result;
344
+ return result;
345
+ }
346
+ export async function __resetSessionBridgeForTest() {
347
+ if (singleton?.ok) await singleton.bridge.close().catch(()=>{});
348
+ singleton = null;
349
+ }
350
+ export function __setSessionBridgeForTest(bridge) {
351
+ singleton = bridge ? {
352
+ ok: true,
353
+ bridge
354
+ } : null;
355
+ }
356
+
357
+
358
+ //# sourceURL=ts/capabilities/session/extension-bridge.ts
@@ -0,0 +1,93 @@
1
+ import { getOrCreateSessionBridge, EXTENSION_STORE_URL } from './extension-bridge.js';
2
+ export const NEEDS_EXTENSION_HINT = '需要一次性安装 GoTry Session Bridge 浏览器扩展(装完零弹窗):' + `推荐 Chrome 应用商店一键安装(自动更新) ${EXTENSION_STORE_URL} ;` + '免审核、版本更新更快的 GitHub 通道:npx gotry setup --extension-from=github(自动下载落位 ~/.gotry/extension;' + '手动下载 github.com/Danceiny/gotry/releases 标签 ext-* 的 gotry-session-bridge.tar.gz),' + '再在 chrome://extensions 开发者模式「加载已解压的扩展程序」指向解压目录';
3
+ export function classifyBridgeFailure(kind) {
4
+ return kind === 'extension-not-connected' ? 'needs-extension' : 'error';
5
+ }
6
+ function bridgeReady(b) {
7
+ return b.ok === true;
8
+ }
9
+ export async function extensionCookieNames(q) {
10
+ const bridge = await getOrCreateSessionBridge();
11
+ if (!bridgeReady(bridge)) return {
12
+ ok: false,
13
+ kind: 'bridge-unavailable',
14
+ summary: bridge.summary
15
+ };
16
+ const outcome = await bridge.bridge.submit({
17
+ kind: 'cookie-names',
18
+ site: q.site,
19
+ timeoutMs: q.timeoutMs ?? 8_000
20
+ }, {
21
+ timeoutMs: q.timeoutMs ?? 8_000
22
+ });
23
+ if (!outcome.ok) return {
24
+ ok: false,
25
+ kind: outcome.reason,
26
+ summary: outcome.summary
27
+ };
28
+ const names = Array.isArray(outcome.result.names) ? outcome.result.names.filter((n)=>typeof n === 'string') : [];
29
+ return {
30
+ ok: true,
31
+ tickets: names
32
+ };
33
+ }
34
+ export async function extensionOpenLogin(q) {
35
+ const bridge = await getOrCreateSessionBridge();
36
+ if (!bridgeReady(bridge)) return {
37
+ ok: false,
38
+ kind: 'bridge-unavailable',
39
+ summary: bridge.summary
40
+ };
41
+ const outcome = await bridge.bridge.submit({
42
+ kind: 'open-login',
43
+ site: q.site,
44
+ url: q.url
45
+ }, {
46
+ timeoutMs: q.timeoutMs ?? 15_000
47
+ });
48
+ if (!outcome.ok) return {
49
+ ok: false,
50
+ kind: outcome.reason,
51
+ summary: outcome.summary
52
+ };
53
+ if (!outcome.result.opened) return {
54
+ ok: false,
55
+ kind: 'job-error',
56
+ summary: outcome.result.error ?? '登录入口打开失败'
57
+ };
58
+ return {
59
+ ok: true,
60
+ opened: true
61
+ };
62
+ }
63
+ export async function extensionSearchJob(q) {
64
+ const bridge = await getOrCreateSessionBridge();
65
+ if (!bridgeReady(bridge)) return {
66
+ ok: false,
67
+ kind: 'bridge-unavailable',
68
+ summary: bridge.summary
69
+ };
70
+ const timeoutMs = q.timeoutMs ?? 30_000;
71
+ const outcome = await bridge.bridge.submit({
72
+ kind: 'search',
73
+ site: q.site,
74
+ url: q.url,
75
+ timeoutMs
76
+ }, {
77
+ timeoutMs: timeoutMs + 10_000
78
+ });
79
+ if (!outcome.ok) return {
80
+ ok: false,
81
+ kind: outcome.reason,
82
+ summary: outcome.summary
83
+ };
84
+ return {
85
+ ok: true,
86
+ body: typeof outcome.result.body === 'string' ? outcome.result.body : '',
87
+ title: typeof outcome.result.title === 'string' ? outcome.result.title : '',
88
+ timedOut: outcome.result.timeout === true
89
+ };
90
+ }
91
+
92
+
93
+ //# sourceURL=ts/capabilities/session/extension-channel.ts