@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.28

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 (533) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1788 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +161 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +553 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +676 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/provider-detail-sanitize.js +64 -0
  41. package/dist/capabilities/resilience.js +191 -0
  42. package/dist/capabilities/route-provider-conformance.js +341 -0
  43. package/dist/capabilities/session/action-cache.js +50 -44
  44. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  45. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  46. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  47. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  48. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  49. package/dist/capabilities/session/benchmark.js +189 -0
  50. package/dist/capabilities/session/bridge-ledger.js +224 -0
  51. package/dist/capabilities/session/extension-bridge.js +636 -0
  52. package/dist/capabilities/session/extension-channel.js +109 -0
  53. package/dist/capabilities/session/extension-distribution.js +230 -0
  54. package/dist/capabilities/session/extract.js +31 -21
  55. package/dist/capabilities/session/golden-score.js +114 -0
  56. package/dist/capabilities/session/health-watch.js +215 -0
  57. package/dist/capabilities/session/read-guard.js +75 -24
  58. package/dist/capabilities/session/static-flight-golden.js +150 -0
  59. package/dist/capabilities/session/transport.js +97 -83
  60. package/dist/capabilities/session/wizard.js +117 -0
  61. package/dist/capabilities/session-consent.js +178 -0
  62. package/dist/capabilities/session-login.js +170 -0
  63. package/dist/capabilities/session-search.js +635 -44
  64. package/dist/capabilities/spawn-bounded.js +203 -0
  65. package/dist/capabilities/sponsor-plugin.js +341 -0
  66. package/dist/capabilities/visa-policy.js +140 -0
  67. package/dist/capabilities/weather.js +243 -138
  68. package/dist/data/session-golden-20.json +25 -0
  69. package/dist/data/sf-golden-manifest.json +103 -0
  70. package/dist/data/sf-static-routes.json +91 -0
  71. package/dist/scripts/action-cache-tests.js +24 -59
  72. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  73. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  74. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  75. package/dist/scripts/agent-reach-error-tests.js +354 -0
  76. package/dist/scripts/agent-reach-tests.js +26 -18
  77. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  78. package/dist/scripts/anything-path-measure-tests.js +133 -0
  79. package/dist/scripts/anything-path-measure.js +150 -0
  80. package/dist/scripts/anything-tests.js +250 -60
  81. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  82. package/dist/scripts/artifacts-capability-tests.js +727 -0
  83. package/dist/scripts/async-collect.js +50 -11
  84. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  85. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  86. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  87. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  88. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  89. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  90. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  91. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  92. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  93. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  94. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  95. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
  96. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  97. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  98. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  99. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  100. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  101. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  102. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  103. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  104. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  105. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  106. package/dist/scripts/booking-executor-tests.js +80 -0
  107. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  108. package/dist/scripts/booking-saga-tests.js +125 -0
  109. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  110. package/dist/scripts/bootstrap-tests.js +1549 -0
  111. package/dist/scripts/bridge-events-tests.js +239 -0
  112. package/dist/scripts/bridge-ledger-tests.js +428 -0
  113. package/dist/scripts/build-changelog.js +238 -0
  114. package/dist/scripts/build-metrics-report.js +331 -0
  115. package/dist/scripts/changelog-tests.js +118 -0
  116. package/dist/scripts/channel-probe-tests.js +103 -0
  117. package/dist/scripts/channel-probe.js +169 -0
  118. package/dist/scripts/channel-registry-tests.js +208 -0
  119. package/dist/scripts/city-scenario-tier-tests.js +234 -0
  120. package/dist/scripts/companion-tests.js +25 -70
  121. package/dist/scripts/dida-cookie-probe.js +87 -0
  122. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  123. package/dist/scripts/dida-sw-probe.js +114 -0
  124. package/dist/scripts/diff-test.js +17 -12
  125. package/dist/scripts/doctor-tests.js +600 -0
  126. package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
  127. package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
  128. package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
  129. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  130. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  131. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  132. package/dist/scripts/dsh-llm-tests.js +437 -0
  133. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  134. package/dist/scripts/dsh-runtime-closure.js +162 -0
  135. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  136. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  137. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  138. package/dist/scripts/effect-tests.js +539 -0
  139. package/dist/scripts/engine-run.js +1 -3
  140. package/dist/scripts/engine-tests.js +16 -10
  141. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  142. package/dist/scripts/evaluation-contract-tests.js +268 -0
  143. package/dist/scripts/extension-distribution-cli.js +39 -0
  144. package/dist/scripts/extension-distribution-tests.js +443 -0
  145. package/dist/scripts/extension-tests.js +988 -0
  146. package/dist/scripts/external-event-tests.js +350 -0
  147. package/dist/scripts/fact-gate-tests.js +869 -0
  148. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  149. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  150. package/dist/scripts/flight-malformed-tests.js +159 -0
  151. package/dist/scripts/flyai-setup-tests.js +228 -0
  152. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  153. package/dist/scripts/flyai-tests.js +692 -0
  154. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  155. package/dist/scripts/fx-contract-tests.js +201 -0
  156. package/dist/scripts/g5-guard-tests.js +150 -0
  157. package/dist/scripts/g5-guard.js +193 -0
  158. package/dist/scripts/gen-lunar-anchors.js +106 -0
  159. package/dist/scripts/geo-atlas-tests.js +283 -0
  160. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  161. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  162. package/dist/scripts/gotry-try-demo.js +49 -0
  163. package/dist/scripts/ground-transfer-tests.js +956 -0
  164. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  165. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  166. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  167. package/dist/scripts/hbcli-tests.js +121 -69
  168. package/dist/scripts/health-watch-cli.js +73 -0
  169. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  170. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  171. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  172. package/dist/scripts/i18n-tests.js +41 -0
  173. package/dist/scripts/incident-tests.js +281 -87
  174. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  175. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  176. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  177. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  178. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  179. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  180. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  181. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  182. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  183. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  184. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  185. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  186. package/dist/scripts/itinerary-deck-tests.js +435 -0
  187. package/dist/scripts/itinerary-html-tests.js +426 -0
  188. package/dist/scripts/journey-tests.js +22 -27
  189. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  190. package/dist/scripts/kernel-manifest-gate.js +126 -0
  191. package/dist/scripts/kernel-manifest-tests.js +212 -0
  192. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  193. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  194. package/dist/scripts/kernel-manifest.js +369 -0
  195. package/dist/scripts/lavish-local-tests.js +1226 -0
  196. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  197. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  198. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  199. package/dist/scripts/ledger-tests.js +382 -246
  200. package/dist/scripts/ledger-workflow-crash.js +10 -20
  201. package/dist/scripts/m3-cohort-tests.js +714 -0
  202. package/dist/scripts/m3-cohort.js +310 -0
  203. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  204. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  205. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  206. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  207. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  208. package/dist/scripts/memory-capture-tests.js +242 -61
  209. package/dist/scripts/memory-decay-tests.js +18 -52
  210. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  211. package/dist/scripts/memory-lifecycle.js +196 -0
  212. package/dist/scripts/memory-metrics.js +24 -12
  213. package/dist/scripts/memory-value-report.js +631 -0
  214. package/dist/scripts/memory-value-tests.js +328 -0
  215. package/dist/scripts/metrics-report-tests.js +151 -0
  216. package/dist/scripts/model-override-e2e.js +137 -0
  217. package/dist/scripts/nightly-evidence-tests.js +203 -0
  218. package/dist/scripts/nightly-evidence.js +303 -0
  219. package/dist/scripts/nudge-digest.js +39 -28
  220. package/dist/scripts/observability-tests.js +381 -0
  221. package/dist/scripts/onboarding-tests.js +161 -0
  222. package/dist/scripts/opensky-check.js +18 -13
  223. package/dist/scripts/opensky-tests.js +28 -28
  224. package/dist/scripts/outcome-projection-tests.js +427 -0
  225. package/dist/scripts/persona-sim-tests.js +603 -0
  226. package/dist/scripts/persona-sim.js +1081 -0
  227. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  228. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  229. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  230. package/dist/scripts/price-drift-tests.js +340 -0
  231. package/dist/scripts/price-drift-watch.js +447 -0
  232. package/dist/scripts/probe-poi-tests.js +53 -45
  233. package/dist/scripts/product-metrics.js +483 -0
  234. package/dist/scripts/publish-preverify.js +110 -0
  235. package/dist/scripts/realtime-pricing-tests.js +76 -0
  236. package/dist/scripts/recall-tests.js +513 -0
  237. package/dist/scripts/replay-async.js +22 -18
  238. package/dist/scripts/replay-real.js +18 -18
  239. package/dist/scripts/replay.js +54 -82
  240. package/dist/scripts/route-provider-conformance-tests.js +391 -0
  241. package/dist/scripts/session-attach-diagnose.js +20 -20
  242. package/dist/scripts/session-attach-poc.js +34 -37
  243. package/dist/scripts/session-benchmark.js +180 -0
  244. package/dist/scripts/session-dida-live-e2e.js +199 -0
  245. package/dist/scripts/session-extract-tests.js +14 -35
  246. package/dist/scripts/session-link-tests.js +478 -0
  247. package/dist/scripts/session-login.js +27 -46
  248. package/dist/scripts/session-tests.js +796 -96
  249. package/dist/scripts/session-zone-crash.js +49 -0
  250. package/dist/scripts/session-zone-ledger-tests.js +432 -0
  251. package/dist/scripts/session-zone-observation-tests.js +492 -0
  252. package/dist/scripts/session-zone-observe.js +135 -0
  253. package/dist/scripts/session-zone-wiring-tests.js +566 -0
  254. package/dist/scripts/session-zones-tests.js +374 -0
  255. package/dist/scripts/sf-live-benchmark.js +367 -0
  256. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  257. package/dist/scripts/sf-live-cli-tests.js +92 -0
  258. package/dist/scripts/sf-live-options.js +46 -0
  259. package/dist/scripts/sf-manifest-tests.js +30 -0
  260. package/dist/scripts/sf-manifest.js +33 -0
  261. package/dist/scripts/sf-soft-score-tests.js +67 -0
  262. package/dist/scripts/sf-summary-tests.js +324 -0
  263. package/dist/scripts/sf-summary.js +525 -0
  264. package/dist/scripts/share-tests.js +432 -0
  265. package/dist/scripts/skeleton-check.js +16 -14
  266. package/dist/scripts/skeleton-integration-test.js +28 -11
  267. package/dist/scripts/skills-contract-tests.js +51 -46
  268. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  269. package/dist/scripts/smoke.js +690 -246
  270. package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
  271. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  272. package/dist/scripts/state-cli-tests.js +226 -101
  273. package/dist/scripts/state-cli.js +565 -198
  274. package/dist/scripts/state-repair-cli.js +66 -0
  275. package/dist/scripts/state-repair-tests.js +140 -0
  276. package/dist/scripts/static-golden-tests.js +165 -0
  277. package/dist/scripts/time-eval-tests.js +212 -192
  278. package/dist/scripts/travel-timeline-tests.js +29 -93
  279. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  280. package/dist/scripts/turn-handoff-collect.js +161 -0
  281. package/dist/scripts/turn-policy-tests.js +55 -0
  282. package/dist/scripts/typed-contract-canary.js +176 -0
  283. package/dist/scripts/tz-resolver-sanity.js +50 -0
  284. package/dist/scripts/tz-resolver-tests.js +584 -0
  285. package/dist/scripts/unified-tests.js +60 -20
  286. package/dist/scripts/visa-policy-tests.js +62 -0
  287. package/dist/scripts/weather-tests.js +276 -41
  288. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  289. package/dist/scripts/write-gate-tests.js +522 -0
  290. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  291. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  292. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  293. package/dist/scripts/z3-race-tests.js +46 -0
  294. package/dist/src/artifact-gate.js +829 -0
  295. package/dist/src/backend/kernel.js +91 -0
  296. package/dist/src/backend/modules/booking-copilot.js +92 -0
  297. package/dist/src/backend/modules/booking-executor.js +124 -0
  298. package/dist/src/backend/modules/session-search.js +551 -0
  299. package/dist/src/benchmark-agent-conformance.js +578 -0
  300. package/dist/src/benchmark-environment-bridge.js +515 -0
  301. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  302. package/dist/src/benchmark-tool-isolation.js +151 -0
  303. package/dist/src/bookable-facts.js +533 -0
  304. package/dist/src/booking-saga.js +97 -0
  305. package/dist/src/booking-surface/availability-policy.js +697 -0
  306. package/dist/src/booking-surface/booking-intent.js +43 -0
  307. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  308. package/dist/src/booking-surface/canonical-schema.js +116 -0
  309. package/dist/src/booking-surface/contracts.js +75 -0
  310. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  311. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  312. package/dist/src/booking-surface/dsh-worker.js +98 -0
  313. package/dist/src/booking-surface/error-codes.js +231 -0
  314. package/dist/src/booking-surface/index.js +9 -0
  315. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  316. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  317. package/dist/src/booking-surface/profile.js +24 -0
  318. package/dist/src/booking-surface/recovery-chain.js +367 -0
  319. package/dist/src/booking-surface/runtime.js +1968 -0
  320. package/dist/src/booking-surface/server.js +674 -0
  321. package/dist/src/booking-surface/startup.js +168 -0
  322. package/dist/src/booking-surface/validation.js +261 -0
  323. package/dist/src/bridge.js +20 -13
  324. package/dist/src/city-scenario-tier.js +378 -0
  325. package/dist/src/companions.js +48 -74
  326. package/dist/src/contracts.js +21 -43
  327. package/dist/src/dsh-llm.js +171 -115
  328. package/dist/src/engine.js +276 -193
  329. package/dist/src/evaluation-cadence.js +97 -0
  330. package/dist/src/evaluation-contracts.js +283 -0
  331. package/dist/src/flight-pack-adapter.js +54 -0
  332. package/dist/src/flight-pack-contract.js +2 -0
  333. package/dist/src/flyai-setup-tool.js +70 -0
  334. package/dist/src/gotry-backend.js +125 -0
  335. package/dist/src/hotel-date-gate.js +118 -0
  336. package/dist/src/i18n.js +88 -0
  337. package/dist/src/index.js +2385 -881
  338. package/dist/src/itinerary-deck.js +139 -0
  339. package/dist/src/itinerary-doc-shared.js +661 -0
  340. package/dist/src/itinerary-html.js +131 -0
  341. package/dist/src/journey.js +155 -108
  342. package/dist/src/lavish-tools.js +400 -0
  343. package/dist/src/ledger-repair-apply.js +538 -0
  344. package/dist/src/ledger-repair-plan.js +463 -0
  345. package/dist/src/loop.js +331 -161
  346. package/dist/src/m3-cohort.js +1577 -0
  347. package/dist/src/memory-capture.js +180 -32
  348. package/dist/src/memory-decay.js +30 -14
  349. package/dist/src/memory-lifecycle.js +1310 -0
  350. package/dist/src/memory-utility.js +27 -40
  351. package/dist/src/mock-llm.js +36 -61
  352. package/dist/src/model.js +81 -36
  353. package/dist/src/outcome-projection.js +746 -0
  354. package/dist/src/realtime-pricing.js +103 -0
  355. package/dist/src/recall/card.js +74 -0
  356. package/dist/src/recall/evaluator.js +140 -0
  357. package/dist/src/recall/integration.js +17 -0
  358. package/dist/src/recall/tick.js +166 -0
  359. package/dist/src/session-link/plan-it-action.js +48 -0
  360. package/dist/src/session-link/session-link.js +248 -0
  361. package/dist/src/session-zone-ledger.js +312 -0
  362. package/dist/src/session-zone-observation.js +354 -0
  363. package/dist/src/session-zone-wiring.js +658 -0
  364. package/dist/src/session-zones.js +510 -0
  365. package/dist/src/share/adapters.js +51 -0
  366. package/dist/src/share/share-consent.js +76 -0
  367. package/dist/src/share/share-deck.js +58 -0
  368. package/dist/src/share/share-token.js +146 -0
  369. package/dist/src/slot-spec.js +84 -106
  370. package/dist/src/state-ledger.js +696 -524
  371. package/dist/src/state-repair.js +299 -0
  372. package/dist/src/subagent-job-id-guard.js +58 -0
  373. package/dist/src/time-anchor.js +231 -33
  374. package/dist/src/tool-packet.js +20 -8
  375. package/dist/src/travel-slots.js +58 -35
  376. package/dist/src/travel-timeline.js +44 -63
  377. package/dist/src/turn-deadline.js +287 -0
  378. package/dist/src/turn-policy.js +118 -0
  379. package/dist/src/tz-resolver.js +143 -0
  380. package/dist/src/unified.js +600 -360
  381. package/dist/src/wish-pool.js +42 -12
  382. package/dist/src/write-gate.js +495 -0
  383. package/dist/src/z3-shared.js +284 -0
  384. package/extension/README.md +21 -0
  385. package/extension/README.zh-CN.md +21 -0
  386. package/extension/background.js +463 -0
  387. package/extension/content-bridge.js +270 -0
  388. package/extension/content-main.js +111 -0
  389. package/extension/manifest.json +60 -0
  390. package/package.json +407 -14
  391. package/schemas/booking.intent.schema.json +193 -0
  392. package/schemas/booking.surface.schema.json +2622 -0
  393. package/ts/capabilities/agent-reach-deep.ts +114 -0
  394. package/ts/capabilities/agent-reach.ts +198 -0
  395. package/ts/capabilities/anything.ts +282 -0
  396. package/ts/capabilities/artifacts.ts +428 -0
  397. package/ts/capabilities/fact-log.ts +54 -0
  398. package/ts/capabilities/flyai-config.ts +388 -0
  399. package/ts/capabilities/flyai.ts +1274 -0
  400. package/ts/capabilities/hbcli.ts +253 -71
  401. package/ts/capabilities/incident-log.ts +88 -59
  402. package/ts/capabilities/itinerary-artifact.ts +214 -0
  403. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  404. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  405. package/ts/capabilities/opensky.ts +121 -0
  406. package/ts/capabilities/session/action-cache.ts +148 -0
  407. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  408. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  409. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  410. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  411. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  412. package/ts/capabilities/session/benchmark.ts +276 -0
  413. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  414. package/ts/capabilities/session/extension-bridge.ts +794 -0
  415. package/ts/capabilities/session/extension-channel.ts +144 -0
  416. package/ts/capabilities/session/extension-distribution.ts +266 -0
  417. package/ts/capabilities/session/extract.ts +54 -0
  418. package/ts/capabilities/session/golden-score.ts +139 -0
  419. package/ts/capabilities/session/health-watch.ts +264 -0
  420. package/ts/capabilities/session/read-guard.ts +141 -0
  421. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  422. package/ts/capabilities/session/transport.ts +146 -0
  423. package/ts/capabilities/session/wizard.ts +140 -0
  424. package/ts/capabilities/session-consent.ts +211 -0
  425. package/ts/capabilities/session-login.ts +208 -0
  426. package/ts/capabilities/session-search.ts +893 -0
  427. package/ts/capabilities/weather.ts +330 -0
  428. package/ts/dsh-runtime/vendor/README.md +68 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  440. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  441. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  442. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  443. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  444. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  445. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  446. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  447. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  448. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  449. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  450. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  451. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  452. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  453. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  454. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  455. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  456. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  457. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  458. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  459. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  460. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  461. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  462. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  463. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  464. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  465. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  466. package/ts/package.json +293 -3
  467. package/ts/scripts/async-collect.ts +91 -0
  468. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  469. package/ts/scripts/skeleton-check.ts +1 -1
  470. package/ts/scripts/skeleton-integration-test.ts +23 -1
  471. package/ts/scripts/state-cli.ts +586 -0
  472. package/ts/scripts/turn-handoff-collect.ts +178 -0
  473. package/ts/src/artifact-gate.ts +991 -0
  474. package/ts/src/benchmark-agent-conformance.ts +619 -0
  475. package/ts/src/benchmark-environment-bridge.ts +538 -0
  476. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  477. package/ts/src/benchmark-tool-isolation.ts +166 -0
  478. package/ts/src/bookable-facts.ts +837 -0
  479. package/ts/src/booking-saga.ts +133 -0
  480. package/ts/src/booking-surface/availability-policy.ts +522 -0
  481. package/ts/src/booking-surface/booking-intent.ts +86 -0
  482. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  483. package/ts/src/booking-surface/canonical-schema.js +116 -0
  484. package/ts/src/booking-surface/contracts.ts +475 -0
  485. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  486. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  487. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  488. package/ts/src/booking-surface/error-codes.ts +241 -0
  489. package/ts/src/booking-surface/index.ts +9 -0
  490. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  491. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  492. package/ts/src/booking-surface/profile.ts +42 -0
  493. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  494. package/ts/src/booking-surface/runtime.ts +1793 -0
  495. package/ts/src/booking-surface/server.ts +733 -0
  496. package/ts/src/booking-surface/startup.ts +201 -0
  497. package/ts/src/booking-surface/validation.ts +217 -0
  498. package/ts/src/companions.ts +113 -0
  499. package/ts/src/contracts.ts +17 -3
  500. package/ts/src/dsh-llm.ts +152 -17
  501. package/ts/src/flyai-setup-tool.ts +77 -0
  502. package/ts/src/index.ts +2123 -200
  503. package/ts/src/itinerary-deck.ts +189 -0
  504. package/ts/src/itinerary-doc-shared.ts +741 -0
  505. package/ts/src/itinerary-html.ts +171 -0
  506. package/ts/src/loop.ts +234 -26
  507. package/ts/src/memory-capture.ts +283 -0
  508. package/ts/src/memory-decay.ts +55 -0
  509. package/ts/src/memory-utility.ts +70 -0
  510. package/ts/src/mock-llm.ts +4 -8
  511. package/ts/src/model.ts +88 -2
  512. package/ts/src/recall/card.ts +106 -0
  513. package/ts/src/recall/evaluator.ts +170 -0
  514. package/ts/src/recall/integration.ts +35 -0
  515. package/ts/src/recall/tick.ts +213 -0
  516. package/ts/src/session-link/plan-it-action.ts +78 -0
  517. package/ts/src/session-link/session-link.ts +275 -0
  518. package/ts/src/share/adapters.ts +93 -0
  519. package/ts/src/share/share-consent.ts +105 -0
  520. package/ts/src/share/share-deck.ts +85 -0
  521. package/ts/src/share/share-token.ts +166 -0
  522. package/ts/src/slot-spec.ts +172 -0
  523. package/ts/src/state-ledger.ts +1166 -0
  524. package/ts/src/subagent-job-id-guard.ts +72 -0
  525. package/ts/src/time-anchor.ts +338 -0
  526. package/ts/src/tool-packet.ts +32 -0
  527. package/ts/src/travel-slots.ts +219 -0
  528. package/ts/src/travel-timeline.ts +83 -0
  529. package/ts/src/turn-deadline.ts +366 -0
  530. package/ts/src/turn-policy.ts +154 -0
  531. package/ts/src/unified.ts +444 -105
  532. package/ts/src/wish-pool.ts +79 -0
  533. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,90 @@
1
+ /**
2
+ * dsh-http-proxy 本地 SSE + 中毒 HTTP 代理反例(issue #268):启动回环 SSE 服务器和
3
+ * 统计命中次数的中毒代理,用公共 installProxyFromEnvironment API 安装代理策略,
4
+ * 将 HTTP(S)_PROXY 指向中毒代理。在 127.0.0.1 上获取 SSE 端点,断言负载成功且
5
+ * 中毒命中计数保持为零;同时断言 proxyRouteFor(loopback) 是直接的,非回环 URL
6
+ * 被分类为代理,且 proxyEnvironmentForChild() 携带回环 NO_PROXY 以及 Node 24 下的
7
+ * NODE_USE_ENV_PROXY。始终 await 返回的 disposer 并在 finally 中关闭两个服务器;
8
+ * 证明全局路由返回到先前的直接状态。
9
+ *
10
+ * 运行:cd ts && npx tsx scripts/dsh-http-proxy-sse-proof.ts
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import http from 'node:http';
14
+ import { installProxyFromEnvironment, proxyRouteFor, proxyEnvironmentForChild } from '@deepseek-ai/dsh-http-proxy';
15
+ // 1. 回环 SSE 服务器。
16
+ const sseServer = http.createServer((req, res) => {
17
+ res.writeHead(200, {
18
+ 'Content-Type': 'text/event-stream',
19
+ 'Cache-Control': 'no-cache',
20
+ Connection: 'keep-alive',
21
+ });
22
+ res.write('data: hello-sse\n\n');
23
+ res.end();
24
+ });
25
+ await new Promise((resolve) => sseServer.listen(0, '127.0.0.1', resolve));
26
+ const sseAddr = sseServer.address();
27
+ assert.ok(sseAddr !== null && typeof sseAddr === 'object', 'SSE 服务器必须已绑定地址');
28
+ const ssePort = sseAddr.port;
29
+ const sseUrl = `http://127.0.0.1:${ssePort}/`;
30
+ // 2. 中毒代理:统计命中次数,返回 502。
31
+ let poisonHits = 0;
32
+ const poisonServer = http.createServer((req, res) => {
33
+ poisonHits++;
34
+ res.writeHead(502);
35
+ res.end('poisoned');
36
+ });
37
+ await new Promise((resolve) => poisonServer.listen(0, '127.0.0.1', resolve));
38
+ const poisonAddr = poisonServer.address();
39
+ assert.ok(poisonAddr !== null && typeof poisonAddr === 'object', '中毒代理必须已绑定地址');
40
+ const poisonPort = poisonAddr.port;
41
+ let disposer;
42
+ try {
43
+ // 3. 用公共 installProxyFromEnvironment API 安装代理策略,指向中毒代理。
44
+ const envMap = new Map([
45
+ ['HTTP_PROXY', `http://127.0.0.1:${poisonPort}`],
46
+ ['HTTPS_PROXY', `http://127.0.0.1:${poisonPort}`],
47
+ ]);
48
+ const envLookup = {
49
+ get(name) {
50
+ const v = envMap.get(name) ?? envMap.get(name.toLowerCase());
51
+ return v !== undefined ? { value: v } : undefined;
52
+ },
53
+ };
54
+ disposer = await installProxyFromEnvironment(envLookup, () => { });
55
+ // 4. proxyRouteFor(loopback) 是直接的;非回环 URL 被分类为代理。
56
+ const loopbackRoute = proxyRouteFor(new URL(sseUrl));
57
+ assert.equal(loopbackRoute.proxied, false, '回环 URL 必须路由为直接(proxyRouteFor)');
58
+ const externalRoute = proxyRouteFor(new URL('http://example.com/'));
59
+ assert.equal(externalRoute.proxied, true, '非回环 URL 必须被分类为代理(proxyRouteFor)');
60
+ // 5. proxyEnvironmentForChild() 携带回环 NO_PROXY 以及 Node 24 下的 NODE_USE_ENV_PROXY。
61
+ const childEnv = proxyEnvironmentForChild();
62
+ assert.equal(childEnv.NODE_USE_ENV_PROXY, '1', 'proxyEnvironmentForChild 必须携带 NODE_USE_ENV_PROXY=1(Node 24)');
63
+ assert.ok(typeof childEnv.NO_PROXY === 'string' && childEnv.NO_PROXY.includes('127.0.0.1'), `proxyEnvironmentForChild NO_PROXY 必须包含 127.0.0.1(当前 ${JSON.stringify(childEnv.NO_PROXY)})`);
64
+ // 6. 在 127.0.0.1 上获取 SSE 端点:负载成功且中毒命中计数保持为零。
65
+ const resp = await fetch(sseUrl);
66
+ assert.equal(resp.status, 200, `SSE fetch 必须成功(status=${resp.status})`);
67
+ const body = await resp.text();
68
+ assert.ok(body.includes('hello-sse'), `SSE 负载必须包含 hello-sse(当前 ${JSON.stringify(body)})`);
69
+ assert.equal(poisonHits, 0, `中毒代理命中计数必须为零(当前 ${poisonHits})——回环绕过代理`);
70
+ }
71
+ finally {
72
+ // 7. 始终 await 返回的 disposer 并在 finally 中 await 关闭两个服务器(包括失败路径)。
73
+ // 不依赖未 await 的 server.close() 让进程退出——显式 await 完成资源清理。
74
+ try {
75
+ if (disposer)
76
+ await disposer();
77
+ }
78
+ finally {
79
+ await Promise.all([
80
+ new Promise((resolve, reject) => sseServer.close((err) => (err ? reject(err) : resolve()))),
81
+ new Promise((resolve, reject) => poisonServer.close((err) => (err ? reject(err) : resolve()))),
82
+ ]);
83
+ }
84
+ }
85
+ // 8. 证明全局路由返回到先前的直接状态。
86
+ const externalAfter = proxyRouteFor(new URL('http://example.com/'));
87
+ assert.equal(externalAfter.proxied, false, 'disposer 后全局路由必须返回直接状态');
88
+ console.log(`HTTP PROXY SSE PROOF: loopback SSE fetch=200 payload-has-hello-sse=true poison-hits=0, ` +
89
+ `loopback route=direct external route=proxied, NODE_USE_ENV_PROXY=1 NO_PROXY has 127.0.0.1, ` +
90
+ `routing restored to direct after dispose`);
@@ -0,0 +1,437 @@
1
+ /**
2
+ * dsh-llm chat() 请求级超时 / 调用方取消 合同测试(run-all §88,issue #618)。
3
+ * 全离线、确定性、零 API 花费:127.0.0.1 上的假 provider 夹具(收下连接却永不应答 / 响应头后正文卡死 /
4
+ * 正常应答),fetch 间谍保证整套测试没有任何请求离开 127.0.0.1。
5
+ *
6
+ * 覆盖:
7
+ * - 预算解析:调用显式值 > GOTRY_LLM_TIMEOUT_MS > 默认 300000;非正整数忽略;超大值夹住(不溢出成 1ms);
8
+ * - 卡死 provider(无响应头 / 响应头后正文卡死 / 非 2xx 的错误正文卡死)在预算内落成
9
+ * LlmRequestError('timeout'),不挂起、usage 不动;
10
+ * - 调用方 signal(中途 abort / 已 abort / 自定义 reason / 带 signal 但先超时)落成 LlmRequestError('aborted'|'timeout');
11
+ * - 正常快应答:请求字节与应答处理逐字不变,usage 照常累计,缺 usage 仍计 responsesMissingUsage(fail-closed 语义不变);
12
+ * - 非 abort 失败(HTTP 500 / 连接被拒 / 缺 key)仍是原样的普通 Error,不被改写;
13
+ * - 调用方不吞:runTurn 与 nightly-evidence 对卡死 provider 抛类型化失败,且 nightly 零写入。
14
+ * 所有等待都套 bounded():断言失败(含「修复被回退后永远挂起」)只会让对应用例失败,绝不会让本测试自己挂起。
15
+ * 运行:cd ts && npx tsx scripts/dsh-llm-tests.ts
16
+ */
17
+ import assert from 'node:assert/strict';
18
+ import { existsSync, mkdtempSync, rmSync } from 'node:fs';
19
+ import { createServer } from 'node:http';
20
+ import { tmpdir } from 'node:os';
21
+ import { dirname, join, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ // 命名空间导入:被测模块的新符号缺失(修复被回退)时只会让对应用例失败(undefined),而不是整个文件在链接期崩掉,
24
+ // 这样红基线能如实显示「卡死用例被 bounded 截住」而不是一个无信息量的 SyntaxError。
25
+ import * as dshLlm from '../src/dsh-llm.js';
26
+ import { newState, runTurn } from '../src/loop.js';
27
+ import { runNightlyEvidence } from './nightly-evidence.js';
28
+ const TS_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
29
+ const { createOpenAICompatLlm, LlmRequestError, resolveLlmTimeoutMs, DEFAULT_LLM_TIMEOUT_MS } = dshLlm;
30
+ /** 超过它还没落定 = 挂起(正常卡死用例预算仅数百毫秒;留足慢机/高负载余量)。 */
31
+ const HANG_BOUND_MS = 6_000;
32
+ /** 卡死用例统一的请求预算:够短让套件快,够长不被调度抖动误伤。 */
33
+ const STALL_TIMEOUT_MS = 400;
34
+ const CLOCK = () => new Date('2026-10-05T00:00:00Z');
35
+ const QUESTION = { key: 'workWindow', text: '你这两周的工作时间是几点到几点?', why: '决定每日可玩时段' };
36
+ const OK_CONTENT = '<think>先想一想</think> 润色后的问句 ';
37
+ const FACTS_CONTENT = JSON.stringify({ calendar: { year: 2026, assertedWeekdays: { '2026-10-05': 'mon' } } });
38
+ function completion(content, usage) {
39
+ return JSON.stringify({ choices: [{ message: { role: 'assistant', content } }], ...(usage ? { usage } : {}) });
40
+ }
41
+ async function startProvider() {
42
+ const sockets = new Set();
43
+ const state = { mode: 'ok' };
44
+ const hits = [];
45
+ const server = createServer((request, response) => {
46
+ const chunks = [];
47
+ request.on('data', chunk => chunks.push(chunk));
48
+ request.on('end', () => {
49
+ hits.push({ method: request.method ?? '', url: request.url ?? '', headers: request.headers, body: Buffer.concat(chunks).toString('utf8') });
50
+ const json = (status, text) => {
51
+ response.writeHead(status, { 'content-type': 'application/json' });
52
+ response.end(text);
53
+ };
54
+ switch (state.mode) {
55
+ case 'ok':
56
+ return json(200, completion(OK_CONTENT, { prompt_tokens: 120, completion_tokens: 30, prompt_cache_hit_tokens: 100, prompt_cache_miss_tokens: 20 }));
57
+ case 'ok-no-cache-detail':
58
+ return json(200, completion(OK_CONTENT, { prompt_tokens: 50, completion_tokens: 10 }));
59
+ case 'ok-no-usage':
60
+ return json(200, completion(OK_CONTENT));
61
+ case 'ok-json':
62
+ return json(200, completion(FACTS_CONTENT, { prompt_tokens: 7, completion_tokens: 3 }));
63
+ case 'error-500':
64
+ return json(500, 'fixture upstream exploded');
65
+ case 'stall':
66
+ return; // 永不应答
67
+ case 'stall-body':
68
+ response.writeHead(200, { 'content-type': 'application/json' });
69
+ response.write('{"choices":[{"message":');
70
+ return;
71
+ case 'stall-error-body':
72
+ response.writeHead(500, { 'content-type': 'text/plain' });
73
+ response.write('upstream says');
74
+ return;
75
+ }
76
+ });
77
+ });
78
+ server.on('connection', socket => {
79
+ sockets.add(socket);
80
+ socket.on('close', () => sockets.delete(socket));
81
+ });
82
+ await new Promise((resolveListen, rejectListen) => {
83
+ server.once('error', rejectListen);
84
+ server.listen(0, '127.0.0.1', () => resolveListen());
85
+ });
86
+ const address = server.address();
87
+ return {
88
+ url: `http://127.0.0.1:${address.port}/v1`,
89
+ get mode() { return state.mode; },
90
+ set mode(next) { state.mode = next; },
91
+ hits,
92
+ closeAll: () => new Promise(resolveClose => {
93
+ for (const socket of sockets)
94
+ socket.destroy();
95
+ server.close(() => resolveClose());
96
+ }),
97
+ };
98
+ }
99
+ // ---- 测试脚手架 ------------------------------------------------------------------------------
100
+ class TestHang extends Error {
101
+ }
102
+ /** 等 work 落定;超过 ms 仍未落定则抛 TestHang(挂起 = 测试失败,而不是测试自己挂起)。 */
103
+ async function bounded(label, work, ms = HANG_BOUND_MS) {
104
+ work.catch(() => { }); // 输掉竞速的一方若稍后 reject,不得变成 unhandled rejection
105
+ let timer;
106
+ try {
107
+ return await Promise.race([
108
+ work,
109
+ new Promise((_resolve, reject) => {
110
+ timer = setTimeout(() => reject(new TestHang(`TEST HANG: ${label} had not settled after ${ms}ms (no request timeout / abort took effect)`)), ms);
111
+ }),
112
+ ]);
113
+ }
114
+ finally {
115
+ if (timer)
116
+ clearTimeout(timer);
117
+ }
118
+ }
119
+ /** 断言 work 以拒绝收场,并返回拒绝原因与耗时;挂起原样上抛为 TestHang,正常返回则判失败。 */
120
+ async function rejection(label, work) {
121
+ const started = performance.now();
122
+ try {
123
+ await bounded(label, work);
124
+ }
125
+ catch (error) {
126
+ if (error instanceof TestHang)
127
+ throw error;
128
+ return { error, elapsedMs: performance.now() - started };
129
+ }
130
+ throw new assert.AssertionError({ message: `${label}: expected the call to reject, but it resolved` });
131
+ }
132
+ async function until(label, predicate, ms = 3_000) {
133
+ const deadline = Date.now() + ms;
134
+ while (!predicate()) {
135
+ if (Date.now() > deadline)
136
+ throw new Error(`${label}: condition not reached within ${ms}ms`);
137
+ await new Promise(resolveWait => setTimeout(resolveWait, 10));
138
+ }
139
+ }
140
+ async function withEnv(patch, body) {
141
+ const saved = Object.fromEntries(Object.keys(patch).map(key => [key, process.env[key]]));
142
+ const apply = (values) => {
143
+ for (const [key, value] of Object.entries(values)) {
144
+ if (value === undefined)
145
+ delete process.env[key];
146
+ else
147
+ process.env[key] = value;
148
+ }
149
+ };
150
+ apply(patch);
151
+ try {
152
+ return await body();
153
+ }
154
+ finally {
155
+ apply(saved);
156
+ }
157
+ }
158
+ function describe(error) {
159
+ return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
160
+ }
161
+ function assertTimeoutFailure(error, timeoutMs) {
162
+ assert.ok(error instanceof LlmRequestError, `expected LlmRequestError, got ${describe(error)}`);
163
+ assert.equal(error.code, 'timeout');
164
+ assert.equal(error.name, 'TimeoutError', 'platform name, so name-based classifiers (persona-sim, channel-probe) recognise it');
165
+ assert.equal(error.timeoutMs, timeoutMs);
166
+ assert.match(error.message, new RegExp(`^llm timeout: .*${timeoutMs}ms`), 'module `llm <kind>: <detail>` message convention');
167
+ assert.equal(error.cause?.name, 'TimeoutError', 'original abort error is kept as cause');
168
+ }
169
+ function assertAbortedFailure(error) {
170
+ assert.ok(error instanceof LlmRequestError, `expected LlmRequestError, got ${describe(error)}`);
171
+ assert.equal(error.code, 'aborted');
172
+ assert.equal(error.name, 'AbortError');
173
+ assert.equal(error.timeoutMs, undefined);
174
+ assert.match(error.message, /^llm aborted: /);
175
+ }
176
+ function assertUntouched(usage, label) {
177
+ assert.deepEqual(usage, { calls: 0, inputTokens: 0, outputTokens: 0, inputCacheHitTokens: 0, inputCacheMissTokens: 0, responsesMissingUsage: 0 }, `${label}: a call that produced no response must not touch the usage tracker`);
178
+ }
179
+ // ---- 用例 ------------------------------------------------------------------------------------
180
+ let passed = 0;
181
+ const failures = [];
182
+ async function test(name, body) {
183
+ try {
184
+ await body();
185
+ passed += 1;
186
+ console.log(` ok ${name}`);
187
+ }
188
+ catch (error) {
189
+ failures.push(name);
190
+ console.log(` FAIL ${name}\n ${describe(error).split('\n').join('\n ')}`);
191
+ }
192
+ }
193
+ const provider = await startProvider();
194
+ const savedFetch = globalThis.fetch;
195
+ const savedEnv = Object.fromEntries(['LLM_API_KEY', 'LLM_BASE_URL', 'LLM_MODEL', 'DEEPSEEK_API_KEY', 'DEEPSEEK_BASE_URL', 'DEEPSEEK_MODEL', 'GOTRY_LLM_TIMEOUT_MS'].map(key => [key, process.env[key]]));
196
+ // 间谍:任何离开 127.0.0.1 的请求都直接抛错(不是仅记录),回归也带不走凭证
197
+ globalThis.fetch = (async (input, init) => {
198
+ const target = new URL(typeof input === 'string' ? input : input instanceof URL ? input.href : input.url);
199
+ if (target.hostname !== '127.0.0.1')
200
+ throw new Error(`TEST ESCAPE: request to ${target.hostname} left the loopback fixture`);
201
+ return savedFetch(input, init);
202
+ });
203
+ process.env['LLM_API_KEY'] = 'fixture-key';
204
+ process.env['LLM_BASE_URL'] = provider.url;
205
+ process.env['LLM_MODEL'] = 'fixture-model';
206
+ for (const alias of ['DEEPSEEK_API_KEY', 'DEEPSEEK_BASE_URL', 'DEEPSEEK_MODEL', 'GOTRY_LLM_TIMEOUT_MS'])
207
+ delete process.env[alias];
208
+ try {
209
+ console.log('dsh-llm request timeout / abort contract (issue #618)');
210
+ await test('timeout budget: default 300000; call > env > default; invalid ignored; huge value clamped', async () => {
211
+ assert.equal(DEFAULT_LLM_TIMEOUT_MS, 300_000);
212
+ assert.equal(resolveLlmTimeoutMs(), 300_000);
213
+ assert.equal(resolveLlmTimeoutMs(1_500), 1_500);
214
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: '45000' }, () => {
215
+ assert.equal(resolveLlmTimeoutMs(), 45_000);
216
+ assert.equal(resolveLlmTimeoutMs(1_500), 1_500, 'an explicit per-call value beats the env var');
217
+ for (const bad of [0, -1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
218
+ assert.equal(resolveLlmTimeoutMs(bad), 45_000, `invalid per-call ${String(bad)} is ignored and falls through to the env var`);
219
+ }
220
+ });
221
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: ' 45000 ' }, () => assert.equal(resolveLlmTimeoutMs(), 45_000, 'surrounding whitespace is tolerated'));
222
+ for (const bad of ['0', '-5', '1.5', 'abc', '', ' ', '1e3', '12ms', '0x10', '+5', 'Infinity', 'NaN', '99999999999999999999999']) {
223
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: bad }, () => assert.equal(resolveLlmTimeoutMs(), 300_000, `env ${JSON.stringify(bad)} is ignored -> default (never "no timeout")`));
224
+ }
225
+ for (const bad of [0, -1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
226
+ assert.equal(resolveLlmTimeoutMs(bad), 300_000, `invalid per-call ${String(bad)} with no env -> default`);
227
+ }
228
+ // Node 把 > 2^31-1 的定时器延迟溢出成 1ms(「配得越大越立刻超时」);必须夹住而不是透传
229
+ assert.equal(resolveLlmTimeoutMs(2 ** 40), 2_147_483_647);
230
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: '3000000000' }, () => assert.equal(resolveLlmTimeoutMs(), 2_147_483_647));
231
+ });
232
+ await test('stalled provider (accepts, never answers): per-call timeoutMs -> typed timeout inside the budget, usage untouched', async () => {
233
+ provider.mode = 'stall';
234
+ const before = provider.hits.length;
235
+ const port = createOpenAICompatLlm(undefined, CLOCK, { timeoutMs: STALL_TIMEOUT_MS });
236
+ const { error, elapsedMs } = await rejection('stalled provider / per-call timeoutMs', port.polishQuestion(QUESTION));
237
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
238
+ assert.ok(elapsedMs >= STALL_TIMEOUT_MS - 50, `rejected after ${elapsedMs.toFixed(0)}ms: must not fire before the budget`);
239
+ assert.ok(elapsedMs < HANG_BOUND_MS, `rejected after ${elapsedMs.toFixed(0)}ms: must settle inside the budget, not at the hang bound`);
240
+ assertUntouched(port.usage, 'stalled provider');
241
+ await until('the stalled request reached the fixture (a real stall, not a refused connection)', () => provider.hits.length === before + 1);
242
+ });
243
+ await test('stalled provider: GOTRY_LLM_TIMEOUT_MS drives the default port', async () => {
244
+ provider.mode = 'stall';
245
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: String(STALL_TIMEOUT_MS) }, async () => {
246
+ const port = createOpenAICompatLlm(undefined, CLOCK);
247
+ const { error } = await rejection('stalled provider / env timeout', port.polishQuestion(QUESTION));
248
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
249
+ assertUntouched(port.usage, 'env timeout');
250
+ });
251
+ });
252
+ await test('per-call timeoutMs beats a generous GOTRY_LLM_TIMEOUT_MS', async () => {
253
+ provider.mode = 'stall';
254
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: '600000' }, async () => {
255
+ const port = createOpenAICompatLlm(undefined, CLOCK, { timeoutMs: STALL_TIMEOUT_MS });
256
+ const { error } = await rejection('stalled provider / per-call over env', port.polishQuestion(QUESTION));
257
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
258
+ });
259
+ });
260
+ await test('timeout covers reading the body: 200 headers then a stalled body -> typed timeout', async () => {
261
+ provider.mode = 'stall-body';
262
+ const port = createOpenAICompatLlm(undefined, CLOCK, { timeoutMs: STALL_TIMEOUT_MS });
263
+ const { error } = await rejection('stalled body', port.polishQuestion(QUESTION));
264
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
265
+ assertUntouched(port.usage, 'stalled body');
266
+ });
267
+ await test('timeout covers the error body too: 500 headers then a stalled body -> typed timeout (not `llm 500`, not a hang)', async () => {
268
+ provider.mode = 'stall-error-body';
269
+ const port = createOpenAICompatLlm(undefined, CLOCK, { timeoutMs: STALL_TIMEOUT_MS });
270
+ const { error } = await rejection('stalled error body', port.polishQuestion(QUESTION));
271
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
272
+ assertUntouched(port.usage, 'stalled error body');
273
+ });
274
+ await test('caller abort mid-flight: rejects promptly with a typed `aborted` failure (default 300s budget is not what ended it)', async () => {
275
+ provider.mode = 'stall';
276
+ const controller = new AbortController();
277
+ const port = createOpenAICompatLlm(undefined, CLOCK, { signal: controller.signal });
278
+ const pending = port.polishQuestion(QUESTION);
279
+ setTimeout(() => controller.abort(), 100);
280
+ const { error, elapsedMs } = await rejection('caller abort', pending);
281
+ assertAbortedFailure(error);
282
+ assert.ok(elapsedMs < HANG_BOUND_MS, `abort took ${elapsedMs.toFixed(0)}ms`);
283
+ assertUntouched(port.usage, 'caller abort');
284
+ });
285
+ await test('caller abort with a custom reason: still `aborted`, and the reason object survives as cause', async () => {
286
+ provider.mode = 'stall';
287
+ const controller = new AbortController();
288
+ const reason = new Error('operator pressed ctrl-c');
289
+ const port = createOpenAICompatLlm(undefined, CLOCK, { signal: controller.signal });
290
+ const pending = port.polishQuestion(QUESTION);
291
+ setTimeout(() => controller.abort(reason), 100);
292
+ const { error } = await rejection('caller abort with reason', pending);
293
+ assertAbortedFailure(error);
294
+ assert.equal(error.cause, reason);
295
+ });
296
+ await test('already-aborted signal: rejects `aborted` without ever reaching the provider', async () => {
297
+ provider.mode = 'ok';
298
+ const controller = new AbortController();
299
+ controller.abort();
300
+ const before = provider.hits.length;
301
+ const port = createOpenAICompatLlm(undefined, CLOCK, { signal: controller.signal });
302
+ const { error } = await rejection('pre-aborted signal', port.polishQuestion(QUESTION));
303
+ assertAbortedFailure(error);
304
+ await new Promise(resolveWait => setTimeout(resolveWait, 100));
305
+ assert.equal(provider.hits.length, before, 'no request may be sent on behalf of a cancelled caller');
306
+ assertUntouched(port.usage, 'pre-aborted');
307
+ });
308
+ await test('caller signal supplied but never aborted: the timeout still wins (AbortSignal.any composition)', async () => {
309
+ provider.mode = 'stall';
310
+ const controller = new AbortController();
311
+ const port = createOpenAICompatLlm(undefined, CLOCK, { signal: controller.signal, timeoutMs: STALL_TIMEOUT_MS });
312
+ const { error } = await rejection('signal + timeout', port.polishQuestion(QUESTION));
313
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
314
+ assert.equal(controller.signal.aborted, false, 'the caller\'s own signal is not aborted by our timeout');
315
+ });
316
+ await test('normal fast response: request bytes and reply handling are unchanged, usage accumulates, missing usage still counts', async () => {
317
+ provider.mode = 'ok';
318
+ const port = createOpenAICompatLlm(undefined, CLOCK);
319
+ const out = await bounded('fast response', port.polishQuestion(QUESTION));
320
+ assert.equal(out, '润色后的问句', '<think> block stripped, text trimmed — same as before');
321
+ const hit = provider.hits.at(-1);
322
+ assert.equal(hit.method, 'POST');
323
+ assert.equal(hit.url, '/v1/chat/completions');
324
+ assert.equal(hit.headers['content-type'], 'application/json');
325
+ assert.equal(hit.headers['authorization'], 'Bearer fixture-key');
326
+ const sent = JSON.parse(hit.body);
327
+ assert.deepEqual(Object.keys(sent), ['model', 'messages', 'temperature'], 'same field set and order (no response_format outside JSON mode)');
328
+ assert.equal(hit.body, JSON.stringify(sent), 'raw request bytes are exactly the canonical serialisation');
329
+ assert.equal(sent.model, 'fixture-model');
330
+ assert.equal(sent.temperature, 0.7);
331
+ assert.equal(sent.messages[0]?.role, 'system');
332
+ assert.deepEqual(sent.messages[1], { role: 'user', content: `【${QUESTION.key}】${QUESTION.text}(为什么问:${QUESTION.why})` });
333
+ assert.deepEqual(port.usage, { calls: 1, inputTokens: 120, outputTokens: 30, inputCacheHitTokens: 100, inputCacheMissTokens: 20, responsesMissingUsage: 0 });
334
+ provider.mode = 'ok-no-cache-detail'; // 无缓存明细:全部输入按 miss 记
335
+ await bounded('fast response / no cache detail', port.polishQuestion(QUESTION));
336
+ assert.deepEqual(port.usage, { calls: 2, inputTokens: 170, outputTokens: 40, inputCacheHitTokens: 100, inputCacheMissTokens: 70, responsesMissingUsage: 0 });
337
+ provider.mode = 'ok-no-usage'; // 缺 usage:calls 照计,成本不可证由 responsesMissingUsage 标出(调用方 fail-closed)
338
+ await bounded('fast response / no usage', port.polishQuestion(QUESTION));
339
+ assert.deepEqual(port.usage, { calls: 3, inputTokens: 170, outputTokens: 40, inputCacheHitTokens: 100, inputCacheMissTokens: 70, responsesMissingUsage: 1 });
340
+ // 显式给出(宽松的)选项不改变正常流程:请求字节与第一次逐字相同
341
+ provider.mode = 'ok';
342
+ const optioned = createOpenAICompatLlm(undefined, CLOCK, { timeoutMs: 30_000, signal: new AbortController().signal });
343
+ assert.equal(await bounded('fast response / options', optioned.polishQuestion(QUESTION)), '润色后的问句');
344
+ assert.equal(provider.hits.at(-1).body, hit.body, 'timeout/signal options must not leak into the request');
345
+ assert.equal(optioned.usage.calls, 1);
346
+ });
347
+ await test('JSON mode request bytes are unchanged (response_format + temperature 0) and the reply still parses', async () => {
348
+ provider.mode = 'ok-json';
349
+ const port = createOpenAICompatLlm(undefined, CLOCK);
350
+ const facts = await bounded('extractFacts', port.extractFacts([{ role: 'user', text: '我这两周在大理远程办公' }], newState()));
351
+ assert.deepEqual(facts.calendar, { year: 2026, assertedWeekdays: { '2026-10-05': 'mon' } });
352
+ const hit = provider.hits.at(-1);
353
+ const sent = JSON.parse(hit.body);
354
+ assert.deepEqual(Object.keys(sent), ['model', 'messages', 'response_format', 'temperature']);
355
+ assert.deepEqual(sent.response_format, { type: 'json_object' });
356
+ assert.equal(sent.temperature, 0);
357
+ assert.equal(hit.body, JSON.stringify(sent));
358
+ assert.deepEqual(port.usage, { calls: 1, inputTokens: 7, outputTokens: 3, inputCacheHitTokens: 0, inputCacheMissTokens: 7, responsesMissingUsage: 0 });
359
+ });
360
+ await test('a huge GOTRY_LLM_TIMEOUT_MS does not overflow into an instant timeout (clamped, normal call succeeds)', async () => {
361
+ provider.mode = 'ok';
362
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: '3000000000' }, async () => {
363
+ const port = createOpenAICompatLlm(undefined, CLOCK);
364
+ assert.equal(await bounded('huge env timeout', port.polishQuestion(QUESTION)), '润色后的问句');
365
+ });
366
+ });
367
+ await test('non-abort failures are untouched: HTTP 500 stays a plain `llm 500:` Error, refused connection stays a transport error, missing key stays the ADR-8 error', async () => {
368
+ provider.mode = 'error-500';
369
+ const port = createOpenAICompatLlm(undefined, CLOCK);
370
+ const http = await rejection('http 500', port.polishQuestion(QUESTION));
371
+ assert.ok(http.error instanceof Error && !(http.error instanceof LlmRequestError), `HTTP failure must not be rewritten: ${describe(http.error)}`);
372
+ assert.equal(http.error.message, 'llm 500: fixture upstream exploded');
373
+ assertUntouched(port.usage, 'http 500');
374
+ // 连接被拒:先占一个端口再关掉,保证确定性地「没人在听」
375
+ const spare = createServer();
376
+ await new Promise(resolveListen => spare.listen(0, '127.0.0.1', () => resolveListen()));
377
+ const deadPort = spare.address().port;
378
+ await new Promise(resolveClose => spare.close(() => resolveClose()));
379
+ await withEnv({ LLM_BASE_URL: `http://127.0.0.1:${deadPort}/v1` }, async () => {
380
+ const refused = await rejection('connection refused', createOpenAICompatLlm(undefined, CLOCK).polishQuestion(QUESTION));
381
+ assert.ok(refused.error instanceof Error && !(refused.error instanceof LlmRequestError), `transport failure must not be rewritten: ${describe(refused.error)}`);
382
+ assert.notEqual(refused.error.name, 'TimeoutError');
383
+ assert.notEqual(refused.error.name, 'AbortError');
384
+ });
385
+ const before = provider.hits.length;
386
+ await withEnv({ LLM_API_KEY: undefined, DEEPSEEK_API_KEY: undefined }, async () => {
387
+ const keyless = await rejection('missing key', createOpenAICompatLlm(undefined, CLOCK).polishQuestion(QUESTION));
388
+ assert.match(keyless.error.message, /LLM_API_KEY 未设置/);
389
+ assert.ok(!(keyless.error instanceof LlmRequestError));
390
+ });
391
+ assert.equal(provider.hits.length, before, 'no request without a key');
392
+ });
393
+ await test('caller does not swallow it: loop.runTurn rejects with the typed timeout (no degraded "success" reply)', async () => {
394
+ provider.mode = 'stall';
395
+ const port = createOpenAICompatLlm(undefined, CLOCK, { timeoutMs: STALL_TIMEOUT_MS });
396
+ const { error } = await rejection('runTurn over a stalled provider', runTurn(newState(), '我想去大理待两周', port, [], null, CLOCK()));
397
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
398
+ });
399
+ await test('caller does not swallow it: nightly-evidence rejects on a stalled provider and writes no evidence', async () => {
400
+ provider.mode = 'stall';
401
+ const evidenceParent = mkdtempSync(join(tmpdir(), 'dsh-llm-nightly-'));
402
+ const evidenceRoot = join(evidenceParent, 'evidence', 'm3');
403
+ const savedCwd = process.cwd();
404
+ process.chdir(TS_ROOT); // nightly-evidence 的价表/提示集/航班包路径相对 ts/
405
+ try {
406
+ await withEnv({ GOTRY_LLM_TIMEOUT_MS: String(STALL_TIMEOUT_MS) }, async () => {
407
+ const { error } = await rejection('nightly over a stalled provider', runNightlyEvidence({ evidenceRoot, dryRun: false, clock: CLOCK }));
408
+ assertTimeoutFailure(error, STALL_TIMEOUT_MS);
409
+ });
410
+ assert.equal(existsSync(join(evidenceRoot, 'cohort.jsonl')), false, 'a timed-out run must not write a cost/evidence record');
411
+ assert.equal(existsSync(evidenceRoot), false, 'and must not even create the evidence directory');
412
+ }
413
+ finally {
414
+ process.chdir(savedCwd);
415
+ rmSync(evidenceParent, { recursive: true, force: true });
416
+ }
417
+ });
418
+ }
419
+ finally {
420
+ globalThis.fetch = savedFetch;
421
+ for (const [key, value] of Object.entries(savedEnv)) {
422
+ if (value === undefined)
423
+ delete process.env[key];
424
+ else
425
+ process.env[key] = value;
426
+ }
427
+ await provider.closeAll();
428
+ }
429
+ if (failures.length > 0) {
430
+ console.log(`\nDSH-LLM TESTS: ${failures.length} FAILED, ${passed} passed`);
431
+ for (const name of failures)
432
+ console.log(` - ${name}`);
433
+ process.exitCode = 1;
434
+ }
435
+ else {
436
+ console.log(`\nDSH-LLM TESTS OK: ${passed} cases (request timeout/abort contract; offline, loopback only)`);
437
+ }