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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +51 -13
  2. package/README.zh-CN.md +26 -12
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +357 -8
  5. package/bin/gotry-inner.js +442 -60
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +15 -1
  10. package/data/airline-airports.json +39 -0
  11. package/dist/capabilities/agent-reach-deep.js +1 -1
  12. package/dist/capabilities/agent-reach.js +1 -1
  13. package/dist/capabilities/anything.js +1 -1
  14. package/dist/capabilities/artifacts.js +1 -1
  15. package/dist/capabilities/effect.js +293 -0
  16. package/dist/capabilities/fact-log.js +41 -0
  17. package/dist/capabilities/flyai.js +20 -6
  18. package/dist/capabilities/hbcli.js +43 -1
  19. package/dist/capabilities/incident-log.js +1 -1
  20. package/dist/capabilities/model-override.js +18 -0
  21. package/dist/capabilities/opensky.js +1 -1
  22. package/dist/capabilities/resilience.js +90 -0
  23. package/dist/capabilities/session/action-cache.js +1 -1
  24. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  25. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  26. package/dist/capabilities/session/benchmark.js +2 -1
  27. package/dist/capabilities/session/extension-bridge.js +358 -0
  28. package/dist/capabilities/session/extension-channel.js +93 -0
  29. package/dist/capabilities/session/extension-distribution.js +234 -0
  30. package/dist/capabilities/session/extract.js +1 -1
  31. package/dist/capabilities/session/golden-score.js +92 -0
  32. package/dist/capabilities/session/health-watch.js +216 -0
  33. package/dist/capabilities/session/read-guard.js +1 -1
  34. package/dist/capabilities/session/static-flight-golden.js +137 -0
  35. package/dist/capabilities/session/transport.js +1 -1
  36. package/dist/capabilities/session/wizard.js +147 -0
  37. package/dist/capabilities/session-consent.js +1 -1
  38. package/dist/capabilities/session-login.js +83 -1
  39. package/dist/capabilities/session-search.js +111 -2
  40. package/dist/capabilities/weather.js +168 -46
  41. package/dist/data/session-golden-20.json +25 -0
  42. package/dist/data/sf-golden-manifest.json +102 -0
  43. package/dist/data/sf-static-routes.json +91 -0
  44. package/dist/scripts/action-cache-tests.js +1 -1
  45. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  46. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  47. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  48. package/dist/scripts/agent-reach-tests.js +1 -1
  49. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  50. package/dist/scripts/anything-tests.js +1 -1
  51. package/dist/scripts/async-collect.js +1 -1
  52. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  53. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  54. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  55. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  56. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  57. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  58. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  59. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  60. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  61. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  62. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  63. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  64. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  65. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  66. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  67. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  68. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  69. package/dist/scripts/booking-saga-tests.js +1 -1
  70. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  71. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  72. package/dist/scripts/bootstrap-tests.js +59 -3
  73. package/dist/scripts/build-changelog.js +279 -0
  74. package/dist/scripts/changelog-tests.js +165 -0
  75. package/dist/scripts/companion-tests.js +1 -1
  76. package/dist/scripts/diff-test.js +1 -1
  77. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  78. package/dist/scripts/dsh-runtime-closure.js +150 -0
  79. package/dist/scripts/effect-tests.js +474 -0
  80. package/dist/scripts/engine-run.js +1 -1
  81. package/dist/scripts/engine-tests.js +1 -1
  82. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  83. package/dist/scripts/evaluation-contract-tests.js +574 -0
  84. package/dist/scripts/extension-distribution-cli.js +35 -0
  85. package/dist/scripts/extension-distribution-tests.js +341 -0
  86. package/dist/scripts/extension-tests.js +573 -0
  87. package/dist/scripts/fact-gate-tests.js +318 -0
  88. package/dist/scripts/flyai-tests.js +1 -1
  89. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  90. package/dist/scripts/hbcli-tests.js +1 -1
  91. package/dist/scripts/health-watch-cli.js +51 -0
  92. package/dist/scripts/i18n-tests.js +1 -1
  93. package/dist/scripts/incident-tests.js +1 -1
  94. package/dist/scripts/journey-tests.js +1 -1
  95. package/dist/scripts/ledger-tests.js +1 -1
  96. package/dist/scripts/ledger-workflow-crash.js +1 -1
  97. package/dist/scripts/memory-capture-tests.js +1 -1
  98. package/dist/scripts/memory-decay-tests.js +1 -1
  99. package/dist/scripts/memory-metrics.js +1 -1
  100. package/dist/scripts/memory-value-report.js +1 -1
  101. package/dist/scripts/model-override-e2e.js +176 -0
  102. package/dist/scripts/nightly-evidence-tests.js +67 -4
  103. package/dist/scripts/nightly-evidence.js +59 -3
  104. package/dist/scripts/nudge-digest.js +1 -1
  105. package/dist/scripts/onboarding-tests.js +186 -0
  106. package/dist/scripts/opensky-check.js +1 -1
  107. package/dist/scripts/opensky-tests.js +1 -1
  108. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  109. package/dist/scripts/price-drift-tests.js +441 -0
  110. package/dist/scripts/price-drift-watch.js +491 -0
  111. package/dist/scripts/probe-poi-tests.js +1 -1
  112. package/dist/scripts/product-metrics.js +1 -1
  113. package/dist/scripts/publish-preverify.js +40 -4
  114. package/dist/scripts/realtime-pricing-tests.js +1 -1
  115. package/dist/scripts/replay-async.js +1 -1
  116. package/dist/scripts/replay-real.js +1 -1
  117. package/dist/scripts/replay.js +1 -1
  118. package/dist/scripts/session-attach-diagnose.js +1 -1
  119. package/dist/scripts/session-attach-poc.js +1 -1
  120. package/dist/scripts/session-benchmark.js +1 -1
  121. package/dist/scripts/session-extract-tests.js +1 -1
  122. package/dist/scripts/session-login.js +1 -1
  123. package/dist/scripts/session-tests.js +70 -20
  124. package/dist/scripts/sf-live-benchmark.js +338 -0
  125. package/dist/scripts/sf-live-cli-tests.js +21 -0
  126. package/dist/scripts/sf-soft-score-tests.js +108 -0
  127. package/dist/scripts/sf-summary.js +93 -0
  128. package/dist/scripts/skeleton-check.js +1 -1
  129. package/dist/scripts/skeleton-integration-test.js +1 -1
  130. package/dist/scripts/skills-contract-tests.js +1 -1
  131. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  132. package/dist/scripts/smoke.js +154 -33
  133. package/dist/scripts/state-cli-tests.js +1 -1
  134. package/dist/scripts/state-cli.js +1 -1
  135. package/dist/scripts/static-golden-tests.js +299 -0
  136. package/dist/scripts/time-eval-tests.js +1 -1
  137. package/dist/scripts/travel-timeline-tests.js +1 -1
  138. package/dist/scripts/unified-tests.js +1 -1
  139. package/dist/scripts/weather-tests.js +694 -44
  140. package/dist/scripts/z3-race-tests.js +1 -1
  141. package/dist/src/artifact-gate.js +310 -0
  142. package/dist/src/benchmark-agent-conformance.js +370 -0
  143. package/dist/src/benchmark-environment-bridge.js +384 -0
  144. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  145. package/dist/src/benchmark-tool-isolation.js +124 -0
  146. package/dist/src/bookable-facts.js +349 -0
  147. package/dist/src/booking-saga.js +1 -1
  148. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  149. package/dist/src/booking-surface/canonical-schema.js +113 -0
  150. package/dist/src/booking-surface/contracts-v2.js +89 -0
  151. package/dist/src/booking-surface/contracts.js +46 -0
  152. package/dist/src/booking-surface/dsh-planner.js +453 -0
  153. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  154. package/dist/src/booking-surface/error-codes.js +94 -0
  155. package/dist/src/booking-surface/index.js +15 -0
  156. package/dist/src/booking-surface/profile.js +68 -0
  157. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  158. package/dist/src/booking-surface/runtime.js +351 -0
  159. package/dist/src/booking-surface/server-v2.js +334 -0
  160. package/dist/src/booking-surface/server.js +302 -0
  161. package/dist/src/booking-surface/startup.js +159 -0
  162. package/dist/src/booking-surface/validation-v2.js +319 -0
  163. package/dist/src/booking-surface/validation.js +809 -0
  164. package/dist/src/bridge.js +1 -1
  165. package/dist/src/companions.js +1 -1
  166. package/dist/src/contracts.js +1 -1
  167. package/dist/src/dsh-llm.js +1 -1
  168. package/dist/src/engine.js +1 -1
  169. package/dist/src/evaluation-cadence.js +234 -0
  170. package/dist/src/evaluation-contracts.js +906 -0
  171. package/dist/src/i18n.js +1 -1
  172. package/dist/src/index.js +234 -60
  173. package/dist/src/journey.js +1 -1
  174. package/dist/src/loop.js +1 -1
  175. package/dist/src/memory-capture.js +1 -1
  176. package/dist/src/memory-decay.js +1 -1
  177. package/dist/src/memory-utility.js +1 -1
  178. package/dist/src/mock-llm.js +1 -1
  179. package/dist/src/model.js +1 -1
  180. package/dist/src/realtime-pricing.js +14 -7
  181. package/dist/src/slot-spec.js +1 -1
  182. package/dist/src/state-ledger.js +2 -1
  183. package/dist/src/time-anchor.js +1 -1
  184. package/dist/src/tool-budget.js +136 -0
  185. package/dist/src/tool-packet.js +1 -1
  186. package/dist/src/travel-slots.js +1 -1
  187. package/dist/src/travel-timeline.js +1 -1
  188. package/dist/src/unified.js +1 -1
  189. package/dist/src/wish-pool.js +1 -1
  190. package/dist/src/z3-shared.js +1 -1
  191. package/extension/README.md +50 -0
  192. package/extension/background.js +160 -0
  193. package/extension/content-bridge.js +41 -0
  194. package/extension/content-main.js +62 -0
  195. package/extension/manifest.json +32 -0
  196. package/package.json +291 -12
  197. package/schemas/booking.surface.v1.schema.json +927 -0
  198. package/schemas/booking.surface.v2.schema.json +61 -0
  199. package/ts/capabilities/fact-log.ts +54 -0
  200. package/ts/capabilities/flyai.ts +16 -3
  201. package/ts/capabilities/hbcli.ts +46 -0
  202. package/ts/capabilities/session/benchmark.ts +4 -1
  203. package/ts/capabilities/session/extension-bridge.ts +377 -0
  204. package/ts/capabilities/session/extension-channel.ts +95 -0
  205. package/ts/capabilities/session/extension-distribution.ts +264 -0
  206. package/ts/capabilities/session/golden-score.ts +139 -0
  207. package/ts/capabilities/session/health-watch.ts +257 -0
  208. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  209. package/ts/capabilities/session/wizard.ts +140 -0
  210. package/ts/capabilities/session-login.ts +63 -2
  211. package/ts/capabilities/session-search.ts +105 -3
  212. package/ts/capabilities/weather.ts +141 -52
  213. package/ts/package.json +3 -3
  214. package/ts/src/artifact-gate.ts +365 -0
  215. package/ts/src/benchmark-agent-conformance.ts +448 -0
  216. package/ts/src/benchmark-environment-bridge.ts +348 -0
  217. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  218. package/ts/src/benchmark-tool-isolation.ts +166 -0
  219. package/ts/src/bookable-facts.ts +557 -0
  220. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  221. package/ts/src/booking-surface/canonical-schema.js +113 -0
  222. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  223. package/ts/src/booking-surface/contracts.ts +380 -0
  224. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  225. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  226. package/ts/src/booking-surface/error-codes.ts +101 -0
  227. package/ts/src/booking-surface/index.ts +12 -0
  228. package/ts/src/booking-surface/profile.ts +42 -0
  229. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  230. package/ts/src/booking-surface/runtime.ts +483 -0
  231. package/ts/src/booking-surface/server-v2.ts +247 -0
  232. package/ts/src/booking-surface/server.ts +324 -0
  233. package/ts/src/booking-surface/startup.ts +196 -0
  234. package/ts/src/booking-surface/validation-v2.ts +205 -0
  235. package/ts/src/booking-surface/validation.ts +453 -0
  236. package/ts/src/index.ts +161 -14
  237. package/ts/src/state-ledger.ts +1 -0
  238. package/ts/src/tool-budget.ts +165 -0
@@ -0,0 +1,234 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ export const DIST_ASSET_TARBALL = 'gotry-session-bridge.tar.gz';
6
+ export const DIST_ASSET_STORE_ZIP = 'gotry-session-bridge-store.zip';
7
+ export const DIST_ASSET_MANIFEST = 'extension-dist-manifest.json';
8
+ export const DEFAULT_RELEASE_BASE = 'https://github.com/Danceiny/gotry/releases';
9
+ export function compareVersions(a, b) {
10
+ const pa = String(a).split('.').map((s)=>Number.parseInt(s, 10) || 0);
11
+ const pb = String(b).split('.').map((s)=>Number.parseInt(s, 10) || 0);
12
+ const n = Math.max(pa.length, pb.length);
13
+ for(let i = 0; i < n; i += 1){
14
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
15
+ if (d !== 0) return d;
16
+ }
17
+ return 0;
18
+ }
19
+ export function distAssetUrls(releaseBase = DEFAULT_RELEASE_BASE) {
20
+ const base = releaseBase.replace(/\/+$/, '');
21
+ return {
22
+ tarball: `${base}/latest/download/${DIST_ASSET_TARBALL}`,
23
+ storeZip: `${base}/latest/download/${DIST_ASSET_STORE_ZIP}`,
24
+ manifest: `${base}/latest/download/${DIST_ASSET_MANIFEST}`
25
+ };
26
+ }
27
+ const SHA256_RE = /^[0-9a-f]{64}$/;
28
+ export function parseDistManifest(raw) {
29
+ let obj;
30
+ try {
31
+ obj = JSON.parse(raw);
32
+ } catch {
33
+ throw new Error('dist-manifest 不是合法 JSON');
34
+ }
35
+ const version = obj.version;
36
+ const tarball = obj.tarball;
37
+ const tarballSha256 = obj.tarballSha256;
38
+ const zip = obj.zip;
39
+ const zipSha256 = obj.zipSha256;
40
+ if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) throw new Error('dist-manifest.version 缺失或非 x.y.z');
41
+ if (tarball !== DIST_ASSET_TARBALL) throw new Error(`dist-manifest.tarball 资产名漂移(期望 ${DIST_ASSET_TARBALL})`);
42
+ if (zip !== DIST_ASSET_STORE_ZIP) throw new Error(`dist-manifest.zip 资产名漂移(期望 ${DIST_ASSET_STORE_ZIP})`);
43
+ if (typeof tarballSha256 !== 'string' || !SHA256_RE.test(tarballSha256)) throw new Error('dist-manifest.tarballSha256 非 64-hex');
44
+ if (typeof zipSha256 !== 'string' || !SHA256_RE.test(zipSha256)) throw new Error('dist-manifest.zipSha256 非 64-hex');
45
+ return {
46
+ version,
47
+ tarball,
48
+ tarballSha256,
49
+ zip,
50
+ zipSha256,
51
+ builtFromCommit: typeof obj.builtFromCommit === 'string' ? obj.builtFromCommit : undefined
52
+ };
53
+ }
54
+ export function sha256Hex(bytes) {
55
+ return createHash('sha256').update(bytes).digest('hex');
56
+ }
57
+ export function readExtensionKey(extensionDir) {
58
+ const p = join(extensionDir, 'manifest.json');
59
+ if (!existsSync(p)) return undefined;
60
+ try {
61
+ const m = JSON.parse(readFileSync(p, 'utf8'));
62
+ return typeof m.key === 'string' ? m.key : undefined;
63
+ } catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ export function readExtensionVersion(extensionDir) {
68
+ const p = join(extensionDir, 'manifest.json');
69
+ if (!existsSync(p)) return undefined;
70
+ try {
71
+ const m = JSON.parse(readFileSync(p, 'utf8'));
72
+ return typeof m.version === 'string' ? m.version : undefined;
73
+ } catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ function runTar(args, spawnFn, timeoutMs) {
78
+ return new Promise((resolve)=>{
79
+ const child = spawnFn('tar', args, {
80
+ stdio: 'ignore'
81
+ });
82
+ let done = false;
83
+ const timer = setTimeout(()=>{
84
+ if (!done) {
85
+ done = true;
86
+ try {
87
+ child.kill('SIGKILL');
88
+ } catch {}
89
+ resolve({
90
+ ok: false,
91
+ error: `tar 超时(>${timeoutMs}ms)`
92
+ });
93
+ }
94
+ }, timeoutMs);
95
+ child.on('error', (e)=>{
96
+ if (!done) {
97
+ done = true;
98
+ clearTimeout(timer);
99
+ resolve({
100
+ ok: false,
101
+ error: `tar 不可用:${e.message}`
102
+ });
103
+ }
104
+ });
105
+ child.on('close', (code)=>{
106
+ if (!done) {
107
+ done = true;
108
+ clearTimeout(timer);
109
+ resolve(code === 0 ? {
110
+ ok: true
111
+ } : {
112
+ ok: false,
113
+ error: `tar exit ${code}`
114
+ });
115
+ }
116
+ });
117
+ });
118
+ }
119
+ async function fetchBytes(url, fetchFn, timeoutMs) {
120
+ try {
121
+ const res = await fetchFn(url, {
122
+ signal: AbortSignal.timeout(timeoutMs)
123
+ });
124
+ if (!res.ok) return {
125
+ ok: false,
126
+ error: `HTTP ${res.status} ${url}`
127
+ };
128
+ const buf = new Uint8Array(await res.arrayBuffer());
129
+ return {
130
+ ok: true,
131
+ bytes: buf
132
+ };
133
+ } catch (e) {
134
+ return {
135
+ ok: false,
136
+ error: `下载失败 ${e.message}`
137
+ };
138
+ }
139
+ }
140
+ export async function installExtensionFromGithub(opts) {
141
+ const timeoutMs = opts.timeoutMs ?? 60_000;
142
+ const fetchFn = opts.fetch ?? fetch;
143
+ const spawnFn = opts.spawnFn ?? spawn;
144
+ const urls = distAssetUrls(opts.releaseBase);
145
+ const fail = (error)=>({
146
+ ok: false,
147
+ action: 'fallback-bundled',
148
+ error
149
+ });
150
+ const mRes = await fetchBytes(urls.manifest, fetchFn, timeoutMs);
151
+ if (!mRes.ok) return fail(mRes.error);
152
+ let manifest;
153
+ try {
154
+ manifest = parseDistManifest(new TextDecoder().decode(mRes.bytes));
155
+ } catch (e) {
156
+ return fail(`dist-manifest 校验失败:${e.message}`);
157
+ }
158
+ const previousVersion = readExtensionVersion(opts.destDir);
159
+ if (previousVersion != null && compareVersions(manifest.version, previousVersion) <= 0) {
160
+ return {
161
+ ok: true,
162
+ action: 'up-to-date',
163
+ version: manifest.version,
164
+ previousVersion
165
+ };
166
+ }
167
+ if (opts.checkOnly) {
168
+ return {
169
+ ok: true,
170
+ action: 'up-to-date',
171
+ version: manifest.version,
172
+ previousVersion
173
+ };
174
+ }
175
+ const tRes = await fetchBytes(urls.tarball, fetchFn, timeoutMs);
176
+ if (!tRes.ok) return fail(tRes.error);
177
+ const actual = sha256Hex(tRes.bytes);
178
+ if (actual !== manifest.tarballSha256) return fail(`SHA256 不符(期望 ${manifest.tarballSha256.slice(0, 12)}… 实得 ${actual.slice(0, 12)}…)`);
179
+ const incoming = `${opts.destDir}.incoming-${process.pid}-${Date.now()}`;
180
+ const tarPath = `${incoming}.tar.gz`;
181
+ try {
182
+ rmSync(incoming, {
183
+ recursive: true,
184
+ force: true
185
+ });
186
+ mkdirSync(incoming, {
187
+ recursive: true
188
+ });
189
+ writeFileSync(tarPath, tRes.bytes);
190
+ const ext = await runTar([
191
+ '-xzf',
192
+ tarPath,
193
+ '-C',
194
+ incoming,
195
+ '--strip-components',
196
+ '1'
197
+ ], spawnFn, timeoutMs);
198
+ if (!ext.ok) return fail(ext.error);
199
+ const incomingKey = readExtensionKey(incoming);
200
+ if (incomingKey == null) return fail('解压产物缺 manifest.json/key(非法包)');
201
+ if (opts.pinnedSourceDir) {
202
+ const pinnedKey = readExtensionKey(opts.pinnedSourceDir);
203
+ if (pinnedKey != null && incomingKey !== pinnedKey) return fail('扩展 key 漂移(通道产物与 bundled 不同 ID——拒绝安装,防端口池/host 权限漂移)');
204
+ }
205
+ const incomingVersion = readExtensionVersion(incoming);
206
+ if (incomingVersion !== manifest.version) return fail(`解压产物版本(${incomingVersion})与 dist-manifest(${manifest.version})不符`);
207
+ rmSync(opts.destDir, {
208
+ recursive: true,
209
+ force: true
210
+ });
211
+ cpSync(incoming, opts.destDir, {
212
+ recursive: true
213
+ });
214
+ return {
215
+ ok: true,
216
+ action: 'installed',
217
+ version: manifest.version,
218
+ previousVersion
219
+ };
220
+ } catch (e) {
221
+ return fail(`落位失败 ${e.message}`);
222
+ } finally{
223
+ rmSync(incoming, {
224
+ recursive: true,
225
+ force: true
226
+ });
227
+ rmSync(tarPath, {
228
+ force: true
229
+ });
230
+ }
231
+ }
232
+
233
+
234
+ //# sourceURL=ts/capabilities/session/extension-distribution.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=ts/capabilities/session/extract.ts
@@ -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
@@ -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=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=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