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

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 (507) 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 +1744 -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 +135 -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 +546 -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 +509 -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/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +50 -44
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +31 -21
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +75 -24
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +97 -83
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +635 -44
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +24 -59
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +50 -11
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +25 -70
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +382 -246
  191. package/dist/scripts/ledger-workflow-crash.js +10 -20
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +18 -52
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +24 -12
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +39 -28
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +20 -20
  227. package/dist/scripts/session-attach-poc.js +34 -37
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +14 -35
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +27 -46
  233. package/dist/scripts/session-tests.js +796 -96
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +690 -246
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +226 -101
  253. package/dist/scripts/state-cli.js +550 -199
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +29 -93
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +48 -74
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2227 -882
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +319 -160
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +30 -14
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +435 -516
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +44 -63
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +535 -359
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +293 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +1948 -198
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +223 -25
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +354 -88
  506. package/ts/src/wish-pool.ts +79 -0
  507. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,185 @@
1
+ import assert from 'node:assert/strict';
2
+ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { installTurnDeadline, TURN_DEADLINE_EXHAUSTED, TURN_DEADLINE_HANDOFF, TURN_HANDOFF_SCHEMA, writeTurnHandoffTicket, } from '../src/turn-deadline.js';
6
+ const success = () => ({ isError: false, value: null, content: [] });
7
+ const handlers = new Map();
8
+ const restricted = [];
9
+ const lifted = [];
10
+ const makeAgent = (id) => ({
11
+ id,
12
+ ctx: {
13
+ tools: {
14
+ restrict(filter) {
15
+ restricted.push({ agentId: id, deny: [...filter.deny] });
16
+ return () => lifted.push(id);
17
+ },
18
+ },
19
+ },
20
+ });
21
+ const ctx = {
22
+ tools: {
23
+ schemas: () => [{ name: 'gotry_alpha' }, { name: 'gotry_beta' }],
24
+ },
25
+ on(event, listener) {
26
+ const bucket = handlers.get(event) ?? [];
27
+ bucket.push(listener);
28
+ handlers.set(event, bucket);
29
+ return () => { };
30
+ },
31
+ };
32
+ // Product-path opt-out: `enabled: false` must install nothing.
33
+ const productCtx = {
34
+ tools: { schemas: () => [] },
35
+ on(event) {
36
+ throw new Error(`opt-out path must not subscribe to ${event}`);
37
+ },
38
+ };
39
+ installTurnDeadline(productCtx, { enabled: false });
40
+ const clock = (() => {
41
+ let t = 1_000_000;
42
+ return { now: () => t, advance: (ms) => { t += ms; } };
43
+ })();
44
+ const handoffRoot = mkdtempSync(join(tmpdir(), 'gotry-turn-handoff-tests-'));
45
+ installTurnDeadline(ctx, { stateRoot: handoffRoot, now: clock.now });
46
+ const execute = handlers.get('tools/execute')?.[0];
47
+ const postExecute = handlers.get('tools/post-execute')?.[0];
48
+ const sessionEvent = handlers.get('session/event')?.[0];
49
+ const sessionDisposed = handlers.get('session/disposed')?.[0];
50
+ assert.ok(execute && postExecute && sessionEvent && sessionDisposed, 'all Cordis boundary hooks register');
51
+ // dsh 事件载荷在 event.data 下(与 benchmark-agent-conformance 同型)。
52
+ const userMessage = (id, text, kind = 'user') => sessionEvent({ id }, { type: 'user/message', data: { source: { kind }, content: [{ type: 'text', text }] } });
53
+ const turnEvent = (id, type) => sessionEvent({ id }, { type });
54
+ // Agent-less calls are programmatic control-plane calls, not planner spend.
55
+ {
56
+ let directBodies = 0;
57
+ for (let i = 0; i < 5; i++) {
58
+ const result = await execute({ token: Symbol(`direct-${i}`) }, async () => { directBodies++; return success(); });
59
+ assert.equal(result.isError, false);
60
+ }
61
+ assert.equal(directBodies, 5);
62
+ }
63
+ // ---- converge 路径:sync-planning 消息,软提示 + 硬阈结构化拒绝 ----------------
64
+ {
65
+ const agent = makeAgent('agent-converge');
66
+ turnEvent(agent.id, 'turn/start');
67
+ userMessage(agent.id, '帮我看看10.1去杭州的高铁票');
68
+ {
69
+ const exec = { token: Symbol('conv-1'), agent };
70
+ const result = await execute(exec, async () => success());
71
+ const post = await postExecute(exec, result, async () => ({ kind: 'accept' }));
72
+ assert.equal(result.isError, false);
73
+ assert.equal((post.additionalContexts ?? []).length, 0, 'no convergence context before soft');
74
+ assert.equal(restricted.length, 0);
75
+ }
76
+ // 推进超过软阈(sync-planning 300s):post-execute 注入 TURN_DEADLINE_SOFT
77
+ clock.advance(300_001);
78
+ {
79
+ const exec = { token: Symbol('conv-2'), agent };
80
+ const result = await execute(exec, async () => success());
81
+ const post = await postExecute(exec, result, async () => ({ kind: 'accept' }));
82
+ const contextText = post.additionalContexts?.flatMap(item => item.content ?? []).map(item => item.text ?? '').join('\n') ?? '';
83
+ assert.ok(contextText.includes('TURN_DEADLINE_SOFT'), 'soft context after crossing softMs');
84
+ assert.ok(contextText.includes('final answer'), 'converge soft text asks for a final answer');
85
+ }
86
+ // 推进超过硬阈(600s):结构化拒绝 + 同步抑制 schema,不落任何工单
87
+ clock.advance(300_000);
88
+ {
89
+ const exec = { token: Symbol('conv-3'), agent };
90
+ let bodyRan = false;
91
+ const result = await execute(exec, async () => { bodyRan = true; return success(); });
92
+ assert.equal(bodyRan, false);
93
+ assert.equal(result.isError, true);
94
+ assert.deepEqual(result.error?.info, { name: 'TurnDeadlineError', code: TURN_DEADLINE_EXHAUSTED });
95
+ assert.match(result.content[0]?.text ?? '', /TURN_DEADLINE_EXHAUSTED/);
96
+ }
97
+ assert.deepEqual(restricted.at(-1), { agentId: agent.id, deny: ['gotry_alpha', 'gotry_beta'] });
98
+ const convergeTicketDir = join(handoffRoot, 'gotry-state', 'turn-handoffs');
99
+ assert.equal(existsSync(convergeTicketDir) ? readdirSync(convergeTicketDir).length : 0, 0, 'converge writes no ticket');
100
+ turnEvent(agent.id, 'turn/end');
101
+ assert.equal(lifted.at(-1), agent.id);
102
+ }
103
+ // ---- handoff 路径:deep-planning 消息(失败轨迹原文)→ 落单转后台 -----------
104
+ {
105
+ const agent = makeAgent('agent-handoff');
106
+ turnEvent(agent.id, 'turn/start');
107
+ const deepMessage = '2026年我还有6天IRW额度 ,我准备再请几天假,在国内待个十几天。'
108
+ + '其中10.3要去湖南衡阳参加同学婚礼。其他我没有特别的要求,请根据我的实际情况,偏好,外部环境等因素,给我安排行程';
109
+ userMessage(agent.id, deepMessage);
110
+ // 软阈(deep 120s):提示语义是「收尾摸底、即将转后台」
111
+ clock.advance(120_001);
112
+ {
113
+ const exec = { token: Symbol('hand-1'), agent };
114
+ const result = await execute(exec, async () => success());
115
+ const post = await postExecute(exec, result, async () => ({ kind: 'accept' }));
116
+ const contextText = post.additionalContexts?.flatMap(item => item.content ?? []).map(item => item.text ?? '').join('\n') ?? '';
117
+ assert.ok(contextText.includes('background planning'), 'handoff soft text announces the handoff');
118
+ assert.equal(result.isError, false);
119
+ }
120
+ // 硬阈(240s):TURN_DEADLINE_HANDOFF 结构化结果 + 工单落盘
121
+ clock.advance(120_000);
122
+ {
123
+ const exec = { token: Symbol('hand-2'), agent };
124
+ let bodyRan = false;
125
+ const result = await execute(exec, async () => { bodyRan = true; return success(); });
126
+ assert.equal(bodyRan, false);
127
+ assert.equal(result.isError, true);
128
+ assert.deepEqual(result.error?.info, { name: 'TurnDeadlineHandoff', code: TURN_DEADLINE_HANDOFF });
129
+ const text = result.content[0]?.text ?? '';
130
+ assert.match(text, /TURN_DEADLINE_HANDOFF/);
131
+ assert.match(text, /th-/, 'result names the persisted ticket id');
132
+ assert.match(text, /约 1 小时/, 'ETA label mirrors the S5 promise');
133
+ }
134
+ // 同步抑制已生效
135
+ assert.deepEqual(restricted.at(-1), { agentId: agent.id, deny: ['gotry_alpha', 'gotry_beta'] });
136
+ const tickets = readdirSync(join(handoffRoot, 'gotry-state', 'turn-handoffs'));
137
+ assert.equal(tickets.length, 1, 'exactly one handoff ticket');
138
+ const ticket = JSON.parse(readFileSync(join(handoffRoot, 'gotry-state', 'turn-handoffs', tickets[0]), 'utf8'));
139
+ assert.equal(ticket.schema, TURN_HANDOFF_SCHEMA);
140
+ assert.equal(ticket.status, 'open');
141
+ assert.equal(ticket.userMessage, deepMessage, 'ticket carries the verbatim user ask');
142
+ assert.ok(ticket.requestedAt);
143
+ assert.equal(ticket.etaLabel, '约 1 小时');
144
+ // 新 user 消息 = 新预算:同 agent 下一轮 quick 消息恢复放行
145
+ turnEvent(agent.id, 'turn/end');
146
+ userMessage(agent.id, '你好');
147
+ {
148
+ const result = await execute({ token: Symbol('hand-3'), agent }, async () => success());
149
+ assert.equal(result.isError, false);
150
+ }
151
+ turnEvent(agent.id, 'turn/end');
152
+ assert.equal(readdirSync(join(handoffRoot, 'gotry-state', 'turn-handoffs')).length, 1, 'no extra tickets');
153
+ }
154
+ // ---- 路由输入纪律:plugin 注入的 user/message 不参与路由 --------------------
155
+ {
156
+ const agent = makeAgent('agent-plugin-msg');
157
+ turnEvent(agent.id, 'turn/start');
158
+ userMessage(agent.id, '你好');
159
+ // plugin 注入的深规划文本不得翻转预算
160
+ userMessage(agent.id, deepPlanningNoise(), 'plugin');
161
+ clock.advance(1_000_000);
162
+ {
163
+ const exec = { token: Symbol('plug-1'), agent };
164
+ const result = await execute(exec, async () => success());
165
+ // quick 硬阈 180s 已被跨过 → converge 拒绝(而非 handoff):证明路由未被 plugin 消息改写
166
+ assert.deepEqual(result.error?.info, { name: 'TurnDeadlineError', code: TURN_DEADLINE_EXHAUSTED });
167
+ }
168
+ turnEvent(agent.id, 'turn/end');
169
+ }
170
+ function deepPlanningNoise() {
171
+ return '系统提醒样例:10.3 婚礼 IRW 请假 十几天 安排行程——这是插件注入,不该参与路由';
172
+ }
173
+ // ---- 工单写入器(独立契约:原子写 + 目录形态) ------------------------------
174
+ {
175
+ const root = mkdtempSync(join(tmpdir(), 'gotry-turn-handoff-writer-'));
176
+ const ticket = await writeTurnHandoffTicket(root, '短消息');
177
+ const dir = join(root, 'gotry-state', 'turn-handoffs');
178
+ assert.equal(readdirSync(dir).length, 1);
179
+ const loaded = JSON.parse(readFileSync(join(dir, `${ticket.id}.json`), 'utf8'));
180
+ assert.equal(loaded.userMessage, '短消息');
181
+ rmSync(root, { recursive: true, force: true });
182
+ }
183
+ sessionDisposed({ id: 'agent-handoff' });
184
+ rmSync(handoffRoot, { recursive: true, force: true });
185
+ console.log('agent turn deadline tests: OK (routing→converge/handoff exits, ticket persistence, plugin-message discipline, lifecycle)');
@@ -1,54 +1,44 @@
1
+ /**
2
+ * agent-reach 深度能力层测试(yt-dlp + gh,系统装了就调,没装降级):
3
+ * 1. yt-dlp:无论装没装,三值之一(found/not-installed/error)且不抛错
4
+ * 2. gh 搜索:gh 已装的环境真实搜 agent-reach;未装环境走 not-installed
5
+ * 3. 证据链标注含 [agent-reach:<bin>@
6
+ * 4. 超时降级
7
+ *
8
+ * 运行: cd ts && npx tsx scripts/agent-reach-deep-tests.ts
9
+ */
1
10
  import assert from 'node:assert/strict';
2
11
  import { videoSubtitle, githubSearch } from '../capabilities/agent-reach-deep.js';
12
+ // 1. yt-dlp 三值
3
13
  {
4
- const r = await videoSubtitle({
5
- url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
6
- timeoutMs: 15_000
7
- });
8
- assert.ok([
9
- 'found',
10
- 'not-installed',
11
- 'error',
12
- 'timeout'
13
- ].includes(r.verdict), `verdict 三值之一: ${r.verdict}`);
14
+ const r = await videoSubtitle({ url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', timeoutMs: 15_000 });
15
+ assert.ok(['found', 'not-installed', 'error', 'timeout'].includes(r.verdict), `verdict 三值之一: ${r.verdict}`);
14
16
  assert.ok(r.evidence.includes('[agent-reach:yt-dlp@'), '证据链前缀');
15
17
  console.log(`1. yt-dlp → ${r.verdict} (${r.latencyMs}ms) OK`);
16
- }{
17
- const r = await githubSearch({
18
- query: 'agent-reach',
19
- limit: 3
20
- });
21
- assert.ok([
22
- 'found',
23
- 'not-installed',
24
- 'error',
25
- 'timeout'
26
- ].includes(r.verdict));
18
+ }
19
+ // 2. gh 搜索(装了就真实搜,没装走 not-installed)
20
+ {
21
+ const r = await githubSearch({ query: 'agent-reach', limit: 3 });
22
+ assert.ok(['found', 'not-installed', 'error', 'timeout'].includes(r.verdict));
27
23
  if (r.verdict === 'found') {
28
24
  assert.ok((r.repos?.length ?? 0) > 0, 'found 应有 repos');
29
25
  const top = r.repos[0];
30
26
  console.log(`2. gh search → found (top: ${top.name} ★${top.stars}) OK`);
31
- } else {
27
+ }
28
+ else {
32
29
  console.log(`2. gh search → ${r.verdict} (${r.stderr?.slice(0, 60)}) OK`);
33
30
  }
34
- }{
35
- const r = await githubSearch({
36
- query: 'hello'
37
- });
31
+ }
32
+ // 3. 证据链格式
33
+ {
34
+ const r = await githubSearch({ query: 'hello' });
38
35
  assert.match(r.evidence, /\[agent-reach:gh@/, 'gh 证据链');
39
36
  console.log('3. 证据链格式 OK');
40
- }{
41
- const r = await githubSearch({
42
- query: 'hello',
43
- timeoutMs: 1
44
- });
45
- assert.ok([
46
- 'timeout',
47
- 'error',
48
- 'not-installed'
49
- ].includes(r.verdict));
37
+ }
38
+ // 4. 超时降级(1ms timeout)
39
+ {
40
+ const r = await githubSearch({ query: 'hello', timeoutMs: 1 });
41
+ assert.ok(['timeout', 'error', 'not-installed'].includes(r.verdict));
50
42
  console.log(`4. 超时 → ${r.verdict} OK`);
51
- }console.log('\nAGENT-REACH DEEP TESTS: 4/4 OK(yt-dlp/gh 可选工具,不装则降级)');
52
-
53
-
54
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-deep-tests.ts
43
+ }
44
+ console.log('\nAGENT-REACH DEEP TESTS: 4/4 OK(yt-dlp/gh 可选工具,不装则降级)');
@@ -0,0 +1,354 @@
1
+ /**
2
+ * agent-reach wrapper 确定性失败面测试(fake python 注入,零网络/零 venv):
3
+ * issue #559 第五缺口回归锁——用户面失败文案必须是人话:有原因、有源归属
4
+ * (agent-reach)、有受影响能力(channel.method)、有下一步(gotry_doctor/setup/清单);
5
+ * 桥进程 stderr 与桥结构化异常原话(任意上游文本,可含凭证 URL/traceback)一律不透传;
6
+ * 形状不对的桥 JSON(null/数组/字段错型)永不抛错;setup(上游 check() 原话)与
7
+ * inventory(上游注册表本体)透传契约不变。anything.ts failureReason 同构口径。
8
+ *
9
+ * 1. 成功数据回归锁:桥 ok JSON → found + data 原样
10
+ * 2. stderr 噪音不降级:exit 0 + 好结果 + 库 warning → 仍 found
11
+ * 3. needs-setup 锁:setup 原话透传;error 安全分类
12
+ * 4. 未知渠道锁:inventory 透传;error 安全描述
13
+ * 5. 缺可执行 python(pythonBin 指向不存在路径)→ not-installed + doctor 指引
14
+ * 6. 可执行存在但解释器缺失(坏 shebang → spawn ENOENT)→ not-installed
15
+ * 7. 缺 agent_reach 包(桥 exit 3 未装 JSON)→ not-installed
16
+ * 8. 超时(桥挂死被 SIGKILL)→ error + 人话超时,不留空 error
17
+ * 9. 输出不可解析(半截 JSON + traceback/凭证样 stderr)→ error 不漏原文
18
+ * 10. 非零退出 + stderr(空 stdout)→ error 不漏 stderr 原文,有原因有下一步
19
+ * 11. 空响应(exit 0 零输出零 stderr)→ error 有原因有下一步
20
+ * 12. 桥结构化 error 含凭证样 URL → 不透传,分类安全文案
21
+ * 13. 形状不对的桥 JSON(null/数组/error 数字或对象/check 错型)→ 永不抛错
22
+ * 14. readUrl 产品边界:缺 venv 指引穿透(undefined 回归锁)/成功数据/凭证安全
23
+ *
24
+ * 运行: cd ts && npx tsx scripts/agent-reach-error-tests.ts
25
+ */
26
+ import assert from 'node:assert/strict';
27
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
28
+ import { tmpdir } from 'node:os';
29
+ import { join } from 'node:path';
30
+ import { reach, readUrl } from '../capabilities/agent-reach.js';
31
+ const tmp = await mkdtemp(join(tmpdir(), 'agent-reach-err-test-'));
32
+ try {
33
+ // fake python:模拟反射桥 agent-reach-bridge.py 的进程级行为(桥 JSON 由脚本原样打出)
34
+ async function fakePython(name, behaviour) {
35
+ const p = join(tmp, name);
36
+ let body = '';
37
+ if (behaviour === 'ok') {
38
+ body = `cat <<'JSON'
39
+ {"ok": true, "data": {"topics": [1, 2, 3]}}
40
+ JSON
41
+ `;
42
+ }
43
+ else if (behaviour === 'ok-string') {
44
+ // readUrl 成功面:上游 WebChannel.read 的 Jina 首行 Title 形态
45
+ body = `cat <<'JSON'
46
+ {"ok": true, "data": "Title: Fake Page\\n\\nhello 正文"}
47
+ JSON
48
+ `;
49
+ }
50
+ else if (behaviour === 'ok-with-stderr-noise') {
51
+ // 上游库 warning 类 stderr 噪音:不得把好结果打成失败
52
+ body = `echo 'DeprecationWarning: datetime.utcnow() is deprecated' >&2
53
+ cat <<'JSON'
54
+ {"ok": true, "data": "fine"}
55
+ JSON
56
+ `;
57
+ }
58
+ else if (behaviour === 'needs-setup') {
59
+ // 桥 exit 3:渠道调用抛错 + 上游 check() 结论(原话透传契约)
60
+ body = `cat <<'JSON'
61
+ {"ok": false, "error": "CookieError: not logged in", "check": {"status": "off", "message": "configure cookies via agent-reach login xueqiu"}}
62
+ JSON
63
+ exit 3
64
+ `;
65
+ }
66
+ else if (behaviour === 'not-installed-pkg') {
67
+ // 桥 exit 3:上游 agent_reach 包未装
68
+ body = `cat <<'JSON'
69
+ {"ok": false, "error": "agent_reach 未安装: No module named 'agent_reach'"}
70
+ JSON
71
+ exit 3
72
+ `;
73
+ }
74
+ else if (behaviour === 'unknown-channel') {
75
+ // 桥 exit 2:未知渠道 + 上游注册表
76
+ body = `cat <<'JSON'
77
+ {"ok": false, "error": "未知渠道 'nosuch'", "channels": {"web": "WebChannel"}}
78
+ JSON
79
+ exit 2
80
+ `;
81
+ }
82
+ else if (behaviour === 'hang') {
83
+ // exec 让 SIGKILL 直接命中 sleep,不留孤儿
84
+ body = `exec sleep 30\n`;
85
+ }
86
+ else if (behaviour === 'bad-shebang') {
87
+ // 文件在但解释器不存在 → spawn 报 ENOENT(venv python 装坏的真实形态)
88
+ await writeFile(p, '#!/no/such/interpreter-xyz\nexit 0\n', { mode: 0o755 });
89
+ return p;
90
+ }
91
+ else if (behaviour === 'truncated-json') {
92
+ // 半截 JSON + traceback/凭证样 stderr(用户面泄漏回归锁,#559)
93
+ body = `printf '%s' '{"ok": true, "data": {"top'
94
+ echo 'Traceback (most recent call last):' >&2
95
+ echo ' File "agent_reach/channels.py", line 42, in read' >&2
96
+ echo 'ConnectionError: 502 for url https://r.jina.ai/?api_key=sk-SECRET-abc123' >&2
97
+ `;
98
+ }
99
+ else if (behaviour === 'exit1-stderr') {
100
+ // 进程级失败:非零退出 + 带凭证样片段的 stderr,stdout 全空
101
+ body = `echo "python: can't open file 'agent-reach-bridge.py': cookie=SESSIONID=LEAK-xyz" >&2
102
+ exit 1
103
+ `;
104
+ }
105
+ else {
106
+ // empty:exit 0,stdout/stderr 全空
107
+ body = `exit 0\n`;
108
+ }
109
+ if (behaviour === 'cred-error') {
110
+ // 桥 exit 3:渠道调用抛错,error 是任意上游异常原话(合成凭证 URL,不换行)
111
+ body = `cat <<'JSON'
112
+ {"ok": false, "error": "RequestsError: 502 for url https://r.jina.ai/?api_key=sk-LEAK-987654"}
113
+ JSON
114
+ exit 3
115
+ `;
116
+ }
117
+ else if (behaviour === 'json-null') {
118
+ body = `printf 'null'\n`;
119
+ }
120
+ else if (behaviour === 'json-array') {
121
+ body = `printf '[1, 2, 3]'\n`;
122
+ }
123
+ else if (behaviour === 'error-number') {
124
+ body = `cat <<'JSON'
125
+ {"ok": false, "error": 987654}
126
+ JSON
127
+ exit 3
128
+ `;
129
+ }
130
+ else if (behaviour === 'error-object') {
131
+ body = `cat <<'JSON'
132
+ {"ok": false, "error": {"trace": "RequestsError for url https://r.jina.ai/?api_key=sk-OBJ-LEAK"}}
133
+ JSON
134
+ exit 3
135
+ `;
136
+ }
137
+ else if (behaviour === 'check-string') {
138
+ // check 是字符串(形状错)→ 不得据此判 needs-setup
139
+ body = `cat <<'JSON'
140
+ {"ok": false, "check": "off", "error": "boom-raw"}
141
+ JSON
142
+ exit 3
143
+ `;
144
+ }
145
+ else if (behaviour === 'check-message-number') {
146
+ // check.status 合法但 message 非字符串 → 路由照旧,setup 不冒充原话
147
+ body = `cat <<'JSON'
148
+ {"ok": false, "check": {"status": "off", "message": 12345}, "error": "boom2"}
149
+ JSON
150
+ exit 3
151
+ `;
152
+ }
153
+ await writeFile(p, `#!/bin/sh\n${body}`, { mode: 0o755 });
154
+ return p;
155
+ }
156
+ // 1. 成功数据回归锁(分类不得碰成功路径)
157
+ {
158
+ const py = await fakePython('py-ok', 'ok');
159
+ const r = await reach({ channel: 'probe', method: 'read', pythonBin: py });
160
+ assert.equal(r.verdict, 'found');
161
+ assert.deepEqual(r.data, { topics: [1, 2, 3] });
162
+ assert.match(r.evidence, /\[agent-reach:probe\.read@2\d{3}-/, '证据链 tag 形态不变');
163
+ console.log('1. 桥 ok JSON → found + data 原样 OK');
164
+ }
165
+ // 2. stderr 噪音不降级
166
+ {
167
+ const py = await fakePython('py-ok-noise', 'ok-with-stderr-noise');
168
+ const r = await reach({ channel: 'probe', method: 'read', pythonBin: py });
169
+ assert.equal(r.verdict, 'found', '库 warning 不得打成失败');
170
+ console.log('2. stderr 噪音 + 好结果 → 仍 found OK');
171
+ }
172
+ // 3. needs-setup 锁:setup = 上游 check() 原话逐字透传;error 走分类安全文案(桥异常原话不透传)
173
+ {
174
+ const py = await fakePython('py-setup', 'needs-setup');
175
+ const r = await reach({ channel: 'xueqiu', method: 'get_stock_quote', args: ['SH600519'], pythonBin: py });
176
+ assert.equal(r.verdict, 'needs-setup');
177
+ assert.equal(r.setup, 'configure cookies via agent-reach login xueqiu', 'setup = 上游 check() 原话');
178
+ assert.ok(r.error && r.error.length > 0, 'error 必须非空(安全分类文案)');
179
+ assert.ok(!r.error.includes('CookieError'), `桥异常原话不得进 error 面,实得:${r.error}`);
180
+ assert.match(r.error ?? '', /xueqiu\.get_stock_quote/, 'error 须带受影响能力');
181
+ assert.match(r.evidence, /@needs-setup@/);
182
+ console.log('3. needs-setup:setup 原话透传 + error 安全分类 OK');
183
+ }
184
+ // 4. 未知渠道锁:inventory 带回上游注册表;error 用自身查询值给安全描述(桥报错文案是任意上游文本,不透传)
185
+ {
186
+ const py = await fakePython('py-unknown', 'unknown-channel');
187
+ const r = await reach({ channel: 'nosuch', method: 'foo', pythonBin: py });
188
+ assert.equal(r.ok, false);
189
+ const channels = (r.inventory?.channels ?? {});
190
+ assert.equal(channels.web, 'WebChannel', 'inventory 带回上游注册表(本体不受影响)');
191
+ assert.match(r.error ?? '', /nosuch\.foo/, '安全描述带受影响能力,实得:' + r.error);
192
+ assert.match(r.error ?? '', /清单/, '安全描述指明按清单改正');
193
+ assert.match(r.error ?? '', /重调/, '安全描述带下一步');
194
+ assert.notEqual(r.error, "未知渠道 'nosuch'", '桥报错原文不透传');
195
+ console.log('4. 未知渠道 → inventory 透传 + error 安全描述 OK');
196
+ }
197
+ // 5. 缺可执行 python(pythonBin 指向不存在路径;与 anything.ts hbcliBin 注入同构)
198
+ {
199
+ const r = await reach({ channel: 'web', method: 'read', pythonBin: join(tmp, 'definitely-missing-python') });
200
+ assert.equal(r.verdict, 'not-installed');
201
+ assert.match(r.setup ?? '', /doctor --fix/, '带补装指引');
202
+ assert.match(r.evidence, /@not-installed@/);
203
+ console.log('5. 缺 python → not-installed + doctor 指引 OK');
204
+ }
205
+ // 6. 可执行存在但解释器缺失(坏 shebang → spawn ENOENT)
206
+ {
207
+ const py = await fakePython('py-bad-shebang', 'bad-shebang');
208
+ const r = await reach({ channel: 'web', method: 'read', pythonBin: py });
209
+ assert.equal(r.verdict, 'not-installed', `实际 verdict=${r.verdict} error=${r.error}`);
210
+ assert.match(r.setup ?? '', /doctor --fix/, '带补装指引');
211
+ console.log('6. 坏 shebang(ENOENT)→ not-installed OK');
212
+ }
213
+ // 7. 缺 agent_reach 包(桥 exit 3 未装 JSON)
214
+ {
215
+ const py = await fakePython('py-nopkg', 'not-installed-pkg');
216
+ const r = await reach({ channel: 'web', method: 'read', pythonBin: py });
217
+ assert.equal(r.verdict, 'not-installed');
218
+ assert.match(r.setup ?? '', /doctor --fix/, '带补装指引');
219
+ console.log('7. 缺 agent_reach 包 → not-installed OK');
220
+ }
221
+ // 8. 超时:桥挂死被 SIGKILL → 人话超时(旧实现回落成空 error,无原因无下一步)
222
+ {
223
+ const py = await fakePython('py-hang', 'hang');
224
+ const r = await reach({ channel: 'web', method: 'read', args: ['https://example.com'], timeoutMs: 400, pythonBin: py });
225
+ assert.equal(r.ok, false);
226
+ assert.equal(r.verdict, 'error');
227
+ assert.ok(r.error && r.error.length > 0, `必须给出原因,实得:${JSON.stringify(r.error)}`);
228
+ assert.ok(/超时/.test(r.error), `原因须指明超时,实得:${r.error}`);
229
+ assert.ok(/agent-reach/.test(r.error), `须有源归属,实得:${r.error}`);
230
+ assert.ok(/timeoutMs|稍后/.test(r.error), `须有下一步,实得:${r.error}`);
231
+ assert.ok(!/(exit null|SIGKILL|ENOENT|spawn )/i.test(r.error), `用户面不得出现进程噪音,实得:${r.error}`);
232
+ assert.match(r.evidence, /\[agent-reach:web\.read@error@/, '证据链 tag 形态不变');
233
+ console.log(`8. 超时 → 人话降级 OK(${r.error})`);
234
+ }
235
+ // 9. 输出不可解析 + traceback/凭证样 stderr:原文不得进用户面(旧实现 slice(0,200) 直漏)
236
+ {
237
+ const py = await fakePython('py-truncated', 'truncated-json');
238
+ const r = await reach({ channel: 'web', method: 'read', args: ['https://example.com'], pythonBin: py });
239
+ assert.equal(r.ok, false);
240
+ assert.equal(r.verdict, 'error');
241
+ assert.ok(r.error && r.error.length > 0, `必须给出原因,实得:${JSON.stringify(r.error)}`);
242
+ assert.ok(!r.error.includes('Traceback'), `不得漏 traceback,实得:${r.error}`);
243
+ assert.ok(!r.error.includes('sk-SECRET-abc123'), `不得漏凭证样片段,实得:${r.error}`);
244
+ assert.ok(!r.error.includes('channels.py'), `不得漏 stderr 原文,实得:${r.error}`);
245
+ assert.ok(/无法解析/.test(r.error), `原因须指明输出不可解析,实得:${r.error}`);
246
+ assert.ok(/agent-reach/.test(r.error), `须有源归属,实得:${r.error}`);
247
+ assert.ok(/doctor/.test(r.error), `须有下一步,实得:${r.error}`);
248
+ console.log(`9. 输出不可解析 → 人话降级 OK(${r.error})`);
249
+ }
250
+ // 10. 非零退出 + 带凭证样片段的 stderr:原文不得进用户面
251
+ {
252
+ const py = await fakePython('py-exit1', 'exit1-stderr');
253
+ const r = await reach({ channel: 'v2ex', method: 'get_hot_topics', pythonBin: py });
254
+ assert.equal(r.ok, false);
255
+ assert.equal(r.verdict, 'error');
256
+ assert.ok(r.error && r.error.length > 0, `必须给出原因,实得:${JSON.stringify(r.error)}`);
257
+ assert.ok(!r.error.includes('LEAK-xyz'), `不得漏 stderr 原文,实得:${r.error}`);
258
+ assert.ok(!r.error.includes("can't open file"), `不得漏进程噪音,实得:${r.error}`);
259
+ assert.ok(/未返回可判定结果/.test(r.error), `原因须说清未返回结果,实得:${r.error}`);
260
+ assert.ok(/退出码 1/.test(r.error), `可带退出码这一安全诊断,实得:${r.error}`);
261
+ assert.ok(/doctor/.test(r.error), `须有下一步,实得:${r.error}`);
262
+ console.log(`10. 非零退出 + stderr → 人话降级 OK(${r.error})`);
263
+ }
264
+ // 11. 空响应(exit 0 零输出):旧实现回落成空 error
265
+ {
266
+ const py = await fakePython('py-empty', 'empty');
267
+ const r = await reach({ channel: 'v2ex', method: 'get_hot_topics', pythonBin: py });
268
+ assert.equal(r.ok, false);
269
+ assert.equal(r.verdict, 'error');
270
+ assert.ok(r.error && r.error.length > 0, `必须给出原因,实得:${JSON.stringify(r.error)}`);
271
+ assert.ok(/未返回可判定结果/.test(r.error), `原因须说清未返回结果,实得:${r.error}`);
272
+ assert.ok(/agent-reach/.test(r.error), `须有源归属,实得:${r.error}`);
273
+ assert.ok(/doctor/.test(r.error), `须有下一步,实得:${r.error}`);
274
+ console.log(`11. 空响应 → 人话降级 OK(${r.error})`);
275
+ }
276
+ // 12. 桥 exit 3 结构化 error 是任意上游异常原话(可含凭证 URL/traceback):不透传,走分类安全文案
277
+ {
278
+ const py = await fakePython('py-cred-error', 'cred-error');
279
+ const r = await reach({ channel: 'web', method: 'read', args: ['https://example.com'], pythonBin: py });
280
+ assert.equal(r.ok, false);
281
+ assert.equal(r.verdict, 'error');
282
+ assert.ok(r.error && r.error.length > 0, `必须给出原因,实得:${JSON.stringify(r.error)}`);
283
+ assert.ok(!r.error.includes('sk-LEAK-987654'), `凭证样 URL 不得进 error 面,实得:${r.error}`);
284
+ assert.ok(!r.error.includes('RequestsError'), `桥异常原话不得透传,实得:${r.error}`);
285
+ assert.ok(/调用失败/.test(r.error), `原因须说清渠道调用失败,实得:${r.error}`);
286
+ assert.ok(/web\.read/.test(r.error), `须带受影响能力,实得:${r.error}`);
287
+ assert.ok(/doctor|check\(\)/.test(r.error), `须有下一步,实得:${r.error}`);
288
+ console.log(`12. 桥结构化 error 带凭证 → 分类安全文案 OK(${r.error})`);
289
+ }
290
+ // 13. JSON 合法但形状不对(null/数组/error 非字符串/check 非对象):永不抛错,按不可解析/安全文案处理
291
+ {
292
+ // 13a. null → parsed 形状闸兜住
293
+ const rA = await reach({ channel: 'v2ex', method: 'get_hot_topics', pythonBin: await fakePython('py-json-null', 'json-null') });
294
+ assert.equal(rA.verdict, 'error');
295
+ assert.ok(rA.error && /无法解析/.test(rA.error), `null 输出须人话降级,实得:${JSON.stringify(rA.error)}`);
296
+ // 13b. 数组 → 同上
297
+ const rB = await reach({ channel: 'v2ex', method: 'get_hot_topics', pythonBin: await fakePython('py-json-array', 'json-array') });
298
+ assert.equal(rB.verdict, 'error');
299
+ assert.ok(rB.error && /无法解析/.test(rB.error), `数组输出须人话降级,实得:${JSON.stringify(rB.error)}`);
300
+ // 13c. error 是数字 → 不得把数字透传成 error,也不得抛错
301
+ const rC = await reach({ channel: 'v2ex', method: 'get_hot_topics', pythonBin: await fakePython('py-error-number', 'error-number') });
302
+ assert.equal(rC.verdict, 'error');
303
+ assert.ok(rC.error && rC.error.length > 0 && /调用失败/.test(rC.error), `error 为数字须安全分类,实得:${JSON.stringify(rC.error)}`);
304
+ assert.ok(!rC.error.includes('987654'), `数字原值不得透传,实得:${rC.error}`);
305
+ // 13d. error 是对象(内嵌凭证样 URL)→ 不透传不抛错
306
+ const rD = await reach({ channel: 'v2ex', method: 'get_hot_topics', pythonBin: await fakePython('py-error-object', 'error-object') });
307
+ assert.equal(rD.verdict, 'error');
308
+ assert.ok(rD.error && /调用失败/.test(rD.error), `error 为对象须安全分类,实得:${JSON.stringify(rD.error)}`);
309
+ assert.ok(!rD.error.includes('sk-OBJ-LEAK'), `对象内凭证样片段不得泄漏,实得:${rD.error}`);
310
+ // 13e. check 是字符串 → 不冒充 needs-setup(形状闸),走安全 error
311
+ const rE = await reach({ channel: 'xueqiu', method: 'get_stock_quote', args: ['SH600519'], pythonBin: await fakePython('py-check-string', 'check-string') });
312
+ assert.equal(rE.verdict, 'error', `check 非对象不得判 needs-setup,实得:${rE.verdict}`);
313
+ assert.ok(rE.error && /调用失败/.test(rE.error), `实得:${JSON.stringify(rE.error)}`);
314
+ assert.ok(!rE.error.includes('boom-raw'), `桥原话不得透传,实得:${rE.error}`);
315
+ // 13f. check.message 非字符串 → verdict 照常走 check.status,但 setup 不冒充原话
316
+ const rF = await reach({ channel: 'xueqiu', method: 'get_stock_quote', args: ['SH600519'], pythonBin: await fakePython('py-check-msg-num', 'check-message-number') });
317
+ assert.equal(rF.verdict, 'needs-setup', 'check.status 合法仍按上游体检结论路由');
318
+ assert.equal(rF.setup, undefined, 'message 非字符串不冒充上游原话');
319
+ assert.ok(rF.error && rF.error.length > 0 && !rF.error.includes('boom2'), `error 须安全非空,实得:${JSON.stringify(rF.error)}`);
320
+ console.log('13. 形状不对的桥 JSON(null/数组/error 数字或对象/check 错型)→ 永不抛错 + 人话降级 OK');
321
+ }
322
+ // 14. readUrl 产品边界(gotry_web_search 渲染 ${r.error}):缺 venv 的 not-installed 指引必须穿过来,不留 undefined
323
+ {
324
+ // 14a. 缺 .venv python(pythonBin 指向不存在路径,不碰真实 venv):旧实现 error=undefined → 工具面渲染 unavailable (undefined)
325
+ const rA = await readUrl({ url: 'https://example.com', pythonBin: join(tmp, 'definitely-missing-python') });
326
+ assert.equal(rA.ok, false);
327
+ assert.equal(rA.via, 'r.jina.ai-error');
328
+ assert.ok(rA.error && rA.error.length > 0, `缺 venv 时 error 不得为 undefined,实得:${JSON.stringify(rA.error)}`);
329
+ assert.match(rA.error, /doctor --fix/, '须带补装指引');
330
+ assert.ok(!String(rA.error).includes('undefined'), '不得渲染 undefined');
331
+ // 14b. 成功数据穿 readUrl(确定性 fake,零网络)
332
+ const rB = await readUrl({ url: 'https://example.com', pythonBin: await fakePython('py-ok-string', 'ok-string') });
333
+ assert.equal(rB.ok, true);
334
+ assert.equal(rB.via, 'r.jina.ai');
335
+ assert.equal(rB.title, 'Fake Page', 'Title 提取');
336
+ assert.ok(rB.content?.includes('正文'), 'content 透传');
337
+ // 14c. 桥 error 对象(内嵌凭证)穿 readUrl → 安全文案
338
+ const rC = await readUrl({ url: 'https://example.com', pythonBin: await fakePython('py-readurl-obj', 'error-object') });
339
+ assert.equal(rC.ok, false);
340
+ assert.ok(rC.error && rC.error.length > 0 && /调用失败/.test(rC.error), `readUrl error 须安全非空,实得:${JSON.stringify(rC.error)}`);
341
+ assert.ok(!rC.error.includes('sk-OBJ-LEAK'), `凭证样片段不得穿 readUrl,实得:${rC.error}`);
342
+ // 14d. needs-setup 的具体上游动作必须穿过 readUrl,不能只说「按 setup 指引」。
343
+ const rD = await readUrl({ url: 'https://example.com', pythonBin: await fakePython('py-readurl-setup', 'needs-setup') });
344
+ assert.equal(rD.ok, false);
345
+ assert.match(rD.error ?? '', /configure cookies via agent-reach login xueqiu/, '保留具体上游配置动作');
346
+ assert.match(rD.error ?? '', /web.read 调用失败/, '保留安全的能力与失败原因');
347
+ assert.ok(!rD.error?.includes('CookieError'), '不夹带桥异常原文');
348
+ console.log('14. readUrl 产品边界:缺依赖和配置指引穿透+成功数据+凭证安全 OK');
349
+ }
350
+ console.log('\nAGENT-REACH ERROR TESTS: 14/14 OK(确定性失败面,人话降级 + 透传契约回归锁)');
351
+ }
352
+ finally {
353
+ await rm(tmp, { recursive: true, force: true });
354
+ }