@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,924 @@
1
+ /**
2
+ * Local Lavish Editor (`lavish-axi`) session adapter for issue #443, parent #438.
3
+ *
4
+ * Scope: a bounded, explicit lifecycle API (open / poll / reply / end / stop) over
5
+ * the real `lavish-axi` CLI protocol. The adapter owns exactly the process group it
6
+ * spawns, the state directory it creates, and the loopback port it allocated; it
7
+ * never adopts, preempts, or kills a server it did not start, never writes to the
8
+ * artifact, and never calls the CLI's SDK self-upgrade (`update`), `setup hooks`,
9
+ * `setup plugin`, `share`, or `export`.
10
+ *
11
+ * Upstream: lavish-axi@0.1.67 (MIT), commit ca4c59d5b3ef84ae7f6f7f93fcaa415ade8a9c73.
12
+ * CLI stdout is TOON and is decoded with the official `@toon-format/toon` package;
13
+ * there is no `--json` mode. CLI failures also arrive on stdout as `{error, code}`.
14
+ *
15
+ * Trust boundary: everything the CLI prints about user feedback is untrusted data
16
+ * (see `LavishUntrustedFeedback`). It is preserved as bounded data and is never
17
+ * treated as system authority, never logged, and never used to touch the filesystem.
18
+ */
19
+ import { spawn } from 'node:child_process';
20
+ import { createServer } from 'node:net';
21
+ import { mkdir, mkdtemp, readFile, realpath, stat } from 'node:fs/promises';
22
+ import { isAbsolute, join, resolve, sep } from 'node:path';
23
+ import { tmpdir } from 'node:os';
24
+ import { decode } from '@toon-format/toon';
25
+ export const LAVISH_AXI_PACKAGE = 'lavish-axi';
26
+ export const LAVISH_AXI_VERSION = '0.1.67';
27
+ export const LAVISH_AXI_ENTRY = 'dist/cli.mjs';
28
+ export const LAVISH_MIN_NODE_MAJOR = 22;
29
+ export const LOOPBACK_HOST = '127.0.0.1';
30
+ const DENIED_PATH_SEGMENTS = new Set(['.git', 'node_modules']);
31
+ const SESSION_URL_RE = /\/session\/[0-9a-f]{16}/gi;
32
+ const MAX_PACKAGE_MANIFEST_BYTES = 64 * 1024;
33
+ const DEFAULT_MAX_STDOUT_BYTES = 4 * 1024 * 1024;
34
+ const DEFAULT_MAX_STDERR_BYTES = 64 * 1024;
35
+ const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
36
+ const DEFAULT_POLL_TIMEOUT_MS = 60_000;
37
+ const MAX_POLL_TIMEOUT_MS = 600_000;
38
+ const SERVER_START_TIMEOUT_MS = 15_000;
39
+ const SERVER_HEALTH_INTERVAL_MS = 100;
40
+ const KILL_GRACE_MS = 2_000;
41
+ const MAX_PROMPTS = 64;
42
+ const MAX_PROMPT_FIELD_CHARS = 16 * 1024;
43
+ const MAX_PROMPT_FIELD_BYTES = 16 * 1024;
44
+ const MAX_ATTACHMENT_REFS_PER_PROMPT = 16;
45
+ const MAX_ARTIFACT_FAILURES = 16;
46
+ const MAX_DOM_SNAPSHOT_BYTES = 256 * 1024;
47
+ const MAX_DETAIL_CHARS = 512;
48
+ const MAX_REPLY_CHARS = 16_384;
49
+ const MAX_REPLY_BYTES = 24 * 1024;
50
+ /**
51
+ * Bounded local session over one owned `lavish-axi server` child process.
52
+ *
53
+ * One instance == one state directory + one loopback port + one owned process group.
54
+ * Every method performs at most one CLI invocation; the adapter never retries.
55
+ */
56
+ export class LavishLocalSession {
57
+ options;
58
+ server = null;
59
+ serverDead = false;
60
+ sessionOpen = false;
61
+ userEnded = false;
62
+ pollOutcomeUnknown = false;
63
+ closed = false;
64
+ commandTail = Promise.resolve();
65
+ stopInFlight = null;
66
+ /** Preserved so failed or incomplete cleanup can never be masked by a later success. */
67
+ unconfirmedStop = null;
68
+ constructor(options) {
69
+ this.options = options;
70
+ }
71
+ static async create(options) {
72
+ return new LavishLocalSession(await resolveOptions(options));
73
+ }
74
+ state() {
75
+ return {
76
+ port: this.options.port,
77
+ stateDir: this.options.stateDir,
78
+ sessionOpen: this.sessionOpen,
79
+ userEnded: this.userEnded,
80
+ pollOutcomeUnknown: this.pollOutcomeUnknown,
81
+ closed: this.closed,
82
+ };
83
+ }
84
+ /** Opens (or re-attaches to) the review session for an accepted HTML artifact path. */
85
+ async open(artifactPath) {
86
+ return this.serializeCommand(() => this.openSerial(artifactPath));
87
+ }
88
+ async openSerial(artifactPath) {
89
+ const guard = this.guardNewCommand();
90
+ if (guard)
91
+ return guard;
92
+ if (this.userEnded) {
93
+ return fail('session-user-ended', 'the user ended this session; the adapter never reopens it');
94
+ }
95
+ const resolved = await this.resolveArtifact(artifactPath);
96
+ if ('code' in resolved)
97
+ return resolved;
98
+ const ready = await this.ensureServer();
99
+ if ('code' in ready)
100
+ return ready;
101
+ const run = await this.runCli(['open', resolved.file, '--no-open']);
102
+ if ('code' in run)
103
+ return run;
104
+ const decoded = readCliBody(run);
105
+ if ('code' in decoded)
106
+ return decoded;
107
+ const body = decoded.value;
108
+ const session = readRecord(body.session);
109
+ if (!session)
110
+ return fail('malformed-output', 'open response has no session object');
111
+ if (session.file !== resolved.file) {
112
+ return fail('malformed-output', 'open response reported a different artifact path');
113
+ }
114
+ const status = readString(session.status);
115
+ if (status !== 'opened' && status !== 'user-ended') {
116
+ return fail('unexpected-status', `unsupported open status ${quote(status)}`);
117
+ }
118
+ const url = readString(session.url);
119
+ if (!url || !isLoopbackSessionUrl(url, this.options.port)) {
120
+ return fail('malformed-output', 'open response carried no loopback session url for this port');
121
+ }
122
+ this.sessionOpen = status === 'opened';
123
+ this.userEnded = status === 'user-ended';
124
+ const warning = readString(body.self_paint_warning);
125
+ return {
126
+ ok: true,
127
+ kind: 'open',
128
+ file: resolved.file,
129
+ url,
130
+ status,
131
+ ...(warning ? { selfPaintWarning: bound(warning, MAX_PROMPT_FIELD_CHARS) } : {}),
132
+ };
133
+ }
134
+ /**
135
+ * Waits up to `timeoutMs` for feedback, an end, or a browser disconnect.
136
+ * A `waiting` result consumed nothing and is safe to poll again.
137
+ */
138
+ async poll(artifactPath, options = {}) {
139
+ return this.serializeCommand(() => this.pollLike('poll', artifactPath, undefined, options));
140
+ }
141
+ /**
142
+ * Answers feedback and waits for the next state in the same CLI invocation,
143
+ * mirroring the protocol's `poll <file> --agent-reply <text>`.
144
+ */
145
+ async reply(artifactPath, text, options = {}) {
146
+ if (typeof text !== 'string' || text.trim().length === 0) {
147
+ return fail('invalid-reply', 'reply text must be a non-empty string');
148
+ }
149
+ if (text === '--') {
150
+ return fail('invalid-reply', 'reply text must not be the bare flag terminator');
151
+ }
152
+ // Bounded before any argv or spawn: `shell: false` prevents interpretation, not resource use.
153
+ if (text.length > MAX_REPLY_CHARS) {
154
+ return fail('invalid-reply', `reply text must not exceed ${MAX_REPLY_CHARS} characters`);
155
+ }
156
+ if (Buffer.byteLength(text, 'utf8') > MAX_REPLY_BYTES) {
157
+ return fail('invalid-reply', `reply text must not exceed ${MAX_REPLY_BYTES} bytes`);
158
+ }
159
+ return this.serializeCommand(() => this.pollLike('reply', artifactPath, text, options));
160
+ }
161
+ /** Ends the session. The owned server may exit on its own right after this. */
162
+ async end(artifactPath) {
163
+ return this.serializeCommand(() => this.endSerial(artifactPath));
164
+ }
165
+ async endSerial(artifactPath) {
166
+ const guard = this.guardNewCommand();
167
+ if (guard)
168
+ return guard;
169
+ const resolved = await this.resolveArtifact(artifactPath);
170
+ if ('code' in resolved)
171
+ return resolved;
172
+ const ready = await this.ensureServer();
173
+ if ('code' in ready)
174
+ return ready;
175
+ const run = await this.runCli(['end', resolved.file]);
176
+ if ('code' in run)
177
+ return run;
178
+ const decoded = readCliBody(run);
179
+ if ('code' in decoded)
180
+ return decoded;
181
+ const body = decoded.value;
182
+ const session = readRecord(body.session);
183
+ const status = session ? readString(session.status) : null;
184
+ if (status !== 'ended') {
185
+ return fail('unexpected-status', `unsupported end status ${quote(status)}`);
186
+ }
187
+ this.sessionOpen = false;
188
+ this.userEnded = false;
189
+ return { ok: true, kind: 'end', file: resolved.file, status: 'ended' };
190
+ }
191
+ /**
192
+ * Reaps only the server this instance started (process group SIGTERM, then
193
+ * SIGKILL, then a bounded wait). Idempotent for the success case: a server
194
+ * that already exited on its own yields `not-running`. A port held by anything
195
+ * else is reported and the failure is preserved across subsequent calls so
196
+ * later stops can never turn an unproven failure into success. A port held by
197
+ * anything else is never touched.
198
+ */
199
+ async stop() {
200
+ if (this.unconfirmedStop !== null)
201
+ return this.unconfirmedStop;
202
+ if (this.stopInFlight !== null)
203
+ return this.stopInFlight;
204
+ if (this.closed)
205
+ return { ok: true, kind: 'stop', status: 'not-running', ownedServerExited: true };
206
+ // Refuse queued commands immediately, then reap after the current owned command settles.
207
+ this.closed = true;
208
+ this.sessionOpen = false;
209
+ const pending = this.commandTail.then(() => this.stopOwnedServer());
210
+ this.stopInFlight = pending;
211
+ this.commandTail = pending.then(() => undefined, () => undefined);
212
+ try {
213
+ return await pending;
214
+ }
215
+ finally {
216
+ if (this.stopInFlight === pending)
217
+ this.stopInFlight = null;
218
+ }
219
+ }
220
+ async stopOwnedServer() {
221
+ const child = this.server;
222
+ this.server = null;
223
+ if (!child)
224
+ return { ok: true, kind: 'stop', status: 'not-running', ownedServerExited: true };
225
+ const alreadyExited = this.serverDead || child.exitCode !== null || child.signalCode !== null;
226
+ if (!alreadyExited)
227
+ await terminateOwnedGroup(child);
228
+ const exited = child.exitCode !== null || child.signalCode !== null;
229
+ if (await this.portIsFree()) {
230
+ return { ok: true, kind: 'stop', status: alreadyExited ? 'not-running' : 'stopped', ownedServerExited: true };
231
+ }
232
+ if (!exited) {
233
+ const incomplete = { ok: true, kind: 'stop', status: 'stopping', ownedServerExited: false };
234
+ this.unconfirmedStop = incomplete;
235
+ return incomplete;
236
+ }
237
+ const failure = fail('port-occupied', `loopback port ${this.options.port} is still held by a process this adapter does not own`);
238
+ this.unconfirmedStop = failure;
239
+ return failure;
240
+ }
241
+ serializeCommand(command) {
242
+ const result = this.commandTail.then(command);
243
+ this.commandTail = result.then(() => undefined, () => undefined);
244
+ return result;
245
+ }
246
+ async pollLike(kind, artifactPath, replyText, options) {
247
+ const guard = this.guardNewCommand();
248
+ if (guard)
249
+ return guard;
250
+ if (this.pollOutcomeUnknown) {
251
+ return fail('poll-outcome-unknown', 'the previous poll ended without a delivered outcome; polling again could double-consume feedback');
252
+ }
253
+ const timeoutMs = this.normalizePollTimeout(options.timeoutMs);
254
+ if ('code' in timeoutMs)
255
+ return timeoutMs;
256
+ const resolved = await this.resolveArtifact(artifactPath);
257
+ if ('code' in resolved)
258
+ return resolved;
259
+ const ready = await this.ensureServer();
260
+ if ('code' in ready)
261
+ return ready;
262
+ const argv = ['poll', resolved.file];
263
+ if (replyText !== undefined)
264
+ argv.push('--agent-reply', replyText);
265
+ argv.push('--timeout-ms', String(timeoutMs.value));
266
+ const run = await this.runCli(argv, timeoutMs.value + this.options.commandTimeoutMs);
267
+ if ('code' in run) {
268
+ // A killed long-poll may or may not have consumed feedback; never retry silently.
269
+ // A spawn failure is the one case where the CLI never ran, so nothing was consumed.
270
+ if (run.code !== 'command-failed')
271
+ this.pollOutcomeUnknown = true;
272
+ return run;
273
+ }
274
+ const body = readCliBody(run);
275
+ if ('code' in body) {
276
+ this.pollOutcomeUnknown = true;
277
+ return body;
278
+ }
279
+ const session = readRecord(body.value.session);
280
+ if (!session) {
281
+ // A zero-exit response we cannot read may still have consumed a delivery.
282
+ this.pollOutcomeUnknown = true;
283
+ return fail('malformed-output', 'poll response has no session object');
284
+ }
285
+ const raw = readString(session.status);
286
+ const status = normalizePollStatus(raw);
287
+ if (!status) {
288
+ this.pollOutcomeUnknown = true;
289
+ return fail('unexpected-status', `unsupported poll status ${quote(raw)}`);
290
+ }
291
+ const endedBy = readString(session.ended_by) ?? undefined;
292
+ if (endedBy === 'user' && (status === 'ended' || session.session_ended === true)) {
293
+ this.userEnded = true;
294
+ }
295
+ if (status === 'ended')
296
+ this.sessionOpen = false;
297
+ return {
298
+ ok: true,
299
+ kind,
300
+ file: resolved.file,
301
+ status,
302
+ sessionEnded: session.session_ended === true,
303
+ ...(endedBy ? { endedBy } : {}),
304
+ feedback: projectFeedback(body.value),
305
+ };
306
+ }
307
+ guardNewCommand() {
308
+ if (this.closed)
309
+ return fail('instance-closed', 'this session was stopped; create a new instance');
310
+ return null;
311
+ }
312
+ normalizePollTimeout(value) {
313
+ const requested = value === undefined ? this.options.defaultPollTimeoutMs : value;
314
+ if (typeof requested !== 'number' || !Number.isInteger(requested) || requested <= 0) {
315
+ return fail('invalid-options', 'timeoutMs must be a positive integer');
316
+ }
317
+ if (requested > this.options.maxPollTimeoutMs) {
318
+ return fail('invalid-options', `timeoutMs must not exceed ${this.options.maxPollTimeoutMs}`);
319
+ }
320
+ return { value: requested };
321
+ }
322
+ async resolveArtifact(input) {
323
+ return resolveArtifactPath(input, this.options.cwd, this.options.allowedRoot);
324
+ }
325
+ async runCli(argv, timeoutMs = this.options.commandTimeoutMs) {
326
+ return runOwnedProcess(process.execPath, [this.options.cli.entry, ...argv], {
327
+ cwd: this.options.cwd,
328
+ env: this.childEnv(),
329
+ timeoutMs,
330
+ maxStdoutBytes: this.options.maxStdoutBytes,
331
+ maxStderrBytes: this.options.maxStderrBytes,
332
+ });
333
+ }
334
+ childEnv() {
335
+ return buildChildEnvironment(this.options.stateDir, this.options.port);
336
+ }
337
+ async ensureServer() {
338
+ if (this.ownedServerLive()) {
339
+ const health = await this.readHealth();
340
+ if (health === 'ours')
341
+ return { ok: true };
342
+ if (health === 'foreign') {
343
+ return fail('port-occupied', `loopback port ${this.options.port} answered but is not this adapter's server`);
344
+ }
345
+ return fail('server-unavailable', 'the owned Lavish server stopped answering; create a new instance');
346
+ }
347
+ const probe = await this.readHealth();
348
+ if (probe !== 'free') {
349
+ return fail('port-occupied', `loopback port ${this.options.port} was already in use; this adapter never adopts it`);
350
+ }
351
+ const child = spawn(process.execPath, [this.options.cli.entry, 'server', '--port', String(this.options.port)], {
352
+ cwd: this.options.cwd,
353
+ env: this.childEnv(),
354
+ shell: false,
355
+ detached: true,
356
+ stdio: ['ignore', 'pipe', 'pipe'],
357
+ });
358
+ child.stdout?.resume();
359
+ child.stderr?.resume();
360
+ this.server = child;
361
+ this.serverDead = false;
362
+ child.once('close', () => {
363
+ this.serverDead = true;
364
+ });
365
+ child.once('error', () => {
366
+ this.serverDead = true;
367
+ });
368
+ const deadline = Date.now() + SERVER_START_TIMEOUT_MS;
369
+ while (Date.now() < deadline) {
370
+ if (this.serverDead)
371
+ break;
372
+ if ((await this.readHealth()) === 'ours')
373
+ return { ok: true };
374
+ await delay(SERVER_HEALTH_INTERVAL_MS);
375
+ }
376
+ await terminateOwnedGroup(child);
377
+ this.server = null;
378
+ return fail('server-start-failed', 'the owned Lavish server did not become healthy on the pinned version');
379
+ }
380
+ ownedServerLive() {
381
+ const child = this.server;
382
+ if (!child || this.serverDead)
383
+ return false;
384
+ return child.exitCode === null && child.signalCode === null;
385
+ }
386
+ /**
387
+ * Own-port health probe. Only a live child of this instance plus a matching
388
+ * `app`/`version` payload counts as ours; any other answer is a foreign server
389
+ * this adapter must never adopt, preempt, or shut down.
390
+ */
391
+ async readHealth() {
392
+ const body = await fetchHealth(this.options.port);
393
+ if (!body)
394
+ return 'free';
395
+ if (!this.ownedServerLive())
396
+ return 'foreign';
397
+ if (body.app === LAVISH_AXI_PACKAGE && body.version === this.options.cli.version)
398
+ return 'ours';
399
+ return 'foreign';
400
+ }
401
+ async portIsFree() {
402
+ return (await fetchHealth(this.options.port)) === null;
403
+ }
404
+ }
405
+ async function resolveOptions(options) {
406
+ if (!isPlainObject(options))
407
+ throw invalidOptions('options must be a plain object');
408
+ const nodeMajor = Number(process.versions.node.split('.')[0]);
409
+ if (!Number.isInteger(nodeMajor) || nodeMajor < LAVISH_MIN_NODE_MAJOR) {
410
+ throw new LavishLocalError('node-unsupported', `${LAVISH_AXI_PACKAGE} requires Node >= ${LAVISH_MIN_NODE_MAJOR}`);
411
+ }
412
+ const cwd = await requireDirectory(options.cwd, 'cwd');
413
+ const allowedRoot = options.allowedRoot === undefined ? cwd : await requireDirectory(options.allowedRoot, 'allowedRoot');
414
+ const cli = await readTrustedCli(options.cliPackageRoot);
415
+ const stateDir = options.stateDir === undefined
416
+ ? await mkdtemp(join(tmpdir(), 'gotry-lavish-local-'))
417
+ : await requireDirectory(options.stateDir, 'stateDir');
418
+ await mkdir(join(stateDir, 'home'), { recursive: true });
419
+ const port = options.port === undefined ? await allocateUnusedLoopbackPort() : requirePort(options.port);
420
+ return {
421
+ cli,
422
+ cwd,
423
+ allowedRoot,
424
+ stateDir,
425
+ port,
426
+ defaultPollTimeoutMs: positiveInt(options.defaultPollTimeoutMs, DEFAULT_POLL_TIMEOUT_MS, 'defaultPollTimeoutMs'),
427
+ maxPollTimeoutMs: positiveInt(options.maxPollTimeoutMs, MAX_POLL_TIMEOUT_MS, 'maxPollTimeoutMs'),
428
+ commandTimeoutMs: positiveInt(options.commandTimeoutMs, DEFAULT_COMMAND_TIMEOUT_MS, 'commandTimeoutMs'),
429
+ maxStdoutBytes: positiveInt(options.maxStdoutBytes, DEFAULT_MAX_STDOUT_BYTES, 'maxStdoutBytes'),
430
+ maxStderrBytes: positiveInt(options.maxStderrBytes, DEFAULT_MAX_STDERR_BYTES, 'maxStderrBytes'),
431
+ };
432
+ }
433
+ export class LavishLocalError extends Error {
434
+ code;
435
+ constructor(code, detail) {
436
+ super(detail);
437
+ this.name = 'LavishLocalError';
438
+ this.code = code;
439
+ }
440
+ }
441
+ /** Resolves the trusted CLI: exact package name, exact pinned version, exact entry. */
442
+ export async function readTrustedCli(cliPackageRoot) {
443
+ const packageRoot = await requireDirectory(cliPackageRoot, 'cliPackageRoot');
444
+ const manifestPath = join(packageRoot, 'package.json');
445
+ let raw;
446
+ try {
447
+ const info = await stat(manifestPath);
448
+ if (!info.isFile() || info.size > MAX_PACKAGE_MANIFEST_BYTES) {
449
+ throw new LavishLocalError('cli-not-trusted', 'the lavish-axi package.json is not a bounded regular file');
450
+ }
451
+ raw = await readFile(manifestPath, 'utf8');
452
+ }
453
+ catch (error) {
454
+ if (error instanceof LavishLocalError)
455
+ throw error;
456
+ throw new LavishLocalError('cli-not-trusted', 'no readable package.json in the supplied lavish-axi package root');
457
+ }
458
+ let manifest;
459
+ try {
460
+ manifest = JSON.parse(raw);
461
+ }
462
+ catch {
463
+ throw new LavishLocalError('cli-not-trusted', 'the lavish-axi package.json is not valid JSON');
464
+ }
465
+ if (!isPlainObject(manifest))
466
+ throw new LavishLocalError('cli-not-trusted', 'the lavish-axi package.json is not an object');
467
+ if (manifest.name !== LAVISH_AXI_PACKAGE) {
468
+ throw new LavishLocalError('cli-not-trusted', `expected package ${LAVISH_AXI_PACKAGE}, refused`);
469
+ }
470
+ if (manifest.version !== LAVISH_AXI_VERSION) {
471
+ throw new LavishLocalError('cli-not-trusted', `expected ${LAVISH_AXI_PACKAGE}@${LAVISH_AXI_VERSION}, refused`);
472
+ }
473
+ const bin = isPlainObject(manifest.bin) ? manifest.bin[LAVISH_AXI_PACKAGE] : undefined;
474
+ if (typeof bin !== 'string' || bin !== LAVISH_AXI_ENTRY) {
475
+ throw new LavishLocalError('cli-not-trusted', `expected bin.${LAVISH_AXI_PACKAGE} to be ${LAVISH_AXI_ENTRY}, refused`);
476
+ }
477
+ const entryPath = join(packageRoot, LAVISH_AXI_ENTRY);
478
+ let entry;
479
+ try {
480
+ entry = await realpath(entryPath);
481
+ }
482
+ catch {
483
+ throw new LavishLocalError('cli-not-trusted', `the declared entry ${LAVISH_AXI_ENTRY} is missing`);
484
+ }
485
+ if (!isInside(packageRoot, entry)) {
486
+ throw new LavishLocalError('cli-not-trusted', 'the declared entry escapes the package root');
487
+ }
488
+ const info = await stat(entry);
489
+ if (!info.isFile())
490
+ throw new LavishLocalError('cli-not-trusted', 'the declared entry is not a regular file');
491
+ return { packageRoot, entry, version: LAVISH_AXI_VERSION };
492
+ }
493
+ /**
494
+ * Accepts only `.html`/`.htm` files whose realpath stays inside `allowedRoot`,
495
+ * with `.git`/`node_modules` denied. Relative input resolves against `cwd`. Both
496
+ * roots are canonicalised first and symlinks are resolved before containment, so a
497
+ * symlink pointing outside the root is rejected rather than followed.
498
+ */
499
+ export async function resolveArtifactPath(input, cwd, allowedRoot) {
500
+ if (typeof input !== 'string' || input.length === 0) {
501
+ return fail('path-rejected', 'artifact path must be a non-empty string');
502
+ }
503
+ if (input.includes('\0'))
504
+ return fail('path-rejected', 'artifact path must not contain NUL');
505
+ const lower = input.toLowerCase();
506
+ if (!lower.endsWith('.html') && !lower.endsWith('.htm')) {
507
+ return fail('path-rejected', 'only .html/.htm artifacts are accepted');
508
+ }
509
+ const realCwd = await safeRealpath(cwd);
510
+ const realRoot = await safeRealpath(allowedRoot);
511
+ const absolute = isAbsolute(input) ? resolve(input) : resolve(realCwd, input);
512
+ let file;
513
+ try {
514
+ file = await realpath(absolute);
515
+ }
516
+ catch {
517
+ return fail('path-rejected', 'artifact path does not exist');
518
+ }
519
+ if (!isInside(realRoot, file))
520
+ return fail('path-rejected', 'artifact real path escapes allowedRoot');
521
+ const info = await stat(file);
522
+ if (!info.isFile())
523
+ return fail('path-rejected', 'artifact path is not a regular file');
524
+ const segments = file.slice(realRoot.length).split(sep).filter(Boolean);
525
+ for (const segment of segments) {
526
+ if (DENIED_PATH_SEGMENTS.has(segment)) {
527
+ return fail('path-rejected', `artifact path must not pass through ${segment}`);
528
+ }
529
+ }
530
+ return { ok: true, file };
531
+ }
532
+ async function safeRealpath(path) {
533
+ try {
534
+ return await realpath(path);
535
+ }
536
+ catch {
537
+ return resolve(path);
538
+ }
539
+ }
540
+ /**
541
+ * Minimal, explicit child environment. Nothing is inherited except PATH, so no
542
+ * ambient `LAVISH_AXI_*` (host/telemetry/state overrides) or Tailscale variables
543
+ * can reach the child. `HOME` points at the owned state directory so no global
544
+ * `~/.lavish-axi` configuration or hook file can be read or written.
545
+ */
546
+ export function buildChildEnvironment(stateDir, port) {
547
+ return {
548
+ PATH: process.env.PATH ?? '/usr/bin:/bin:/usr/sbin:/sbin',
549
+ HOME: join(stateDir, 'home'),
550
+ LAVISH_AXI_STATE_DIR: stateDir,
551
+ LAVISH_AXI_PORT: String(port),
552
+ LAVISH_AXI_HOST: LOOPBACK_HOST,
553
+ LAVISH_AXI_LINK_HOST: LOOPBACK_HOST,
554
+ LAVISH_AXI_TELEMETRY: '0',
555
+ LAVISH_AXI_NO_OPEN: '1',
556
+ };
557
+ }
558
+ export async function allocateUnusedLoopbackPort() {
559
+ const probe = createServer();
560
+ try {
561
+ await new Promise((resolveListen, rejectListen) => {
562
+ probe.once('error', rejectListen);
563
+ probe.listen(0, LOOPBACK_HOST, resolveListen);
564
+ });
565
+ const address = probe.address();
566
+ if (!address || typeof address === 'string')
567
+ throw new LavishLocalError('invalid-options', 'no loopback port was allocated');
568
+ return address.port;
569
+ }
570
+ finally {
571
+ await new Promise((resolveClose) => probe.close(() => resolveClose()));
572
+ }
573
+ }
574
+ async function fetchHealth(port) {
575
+ try {
576
+ const response = await fetch(`http://${LOOPBACK_HOST}:${port}/health`, {
577
+ signal: AbortSignal.timeout(2_000),
578
+ });
579
+ if (!response.ok)
580
+ return null;
581
+ const body = await response.json();
582
+ return isPlainObject(body) ? body : null;
583
+ }
584
+ catch {
585
+ return null;
586
+ }
587
+ }
588
+ /**
589
+ * Runs one CLI invocation in its own process group and always reaps it before
590
+ * returning. Arguments are passed as an array with `shell: false`; no command
591
+ * string is ever assembled.
592
+ */
593
+ async function runOwnedProcess(command, args, options) {
594
+ let child;
595
+ try {
596
+ child = spawn(command, args, {
597
+ cwd: options.cwd,
598
+ env: options.env,
599
+ shell: false,
600
+ detached: true,
601
+ stdio: ['ignore', 'pipe', 'pipe'],
602
+ });
603
+ }
604
+ catch {
605
+ return fail('command-failed', 'the Lavish CLI could not be started');
606
+ }
607
+ const stdoutChunks = [];
608
+ const stderrChunks = [];
609
+ let stdoutBytes = 0;
610
+ let stderrBytes = 0;
611
+ let outputExceeded = false;
612
+ let timedOut = false;
613
+ const closePromise = new Promise((resolveClose) => {
614
+ child.once('close', () => resolveClose());
615
+ child.once('error', () => resolveClose());
616
+ });
617
+ const kill = (reason) => {
618
+ if (reason === 'timeout')
619
+ timedOut = true;
620
+ else
621
+ outputExceeded = true;
622
+ void terminateOwnedGroup(child);
623
+ };
624
+ const timer = setTimeout(() => kill('timeout'), options.timeoutMs);
625
+ child.stdout?.on('data', (chunk) => {
626
+ stdoutBytes += chunk.length;
627
+ if (stdoutBytes > options.maxStdoutBytes) {
628
+ kill('overflow');
629
+ return;
630
+ }
631
+ stdoutChunks.push(chunk);
632
+ });
633
+ child.stderr?.on('data', (chunk) => {
634
+ stderrBytes += chunk.length;
635
+ if (stderrBytes > options.maxStderrBytes) {
636
+ kill('overflow');
637
+ return;
638
+ }
639
+ stderrChunks.push(chunk);
640
+ });
641
+ await closePromise;
642
+ clearTimeout(timer);
643
+ if (timedOut)
644
+ return fail('command-timeout', 'the Lavish CLI exceeded the adapter deadline');
645
+ if (outputExceeded)
646
+ return fail('output-too-large', 'the Lavish CLI wrote more bytes than the adapter accepts');
647
+ return {
648
+ exitCode: child.exitCode,
649
+ stdout: Buffer.concat(stdoutChunks).toString('utf8'),
650
+ stderr: Buffer.concat(stderrChunks).toString('utf8'),
651
+ timedOut,
652
+ outputExceeded,
653
+ };
654
+ }
655
+ /** SIGTERM to the owned process group, then SIGKILL, then a bounded wait. */
656
+ async function terminateOwnedGroup(child) {
657
+ if (child.exitCode !== null || child.signalCode !== null)
658
+ return;
659
+ signalGroup(child, 'SIGTERM');
660
+ if (await settledWithin(child, KILL_GRACE_MS))
661
+ return;
662
+ signalGroup(child, 'SIGKILL');
663
+ await settledWithin(child, KILL_GRACE_MS);
664
+ }
665
+ function signalGroup(child, signal) {
666
+ const pid = child.pid;
667
+ if (pid !== undefined) {
668
+ try {
669
+ process.kill(-pid, signal);
670
+ return;
671
+ }
672
+ catch {
673
+ // fall through to the direct kill below
674
+ }
675
+ }
676
+ try {
677
+ child.kill(signal);
678
+ }
679
+ catch {
680
+ // the process is already gone
681
+ }
682
+ }
683
+ function settledWithin(child, ms) {
684
+ if (child.exitCode !== null || child.signalCode !== null)
685
+ return Promise.resolve(true);
686
+ return new Promise((resolveSettled) => {
687
+ const timer = setTimeout(() => resolveSettled(false), ms);
688
+ child.once('close', () => {
689
+ clearTimeout(timer);
690
+ resolveSettled(true);
691
+ });
692
+ });
693
+ }
694
+ function decodeCliOutput(stdout) {
695
+ let decoded;
696
+ try {
697
+ decoded = decode(stdout);
698
+ }
699
+ catch {
700
+ return fail('malformed-output', 'the Lavish CLI stdout was not decodable TOON');
701
+ }
702
+ if (!isPlainObject(decoded))
703
+ return fail('malformed-output', 'the Lavish CLI stdout was not a TOON object');
704
+ return { value: decoded };
705
+ }
706
+ /**
707
+ * Reads one CLI result. A structured error object wins over the exit code because
708
+ * it carries the real reason; otherwise a non-zero exit code always fails closed,
709
+ * so well-formed stdout from a failed run is never mistaken for success.
710
+ */
711
+ function readCliBody(run) {
712
+ const decoded = decodeCliOutput(run.stdout);
713
+ if (!('code' in decoded) && 'error' in decoded.value)
714
+ return cliFailure(decoded.value);
715
+ if (run.exitCode !== 0) {
716
+ const exit = run.exitCode === null ? 'no exit code (signalled)' : `exit code ${run.exitCode}`;
717
+ return fail('command-failed', `the Lavish CLI reported ${exit}`);
718
+ }
719
+ return decoded;
720
+ }
721
+ function cliFailure(body) {
722
+ const code = readString(body.code) ?? 'UNKNOWN';
723
+ const message = readString(body.error) ?? 'the Lavish CLI reported an error';
724
+ return fail('command-failed', `${bound(code, 64)}: ${bound(redact(message), MAX_DETAIL_CHARS)}`);
725
+ }
726
+ function projectFeedback(body) {
727
+ const rawPrompts = Array.isArray(body.prompts) ? body.prompts : [];
728
+ const prompts = [];
729
+ let promptsMalformed = 0;
730
+ for (const raw of rawPrompts.slice(0, MAX_PROMPTS)) {
731
+ const record = readRecord(raw);
732
+ if (!record)
733
+ continue;
734
+ const tag = bound(readString(record.tag) ?? '', 64);
735
+ const target = readRecord(record.target);
736
+ const attachments = [];
737
+ if (Array.isArray(record.attachments)) {
738
+ for (const rawRef of record.attachments.slice(0, MAX_ATTACHMENT_REFS_PER_PROMPT)) {
739
+ const ref = readRecord(rawRef);
740
+ const id = ref ? readString(ref.id) : null;
741
+ if (!id)
742
+ continue;
743
+ attachments.push({ id: bound(id, 128), name: bound(ref ? readString(ref.name) ?? '' : '', 256) });
744
+ }
745
+ }
746
+ const textRaw = readString(record.text);
747
+ const text = boundField(textRaw ?? '', MAX_PROMPT_FIELD_CHARS, MAX_PROMPT_FIELD_BYTES);
748
+ const promptProjection = projectUserPrompt(record.prompt);
749
+ if (promptProjection.malformed)
750
+ promptsMalformed += 1;
751
+ prompts.push({
752
+ uid: bound(readString(record.uid) ?? '', 128),
753
+ tag,
754
+ selector: bound(readString(record.selector) ?? '', 512),
755
+ text: text.value,
756
+ textTruncated: text.truncated,
757
+ prompt: promptProjection.value,
758
+ promptTruncated: promptProjection.truncated,
759
+ ...(target && readString(target.type) ? { target: { type: bound(readString(target.type), 64) } } : {}),
760
+ attachments,
761
+ });
762
+ }
763
+ const rawFailures = Array.isArray(body.artifact_failures) ? body.artifact_failures : [];
764
+ const artifactFailures = [];
765
+ for (const raw of rawFailures.slice(0, MAX_ARTIFACT_FAILURES)) {
766
+ const record = readRecord(raw);
767
+ if (!record)
768
+ continue;
769
+ artifactFailures.push({
770
+ kind: bound(readString(record.kind) ?? '', 64),
771
+ detail: bound(readString(record.detail) ?? '', MAX_PROMPT_FIELD_CHARS),
772
+ });
773
+ }
774
+ const rawSnapshot = readString(body.dom_snapshot) ?? '';
775
+ const snapshotBytes = Buffer.byteLength(rawSnapshot, 'utf8');
776
+ const domSnapshot = snapshotBytes > MAX_DOM_SNAPSHOT_BYTES
777
+ ? Buffer.from(rawSnapshot, 'utf8').subarray(0, MAX_DOM_SNAPSHOT_BYTES).toString('utf8')
778
+ : rawSnapshot;
779
+ return {
780
+ trust: 'untrusted',
781
+ prompts,
782
+ promptsTruncated: rawPrompts.length > prompts.length,
783
+ promptsMalformed,
784
+ artifactFailures,
785
+ artifactFailuresTruncated: rawFailures.length > artifactFailures.length,
786
+ domSnapshot,
787
+ domSnapshotTruncated: snapshotBytes > MAX_DOM_SNAPSHOT_BYTES,
788
+ };
789
+ }
790
+ /**
791
+ * Projects the upstream `prompt` field (the user instruction) without ever
792
+ * falling back to the `text` (selected-element context) field. Only a
793
+ * **missing or non-string** `prompt` is reported as malformed; an empty-string
794
+ * `prompt` is a legitimate "no text, attachments only" request and is
795
+ * preserved as data (not substituted, not flagged). Consumers audit the
796
+ * `malformed` flag instead of acting on a value that was substituted for it.
797
+ */
798
+ function projectUserPrompt(raw) {
799
+ if (typeof raw !== 'string')
800
+ return { value: '', truncated: false, malformed: true };
801
+ return { ...boundField(raw, MAX_PROMPT_FIELD_CHARS, MAX_PROMPT_FIELD_BYTES), malformed: false };
802
+ }
803
+ /**
804
+ * Char- and byte-bounded string projection with an explicit truncation flag.
805
+ * Multi-byte UTF-8 sequences are never split: if the byte cap lands inside a
806
+ * sequence, the cut backs up to the start of that sequence.
807
+ */
808
+ function boundField(raw, maxChars, maxBytes) {
809
+ const charClipped = raw.length > maxChars;
810
+ const charBounded = charClipped ? raw.slice(0, maxChars) : raw;
811
+ if (!charClipped && Buffer.byteLength(charBounded, 'utf8') <= maxBytes) {
812
+ return { value: charBounded, truncated: false };
813
+ }
814
+ if (Buffer.byteLength(charBounded, 'utf8') <= maxBytes) {
815
+ return { value: charBounded, truncated: true };
816
+ }
817
+ return { value: truncateUtf8Bytes(charBounded, maxBytes), truncated: true };
818
+ }
819
+ function truncateUtf8Bytes(text, maxBytes) {
820
+ const bytes = Buffer.from(text, 'utf8');
821
+ if (bytes.length <= maxBytes)
822
+ return text;
823
+ let pos = 0;
824
+ while (pos < bytes.length) {
825
+ const byte = bytes[pos];
826
+ if (byte === undefined)
827
+ break;
828
+ const seqLen = (byte & 0x80) === 0 ? 1 :
829
+ (byte & 0xE0) === 0xC0 ? 2 :
830
+ (byte & 0xF0) === 0xE0 ? 3 :
831
+ (byte & 0xF8) === 0xF0 ? 4 : -1;
832
+ if (seqLen < 0)
833
+ break;
834
+ if (pos + seqLen > maxBytes)
835
+ break;
836
+ pos += seqLen;
837
+ }
838
+ return bytes.subarray(0, pos).toString('utf8');
839
+ }
840
+ function normalizePollStatus(raw) {
841
+ if (raw === 'waiting' || raw === 'feedback' || raw === 'ended')
842
+ return raw;
843
+ if (raw === 'browser_disconnected')
844
+ return 'browser-disconnected';
845
+ return null;
846
+ }
847
+ function isLoopbackSessionUrl(url, port) {
848
+ try {
849
+ const parsed = new URL(url);
850
+ return parsed.protocol === 'http:'
851
+ && parsed.hostname === LOOPBACK_HOST
852
+ && parsed.port === String(port)
853
+ && /^\/session\/[0-9a-f]{16}$/.test(parsed.pathname);
854
+ }
855
+ catch {
856
+ return false;
857
+ }
858
+ }
859
+ /** Strips the opaque session access key so no caller can log it by accident. */
860
+ export function redactLavishSessionUrl(text) {
861
+ return text.replace(SESSION_URL_RE, '/session/[redacted]');
862
+ }
863
+ function redact(text) {
864
+ return redactLavishSessionUrl(text);
865
+ }
866
+ function bound(value, maxChars) {
867
+ return value.length > maxChars ? value.slice(0, maxChars) : value;
868
+ }
869
+ function quote(value) {
870
+ return value === null ? 'null' : `${bound(redact(value), 64)}`;
871
+ }
872
+ function fail(code, detail) {
873
+ return { ok: false, kind: 'error', code, detail: bound(redact(detail), MAX_DETAIL_CHARS) };
874
+ }
875
+ function invalidOptions(detail) {
876
+ return new LavishLocalError('invalid-options', detail);
877
+ }
878
+ async function requireDirectory(value, label) {
879
+ if (typeof value !== 'string' || value.length === 0 || !isAbsolute(value)) {
880
+ throw invalidOptions(`${label} must be an absolute path`);
881
+ }
882
+ let resolved;
883
+ try {
884
+ resolved = await realpath(value);
885
+ }
886
+ catch {
887
+ throw invalidOptions(`${label} does not exist`);
888
+ }
889
+ const info = await stat(resolved);
890
+ if (!info.isDirectory())
891
+ throw invalidOptions(`${label} is not a directory`);
892
+ return resolved;
893
+ }
894
+ function requirePort(value) {
895
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 1 || value > 65535) {
896
+ throw invalidOptions('port must be an integer in [1, 65535]');
897
+ }
898
+ return value;
899
+ }
900
+ function positiveInt(value, fallback, label) {
901
+ if (value === undefined)
902
+ return fallback;
903
+ if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
904
+ throw invalidOptions(`${label} must be a positive integer`);
905
+ }
906
+ return value;
907
+ }
908
+ function isInside(root, candidate) {
909
+ if (candidate === root)
910
+ return false;
911
+ return candidate.startsWith(root.endsWith(sep) ? root : root + sep);
912
+ }
913
+ function readRecord(value) {
914
+ return isPlainObject(value) ? value : null;
915
+ }
916
+ function readString(value) {
917
+ return typeof value === 'string' && value.length > 0 ? value : null;
918
+ }
919
+ function isPlainObject(value) {
920
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
921
+ }
922
+ function delay(ms) {
923
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
924
+ }