@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,377 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
3
+ import { homedir, platform } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const DEFAULT_SOURCE_DIR_CANDIDATES = [
6
+ join(process.cwd(), 'extension'),
7
+ join(process.cwd(), 'dist', 'extension'),
8
+ join(process.cwd(), 'ts', '..', 'extension')
9
+ ];
10
+ function resolveExtensionSourceDir() {
11
+ for (const candidate of DEFAULT_SOURCE_DIR_CANDIDATES){
12
+ if (existsSync(join(candidate, 'manifest.json'))) return candidate;
13
+ }
14
+ return DEFAULT_SOURCE_DIR_CANDIDATES[0];
15
+ }
16
+ export function defaultExtensionDir() {
17
+ return join(homedir(), '.gotry', 'extension');
18
+ }
19
+ function detectPlatform(force) {
20
+ if (force) return force;
21
+ if (process.env.GOTRY_ONBOARDING_HEADLESS === '1') return 'headless';
22
+ const p = platform();
23
+ if (p === 'darwin' || p === 'linux' || p === 'win32') {
24
+ if (p !== 'darwin' && !process.env.DISPLAY) return 'headless';
25
+ return p;
26
+ }
27
+ return 'headless';
28
+ }
29
+ function ensureExtensionFiles(source, target, dryRun) {
30
+ if (dryRun) return {
31
+ step: 'ensure-extension-files',
32
+ status: 'skip',
33
+ summary: '[dry-run] skip 扩展落位'
34
+ };
35
+ try {
36
+ if (!existsSync(source) || !existsSync(join(source, 'manifest.json'))) {
37
+ return {
38
+ step: 'ensure-extension-files',
39
+ status: 'fail',
40
+ summary: `源扩展目录缺失 manifest.json: ${source}(先跑 npx gotry setup 落位)`
41
+ };
42
+ }
43
+ mkdirSync(target, {
44
+ recursive: true
45
+ });
46
+ const required = [
47
+ 'manifest.json',
48
+ 'background.js',
49
+ 'content-main.js',
50
+ 'content-bridge.js'
51
+ ];
52
+ for (const f of required){
53
+ const src = join(source, f);
54
+ if (!existsSync(src)) return {
55
+ step: 'ensure-extension-files',
56
+ status: 'fail',
57
+ summary: `源扩展缺 ${f}(重新 npm install 或 gotry setup)`
58
+ };
59
+ copyFileSync(src, join(target, f));
60
+ }
61
+ return {
62
+ step: 'ensure-extension-files',
63
+ status: 'ok',
64
+ summary: `扩展已落位 ${target}(manifest+3 文件)`
65
+ };
66
+ } catch (e) {
67
+ return {
68
+ step: 'ensure-extension-files',
69
+ status: 'fail',
70
+ summary: `扩展落位失败: ${e instanceof Error ? e.message.slice(0, 120) : String(e)}`
71
+ };
72
+ }
73
+ }
74
+ function openChromeExtensions(sp, plat, dryRun) {
75
+ if (dryRun) return {
76
+ step: 'open-chrome-extensions',
77
+ status: 'skip',
78
+ summary: '[dry-run] skip 打开 chrome://extensions'
79
+ };
80
+ if (plat === 'headless') return {
81
+ step: 'open-chrome-extensions',
82
+ status: 'skip',
83
+ summary: 'headless 环境跳过 GUI;请在你桌面 Chrome 手动打开 chrome://extensions'
84
+ };
85
+ const cmd = (()=>{
86
+ if (plat === 'darwin') return {
87
+ bin: 'open',
88
+ args: [
89
+ '-a',
90
+ 'Google Chrome',
91
+ 'chrome://extensions'
92
+ ]
93
+ };
94
+ if (plat === 'linux') return {
95
+ bin: 'xdg-open',
96
+ args: [
97
+ 'chrome://extensions'
98
+ ]
99
+ };
100
+ return {
101
+ bin: 'cmd',
102
+ args: [
103
+ '/c',
104
+ 'start',
105
+ 'chrome',
106
+ 'chrome://extensions'
107
+ ]
108
+ };
109
+ })();
110
+ try {
111
+ const child = sp(cmd.bin, cmd.args, {
112
+ detached: true,
113
+ stdio: 'ignore'
114
+ });
115
+ child.unref?.();
116
+ return {
117
+ step: 'open-chrome-extensions',
118
+ status: 'ok',
119
+ summary: `已打开 chrome://extensions(${plat})`
120
+ };
121
+ } catch (e) {
122
+ return {
123
+ step: 'open-chrome-extensions',
124
+ status: 'fail',
125
+ summary: `自动打开失败: ${e instanceof Error ? e.message.slice(0, 80) : String(e)};请手开 chrome://extensions`
126
+ };
127
+ }
128
+ }
129
+ async function clipboardExtensionPath(sp, plat, path, dryRun) {
130
+ if (dryRun) return {
131
+ step: 'clipboard-extension-path',
132
+ status: 'skip',
133
+ summary: '[dry-run] skip 剪贴板复制'
134
+ };
135
+ if (plat === 'headless') return {
136
+ step: 'clipboard-extension-path',
137
+ status: 'skip',
138
+ summary: `headless 无剪贴板;扩展目录路径:${path}`
139
+ };
140
+ const cmd = (()=>{
141
+ if (plat === 'darwin') return {
142
+ bin: 'pbcopy',
143
+ args: []
144
+ };
145
+ if (plat === 'linux') return {
146
+ bin: 'xclip',
147
+ args: [
148
+ '-selection',
149
+ 'clipboard'
150
+ ]
151
+ };
152
+ return {
153
+ bin: 'clip',
154
+ args: []
155
+ };
156
+ })();
157
+ return new Promise((resolveStep)=>{
158
+ let child;
159
+ try {
160
+ child = sp(cmd.bin, cmd.args, {
161
+ stdio: [
162
+ 'pipe',
163
+ 'ignore',
164
+ 'ignore'
165
+ ]
166
+ });
167
+ } catch {
168
+ resolveStep({
169
+ step: 'clipboard-extension-path',
170
+ status: 'skip',
171
+ summary: `剪贴板工具不可用(${cmd.bin});扩展目录路径手动复制: ${path}`
172
+ });
173
+ return;
174
+ }
175
+ let settled = false;
176
+ const settle = (s)=>{
177
+ if (settled) return;
178
+ settled = true;
179
+ resolveStep(s);
180
+ };
181
+ child.on('error', ()=>settle({
182
+ step: 'clipboard-extension-path',
183
+ status: 'skip',
184
+ summary: `剪贴板工具启动失败(${cmd.bin});扩展目录路径手动复制: ${path}`
185
+ }));
186
+ child.on('exit', (code)=>{
187
+ if (code === 0) settle({
188
+ step: 'clipboard-extension-path',
189
+ status: 'ok',
190
+ summary: `已复制扩展目录到剪贴板: ${path}`
191
+ });
192
+ else settle({
193
+ step: 'clipboard-extension-path',
194
+ status: 'fail',
195
+ summary: `剪贴板工具退出码 ${code}(${cmd.bin});扩展目录路径手动复制: ${path}`
196
+ });
197
+ });
198
+ try {
199
+ child.stdin?.write(path);
200
+ child.stdin?.end();
201
+ } catch {
202
+ settle({
203
+ step: 'clipboard-extension-path',
204
+ status: 'skip',
205
+ summary: `剪贴板工具 stdin 不可写(${cmd.bin});扩展目录路径手动复制: ${path}`
206
+ });
207
+ }
208
+ });
209
+ }
210
+ function panelGuideTerminal(plat, extensionDir, watchMs) {
211
+ const lines = [
212
+ '┌─ GoTry 扩展安装 ─────────────────────────────────┐',
213
+ '│ 共 3 步,约 30 秒,装完零弹窗 │',
214
+ '│ │',
215
+ '│ ① 在 Chrome 右上角开启「开发者模式」 │',
216
+ `│ ② 点「加载已解压的扩展程序」选这个目录(已复制): │`,
217
+ `│ ${extensionDir}`,
218
+ '│ ③ 弹窗「添加扩展」点「添加」 │',
219
+ `│ │`,
220
+ `│ 后台探活中(最长 ${Math.round(watchMs / 1000)}s)… │`,
221
+ '└───────────────────────────────────────────────────┘'
222
+ ];
223
+ if (plat === 'headless') {
224
+ process.stdout.write(lines.join('\n') + '\n');
225
+ return {
226
+ step: 'panel-guide',
227
+ status: 'skip',
228
+ summary: 'headless 终端面板已打印'
229
+ };
230
+ }
231
+ process.stdout.write(lines.join('\n') + '\n');
232
+ return {
233
+ step: 'panel-guide',
234
+ status: 'skip',
235
+ summary: '终端面板已打印(GUI 面板降级)'
236
+ };
237
+ }
238
+ function panelGuideGui(sp, plat, extensionDir, dryRun) {
239
+ if (dryRun) return {
240
+ step: 'panel-guide',
241
+ status: 'skip',
242
+ summary: '[dry-run] skip GUI 面板'
243
+ };
244
+ if (plat === 'headless') return panelGuideTerminal(plat, extensionDir, 120_000);
245
+ const message = `在 Chrome 右上角开启「开发者模式」 → 点「加载已解压的扩展程序」 → 选择(已复制):${extensionDir} → 弹窗点「添加扩展」`;
246
+ try {
247
+ if (plat === 'darwin') {
248
+ const script = `display dialog "${message.replace(/"/g, '\\"')}" with title "GoTry 扩展安装" buttons {"我装好了"} default button 1`;
249
+ sp('osascript', [
250
+ '-e',
251
+ script
252
+ ], {
253
+ stdio: 'ignore',
254
+ detached: true
255
+ }).unref?.();
256
+ return {
257
+ step: 'panel-guide',
258
+ status: 'ok',
259
+ summary: '已弹 macOS Cocoa 引导面板'
260
+ };
261
+ }
262
+ if (plat === 'linux') {
263
+ const child = sp('zenity', [
264
+ '--info',
265
+ '--title=GoTry 扩展安装',
266
+ `--text=${message}`
267
+ ], {
268
+ stdio: 'ignore',
269
+ detached: true
270
+ });
271
+ child.unref?.();
272
+ child.on('error', ()=>{
273
+ process.stdout.write(`(zenity 不可用,走终端面板)扩展目录: ${extensionDir}\n`);
274
+ });
275
+ return {
276
+ step: 'panel-guide',
277
+ status: 'ok',
278
+ summary: '已弹 zenity 引导面板'
279
+ };
280
+ }
281
+ return panelGuideTerminal(plat, extensionDir, 120_000);
282
+ } catch (e) {
283
+ return panelGuideTerminal(plat, extensionDir, 120_000);
284
+ }
285
+ }
286
+ async function watchExtensionReady(dryRun, sourceDir, extensionDir) {
287
+ if (dryRun) return {
288
+ step: 'watch-extension-ready',
289
+ status: 'skip',
290
+ summary: '[dry-run] skip health-watch'
291
+ };
292
+ if (!existsSync(extensionDir) || !existsSync(join(extensionDir, 'manifest.json'))) {
293
+ return {
294
+ step: 'watch-extension-ready',
295
+ status: 'fail',
296
+ summary: `落位缺失 manifest.json:${extensionDir}`
297
+ };
298
+ }
299
+ const srcStat = statSync(sourceDir);
300
+ const dstStat = statSync(extensionDir);
301
+ if (srcStat.mtimeMs > dstStat.mtimeMs) {
302
+ return {
303
+ step: 'watch-extension-ready',
304
+ status: 'fail',
305
+ summary: `扩展文件可能过期(源比落位新);重跑 npx gotry setup`
306
+ };
307
+ }
308
+ const manifest = JSON.parse(await (await import('node:fs/promises')).readFile(join(extensionDir, 'manifest.json'), 'utf8'));
309
+ if (typeof manifest.key !== 'string' || manifest.key.length < 100) {
310
+ return {
311
+ step: 'watch-extension-ready',
312
+ status: 'fail',
313
+ summary: 'manifest.key 缺失或过短,扩展 ID 不能稳定'
314
+ };
315
+ }
316
+ return {
317
+ step: 'watch-extension-ready',
318
+ status: 'ok',
319
+ summary: '扩展文件就绪(manifest.key 校验通过,后台轮询就绪态由 health-watch 接管)'
320
+ };
321
+ }
322
+ export async function runOnboardingWizard(opts = {}) {
323
+ const sp = opts.spawn ?? spawn;
324
+ const dryRun = opts.dryRun ?? false;
325
+ const plat = detectPlatform(opts.forcePlatform);
326
+ const extensionDir = opts.extensionDir ?? defaultExtensionDir();
327
+ const sourceDir = opts.sourceDir ?? resolveExtensionSourceDir();
328
+ const steps = [];
329
+ steps.push(ensureExtensionFiles(sourceDir, extensionDir, dryRun));
330
+ steps.push(openChromeExtensions(sp, plat, dryRun));
331
+ if (plat !== 'headless') {
332
+ steps.push(await clipboardExtensionPath(sp, plat, extensionDir, dryRun));
333
+ steps.push(panelGuideGui(sp, plat, extensionDir, dryRun));
334
+ } else {
335
+ steps.push({
336
+ step: 'clipboard-extension-path',
337
+ status: 'skip',
338
+ summary: 'headless 跳过剪贴板'
339
+ });
340
+ steps.push(panelGuideTerminal(plat, extensionDir, 120_000));
341
+ }
342
+ steps.push(await watchExtensionReady(dryRun, sourceDir, extensionDir));
343
+ const ok = steps.every((s)=>s.status !== 'fail');
344
+ return {
345
+ ok,
346
+ steps,
347
+ platform: plat,
348
+ extensionDir
349
+ };
350
+ }
351
+ export function printWizardResult(result, destination = process.stdout) {
352
+ const tag = (s)=>{
353
+ if (s.status === 'ok') return '✅';
354
+ if (s.status === 'skip') return '⏭ ';
355
+ return '❌';
356
+ };
357
+ for (const s of result.steps){
358
+ destination.write(`${tag(s)} ${s.step}: ${s.summary}\n`);
359
+ }
360
+ destination.write(`platform=${result.platform}; extensionDir=${result.extensionDir}; overall=${result.ok ? 'ok' : 'has-failures'}\n`);
361
+ }
362
+ export function describeExtensionDir(extensionDir) {
363
+ if (!existsSync(extensionDir)) return {
364
+ exists: false,
365
+ fileCount: 0,
366
+ hasManifest: false
367
+ };
368
+ const files = readdirSync(extensionDir);
369
+ return {
370
+ exists: true,
371
+ fileCount: files.length,
372
+ hasManifest: files.includes('manifest.json')
373
+ };
374
+ }
375
+
376
+
377
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/wizard.ts
@@ -79,4 +79,4 @@ export function approvalFromContext(ctx) {
79
79
  }
80
80
 
81
81
 
82
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session-consent.ts
82
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-consent.ts
@@ -1,4 +1,6 @@
1
1
  import { openSession } from './session/transport.js';
2
+ import { extensionCookieNames, extensionOpenLogin, classifyBridgeFailure, NEEDS_EXTENSION_HINT } from './session/extension-channel.js';
3
+ import { resolveTransportMode } from './session-search.js';
2
4
  export const LOGIN_TARGETS = {
3
5
  'ctrip-flight': {
4
6
  domain: 'ctrip.com',
@@ -47,6 +49,9 @@ export async function sessionLogin(q = {}) {
47
49
  if (!target) {
48
50
  return err(site, 'error', `未知站点 ${site}(可选 ${Object.keys(LOGIN_TARGETS).join('/')})`, started, ts);
49
51
  }
52
+ if (resolveTransportMode() === 'extension') {
53
+ return sessionLoginViaExtension(site, target, q, started, ts);
54
+ }
50
55
  const t = await openSession({
51
56
  mode: 'cdp',
52
57
  guard: false
@@ -114,6 +119,77 @@ export async function sessionLogin(q = {}) {
114
119
  return err(site, 'error', e instanceof Error ? e.message : String(e), started, ts);
115
120
  }
116
121
  }
122
+ async function sessionLoginViaExtension(site, target, q, started, ts) {
123
+ const evidenceTag = `[会话:${site}-login@${ts}]`;
124
+ const quick = (verdict, error)=>({
125
+ ok: false,
126
+ via: 'session-login-error',
127
+ evidence: `[会话:login-error@${ts}] ${error.slice(0, 200)}`,
128
+ latencyMs: Date.now() - started,
129
+ verdict,
130
+ site,
131
+ error: verdict === 'needs-extension' ? `${error};${NEEDS_EXTENSION_HINT}`.slice(0, 400) : error.slice(0, 400)
132
+ });
133
+ const pre = await extensionCookieNames({
134
+ site,
135
+ domain: target.domain,
136
+ ticketNames: target.names,
137
+ timeoutMs: 8_000
138
+ });
139
+ if (!pre.ok) return quick(classifyBridgeFailure(pre.kind) === 'needs-extension' ? 'needs-extension' : 'error', pre.summary);
140
+ if (pre.tickets.length > 0) {
141
+ return {
142
+ ok: true,
143
+ via: 'session-login',
144
+ latencyMs: Date.now() - started,
145
+ verdict: 'logged-in',
146
+ site,
147
+ tickets: pre.tickets,
148
+ evidence: `${evidenceTag} 自动检测:票据 cookie 已在(先前登录已生效)——[${pre.tickets.join(', ')}](只读名字,0 网页交互,零弹窗)`
149
+ };
150
+ }
151
+ const opened = await extensionOpenLogin({
152
+ site,
153
+ url: target.entryUrl,
154
+ timeoutMs: 15_000
155
+ });
156
+ if (!opened.ok) return quick(classifyBridgeFailure(opened.kind) === 'needs-extension' ? 'needs-extension' : 'error', opened.summary);
157
+ const waitMs = Math.min(Math.max(q.waitMs ?? 90_000, 0), 300_000);
158
+ const pollMs = Math.min(Math.max(q.pollMs ?? 3_000, 500), 10_000);
159
+ const deadline = Date.now() + waitMs;
160
+ let tickets = [];
161
+ while(Date.now() < deadline){
162
+ await new Promise((resolve)=>setTimeout(resolve, pollMs));
163
+ const poll = await extensionCookieNames({
164
+ site,
165
+ domain: target.domain,
166
+ ticketNames: target.names,
167
+ timeoutMs: 8_000
168
+ });
169
+ if (poll.ok) tickets = poll.tickets;
170
+ if (tickets.length > 0) break;
171
+ }
172
+ if (tickets.length > 0) {
173
+ return {
174
+ ok: true,
175
+ via: 'session-login',
176
+ latencyMs: Date.now() - started,
177
+ verdict: 'logged-in',
178
+ site,
179
+ tickets,
180
+ evidence: `${evidenceTag} 票据 cookie 已检出 [${tickets.join(', ')}](只读名字;登录在你自己的浏览器里完成,gotry 全程未接触任何密码/验证码/cookie 值)`
181
+ };
182
+ }
183
+ return {
184
+ ok: true,
185
+ via: 'session-login',
186
+ latencyMs: Date.now() - started,
187
+ verdict: 'pending',
188
+ site,
189
+ tickets: [],
190
+ evidence: `${evidenceTag} 登录入口已在你的 Chrome 置前打开(${target.label});在标签页里正常登录完成后再说一声「继续查」即可——gotry 只检查"是否已登录",永不收集你的账号信息`
191
+ };
192
+ }
117
193
 
118
194
 
119
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session-login.ts
195
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-login.ts
@@ -1,4 +1,7 @@
1
1
  import { openSession } from './session/transport.js';
2
+ import { extensionCookieNames, extensionSearchJob, classifyBridgeFailure, NEEDS_EXTENSION_HINT } from './session/extension-channel.js';
3
+ import { appendFileSync, mkdirSync } from 'node:fs';
4
+ import { dirname } from 'node:path';
2
5
  import { buildEntryUrl, NETWORK_HINTS, parseBatchSearch, LOGIN_COOKIE_NAMES, SITE_DOMAIN } from './session/adapters/ctrip-flight.js';
3
6
  const MIN_INTERVAL_MS = 30_000;
4
7
  const lastCallAt = new Map();
@@ -9,6 +12,24 @@ const CHALLENGE_RE = /验证|滑块|captcha|verify/i;
9
12
  export function classifyTransportFailure(summary, cdpMode) {
10
13
  return cdpMode && /日常 Chrome 未开调试端口|cdp attach 失败/.test(summary) ? 'needs-attach' : 'error';
11
14
  }
15
+ export function resolveTransportMode(profileDir) {
16
+ if (profileDir !== undefined) return 'persistent';
17
+ return (process.env.GOTRY_SESSION_TRANSPORT ?? '').trim().toLowerCase() === 'cdp' ? 'cdp' : 'extension';
18
+ }
19
+ export function appendExtensionAudit(auditPath, entry) {
20
+ if (!auditPath) return;
21
+ try {
22
+ const record = {
23
+ ts: new Date().toISOString(),
24
+ ...entry,
25
+ url: entry.url.slice(0, 400)
26
+ };
27
+ mkdirSync(dirname(auditPath), {
28
+ recursive: true
29
+ });
30
+ appendFileSync(auditPath, JSON.stringify(record) + '\n');
31
+ } catch {}
32
+ }
12
33
  export async function sessionFlightSearch(q) {
13
34
  const started = Date.now();
14
35
  const ts = new Date().toISOString();
@@ -30,11 +51,57 @@ export async function sessionFlightSearch(q) {
30
51
  if (!entry.ok || !entry.url) {
31
52
  return err('error', `unresolved entry: ${(entry.unresolved ?? []).join('/')} 不在城市码表`);
32
53
  }
54
+ const mode = resolveTransportMode(q.profileDir);
55
+ if (mode === 'extension') {
56
+ const login = await extensionCookieNames({
57
+ site,
58
+ domain: SITE_DOMAIN.replace(/^\./, ''),
59
+ ticketNames: LOGIN_COOKIE_NAMES
60
+ });
61
+ if (!login.ok) {
62
+ const verdict = classifyBridgeFailure(login.kind);
63
+ return err(verdict, verdict === 'needs-extension' ? `${login.summary};${NEEDS_EXTENSION_HINT}` : login.summary);
64
+ }
65
+ if (login.tickets.length === 0 && !q.allowAnonymous) {
66
+ return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)');
67
+ }
68
+ const r = await extensionSearchJob({
69
+ site,
70
+ url: entry.url,
71
+ timeoutMs: q.timeoutMs
72
+ });
73
+ appendExtensionAudit(q.auditPath, {
74
+ kind: 'extension-session-job',
75
+ site,
76
+ url: entry.url,
77
+ jobId: 'search',
78
+ result: r.ok ? r.timedOut ? 'timeout' : `body ${r.body.length}B title="${r.title.slice(0, 60)}"` : `${r.kind}:${r.summary.slice(0, 120)}`
79
+ });
80
+ if (!r.ok) {
81
+ const verdict = classifyBridgeFailure(r.kind);
82
+ return err(verdict, verdict === 'needs-extension' ? `${r.summary};${NEEDS_EXTENSION_HINT}` : r.summary);
83
+ }
84
+ const title = r.title;
85
+ const head = r.body.slice(0, 5000);
86
+ if (CHALLENGE_RE.test(title + head)) {
87
+ return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
88
+ }
89
+ const options = parseBatchSearch(r.body);
90
+ const verdict = options.length > 0 ? 'hit' : 'miss';
91
+ return {
92
+ ok: true,
93
+ via: 'session-ctrip-flight',
94
+ evidence: `[会话:${site}@${ts}] ${options.length} options;transport=extension(被动嗅探,零系统弹窗;扩展零写行为=物理只读)${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
95
+ latencyMs: Date.now() - started,
96
+ verdict,
97
+ options
98
+ };
99
+ }
33
100
  const t = await openSession({
34
101
  profileDir: q.profileDir,
35
102
  headless: q.headless,
36
103
  auditPath: q.auditPath,
37
- mode: q.profileDir ? 'persistent' : 'cdp',
104
+ mode: mode === 'persistent' ? 'persistent' : 'cdp',
38
105
  newPage: true
39
106
  });
40
107
  if (!t.ok) {
@@ -93,4 +160,4 @@ export async function sessionFlightSearch(q) {
93
160
  }
94
161
 
95
162
 
96
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session-search.ts
163
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-search.ts
@@ -232,4 +232,4 @@ export async function getClimate(point, month, opts = {}) {
232
232
  }
233
233
 
234
234
 
235
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/weather.ts
235
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/weather.ts
@@ -104,4 +104,4 @@ console.log(`\nACTION-CACHE: ${pass} pass, ${fail} fail`);
104
104
  process.exit(fail > 0 ? 1 : 0);
105
105
 
106
106
 
107
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/action-cache-tests.ts
107
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/action-cache-tests.ts
@@ -51,4 +51,4 @@ import { videoSubtitle, githubSearch } from '../capabilities/agent-reach-deep.js
51
51
  }console.log('\nAGENT-REACH DEEP TESTS: 4/4 OK(yt-dlp/gh 可选工具,不装则降级)');
52
52
 
53
53
 
54
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-deep-tests.ts
54
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/agent-reach-deep-tests.ts
@@ -35,4 +35,4 @@ import { readUrl } from '../capabilities/agent-reach.js';
35
35
  }console.log('\nAGENT-REACH TESTS: 3/3 OK(readUrl 薄壳;上游真读见 wrapper 套)');
36
36
 
37
37
 
38
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-tests.ts
38
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/agent-reach-tests.ts
@@ -80,4 +80,4 @@ import { reach, reachStatus } from '../capabilities/agent-reach.js';
80
80
  }console.log('\nAGENT-REACH WRAPPER TESTS: 7/7 OK(反射桥 + doctor 透传 + 自描述错误)');
81
81
 
82
82
 
83
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-wrapper-tests.ts
83
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/agent-reach-wrapper-tests.ts
@@ -92,4 +92,4 @@ while :; do sleep 5; done
92
92
  }
93
93
 
94
94
 
95
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/anything-tests.ts
95
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/anything-tests.ts
@@ -62,4 +62,4 @@ console.log(JSON.stringify(outcome));
62
62
  process.exitCode = outcome.status === 'succeeded' ? 0 : 2;
63
63
 
64
64
 
65
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/async-collect.ts
65
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/async-collect.ts
@@ -184,4 +184,4 @@ console.log(`\nBOOKING SAGA TESTS: ${pass} ok, ${fail} fail`);
184
184
  if (fail > 0) process.exit(1);
185
185
 
186
186
 
187
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/booking-saga-tests.ts
187
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/booking-saga-tests.ts