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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1744 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +135 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +546 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +509 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +50 -44
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +31 -21
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +75 -24
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +97 -83
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +635 -44
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +24 -59
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +50 -11
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +25 -70
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +382 -246
  191. package/dist/scripts/ledger-workflow-crash.js +10 -20
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +18 -52
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +24 -12
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +39 -28
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +20 -20
  227. package/dist/scripts/session-attach-poc.js +34 -37
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +14 -35
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +27 -46
  233. package/dist/scripts/session-tests.js +796 -96
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +690 -246
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +226 -101
  253. package/dist/scripts/state-cli.js +550 -199
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +29 -93
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +48 -74
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2227 -882
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +319 -160
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +30 -14
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +435 -516
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +44 -63
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +535 -359
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +293 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +1948 -198
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +223 -25
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +354 -88
  506. package/ts/src/wish-pool.ts +79 -0
  507. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,675 @@
1
+ /**
2
+ * Fresh-profile, installed-package Web E2E for GoTry artifact cards.
3
+ *
4
+ * The only model endpoint is a local OpenAI-compatible SSE relay. The test
5
+ * starts `gotry web --no-open --port 0`, captures the authenticated `dsh web:`
6
+ * URL, then drives the real composer with a fresh headless Chrome profile.
7
+ * It verifies the public Client cards, path selection, the normal read → edit
8
+ * → gotry_artifacts_read sequence, and the visibly refreshed content/version.
9
+ * Temporary package install, HOME/DSH_HOME, workspace, relay, browser, and
10
+ * server state are cleaned on both success and failure.
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import { createHash } from 'node:crypto';
14
+ import { createServer } from 'node:http';
15
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
16
+ import { tmpdir } from 'node:os';
17
+ import { basename, join } from 'node:path';
18
+ import { spawn } from 'node:child_process';
19
+ import { promisify } from 'node:util';
20
+ import { execFile as execFileCallback } from 'node:child_process';
21
+ import puppeteer from 'puppeteer-core';
22
+ const execFile = promisify(execFileCallback);
23
+ const ROOT = join(import.meta.dirname, '..', '..');
24
+ const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
25
+ const TIMEOUT_MS = 120_000;
26
+ function sse(data) {
27
+ return `data: ${JSON.stringify(data)}\n\n`;
28
+ }
29
+ function toolResponse(id, name, args) {
30
+ return sse({
31
+ id,
32
+ object: 'chat.completion.chunk',
33
+ created: 1,
34
+ model: 'synthetic-artifact-web',
35
+ choices: [{ index: 0, delta: { role: 'assistant', tool_calls: [{ index: 0, id: `${id}-call`, type: 'function', function: { name, arguments: JSON.stringify(args) } }] }, finish_reason: null }],
36
+ }) + sse({
37
+ id: `${id}-finish`,
38
+ object: 'chat.completion.chunk',
39
+ created: 1,
40
+ model: 'synthetic-artifact-web',
41
+ choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }],
42
+ }) + 'data: [DONE]\n\n';
43
+ }
44
+ function textResponse(id, text) {
45
+ return sse({
46
+ id,
47
+ object: 'chat.completion.chunk',
48
+ created: 1,
49
+ model: 'synthetic-artifact-web',
50
+ choices: [{ index: 0, delta: { role: 'assistant', content: text }, finish_reason: null }],
51
+ }) + sse({
52
+ id: `${id}-finish`,
53
+ object: 'chat.completion.chunk',
54
+ created: 1,
55
+ model: 'synthetic-artifact-web',
56
+ choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
57
+ }) + 'data: [DONE]\n\n';
58
+ }
59
+ function toolNames(body) {
60
+ return (body.tools ?? []).map(tool => {
61
+ const fn = tool.function;
62
+ return String(fn?.name ?? tool.name ?? '');
63
+ }).filter(Boolean);
64
+ }
65
+ function hasToolMessage(body) {
66
+ return (body.messages ?? []).some(message => message.role === 'tool');
67
+ }
68
+ function lastUserText(body) {
69
+ const message = [...(body.messages ?? [])].reverse().find(item => item.role === 'user');
70
+ const content = message?.content;
71
+ if (typeof content === 'string')
72
+ return content;
73
+ return JSON.stringify(content ?? '');
74
+ }
75
+ async function startRelay() {
76
+ const bodies = [];
77
+ const servedTools = [];
78
+ let phase = 'first-list';
79
+ const server = createServer((req, res) => {
80
+ if (req.method !== 'POST' || !req.url?.endsWith('/chat/completions')) {
81
+ res.writeHead(404).end('not found');
82
+ return;
83
+ }
84
+ const chunks = [];
85
+ req.on('data', chunk => chunks.push(Buffer.from(chunk)));
86
+ req.on('end', () => {
87
+ let body = {};
88
+ try {
89
+ body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
90
+ }
91
+ catch { /* dsh will report malformed upstream */ }
92
+ bodies.push(body);
93
+ res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' });
94
+ const names = toolNames(body);
95
+ const hasArtifacts = names.includes('gotry_artifacts_list') || names.includes('gotry_artifacts_read');
96
+ let payload;
97
+ if (!hasArtifacts) {
98
+ payload = textResponse(`aux-${bodies.length}`, '');
99
+ }
100
+ else if (phase === 'first-list') {
101
+ phase = 'first-read';
102
+ servedTools.push('gotry_artifacts_list');
103
+ payload = toolResponse('artifact-first-list', 'gotry_artifacts_list', { limit: 20 });
104
+ }
105
+ else if (phase === 'first-read' && hasToolMessage(body)) {
106
+ phase = 'first-final';
107
+ servedTools.push('gotry_artifacts_read');
108
+ payload = toolResponse('artifact-first-read', 'gotry_artifacts_read', { path: 'trip-2027.md' });
109
+ }
110
+ else if (phase === 'first-final' && hasToolMessage(body)) {
111
+ phase = 'second-read';
112
+ payload = textResponse('artifact-first-final', '已展示行程产物。');
113
+ }
114
+ else if (phase === 'second-read' && lastUserText(body).includes('修改')) {
115
+ phase = 'second-edit';
116
+ servedTools.push('read');
117
+ payload = toolResponse('artifact-second-read', 'read', { file_path: 'trip-2027.md' });
118
+ }
119
+ else if (phase === 'second-edit' && hasToolMessage(body)) {
120
+ phase = 'second-artifact-read';
121
+ servedTools.push('edit');
122
+ payload = toolResponse('artifact-second-edit', 'edit', { file_path: 'trip-2027.md', old_string: 'Day 5: return', new_string: 'Day 5: return\nDay 6: revision requested', replace_all: false });
123
+ }
124
+ else if (phase === 'second-artifact-read' && hasToolMessage(body)) {
125
+ phase = 'second-final';
126
+ servedTools.push('gotry_artifacts_read');
127
+ payload = toolResponse('artifact-second-artifact-read', 'gotry_artifacts_read', { path: 'trip-2027.md' });
128
+ }
129
+ else if (phase === 'second-final' && hasToolMessage(body)) {
130
+ phase = 'third-list';
131
+ payload = textResponse('artifact-second-final', '已更新并重新读取行程产物。');
132
+ }
133
+ else if (phase === 'third-list' && lastUserText(body).includes('HTML')) {
134
+ phase = 'third-read';
135
+ servedTools.push('gotry_artifacts_list');
136
+ payload = toolResponse('artifact-third-list', 'gotry_artifacts_list', { limit: 20 });
137
+ }
138
+ else if (phase === 'third-read' && hasToolMessage(body)) {
139
+ phase = 'third-final';
140
+ servedTools.push('gotry_artifacts_read');
141
+ payload = toolResponse('artifact-third-read', 'gotry_artifacts_read', { path: 'trip-2027.html' });
142
+ }
143
+ else if (phase === 'third-final' && hasToolMessage(body)) {
144
+ payload = textResponse('artifact-third-final', '已读取 HTML 产物,请使用列表中的「Open HTML preview」按钮验证原生预览。');
145
+ }
146
+ else {
147
+ payload = textResponse(`unexpected-${bodies.length}`, '');
148
+ }
149
+ res.end(payload);
150
+ });
151
+ });
152
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
153
+ return {
154
+ port: server.address().port,
155
+ bodies,
156
+ servedTools,
157
+ close: () => new Promise(resolve => server.close(() => resolve())),
158
+ };
159
+ }
160
+ async function run(command, args, options) {
161
+ const result = await execFile(command, args, { cwd: options.cwd, env: options.env, maxBuffer: 16 * 1024 * 1024 });
162
+ return result.stdout;
163
+ }
164
+ function waitForLine(child, pattern, timeoutMs) {
165
+ return new Promise((resolve, reject) => {
166
+ let output = '';
167
+ const timer = setTimeout(() => { reject(new Error(`timed out waiting for ${pattern}; output=${output.slice(-4000)}`)); }, timeoutMs);
168
+ const onData = (chunk) => {
169
+ output += chunk.toString();
170
+ const match = output.match(pattern);
171
+ if (!match)
172
+ return;
173
+ clearTimeout(timer);
174
+ child.stdout.off('data', onData);
175
+ child.stderr.off('data', onData);
176
+ resolve({ url: match[1], output });
177
+ };
178
+ child.stdout.on('data', onData);
179
+ child.stderr.on('data', onData);
180
+ });
181
+ }
182
+ async function main() {
183
+ assert.ok(existsSync(CHROME), `Chrome missing: ${CHROME}`);
184
+ const outputDir = process.env.GOTRY_ARTIFACT_WEB_E2E_OUT || mkdtempSync(join(tmpdir(), 'gotry-artifact-web-evidence-'));
185
+ mkdirSync(outputDir, { recursive: true });
186
+ const packageDir = mkdtempSync(join(tmpdir(), 'gotry-artifact-web-pack-'));
187
+ const consumerDir = mkdtempSync(join(tmpdir(), 'gotry-artifact-web-consumer-'));
188
+ const workspaceDir = mkdtempSync(join(tmpdir(), 'gotry-artifact-web-workspace-'));
189
+ const homeDir = mkdtempSync(join(tmpdir(), 'gotry-artifact-web-home-'));
190
+ const dshHome = mkdtempSync(join(tmpdir(), 'gotry-artifact-web-dsh-'));
191
+ const userDataDir = mkdtempSync(join(tmpdir(), 'gotry-artifact-web-chrome-'));
192
+ const fixture = '# Trip 2027\n\nDay 1: arrival\nDay 2: city walk\nDay 3: museum\nDay 4: rest\nDay 5: return\n';
193
+ writeFileSync(join(workspaceDir, 'trip-2027.md'), fixture, { mode: 0o600 });
194
+ const htmlSentinel = 'gotry-artifact-web-html-sentinel-must-not-run-in-host';
195
+ const htmlFixture = `<!doctype html>
196
+ <html lang="en">
197
+ <head>
198
+ <meta charset="utf-8">
199
+ <title>GoTry artifact-web HTML</title>
200
+ </head>
201
+ <body>
202
+ <h1 id="artifact-web-h1">GoTry artifact-web visible heading</h1>
203
+ <p id="artifact-web-p">GoTry artifact-web visible body line — synthetic only.</p>
204
+ <script>
205
+ window.__artifact_web_host_executed__ = true;
206
+ document.documentElement.setAttribute('data-${htmlSentinel}', 'host-ran-it');
207
+ </script>
208
+ </body>
209
+ </html>
210
+ `;
211
+ writeFileSync(join(workspaceDir, 'trip-2027.html'), htmlFixture, { mode: 0o600 });
212
+ writeFileSync(join(consumerDir, 'package.json'), JSON.stringify({ private: true, dependencies: {} }, null, 2));
213
+ const relay = await startRelay();
214
+ let browser = null;
215
+ let page = null;
216
+ let server = null;
217
+ let passed = false;
218
+ let serverOutput = '';
219
+ const assertions = {
220
+ isolation: { home: homeDir, dshHome, workspace: workspaceDir, userDataDir, liveFlags: { GOTRY_SESSION_LIVE: '0', GOTRY_HBCLI_LIVE: '0', GOTRY_HOTELBYTE_SKILLS_LIVE: '0' } },
221
+ };
222
+ try {
223
+ await run(process.execPath, [join(ROOT, 'scripts/build-dist.mjs')], { cwd: ROOT, env: process.env });
224
+ await run('npm', ['pack', '--ignore-scripts', '--pack-destination', packageDir], { cwd: ROOT, env: process.env });
225
+ const tarball = join(packageDir, readdirSync(packageDir).find(name => name.endsWith('.tgz')) || '');
226
+ assert.ok(existsSync(tarball), 'npm pack did not produce a GoTry tarball');
227
+ const packageJson = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
228
+ await run('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', tarball], { cwd: consumerDir, env: { ...process.env, HOME: homeDir } });
229
+ const installedRoot = join(consumerDir, 'node_modules', '@danceiny', 'gotry');
230
+ const installedBin = join(installedRoot, 'bin', 'gotry-inner.js');
231
+ assert.ok(existsSync(installedBin), 'packed GoTry binary missing');
232
+ assert.ok(existsSync(join(installedRoot, 'client', 'client.js')), 'packed public client missing');
233
+ assert.equal(JSON.parse(readFileSync(join(installedRoot, 'package.json'), 'utf8')).exports['./client'], './client/client.js');
234
+ assertions.package = { version: packageJson.version, tarball: basename(tarball), installedClient: join(installedRoot, 'client', 'client.js') };
235
+ const env = {
236
+ ...process.env,
237
+ HOME: homeDir,
238
+ DSH_HOME: dshHome,
239
+ GOTRY_ONBOARDING_SKIP: '1',
240
+ GOTRY_SETUP_SKIP: '1',
241
+ GOTRY_SESSION_LIVE: '0',
242
+ GOTRY_HBCLI_LIVE: '0',
243
+ GOTRY_HOTELBYTE_SKILLS_LIVE: '0',
244
+ LLM_API_KEY: 'synthetic-artifact-web-key',
245
+ LLM_BASE_URL: `http://127.0.0.1:${relay.port}/v1`,
246
+ LLM_MODEL: 'synthetic-artifact-web',
247
+ };
248
+ const child = spawn(process.execPath, [installedBin, 'web', '--no-open', '--port', '0'], { cwd: workspaceDir, env, stdio: ['ignore', 'pipe', 'pipe'] });
249
+ server = child;
250
+ child.stdout.on('data', chunk => { serverOutput += chunk.toString(); });
251
+ child.stderr.on('data', chunk => { serverOutput += chunk.toString(); });
252
+ const urlResult = await waitForLine(child, /dsh web: (http:\/\/127\.0\.0\.1:\d+\/\?token=[^\s)]+)/, TIMEOUT_MS);
253
+ serverOutput += urlResult.output;
254
+ assertions.server = { authenticatedUrlCaptured: true, urlHost: new URL(urlResult.url).host, port: new URL(urlResult.url).port };
255
+ browser = await puppeteer.launch({ executablePath: CHROME, headless: true, userDataDir, args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'] });
256
+ page = await browser.newPage();
257
+ page.on('console', (message) => { serverOutput += `\n[browser console ${message.type()}] ${message.text()}`; });
258
+ page.on('pageerror', (error) => { serverOutput += `\n[browser pageerror] ${error.stack || error.message}`; });
259
+ page.setDefaultTimeout(30_000);
260
+ await page.goto(urlResult.url, { waitUntil: 'networkidle2', timeout: 30_000 });
261
+ // A fresh DSH profile may show the product's first-run disclosure before
262
+ // the composer is mounted. Dismiss that visible UI gate through its real
263
+ // button; do not seed cookies or attach an existing browser profile.
264
+ await page.evaluate(() => {
265
+ const button = [...document.querySelectorAll('button')].find(node => node.textContent?.trim() === '继续');
266
+ button?.click();
267
+ });
268
+ // Register the isolated fixture through DSH's authenticated public RPC
269
+ // carrier. The browser still selects the resulting workspace through the
270
+ // real workspace picker; no DSH registry file or user profile is seeded.
271
+ const workspaceTitle = basename(workspaceDir);
272
+ const workspaceRpc = await page.evaluate(async (path) => {
273
+ const rpcId = crypto.randomUUID();
274
+ const response = await fetch(`/api/workspace/create`, {
275
+ method: 'POST',
276
+ headers: { 'content-type': 'application/json' },
277
+ body: JSON.stringify({
278
+ type: 'client-request',
279
+ rpcId,
280
+ method: 'workspace/create',
281
+ payload: { args: { request: { path } } },
282
+ }),
283
+ });
284
+ return { status: response.status, body: await response.text() };
285
+ }, workspaceDir);
286
+ assert.equal(workspaceRpc.status, 200, `workspace RPC failed: ${workspaceRpc.status} ${workspaceRpc.body}`);
287
+ const workspaceResult = JSON.parse(workspaceRpc.body);
288
+ assert.equal(workspaceResult.result?.ok, true, `workspace RPC rejected: ${workspaceRpc.body}`);
289
+ assertions.workspace = { rpc: 'workspace/create', title: workspaceResult.result?.value?.workspace?.title || workspaceTitle };
290
+ // Reconnect the public workspace feed so the browser observes the
291
+ // host-authoritative registration through its normal baseline path.
292
+ await page.reload({ waitUntil: 'networkidle2', timeout: 30_000 });
293
+ await page.evaluate(() => {
294
+ const button = [...document.querySelectorAll('button')].find(node => node.textContent?.trim() === '继续');
295
+ button?.click();
296
+ });
297
+ await page.waitForFunction((title) => document.body.innerText.includes(title), { timeout: 30_000 }, workspaceTitle);
298
+ await page.click('button[aria-label="选择工作区"], button[aria-label="Select workspace"]');
299
+ await page.waitForFunction((title) => [...document.querySelectorAll('[role="menuitem"], [role="option"], button')].some(node => node.textContent?.trim() === title), { timeout: 30_000 }, workspaceTitle);
300
+ await page.evaluate((title) => {
301
+ const node = [...document.querySelectorAll('[role="menuitem"], [role="option"], button')].find(candidate => candidate.textContent?.trim() === title);
302
+ node?.click();
303
+ }, workspaceTitle);
304
+ await page.waitForSelector('[contenteditable="true"]');
305
+ const submit = async (text) => {
306
+ const editor = await page.$('[contenteditable="true"]');
307
+ assert.ok(editor, 'real composer missing');
308
+ await editor.click();
309
+ await page.keyboard.type(text);
310
+ const button = await page.$('button[aria-label="发送消息"], button[aria-label="Send message"]');
311
+ assert.ok(button, 'real composer submit button missing');
312
+ await button.click();
313
+ };
314
+ await submit('请列出并打开刚才生成的 trip-2027.md 行程产物。');
315
+ const revealArtifactCalls = async () => {
316
+ await page.evaluate(() => {
317
+ for (const process of document.querySelectorAll('[data-turn-process]')) {
318
+ if (process.getAttribute('aria-expanded') !== 'true')
319
+ process.click();
320
+ }
321
+ });
322
+ };
323
+ await page.waitForFunction(() => document.querySelector('[data-turn-process="1"]') !== null, { timeout: 60_000 });
324
+ await revealArtifactCalls();
325
+ await page.waitForSelector('[data-gotry-artifact-card="list"]');
326
+ await page.waitForSelector('[data-gotry-artifact-card="list"][data-gotry-artifact-state="ok"]');
327
+ await page.waitForSelector('[data-gotry-artifact-card="read"] [data-gotry-artifact-line="1"]');
328
+ assert.equal(await page.$eval('[data-gotry-artifact-card="list"]', (node) => {
329
+ const rect = node.getBoundingClientRect();
330
+ return rect.width > 0 && rect.height > 0;
331
+ }), true, 'artifact list card is not visibly rendered');
332
+ assert.equal(await page.$eval('[data-gotry-artifact-card="read"]', (node) => {
333
+ const rect = node.getBoundingClientRect();
334
+ return rect.width > 0 && rect.height > 0;
335
+ }), true, 'artifact read card is not visibly rendered');
336
+ const pathButton = await page.$('[data-gotry-artifact-path$="trip-2027.md"]');
337
+ assert.ok(pathButton, 'list card path button missing');
338
+ await pathButton.click();
339
+ await page.waitForSelector('[data-gotry-artifact-selection$="trip-2027.md"]');
340
+ const firstSnapshot = await page.evaluate(() => ({
341
+ listCards: document.querySelectorAll('[data-gotry-artifact-card="list"]').length,
342
+ readCards: document.querySelectorAll('[data-gotry-artifact-card="read"]').length,
343
+ paths: [...document.querySelectorAll('[data-gotry-artifact-path]')].map(node => node.textContent),
344
+ selected: document.querySelector('[data-gotry-artifact-selection]')?.textContent || null,
345
+ identity: document.querySelector('[data-gotry-artifact-identity]')?.textContent || null,
346
+ line1: document.querySelector('[data-gotry-artifact-line="1"]')?.textContent || null,
347
+ }));
348
+ assert.equal(firstSnapshot.listCards, 1);
349
+ assert.ok(firstSnapshot.readCards >= 1);
350
+ assert.ok(firstSnapshot.paths.some((path) => path?.endsWith('trip-2027.md')));
351
+ assert.ok(firstSnapshot.selected?.endsWith('trip-2027.md'));
352
+ assert.match(firstSnapshot.identity || '', /来源|source/);
353
+ assertions.firstTurn = firstSnapshot;
354
+ await submit('请修改刚才的行程产物,在末尾追加 Day 6,然后重新读取并展示更新版本。');
355
+ await page.waitForFunction(() => document.querySelector('[data-turn-process="2"]') !== null, { timeout: 60_000 });
356
+ await revealArtifactCalls();
357
+ await page.waitForFunction(() => document.querySelectorAll('[data-gotry-artifact-card="read"]').length >= 2, { timeout: 60_000 });
358
+ await page.waitForFunction(() => [...document.querySelectorAll('[data-gotry-artifact-card="read"]')].some(node => node.textContent?.includes('Day 6: revision requested')), { timeout: 60_000 });
359
+ // The artifact card has the fresh read result, but the public DSH file
360
+ // preview intentionally keeps its old page until the user acknowledges
361
+ // the changed-file banner. Exercise that visible reload control instead
362
+ // of treating the custom card or hidden DOM as sidebar evidence.
363
+ await page.waitForSelector('[data-textpreview-changed]', { timeout: 60_000 });
364
+ const staleRightPreview = await page.$eval('[data-textpreview-state="text"]', (node) => {
365
+ const rect = node.getBoundingClientRect();
366
+ const body = node.querySelector('[data-textpreview-body]');
367
+ return {
368
+ visible: rect.width > 0 && rect.height > 0,
369
+ changedBanner: Boolean(node.querySelector('[data-textpreview-changed]')),
370
+ includesDay6: body?.textContent?.includes('Day 6: revision requested') || false,
371
+ };
372
+ });
373
+ assert.equal(staleRightPreview.visible, true, 'right file preview is not visibly rendered before reload');
374
+ assert.equal(staleRightPreview.changedBanner, true, 'right file preview did not show the changed-file banner');
375
+ assert.equal(staleRightPreview.includesDay6, false, 'right file preview unexpectedly contained Day 6 before reload');
376
+ const reloadButton = await page.$('[data-textpreview-changed] [data-textpreview-reload-now]');
377
+ assert.ok(reloadButton, 'right file preview reload control missing');
378
+ const reloadControl = await page.$eval('[data-textpreview-changed] [data-textpreview-reload-now]', (node) => {
379
+ const rect = node.getBoundingClientRect();
380
+ return {
381
+ visible: rect.width > 0 && rect.height > 0,
382
+ text: node.textContent?.trim() || '',
383
+ ariaLabel: node.getAttribute('aria-label'),
384
+ };
385
+ });
386
+ assert.equal(reloadControl.visible, true, 'right file preview reload control is not visible');
387
+ assert.match(reloadControl.text, /重新载入|Reload/);
388
+ await reloadButton.click();
389
+ await page.waitForFunction(() => {
390
+ const preview = document.querySelector('[data-textpreview-state="text"]');
391
+ const body = preview?.querySelector('[data-textpreview-body]');
392
+ return Boolean(preview && body?.textContent?.includes('Day 6: revision requested'));
393
+ }, { timeout: 60_000 });
394
+ await page.$eval('[data-textpreview-body]', (node) => {
395
+ const body = node;
396
+ body.scrollTop = body.scrollHeight;
397
+ });
398
+ // The right preview pane may sit below the chat fold on a 600-tall
399
+ // viewport; ensure the pane itself is in view, then re-scroll its body so
400
+ // the Day 6 line settles inside the pane's visible area before measuring.
401
+ await page.$eval('[data-textpreview-state="text"]', (node) => { node.scrollIntoView({ block: 'end' }); });
402
+ await page.$eval('[data-textpreview-body]', (node) => {
403
+ const body = node;
404
+ body.scrollTop = body.scrollHeight;
405
+ });
406
+ const rightPreviewAfterReload = await page.$eval('[data-textpreview-state="text"]', (node) => {
407
+ const rect = node.getBoundingClientRect();
408
+ const body = node.querySelector('[data-textpreview-body]');
409
+ // The preview body may render Day 6 as a Markdown paragraph rather than
410
+ // discrete line elements (data-textpreview-line is not always present in
411
+ // the current rendered DOM); find the smallest descendant whose
412
+ // textContent includes the Day 6 marker so visibility is checked against
413
+ // the actual rendered node, preserving the "Day 6 visible after reload"
414
+ // semantic.
415
+ const all = Array.from(node.querySelectorAll('*'));
416
+ let day6 = null;
417
+ let bestLen = Number.POSITIVE_INFINITY;
418
+ for (let i = 0; i < all.length; i++) {
419
+ const candidate = all[i];
420
+ const text = candidate.textContent || '';
421
+ if (!text.includes('Day 6: revision requested'))
422
+ continue;
423
+ if (text.length > bestLen)
424
+ continue;
425
+ bestLen = text.length;
426
+ day6 = candidate;
427
+ }
428
+ const day6Rect = day6 ? day6.getBoundingClientRect() : null;
429
+ const day6Visible = Boolean(day6Rect && day6Rect.width > 0 && day6Rect.height > 0
430
+ && day6Rect.bottom > 0 && day6Rect.top < window.innerHeight
431
+ && day6Rect.bottom <= rect.bottom + 1 && day6Rect.top >= rect.top - 1);
432
+ return {
433
+ visible: rect.width > 0 && rect.height > 0,
434
+ changedBanner: Boolean(node.querySelector('[data-textpreview-changed]')),
435
+ bodyIncludesDay6: body?.textContent?.includes('Day 6: revision requested') || false,
436
+ day6Visible,
437
+ day6Rect: day6Rect ? { top: day6Rect.top, bottom: day6Rect.bottom, height: day6Rect.height, width: day6Rect.width } : null,
438
+ day6Tag: day6 ? day6.tagName : null,
439
+ paneRect: { top: rect.top, bottom: rect.bottom, height: rect.height },
440
+ bodyText: body?.textContent || '',
441
+ };
442
+ });
443
+ assert.equal(rightPreviewAfterReload.visible, true, 'right file preview is not visibly rendered after reload');
444
+ assert.equal(rightPreviewAfterReload.changedBanner, false, 'right file preview still shows the changed-file banner after reload');
445
+ assert.equal(rightPreviewAfterReload.bodyIncludesDay6, true, 'right file preview body does not contain Day 6 after reload');
446
+ assert.equal(rightPreviewAfterReload.day6Visible, true, `right file preview Day 6 is not visible after reload (day6Rect=${JSON.stringify(rightPreviewAfterReload.day6Rect)}; paneRect=${JSON.stringify(rightPreviewAfterReload.paneRect)}; day6Tag=${rightPreviewAfterReload.day6Tag})`);
447
+ // DSH may collapse a completed turn after the last tool result arrives;
448
+ // reopen the real process disclosure and place the revised card in view so
449
+ // the receipt proves visible, not merely hidden-DOM, refreshed evidence.
450
+ await revealArtifactCalls();
451
+ await page.evaluate(() => {
452
+ const cards = document.querySelectorAll('[data-gotry-artifact-card="read"]');
453
+ cards[cards.length - 1]?.scrollIntoView({ block: 'center' });
454
+ });
455
+ assert.equal(await page.$eval('[data-gotry-artifact-card="read"]:last-of-type', (node) => {
456
+ const rect = node.getBoundingClientRect();
457
+ return rect.width > 0 && rect.height > 0;
458
+ }), true, 'updated artifact read card is not visibly rendered');
459
+ const finalSnapshot = await page.evaluate(() => ({
460
+ readCards: document.querySelectorAll('[data-gotry-artifact-card="read"]').length,
461
+ updated: [...document.querySelectorAll('[data-gotry-artifact-card="read"]')].some(node => node.textContent?.includes('Day 6: revision requested')),
462
+ versions: [...document.querySelectorAll('[data-gotry-artifact-version]')].map(node => node.getAttribute('data-gotry-artifact-version')),
463
+ identities: [...document.querySelectorAll('[data-gotry-artifact-identity]')].map(node => node.textContent),
464
+ updatedLine: [...document.querySelectorAll('[data-gotry-artifact-line]')].find(node => node.textContent?.includes('Day 6'))?.textContent || null,
465
+ }));
466
+ assert.ok(finalSnapshot.readCards >= 2);
467
+ assert.equal(finalSnapshot.updated, true);
468
+ assert.ok(finalSnapshot.versions.filter(Boolean).length >= 2);
469
+ assert.ok(finalSnapshot.updatedLine?.includes('Day 6'));
470
+ assertions.secondTurn = { ...finalSnapshot, rightPreview: rightPreviewAfterReload, reloadControl };
471
+ // ─── Third turn — native HTML preview via real registered gotry_artifacts_list/read
472
+ // and the public Client's [data-gotry-artifact-open="html-preview"] button,
473
+ // which dispatches the installed host's native openFile path. The slide-in
474
+ // animation finishes after the iframe mounts; wait for the iframe to be
475
+ // fully inside the actual 1440x1000 viewport (1px rounding tolerance)
476
+ // before reading bounding boxes, so visible-ness is asserted in viewport
477
+ // terms, not against hidden/animating geometry.
478
+ await submit('请用 gotry_artifacts_list 列出当前工作区里 HTML 产物,然后用 gotry_artifacts_read 读取 trip-2027.html 的源码展示给用户。');
479
+ await page.waitForFunction(() => document.querySelector('[data-turn-process="3"]') !== null, { timeout: 60_000 });
480
+ await revealArtifactCalls();
481
+ await page.waitForFunction(() => [...document.querySelectorAll('[data-gotry-artifact-card="read"]')].some(node => node.getAttribute('data-gotry-artifact-source') === 'html'), { timeout: 60_000 });
482
+ const htmlSourceSnapshot = await page.evaluate((sentinel) => {
483
+ const card = document.querySelector('[data-gotry-artifact-card="read"][data-gotry-artifact-source="html"]');
484
+ const lines = [...(card?.querySelectorAll('[data-gotry-artifact-line]') ?? [])].map(node => node.textContent || '');
485
+ const concatenated = lines.join('\n');
486
+ const hostScriptMarker = window['__artifact_web_host_executed__'];
487
+ const hostDataMarker = document.documentElement.getAttribute(`data-${sentinel}`);
488
+ const inertTextOnly = Boolean(card) && card.querySelector('script') === null && card.querySelector('iframe') === null;
489
+ return {
490
+ lineCount: lines.length,
491
+ concatenatedIncludesDoctype: concatenated.includes('<!doctype html>'),
492
+ concatenatedIncludesScriptTag: concatenated.includes('<script>'),
493
+ concatenatedIncludesH1: concatenated.includes('GoTry artifact-web visible heading'),
494
+ hostScriptMarker: hostScriptMarker ?? null,
495
+ hostDataMarker: hostDataMarker ?? null,
496
+ inertTextOnly,
497
+ };
498
+ }, htmlSentinel);
499
+ assert.equal(htmlSourceSnapshot.inertTextOnly, true, 'HTML read card is not inert text');
500
+ assert.equal(htmlSourceSnapshot.concatenatedIncludesDoctype, true, 'HTML read card missing <!doctype html> in text');
501
+ assert.equal(htmlSourceSnapshot.concatenatedIncludesScriptTag, true, 'HTML read card missing <script> as text');
502
+ assert.equal(htmlSourceSnapshot.hostScriptMarker, null, 'host executed the source-text script sentinel');
503
+ assert.equal(htmlSourceSnapshot.hostDataMarker, null, 'host set sentinel data attribute from source text');
504
+ const htmlPathButton = await page.evaluateHandle((canonical) => {
505
+ // Bind to the LATEST list card so the click lands on the third turn's
506
+ // HTML list, not the first turn's collapsed list card that already
507
+ // mentions the same canonical filename. Document-order last list wins.
508
+ const lists = Array.from(document.querySelectorAll('[data-gotry-artifact-card="list"]'));
509
+ const latest = lists[lists.length - 1];
510
+ if (!latest)
511
+ return null;
512
+ const candidate = latest.querySelector(`[data-gotry-artifact-open="html-preview"][aria-label*="${canonical}"]`);
513
+ return candidate;
514
+ }, 'trip-2027.html');
515
+ assert.ok(htmlPathButton, 'real html-preview button for canonical trip-2027.html missing from latest list card');
516
+ const htmlButtonSnapshot = await htmlPathButton.evaluate((node) => ({
517
+ ariaLabel: node.getAttribute('aria-label') || '',
518
+ visible: node.getBoundingClientRect().width > 0 && node.getBoundingClientRect().height > 0,
519
+ listIndex: [...document.querySelectorAll('[data-gotry-artifact-card="list"]')].indexOf(node.closest('[data-gotry-artifact-card="list"]')),
520
+ }));
521
+ assert.ok(htmlButtonSnapshot.ariaLabel.includes('trip-2027.html'), `unexpected html-preview aria-label: ${htmlButtonSnapshot.ariaLabel}`);
522
+ assert.equal(htmlButtonSnapshot.visible, true, 'html-preview button not visibly rendered');
523
+ assert.ok(htmlButtonSnapshot.listIndex >= 1, `html-preview button must bind to a non-first list card (got index ${htmlButtonSnapshot.listIndex})`);
524
+ // Switch to the bounded 1440x1000 viewport only for the HTML native preview
525
+ // so the slide-in animation can settle to a deterministic in-viewport frame.
526
+ await page.setViewport({ width: 1440, height: 1000 });
527
+ const iframesBefore = await page.evaluate(() => [...document.querySelectorAll('iframe')].map(f => ({ src: f.getAttribute('src') || '', sandbox: f.getAttribute('sandbox') || '' })));
528
+ await htmlPathButton.click();
529
+ const blobFrame = await page.waitForFrame((f) => f.url().startsWith('blob:'), { timeout: 30_000 });
530
+ assert.ok(blobFrame, 'no blob: iframe appeared after clicking real Open HTML preview');
531
+ await blobFrame.waitForSelector('#artifact-web-h1', { visible: true, timeout: 30_000 });
532
+ await page.waitForFunction(() => {
533
+ const node = document.querySelector('iframe[src^="blob:"]');
534
+ if (!node)
535
+ return false;
536
+ const r = node.getBoundingClientRect();
537
+ return r.width > 0 && r.height > 0 && r.x >= 0 && r.y >= 0 && r.right <= window.innerWidth + 1 && r.bottom <= window.innerHeight + 1;
538
+ }, { timeout: 30_000 });
539
+ const iframeSnapshot = await page.evaluate(() => {
540
+ const node = document.querySelector('iframe[src^="blob:"]');
541
+ if (!node)
542
+ return null;
543
+ const r = node.getBoundingClientRect();
544
+ return {
545
+ src: node.getAttribute('src') || '',
546
+ sandbox: node.getAttribute('sandbox') || '',
547
+ width: r.width, height: r.height, x: r.x, y: r.y,
548
+ title: node.getAttribute('title') || '',
549
+ };
550
+ });
551
+ assert.ok(iframeSnapshot, 'blob iframe DOM element missing');
552
+ assert.ok(iframeSnapshot.src.startsWith('blob:'), `iframe src is not blob: ${iframeSnapshot.src}`);
553
+ assert.equal(iframeSnapshot.sandbox, 'allow-scripts', `iframe sandbox must be exactly allow-scripts: ${iframeSnapshot.sandbox}`);
554
+ const vp = page.viewport();
555
+ assert.ok(iframeSnapshot.width > 0 && iframeSnapshot.height > 0, 'iframe has zero size');
556
+ assert.ok(iframeSnapshot.x + iframeSnapshot.width <= vp.width + 1 && iframeSnapshot.y + iframeSnapshot.height <= vp.height + 1, 'iframe must fit the actual 1440x1000 viewport');
557
+ const visibleContent = await blobFrame.evaluate(() => {
558
+ const h1 = document.querySelector('h1');
559
+ const p = document.querySelector('p');
560
+ const win = document.defaultView || window;
561
+ const out = { readyState: document.readyState };
562
+ const elems = [['h1', h1], ['p', p]];
563
+ for (let i = 0; i < elems.length; i++) {
564
+ const [key, el] = elems[i];
565
+ if (!el) {
566
+ out[key] = null;
567
+ continue;
568
+ }
569
+ const cs = win.getComputedStyle(el);
570
+ const rect = el.getBoundingClientRect();
571
+ out[key] = {
572
+ text: el.textContent || '',
573
+ id: el.id || '',
574
+ tag: el.tagName,
575
+ rect: { width: rect.width, height: rect.height, top: rect.top, bottom: rect.bottom, left: rect.left, right: rect.right },
576
+ display: cs.display,
577
+ visibility: cs.visibility,
578
+ opacity: Number(cs.opacity),
579
+ };
580
+ }
581
+ return out;
582
+ });
583
+ const iframeHeight = iframeSnapshot.height;
584
+ const iframeWidth = iframeSnapshot.width;
585
+ const isVisuallyShown = (m) => Boolean(m
586
+ && m.rect.width > 0 && m.rect.height > 0
587
+ && m.display !== 'none' && m.visibility !== 'hidden' && m.visibility !== 'collapse'
588
+ && m.opacity > 0
589
+ && m.rect.top >= 0 && m.rect.bottom <= iframeHeight + 1
590
+ && m.rect.left >= 0 && m.rect.right <= iframeWidth + 1);
591
+ assert.ok(visibleContent.h1, 'iframe h1 missing');
592
+ assert.ok(visibleContent.p, 'iframe p missing');
593
+ assert.equal(visibleContent.h1.text, 'GoTry artifact-web visible heading');
594
+ assert.equal(visibleContent.h1.id, 'artifact-web-h1');
595
+ assert.equal(visibleContent.p.text, 'GoTry artifact-web visible body line — synthetic only.');
596
+ assert.equal(isVisuallyShown(visibleContent.h1), true, `iframe h1 not visually shown: ${JSON.stringify(visibleContent.h1)}`);
597
+ assert.equal(isVisuallyShown(visibleContent.p), true, `iframe p not visually shown: ${JSON.stringify(visibleContent.p)}`);
598
+ assert.equal(await page.evaluate(() => window['__artifact_web_host_executed__'] ?? null), null, 'host sentinel mutated during native preview');
599
+ // Scroll the latest (third-turn) source-text read card into the viewport so
600
+ // the page screenshot proves the latest HTML read card is the one visible
601
+ // alongside the right-side native preview — not a stale first-turn MD card.
602
+ await page.evaluate(() => {
603
+ const reads = Array.from(document.querySelectorAll('[data-gotry-artifact-card="read"]'));
604
+ const latest = reads[reads.length - 1];
605
+ latest?.scrollIntoView({ block: 'center' });
606
+ });
607
+ await page.waitForFunction(() => {
608
+ const reads = Array.from(document.querySelectorAll('[data-gotry-artifact-card="read"]'));
609
+ const latest = reads[reads.length - 1];
610
+ if (!latest)
611
+ return false;
612
+ const r = latest.getBoundingClientRect();
613
+ return r.width > 0 && r.height > 0 && r.top < window.innerHeight && r.bottom > 0;
614
+ }, { timeout: 30_000 });
615
+ const htmlScreenshotPath = join(outputDir, 'artifact-web-e2e.native-html.png');
616
+ await page.screenshot({ path: htmlScreenshotPath, fullPage: false });
617
+ const iframeElement = await page.$('iframe[src^="blob:"]');
618
+ const htmlIframeScreenshotPath = join(outputDir, 'artifact-web-e2e.native-html.iframe.png');
619
+ if (iframeElement)
620
+ await iframeElement.screenshot({ path: htmlIframeScreenshotPath });
621
+ assertions.nativeHtml = {
622
+ viewport: vp,
623
+ htmlSourceCard: htmlSourceSnapshot,
624
+ htmlPathButton: htmlButtonSnapshot,
625
+ iframesBeforeClick: iframesBefore,
626
+ iframe: iframeSnapshot,
627
+ iframeContent: visibleContent,
628
+ screenshots: { page: htmlScreenshotPath, iframe: htmlIframeScreenshotPath },
629
+ htmlFixture: { path: join(workspaceDir, 'trip-2027.html'), sentinel: htmlSentinel },
630
+ };
631
+ assert.deepEqual(relay.servedTools, [
632
+ 'gotry_artifacts_list',
633
+ 'gotry_artifacts_read',
634
+ 'read',
635
+ 'edit',
636
+ 'gotry_artifacts_read',
637
+ 'gotry_artifacts_list',
638
+ 'gotry_artifacts_read',
639
+ ]);
640
+ assertions.relay = { toolCalls: relay.servedTools, bodyCount: relay.bodies.length };
641
+ const screenshotPath = join(outputDir, 'artifact-web-e2e.png');
642
+ await page.screenshot({ path: screenshotPath, fullPage: true });
643
+ const domPath = join(outputDir, 'artifact-web-e2e.dom.json');
644
+ writeFileSync(domPath, JSON.stringify(assertions, null, 2) + '\n', { mode: 0o600 });
645
+ const screenshotSha256 = createHash('sha256').update(readFileSync(screenshotPath)).digest('hex');
646
+ const domSha256 = createHash('sha256').update(readFileSync(domPath)).digest('hex');
647
+ const receipt = { screenshot: { path: screenshotPath, sha256: screenshotSha256 }, domAssertions: { path: domPath, sha256: domSha256 }, serverOutputSha256: createHash('sha256').update(serverOutput).digest('hex') };
648
+ writeFileSync(join(outputDir, 'artifact-web-e2e.receipt.json'), JSON.stringify(receipt, null, 2) + '\n', { mode: 0o600 });
649
+ passed = true;
650
+ console.log(`DSH ARTIFACT WEB E2E: PASS fresh Chrome profile, list→select/open→preview→read→edit→updated read; screenshot=${screenshotPath}; screenshotSha256=${screenshotSha256}; dom=${domPath}; domSha256=${domSha256}`);
651
+ }
652
+ finally {
653
+ if (page && !passed) {
654
+ try {
655
+ const failureHtml = join(outputDir, 'artifact-web-e2e.failure.html');
656
+ const failurePng = join(outputDir, 'artifact-web-e2e.failure.png');
657
+ writeFileSync(failureHtml, await page.content(), { mode: 0o600 });
658
+ await page.screenshot({ path: failurePng, fullPage: true });
659
+ writeFileSync(join(outputDir, 'artifact-web-e2e.failure.log'), serverOutput, { mode: 0o600 });
660
+ console.error(`DSH ARTIFACT WEB E2E failure artifacts: html=${failureHtml}; screenshot=${failurePng}; log=${join(outputDir, 'artifact-web-e2e.failure.log')}`);
661
+ }
662
+ catch { /* browser may have failed before a document existed */ }
663
+ }
664
+ await browser?.close().catch(() => { });
665
+ if (server && !server.killed) {
666
+ server.kill('SIGTERM');
667
+ await new Promise(resolve => { server?.once('close', () => resolve()); setTimeout(resolve, 2_000); });
668
+ }
669
+ await relay.close().catch(() => { });
670
+ for (const directory of [packageDir, consumerDir, workspaceDir, homeDir, dshHome, userDataDir]) {
671
+ rmSync(directory, { recursive: true, force: true });
672
+ }
673
+ }
674
+ }
675
+ await main();