@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
@@ -108,9 +108,9 @@ if (process.env.GOTRY_SESSION_LIVE === '0') {
108
108
  destination: '丽江',
109
109
  depDate: '2026-10-01'
110
110
  });
111
- const sentinelBlocked = fr.verdict === 'error' && /sentinel|block/i.test(fr.error ?? '');
111
+ const sentinelBlocked = fr.verdict === 'error' && /sentinel|block|trial limit/i.test(fr.error ?? '');
112
112
  if (sentinelBlocked) {
113
- console.log(' WARN - 飞猪 Sentinel 限流(2026-08-28 实测,配额未文档化)——降级合同验证通过,跳过 hit 断言');
113
+ console.log(' WARN - 飞猪上游限流(Sentinel 2026-08-28 / trial-limit 429 2026-08-31 实测,配额未文档化)——降级合同验证通过,跳过 hit 断言');
114
114
  assert(fr.ok === false && /\[实时API:flyai@error@/.test(fr.evidence), '限流降级:结构化 error + 证据链错误形');
115
115
  } else {
116
116
  assert(fr.ok === true && fr.verdict === 'hit', '上海→丽江 hit', fr);
@@ -137,6 +137,48 @@ if (process.env.GOTRY_SESSION_LIVE === '0') {
137
137
  recursive: true,
138
138
  force: true
139
139
  });
140
+ }{
141
+ const bigDir = mkdtempSync(join(tmpdir(), 'flyai-trunc-'));
142
+ const payloadJs = join(bigDir, 'payload.js');
143
+ const itemCount = 100;
144
+ writeFileSync(payloadJs, `
145
+ const items = []
146
+ for (let i = 0; i < ${itemCount}; i++) {
147
+ items.push({
148
+ journeys: [{ segments: [{
149
+ marketingTransportName: '吉祥航空', marketingTransportNo: 'HO' + (1000 + i),
150
+ depDateTime: '2026-09-15 08:30', arrDateTime: '2026-09-15 12:10',
151
+ depStationName: '浦东国际机场', arrStationName: '长水机场', duration: '220', seatClassName: '经济舱',
152
+ }] }],
153
+ ticketPrice: String(800 + i),
154
+ jumpUrl: 'https://fliggy.com/item/' + 'x'.repeat(800) + i,
155
+ })
156
+ }
157
+ const payload = JSON.stringify({ data: { itemList: items } })
158
+ const step = Math.ceil(payload.length / 100)
159
+ for (let i = 0; i < 100; i++) process.stdout.write(payload.slice(i * step, (i + 1) * step))
160
+ process.exit(0)
161
+ `);
162
+ const bigBin = join(bigDir, 'flyai-cli-big');
163
+ writeFileSync(bigBin, `#!/bin/sh\nexec node "${payloadJs}"\n`, {
164
+ mode: 0o755
165
+ });
166
+ const tr = await flyaiSearch({
167
+ kind: 'flight',
168
+ origin: '上海',
169
+ destination: '昆明',
170
+ depDate: '2026-09-15',
171
+ cliBin: bigBin
172
+ });
173
+ assert(tr.ok === true && tr.verdict === 'hit' && tr.options?.length === itemCount, `大载荷 ${itemCount} 条(>64KB)完整解析无截断(#84)`, {
174
+ verdict: tr.verdict,
175
+ options: tr.options?.length,
176
+ error: tr.error
177
+ });
178
+ rmSync(bigDir, {
179
+ recursive: true,
180
+ force: true
181
+ });
140
182
  }console.log('G. sessionFlightSearch(live,Chrome+携程;默认跳过,GOTRY_SESSION_LIVE=1 显式开启)');
141
183
  if (process.env.GOTRY_SESSION_LIVE !== '1') {
142
184
  console.log(' SKIP - 会话 live 探针默认关(测试不再自动开浏览器窗口;GOTRY_SESSION_LIVE=1 显式开启)');
@@ -193,24 +235,28 @@ if (process.env.GOTRY_SESSION_LIVE !== '1') {
193
235
  }
194
236
  console.log('H. flyaiSearch hotel(飞猪官方 search-hotel)');
195
237
  {
196
- const hr = await flyaiSearch({
197
- kind: 'hotel',
198
- destName: '大理',
199
- checkInDate: '2026-10-01',
200
- checkOutDate: '2026-10-03'
201
- });
202
- const hSentinel = hr.verdict === 'error' && /sentinel|block/i.test(hr.error ?? '');
203
- if (hSentinel) {
204
- console.log(' WARN - 飞猪 Sentinel 限流——降级合同验证通过,跳过 hit 断言');
205
- assert(hr.ok === false && /\[实时API:flyai@error@/.test(hr.evidence), '酒店限流降级:结构化 error + 证据链错误形');
206
- } else if (hr.ok === false) {
207
- console.log(` WARN - flyai hotel 端点降级(${String(hr.error).slice(0, 60)})——证据链合同通过,hit 断言跳过`);
208
- assert(/\[实时API:flyai@error@/.test(hr.evidence), '端点降级仍带证据链错误形');
238
+ if (process.env.GOTRY_SESSION_LIVE === '0') {
239
+ console.log(' SKIP - GOTRY_SESSION_LIVE=0(离线门禁不调用 FlyAI 酒店实时端点)');
209
240
  } else {
210
- assert(hr.verdict === 'hit' && (hr.hotels?.length ?? 0) >= 1, '大理酒店 hit', hr.hotels?.[0]);
211
- assert(hr.hotels?.every((h)=>!!h.name && !!h.jumpUrl) ?? false, '条目结构化(name + jumpUrl 透传)', hr.hotels?.[0]);
241
+ const hr = await flyaiSearch({
242
+ kind: 'hotel',
243
+ destName: '大理',
244
+ checkInDate: '2026-10-01',
245
+ checkOutDate: '2026-10-03'
246
+ });
247
+ const hSentinel = hr.verdict === 'error' && /sentinel|block/i.test(hr.error ?? '');
248
+ if (hSentinel) {
249
+ console.log(' WARN - 飞猪 Sentinel 限流——降级合同验证通过,跳过 hit 断言');
250
+ assert(hr.ok === false && /\[实时API:flyai@error@/.test(hr.evidence), '酒店限流降级:结构化 error + 证据链错误形');
251
+ } else if (hr.ok === false) {
252
+ console.log(` WARN - flyai hotel 端点降级(${String(hr.error).slice(0, 60)})——证据链合同通过,hit 断言跳过`);
253
+ assert(/\[实时API:flyai@error@/.test(hr.evidence), '端点降级仍带证据链错误形');
254
+ } else {
255
+ assert(hr.verdict === 'hit' && (hr.hotels?.length ?? 0) >= 1, '大理酒店 hit', hr.hotels?.[0]);
256
+ assert(hr.hotels?.every((h)=>!!h.name && !!h.jumpUrl) ?? false, '条目结构化(name + jumpUrl 透传)', hr.hotels?.[0]);
257
+ }
258
+ assert(/\[实时API:flyai/.test(hr.evidence), '证据链 [实时API:flyai@*]');
212
259
  }
213
- assert(/\[实时API:flyai/.test(hr.evidence), '证据链 [实时API:flyai@*]');
214
260
  const fakeDir = mkdtempSync(join(tmpdir(), 'flyai-hotel-fake-'));
215
261
  const fakeCliH = join(fakeDir, 'flyai-hotel-fake');
216
262
  writeFileSync(fakeCliH, '#!/bin/sh\necho \'{"data":{"itemList":[{"name":"大理A 酒店","shId":"1","star":"高档型","rate":null,"price":"\\u00a57xx","address":"addr","interestsPoi":"近洱海","detailUrl":"https://router.feizhu.com/x"},{"star":"舒适型"}]}}\'\nexit 0\n', {
@@ -399,7 +445,11 @@ console.log('H. flyaiSearch hotel(飞猪官方 search-hotel)');
399
445
  } else {
400
446
  console.log(' SKIP - 登录 live 探针默认关(GOTRY_SESSION_LIVE=1 opt-in;工具面/桌面入口才真调)');
401
447
  }
402
- }console.log(`\nSESSION P1: ${pass} pass, ${fail} fail`);
448
+ }if (process.env.GOTRY_SESSION_TEST_FORCE_FAILURE === '1') {
449
+ assert(false, 'forced session failure propagation proof');
450
+ }
451
+ console.log(`\nSESSION P1: ${pass} pass, ${fail} fail`);
452
+ if (fail > 0) process.exitCode = 1;
403
453
 
404
454
 
405
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-tests.ts
455
+ //# sourceURL=ts/scripts/session-tests.ts
@@ -0,0 +1,338 @@
1
+ import { mkdirSync, writeFileSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { evaluateDoubleSource, SESSION_FIELD_ACCURACY_THRESHOLD } from '../capabilities/session/benchmark.js';
5
+ import { sessionFlightSearch } from '../capabilities/session-search.js';
6
+ import { flyaiSearch } from '../capabilities/flyai.js';
7
+ import { loadStaticFlightSnapshot, parseGoldenSource, resolveStaticGolden } from '../capabilities/session/static-flight-golden.js';
8
+ import { scoreSessionAgainstGoldenBand } from '../capabilities/session/golden-score.js';
9
+ function loadFlightQueries() {
10
+ const golden = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'data', 'session-golden-20.json'), 'utf8'));
11
+ return golden.queries.filter((q)=>q.id.startsWith('sf-')).slice(0, 8);
12
+ }
13
+ function loadManualGolden() {
14
+ return JSON.parse(readFileSync(join(import.meta.dirname, '..', 'data', 'sf-golden-manifest.json'), 'utf8'));
15
+ }
16
+ function manualToRecord(m, sample) {
17
+ return {
18
+ query_id: m.query_id,
19
+ route_segments: [
20
+ {
21
+ from: m.from,
22
+ to: m.to,
23
+ departure_at: `${m.date}T${sample.depTime}:00+08:00`,
24
+ arrival_at: `${m.date}T${sample.arrTime}:00+08:00`,
25
+ transport_number: sample.flightNo
26
+ }
27
+ ],
28
+ journey_type: 'direct',
29
+ currency: 'CNY',
30
+ price: sample.price,
31
+ source: 'manual-golden',
32
+ fetched_at: new Date().toISOString(),
33
+ verdict: 'hit',
34
+ latency_ms: 0,
35
+ read_guard_blocked: 0
36
+ };
37
+ }
38
+ async function flyaiOfficial(q) {
39
+ const started = Date.now();
40
+ try {
41
+ const r = await flyaiSearch({
42
+ kind: 'flight',
43
+ origin: q.from,
44
+ destination: q.to,
45
+ depDate: q.date,
46
+ timeoutMs: 30_000
47
+ });
48
+ const latencyMs = Date.now() - started;
49
+ if (r.ok && r.options && r.options.length > 0) {
50
+ const sorted = r.options.slice().sort((a, b)=>Date.parse(a.depDateTime) - Date.parse(b.depDateTime));
51
+ const opt = sorted[0];
52
+ const rec = {
53
+ query_id: q.id,
54
+ route_segments: [
55
+ {
56
+ from: q.from,
57
+ to: q.to,
58
+ departure_at: opt.depDateTime,
59
+ arrival_at: opt.arrDateTime,
60
+ transport_number: opt.no
61
+ }
62
+ ],
63
+ journey_type: 'direct',
64
+ currency: 'CNY',
65
+ price: opt.price,
66
+ source: 'flyai',
67
+ fetched_at: new Date().toISOString(),
68
+ verdict: 'hit',
69
+ latency_ms: latencyMs,
70
+ read_guard_blocked: 0
71
+ };
72
+ return {
73
+ record: rec,
74
+ latencyMs
75
+ };
76
+ }
77
+ return {
78
+ record: {
79
+ ...emptyOfficialRecord(q, 'flyai'),
80
+ latency_ms: latencyMs,
81
+ verdict: 'error'
82
+ },
83
+ latencyMs,
84
+ error: r.error ?? 'no options'
85
+ };
86
+ } catch (e) {
87
+ return {
88
+ record: emptyOfficialRecord(q, 'flyai'),
89
+ latencyMs: Date.now() - started,
90
+ error: e instanceof Error ? e.message : String(e)
91
+ };
92
+ }
93
+ }
94
+ function manualOfficial(q, manifest) {
95
+ const started = Date.now();
96
+ const m = manifest.matches.find((x)=>x.query_id === q.id);
97
+ if (!m) return {
98
+ record: emptyOfficialRecord(q, 'manual-golden'),
99
+ latencyMs: Date.now() - started
100
+ };
101
+ const depMedian = hhmmMid(m.window_dep_local.earliest, m.window_dep_local.latest);
102
+ const arrMedian = hhmmMid(m.window_arr_local.earliest, m.window_arr_local.latest);
103
+ const priceMedian = Math.round((m.price_cny.min + m.price_cny.max) / 2);
104
+ return {
105
+ record: manualToRecord(m, {
106
+ depTime: depMedian,
107
+ arrTime: arrMedian,
108
+ flightNo: m.known_flights[0] ?? '',
109
+ price: priceMedian
110
+ }),
111
+ latencyMs: Date.now() - started
112
+ };
113
+ }
114
+ function hhmmMid(a, b) {
115
+ const aMin = parseInt(a.split(':')[0], 10) * 60 + parseInt(a.split(':')[1], 10);
116
+ const bMin = parseInt(b.split(':')[0], 10) * 60 + parseInt(b.split(':')[1], 10);
117
+ const mid = Math.round((aMin + bMin) / 2);
118
+ return `${Math.floor(mid / 60).toString().padStart(2, '0')}:${(mid % 60).toString().padStart(2, '0')}`;
119
+ }
120
+ function emptyOfficialRecord(q, source) {
121
+ return {
122
+ query_id: q.id,
123
+ route_segments: [],
124
+ journey_type: 'direct',
125
+ currency: '',
126
+ price: 0,
127
+ source,
128
+ fetched_at: new Date().toISOString(),
129
+ verdict: 'error',
130
+ latency_ms: 0,
131
+ read_guard_blocked: 0
132
+ };
133
+ }
134
+ async function runOne(q, requestedSource, manifest, staticContext) {
135
+ const startedAt = new Date().toISOString();
136
+ let official = null;
137
+ let officialLatencyMs = null;
138
+ let effectiveSource = requestedSource === 'manual' ? 'manual-golden' : requestedSource;
139
+ let fallbackReason = null;
140
+ let estimatedFields = [];
141
+ let officialProvenance = null;
142
+ if (requestedSource === 'manual') {
143
+ const m = manualOfficial(q, manifest);
144
+ official = m.record;
145
+ officialLatencyMs = m.latencyMs;
146
+ } else if (requestedSource === 'flyai') {
147
+ const r = await flyaiOfficial(q);
148
+ official = r.record;
149
+ officialLatencyMs = r.latencyMs;
150
+ } else {
151
+ const started = Date.now();
152
+ const manual = manualOfficial(q, manifest);
153
+ const resolution = resolveStaticGolden({
154
+ query: q,
155
+ manualRecord: manual.record ?? emptyOfficialRecord(q, 'manual-golden'),
156
+ snapshot: staticContext.snapshot,
157
+ snapshotError: staticContext.error,
158
+ warn: (message)=>console.error(message)
159
+ });
160
+ official = resolution.record;
161
+ officialLatencyMs = Date.now() - started;
162
+ effectiveSource = resolution.effective_source;
163
+ fallbackReason = resolution.fallback_reason ?? null;
164
+ estimatedFields = resolution.estimated_fields;
165
+ officialProvenance = resolution.provenance;
166
+ }
167
+ const sessStarted = Date.now();
168
+ const sessRes = await sessionFlightSearch({
169
+ from: q.from,
170
+ to: q.to,
171
+ date: q.date,
172
+ timeoutMs: 25_000
173
+ });
174
+ const sessionLatencyMs = Date.now() - sessStarted;
175
+ const sessionVerdict = sessRes.verdict;
176
+ const sessionError = sessRes.error;
177
+ let session = null;
178
+ if (sessRes.verdict === 'hit' && sessRes.options && sessRes.options.length > 0) {
179
+ const sorted = sessRes.options.slice().sort((a, b)=>Date.parse(a.depDateTime) - Date.parse(b.depDateTime));
180
+ const opt = sorted[0];
181
+ session = {
182
+ query_id: q.id,
183
+ route_segments: [
184
+ {
185
+ from: q.from,
186
+ to: q.to,
187
+ departure_at: opt.depDateTime,
188
+ arrival_at: opt.arrDateTime,
189
+ transport_number: opt.flightNo
190
+ }
191
+ ],
192
+ journey_type: 'direct',
193
+ currency: 'CNY',
194
+ price: opt.price,
195
+ source: 'ctrip-flight',
196
+ fetched_at: new Date().toISOString(),
197
+ verdict: 'hit',
198
+ latency_ms: sessionLatencyMs,
199
+ read_guard_blocked: 0
200
+ };
201
+ } else {
202
+ session = {
203
+ query_id: q.id,
204
+ route_segments: [],
205
+ journey_type: 'direct',
206
+ currency: '',
207
+ price: 0,
208
+ source: 'ctrip-flight',
209
+ fetched_at: new Date().toISOString(),
210
+ verdict: sessRes.verdict === 'hit' || sessRes.verdict === 'miss' ? sessRes.verdict : 'error',
211
+ latency_ms: sessionLatencyMs,
212
+ read_guard_blocked: 0
213
+ };
214
+ }
215
+ const doubleSource = evaluateDoubleSource({
216
+ official: official ?? undefined,
217
+ session: session ?? undefined
218
+ });
219
+ let softResult = null;
220
+ const m = manifest.matches.find((x)=>x.query_id === q.id);
221
+ if (session?.verdict === 'hit' && m) {
222
+ softResult = scoreSessionAgainstGoldenBand(session, m, effectiveSource);
223
+ }
224
+ return {
225
+ query_id: q.id,
226
+ expected: {
227
+ from: q.from,
228
+ to: q.to,
229
+ date: q.date,
230
+ kind: q.kind
231
+ },
232
+ requested_source: requestedSource,
233
+ effective_source: effectiveSource,
234
+ fallback_reason: fallbackReason,
235
+ estimated_fields: estimatedFields,
236
+ official_provenance: officialProvenance,
237
+ official,
238
+ official_source: effectiveSource,
239
+ session,
240
+ doubleSource,
241
+ softScore: softResult,
242
+ officialLatencyMs,
243
+ sessionLatencyMs,
244
+ sessionVerdict: String(sessionVerdict),
245
+ sessionError,
246
+ started_at: startedAt
247
+ };
248
+ }
249
+ async function main() {
250
+ const args = process.argv.slice(2);
251
+ const requestedSource = parseGoldenSource(args);
252
+ const queries = loadFlightQueries();
253
+ if (queries.length < 8) {
254
+ console.error(`金标准 sf-* 不足 8 条(实际 ${queries.length})`);
255
+ process.exit(1);
256
+ }
257
+ const manifest = loadManualGolden();
258
+ const staticContext = {};
259
+ if (requestedSource === 'static') {
260
+ try {
261
+ staticContext.snapshot = loadStaticFlightSnapshot();
262
+ } catch (error) {
263
+ staticContext.error = error instanceof Error ? error.message : String(error);
264
+ }
265
+ }
266
+ const sourceDescription = requestedSource === 'manual' ? '手工 golden (sf-golden-manifest.json,零网络零 vendor)' : requestedSource === 'flyai' ? 'flyai (FlyAI 官方)' : 'static (OpenFlights route/carrier + manual time/price band,估算字段显式标注)';
267
+ console.log(`[sf-live-benchmark] requested official source = ${sourceDescription}`);
268
+ console.log(`[sf-live-benchmark] 阈值:字段准确率 ≥${SESSION_FIELD_ACCURACY_THRESHOLD * 100}%(软命中)`);
269
+ const runStartedAt = new Date().toISOString();
270
+ const runStartedMs = Date.now();
271
+ const records = [];
272
+ for (const q of queries){
273
+ if (records.length > 0) await new Promise((r)=>setTimeout(r, 35_000));
274
+ console.log(`\n[sf-live-benchmark] ${q.id} ${q.from}→${q.to} ${q.date}`);
275
+ const rec = await runOne(q, requestedSource, manifest, staticContext);
276
+ records.push(rec);
277
+ const evPath = join(homedir(), '.gotry', 'evidence', 'session', q.id, `${runStartedAt.replace(/[:.]/g, '-')}.json`);
278
+ mkdirSync(dirname(evPath), {
279
+ recursive: true
280
+ });
281
+ writeFileSync(evPath, JSON.stringify(rec, null, 2));
282
+ console.log(` official(requested=${rec.requested_source},effective=${rec.effective_source}): verdict=${rec.official?.verdict ?? '-'} latency=${rec.officialLatencyMs ?? 0}ms${rec.fallback_reason ? ` fallback=${rec.fallback_reason}` : ''}`);
283
+ console.log(` session(ctrip): verdict=${rec.sessionVerdict} latency=${rec.sessionLatencyMs ?? 0}ms${rec.sessionError ? ` err=${rec.sessionError.slice(0, 80)}` : ''}`);
284
+ console.log(` doubleSource: state=${rec.doubleSource.state} quota=${rec.doubleSource.quota_disposition} price_delta=${rec.doubleSource.price_delta ?? '-'} mismatches=${rec.doubleSource.mismatches.length}`);
285
+ if (rec.softScore) {
286
+ console.log(` soft score: ${(rec.softScore.accuracy * 100).toFixed(1)}% (${rec.softScore.correct}/${rec.softScore.total}) ${rec.softScore.pass ? '✅' : '❌'} missing=${JSON.stringify(rec.softScore.missing)} incorrect=${JSON.stringify(rec.softScore.incorrect)}`);
287
+ }
288
+ }
289
+ const accuracy_pass = records.filter((r)=>r.softScore?.pass === true).length;
290
+ const comparable = records.filter((r)=>r.doubleSource.state === 'comparable').length;
291
+ const hit = records.filter((r)=>r.sessionVerdict === 'hit').length;
292
+ const challenge = records.filter((r)=>r.sessionVerdict === 'challenged' || r.doubleSource.state === 'challenge_stop' || r.doubleSource.state === 'guard_violation').length;
293
+ const no_data = records.filter((r)=>r.official?.verdict !== 'hit' || r.sessionVerdict !== 'hit' && r.sessionVerdict !== 'challenged').length;
294
+ const live_under_15s = records.filter((r)=>r.sessionLatencyMs !== null && r.sessionVerdict === 'hit' && r.sessionLatencyMs < 15_000).length;
295
+ const effectiveSources = Array.from(new Set(records.map((record)=>record.effective_source)));
296
+ const fallbackCount = records.filter((record)=>record.fallback_reason !== null).length;
297
+ const summary = {
298
+ started_at: runStartedAt,
299
+ duration_ms: Date.now() - runStartedMs,
300
+ threshold: SESSION_FIELD_ACCURACY_THRESHOLD,
301
+ total: records.length,
302
+ accuracy_pass,
303
+ comparable,
304
+ hit,
305
+ challenge,
306
+ no_data,
307
+ live_under_15s,
308
+ requested_source: requestedSource,
309
+ effective_sources: effectiveSources,
310
+ fallback_count: fallbackCount,
311
+ golden_source: requestedSource === 'manual' ? 'manual-golden' : requestedSource,
312
+ records
313
+ };
314
+ const summaryPath = join(homedir(), '.gotry', 'evidence', 'session', 'sf-summary', `${runStartedAt.replace(/[:.]/g, '-')}.json`);
315
+ mkdirSync(dirname(summaryPath), {
316
+ recursive: true
317
+ });
318
+ writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
319
+ console.log(`\n──── sf-live-benchmark 汇总 ────`);
320
+ console.log(`golden requested = ${summary.requested_source}`);
321
+ console.log(`golden effective = ${summary.effective_sources.join(',')};fallback=${summary.fallback_count}`);
322
+ console.log(`跑批 query 数: ${records.length}`);
323
+ console.log(`verdict=hit: ${hit}/${records.length}`);
324
+ console.log(`双源合同=comparable: ${comparable}/${records.length}`);
325
+ console.log(`字段准确率 ≥${SESSION_FIELD_ACCURACY_THRESHOLD * 100}% (软命中): ${accuracy_pass}/${records.filter((r)=>r.softScore !== null).length}`);
326
+ console.log(`live <15s (hit): ${live_under_15s}/${hit || 0}`);
327
+ console.log(`challenge/guard 触发: ${challenge}`);
328
+ console.log(`总耗时: ${summary.duration_ms}ms`);
329
+ process.exit(0);
330
+ }
331
+ main().catch((e)=>{
332
+ console.error(`[sf-live-benchmark] 异常: ${e instanceof Error ? e.message : String(e)}`);
333
+ console.error(e instanceof Error ? e.stack : '');
334
+ process.exit(1);
335
+ });
336
+
337
+
338
+ //# sourceURL=ts/scripts/sf-live-benchmark.ts
@@ -0,0 +1,21 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { join } from 'node:path';
4
+ const tsxCli = join(import.meta.dirname, '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');
5
+ const runner = join(import.meta.dirname, 'sf-live-benchmark.ts');
6
+ const unknown = spawnSync(process.execPath, [
7
+ tsxCli,
8
+ runner,
9
+ '--golden=ctrip-open'
10
+ ], {
11
+ cwd: join(import.meta.dirname, '..'),
12
+ encoding: 'utf8',
13
+ timeout: 5_000
14
+ });
15
+ assert.equal(unknown.status, 1, `未知 vendor 应在网络调用前 exit 1,实际 status=${unknown.status} error=${unknown.error?.message ?? '-'}`);
16
+ assert.match(unknown.stderr, /不支持的 golden vendor: ctrip-open/);
17
+ assert.equal(unknown.stdout.includes('sf-01 上海→丽江'), false, 'fail-closed 前不得启动任何 query');
18
+ console.log('SF LIVE CLI TESTS: unknown vendor fail-closed before network OK');
19
+
20
+
21
+ //# sourceURL=ts/scripts/sf-live-cli-tests.ts
@@ -0,0 +1,108 @@
1
+ import assert from 'node:assert/strict';
2
+ const modulePath = '../capabilities/session/golden-score.js';
3
+ const scorer = await import(modulePath).catch(()=>({}));
4
+ assert.equal(typeof scorer.scoreSessionAgainstGoldenBand, 'function', 'soft score 必须从 live runner 抽出,static provider 才能走同一离线评分合同');
5
+ const band = {
6
+ query_id: 'sf-01',
7
+ from: '上海',
8
+ to: '丽江',
9
+ date: '2026-10-01',
10
+ window_dep_local: {
11
+ earliest: '06:00',
12
+ latest: '22:00'
13
+ },
14
+ window_arr_local: {
15
+ earliest: '10:00',
16
+ latest: '23:59'
17
+ },
18
+ duration_min: {
19
+ min: 180,
20
+ max: 720
21
+ },
22
+ price_cny: {
23
+ min: 800,
24
+ max: 4000
25
+ },
26
+ transport_hint: '公开班期承运提示',
27
+ known_flights: [
28
+ 'HO5577',
29
+ 'MU6145',
30
+ '9C8779'
31
+ ]
32
+ };
33
+ const session = {
34
+ query_id: 'sf-01',
35
+ route_segments: [
36
+ {
37
+ from: '上海',
38
+ to: '丽江',
39
+ departure_at: '2026-10-01T06:45:00+08:00',
40
+ arrival_at: '2026-10-01T10:15:00+08:00',
41
+ transport_number: 'MU6145'
42
+ }
43
+ ],
44
+ journey_type: 'direct',
45
+ currency: 'CNY',
46
+ price: 1200,
47
+ source: 'ctrip-flight',
48
+ fetched_at: '2026-08-30T12:00:00.000Z',
49
+ verdict: 'hit',
50
+ latency_ms: 1200,
51
+ read_guard_blocked: 0
52
+ };
53
+ const score = scorer.scoreSessionAgainstGoldenBand(session, band, 'static-openflights+manual-band');
54
+ assert.deepEqual(score, {
55
+ pass: true,
56
+ total: 13,
57
+ correct: 13,
58
+ accuracy: 1,
59
+ missing: [],
60
+ incorrect: [],
61
+ golden_source: 'static-openflights+manual-band'
62
+ });
63
+ const wrongOrigin = scorer.scoreSessionAgainstGoldenBand({
64
+ ...session,
65
+ route_segments: [
66
+ {
67
+ ...session.route_segments[0],
68
+ from: '广州'
69
+ }
70
+ ]
71
+ }, band, 'static-openflights+manual-band');
72
+ assert.equal(wrongOrigin.accuracy, 12 / 13);
73
+ assert.equal(wrongOrigin.pass, false, 'from/to 是硬字段;高于 90% 也不能放过错误 OD');
74
+ assert.deepEqual(wrongOrigin.incorrect, [
75
+ 'route_segments[0].from'
76
+ ]);
77
+ const missingPrice = scorer.scoreSessionAgainstGoldenBand({
78
+ ...session,
79
+ price: 0
80
+ }, band, 'static-openflights+manual-band');
81
+ assert.equal(missingPrice.total, 13, '价格缺失不能靠缩小分母抬高准确率');
82
+ assert.equal(missingPrice.pass, false);
83
+ assert.deepEqual(missingPrice.missing, [
84
+ 'price'
85
+ ]);
86
+ const wrongDate = scorer.scoreSessionAgainstGoldenBand({
87
+ ...session,
88
+ route_segments: [
89
+ {
90
+ ...session.route_segments[0],
91
+ departure_at: '2026-10-02T06:45:00+08:00',
92
+ arrival_at: '2026-10-02T10:15:00+08:00'
93
+ }
94
+ ]
95
+ }, band, 'static-openflights+manual-band');
96
+ assert.equal(wrongDate.pass, false, '仅 HH:mm 落窗但服务日期错误时必须 fail-closed');
97
+ assert.ok(wrongDate.incorrect.includes('route_segments[0].departure_at.date'));
98
+ assert.ok(wrongDate.incorrect.includes('route_segments[0].arrival_at.date'));
99
+ const wrongSessionSource = scorer.scoreSessionAgainstGoldenBand({
100
+ ...session,
101
+ source: 'manual-golden'
102
+ }, band, 'static-openflights+manual-band');
103
+ assert.equal(wrongSessionSource.pass, false, 'golden 自身不能冒充待核验的 Ctrip session 来源');
104
+ assert.ok(wrongSessionSource.incorrect.includes('source'));
105
+ console.log('SF SOFT SCORE TESTS: fixed 13-field score + hard OD/date/session-source fail-closed OK');
106
+
107
+
108
+ //# sourceURL=ts/scripts/sf-soft-score-tests.ts
@@ -0,0 +1,93 @@
1
+ import { mkdirSync, writeFileSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { SESSION_FIELD_ACCURACY_THRESHOLD } from '../capabilities/session/benchmark.js';
5
+ const evidenceRoot = join(homedir(), '.gotry', 'evidence', 'session');
6
+ function main() {
7
+ const out = {
8
+ started_at: new Date().toISOString(),
9
+ total: 0,
10
+ threshold: SESSION_FIELD_ACCURACY_THRESHOLD,
11
+ accuracy_pass: 0,
12
+ accuracy_eligible: 0,
13
+ comparable: 0,
14
+ hit: 0,
15
+ challenge: 0,
16
+ live_under_15s: 0,
17
+ sources: {
18
+ manual_golden_query_ids: [],
19
+ flyai_query_ids: []
20
+ },
21
+ records: []
22
+ };
23
+ for(let i = 1; i <= 8; i += 1){
24
+ const q = `sf-${i.toString().padStart(2, '0')}`;
25
+ const qDir = join(evidenceRoot, q);
26
+ try {
27
+ const files = readdirSync(qDir).sort();
28
+ if (files.length === 0) continue;
29
+ const manualGoldens = files.filter((f)=>{
30
+ try {
31
+ const r = JSON.parse(readFileSync(join(qDir, f), 'utf8'));
32
+ return r.official_source === 'manual-golden' || r.official?.source === 'manual-golden';
33
+ } catch {
34
+ return false;
35
+ }
36
+ });
37
+ const fileName = manualGoldens.length > 0 ? manualGoldens[manualGoldens.length - 1] : files[files.length - 1];
38
+ const r = JSON.parse(readFileSync(join(qDir, fileName), 'utf8'));
39
+ out.total += 1;
40
+ if (r.doubleSource?.state === 'comparable') out.comparable += 1;
41
+ if (r.session?.verdict === 'hit') out.hit += 1;
42
+ if (r.session?.verdict === 'challenged') out.challenge += 1;
43
+ if (r.softScore) {
44
+ out.accuracy_eligible += 1;
45
+ if (r.softScore.pass) out.accuracy_pass += 1;
46
+ }
47
+ if (r.sessionLatencyMs !== null && r.session?.verdict === 'hit' && r.sessionLatencyMs < 15_000) out.live_under_15s += 1;
48
+ const seg0 = r.session?.verdict === 'hit' && r.session.route_segments?.length ? r.session.route_segments[0] : null;
49
+ const sessionFlight = seg0 ? {
50
+ flight_no: seg0.transport_number,
51
+ dep: seg0.departure_at,
52
+ arr: seg0.arrival_at,
53
+ price: r.session.price
54
+ } : null;
55
+ out.records.push({
56
+ query_id: q,
57
+ expected: r.expected,
58
+ official_source: r.official_source,
59
+ session_verdict: r.session?.verdict,
60
+ session_flight: sessionFlight,
61
+ soft_score: r.softScore ? {
62
+ accuracy: r.softScore.accuracy,
63
+ pass: r.softScore.pass,
64
+ missing: r.softScore.missing,
65
+ incorrect: r.softScore.incorrect
66
+ } : null,
67
+ session_latency_ms: r.sessionLatencyMs,
68
+ session_error: r.sessionError
69
+ });
70
+ } catch {}
71
+ }
72
+ for (const r of out.records){
73
+ if (r.official_source === 'manual-golden') out.sources.manual_golden_query_ids.push(r.query_id);
74
+ else out.sources.flyai_query_ids.push(r.query_id);
75
+ }
76
+ const summaryPath = join(evidenceRoot, 'sf-summary', `${out.started_at.replace(/[:.]/g, '-')}.json`);
77
+ mkdirSync(join(evidenceRoot, 'sf-summary'), {
78
+ recursive: true
79
+ });
80
+ writeFileSync(summaryPath, JSON.stringify(out, null, 2));
81
+ console.log(`──── sf-summary (unified,8 query) ────`);
82
+ console.log(`total: ${out.total}`);
83
+ console.log(`verdict=hit: ${out.hit}/${out.total}`);
84
+ console.log(`soft score ≥${out.threshold * 100}%: ${out.accuracy_pass}/${out.accuracy_eligible}`);
85
+ console.log(`live <15s (hit): ${out.live_under_15s}/${out.hit}`);
86
+ console.log(`manual golden query_ids: ${out.sources.manual_golden_query_ids.join(', ') || '(none)'}`);
87
+ console.log(`flyai query_ids: ${out.sources.flyai_query_ids.join(', ') || '(none)'}`);
88
+ console.log(`summary: ${summaryPath}`);
89
+ }
90
+ main();
91
+
92
+
93
+ //# sourceURL=ts/scripts/sf-summary.ts