@danceiny/gotry 0.0.1-rc.8 → 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 (506) hide show
  1. package/README.md +172 -217
  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 -49
  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 +968 -0
  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 +126 -0
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
  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 +54 -0
  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 +50 -0
  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 +107 -0
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +109 -0
  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 +686 -0
  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 +72 -0
  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 +68 -10
  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 +67 -0
  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 +480 -0
  191. package/dist/scripts/ledger-workflow-crash.js +32 -0
  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 +49 -0
  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 +29 -31
  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 +43 -42
  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 +31 -0
  227. package/dist/scripts/session-attach-poc.js +62 -0
  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 +60 -0
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +29 -0
  233. package/dist/scripts/session-tests.js +862 -0
  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 +703 -219
  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 +261 -0
  253. package/dist/scripts/state-cli.js +585 -0
  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 +59 -0
  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 +86 -0
  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 +2305 -731
  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 +358 -162
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +47 -0
  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 +767 -0
  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 +59 -0
  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 +538 -360
  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 +296 -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 +2107 -224
  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 +278 -40
  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 +360 -89
  506. package/ts/src/wish-pool.ts +79 -0
@@ -0,0 +1,1452 @@
1
+ import assert from 'node:assert/strict';
2
+ import { chmodSync, lstatSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { Context } from '@deepseek-ai/cordis';
6
+ import { apply } from '../src/index.js';
7
+ import { BRIDGE_ERROR_CONTRACT, BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, registerBenchmarkEnvironmentBridge, } from '../src/benchmark-environment-bridge.js';
8
+ import { installBenchmarkToolIsolation } from '../src/benchmark-tool-isolation.js';
9
+ import { BENCHMARK_BRIDGE_CALL_FAILED, BENCHMARK_BRIDGE_CALL_REQUIRED, BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED, BENCHMARK_BRIDGE_OUTPUT_TRUNCATED, BENCHMARK_BRIDGE_RUNNER_FAILED, BENCHMARK_BRIDGE_SPAWN_FAILED, BENCHMARK_BRIDGE_TIMED_OUT, BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE, BENCHMARK_TERMINAL_INVALID, MAX_CONFORMANCE_RETRIES, benchmarkChildFailureForConformanceCode, createBenchmarkAgentConformance, installBenchmarkAgentConformance, parseBenchmarkTerminal, terminalSchemaOutline, validateTerminalBodySchema, validateTerminalBodyValue, validateTerminalOutputConfig, } from '../src/benchmark-agent-conformance.js';
10
+ import { BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES, BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA, appendBoundedChildDiagnostic, classifyBenchmarkChildFailure, classifyBenchmarkTurnEnd, createBenchmarkDiagnosticArbiter, parseBenchmarkChildDiagnostic, } from '../src/benchmark-headless-child-diagnostics.js';
11
+ // Round 6 RED tests: terminal facts must be classified from the structured
12
+ // turn/end envelope, without consulting stderr or reflecting its body.
13
+ {
14
+ const exactFamilies = [
15
+ [['AUTH', 'INVALID_CREDENTIAL', 'MISSING_CREDENTIAL'], 'child_model_auth'],
16
+ [['QUOTA', 'RATE_LIMIT'], 'child_model_capacity'],
17
+ [['SERVER'], 'child_model_server'],
18
+ [['TRANSPORT', 'TIMEOUT'], 'child_model_transport'],
19
+ [['EMPTY_RESPONSE', 'STREAM_CLOSED', 'MALFORMED_RESPONSE', 'INVALID_RESPONSE'], 'child_model_stream'],
20
+ [['INVALID_REQUEST', 'CONTEXT_WINDOW_EXCEEDED', 'NO_ADAPTER', 'UNKNOWN_MODEL', 'UNSUPPORTED_OPTION'], 'child_model_request'],
21
+ [['ABORTED'], 'child_aborted'],
22
+ ];
23
+ for (const [codes, expected] of exactFamilies) {
24
+ for (const code of codes)
25
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code } }), expected);
26
+ }
27
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'RATE_LIMIT', message: 'sentinel' } }), 'child_model_capacity');
28
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'AUTH', message: 'sentinel' } }), 'child_model_auth');
29
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'MISSING_CREDENTIAL', message: 'sentinel' } }), 'child_model_auth');
30
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'QUOTA', message: 'sentinel' } }), 'child_model_capacity');
31
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'SERVER', message: 'sentinel' } }), 'child_model_server');
32
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'TRANSPORT', message: 'api-key sentinel' } }), 'child_model_transport');
33
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 401, message: 'key sentinel' } }), 'child_model_auth');
34
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 403, message: 'key sentinel' } }), 'child_model_auth');
35
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 429, message: 'quota sentinel' } }), 'child_model_capacity');
36
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 503, message: 'server sentinel' } }), 'child_model_server');
37
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 500, message: 'server sentinel' } }), 'child_model_server');
38
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 599, message: 'server sentinel' } }), 'child_model_server');
39
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'PI_AI_ERROR', message: 'opaque' } }), 'child_runtime_error');
40
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'INVALID_RESPONSE', message: 'opaque' } }), 'child_model_stream');
41
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'UNSUPPORTED_OPTION', message: 'opaque' } }), 'child_model_request');
42
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 503.5, message: 'opaque' } }), 'child_runtime_error');
43
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 99, message: 'opaque' } }), 'child_runtime_error');
44
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'OTHER', status: 600, message: 'opaque' } }), 'child_runtime_error');
45
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'rate_limit', message: 'opaque' } }), 'child_runtime_error');
46
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'UNKNOWN', message: 'opaque' } }), 'child_runtime_error');
47
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'error', error: { code: 'UNKNOWN', message: 'message sentinel', requestId: 'request sentinel', path: 'path sentinel', prompt: 'prompt sentinel', key: 'key sentinel' } }), 'child_runtime_error');
48
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'blocked' }), 'child_blocked');
49
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'max-tokens' }), 'child_max_tokens');
50
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'aborted' }), 'child_aborted');
51
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'interrupted' }), 'child_interrupted');
52
+ assert.equal(classifyBenchmarkTurnEnd({ kind: 'completed' }), undefined);
53
+ const writes = [];
54
+ const arbiter = createBenchmarkDiagnosticArbiter(code => writes.push(code));
55
+ arbiter.offer('session-a', 'child_conformance_failure');
56
+ arbiter.offer('session-a', 'child_runtime_error');
57
+ arbiter.offer('session-a', 'child_bridge_failure');
58
+ arbiter.offer('session-b', 'child_model_server');
59
+ arbiter.flush('session-a');
60
+ arbiter.flush('session-a');
61
+ arbiter.flush('session-b');
62
+ arbiter.flush('session-b');
63
+ assert.deepEqual(writes, ['child_bridge_failure', 'child_model_server']);
64
+ }
65
+ assert.equal(MAX_CONFORMANCE_RETRIES, 1);
66
+ // Round 12(#215):terminal body schema 是 closed 结构合同。既有信封/协议用例沿用
67
+ // 一个宽松但 closed 的 fixture(全部键可选),schema 严格性由 Round 12 专段的
68
+ // ChinaTravel-like closed schema 覆盖。
69
+ const OK_BODY_SCHEMA = {
70
+ type: 'object',
71
+ properties: {
72
+ ok: { type: 'boolean' },
73
+ status: { type: 'string' },
74
+ x: { type: 'string' },
75
+ },
76
+ required: [],
77
+ additionalProperties: false,
78
+ };
79
+ const projection = {
80
+ toolName: 'gotry_benchmark_environment',
81
+ allowedTools: ['lookup'],
82
+ terminal: { tag: 'done', max_bytes: 1024, body_schema: OK_BODY_SCHEMA },
83
+ };
84
+ assert.equal(validateTerminalOutputConfig(projection.terminal), true);
85
+ for (const invalid of [
86
+ null,
87
+ { tag: 'done' },
88
+ { tag: '1bad', max_bytes: 1024 },
89
+ { tag: 'done', max_bytes: 0 },
90
+ { tag: 'done', max_bytes: 1024 * 1024 + 1 },
91
+ { tag: 'done', max_bytes: 1024, extra: true },
92
+ { tag: 'done', max_bytes: 1024 },
93
+ { tag: 'done', max_bytes: 1024, body_schema: { type: 'object', properties: {}, required: [], additionalProperties: true } },
94
+ ])
95
+ assert.equal(validateTerminalOutputConfig(invalid), false);
96
+ assert.deepEqual(parseBenchmarkTerminal(' \n<done>{"ok":true}</done>\n', projection.terminal), { ok: true, value: { ok: true } });
97
+ for (const invalid of [
98
+ 'prose <done>{"ok":true}</done>',
99
+ '<done>{"ok":true}</done> trailing',
100
+ '<done>```json\n{"ok":true}\n```</done>',
101
+ '<done>{"ok":true}</done><done>{"ok":true}</done>',
102
+ '<done>{"value":"</done><done>"}</done>',
103
+ '<wrong>{"ok":true}</wrong>',
104
+ '<done>[{"ok":true}]</done>',
105
+ '<done>true</done>',
106
+ '<done>{"ok":</done>',
107
+ ])
108
+ assert.equal(parseBenchmarkTerminal(invalid, projection.terminal).ok, false);
109
+ assert.equal(parseBenchmarkTerminal(`<done>{"x":"${'y'.repeat(1024)}"}</done>`, projection.terminal).ok, false);
110
+ // Reasoning-model compatibility (Round 9): remove complete <think> blocks before
111
+ // applying the unchanged strict terminal-envelope validation. Ordinary prose and
112
+ // unclosed thinking blocks remain fail-closed.
113
+ for (const [raw, expectOk] of [
114
+ ['<think>plan it</think><done>{"ok":true}</done>', true],
115
+ ['<think>a</think>\n<THINK>b</THINK>\n<done>{"ok":true}</done>', true],
116
+ ['<done>{"ok":true}</done><think>after</think>', true],
117
+ ['<think>unclosed<done>{"ok":true}</done>', false],
118
+ ['prose<think>x</think><done>{"ok":true}</done>', false],
119
+ ]) {
120
+ assert.equal(parseBenchmarkTerminal(raw, projection.terminal).ok, expectOk, raw);
121
+ }
122
+ // Round 12 RED tests (issue #215): exact terminal schema projection. The bridge
123
+ // config carries a data-value-free closed body schema; the same structure contract
124
+ // is projected into the system prompt and the single terminal correction; the
125
+ // terminal body is fail-closed validated against it — no autofix, no coercion.
126
+ {
127
+ const travelSchema = {
128
+ type: 'object',
129
+ properties: {
130
+ people_number: { type: 'integer' },
131
+ start_city: { type: 'string' },
132
+ target_city: { type: 'string' },
133
+ itinerary: {
134
+ type: 'array',
135
+ items: {
136
+ type: 'object',
137
+ properties: {
138
+ day: { type: 'integer' },
139
+ activities: {
140
+ type: 'array',
141
+ items: {
142
+ type: 'object',
143
+ properties: {
144
+ type: { type: 'string' },
145
+ start_time: { type: 'string' },
146
+ cost: { type: 'number' },
147
+ transports: {
148
+ type: 'array',
149
+ items: {
150
+ type: 'object',
151
+ properties: { start: { type: 'string' }, mode: { type: 'string' } },
152
+ required: ['start', 'mode'],
153
+ additionalProperties: false,
154
+ },
155
+ },
156
+ },
157
+ required: ['type', 'start_time', 'cost', 'transports'],
158
+ additionalProperties: false,
159
+ },
160
+ },
161
+ },
162
+ required: ['day', 'activities'],
163
+ additionalProperties: false,
164
+ },
165
+ },
166
+ },
167
+ required: ['people_number', 'start_city', 'target_city', 'itinerary'],
168
+ additionalProperties: false,
169
+ };
170
+ const travelConfig = { tag: 'done', max_bytes: 65_536, body_schema: travelSchema };
171
+ assert.equal(validateTerminalOutputConfig(travelConfig), true, 'ChinaTravel-like closed schema is a valid terminal config');
172
+ const legalBody = {
173
+ people_number: 2,
174
+ start_city: 'Dubai',
175
+ target_city: 'Chengdu',
176
+ itinerary: [{ day: 1, activities: [{ type: 'attraction', start_time: '09:00', cost: 0, transports: [{ start: 'hotel', mode: 'walk' }] }] }],
177
+ };
178
+ // Schema dialect: structural keywords only; data-bearing annotation faces are rejected.
179
+ assert.equal(validateTerminalBodySchema(travelSchema), true);
180
+ const mutate = (fn) => {
181
+ const copy = JSON.parse(JSON.stringify(travelSchema));
182
+ fn(copy);
183
+ return copy;
184
+ };
185
+ for (const [label, schema] of [
186
+ ['extra root key', mutate(s => { s.description = 'the plan'; })],
187
+ ['enum on scalar', mutate(s => { s.properties.people_number.enum = [2]; })],
188
+ ['const on scalar', mutate(s => { s.properties.start_city.const = 'Dubai'; })],
189
+ ['default on scalar', mutate(s => { s.properties.target_city.default = 'Chengdu'; })],
190
+ ['examples key', mutate(s => { s.properties.itinerary.examples = []; })],
191
+ ['pattern on scalar', mutate(s => { s.properties.start_city.pattern = '^[A-Z]'; })],
192
+ ['open object', mutate(s => { s.additionalProperties = true; })],
193
+ ['missing additionalProperties', mutate(s => { delete s.additionalProperties; })],
194
+ ['array without items', mutate(s => { s.properties.itinerary = { type: 'array' }; })],
195
+ ['object without required', mutate(s => { delete s.required; })],
196
+ ['unknown type', mutate(s => { s.properties.people_number.type = 'float'; })],
197
+ ['allOf composition', mutate(s => { s.allOf = []; })],
198
+ ]) {
199
+ assert.equal(validateTerminalBodySchema(schema), false, `schema dialect rejects: ${label}`);
200
+ }
201
+ // Value validation: legal ChinaTravel-like hierarchy passes; the five Round 11
202
+ // failure classes stay rejected without any autofix.
203
+ assert.equal(validateTerminalBodyValue(legalBody, travelSchema), true, 'legal ChinaTravel-like hierarchy passes');
204
+ assert.equal(parseBenchmarkTerminal(`<done>${JSON.stringify(legalBody)}</done>`, travelConfig).ok, true, 'legal body parses through the terminal gate');
205
+ for (const [label, body] of [
206
+ ['extra root key (Round 11 budget/total_cost case)', { ...legalBody, budget: { total_cost: 1 } }],
207
+ ['itinerary item written as direct activity (missing day)', { ...legalBody, itinerary: [{ type: 'attraction', start_time: '09:00', cost: 0, transports: [] }] }],
208
+ ['missing activities array', { ...legalBody, itinerary: [{ day: 1 }] }],
209
+ ['wrong type (string day)', { ...legalBody, itinerary: [{ day: '1', activities: [] }] }],
210
+ ['nested extra field', { ...legalBody, itinerary: [{ day: 1, activities: [{ type: 'attraction', start_time: '09:00', cost: 0, transports: [], unexpected: 1 }] }] }],
211
+ ]) {
212
+ assert.equal(validateTerminalBodyValue(body, travelSchema), false, `value validation rejects: ${label}`);
213
+ assert.equal(parseBenchmarkTerminal(`<done>${JSON.stringify(body)}</done>`, travelConfig).ok, false, `terminal gate rejects: ${label}`);
214
+ }
215
+ // Envelope byte limit still fails closed for a schema-valid but oversized body.
216
+ assert.equal(parseBenchmarkTerminal(`<done>${' '.repeat(2000)}${JSON.stringify({ ok: true })}</done>`, projection.terminal).ok, false, 'schema-valid body over max_bytes still rejects');
217
+ // Single-source projection: the outline is deterministic and appears verbatim in
218
+ // both the system section and the one terminal correction.
219
+ const outline = terminalSchemaOutline(travelSchema);
220
+ assert.equal(outline, terminalSchemaOutline(JSON.parse(JSON.stringify(travelSchema))), 'outline is deterministic');
221
+ assert.equal(outline, 'object{people_number:integer,start_city:string,target_city:string,itinerary:array<object{day:integer,activities:array<object{type:string,start_time:string,cost:number,transports:array<object{start:string,mode:string}>}>}>}', 'outline renders the exact structural contract');
222
+ assert.match(outline, /object\{/, 'outline renders object nodes');
223
+ assert.ok(!outline.includes('optional') && !outline.includes('?'), 'all keys required render without optional markers');
224
+ }
225
+ function turnStart(turn = 1) {
226
+ return { type: 'turn/start', data: { turn } };
227
+ }
228
+ function turnEnd(turn = 1) {
229
+ return { type: 'turn/end', data: { turn, reason: { kind: 'completed' } } };
230
+ }
231
+ function toolCall(callId = 'call-1', options = {}) {
232
+ const { turn = 1, step = 1, action = 'call', tool = 'lookup' } = options;
233
+ return {
234
+ type: 'tool/call',
235
+ data: {
236
+ turn,
237
+ step,
238
+ callId,
239
+ name: projection.toolName,
240
+ arguments: JSON.stringify({ action, tool, arguments: {} }),
241
+ },
242
+ };
243
+ }
244
+ function toolResult(callId = 'call-1', options = {}) {
245
+ const { turn = 1, step = 1, ok = true, isError = false, error = 'runner_failed', outcome, payload } = options;
246
+ // 0.2.0-rc.2 线型:toolCallId/isError 在 message 顶层,content 是单个文本载荷块。
247
+ return {
248
+ type: 'tool/result',
249
+ data: {
250
+ turn,
251
+ step,
252
+ message: {
253
+ source: { kind: 'tool', callId },
254
+ toolCallId: callId,
255
+ isError,
256
+ content: [{ type: 'text', text: JSON.stringify(payload ?? (outcome ? { ok: true, outcome } : ok ? { ok: true, result: {} } : { ok: false, error })) }],
257
+ },
258
+ },
259
+ };
260
+ }
261
+ {
262
+ const state = createBenchmarkAgentConformance(projection);
263
+ state.observe(turnStart());
264
+ state.observe(toolCall());
265
+ state.observe(toolResult('call-1', { outcome: { schema_version: 'gotry_benchmark_tool_result_v1', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' } }));
266
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'terminal' }, 'domain outcome alone permits tagged terminal');
267
+ state.observe(assistant('<done>{"status":"miss"}</done>', { step: 2 }));
268
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'domain-only tagged terminal is accepted');
269
+ }
270
+ {
271
+ const state = createBenchmarkAgentConformance(projection);
272
+ state.observe(turnStart());
273
+ state.observe(toolCall());
274
+ state.observe(toolResult('call-1', { outcome: { schema_version: 'gotry_benchmark_tool_result_v1', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' } }));
275
+ state.observe(toolCall('call-2', { step: 2 }));
276
+ state.observe(toolResult('call-2'));
277
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 3 }));
278
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'domain outcome followed by concrete result accepts');
279
+ }
280
+ {
281
+ const state = createBenchmarkAgentConformance(projection);
282
+ state.observe(turnStart());
283
+ state.observe(toolCall());
284
+ state.observe(toolResult('call-1', { outcome: { schema_version: 'gotry_benchmark_tool_result_v1', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' } }));
285
+ state.observe(toolCall('call-2', { step: 2 }));
286
+ state.observe(toolResult('call-2', { ok: false, error: 'runner_failed' }));
287
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_RUNNER_FAILED }, 'infra failure overrides domain-only outcome');
288
+ }
289
+ {
290
+ const state = createBenchmarkAgentConformance(projection);
291
+ state.observe(turnStart());
292
+ state.observe(toolCall());
293
+ state.observe(toolResult('call-1', { payload: { ok: true, result: {}, outcome: { status: 'miss' } } }));
294
+ state.observe(assistant('<done>{"status":"succeeded"}</done>'));
295
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_CALL_FAILED }, 'ambiguous ok wrapper with result and outcome fails closed');
296
+ }
297
+ {
298
+ const state = createBenchmarkAgentConformance(projection);
299
+ state.observe(turnStart());
300
+ state.observe(toolCall());
301
+ state.observe(toolResult('call-1', { payload: { ok: true, outcome: {} } }));
302
+ state.observe(assistant('<done>{"status":"miss"}</done>'));
303
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_CALL_FAILED }, 'malformed inner domain outcome fails closed');
304
+ }
305
+ function assistant(text, options = {}) {
306
+ const { turn = 1, step = 2, interrupted = false } = options;
307
+ return {
308
+ type: 'assistant/message',
309
+ data: {
310
+ turn,
311
+ step,
312
+ message: { content: [{ type: 'text', text }] },
313
+ ...(interrupted ? { interrupted: true } : {}),
314
+ },
315
+ };
316
+ }
317
+ {
318
+ const state = createBenchmarkAgentConformance(projection);
319
+ state.observe(turnStart());
320
+ state.observe(assistant('I would run the CLI.', { step: 1 }));
321
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'call' }, 'no-call first stop gets one correction');
322
+ assert.equal(state.guardBridgeExecution(), undefined, 'call correction still permits the first real bridge dispatch');
323
+ state.observe(toolCall('call-a', { step: 2 }));
324
+ state.observe(toolResult('call-a', { step: 2 }));
325
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 3 }));
326
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'call correction may converge to one successful terminal');
327
+ }
328
+ {
329
+ const state = createBenchmarkAgentConformance(projection);
330
+ state.observe(turnStart());
331
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 1 }));
332
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'call' }, 'valid terminal without a call still needs a call');
333
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 2 }));
334
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_CALL_REQUIRED });
335
+ }
336
+ {
337
+ const state = createBenchmarkAgentConformance(projection);
338
+ state.observe(turnStart());
339
+ state.observe(toolCall());
340
+ state.observe(toolResult());
341
+ state.observe(assistant('bad terminal'));
342
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'terminal' }, 'bad terminal gets one format-only correction');
343
+ assert.equal(state.guardBridgeExecution(), BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED);
344
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 3 }));
345
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'format-only correction can reuse the successful result');
346
+ }
347
+ {
348
+ const state = createBenchmarkAgentConformance(projection);
349
+ state.observe(turnStart());
350
+ state.observe(toolCall());
351
+ state.observe(toolResult());
352
+ state.observe(assistant('bad terminal'));
353
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'terminal' });
354
+ state.observe(toolCall('call-2', { step: 3 }));
355
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED });
356
+ }
357
+ {
358
+ const state = createBenchmarkAgentConformance(projection);
359
+ state.observe(turnStart());
360
+ state.observe(toolCall());
361
+ state.observe(toolResult());
362
+ state.observe(assistant('bad terminal'));
363
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'terminal' });
364
+ state.observe(assistant('still bad', { step: 3 }));
365
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_TERMINAL_INVALID });
366
+ }
367
+ {
368
+ const state = createBenchmarkAgentConformance(projection);
369
+ state.observe(turnStart());
370
+ state.observe(toolCall());
371
+ state.observe(toolResult('call-1', { ok: false }));
372
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_RUNNER_FAILED }, 'structured runner failure is not retried');
373
+ }
374
+ {
375
+ const state = createBenchmarkAgentConformance(projection);
376
+ state.observe(turnStart());
377
+ state.observe(toolCall());
378
+ state.observe(toolResult('call-1', { ok: false, error: 'output_truncated' }));
379
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_BRIDGE_OUTPUT_TRUNCATED }, 'structured runner truncation has a distinct reason');
380
+ }
381
+ {
382
+ const state = createBenchmarkAgentConformance(projection);
383
+ state.observe(turnStart());
384
+ state.observe(toolCall('failed', { step: 1 }));
385
+ state.observe(toolResult('failed', { step: 1, ok: false }));
386
+ state.observe(toolCall('successful', { step: 2 }));
387
+ state.observe(toolResult('successful', { step: 2 }));
388
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 3 }));
389
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'a model-owned later success can recover from an earlier failed call without a conformance retry');
390
+ }
391
+ {
392
+ const state = createBenchmarkAgentConformance(projection);
393
+ state.observe(turnStart());
394
+ state.observe(toolCall('successful', { step: 1 }));
395
+ state.observe(toolResult('successful', { step: 1 }));
396
+ state.observe(toolCall('failed', { step: 2 }));
397
+ state.observe(toolResult('failed', { step: 2, ok: false }));
398
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 3 }));
399
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'a later failed optional call does not erase an already paired successful result');
400
+ }
401
+ {
402
+ const state = createBenchmarkAgentConformance(projection);
403
+ state.observe(turnStart());
404
+ state.observe(toolCall('successful', { step: 1 }));
405
+ state.observe(toolResult('successful', { step: 1 }));
406
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 2 }));
407
+ state.observe(toolCall('domain', { step: 3 }));
408
+ state.observe(toolResult('domain', { step: 3, outcome: { schema_version: 'gotry_benchmark_tool_result_v1', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' } }));
409
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'terminal' }, 'a terminal before the latest domain response is stale');
410
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 4 }));
411
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'a fresh terminal after the latest domain response may reuse the earlier concrete result');
412
+ }
413
+ {
414
+ const state = createBenchmarkAgentConformance(projection);
415
+ state.observe(turnStart());
416
+ state.observe(toolCall('successful', { step: 1 }));
417
+ state.observe(toolResult('successful', { step: 1 }));
418
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 2 }));
419
+ state.observe(toolCall('failed', { step: 3 }));
420
+ state.observe(toolResult('failed', { step: 3, ok: false }));
421
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'terminal' }, 'a terminal before a later optional failure is stale');
422
+ state.observe(assistant('<done>{"status":"succeeded"}</done>', { step: 4 }));
423
+ assert.deepEqual(state.stopping(1), { kind: 'accept' }, 'a fresh terminal may still converge after an optional failure when a concrete result exists');
424
+ }
425
+ {
426
+ const state = createBenchmarkAgentConformance(projection);
427
+ state.observe(turnStart());
428
+ state.observe(toolCall('discovery', { action: 'tools' }));
429
+ state.observe(toolResult('discovery'));
430
+ state.observe(assistant('<done>{"status":"succeeded"}</done>'));
431
+ assert.deepEqual(state.stopping(1), { kind: 'steer', mode: 'call' }, 'action tools does not satisfy the call gate');
432
+ state.observe(turnEnd());
433
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE });
434
+ state.observe(turnStart(2));
435
+ assert.deepEqual(state.stopping(1), { kind: 'reject', code: BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE }, 'turn state cannot leak across turns');
436
+ }
437
+ {
438
+ const rootListeners = new Map();
439
+ const scopedListeners = new Map();
440
+ const guards = [];
441
+ const steers = [];
442
+ const runtimeWrites = [];
443
+ const runEffect = (action) => {
444
+ const disposers = [];
445
+ const value = action();
446
+ if (value && typeof value.next === 'function') {
447
+ let item = value.next();
448
+ while (!item.done) {
449
+ if (typeof item.value === 'function')
450
+ disposers.push(item.value);
451
+ item = value.next();
452
+ }
453
+ }
454
+ return () => { for (const dispose of disposers.reverse())
455
+ dispose(); };
456
+ };
457
+ const add = (target, name, listener) => {
458
+ const list = target.get(name) ?? [];
459
+ list.push(listener);
460
+ target.set(name, list);
461
+ return () => target.set(name, list.filter(candidate => candidate !== listener));
462
+ };
463
+ const session = {};
464
+ const agent = {
465
+ session,
466
+ steer(message) { steers.push(message); },
467
+ ctx: {
468
+ tools: { guard(check) { guards.push(check); return () => { }; } },
469
+ effect: runEffect,
470
+ on(name, listener) { return add(scopedListeners, name, listener); },
471
+ },
472
+ };
473
+ const ctx = {
474
+ on(name, listener) { return add(rootListeners, name, listener); },
475
+ };
476
+ installBenchmarkAgentConformance(ctx, projection, code => runtimeWrites.push(code));
477
+ rootListeners.get('agent/created')[0]({ agent });
478
+ rootListeners.get('session/event')[0](session, turnStart());
479
+ rootListeners.get('session/event')[0](session, { type: 'llm/retry', data: { turn: 1, reason: { kind: 'error', error: { code: 'RATE_LIMIT' } } } });
480
+ rootListeners.get('session/event')[0](session, { type: 'agent/request-error', data: { turn: 1, error: { code: 'SERVER' } } });
481
+ rootListeners.get('session/event')[0](session, assistant('prose only', { step: 1 }));
482
+ rootListeners.get('agent/turn-stopping')[0]({ agent, turn: 1 });
483
+ assert.equal(steers.length, 1, 'runtime wiring steers exactly once at the stop boundary');
484
+ assert.equal(steers[0].role, 'user');
485
+ assert.equal(Object.isFrozen(steers[0]), true, 'correction uses the official immutable DSH user message');
486
+ assert.equal(Object.isFrozen(steers[0].content), true, 'correction content is deeply frozen');
487
+ assert.equal(guards[0]({ name: projection.toolName }), undefined, 'call correction leaves bridge execution available');
488
+ const assembled = await scopedListeners.get('system-prompt/assemble')[0]({}, {}, async () => ({ sections: [], tools: [] }));
489
+ assert.match(assembled.sections[0].text, /agent_env\.cli/);
490
+ assert.match(assembled.sections[0].text, /\"action\":\"call\"/);
491
+ assert.match(assembled.sections[0].text, /<done>/);
492
+ assert.equal(assembled.sections[0].text.includes('/tmp/'), false);
493
+ // Round 12(#215):system section 与 terminal 纠正投影同一份结构合同(单一来源)。
494
+ const round12Outline = terminalSchemaOutline(OK_BODY_SCHEMA);
495
+ assert.ok(assembled.sections[0].text.includes(`matching exactly ${round12Outline}`), 'system section projects the terminal body schema outline');
496
+ // Intermediate retry/request errors never write; only final completed observes recovery.
497
+ rootListeners.get('session/event')[0](session, turnEnd());
498
+ assert.deepEqual(runtimeWrites, []);
499
+ // A final structured model error writes exactly once, despite duplicate end/dispose.
500
+ rootListeners.get('session/event')[0](session, turnStart(2));
501
+ rootListeners.get('session/event')[0](session, { type: 'turn/end', data: { turn: 2, reason: { kind: 'error', error: { code: 'SERVER', message: 'sentinel' } } } });
502
+ rootListeners.get('session/event')[0](session, { type: 'turn/end', data: { turn: 2, reason: { kind: 'error', error: { code: 'UNKNOWN_MODEL' } } } });
503
+ assert.deepEqual(runtimeWrites, ['child_model_server']);
504
+ rootListeners.get('session/disposed')[0](session);
505
+ // A malformed stopping payload with a valid session is arbited above a later generic error.
506
+ const session2 = {};
507
+ const agent2 = { session: session2, steer() { }, ctx: agent.ctx };
508
+ rootListeners.get('agent/created')[0]({ agent: agent2 });
509
+ rootListeners.get('session/event')[0](session2, turnStart());
510
+ assert.throws(() => rootListeners.get('agent/turn-stopping')[0]({ agent: agent2 }), new RegExp(BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE));
511
+ rootListeners.get('session/event')[0](session2, { type: 'turn/end', data: { turn: 1, reason: { kind: 'error', error: { code: 'UNKNOWN' } } } });
512
+ assert.deepEqual(runtimeWrites, ['child_model_server', 'child_conformance_failure']);
513
+ rootListeners.get('session/disposed')[0](session2);
514
+ const session3 = {};
515
+ const agent3 = { session: session3, steer() { }, ctx: agent.ctx };
516
+ rootListeners.get('agent/created')[0]({ agent: agent3 });
517
+ rootListeners.get('session/event')[0](session3, turnStart());
518
+ rootListeners.get('session/event')[0](session3, { type: 'turn/end', data: { turn: 1, reason: { kind: 'error', error: { code: 'TIMEOUT' } } } });
519
+ assert.deepEqual(runtimeWrites, ['child_model_server', 'child_conformance_failure', 'child_model_transport'], 'session diagnostics remain isolated');
520
+ rootListeners.get('session/disposed')[0](session3);
521
+ }
522
+ function fakeHandle(outcome) {
523
+ const stdout = outcome.stdout ?? '';
524
+ const stderr = outcome.stderr ?? '';
525
+ const reader = {
526
+ readFrom: (_offset) => {
527
+ if (outcome.readFailure)
528
+ throw new Error('collected output reading failed');
529
+ return { text: stdout, nextOffset: Buffer.byteLength(stdout), lossy: outcome.lossy ?? false };
530
+ },
531
+ };
532
+ const errorReader = {
533
+ readFrom: (_offset) => {
534
+ if (outcome.readFailure)
535
+ throw new Error('collected output reading failed');
536
+ return { text: stderr, nextOffset: Buffer.byteLength(stderr), lossy: false };
537
+ },
538
+ };
539
+ let rejectDone;
540
+ const done = outcome.spawnReject
541
+ ? Promise.reject(new Error('spawn rejected'))
542
+ : outcome.waitForAbort
543
+ ? new Promise((_resolve, reject) => { rejectDone = reject; })
544
+ : Promise.resolve({ exitCode: outcome.exitCode ?? 0, signal: outcome.signal ?? null });
545
+ return {
546
+ stdin: undefined,
547
+ stdout: undefined,
548
+ stderr: undefined,
549
+ collected: { stdout: reader, stderr: errorReader },
550
+ done,
551
+ terminate() { if (outcome.waitForAbort)
552
+ rejectDone?.(new Error('timed out')); },
553
+ waitForExit: async () => true,
554
+ };
555
+ }
556
+ function okEnvelope(result) {
557
+ return JSON.stringify({ schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'ok', result });
558
+ }
559
+ function lookupInputSchema() {
560
+ return {
561
+ type: 'object',
562
+ properties: {
563
+ city: { type: 'string', enum: ['Dubai', 'Abu Dhabi'], description: 'Declared city name.' },
564
+ payload: { type: 'string' },
565
+ notes: { type: 'string' },
566
+ executable: { type: 'string' },
567
+ cwd: { type: 'string' },
568
+ argv: { type: 'array', items: { type: 'string' } },
569
+ },
570
+ required: ['city'],
571
+ additionalProperties: false,
572
+ };
573
+ }
574
+ function bridgeCall(tool, argumentsValue) {
575
+ return { action: 'call', tool, arguments: argumentsValue };
576
+ }
577
+ async function assertRealCordisWaterfallOrdering() {
578
+ const ctx = new Context();
579
+ const bridge = {
580
+ name: 'gotry_benchmark_environment',
581
+ description: 'benchmark bridge',
582
+ parameters: { oneOf: [{ type: 'object', properties: { action: { const: 'tools' } }, required: ['action'], additionalProperties: false }] },
583
+ };
584
+ const exactSchema = structuredClone(bridge);
585
+ let addPreStepTool = false;
586
+ const rootTools = {
587
+ get(name) { return name === bridge.name ? bridge : undefined; },
588
+ schemas(agent) {
589
+ return agent && addPreStepTool ? [exactSchema, { name: 'non_bridge' }] : [exactSchema];
590
+ },
591
+ };
592
+ ctx.provide('tools', rootTools);
593
+ ctx.provide('agents', { list: () => [] });
594
+ const bus = ctx;
595
+ bus.on('system-prompt/assemble', async (_assembly, _context, next) => {
596
+ const result = await next();
597
+ return { ...result, tools: [...result.tools, { name: 'non_bridge' }] };
598
+ });
599
+ bus.on('agent/pre-step', async (_payload, next) => {
600
+ const result = await next();
601
+ addPreStepTool = true;
602
+ return result;
603
+ });
604
+ installBenchmarkToolIsolation(ctx);
605
+ const scopedEffect = (action, label) => ctx.effect(action, label);
606
+ const scopedTools = {
607
+ guard: () => ctx.effect(() => () => undefined),
608
+ presentAs: () => ctx.effect(() => () => undefined),
609
+ restrict: () => ctx.effect(() => () => undefined),
610
+ };
611
+ const agent = { ctx: { tools: scopedTools, effect: scopedEffect, on: bus.on } };
612
+ bus.emit('agent/created', { agent });
613
+ await assert.rejects(bus.waterfall('system-prompt/assemble', { tools: [] }, { agent, scope: agent }, async () => ({ tools: [exactSchema] })), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'prepend assembly guard observes an earlier listener post-next mutation');
614
+ await assert.rejects(bus.waterfall('agent/pre-step', { agent }, async () => ({ kind: 'enter' })), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'prepend pre-step guard observes an earlier listener post-next scope mutation');
615
+ await ctx.fiber.dispose();
616
+ }
617
+ await assertRealCordisWaterfallOrdering();
618
+ const root = mkdtempSync(join(tmpdir(), 'gotry-benchmark-bridge-test-'));
619
+ const ambientSentinelNames = [
620
+ 'GOTRY_BENCHMARK_BRIDGE_PARENT_SENTINEL',
621
+ 'GOTRY_LLM_MODEL',
622
+ 'DATABASE_URL',
623
+ 'SSH_AUTH_SOCK',
624
+ 'AWS_PROFILE',
625
+ 'HTTPS_PROXY',
626
+ ];
627
+ const ambientSentinels = new Map(ambientSentinelNames.map(name => [name, process.env[name]]));
628
+ try {
629
+ delete process.env.GOTRY_LLM_MODEL;
630
+ const timedOutDiagnostic = '\n' + JSON.stringify({
631
+ schema_version: BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA,
632
+ code: 'child_bridge_timed_out',
633
+ }) + '\n';
634
+ assert.equal(parseBenchmarkChildDiagnostic(timedOutDiagnostic), 'child_bridge_timed_out', 'strict control record parses to its allowlisted reason code');
635
+ assert.equal(classifyBenchmarkChildFailure({ code: 1, diagnostic: timedOutDiagnostic }), 'child_bridge_timed_out', 'structured control data maps to a stable bridge timeout code');
636
+ assert.equal(classifyBenchmarkChildFailure({ code: 1, diagnostic: `provider text contains child_bridge_timed_out and ${BENCHMARK_BRIDGE_TIMED_OUT}` }), 'child_nonzero_exit', 'free text cannot impersonate a structured bridge reason');
637
+ assert.equal(parseBenchmarkChildDiagnostic(JSON.stringify({ schema_version: BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA, code: 'child_bridge_timed_out', extra: 'rejected' })), undefined, 'control records with extra keys fail closed');
638
+ assert.equal(benchmarkChildFailureForConformanceCode(BENCHMARK_BRIDGE_RUNNER_FAILED), 'child_bridge_runner_failed', 'runner failure maps to a stable structured child reason');
639
+ assert.equal(benchmarkChildFailureForConformanceCode(BENCHMARK_BRIDGE_SPAWN_FAILED), 'child_bridge_spawn_failed', 'spawn failure maps to a stable structured child reason');
640
+ assert.equal(benchmarkChildFailureForConformanceCode(BENCHMARK_BRIDGE_OUTPUT_TRUNCATED), 'child_bridge_output_truncated', 'runner output truncation maps to a stable structured child reason');
641
+ assert.equal(classifyBenchmarkChildFailure({ code: 0, outputTruncated: true }), 'child_output_truncated', 'truncated terminal output takes precedence over control data');
642
+ assert.equal(classifyBenchmarkChildFailure({ code: null, signal: 'SIGTERM', diagnostic: timedOutDiagnostic }), 'child_signaled', 'outer child signal takes precedence over an inner bridge diagnostic');
643
+ assert.equal(classifyBenchmarkChildFailure({ code: 0, diagnostic: '' }), 'child_lifecycle_failure', 'unexpected zero-exit diagnostic path remains a stable lifecycle failure');
644
+ const noisyPrefix = Buffer.alloc(BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES + 10, 'x');
645
+ const boundedControl = appendBoundedChildDiagnostic(appendBoundedChildDiagnostic(Buffer.alloc(0), noisyPrefix), timedOutDiagnostic);
646
+ assert.equal(boundedControl.length, BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES, 'control capture is bounded');
647
+ assert.equal(parseBenchmarkChildDiagnostic(boundedControl.toString('utf8')), 'child_bridge_timed_out', 'rolling tail preserves a final structured reason after bounded noise');
648
+ const configPath = join(root, 'bridge.json');
649
+ writeFileSync(configPath, JSON.stringify({
650
+ schema_version: 'gotry_benchmark_environment_bridge_v4',
651
+ enabled: true,
652
+ executable: process.execPath,
653
+ cwd: root,
654
+ argv_prefix: ['-m', 'agent_env.cli', '--lang', 'en'],
655
+ tools: [
656
+ { name: 'lookup', description: 'Lookup one declared city.', input_schema: lookupInputSchema(), output_keys: ['city', 'nested'], domain_outcomes: [{ status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' }, { status: 'error', code: 'AMBIGUOUS', recovery: 'choose_alternative' }] },
657
+ { name: 'constructor', description: 'Construct one declared city.', input_schema: lookupInputSchema(), output_keys: ['legacy'], domain_outcomes: [{ status: 'error', code: 'INVALID', recovery: 'retry_same' }] },
658
+ { name: 'toString', description: 'Stringify one declared city.', input_schema: lookupInputSchema(), output_keys: ['city', 'nested'], domain_outcomes: [] },
659
+ ],
660
+ timeout_ms: 20,
661
+ max_output_bytes: 4_096,
662
+ terminal_output: { tag: 'done', max_bytes: 4_096, body_schema: OK_BODY_SCHEMA },
663
+ isolation: {
664
+ mode: 'host-enforced',
665
+ writes: 'forbidden',
666
+ network: 'denied',
667
+ },
668
+ }));
669
+ const registered = [];
670
+ let visibleBridge;
671
+ let shadowedAgent;
672
+ const scopedExtraSchemas = new Map();
673
+ const spawnSpecs = [];
674
+ const agentCreatedListeners = [];
675
+ const eventNames = [];
676
+ const promptVariables = [];
677
+ const assemblyListeners = [];
678
+ const preStepListeners = [];
679
+ const disposedListeners = [];
680
+ const eventOptions = new Map();
681
+ const runEffect = (action) => {
682
+ const yielded = [];
683
+ const value = action();
684
+ if (value && typeof value.next === 'function') {
685
+ let step = value.next();
686
+ while (!step.done) {
687
+ if (typeof step.value === 'function')
688
+ yielded.push(step.value);
689
+ step = value.next();
690
+ }
691
+ }
692
+ let active = true;
693
+ return async () => {
694
+ if (!active)
695
+ return;
696
+ active = false;
697
+ for (const dispose of yielded.reverse())
698
+ await dispose();
699
+ };
700
+ };
701
+ let disposeRootIsolation;
702
+ let timeoutSignal;
703
+ const outcomes = [{ stdout: okEnvelope([{ city: 'Dubai' }]) }];
704
+ process.env.GOTRY_BENCHMARK_BRIDGE_PARENT_SENTINEL = 'must-not-cross-boundary';
705
+ process.env.DATABASE_URL = 'postgres://secret';
706
+ process.env.SSH_AUTH_SOCK = '/tmp/secret.sock';
707
+ process.env.AWS_PROFILE = 'secret-profile';
708
+ process.env.HTTPS_PROXY = 'https://secret-proxy';
709
+ const ctx = {
710
+ tools: {
711
+ register(tool) {
712
+ registered.push(tool);
713
+ return () => { };
714
+ },
715
+ get(name, agent) { return name === 'gotry_benchmark_environment' ? (agent !== undefined && agent === shadowedAgent ? { name } : registered.find(tool => tool.name === name)) : undefined; },
716
+ schemas(agent) {
717
+ const project = (tool) => ({ name: tool.name, description: tool.description, parameters: structuredClone(tool.parameters) });
718
+ const schemas = registered.map(project);
719
+ if (agent === undefined)
720
+ return schemas;
721
+ return [...schemas.filter(schema => schema.name === 'gotry_benchmark_environment'), ...(scopedExtraSchemas.get(agent) ?? [])];
722
+ },
723
+ },
724
+ systemPrompt: { variable(name) { promptVariables.push(name); } },
725
+ on(event, listener, options) {
726
+ eventNames.push(event);
727
+ eventOptions.set(event, options);
728
+ if (event === 'agent/created')
729
+ agentCreatedListeners.push(listener);
730
+ if (event === 'agent/disposed')
731
+ disposedListeners.push(listener);
732
+ if (event === 'system-prompt/assemble')
733
+ assemblyListeners.push(listener);
734
+ if (event === 'agent/pre-step')
735
+ preStepListeners.push(listener);
736
+ return () => { };
737
+ },
738
+ effect(action, label) {
739
+ const dispose = runEffect(action);
740
+ if (label === 'benchmark-environment-tool-isolation')
741
+ disposeRootIsolation = dispose;
742
+ return dispose;
743
+ },
744
+ get(name) {
745
+ if (name === 'subprocess')
746
+ return this.subprocess;
747
+ if (name === 'agents')
748
+ return this.agents;
749
+ },
750
+ subprocess: {
751
+ spawn(spec) {
752
+ spawnSpecs.push(spec);
753
+ const outcome = outcomes.shift() ?? { stdout: okEnvelope({}) };
754
+ if (outcome.spawnError)
755
+ throw new Error('fake spawn failed');
756
+ if (outcome.waitForAbort) {
757
+ timeoutSignal = spec.signal;
758
+ const handle = fakeHandle(outcome);
759
+ spec.signal?.addEventListener('abort', () => handle.terminate?.(), { once: true });
760
+ return handle;
761
+ }
762
+ return fakeHandle(outcome);
763
+ },
764
+ },
765
+ agents: { list() { return []; } },
766
+ };
767
+ const coldStartListeners = [];
768
+ const coldStartRoot = { name: 'gotry_benchmark_environment' };
769
+ assert.throws(() => installBenchmarkToolIsolation({
770
+ tools: { get(name) { return name === 'gotry_benchmark_environment' ? coldStartRoot : undefined; }, schemas() { return [{ name: 'gotry_benchmark_environment' }]; } },
771
+ agents: { list() { return [{ id: 'already-live' }]; } },
772
+ on(event) { coldStartListeners.push(event); return () => { }; },
773
+ }), /cold-start|live-agent/i, 'installing benchmark isolation with an existing agent fails hard');
774
+ assert.deepEqual(coldStartListeners, [], 'cold-start rejection does not register an isolation listener');
775
+ const processListenersBeforeBenchmark = {
776
+ uncaughtException: process.listenerCount('uncaughtException'),
777
+ unhandledRejection: process.listenerCount('unhandledRejection'),
778
+ };
779
+ apply(ctx, {
780
+ stateRoot: root,
781
+ timeoutMs: 20,
782
+ hbcliBin: '',
783
+ sessionAccess: 'off',
784
+ benchmarkEnvironmentConfigPath: configPath,
785
+ });
786
+ assert.ok(registered.some(tool => tool.name === 'gotry_benchmark_environment'), 'an explicit valid owner-local config registers the benchmark environment bridge');
787
+ assert.deepEqual(registered.map(tool => tool.name), ['gotry_benchmark_environment'], 'benchmark mode boots only the single bridge tool instead of the product tool catalog');
788
+ assert.deepEqual(promptVariables, [], 'benchmark mode does not install product prompt variables');
789
+ assert.equal(eventNames.includes('tools/pre-execute'), false, 'benchmark mode does not install the product session-consent hook');
790
+ assert.deepEqual({
791
+ uncaughtException: process.listenerCount('uncaughtException'),
792
+ unhandledRejection: process.listenerCount('unhandledRejection'),
793
+ }, processListenersBeforeBenchmark, 'benchmark mode does not install product process incident guards');
794
+ assert.deepEqual([...eventNames].sort(), [
795
+ 'agent/created',
796
+ 'agent/created',
797
+ 'agent/disposed',
798
+ 'agent/disposed',
799
+ 'agent/pre-step',
800
+ 'agent/turn-stopping',
801
+ 'session/disposed',
802
+ 'session/disposed',
803
+ 'session/event',
804
+ 'session/event',
805
+ 'system-prompt/assemble',
806
+ 'tools/execute',
807
+ 'tools/post-execute',
808
+ ], 'benchmark root listeners come only from budget, isolation, and conformance when model override is unset');
809
+ visibleBridge = registered.find(tool => tool.name === 'gotry_benchmark_environment');
810
+ const restrictions = [];
811
+ const guards = [];
812
+ const presentations = [];
813
+ const cleanupCounts = { restrict: 0, guard: 0, presentAs: 0, assembly: 0 };
814
+ const firstScopedAssemblyListeners = [];
815
+ const scopedTools = {
816
+ restrict(filter) { restrictions.push(filter); return () => { cleanupCounts.restrict += 1; }; },
817
+ guard(check) { guards.push(check); return () => { cleanupCounts.guard += 1; }; },
818
+ presentAs(mode) { presentations.push(mode); return () => { cleanupCounts.presentAs += 1; }; },
819
+ };
820
+ assert.equal(agentCreatedListeners.length, 2, 'opt-in bridge installs exactly isolation and conformance agent listeners');
821
+ const isolatedAgentEffects = [];
822
+ const isolatedAgent = { session: {}, steer(_message) { }, ctx: {
823
+ tools: scopedTools,
824
+ effect: (action) => {
825
+ const dispose = runEffect(action);
826
+ isolatedAgentEffects.push(dispose);
827
+ return dispose;
828
+ },
829
+ on: (event, listener, options) => {
830
+ assert.equal(event, 'system-prompt/assemble');
831
+ assert.deepEqual(options, { prepend: true });
832
+ firstScopedAssemblyListeners.push(listener);
833
+ return () => { cleanupCounts.assembly += 1; };
834
+ },
835
+ } };
836
+ for (const listener of agentCreatedListeners)
837
+ listener({ agent: isolatedAgent });
838
+ assert.deepEqual(restrictions, [{ allow: ['gotry_benchmark_environment'] }], 'agent scope allows only the bridge tool');
839
+ assert.deepEqual(presentations, ['native'], 'agent scope forces native tool presentation');
840
+ assert.ok(eventNames.includes('agent/pre-step'), 'isolation observes pre-step before each request');
841
+ assert.deepEqual(eventOptions.get('agent/pre-step'), { prepend: true }, 'pre-step isolation wraps every previously registered listener');
842
+ assert.ok(eventNames.includes('agent/disposed'), 'isolation cleans up on agent disposal');
843
+ assert.ok(assemblyListeners.length > 0, 'isolation validates final assembled tool surface');
844
+ assert.equal(firstScopedAssemblyListeners.length, 2, 'agent owns one isolation assembly listener and one conformance section listener');
845
+ assert.deepEqual(eventOptions.get('system-prompt/assemble'), { prepend: true }, 'assembly isolation wraps every previously registered listener');
846
+ assert.equal(disposedListeners.length, 2, 'isolation and conformance each own one agent/disposed listener');
847
+ assert.ok(disposeRootIsolation, 'root isolation effect exposes plugin-lifecycle cleanup');
848
+ const exactSchema = {
849
+ name: visibleBridge.name,
850
+ description: visibleBridge.description,
851
+ parameters: structuredClone(visibleBridge.parameters),
852
+ };
853
+ const assemble = assemblyListeners[0];
854
+ const nextExact = async () => ({ tools: [exactSchema] });
855
+ assert.deepEqual(await assemble({ tools: [] }, { agent: isolatedAgent, scope: isolatedAgent }, nextExact), { tools: [exactSchema] }, 'legal final assembly passes unchanged');
856
+ await assert.rejects(async () => await assemble({ tools: [] }, { agent: isolatedAgent, scope: isolatedAgent }, async () => ({ tools: [exactSchema, { name: 'own_side_effect' }] })), /BENCHMARK_TOOL_SURFACE_VIOLATION/);
857
+ await assert.rejects(async () => await assemble({ tools: [] }, { agent: isolatedAgent, scope: isolatedAgent }, async () => ({ tools: [{ ...exactSchema, description: `${exactSchema.description ?? ''} tampered` }] })), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'final assembly rejects same-name schema mutation');
858
+ const diagnosticAssembly = { tools: [{ name: 'diagnostic_tool' }] };
859
+ assert.equal(await assemble({ tools: [] }, { scope: { id: 'diagnostic-scope' } }, async () => diagnosticAssembly), diagnosticAssembly, 'non-agent diagnostic assembly passes through');
860
+ await assert.rejects(async () => await assemble({ tools: [] }, { agent: isolatedAgent, scope: { id: 'wrong-scope' } }, nextExact), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'agent assembly requires the same agent and scope');
861
+ const preStep = preStepListeners[0];
862
+ assert.deepEqual(await preStep({ agent: isolatedAgent }, async () => ({ decision: 'continue' })), { decision: 'continue' }, 'legal pre-step decision passes unchanged');
863
+ scopedExtraSchemas.set(isolatedAgent, [{ name: 'own_side_effect' }]);
864
+ await assert.rejects(async () => await preStep({ agent: isolatedAgent }, async () => ({ decision: 'continue' })), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'downstream pre-step scope expansion fails closed');
865
+ scopedExtraSchemas.delete(isolatedAgent);
866
+ shadowedAgent = isolatedAgent;
867
+ await assert.rejects(async () => await assemble({ tools: [] }, { agent: isolatedAgent, scope: isolatedAgent }, nextExact), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'same-name scoped shadow fails final assembly identity check');
868
+ shadowedAgent = undefined;
869
+ assert.equal(guards.length, 2, 'agent scope installs exact isolation and conformance guards');
870
+ const guard = guards[0];
871
+ const originalAgent = { ctx: { tools: { get: (name) => name === 'gotry_benchmark_environment' ? visibleBridge : undefined } } };
872
+ const shadowAgent = { ctx: { tools: { get: (name) => name === 'gotry_benchmark_environment' ? { name } : undefined } } };
873
+ shadowedAgent = shadowAgent;
874
+ assert.equal(guard({ name: 'gotry_benchmark_environment', args: {}, agent: originalAgent }), undefined, 'original bridge definition is allowed');
875
+ assert.equal(guard({ name: 'gotry_benchmark_environment', args: {}, agent: shadowAgent }), 'BENCHMARK_TOOL_NOT_ALLOWED', 'same-name shadow definition is denied');
876
+ assert.equal(guard({ name: 'gotry_benchmark_environment', args: { path: '/private/secret' }, agent: isolatedAgent }), undefined, 'bridge tool is allowed');
877
+ const denied = guard({ name: 'other_tool', args: { path: '/private/secret', token: 'secret' } });
878
+ assert.equal(denied, 'BENCHMARK_TOOL_NOT_ALLOWED', 'non-bridge tools are denied without argument/path echo');
879
+ assert.equal(denied?.includes('/private/secret'), false);
880
+ assert.equal(denied?.includes('secret'), false);
881
+ assert.equal(guard({ name: 'other_tool', args: { different: true } }), denied, 'denial reason is stable');
882
+ assert.throws(() => agentCreatedListeners[0]({ agent: { ctx: { tools: { restrict(filter) { void filter; }, guard(check) { void check; } } } } }), /presentAs/, 'missing scoped presentAs fails hard');
883
+ assert.throws(() => agentCreatedListeners[0]({ agent: { ctx: { tools: scopedTools } } }), /effect/, 'missing scoped effect fails hard');
884
+ assert.throws(() => agentCreatedListeners[0]({ agent: { ctx: { tools: scopedTools, effect: (action) => runEffect(action) } } }), /event bus/, 'missing scoped event bus fails hard');
885
+ assert.throws(() => agentCreatedListeners[0]({ agent: { ctx: { tools: { guard: guards[0] } } } }), /restrict/, 'missing scoped restrict fails hard');
886
+ assert.throws(() => agentCreatedListeners[0]({ agent: { ctx: { tools: { restrict(filter) { void filter; } } } } }), /guard/, 'missing scoped guard fails hard');
887
+ assert.throws(() => installBenchmarkToolIsolation({
888
+ tools: { get(name) { return name === 'gotry_benchmark_environment' ? { name: 'gotry_benchmark_environment' } : undefined; }, schemas() { return [{ name: 'gotry_benchmark_environment' }]; } },
889
+ agents: { list() { return []; } },
890
+ on() { return () => { }; },
891
+ }), /effect/, 'missing ctx.effect fails hard');
892
+ await disposedListeners[0]({ agent: isolatedAgent });
893
+ assert.deepEqual(cleanupCounts, { restrict: 1, guard: 1, presentAs: 1, assembly: 1 }, 'agent disposal releases every scoped isolation effect exactly once');
894
+ await disposedListeners[1]({ agent: isolatedAgent });
895
+ await isolatedAgentEffects[1]();
896
+ assert.deepEqual(cleanupCounts, { restrict: 1, guard: 2, presentAs: 1, assembly: 2 }, 'agent-scope disposal also releases conformance guard and prompt section');
897
+ const secondCleanup = { restrict: 0, guard: 0, presentAs: 0, assembly: 0 };
898
+ const secondGuards = [];
899
+ const secondScopedAssemblyListeners = [];
900
+ const secondTools = {
901
+ restrict() { return () => { secondCleanup.restrict += 1; }; },
902
+ guard(check) { secondGuards.push(check); return () => { secondCleanup.guard += 1; }; },
903
+ presentAs() { return () => { secondCleanup.presentAs += 1; }; },
904
+ };
905
+ const secondAgentEffects = [];
906
+ const secondAgent = { session: {}, steer(_message) { }, ctx: {
907
+ tools: secondTools,
908
+ effect: (action) => {
909
+ const dispose = runEffect(action);
910
+ secondAgentEffects.push(dispose);
911
+ return dispose;
912
+ },
913
+ on: (_event, listener) => {
914
+ secondScopedAssemblyListeners.push(listener);
915
+ return () => { secondCleanup.assembly += 1; };
916
+ },
917
+ } };
918
+ for (const listener of agentCreatedListeners)
919
+ listener({ agent: secondAgent });
920
+ let inFlightNextCalls = 0;
921
+ await assert.rejects(async () => await secondScopedAssemblyListeners[0]({}, { agent: secondAgent, scope: secondAgent }, async () => {
922
+ inFlightNextCalls += 1;
923
+ await disposeRootIsolation();
924
+ return { tools: [exactSchema] };
925
+ }), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'plugin unload during assembly fails closed before returning a model-visible result');
926
+ assert.equal(inFlightNextCalls, 1, 'in-flight quarantine test reaches the controlled unload point');
927
+ assert.deepEqual(secondCleanup, { restrict: 0, guard: 0, presentAs: 0, assembly: 0 }, 'plugin unload keeps a live agent quarantined');
928
+ let quarantineNextCalls = 0;
929
+ await assert.rejects(async () => await secondScopedAssemblyListeners[0]({}, { agent: secondAgent, scope: secondAgent }, async () => {
930
+ quarantineNextCalls += 1;
931
+ return { tools: [exactSchema] };
932
+ }), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'plugin-unloaded live agent rejects assembly before model request');
933
+ assert.equal(quarantineNextCalls, 0, 'quarantine does not enter the remaining assembly chain');
934
+ assert.equal(secondGuards[0]({ name: 'other_tool', agent: secondAgent }), 'BENCHMARK_TOOL_NOT_ALLOWED', 'quarantined agent still denies non-bridge dispatch after plugin unload');
935
+ assert.equal(secondGuards[0]({ name: 'gotry_benchmark_environment', agent: secondAgent }), 'BENCHMARK_TOOL_NOT_ALLOWED', 'quarantined agent also denies bridge dispatch after plugin unload');
936
+ assert.deepEqual(cleanupCounts, { restrict: 1, guard: 2, presentAs: 1, assembly: 2 }, 'plugin unload does not double-dispose an already removed agent');
937
+ assert.throws(() => agentCreatedListeners[0]({ agent: secondAgent }), /BENCHMARK_TOOL_SURFACE_VIOLATION/, 'agent creation during plugin stop fails closed');
938
+ for (const dispose of secondAgentEffects)
939
+ await dispose();
940
+ assert.deepEqual(secondCleanup, { restrict: 1, guard: 2, presentAs: 1, assembly: 2 }, 'agent disposal releases its quarantined isolation and conformance effects');
941
+ const bridge = registered.find(tool => tool.name === 'gotry_benchmark_environment');
942
+ assert.ok(bridge.execute, 'registered bridge exposes execute');
943
+ const parameters = bridge.parameters;
944
+ assert.equal(parameters.type, 'object', 'flat bridge uses an object root for model-facing parameters');
945
+ assert.equal(parameters.oneOf, undefined, 'model-facing wire has no top-level oneOf');
946
+ assert.deepEqual(parameters.required, ['action'], 'action is the only universally required wire field');
947
+ assert.equal(parameters.additionalProperties, false);
948
+ assert.deepEqual(parameters.properties?.action?.enum, ['tools', 'call', 'errors']);
949
+ assert.deepEqual(parameters.properties?.tool?.enum, ['lookup', 'constructor', 'toString'], 'tool enum exposes frozen descriptor names');
950
+ assert.equal(parameters.properties?.arguments?.type, 'object', 'arguments remains a generic object on the open wire');
951
+ assert.equal(parameters.properties?.arguments?.additionalProperties, true);
952
+ assert.equal(Object.isFrozen(bridge.parameters), true, 'registered raw parameters are frozen');
953
+ assert.equal(Object.isFrozen(parameters.properties?.tool?.enum), true, 'descriptor-derived tool enum is frozen');
954
+ assert.throws(() => { parameters.properties?.tool?.enum.push('escape'); }, TypeError);
955
+ const beforeProtocolRejected = spawnSpecs.length;
956
+ await assert.rejects(() => bridge.execute({ query: bridgeCall('lookup', { city: 'Dubai' }) }, null), /invalid arguments/, 'legacy nested query envelope is rejected by the flat protocol');
957
+ await assert.rejects(() => bridge.execute({}, null), /invalid arguments/, 'empty object is rejected by the flat protocol');
958
+ await assert.rejects(() => bridge.execute({ action: 'tools', tool: 'lookup' }, null), /invalid arguments/, 'mixed action/tool fields are rejected by the flat protocol');
959
+ await assert.rejects(() => bridge.execute({ action: 'tools', arguments: {} }, null), /invalid arguments/, 'tools rejects mixed arguments');
960
+ await assert.rejects(() => bridge.execute({ action: 'errors', tool: 'lookup' }, null), /invalid arguments/, 'errors rejects mixed tool fields');
961
+ await assert.rejects(() => bridge.execute({ action: 'errors', arguments: {} }, null), /invalid arguments/, 'errors rejects mixed arguments');
962
+ await assert.rejects(() => bridge.execute({ ...bridgeCall('lookup', { city: 'Dubai' }), extra: true }, null), /invalid arguments/, 'extra top-level fields are rejected by the flat protocol');
963
+ await assert.rejects(() => bridge.execute({ action: 'call' }, null), /invalid arguments/, 'call without tool or arguments is rejected');
964
+ await assert.rejects(() => bridge.execute({ action: 'call', tool: 'lookup' }, null), /invalid arguments/, 'call without arguments is rejected');
965
+ await assert.rejects(() => bridge.execute({ action: 'call', arguments: { city: 'Dubai' } }, null), /invalid arguments/, 'call without tool is rejected');
966
+ await assert.rejects(() => bridge.execute(bridgeCall('lookup', { city: 'Sharjah' }), null), /invalid arguments/, 'invalid descriptor argument values are rejected by the flat protocol');
967
+ await assert.rejects(() => bridge.execute({ action: 'call', tool: 'lookup', arguments: {} }, null), /invalid arguments/, 'missing required descriptor arguments are rejected by the flat protocol');
968
+ for (const tool of ['lookup', 'constructor', 'toString']) {
969
+ await assert.rejects(() => bridge.execute(bridgeCall(tool, { city: 'Sharjah' }), null), /invalid arguments/, `${tool} rejects invalid descriptor arguments`);
970
+ }
971
+ assert.equal(spawnSpecs.length, beforeProtocolRejected, 'flat protocol rejections happen before spawn');
972
+ const args = { city: 'Dubai', payload: '$(touch /tmp/nope)' };
973
+ const result = await bridge.execute(bridgeCall('lookup', args), null);
974
+ assert.deepEqual(spawnSpecs[0]?.argv, [
975
+ process.execPath, '-m', 'agent_env.cli', '--lang', 'en', 'call', 'lookup', JSON.stringify(args),
976
+ ], 'call uses only the configured executable/prefix and fixed lookup subcommand argv');
977
+ assert.equal(spawnSpecs[0]?.cwd, root, 'call uses configured cwd');
978
+ assert.equal(spawnSpecs[0]?.stdio.stdin, 'ignore', 'call never exposes stdin');
979
+ assert.equal(spawnSpecs[0]?.stdio.stdout.maxBytes, 4_096, 'stdout cap comes from config');
980
+ assert.equal(spawnSpecs[0]?.stdio.stderr.maxBytes, 4_096, 'stderr cap comes from config');
981
+ assert.ok((spawnSpecs[0]?.graceMs ?? 0) > 0 && (spawnSpecs[0]?.graceMs ?? Infinity) <= 1_000, 'graceMs is bounded');
982
+ assert.equal(spawnSpecs[0]?.env?.GOTRY_BENCHMARK_BRIDGE_PARENT_SENTINEL, undefined, 'parent sentinel is not inherited');
983
+ for (const key of ['DATABASE_URL', 'SSH_AUTH_SOCK', 'AWS_PROFILE', 'HTTPS_PROXY', 'GOTRY_BENCHMARK_BRIDGE_PARENT_SENTINEL']) {
984
+ assert.equal(spawnSpecs[0]?.env?.[key], undefined, `${key} is tombstoned in bridge subprocess env`);
985
+ }
986
+ assert.equal(spawnSpecs[0]?.env?.PYTHONDONTWRITEBYTECODE, '1');
987
+ assert.equal(spawnSpecs[0]?.env?.PYTHONNOUSERSITE, '1');
988
+ assert.deepEqual(result, { ok: true, result: [{ city: 'Dubai' }] }, 'one-line JSON stdout becomes structured result');
989
+ outcomes.push({ stdout: okEnvelope({ city: 'Dubai' }) });
990
+ const unmappedResult = await bridge.execute(bridgeCall('toString', { city: 'Dubai' }), null);
991
+ assert.deepEqual(unmappedResult, { ok: true, result: { city: 'Dubai' } }, 'allowed tool without a positive mapping retains the recursive denylist only');
992
+ outcomes.push({ stdout: okEnvelope({ legacy: 'value' }) });
993
+ const legacyResult = await bridge.execute(bridgeCall('constructor', { city: 'Dubai' }), null);
994
+ assert.deepEqual(legacyResult, { ok: true, result: { legacy: 'value' } }, 'mapped constructor accepts its declared positive key');
995
+ outcomes.push({ stdout: okEnvelope({ city: 'Dubai' }) });
996
+ const constructorUnexpected = await bridge.execute(bridgeCall('constructor', { city: 'Dubai' }), null);
997
+ assert.deepEqual(constructorUnexpected, { ok: false, error: 'forbidden_output' }, 'mapped constructor rejects undeclared positive keys');
998
+ outcomes.push({ stdout: okEnvelope({ nested: { city: 'Dubai' } }) });
999
+ const nestedAllowedResult = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1000
+ assert.deepEqual(nestedAllowedResult, { ok: true, result: { nested: { city: 'Dubai' } } }, 'configured positive output allowlist accepts declared nested keys');
1001
+ outcomes.push({ stdout: okEnvelope({ city: 'Dubai', unexpected: 'secret' }) });
1002
+ const unexpectedResult = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1003
+ assert.deepEqual(unexpectedResult, { ok: false, error: 'forbidden_output' }, 'configured positive output allowlist rejects unexpected keys without reflecting them');
1004
+ outcomes.push({ stdout: okEnvelope([{ city: 'Dubai', nested: { unexpected: 'secret' } }]) });
1005
+ const nestedUnexpectedResult = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1006
+ assert.deepEqual(nestedUnexpectedResult, { ok: false, error: 'forbidden_output' }, 'configured positive output allowlist recurses through arrays and objects');
1007
+ for (const forbidden of [
1008
+ 'gold', 'goldAnswer', 'oracle', 'expected', 'expected_answer', 'answer', 'label',
1009
+ 'score', 'reward', 'ground_truth', 'groundTruth', 'hidden_query', 'hidden-query',
1010
+ 'loader_metadata', 'loaderMetadata', 'reference', 'gоld',
1011
+ ]) {
1012
+ outcomes.push({ stdout: okEnvelope({ nested: { [forbidden]: 'secret' } }) });
1013
+ const forbiddenResult = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1014
+ assert.deepEqual(forbiddenResult, { ok: false, error: 'forbidden_output' }, `recursive no-oracle key ${forbidden} is rejected without reflecting its name`);
1015
+ }
1016
+ outcomes.push({ stdout: okEnvelope('the hidden answer') });
1017
+ const primitiveOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1018
+ assert.deepEqual(primitiveOutput, { ok: false, error: 'invalid_output' }, 'primitive result strings cannot bypass the structured visible-output boundary');
1019
+ outcomes.push({ stdout: okEnvelope(['PRIVATE_ARRAY_VALUE']) });
1020
+ const primitiveArrayOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1021
+ assert.deepEqual(primitiveArrayOutput, { ok: false, error: 'forbidden_output' }, 'top-level primitive arrays cannot bypass the positive output-key boundary');
1022
+ assert.equal(JSON.stringify(primitiveArrayOutput).includes('PRIVATE_ARRAY_VALUE'), false, 'rejected primitive array values are not reflected');
1023
+ outcomes.push({ stdout: okEnvelope([["PRIVATE_NESTED_ARRAY_VALUE"]]) });
1024
+ const nestedPrimitiveArrayOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1025
+ assert.deepEqual(nestedPrimitiveArrayOutput, { ok: false, error: 'forbidden_output' }, 'nested primitive arrays without a declared-key ancestor fail closed');
1026
+ assert.equal(JSON.stringify(nestedPrimitiveArrayOutput).includes('PRIVATE_NESTED_ARRAY_VALUE'), false, 'rejected nested primitive array values are not reflected');
1027
+ outcomes.push({ stdout: okEnvelope([{ city: 'Dubai' }, 'PRIVATE_MIXED_ARRAY_VALUE']) });
1028
+ const mixedArrayOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1029
+ assert.deepEqual(mixedArrayOutput, { ok: false, error: 'forbidden_output' }, 'a valid record cannot mask an unkeyed primitive sibling');
1030
+ assert.equal(JSON.stringify(mixedArrayOutput).includes('PRIVATE_MIXED_ARRAY_VALUE'), false, 'rejected mixed-array primitive values are not reflected');
1031
+ outcomes.push({ stdout: okEnvelope([]) });
1032
+ const emptyArrayOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1033
+ assert.deepEqual(emptyArrayOutput, { ok: true, result: [] }, 'an empty top-level result array is an explicit non-reflecting collection');
1034
+ outcomes.push({ stdout: okEnvelope([{ city: 'Dubai' }]) });
1035
+ const recordArrayOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1036
+ assert.deepEqual(recordArrayOutput, { ok: true, result: [{ city: 'Dubai' }] }, 'top-level arrays of records remain valid when every leaf is covered by a declared key');
1037
+ outcomes.push({ stdout: okEnvelope({ city: ['Dubai'] }) });
1038
+ const keyedPrimitiveArrayOutput = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1039
+ assert.deepEqual(keyedPrimitiveArrayOutput, { ok: true, result: { city: ['Dubai'] } }, 'primitive arrays remain valid below a declared output key');
1040
+ const beforeOversized = spawnSpecs.length;
1041
+ const oversized = await bridge.execute(bridgeCall('lookup', { city: 'Dubai', notes: 'x'.repeat(65_537) }), null);
1042
+ assert.deepEqual(oversized, { ok: false, error: 'invalid_arguments', reason: 'serialization_limit' });
1043
+ assert.equal(spawnSpecs.length, beforeOversized, 'oversized serialized arguments are rejected before spawn');
1044
+ const beforeDeep = spawnSpecs.length;
1045
+ let deep = {};
1046
+ for (let index = 0; index < 13; index++)
1047
+ deep = { next: deep };
1048
+ await assert.rejects(() => bridge.execute(bridgeCall('lookup', { city: 'Dubai', ...deep }), null), /invalid arguments/);
1049
+ assert.equal(spawnSpecs.length, beforeDeep, 'schema-invalid deep arguments are rejected before spawn');
1050
+ const beforeOverride = spawnSpecs.length;
1051
+ const overrideResult = await bridge.execute(bridgeCall('lookup', {
1052
+ city: 'Dubai', executable: '/tmp/evil', cwd: '/tmp/evil', argv: ['--unsafe'],
1053
+ }), null);
1054
+ assert.deepEqual(spawnSpecs[beforeOverride]?.argv, [
1055
+ process.execPath, '-m', 'agent_env.cli', '--lang', 'en', 'call', 'lookup', JSON.stringify({
1056
+ city: 'Dubai', executable: '/tmp/evil', cwd: '/tmp/evil', argv: ['--unsafe'],
1057
+ }),
1058
+ ], 'model executable/cwd/argv fields remain data and cannot override config');
1059
+ assert.equal(overrideResult.ok, true, 'override-shaped arguments still use the configured bridge');
1060
+ outcomes.push({ stdout: okEnvelope({ city: 'Dubai' }), lossy: true });
1061
+ const truncated = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1062
+ assert.deepEqual(truncated, { ok: false, error: 'output_truncated' }, 'lossy stdout is rejected without parsing partial output');
1063
+ outcomes.push({ stdout: okEnvelope({ city: 'Dubai' }), stderr: 'private runner diagnostic', exitCode: 17, signal: 'SIGTERM' });
1064
+ const failed = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1065
+ assert.deepEqual(failed, { ok: false, error: 'runner_failed', exit_code: 17, signal: 'SIGTERM' }, 'nonzero runner result is structured without stderr echo');
1066
+ // The bridge protocol/infrastructure failure vocabulary is distinct from
1067
+ // per-tool domain outcomes and remains discoverable through action=errors.
1068
+ const errorsTable = await bridge.execute({ action: 'errors' }, null);
1069
+ assert.ok(errorsTable.ok === true && errorsTable.errors, 'bridge failure contract is discoverable');
1070
+ for (const code of ['invalid_action', 'disallowed_tool', 'invalid_arguments', 'timed_out', 'output_truncated', 'invalid_json', 'invalid_output', 'runner_failed', 'spawn_failed', 'forbidden_output']) {
1071
+ const row = errorsTable.errors?.[code];
1072
+ assert.ok(row && typeof row.recoverable === 'boolean' && row.remedy.length > 0, `failure contract contains ${code} with recovery guidance`);
1073
+ }
1074
+ assert.equal(errorsTable.errors?.invalid_arguments?.recoverable, true, 'invalid caller arguments are recoverable');
1075
+ assert.equal(errorsTable.errors?.forbidden_output?.recoverable, false, 'policy-boundary output is not recoverable');
1076
+ assert.deepEqual(errorsTable, { ok: true, errors: BRIDGE_ERROR_CONTRACT }, 'errors action returns the exact closed failure contract');
1077
+ const toolsTable = await bridge.execute({ action: 'tools' }, null);
1078
+ assert.deepEqual(toolsTable, {
1079
+ ok: true,
1080
+ tools: [
1081
+ { name: 'lookup', description: 'Lookup one declared city.', input_schema: lookupInputSchema(), output_keys: ['city', 'nested'], domain_outcomes: [{ status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' }, { status: 'error', code: 'AMBIGUOUS', recovery: 'choose_alternative' }] },
1082
+ { name: 'constructor', description: 'Construct one declared city.', input_schema: lookupInputSchema(), output_keys: ['legacy'], domain_outcomes: [{ status: 'error', code: 'INVALID', recovery: 'retry_same' }] },
1083
+ { name: 'toString', description: 'Stringify one declared city.', input_schema: lookupInputSchema(), output_keys: ['city', 'nested'], domain_outcomes: [] },
1084
+ ],
1085
+ }, 'tools action returns every exact frozen descriptor, including its input schema');
1086
+ for (const stdout of ['not-json', '{"result":1}{"result":2}']) {
1087
+ outcomes.push({ stdout });
1088
+ const malformed = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1089
+ assert.deepEqual(malformed, { ok: false, error: 'invalid_json' }, 'malformed or multi-value JSON is rejected');
1090
+ }
1091
+ outcomes.push({ stdout: JSON.stringify({ schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' }) });
1092
+ const miss = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1093
+ assert.deepEqual(miss, { ok: true, outcome: { schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' } }, 'exit-zero domain miss is a successful typed outcome');
1094
+ outcomes.push({ stdout: JSON.stringify({ schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'error', code: 'AMBIGUOUS', recovery: 'choose_alternative' }) });
1095
+ const domainError = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1096
+ assert.deepEqual(domainError, { ok: true, outcome: { schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'error', code: 'AMBIGUOUS', recovery: 'choose_alternative' } }, 'declared exit-zero domain error is transport success');
1097
+ for (const invalidEnvelope of [
1098
+ { schema_version: 'legacy', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' },
1099
+ { schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'miss', code: 'UNKNOWN', recovery: 'revise_arguments' },
1100
+ { schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'miss', code: 'NOT_FOUND', recovery: 'retry_same' },
1101
+ { schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'error', code: 'AMBIGUOUS', recovery: 'choose_alternative', message: 'private free text' },
1102
+ ]) {
1103
+ outcomes.push({ stdout: JSON.stringify(invalidEnvelope) });
1104
+ assert.deepEqual(await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null), { ok: false, error: 'invalid_output' }, 'undeclared or non-exact domain envelope fails closed');
1105
+ }
1106
+ outcomes.push({ stdout: JSON.stringify({ result: { city: 'Dubai' } }) });
1107
+ assert.deepEqual(await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null), { ok: false, error: 'invalid_output' }, 'legacy versionless result envelope is rejected');
1108
+ outcomes.push({ stdout: JSON.stringify({ schema_version: BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' }), exitCode: 23 });
1109
+ assert.deepEqual(await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null), { ok: false, error: 'runner_failed', exit_code: 23, signal: null }, 'nonzero exit remains infrastructure failure even when stdout resembles a domain envelope');
1110
+ outcomes.push({ spawnError: true });
1111
+ const spawnFailed = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1112
+ assert.deepEqual(spawnFailed, { ok: false, error: 'spawn_failed' }, 'spawn infrastructure failure is structured');
1113
+ outcomes.push({ spawnReject: true });
1114
+ const asyncSpawnFailed = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1115
+ assert.deepEqual(asyncSpawnFailed, { ok: false, error: 'spawn_failed' }, 'public done rejection is classified as spawn/provider failure');
1116
+ outcomes.push({ stdout: okEnvelope({ city: 'Dubai' }), readFailure: true });
1117
+ const postSuccessCollectFailed = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1118
+ assert.deepEqual(postSuccessCollectFailed, { ok: false, error: 'runner_failed', exit_code: 0, signal: null }, 'collected output reading failure after a resolved done is runner_failed, not spawn_failed');
1119
+ outcomes.push({ waitForAbort: true });
1120
+ const timedOut = await bridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null);
1121
+ assert.deepEqual(timedOut, { ok: false, error: 'timed_out' }, 'deadline abort is surfaced as timed_out');
1122
+ assert.equal(timeoutSignal?.aborted, true, 'timeout abort signal is fired');
1123
+ const beforeInvalidArgs = spawnSpecs.length;
1124
+ await assert.rejects(() => bridge.execute(bridgeCall('lookup', ['not', 'plain']), null), /invalid arguments/);
1125
+ assert.equal(spawnSpecs.length, beforeInvalidArgs, 'non-object arguments are rejected before spawn');
1126
+ for (const [label, argumentsValue] of [
1127
+ ['missing required city', {}],
1128
+ ['wrong city type', { city: 7 }],
1129
+ ['undeclared country', { city: 'Dubai', country: 'AE' }],
1130
+ ]) {
1131
+ await assert.rejects(() => bridge.execute(bridgeCall('lookup', argumentsValue), null), /invalid arguments/, `${label} is rejected by the descriptor-derived runtime validator`);
1132
+ }
1133
+ assert.equal(spawnSpecs.length, beforeInvalidArgs, 'required, type, and closed-object argument failures all happen before spawn');
1134
+ await assert.rejects(() => bridge.execute({ action: 'inspect' }, null), /invalid arguments/);
1135
+ const beforeRejected = spawnSpecs.length;
1136
+ await assert.rejects(() => bridge.execute(bridgeCall('delete_all', {}), null), /invalid arguments/);
1137
+ assert.equal(spawnSpecs.length, beforeRejected, 'disallowed tool is rejected before spawn');
1138
+ const spacedConfigPath = join(root, ' benchmark-environment-config.json ');
1139
+ writeFileSync(spacedConfigPath, readFileSync(configPath));
1140
+ const spacedTools = [];
1141
+ const spacedCtx = {
1142
+ tools: {
1143
+ register(tool) { spacedTools.push(tool); return () => { }; },
1144
+ get(name) { return spacedTools.find(tool => tool.name === name); },
1145
+ schemas() { return spacedTools.map(tool => ({ name: tool.name })); },
1146
+ },
1147
+ agents: { list() { return []; } },
1148
+ systemPrompt: { variable() { } },
1149
+ on() { return () => { }; },
1150
+ effect() { return () => { }; },
1151
+ get(name) {
1152
+ if (name === 'subprocess')
1153
+ return { spawn: (_spec) => fakeHandle({ stdout: '{}' }) };
1154
+ if (name === 'agents')
1155
+ return { list() { return []; } };
1156
+ return undefined;
1157
+ },
1158
+ };
1159
+ apply(spacedCtx, {
1160
+ stateRoot: root, timeoutMs: 20, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: spacedConfigPath,
1161
+ });
1162
+ assert.deepEqual(spacedTools.map(tool => tool.name), ['gotry_benchmark_environment'], 'benchmark bridge loads a valid raw path with whitespace basename');
1163
+ const disabled = [];
1164
+ const disabledVariables = [];
1165
+ const disabledCtx = {
1166
+ tools: { register(tool) { disabled.push(tool); return () => { }; } },
1167
+ systemPrompt: { variable(name) { disabledVariables.push(name); } },
1168
+ };
1169
+ apply(disabledCtx, {
1170
+ stateRoot: root, timeoutMs: 1_000, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: '',
1171
+ });
1172
+ assert.equal(disabled.some(tool => tool.name === 'gotry_benchmark_environment'), false, 'empty config path keeps bridge default-off');
1173
+ assert.ok(disabled.length > 1, 'normal product mode keeps the full GoTry tool catalog');
1174
+ assert.deepEqual(disabledVariables, ['current_date', 'time_anchor_card', 'motivation_brief', 'channel_routing_card'], 'normal product mode keeps its prompt variables');
1175
+ const whitespace = [];
1176
+ const whitespaceCtx = {
1177
+ tools: { register(tool) { whitespace.push(tool); return () => { }; } },
1178
+ systemPrompt: { variable() { } },
1179
+ };
1180
+ apply(whitespaceCtx, {
1181
+ stateRoot: root, timeoutMs: 1_000, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: ' \t ',
1182
+ });
1183
+ assert.equal(whitespace.some(tool => tool.name === 'gotry_benchmark_environment'), false, 'whitespace config path keeps benchmark mode default-off');
1184
+ assert.ok(whitespace.length > 1, 'whitespace path preserves the ordinary product tool catalog');
1185
+ const originalModelOverride = process.env.GOTRY_LLM_MODEL;
1186
+ process.env.GOTRY_LLM_MODEL = 'round7-model-preserved';
1187
+ try {
1188
+ const modelTools = [];
1189
+ let modelRequest;
1190
+ const modelEvents = [];
1191
+ const modelCtx = {
1192
+ tools: {
1193
+ register(tool) { modelTools.push(tool); return () => { }; },
1194
+ get(name) { return modelTools.find(tool => tool.name === name); },
1195
+ schemas() { return modelTools.map(tool => ({ name: tool.name })); },
1196
+ },
1197
+ agents: { list() { return []; } },
1198
+ systemPrompt: { variable() { } },
1199
+ on(event, listener) {
1200
+ modelEvents.push(event);
1201
+ if (event === 'agent/request')
1202
+ modelRequest = listener;
1203
+ return () => { };
1204
+ },
1205
+ effect() { return () => { }; },
1206
+ get(name) {
1207
+ if (name === 'subprocess')
1208
+ return { spawn: (_spec) => fakeHandle({ stdout: '{}' }) };
1209
+ if (name === 'agents')
1210
+ return { list() { return []; } };
1211
+ return undefined;
1212
+ },
1213
+ };
1214
+ apply(modelCtx, {
1215
+ stateRoot: root, timeoutMs: 20, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: configPath,
1216
+ });
1217
+ assert.deepEqual(modelTools.map(tool => tool.name), ['gotry_benchmark_environment'], 'benchmark model override does not re-enable product tools');
1218
+ assert.ok(modelEvents.includes('agent/request'), 'benchmark mode preserves the model override hook');
1219
+ assert.ok(modelRequest);
1220
+ assert.deepEqual(await modelRequest({}, async () => ({ provider: 'persisted', model: 'old', reasoningEffort: 'high', marker: 'kept' })), { provider: 'deepseek-official', model: 'round7-model-preserved', marker: 'kept' }, 'benchmark model override remains effective and replaces the persisted model only');
1221
+ }
1222
+ finally {
1223
+ if (originalModelOverride === undefined)
1224
+ delete process.env.GOTRY_LLM_MODEL;
1225
+ else
1226
+ process.env.GOTRY_LLM_MODEL = originalModelOverride;
1227
+ }
1228
+ const validConfig = {
1229
+ schema_version: 'gotry_benchmark_environment_bridge_v4',
1230
+ enabled: true,
1231
+ executable: process.execPath,
1232
+ cwd: root,
1233
+ argv_prefix: ['-m', 'agent_env.cli', '--lang', 'en'],
1234
+ tools: [{
1235
+ name: 'lookup',
1236
+ description: 'Lookup one declared city.',
1237
+ input_schema: {
1238
+ type: 'object',
1239
+ properties: {
1240
+ city: { type: 'string', enum: ['Dubai', 'Abu Dhabi'], description: 'Declared city name.' },
1241
+ },
1242
+ required: ['city'],
1243
+ additionalProperties: false,
1244
+ },
1245
+ output_keys: ['city'],
1246
+ domain_outcomes: [{ status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' }],
1247
+ }],
1248
+ timeout_ms: 20,
1249
+ max_output_bytes: 4_096,
1250
+ terminal_output: { tag: 'done', max_bytes: 4_096, body_schema: OK_BODY_SCHEMA },
1251
+ isolation: { mode: 'host-enforced', writes: 'forbidden', network: 'denied' },
1252
+ };
1253
+ const clonedConfig = () => JSON.parse(JSON.stringify(validConfig));
1254
+ const configWithTool = (mutate) => {
1255
+ const config = clonedConfig();
1256
+ mutate(config.tools[0]);
1257
+ return config;
1258
+ };
1259
+ const nestedInputSchema = (objectLevels) => {
1260
+ let child = { type: 'string' };
1261
+ for (let index = 0; index < objectLevels; index += 1) {
1262
+ child = {
1263
+ type: 'object',
1264
+ properties: { next: child },
1265
+ required: ['next'],
1266
+ additionalProperties: false,
1267
+ };
1268
+ }
1269
+ return child;
1270
+ };
1271
+ const flatInputSchema = (count) => ({
1272
+ type: 'object',
1273
+ properties: Object.fromEntries(Array.from({ length: count }, (_, index) => [`p${index}`, { type: 'string' }])),
1274
+ required: [],
1275
+ additionalProperties: false,
1276
+ });
1277
+ const totalEnumInputSchema = (groups) => ({
1278
+ type: 'object',
1279
+ properties: Object.fromEntries(Array.from({ length: groups }, (_, group) => [
1280
+ `e${group}`,
1281
+ { type: 'string', enum: Array.from({ length: 64 }, (_, index) => `v${group}_${index}`) },
1282
+ ])),
1283
+ required: [],
1284
+ additionalProperties: false,
1285
+ });
1286
+ const byteSizedInputSchema = (targetBytes) => {
1287
+ const properties = Object.fromEntries(Array.from({ length: 64 }, (_, index) => [
1288
+ `b${index}`,
1289
+ { type: 'string', description: '' },
1290
+ ]));
1291
+ const schema = { type: 'object', properties, required: [], additionalProperties: false };
1292
+ let remaining = targetBytes - Buffer.byteLength(JSON.stringify(schema), 'utf8');
1293
+ assert.ok(remaining >= 0 && remaining <= 64 * 512, 'byte-boundary fixture has enough bounded description capacity');
1294
+ for (const property of Object.values(properties)) {
1295
+ const added = Math.min(512, remaining);
1296
+ property.description = 'x'.repeat(added);
1297
+ remaining -= added;
1298
+ }
1299
+ assert.equal(remaining, 0, 'byte-boundary fixture reaches the requested serialized size');
1300
+ assert.equal(Buffer.byteLength(JSON.stringify(schema), 'utf8'), targetBytes);
1301
+ return schema;
1302
+ };
1303
+ const frozenProjection = registerBenchmarkEnvironmentBridge(configPath, () => { }, {
1304
+ spawn: (_spec) => fakeHandle({ stdout: '{"result":{}}' }),
1305
+ });
1306
+ assert.equal(Object.isFrozen(frozenProjection), true, 'bridge projection is frozen');
1307
+ assert.equal(Object.isFrozen(frozenProjection.allowedTools), true, 'projected allowlist is frozen');
1308
+ assert.equal(Object.isFrozen(frozenProjection.terminal), true, 'projected terminal contract is frozen');
1309
+ assert.throws(() => { frozenProjection.allowedTools.push('escape'); }, TypeError);
1310
+ assert.throws(() => { frozenProjection.terminal.tag = 'escape'; }, TypeError);
1311
+ const bridgeRegistrationFor = (config, withSubprocess = true) => {
1312
+ const path = join(root, `config-${Math.random().toString(36).slice(2)}.json`);
1313
+ writeFileSync(path, JSON.stringify(config));
1314
+ const tools = [];
1315
+ const freshCtx = {
1316
+ tools: { register(tool) { tools.push(tool); return () => { }; }, get(name) { return tools.find(tool => tool.name === name); }, schemas() { return tools.map(tool => ({ name: tool.name })); } },
1317
+ systemPrompt: { variable() { } },
1318
+ on() { return () => { }; },
1319
+ effect(action) { return runEffect(action); },
1320
+ agents: { list() { return []; } },
1321
+ get(name) {
1322
+ if (name === 'subprocess')
1323
+ return this.subprocess;
1324
+ if (name === 'agents')
1325
+ return this.agents;
1326
+ },
1327
+ ...(withSubprocess ? { subprocess: { spawn: (_spec) => fakeHandle({ stdout: '{"result":{}}' }) } } : {}),
1328
+ };
1329
+ apply(freshCtx, {
1330
+ stateRoot: root, timeoutMs: 20, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: path,
1331
+ });
1332
+ return tools.some(tool => tool.name === 'gotry_benchmark_environment');
1333
+ };
1334
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, unknown: true }), /benchmark environment bridge configuration unavailable/, 'unknown top-level config key fails hard');
1335
+ assert.throws(() => bridgeRegistrationFor({
1336
+ ...validConfig,
1337
+ tools: [{ ...validConfig.tools[0], input_schema: { type: 'object' } }],
1338
+ }), /benchmark environment bridge configuration unavailable/, 'v3 rejects an open annotation-only input schema');
1339
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, schema_version: 'gotry_benchmark_environment_bridge_v1' }), /benchmark environment bridge configuration unavailable/, 'v1 config cannot silently omit the Round 3 terminal semantics');
1340
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, schema_version: 'gotry_benchmark_environment_bridge_v3' }), /benchmark environment bridge configuration unavailable/, 'v3 config cannot silently omit the Round 12 terminal body schema (#215)');
1341
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, terminal_output: { ...validConfig.terminal_output, body_schema: { ...validConfig.terminal_output.body_schema, additionalProperties: true } } }), /benchmark environment bridge configuration unavailable/, 'non-closed terminal body schema fails the config load closed');
1342
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, tools: [validConfig.tools[0], validConfig.tools[0]] }), /benchmark environment bridge configuration unavailable/, 'duplicate tool descriptor fails hard');
1343
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { delete tool.output_keys; })), /benchmark environment bridge configuration unavailable/, 'missing output_keys fails hard');
1344
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.output_keys = []; })), /benchmark environment bridge configuration unavailable/, 'empty output allowlist fails closed at registration');
1345
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.output_keys = ['city', 'city']; })), /benchmark environment bridge configuration unavailable/, 'duplicate output key fails hard');
1346
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.output_keys = ['not a key']; })), /benchmark environment bridge configuration unavailable/, 'non-identifier output key fails hard');
1347
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { delete tool.domain_outcomes; })), /benchmark environment bridge configuration unavailable/, 'missing domain_outcomes fails hard');
1348
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.domain_outcomes = [{ status: 'unknown', code: 'NOT_FOUND', recovery: 'none' }]; })), /benchmark environment bridge configuration unavailable/, 'unknown domain status fails hard');
1349
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.domain_outcomes = [{ status: 'miss', code: 'NOT_FOUND', recovery: 'free_text' }]; })), /benchmark environment bridge configuration unavailable/, 'free-text domain recovery fails hard');
1350
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.domain_outcomes = [{ status: 'miss', code: 'NOT_FOUND', recovery: 'none' }, { status: 'error', code: 'NOT_FOUND', recovery: 'none' }]; })), /benchmark environment bridge configuration unavailable/, 'duplicate domain code fails hard');
1351
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.name = 'lookup;rm'; })), /benchmark environment bridge configuration unavailable/, 'shell metacharacter tool identifier fails hard');
1352
+ for (const [label, schema] of [
1353
+ ['reference keyword', { type: 'object', properties: { city: { $ref: '#/city' } }, required: ['city'], additionalProperties: false }],
1354
+ ['default keyword', { type: 'object', properties: { city: { type: 'string', default: 'Dubai' } }, required: ['city'], additionalProperties: false }],
1355
+ ['examples keyword', { type: 'object', properties: { city: { type: 'string', examples: ['Dubai'] } }, required: ['city'], additionalProperties: false }],
1356
+ ['unknown keyword', { type: 'object', properties: { city: { type: 'string', title: 'City' } }, required: ['city'], additionalProperties: false }],
1357
+ ['open nested object', { type: 'object', properties: { nested: { type: 'object', properties: {}, required: [] } }, required: ['nested'], additionalProperties: false }],
1358
+ ['array without items', { type: 'object', properties: { values: { type: 'array' } }, required: ['values'], additionalProperties: false }],
1359
+ ['duplicate required', { type: 'object', properties: { city: { type: 'string' } }, required: ['city', 'city'], additionalProperties: false }],
1360
+ ['unknown required', { type: 'object', properties: { city: { type: 'string' } }, required: ['country'], additionalProperties: false }],
1361
+ ['type-mismatched enum', { type: 'object', properties: { city: { type: 'string', enum: ['Dubai', 7] } }, required: ['city'], additionalProperties: false }],
1362
+ ['duplicate enum', { type: 'object', properties: { city: { type: 'string', enum: ['Dubai', 'Dubai'] } }, required: ['city'], additionalProperties: false }],
1363
+ ]) {
1364
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = schema; })), /benchmark environment bridge configuration unavailable/, `${label} fails closed`);
1365
+ }
1366
+ assert.equal(bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = nestedInputSchema(8); })), true, 'schema depth eight is accepted');
1367
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = nestedInputSchema(9); })), /benchmark environment bridge configuration unavailable/, 'schema depth nine is rejected');
1368
+ assert.equal(bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = flatInputSchema(255); })), true, 'the 256-node total boundary is accepted');
1369
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = flatInputSchema(256); })), /benchmark environment bridge configuration unavailable/, 'the 256-node total boundary plus one is rejected');
1370
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = flatInputSchema(257); })), /benchmark environment bridge configuration unavailable/, 'the property total bound rejects 257 properties');
1371
+ assert.equal(bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = totalEnumInputSchema(4); })), true, 'the 256-enum-value total boundary is accepted');
1372
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = totalEnumInputSchema(5); })), /benchmark environment bridge configuration unavailable/, 'the enum-value total bound rejects values above 256');
1373
+ assert.equal(bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = byteSizedInputSchema(16 * 1024); })), true, 'the 16 KiB schema boundary is accepted');
1374
+ assert.throws(() => bridgeRegistrationFor(configWithTool(tool => { tool.input_schema = byteSizedInputSchema(16 * 1024 + 1); })), /benchmark environment bridge configuration unavailable/, 'the schema byte boundary plus one is rejected');
1375
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, argv_prefix: ['agent\n--unsafe'] }), /benchmark environment bridge configuration unavailable/, 'argv control separator fails hard');
1376
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, isolation: { mode: 'host-enforced', writes: 'forbidden' } }), /benchmark environment bridge configuration unavailable/, 'incomplete isolation policy fails hard');
1377
+ const { terminal_output: _terminalOutput, ...missingTerminalConfig } = validConfig;
1378
+ assert.throws(() => bridgeRegistrationFor(missingTerminalConfig), /benchmark environment bridge configuration unavailable/, 'terminal output contract is required');
1379
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, terminal_output: { tag: '1invalid', max_bytes: 4_096 } }), /benchmark environment bridge configuration unavailable/, 'terminal tag must be an identifier');
1380
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, terminal_output: { tag: 'done', max_bytes: 0 } }), /benchmark environment bridge configuration unavailable/, 'terminal output lower bound fails hard');
1381
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, terminal_output: { tag: 'done', max_bytes: 1024 * 1024 + 1 } }), /benchmark environment bridge configuration unavailable/, 'terminal output upper bound fails hard');
1382
+ assert.throws(() => bridgeRegistrationFor({ ...validConfig, terminal_output: { tag: 'done', max_bytes: 4_096, extra: true } }), /benchmark environment bridge configuration unavailable/, 'terminal output rejects unknown keys');
1383
+ assert.throws(() => bridgeRegistrationFor(validConfig, false), /benchmark environment bridge subprocess unavailable/, 'explicit opt-in without an active subprocess provider fails hard');
1384
+ assert.equal(loadConfigRegistration(configPath, root), true, 'owner-local regular 0644 config remains valid');
1385
+ assert.throws(() => loadConfigRegistration('bridge.json', root), /benchmark environment bridge configuration unavailable/, 'relative config path fails hard');
1386
+ const symlinkPath = join(root, 'bridge-symlink.json');
1387
+ symlinkSync(configPath, symlinkPath);
1388
+ assert.equal(lstatSync(symlinkPath).isSymbolicLink(), true);
1389
+ assert.throws(() => loadConfigRegistration(symlinkPath, root), /benchmark environment bridge configuration unavailable/, 'symlink config path fails hard');
1390
+ const widePath = join(root, 'bridge-wide.json');
1391
+ writeFileSync(widePath, JSON.stringify(validConfig));
1392
+ chmodSync(widePath, 0o666);
1393
+ assert.throws(() => loadConfigRegistration(widePath, root), /benchmark environment bridge configuration unavailable/, 'group/world writable config fails hard');
1394
+ const strictConfigPath = join(root, 'strict-bridge.json');
1395
+ writeFileSync(strictConfigPath, JSON.stringify(validConfig));
1396
+ const strictTools = [];
1397
+ const strictCtx = {
1398
+ tools: { register(tool) { strictTools.push(tool); return () => { }; }, get(name) { return strictTools.find(tool => tool.name === name); }, schemas() { return strictTools.map(tool => ({ name: tool.name })); } },
1399
+ systemPrompt: { variable() { } },
1400
+ on() { return () => { }; },
1401
+ effect(_action) { return () => { }; },
1402
+ agents: { list() { return []; } },
1403
+ get(name) {
1404
+ if (name === 'subprocess')
1405
+ return this.subprocess;
1406
+ if (name === 'agents')
1407
+ return this.agents;
1408
+ },
1409
+ subprocess: { spawn: (_spec) => fakeHandle({ stdout: okEnvelope({ city: 'Dubai' }) }) },
1410
+ };
1411
+ apply(strictCtx, {
1412
+ stateRoot: root, timeoutMs: 20, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: strictConfigPath,
1413
+ });
1414
+ const strictBridge = strictTools.find(tool => tool.name === 'gotry_benchmark_environment');
1415
+ assert.ok(strictBridge?.execute, 'strict v3 config registers the bridge');
1416
+ assert.deepEqual(await strictBridge.execute(bridgeCall('lookup', { city: 'Dubai' }), null), { ok: true, result: { city: 'Dubai' } }, 'strict v3 result envelope executes safe structured output');
1417
+ console.log('BENCHMARK ENVIRONMENT BRIDGE TESTS: registration + TDD bridge contract assertions');
1418
+ }
1419
+ catch (error) {
1420
+ console.error(error);
1421
+ process.exitCode = 1;
1422
+ }
1423
+ finally {
1424
+ for (const [name, value] of ambientSentinels) {
1425
+ if (value === undefined)
1426
+ delete process.env[name];
1427
+ else
1428
+ process.env[name] = value;
1429
+ }
1430
+ rmSync(root, { recursive: true, force: true });
1431
+ }
1432
+ function loadConfigRegistration(path, stateRoot) {
1433
+ const tools = [];
1434
+ const freshCtx = {
1435
+ tools: { register(tool) { tools.push(tool); return () => { }; }, get(name) { return tools.find(tool => tool.name === name); }, schemas() { return tools.map(tool => ({ name: tool.name })); } },
1436
+ systemPrompt: { variable() { } },
1437
+ agents: { list() { return []; } },
1438
+ on() { return () => { }; },
1439
+ effect(_action) { return () => { }; },
1440
+ get(name) {
1441
+ if (name === 'subprocess')
1442
+ return this.subprocess;
1443
+ if (name === 'agents')
1444
+ return this.agents;
1445
+ },
1446
+ subprocess: { spawn: (_spec) => fakeHandle({ stdout: '{"result":{}}' }) },
1447
+ };
1448
+ apply(freshCtx, {
1449
+ stateRoot, timeoutMs: 20, hbcliBin: '', sessionAccess: 'off', benchmarkEnvironmentConfigPath: path,
1450
+ });
1451
+ return tools.some(tool => tool.name === 'gotry_benchmark_environment');
1452
+ }