@danceiny/gotry 0.0.1-rc.16 → 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 (229) hide show
  1. package/README.md +47 -13
  2. package/README.zh-CN.md +20 -10
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +111 -51
  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 +5 -1
  10. package/dist/capabilities/agent-reach-deep.js +1 -1
  11. package/dist/capabilities/agent-reach.js +1 -1
  12. package/dist/capabilities/anything.js +1 -1
  13. package/dist/capabilities/artifacts.js +1 -1
  14. package/dist/capabilities/effect.js +1 -1
  15. package/dist/capabilities/fact-log.js +1 -1
  16. package/dist/capabilities/flyai.js +20 -6
  17. package/dist/capabilities/hbcli.js +1 -1
  18. package/dist/capabilities/incident-log.js +1 -1
  19. package/dist/capabilities/model-override.js +18 -0
  20. package/dist/capabilities/opensky.js +1 -1
  21. package/dist/capabilities/resilience.js +1 -1
  22. package/dist/capabilities/session/action-cache.js +1 -1
  23. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  24. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  25. package/dist/capabilities/session/benchmark.js +1 -1
  26. package/dist/capabilities/session/extension-bridge.js +19 -6
  27. package/dist/capabilities/session/extension-channel.js +3 -3
  28. package/dist/capabilities/session/extension-distribution.js +234 -0
  29. package/dist/capabilities/session/extract.js +1 -1
  30. package/dist/capabilities/session/golden-score.js +92 -0
  31. package/dist/capabilities/session/health-watch.js +1 -1
  32. package/dist/capabilities/session/read-guard.js +1 -1
  33. package/dist/capabilities/session/static-flight-golden.js +137 -0
  34. package/dist/capabilities/session/transport.js +1 -1
  35. package/dist/capabilities/session/wizard.js +21 -251
  36. package/dist/capabilities/session-consent.js +1 -1
  37. package/dist/capabilities/session-login.js +11 -5
  38. package/dist/capabilities/session-search.js +46 -4
  39. package/dist/capabilities/weather.js +168 -46
  40. package/dist/data/session-golden-20.json +25 -0
  41. package/dist/data/sf-golden-manifest.json +102 -0
  42. package/dist/data/sf-static-routes.json +91 -0
  43. package/dist/scripts/action-cache-tests.js +1 -1
  44. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  45. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  46. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  47. package/dist/scripts/agent-reach-tests.js +1 -1
  48. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  49. package/dist/scripts/anything-tests.js +1 -1
  50. package/dist/scripts/async-collect.js +1 -1
  51. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  52. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  53. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  54. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  55. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  56. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  57. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  58. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  59. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  60. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  61. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  62. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  63. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  64. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  65. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  66. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  67. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  68. package/dist/scripts/booking-saga-tests.js +1 -1
  69. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  70. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  71. package/dist/scripts/bootstrap-tests.js +30 -10
  72. package/dist/scripts/build-changelog.js +1 -1
  73. package/dist/scripts/changelog-tests.js +1 -1
  74. package/dist/scripts/companion-tests.js +1 -1
  75. package/dist/scripts/diff-test.js +1 -1
  76. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  77. package/dist/scripts/dsh-runtime-closure.js +150 -0
  78. package/dist/scripts/effect-tests.js +1 -1
  79. package/dist/scripts/engine-run.js +1 -1
  80. package/dist/scripts/engine-tests.js +1 -1
  81. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  82. package/dist/scripts/evaluation-contract-tests.js +574 -0
  83. package/dist/scripts/extension-distribution-cli.js +35 -0
  84. package/dist/scripts/extension-distribution-tests.js +341 -0
  85. package/dist/scripts/extension-tests.js +128 -23
  86. package/dist/scripts/fact-gate-tests.js +1 -1
  87. package/dist/scripts/flyai-tests.js +1 -1
  88. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  89. package/dist/scripts/hbcli-tests.js +1 -1
  90. package/dist/scripts/health-watch-cli.js +1 -1
  91. package/dist/scripts/i18n-tests.js +1 -1
  92. package/dist/scripts/incident-tests.js +1 -1
  93. package/dist/scripts/journey-tests.js +1 -1
  94. package/dist/scripts/ledger-tests.js +1 -1
  95. package/dist/scripts/ledger-workflow-crash.js +1 -1
  96. package/dist/scripts/memory-capture-tests.js +1 -1
  97. package/dist/scripts/memory-decay-tests.js +1 -1
  98. package/dist/scripts/memory-metrics.js +1 -1
  99. package/dist/scripts/memory-value-report.js +1 -1
  100. package/dist/scripts/model-override-e2e.js +176 -0
  101. package/dist/scripts/nightly-evidence-tests.js +1 -1
  102. package/dist/scripts/nightly-evidence.js +1 -1
  103. package/dist/scripts/nudge-digest.js +1 -1
  104. package/dist/scripts/onboarding-tests.js +21 -53
  105. package/dist/scripts/opensky-check.js +1 -1
  106. package/dist/scripts/opensky-tests.js +1 -1
  107. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  108. package/dist/scripts/price-drift-tests.js +1 -1
  109. package/dist/scripts/price-drift-watch.js +1 -1
  110. package/dist/scripts/probe-poi-tests.js +1 -1
  111. package/dist/scripts/product-metrics.js +1 -1
  112. package/dist/scripts/publish-preverify.js +40 -4
  113. package/dist/scripts/realtime-pricing-tests.js +1 -1
  114. package/dist/scripts/replay-async.js +1 -1
  115. package/dist/scripts/replay-real.js +1 -1
  116. package/dist/scripts/replay.js +1 -1
  117. package/dist/scripts/session-attach-diagnose.js +1 -1
  118. package/dist/scripts/session-attach-poc.js +1 -1
  119. package/dist/scripts/session-benchmark.js +1 -1
  120. package/dist/scripts/session-extract-tests.js +1 -1
  121. package/dist/scripts/session-login.js +1 -1
  122. package/dist/scripts/session-tests.js +70 -20
  123. package/dist/scripts/sf-live-benchmark.js +338 -0
  124. package/dist/scripts/sf-live-cli-tests.js +21 -0
  125. package/dist/scripts/sf-soft-score-tests.js +108 -0
  126. package/dist/scripts/sf-summary.js +93 -0
  127. package/dist/scripts/skeleton-check.js +1 -1
  128. package/dist/scripts/skeleton-integration-test.js +1 -1
  129. package/dist/scripts/skills-contract-tests.js +1 -1
  130. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  131. package/dist/scripts/smoke.js +79 -33
  132. package/dist/scripts/state-cli-tests.js +1 -1
  133. package/dist/scripts/state-cli.js +1 -1
  134. package/dist/scripts/static-golden-tests.js +299 -0
  135. package/dist/scripts/time-eval-tests.js +1 -1
  136. package/dist/scripts/travel-timeline-tests.js +1 -1
  137. package/dist/scripts/unified-tests.js +1 -1
  138. package/dist/scripts/weather-tests.js +694 -44
  139. package/dist/scripts/z3-race-tests.js +1 -1
  140. package/dist/src/artifact-gate.js +1 -1
  141. package/dist/src/benchmark-agent-conformance.js +370 -0
  142. package/dist/src/benchmark-environment-bridge.js +384 -0
  143. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  144. package/dist/src/benchmark-tool-isolation.js +124 -0
  145. package/dist/src/bookable-facts.js +1 -1
  146. package/dist/src/booking-saga.js +1 -1
  147. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  148. package/dist/src/booking-surface/canonical-schema.js +113 -0
  149. package/dist/src/booking-surface/contracts-v2.js +89 -0
  150. package/dist/src/booking-surface/contracts.js +46 -0
  151. package/dist/src/booking-surface/dsh-planner.js +453 -0
  152. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  153. package/dist/src/booking-surface/error-codes.js +94 -0
  154. package/dist/src/booking-surface/index.js +15 -0
  155. package/dist/src/booking-surface/profile.js +68 -0
  156. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  157. package/dist/src/booking-surface/runtime.js +351 -0
  158. package/dist/src/booking-surface/server-v2.js +334 -0
  159. package/dist/src/booking-surface/server.js +302 -0
  160. package/dist/src/booking-surface/startup.js +159 -0
  161. package/dist/src/booking-surface/validation-v2.js +319 -0
  162. package/dist/src/booking-surface/validation.js +809 -0
  163. package/dist/src/bridge.js +1 -1
  164. package/dist/src/companions.js +1 -1
  165. package/dist/src/contracts.js +1 -1
  166. package/dist/src/dsh-llm.js +1 -1
  167. package/dist/src/engine.js +1 -1
  168. package/dist/src/evaluation-cadence.js +234 -0
  169. package/dist/src/evaluation-contracts.js +906 -0
  170. package/dist/src/i18n.js +1 -1
  171. package/dist/src/index.js +50 -19
  172. package/dist/src/journey.js +1 -1
  173. package/dist/src/loop.js +1 -1
  174. package/dist/src/memory-capture.js +1 -1
  175. package/dist/src/memory-decay.js +1 -1
  176. package/dist/src/memory-utility.js +1 -1
  177. package/dist/src/mock-llm.js +1 -1
  178. package/dist/src/model.js +1 -1
  179. package/dist/src/realtime-pricing.js +1 -1
  180. package/dist/src/slot-spec.js +1 -1
  181. package/dist/src/state-ledger.js +2 -1
  182. package/dist/src/time-anchor.js +1 -1
  183. package/dist/src/tool-budget.js +136 -0
  184. package/dist/src/tool-packet.js +1 -1
  185. package/dist/src/travel-slots.js +1 -1
  186. package/dist/src/travel-timeline.js +1 -1
  187. package/dist/src/unified.js +1 -1
  188. package/dist/src/wish-pool.js +1 -1
  189. package/dist/src/z3-shared.js +1 -1
  190. package/extension/README.md +31 -7
  191. package/package.json +286 -11
  192. package/schemas/booking.surface.v1.schema.json +927 -0
  193. package/schemas/booking.surface.v2.schema.json +61 -0
  194. package/ts/capabilities/flyai.ts +16 -3
  195. package/ts/capabilities/session/extension-bridge.ts +37 -14
  196. package/ts/capabilities/session/extension-channel.ts +6 -3
  197. package/ts/capabilities/session/extension-distribution.ts +264 -0
  198. package/ts/capabilities/session/golden-score.ts +139 -0
  199. package/ts/capabilities/session/health-watch.ts +1 -1
  200. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  201. package/ts/capabilities/session/wizard.ts +34 -176
  202. package/ts/capabilities/session-login.ts +15 -5
  203. package/ts/capabilities/session-search.ts +40 -3
  204. package/ts/capabilities/weather.ts +141 -52
  205. package/ts/package.json +3 -3
  206. package/ts/src/benchmark-agent-conformance.ts +448 -0
  207. package/ts/src/benchmark-environment-bridge.ts +348 -0
  208. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  209. package/ts/src/benchmark-tool-isolation.ts +166 -0
  210. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  211. package/ts/src/booking-surface/canonical-schema.js +113 -0
  212. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  213. package/ts/src/booking-surface/contracts.ts +380 -0
  214. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  215. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  216. package/ts/src/booking-surface/error-codes.ts +101 -0
  217. package/ts/src/booking-surface/index.ts +12 -0
  218. package/ts/src/booking-surface/profile.ts +42 -0
  219. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  220. package/ts/src/booking-surface/runtime.ts +483 -0
  221. package/ts/src/booking-surface/server-v2.ts +247 -0
  222. package/ts/src/booking-surface/server.ts +324 -0
  223. package/ts/src/booking-surface/startup.ts +196 -0
  224. package/ts/src/booking-surface/validation-v2.ts +205 -0
  225. package/ts/src/booking-surface/validation.ts +453 -0
  226. package/ts/src/index.ts +64 -11
  227. package/ts/src/state-ledger.ts +1 -0
  228. package/ts/src/tool-budget.ts +165 -0
  229. package/dist/scripts/wizard-bootstrap.js +0 -32
@@ -0,0 +1,92 @@
1
+ import { SESSION_FIELD_ACCURACY_THRESHOLD } from './benchmark.js';
2
+ function parseHHmm(value) {
3
+ const [hours, minutes] = value.split(':');
4
+ return parseInt(hours, 10) * 60 + parseInt(minutes, 10);
5
+ }
6
+ function parseLocalMinutes(value) {
7
+ const match = value.match(/(\d{2}):(\d{2})/);
8
+ if (!match) return null;
9
+ return parseInt(match[1], 10) * 60 + parseInt(match[2], 10);
10
+ }
11
+ function parseLocalDate(value) {
12
+ return value.match(/^(\d{4}-\d{2}-\d{2})[T ]/)?.[1] ?? null;
13
+ }
14
+ export function scoreSessionAgainstGoldenBand(session, band, goldenSource) {
15
+ const missing = [];
16
+ const hardIncorrect = [];
17
+ const segment = session.route_segments[0];
18
+ if (!session.query_id) missing.push('query_id');
19
+ else if (session.query_id !== band.query_id) hardIncorrect.push('query_id');
20
+ if (!session.currency) missing.push('currency');
21
+ else if (session.currency !== 'CNY') hardIncorrect.push('currency');
22
+ if (!session.source) missing.push('source');
23
+ else if (session.source !== 'ctrip-flight') hardIncorrect.push('source');
24
+ if (!session.verdict) missing.push('verdict');
25
+ else if (session.verdict !== 'hit') hardIncorrect.push('verdict');
26
+ if (!segment) {
27
+ missing.push('route_segments[0].from', 'route_segments[0].to', 'route_segments[0].departure_at', 'route_segments[0].arrival_at', 'route_segments[0].transport_number');
28
+ } else {
29
+ if (!segment.from) missing.push('route_segments[0].from');
30
+ else if (segment.from !== band.from) hardIncorrect.push('route_segments[0].from');
31
+ if (!segment.to) missing.push('route_segments[0].to');
32
+ else if (segment.to !== band.to) hardIncorrect.push('route_segments[0].to');
33
+ if (!segment.departure_at) missing.push('route_segments[0].departure_at');
34
+ else if (parseLocalDate(segment.departure_at) !== band.date) {
35
+ hardIncorrect.push('route_segments[0].departure_at.date');
36
+ }
37
+ if (!segment.arrival_at) missing.push('route_segments[0].arrival_at');
38
+ else if (parseLocalDate(segment.arrival_at) !== band.date) {
39
+ hardIncorrect.push('route_segments[0].arrival_at.date');
40
+ }
41
+ if (!segment.transport_number) missing.push('route_segments[0].transport_number');
42
+ }
43
+ const softChecks = [];
44
+ if (segment?.departure_at) {
45
+ const minutes = parseLocalMinutes(segment.departure_at);
46
+ softChecks.push({
47
+ path: 'route_segments[0].departure_at',
48
+ ok: minutes !== null && minutes >= parseHHmm(band.window_dep_local.earliest) && minutes <= parseHHmm(band.window_dep_local.latest)
49
+ });
50
+ }
51
+ if (segment?.arrival_at) {
52
+ const minutes = parseLocalMinutes(segment.arrival_at);
53
+ softChecks.push({
54
+ path: 'route_segments[0].arrival_at',
55
+ ok: minutes !== null && minutes >= parseHHmm(band.window_arr_local.earliest) && minutes <= parseHHmm(band.window_arr_local.latest)
56
+ });
57
+ }
58
+ if (segment?.transport_number) {
59
+ const actual = segment.transport_number.toUpperCase();
60
+ softChecks.push({
61
+ path: 'route_segments[0].transport_number',
62
+ ok: band.known_flights.some((flight)=>actual.startsWith(flight.toUpperCase())) || band.known_flights.some((flight)=>actual.includes(flight.toUpperCase().slice(0, 2)))
63
+ });
64
+ }
65
+ if (Number.isFinite(session.price) && session.price > 0) {
66
+ const margin = band.price_cny.max * 0.15;
67
+ softChecks.push({
68
+ path: 'price',
69
+ ok: session.price >= band.price_cny.min - margin && session.price <= band.price_cny.max + margin
70
+ });
71
+ } else missing.push('price');
72
+ const softIncorrect = softChecks.filter((check)=>!check.ok).map((check)=>check.path);
73
+ const incorrect = [
74
+ ...hardIncorrect,
75
+ ...softIncorrect
76
+ ];
77
+ const total = 13;
78
+ const correct = Math.max(0, total - new Set(missing).size - incorrect.length);
79
+ const accuracy = total > 0 ? correct / total : 0;
80
+ return {
81
+ pass: missing.length === 0 && hardIncorrect.length === 0 && accuracy >= SESSION_FIELD_ACCURACY_THRESHOLD,
82
+ total,
83
+ correct,
84
+ accuracy,
85
+ missing: Array.from(new Set(missing)),
86
+ incorrect,
87
+ golden_source: goldenSource
88
+ };
89
+ }
90
+
91
+
92
+ //# sourceURL=ts/capabilities/session/golden-score.ts
@@ -213,4 +213,4 @@ export const HEALTH_WATCH_INTERVAL_FLOOR_MS = 1_000;
213
213
  export const HEALTH_WATCH_INTERVAL_CEIL_MS = EXTENSION_CONNECTED_WINDOW_MS - 1_000;
214
214
 
215
215
 
216
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/health-watch.ts
216
+ //# sourceURL=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-publish/ts/capabilities/session/read-guard.ts
99
+ //# sourceURL=ts/capabilities/session/read-guard.ts
@@ -0,0 +1,137 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export function parseGoldenSource(args) {
4
+ const raw = args.find((arg)=>arg.startsWith('--golden='))?.slice('--golden='.length) ?? 'manual';
5
+ if (raw === 'manual' || raw === 'flyai' || raw === 'static') return raw;
6
+ throw new Error(`不支持的 golden vendor: ${raw}`);
7
+ }
8
+ export function loadStaticFlightSnapshot(path = join(import.meta.dirname, '..', '..', 'data', 'sf-static-routes.json')) {
9
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
10
+ if (!isRecord(parsed) || parsed.schema_version !== 'sf-static-routes.v1') {
11
+ throw new Error(`invalid static snapshot schema_version: ${String(isRecord(parsed) ? parsed.schema_version : undefined)}`);
12
+ }
13
+ const source = parsed.source;
14
+ if (!isRecord(source) || ![
15
+ 'name',
16
+ 'url',
17
+ 'revision',
18
+ 'license',
19
+ 'retrieved_at'
20
+ ].every((key)=>typeof source[key] === 'string') || !Array.isArray(source.limitations) || !source.limitations.every((item)=>typeof item === 'string')) {
21
+ throw new Error('invalid static snapshot source');
22
+ }
23
+ if (!Array.isArray(parsed.routes) || !parsed.routes.every(isStaticFlightRoute)) {
24
+ throw new Error('invalid static snapshot routes');
25
+ }
26
+ return parsed;
27
+ }
28
+ function isRecord(value) {
29
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
30
+ }
31
+ function isStaticFlightRoute(value) {
32
+ if (!isRecord(value) || typeof value.query_id !== 'string' || typeof value.from_city !== 'string' || typeof value.to_city !== 'string' || value.route_kind !== 'direct' && value.route_kind !== 'transfer' || !Array.isArray(value.legs) || value.legs.length === 0) return false;
33
+ return value.legs.every((leg)=>isRecord(leg) && typeof leg.from_iata === 'string' && typeof leg.to_iata === 'string' && Array.isArray(leg.carrier_codes) && leg.carrier_codes.every((carrier)=>typeof carrier === 'string'));
34
+ }
35
+ function provenance(snapshot) {
36
+ return {
37
+ route_source: snapshot?.source.url ?? '',
38
+ route_revision: snapshot?.source.revision ?? '',
39
+ route_license: snapshot?.source.license ?? '',
40
+ band_source: 'sf-golden-manifest.json'
41
+ };
42
+ }
43
+ function formatWithOffset(timestampMs, offset) {
44
+ const sign = offset.startsWith('-') ? -1 : 1;
45
+ const [hours, minutes] = offset.slice(1).split(':').map(Number);
46
+ const offsetMinutes = sign * ((hours ?? 0) * 60 + (minutes ?? 0));
47
+ const local = new Date(timestampMs + offsetMinutes * 60_000).toISOString().slice(0, 19);
48
+ return `${local}${offset}`;
49
+ }
50
+ function buildSegments(query, route, manualSegment) {
51
+ if (route.route_kind === 'direct' && route.legs.length === 1) {
52
+ return [
53
+ {
54
+ from: query.from,
55
+ to: query.to,
56
+ departure_at: manualSegment.departure_at,
57
+ arrival_at: manualSegment.arrival_at,
58
+ transport_number: `${route.legs[0].carrier_codes[0]}-STATIC`
59
+ }
60
+ ];
61
+ }
62
+ if (route.route_kind !== 'transfer' || route.legs.length !== 2) return null;
63
+ const departureMs = Date.parse(manualSegment.departure_at);
64
+ const arrivalMs = Date.parse(manualSegment.arrival_at);
65
+ if (!Number.isFinite(departureMs) || !Number.isFinite(arrivalMs) || arrivalMs <= departureMs) return null;
66
+ const offset = manualSegment.departure_at.match(/([+-]\d{2}:\d{2})$/)?.[1] ?? '+08:00';
67
+ const totalMinutes = Math.floor((arrivalMs - departureMs) / 60_000);
68
+ if (totalMinutes < 90) return null;
69
+ const layoverMinutes = Math.min(60, Math.floor(totalMinutes / 3));
70
+ const travelMinutes = totalMinutes - layoverMinutes;
71
+ const firstTravelMinutes = Math.floor(travelMinutes / 2);
72
+ const secondDepartureMs = arrivalMs - (travelMinutes - firstTravelMinutes) * 60_000;
73
+ const middle = route.legs[0].to_iata;
74
+ return [
75
+ {
76
+ from: query.from,
77
+ to: middle,
78
+ departure_at: manualSegment.departure_at,
79
+ arrival_at: formatWithOffset(departureMs + firstTravelMinutes * 60_000, offset),
80
+ transport_number: `${route.legs[0].carrier_codes[0]}-STATIC`
81
+ },
82
+ {
83
+ from: middle,
84
+ to: query.to,
85
+ departure_at: formatWithOffset(secondDepartureMs, offset),
86
+ arrival_at: manualSegment.arrival_at,
87
+ transport_number: `${route.legs[1].carrier_codes[0]}-STATIC`
88
+ }
89
+ ];
90
+ }
91
+ export function resolveStaticGolden(input) {
92
+ const route = input.snapshot?.routes.find((candidate)=>candidate.query_id === input.query.id);
93
+ let fallbackReason;
94
+ if (input.snapshotError) {
95
+ fallbackReason = `static snapshot unavailable: ${input.snapshotError}`;
96
+ } else if (!route) {
97
+ fallbackReason = `static route missing for ${input.query.id}`;
98
+ } else if (route.from_city !== input.query.from || route.to_city !== input.query.to) {
99
+ fallbackReason = `static route mismatch for ${input.query.id}`;
100
+ } else if (route.legs.some((leg)=>leg.carrier_codes.length === 0)) {
101
+ fallbackReason = `static carrier coverage missing for ${input.query.id}`;
102
+ }
103
+ const manualSegment = input.manualRecord.route_segments[0];
104
+ const segments = !fallbackReason && route && manualSegment ? buildSegments(input.query, route, manualSegment) : null;
105
+ if (!fallbackReason && !segments) fallbackReason = `static route cannot form comparable record for ${input.query.id}`;
106
+ if (fallbackReason) {
107
+ input.warn?.(`[sf-live-benchmark] static vendor failed for ${input.query.id}: ${fallbackReason}; fallback=manual-golden`);
108
+ return {
109
+ requested_source: 'static',
110
+ effective_source: 'manual-golden',
111
+ record: input.manualRecord,
112
+ fallback_reason: fallbackReason,
113
+ estimated_fields: [],
114
+ provenance: provenance(input.snapshot)
115
+ };
116
+ }
117
+ return {
118
+ requested_source: 'static',
119
+ effective_source: 'static-openflights+manual-band',
120
+ record: {
121
+ ...input.manualRecord,
122
+ route_segments: segments,
123
+ journey_type: route.route_kind,
124
+ source: 'static-openflights+manual-band'
125
+ },
126
+ estimated_fields: [
127
+ 'route_segments[].departure_at',
128
+ 'route_segments[].arrival_at',
129
+ 'route_segments[].transport_number',
130
+ 'price'
131
+ ],
132
+ provenance: provenance(input.snapshot)
133
+ };
134
+ }
135
+
136
+
137
+ //# sourceURL=ts/capabilities/session/static-flight-golden.ts
@@ -113,4 +113,4 @@ export async function openSession(opts = {}) {
113
113
  }
114
114
 
115
115
 
116
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/transport.ts
116
+ //# sourceURL=ts/capabilities/session/transport.ts
@@ -1,6 +1,5 @@
1
- import { spawn } from 'node:child_process';
2
1
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
3
- import { homedir, platform } from 'node:os';
2
+ import { homedir } from 'node:os';
4
3
  import { join } from 'node:path';
5
4
  const DEFAULT_SOURCE_DIR_CANDIDATES = [
6
5
  join(process.cwd(), 'extension'),
@@ -16,16 +15,6 @@ function resolveExtensionSourceDir() {
16
15
  export function defaultExtensionDir() {
17
16
  return join(homedir(), '.gotry', 'extension');
18
17
  }
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
18
  function ensureExtensionFiles(source, target, dryRun) {
30
19
  if (dryRun) return {
31
20
  step: 'ensure-extension-files',
@@ -71,246 +60,42 @@ function ensureExtensionFiles(source, target, dryRun) {
71
60
  };
72
61
  }
73
62
  }
74
- function openChromeExtensions(sp, plat, dryRun) {
63
+ async function watchExtensionReady(dryRun, sourceDir, extensionDir) {
75
64
  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',
65
+ step: 'watch-extension-ready',
82
66
  status: 'skip',
83
- summary: 'headless 环境跳过 GUI;请在你桌面 Chrome 手动打开 chrome://extensions'
67
+ summary: '[dry-run] skip health-watch precheck'
84
68
  };
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) {
69
+ if (!existsSync(extensionDir) || !existsSync(join(extensionDir, 'manifest.json'))) {
122
70
  return {
123
- step: 'open-chrome-extensions',
71
+ step: 'watch-extension-ready',
124
72
  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 终端面板已打印'
73
+ summary: `落位缺失 manifest.json:${extensionDir}`
229
74
  };
230
75
  }
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
76
  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?.();
77
+ const srcStat = statSync(sourceDir);
78
+ const dstStat = statSync(extensionDir);
79
+ if (srcStat.mtimeMs > dstStat.mtimeMs) {
256
80
  return {
257
- step: 'panel-guide',
258
- status: 'ok',
259
- summary: '已弹 macOS Cocoa 引导面板'
81
+ step: 'watch-extension-ready',
82
+ status: 'fail',
83
+ summary: `扩展文件可能过期(源比落位新);重跑 npx gotry setup`
260
84
  };
261
85
  }
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
- });
86
+ const manifest = JSON.parse(await (await import('node:fs/promises')).readFile(join(extensionDir, 'manifest.json'), 'utf8'));
87
+ if (typeof manifest.key !== 'string' || manifest.key.length < 100) {
275
88
  return {
276
- step: 'panel-guide',
277
- status: 'ok',
278
- summary: '已弹 zenity 引导面板'
89
+ step: 'watch-extension-ready',
90
+ status: 'fail',
91
+ summary: 'manifest.key 缺失或过短,扩展 ID 不能稳定'
279
92
  };
280
93
  }
281
- return panelGuideTerminal(plat, extensionDir, 120_000);
282
94
  } 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
95
  return {
294
96
  step: 'watch-extension-ready',
295
97
  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 不能稳定'
98
+ summary: `静态预检失败: ${e instanceof Error ? e.message.slice(0, 120) : String(e)}`
314
99
  };
315
100
  }
316
101
  return {
@@ -320,31 +105,16 @@ async function watchExtensionReady(dryRun, sourceDir, extensionDir) {
320
105
  };
321
106
  }
322
107
  export async function runOnboardingWizard(opts = {}) {
323
- const sp = opts.spawn ?? spawn;
324
108
  const dryRun = opts.dryRun ?? false;
325
- const plat = detectPlatform(opts.forcePlatform);
326
109
  const extensionDir = opts.extensionDir ?? defaultExtensionDir();
327
110
  const sourceDir = opts.sourceDir ?? resolveExtensionSourceDir();
328
111
  const steps = [];
329
112
  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
113
  steps.push(await watchExtensionReady(dryRun, sourceDir, extensionDir));
343
114
  const ok = steps.every((s)=>s.status !== 'fail');
344
115
  return {
345
116
  ok,
346
117
  steps,
347
- platform: plat,
348
118
  extensionDir
349
119
  };
350
120
  }
@@ -357,7 +127,7 @@ export function printWizardResult(result, destination = process.stdout) {
357
127
  for (const s of result.steps){
358
128
  destination.write(`${tag(s)} ${s.step}: ${s.summary}\n`);
359
129
  }
360
- destination.write(`platform=${result.platform}; extensionDir=${result.extensionDir}; overall=${result.ok ? 'ok' : 'has-failures'}\n`);
130
+ destination.write(`extensionDir=${result.extensionDir}; overall=${result.ok ? 'ok' : 'has-failures'}\n`);
361
131
  }
362
132
  export function describeExtensionDir(extensionDir) {
363
133
  if (!existsSync(extensionDir)) return {
@@ -374,4 +144,4 @@ export function describeExtensionDir(extensionDir) {
374
144
  }
375
145
 
376
146
 
377
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/wizard.ts
147
+ //# sourceURL=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-publish/ts/capabilities/session-consent.ts
82
+ //# sourceURL=ts/capabilities/session-consent.ts
@@ -1,5 +1,6 @@
1
1
  import { openSession } from './session/transport.js';
2
- import { extensionCookieNames, extensionOpenLogin, classifyBridgeFailure, NEEDS_EXTENSION_HINT } from './session/extension-channel.js';
2
+ import { extensionCookieNames, extensionOpenLogin, classifyBridgeFailure } from './session/extension-channel.js';
3
+ import { EXTENSION_STORE_URL } from './session/extension-bridge.js';
3
4
  import { resolveTransportMode } from './session-search.js';
4
5
  export const LOGIN_TARGETS = {
5
6
  'ctrip-flight': {
@@ -121,15 +122,20 @@ export async function sessionLogin(q = {}) {
121
122
  }
122
123
  async function sessionLoginViaExtension(site, target, q, started, ts) {
123
124
  const evidenceTag = `[会话:${site}-login@${ts}]`;
124
- const quick = (verdict, error)=>({
125
+ const quick = (verdict, error)=>{
126
+ const base = {
125
127
  ok: false,
126
128
  via: 'session-login-error',
127
129
  evidence: `[会话:login-error@${ts}] ${error.slice(0, 200)}`,
128
130
  latencyMs: Date.now() - started,
129
131
  verdict,
130
132
  site,
131
- error: verdict === 'needs-extension' ? `${error};${NEEDS_EXTENSION_HINT}`.slice(0, 400) : error.slice(0, 400)
132
- });
133
+ error: error.slice(0, 400)
134
+ };
135
+ if (verdict === 'needs-extension') base.installUrl = EXTENSION_STORE_URL;
136
+ if (verdict === 'needs-extension') base.installAction = 'add-to-chrome';
137
+ return base;
138
+ };
133
139
  const pre = await extensionCookieNames({
134
140
  site,
135
141
  domain: target.domain,
@@ -192,4 +198,4 @@ async function sessionLoginViaExtension(site, target, q, started, ts) {
192
198
  }
193
199
 
194
200
 
195
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-login.ts
201
+ //# sourceURL=ts/capabilities/session-login.ts