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

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 (139) hide show
  1. package/README.md +10 -6
  2. package/README.zh-CN.md +10 -6
  3. package/bin/gotry-bootstrap.js +294 -5
  4. package/cordis.gotry-patch.yml +10 -0
  5. package/data/airline-airports.json +39 -0
  6. package/dist/capabilities/agent-reach-deep.js +1 -1
  7. package/dist/capabilities/agent-reach.js +1 -1
  8. package/dist/capabilities/anything.js +1 -1
  9. package/dist/capabilities/artifacts.js +1 -1
  10. package/dist/capabilities/effect.js +293 -0
  11. package/dist/capabilities/fact-log.js +41 -0
  12. package/dist/capabilities/flyai.js +1 -1
  13. package/dist/capabilities/hbcli.js +43 -1
  14. package/dist/capabilities/incident-log.js +1 -1
  15. package/dist/capabilities/opensky.js +1 -1
  16. package/dist/capabilities/resilience.js +90 -0
  17. package/dist/capabilities/session/action-cache.js +1 -1
  18. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  19. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  20. package/dist/capabilities/session/benchmark.js +2 -1
  21. package/dist/capabilities/session/extension-bridge.js +345 -0
  22. package/dist/capabilities/session/extension-channel.js +93 -0
  23. package/dist/capabilities/session/extract.js +1 -1
  24. package/dist/capabilities/session/health-watch.js +216 -0
  25. package/dist/capabilities/session/read-guard.js +1 -1
  26. package/dist/capabilities/session/transport.js +1 -1
  27. package/dist/capabilities/session/wizard.js +377 -0
  28. package/dist/capabilities/session-consent.js +1 -1
  29. package/dist/capabilities/session-login.js +77 -1
  30. package/dist/capabilities/session-search.js +69 -2
  31. package/dist/capabilities/weather.js +1 -1
  32. package/dist/scripts/action-cache-tests.js +1 -1
  33. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  34. package/dist/scripts/agent-reach-tests.js +1 -1
  35. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  36. package/dist/scripts/anything-tests.js +1 -1
  37. package/dist/scripts/async-collect.js +1 -1
  38. package/dist/scripts/booking-saga-tests.js +1 -1
  39. package/dist/scripts/bootstrap-tests.js +39 -3
  40. package/dist/scripts/build-changelog.js +279 -0
  41. package/dist/scripts/changelog-tests.js +165 -0
  42. package/dist/scripts/companion-tests.js +1 -1
  43. package/dist/scripts/diff-test.js +1 -1
  44. package/dist/scripts/effect-tests.js +474 -0
  45. package/dist/scripts/engine-run.js +1 -1
  46. package/dist/scripts/engine-tests.js +1 -1
  47. package/dist/scripts/extension-tests.js +468 -0
  48. package/dist/scripts/fact-gate-tests.js +318 -0
  49. package/dist/scripts/flyai-tests.js +1 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  51. package/dist/scripts/hbcli-tests.js +1 -1
  52. package/dist/scripts/health-watch-cli.js +51 -0
  53. package/dist/scripts/i18n-tests.js +1 -1
  54. package/dist/scripts/incident-tests.js +1 -1
  55. package/dist/scripts/journey-tests.js +1 -1
  56. package/dist/scripts/ledger-tests.js +1 -1
  57. package/dist/scripts/ledger-workflow-crash.js +1 -1
  58. package/dist/scripts/memory-capture-tests.js +1 -1
  59. package/dist/scripts/memory-decay-tests.js +1 -1
  60. package/dist/scripts/memory-metrics.js +1 -1
  61. package/dist/scripts/memory-value-report.js +1 -1
  62. package/dist/scripts/nightly-evidence-tests.js +67 -4
  63. package/dist/scripts/nightly-evidence.js +59 -3
  64. package/dist/scripts/nudge-digest.js +1 -1
  65. package/dist/scripts/onboarding-tests.js +218 -0
  66. package/dist/scripts/opensky-check.js +1 -1
  67. package/dist/scripts/opensky-tests.js +1 -1
  68. package/dist/scripts/price-drift-tests.js +441 -0
  69. package/dist/scripts/price-drift-watch.js +491 -0
  70. package/dist/scripts/probe-poi-tests.js +1 -1
  71. package/dist/scripts/product-metrics.js +1 -1
  72. package/dist/scripts/publish-preverify.js +1 -1
  73. package/dist/scripts/realtime-pricing-tests.js +1 -1
  74. package/dist/scripts/replay-async.js +1 -1
  75. package/dist/scripts/replay-real.js +1 -1
  76. package/dist/scripts/replay.js +1 -1
  77. package/dist/scripts/session-attach-diagnose.js +1 -1
  78. package/dist/scripts/session-attach-poc.js +1 -1
  79. package/dist/scripts/session-benchmark.js +1 -1
  80. package/dist/scripts/session-extract-tests.js +1 -1
  81. package/dist/scripts/session-login.js +1 -1
  82. package/dist/scripts/session-tests.js +1 -1
  83. package/dist/scripts/skeleton-check.js +1 -1
  84. package/dist/scripts/skeleton-integration-test.js +1 -1
  85. package/dist/scripts/skills-contract-tests.js +1 -1
  86. package/dist/scripts/smoke.js +76 -1
  87. package/dist/scripts/state-cli-tests.js +1 -1
  88. package/dist/scripts/state-cli.js +1 -1
  89. package/dist/scripts/time-eval-tests.js +1 -1
  90. package/dist/scripts/travel-timeline-tests.js +1 -1
  91. package/dist/scripts/unified-tests.js +1 -1
  92. package/dist/scripts/weather-tests.js +1 -1
  93. package/dist/scripts/wizard-bootstrap.js +32 -0
  94. package/dist/scripts/z3-race-tests.js +1 -1
  95. package/dist/src/artifact-gate.js +310 -0
  96. package/dist/src/bookable-facts.js +349 -0
  97. package/dist/src/booking-saga.js +1 -1
  98. package/dist/src/bridge.js +1 -1
  99. package/dist/src/companions.js +1 -1
  100. package/dist/src/contracts.js +1 -1
  101. package/dist/src/dsh-llm.js +1 -1
  102. package/dist/src/engine.js +1 -1
  103. package/dist/src/i18n.js +1 -1
  104. package/dist/src/index.js +189 -46
  105. package/dist/src/journey.js +1 -1
  106. package/dist/src/loop.js +1 -1
  107. package/dist/src/memory-capture.js +1 -1
  108. package/dist/src/memory-decay.js +1 -1
  109. package/dist/src/memory-utility.js +1 -1
  110. package/dist/src/mock-llm.js +1 -1
  111. package/dist/src/model.js +1 -1
  112. package/dist/src/realtime-pricing.js +14 -7
  113. package/dist/src/slot-spec.js +1 -1
  114. package/dist/src/state-ledger.js +1 -1
  115. package/dist/src/time-anchor.js +1 -1
  116. package/dist/src/tool-packet.js +1 -1
  117. package/dist/src/travel-slots.js +1 -1
  118. package/dist/src/travel-timeline.js +1 -1
  119. package/dist/src/unified.js +1 -1
  120. package/dist/src/wish-pool.js +1 -1
  121. package/dist/src/z3-shared.js +1 -1
  122. package/extension/README.md +26 -0
  123. package/extension/background.js +160 -0
  124. package/extension/content-bridge.js +41 -0
  125. package/extension/content-main.js +62 -0
  126. package/extension/manifest.json +32 -0
  127. package/package.json +6 -2
  128. package/ts/capabilities/fact-log.ts +54 -0
  129. package/ts/capabilities/hbcli.ts +46 -0
  130. package/ts/capabilities/session/benchmark.ts +4 -1
  131. package/ts/capabilities/session/extension-bridge.ts +354 -0
  132. package/ts/capabilities/session/extension-channel.ts +92 -0
  133. package/ts/capabilities/session/health-watch.ts +257 -0
  134. package/ts/capabilities/session/wizard.ts +282 -0
  135. package/ts/capabilities/session-login.ts +53 -2
  136. package/ts/capabilities/session-search.ts +68 -3
  137. package/ts/src/artifact-gate.ts +365 -0
  138. package/ts/src/bookable-facts.ts +557 -0
  139. package/ts/src/index.ts +102 -8
@@ -0,0 +1,345 @@
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_CONNECTED_WINDOW_MS = 45_000;
14
+ export const JOBS_LONG_POLL_MS = 20_000;
15
+ export const DEFAULT_EXTENSION_WAIT_MS = 6_000;
16
+ export const MAX_RESULT_BODY_BYTES = 8 * 1024 * 1024;
17
+ function readBody(req, cap) {
18
+ return new Promise((resolve)=>{
19
+ const chunks = [];
20
+ let size = 0;
21
+ let done = false;
22
+ req.on('data', (c)=>{
23
+ if (done) return;
24
+ size += c.length;
25
+ if (size > cap) {
26
+ done = true;
27
+ req.removeAllListeners('data');
28
+ resolve({
29
+ err: `body 超 ${cap} 字节上限(防失控回包)`
30
+ });
31
+ return;
32
+ }
33
+ chunks.push(c);
34
+ });
35
+ req.on('end', ()=>{
36
+ if (!done) {
37
+ done = true;
38
+ resolve(Buffer.concat(chunks).toString('utf8'));
39
+ }
40
+ });
41
+ req.on('error', ()=>{
42
+ if (!done) {
43
+ done = true;
44
+ resolve({
45
+ err: 'body 读取失败'
46
+ });
47
+ }
48
+ });
49
+ });
50
+ }
51
+ export async function createSessionBridge(opts = {}) {
52
+ const ports = opts.ports ?? [
53
+ ...BRIDGE_PORTS
54
+ ];
55
+ const extensionOrigin = opts.extensionOrigin ?? EXTENSION_ORIGIN;
56
+ const now = opts.now ?? Date.now;
57
+ const queue = [];
58
+ const inFlight = new Map();
59
+ const parked = [];
60
+ let lastSeenAt = 0;
61
+ let closed = false;
62
+ let port = 0;
63
+ const extensionConnected = ()=>lastSeenAt > 0 && now() - lastSeenAt < EXTENSION_CONNECTED_WINDOW_MS;
64
+ function takeQueued() {
65
+ const next = queue.shift();
66
+ if (next) inFlight.set(next.job.jobId, next);
67
+ return next ?? null;
68
+ }
69
+ const server = createServer((req, res)=>{
70
+ if (closed) {
71
+ res.statusCode = 503;
72
+ res.end();
73
+ return;
74
+ }
75
+ const urlPath = (req.url ?? '').split('?')[0];
76
+ const origin = req.headers.origin;
77
+ const isPost = req.method === 'POST';
78
+ if (isPost && origin !== extensionOrigin) {
79
+ res.statusCode = 403;
80
+ res.setHeader('content-type', 'application/json');
81
+ res.end(JSON.stringify({
82
+ ok: false,
83
+ error: 'origin 不在桥白名单'
84
+ }));
85
+ return;
86
+ }
87
+ if (origin === extensionOrigin) {
88
+ lastSeenAt = now();
89
+ }
90
+ const finish = (code, body)=>{
91
+ res.statusCode = code;
92
+ res.setHeader('content-type', 'application/json');
93
+ res.end(JSON.stringify(body));
94
+ };
95
+ if (req.method === 'GET' && urlPath === '/health') {
96
+ finish(200, {
97
+ ok: true,
98
+ service: 'gotry-session-bridge',
99
+ protocol: BRIDGE_PROTOCOL
100
+ });
101
+ return;
102
+ }
103
+ if (req.method === 'GET' && urlPath === '/status') {
104
+ finish(200, {
105
+ ok: true,
106
+ port,
107
+ protocol: BRIDGE_PROTOCOL,
108
+ extensionConnected: extensionConnected(),
109
+ lastSeenMsAgo: lastSeenAt > 0 ? now() - lastSeenAt : null,
110
+ queued: queue.length,
111
+ inFlight: inFlight.size
112
+ });
113
+ return;
114
+ }
115
+ if (isPost && urlPath === '/jobs') {
116
+ const next = takeQueued();
117
+ if (next) {
118
+ finish(200, {
119
+ job: next.job
120
+ });
121
+ return;
122
+ }
123
+ const parkTimer = setTimeout(()=>{
124
+ const i = parked.findIndex((p)=>p.res === res);
125
+ if (i >= 0) parked.splice(i, 1);
126
+ finish(200, {
127
+ job: null
128
+ });
129
+ }, JOBS_LONG_POLL_MS);
130
+ parked.push({
131
+ res,
132
+ timer: parkTimer
133
+ });
134
+ res.on('close', ()=>{
135
+ const i = parked.findIndex((p)=>p.res === res);
136
+ if (i >= 0) parked.splice(i, 1);
137
+ clearTimeout(parkTimer);
138
+ });
139
+ return;
140
+ }
141
+ if (isPost && urlPath.startsWith('/results/')) {
142
+ const jobId = decodeURIComponent(urlPath.slice('/results/'.length));
143
+ void readBody(req, MAX_RESULT_BODY_BYTES).then((body)=>{
144
+ if (typeof body !== 'string') {
145
+ finish(400, {
146
+ ok: false,
147
+ error: body.err
148
+ });
149
+ return;
150
+ }
151
+ let parsed;
152
+ try {
153
+ parsed = JSON.parse(body);
154
+ } catch {
155
+ finish(400, {
156
+ ok: false,
157
+ error: '回包不是 JSON'
158
+ });
159
+ return;
160
+ }
161
+ finish(200, {
162
+ ok: true
163
+ });
164
+ resolveJob(jobId, parsed);
165
+ });
166
+ return;
167
+ }
168
+ finish(404, {
169
+ ok: false,
170
+ error: 'unknown endpoint'
171
+ });
172
+ });
173
+ function resolveJob(jobId, parsed) {
174
+ const inFlightHit = inFlight.get(jobId);
175
+ if (inFlightHit) {
176
+ inFlight.delete(jobId);
177
+ if (inFlightHit.timer) clearTimeout(inFlightHit.timer);
178
+ inFlightHit.resolve({
179
+ ok: true,
180
+ result: parsed
181
+ });
182
+ return;
183
+ }
184
+ const idx = queue.findIndex((q)=>q.job.jobId === jobId);
185
+ if (idx < 0) return;
186
+ const [entry] = queue.splice(idx, 1);
187
+ if (entry.timer) clearTimeout(entry.timer);
188
+ entry.resolve({
189
+ ok: true,
190
+ result: parsed
191
+ });
192
+ }
193
+ function dispatchToParked(entry) {
194
+ const p = parked.shift();
195
+ if (!p) return false;
196
+ clearTimeout(p.timer);
197
+ inFlight.set(entry.job.jobId, entry);
198
+ p.res.statusCode = 200;
199
+ p.res.setHeader('content-type', 'application/json');
200
+ p.res.end(JSON.stringify({
201
+ job: entry.job
202
+ }));
203
+ return true;
204
+ }
205
+ const bridge = {
206
+ port: 0,
207
+ extensionConnected,
208
+ submit (job, submitOpts = {}) {
209
+ const extensionWaitMs = submitOpts.extensionWaitMs ?? DEFAULT_EXTENSION_WAIT_MS;
210
+ const timeoutMs = submitOpts.timeoutMs ?? job.timeoutMs ?? 30_000;
211
+ const full = {
212
+ jobId: randomUUID(),
213
+ ...job
214
+ };
215
+ return new Promise((resolve)=>{
216
+ if (closed) {
217
+ resolve({
218
+ ok: false,
219
+ reason: 'bridge-unavailable',
220
+ summary: '扩展桥已关闭'
221
+ });
222
+ return;
223
+ }
224
+ const entry = {
225
+ job: full,
226
+ resolve: null,
227
+ timer: null
228
+ };
229
+ const settle = (outcome)=>{
230
+ if (entry.timer) clearTimeout(entry.timer);
231
+ const i = queue.findIndex((q)=>q.job.jobId === full.jobId);
232
+ if (i >= 0) queue.splice(i, 1);
233
+ inFlight.delete(full.jobId);
234
+ resolve(outcome);
235
+ };
236
+ entry.resolve = settle;
237
+ entry.timer = setTimeout(()=>{
238
+ settle({
239
+ ok: false,
240
+ reason: 'timeout',
241
+ summary: `扩展未在 ${timeoutMs}ms 内回包(标签页无嗅探命中或扩展已停用)`
242
+ });
243
+ }, timeoutMs);
244
+ if (!dispatchToParked(entry)) queue.push(entry);
245
+ let waited = 0;
246
+ const waitTimer = setInterval(()=>{
247
+ waited += 250;
248
+ if (bridge.extensionConnected()) {
249
+ clearInterval(waitTimer);
250
+ return;
251
+ }
252
+ if (waited >= extensionWaitMs) {
253
+ clearInterval(waitTimer);
254
+ settle({
255
+ ok: false,
256
+ reason: 'extension-not-connected',
257
+ summary: 'GoTry Session Bridge 扩展未连接(未安装或已停用)。一次性安装:npx gotry setup 查看指引 → chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension'
258
+ });
259
+ }
260
+ }, 250);
261
+ });
262
+ },
263
+ close: ()=>new Promise((resolve)=>{
264
+ closed = true;
265
+ const voided = {
266
+ ok: false,
267
+ reason: 'bridge-unavailable',
268
+ summary: '扩展桥关闭,任务作废(零花费)'
269
+ };
270
+ for (const q of queue.splice(0)){
271
+ if (q.timer) clearTimeout(q.timer);
272
+ q.resolve(voided);
273
+ }
274
+ for (const [, q] of inFlight){
275
+ if (q.timer) clearTimeout(q.timer);
276
+ q.resolve(voided);
277
+ }
278
+ inFlight.clear();
279
+ for (const p of parked.splice(0)){
280
+ clearTimeout(p.timer);
281
+ p.res.destroy();
282
+ }
283
+ server.close(()=>resolve());
284
+ server.closeAllConnections?.();
285
+ })
286
+ };
287
+ const listenAt = (p)=>new Promise((resolve, reject)=>{
288
+ const onError = (e)=>{
289
+ server.off('listening', onListening);
290
+ reject(e);
291
+ };
292
+ const onListening = ()=>{
293
+ server.off('error', onError);
294
+ resolve();
295
+ };
296
+ server.once('error', onError);
297
+ server.once('listening', onListening);
298
+ server.listen(p, '127.0.0.1');
299
+ });
300
+ for (const p of ports){
301
+ try {
302
+ await listenAt(p);
303
+ if (!opts.keepBridge) {
304
+ server.unref();
305
+ }
306
+ port = server.address().port;
307
+ bridge.port = port;
308
+ return {
309
+ ok: true,
310
+ bridge
311
+ };
312
+ } catch {}
313
+ }
314
+ return {
315
+ ok: false,
316
+ summary: `扩展桥端口池 ${ports.join('/')} 全部被占(多为并行 gotry 实例);不影响官方通道 gotry_flyai_search。关闭多余实例后重试。`
317
+ };
318
+ }
319
+ let singleton = null;
320
+ export async function getOrCreateSessionBridge(opts = {}) {
321
+ if (singleton) return singleton;
322
+ const created = await createSessionBridge(opts);
323
+ const result = created.ok ? {
324
+ ok: true,
325
+ bridge: created.bridge
326
+ } : {
327
+ ok: false,
328
+ summary: created.summary
329
+ };
330
+ singleton = result;
331
+ return result;
332
+ }
333
+ export async function __resetSessionBridgeForTest() {
334
+ if (singleton?.ok) await singleton.bridge.close().catch(()=>{});
335
+ singleton = null;
336
+ }
337
+ export function __setSessionBridgeForTest(bridge) {
338
+ singleton = bridge ? {
339
+ ok: true,
340
+ bridge
341
+ } : null;
342
+ }
343
+
344
+
345
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/extension-bridge.ts
@@ -0,0 +1,93 @@
1
+ import { getOrCreateSessionBridge } from './extension-bridge.js';
2
+ export const NEEDS_EXTENSION_HINT = '需要一次性安装 GoTry Session Bridge 浏览器扩展(约 30 秒,装完零弹窗):跑 npx gotry setup 拿指引,' + '或在 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension';
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=/Users/bytedance/work/gotry-publish/ts/capabilities/session/extension-channel.ts
@@ -37,4 +37,4 @@ export function nameAffinity(a, b) {
37
37
  }
38
38
 
39
39
 
40
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/extract.ts
40
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/extract.ts
@@ -0,0 +1,216 @@
1
+ import { request } from 'node:http';
2
+ import { BRIDGE_PORTS, EXTENSION_CONNECTED_WINDOW_MS } from './extension-bridge.js';
3
+ export async function combinedProbe(opts, businessProbe) {
4
+ if (!await defaultProbe(opts)) return false;
5
+ if (!businessProbe) return true;
6
+ try {
7
+ const channel = await import('./extension-channel.js');
8
+ const bridge = await import('./extension-bridge.js');
9
+ bridge.getOrCreateSessionBridge({
10
+ keepBridge: opts.keepBridge === true
11
+ });
12
+ const cookie = await channel.extensionCookieNames({
13
+ site: 'ctrip-flight',
14
+ domain: 'ctrip.com',
15
+ ticketNames: [
16
+ 'cticket',
17
+ 'uid'
18
+ ],
19
+ timeoutMs: 8_000
20
+ });
21
+ return cookie.ok;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+ export async function defaultProbe(opts) {
27
+ const ports = opts.ports ?? [
28
+ ...BRIDGE_PORTS
29
+ ];
30
+ for (const port of ports){
31
+ const ok = await new Promise((resolve)=>{
32
+ const req = request({
33
+ host: '127.0.0.1',
34
+ port,
35
+ path: '/status',
36
+ method: 'GET',
37
+ timeout: 2_000
38
+ }, (res)=>{
39
+ if (!res.statusCode || res.statusCode >= 400) {
40
+ resolve(false);
41
+ return;
42
+ }
43
+ let buf = '';
44
+ res.setEncoding('utf8');
45
+ res.on('data', (c)=>{
46
+ buf += c;
47
+ });
48
+ res.on('end', ()=>{
49
+ try {
50
+ const payload = JSON.parse(buf);
51
+ resolve(payload.extensionConnected === true);
52
+ } catch {
53
+ resolve(false);
54
+ }
55
+ });
56
+ res.on('error', ()=>resolve(false));
57
+ });
58
+ req.on('error', ()=>resolve(false));
59
+ req.on('timeout', ()=>{
60
+ req.destroy();
61
+ resolve(false);
62
+ });
63
+ req.end();
64
+ });
65
+ if (ok) return true;
66
+ }
67
+ return false;
68
+ }
69
+ export function startExtensionHealthWatch(opts = {}) {
70
+ const timeoutMs = opts.timeoutMs ?? 120_000;
71
+ const intervalMs = opts.intervalMs ?? 5_000;
72
+ const maxRetries = opts.maxRetries ?? 1;
73
+ const businessProbe = opts.businessProbe ?? true;
74
+ const probe = opts.probe ?? (async ()=>{
75
+ const bridge = await import('./extension-bridge.js');
76
+ await bridge.getOrCreateSessionBridge({
77
+ keepBridge: opts.keepBridge === true
78
+ });
79
+ return combinedProbe(opts, businessProbe);
80
+ });
81
+ const now = opts.now ?? Date.now;
82
+ const state = {
83
+ cancelled: false,
84
+ readyCount: 0,
85
+ resolveOutcome: null
86
+ };
87
+ const finish = (outcome)=>{
88
+ if (state.resolveOutcome) {
89
+ const resolve = state.resolveOutcome;
90
+ state.resolveOutcome = null;
91
+ resolve(outcome);
92
+ }
93
+ };
94
+ const onAbort = ()=>{
95
+ state.cancelled = true;
96
+ finish({
97
+ ready: false,
98
+ reason: 'cancelled',
99
+ attempts: state.readyCount,
100
+ waitedMs: 0
101
+ });
102
+ };
103
+ opts.signal?.addEventListener('abort', onAbort, {
104
+ once: true
105
+ });
106
+ const waitReady = ()=>new Promise((resolve)=>{
107
+ if (state.cancelled) {
108
+ resolve({
109
+ ready: false,
110
+ reason: 'cancelled',
111
+ attempts: 0,
112
+ waitedMs: 0
113
+ });
114
+ return;
115
+ }
116
+ if (timeoutMs === 0) {
117
+ resolve({
118
+ ready: false,
119
+ reason: 'timeout',
120
+ attempts: 0,
121
+ waitedMs: 0
122
+ });
123
+ return;
124
+ }
125
+ state.resolveOutcome = resolve;
126
+ const deadline = now() + timeoutMs;
127
+ let attempts = 0;
128
+ let waitedMs = 0;
129
+ const tick = async ()=>{
130
+ if (state.cancelled || state.resolveOutcome === null) return;
131
+ attempts += 1;
132
+ try {
133
+ const ready = await probe();
134
+ if (ready && !state.cancelled) {
135
+ state.readyCount += 1;
136
+ waitedMs = timeoutMs - Math.max(0, deadline - now());
137
+ if (state.readyCount <= maxRetries) {
138
+ finish({
139
+ ready: true,
140
+ attempts,
141
+ waitedMs: waitedMs > 0 ? waitedMs : 0
142
+ });
143
+ return;
144
+ }
145
+ }
146
+ } catch {}
147
+ waitedMs = timeoutMs - Math.max(0, deadline - now());
148
+ if (now() >= deadline) {
149
+ finish({
150
+ ready: false,
151
+ reason: 'timeout',
152
+ attempts,
153
+ waitedMs
154
+ });
155
+ return;
156
+ }
157
+ const remaining = deadline - now();
158
+ const delay = Math.min(intervalMs, Math.max(0, remaining));
159
+ if (delay <= 0) {
160
+ finish({
161
+ ready: false,
162
+ reason: 'timeout',
163
+ attempts,
164
+ waitedMs
165
+ });
166
+ return;
167
+ }
168
+ setTimeout(tick, delay);
169
+ };
170
+ void tick();
171
+ });
172
+ return {
173
+ waitReady,
174
+ cancel: ()=>onAbort(),
175
+ get readyCount () {
176
+ return state.readyCount;
177
+ }
178
+ };
179
+ }
180
+ export async function withAutoRetryOnExtension(args) {
181
+ const first = await args.fetch();
182
+ if (!args.isExtensionNeeded(first)) {
183
+ return {
184
+ result: first,
185
+ retried: false,
186
+ attempts: 1,
187
+ outcome: {
188
+ ready: true,
189
+ attempts: 0,
190
+ waitedMs: 0
191
+ }
192
+ };
193
+ }
194
+ const watch = startExtensionHealthWatch(args.watch ?? {});
195
+ const outcome = await watch.waitReady();
196
+ if (!outcome.ready) {
197
+ return {
198
+ result: first,
199
+ retried: false,
200
+ attempts: 1,
201
+ outcome
202
+ };
203
+ }
204
+ const retriedResult = await (args.retry ?? args.fetch)();
205
+ return {
206
+ result: retriedResult,
207
+ retried: true,
208
+ attempts: 2,
209
+ outcome
210
+ };
211
+ }
212
+ export const HEALTH_WATCH_INTERVAL_FLOOR_MS = 1_000;
213
+ export const HEALTH_WATCH_INTERVAL_CEIL_MS = EXTENSION_CONNECTED_WINDOW_MS - 1_000;
214
+
215
+
216
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/health-watch.ts
@@ -96,4 +96,4 @@ export async function attachReadGuardPuppeteer(browser, auditPath) {
96
96
  }
97
97
 
98
98
 
99
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/read-guard.ts
99
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/read-guard.ts
@@ -113,4 +113,4 @@ export async function openSession(opts = {}) {
113
113
  }
114
114
 
115
115
 
116
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/transport.ts
116
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/transport.ts