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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/README.md +172 -217
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1744 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -49
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +135 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +546 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +968 -0
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +509 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +126 -0
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +54 -0
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +50 -0
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +107 -0
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +109 -0
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +686 -0
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +72 -0
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +68 -10
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +67 -0
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +480 -0
  191. package/dist/scripts/ledger-workflow-crash.js +32 -0
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +49 -0
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +29 -31
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +43 -42
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +31 -0
  227. package/dist/scripts/session-attach-poc.js +62 -0
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +60 -0
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +29 -0
  233. package/dist/scripts/session-tests.js +862 -0
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +703 -219
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +261 -0
  253. package/dist/scripts/state-cli.js +585 -0
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +59 -0
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +86 -0
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2305 -731
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +358 -162
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +47 -0
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +767 -0
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +59 -0
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +538 -360
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +296 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +2107 -224
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +278 -40
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +360 -89
  506. package/ts/src/wish-pool.ts +79 -0
@@ -6,31 +6,42 @@
6
6
  * gotry help # help
7
7
  *
8
8
  * 工作流程:
9
- * 1. 解析 argv + .env(provider-neutral → DEEPSEEK_API_KEY)
9
+ * 1. 解析 argv + .env(provider-neutral → DEEPSEEK_API_KEY/DEEPSEEK_BASE_URL)
10
10
  * 2. 定位 dsh runtime:
11
- * a. repo checkout → vendored ts/dsh-runtime/node_modules/...
12
- * b. npm 安装 → createRequire 解析依赖 @deepseek-ai/dsh(不走 npx:
13
- * dsh cordis-loader 在子 cwd 求值 plugin name,必须绝对路径 patch)
11
+ * a. repo checkout / npm 安装 → root package 解析锁定的 @deepseek-ai/dsh
12
+ * b. legacy vendored fallback 已按 D-27 移除(issue #120)——root 缺失即
13
+ * fail-closed 报错指重装,不再有「解析成功却跑不起来」的玄学形态
14
14
  * 3. 运行时生成 patch(os.tmpdir):把 gotry-tools 插件路径重写为按本包
15
15
  * 位置解析的绝对路径 —— 仓内 cordis.gotry-patch.yml 里的 name 行只是
16
16
  * 占位(本机绝对路径),随 tarball 分发后对其他机器必错。
17
17
  */
18
18
 
19
- import { spawn } from 'node:child_process'
20
- import { existsSync, readFileSync, writeFileSync } from 'node:fs'
21
- import { fileURLToPath } from 'node:url'
19
+ import { spawn, spawnSync } from 'node:child_process'
20
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
21
+ import { fileURLToPath, pathToFileURL } from 'node:url'
22
22
  import { dirname, join } from 'node:path'
23
- import { tmpdir } from 'node:os'
23
+ import { homedir, tmpdir } from 'node:os'
24
24
  import { createRequire } from 'node:module'
25
+ import {
26
+ benchmarkRuntimeSupported,
27
+ resolveDshPackage,
28
+ selectDshCwd,
29
+ selectDshRuntime,
30
+ supportsNodeVersion,
31
+ } from './gotry-runtime-resolution.js'
32
+ import { onboardingSkipReason, orchestrateWebLaunch } from './gotry-bootstrap.js'
33
+ import { spawnOwnedChild, terminateOwnedChild } from './gotry-process-liveness.js'
25
34
 
26
35
  const here = dirname(fileURLToPath(import.meta.url))
27
36
  const repoRoot = join(here, '..')
37
+ const sourceCheckoutMode = existsSync(join(repoRoot, '.git'))
28
38
  const require_ = createRequire(import.meta.url)
39
+ const rootRequire = createRequire(join(repoRoot, 'package.json'))
29
40
 
30
41
  // --- 环境 .env 加载(provider-neutral) ---
31
42
  // npm 安装模式优先读用户当前目录的 .env(包目录内不该有凭证);repo 检出读仓根。
32
- const vendoredDshEarly = existsSync(join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js'))
33
- const envCandidates = vendoredDshEarly
43
+ const sourceDshEarly = resolveDshPackage(rootRequire)
44
+ const envCandidates = sourceCheckoutMode || sourceDshEarly
34
45
  ? [join(repoRoot, '.env')]
35
46
  : [join(process.cwd(), '.env'), join(repoRoot, '.env')]
36
47
  const envLines = []
@@ -46,6 +57,22 @@ for (const line of envLines) {
46
57
  if (process.env.LLM_API_KEY && !process.env.DEEPSEEK_API_KEY) {
47
58
  process.env.DEEPSEEK_API_KEY = process.env.LLM_API_KEY
48
59
  }
60
+ // base 同点映射(issue #48):dsh 侧 llm-deepseek 读 DEEPSEEK_BASE_URL 并拼
61
+ // `${base}/chat/completions`(与 ts/src/dsh-llm.ts 同语义,自定义端点一般含 /v1);
62
+ // 只映射 key 不映射 base 时,OpenAI 兼容 key 会被发往 DeepSeek 官方端点必然 401。
63
+ if (process.env.LLM_BASE_URL && !process.env.DEEPSEEK_BASE_URL) {
64
+ process.env.DEEPSEEK_BASE_URL = process.env.LLM_BASE_URL
65
+ }
66
+ // model 映射(issue #77):LLM_MODEL 显式存在时让它真正驱动 dsh 会话面——
67
+ // ① 这里映射为 GOTRY_LLM_MODEL 传给 gotry-tools 插件,在 agent/request 瀑布做
68
+ // 内存覆盖(dsh settings 用户层 ~/.dsh 压过 composition 层,单靠 patch 不够;
69
+ // 覆盖零持久化,进程退即散,不改写用户设置);
70
+ // ② 下方运行时 patch 追加两条 by-id 覆盖:agent-default-model 默认模型 +
71
+ // llm-deepseek 目录条目(web UI 模型页可见、元数据可记账)。
72
+ // 不设 LLM_MODEL 则两条都不动——默认路径(组合配置或用户 web 设置)面不变。
73
+ if (process.env.LLM_MODEL && !process.env.GOTRY_LLM_MODEL) {
74
+ process.env.GOTRY_LLM_MODEL = process.env.LLM_MODEL
75
+ }
49
76
 
50
77
  // --- argv 解析 ---
51
78
  const args = process.argv.slice(2)
@@ -56,53 +83,88 @@ if (help) {
56
83
 
57
84
  Usage:
58
85
  gotry web # dsh Web UI on http://127.0.0.1:3080
86
+ # (interactive TTY) 启动前若发现可自动安装的可选能力,问一次是否配置;
87
+ # 确认复用 doctor --fix 幂等安装,拒绝立即继续。CI/非 TTY/全健康不弹问。
88
+ # 跳过该问: --no-onboarding 或 GOTRY_ONBOARDING_SKIP=1
89
+ gotry setup # 扩展就位检查/指引(商店一键装)
90
+ gotry setup calendar # 可选日历(CalDAV 工作窗口)挂载开关:默认关;--off 关闭;--status 查看
91
+ gotry doctor # 可选依赖体检:扩展/agent-reach/hbcli/flyai/sidebar 状态 + 补装指引
92
+ gotry doctor --fix # 体检 + 按报告补装(hbcli 官方脚本 / agent-reach pip / sidebar 插件)
59
93
  gotry "一段完整任务..." # headless 一问一答
60
94
  gotry help # this help
61
-
62
- Prerequisites:
63
- • Node 22+
64
- • \`.env\` 里 LLM_API_KEY (DeepSeek / OpenAI 兼容均可)
95
+ gotry --version # 版本号(bug 报告必填项)
65
96
 
66
97
  Detail: https://github.com/Danceiny/gotry — README
67
98
  `)
68
99
  process.exit(0)
69
100
  }
70
101
 
71
- // mode 决定路径: 'web'/'help'/'help' 是字面命令;否则第一段 args[0] 是任务本身的一部分
72
- const literal = new Set(['web', 'help', '-h', '--help'])
102
+ // repoRoot 在 npm 安装与源码检出两种形态下都是包根,version 统一从这里取
103
+ if (args[0] === '-v' || args[0] === '--version') {
104
+ console.log(JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf-8')).version)
105
+ process.exit(0)
106
+ }
107
+
108
+ // mode 决定路径: 'web'/'setup'/'doctor'/'help' 是字面命令;否则第一段 args[0] 是任务本身的一部分
109
+ const literal = new Set(['web', 'setup', 'doctor', 'help', '-h', '--help'])
73
110
  const isLiteral = literal.has(args[0])
74
111
  const mode = isLiteral ? args[0] : 'headless'
75
112
  const rest = isLiteral ? args.slice(1) : args
76
113
 
77
- // --- dsh runtime 定位:repo checkout(vendored)优先,npm 安装走依赖解析 ---
78
- const vendoredDsh = join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js')
79
- let dshBin = ''
80
- let dshCwd = ''
81
- if (existsSync(vendoredDsh)) {
82
- dshBin = vendoredDsh
83
- dshCwd = join(repoRoot, 'ts/dsh-runtime')
84
- } else {
85
- try {
86
- dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
87
- dshCwd = process.cwd() // npm 安装:gotry-state 落在用户调用目录
88
- } catch {
89
- dshBin = ''
90
- }
114
+ // setup/doctor:外部依赖自举与体检,不需要 dsh runtime 与 LLM key,同步分发后即退
115
+ if (mode === 'setup' || mode === 'doctor') {
116
+ const r = spawnSync(process.execPath, [join(here, 'gotry-bootstrap.js'), mode, ...rest], { stdio: 'inherit' })
117
+ process.exit(r.status ?? (r.error ? 1 : 0))
91
118
  }
92
119
 
120
+ const benchmarkEnvironmentConfig = process.env.GOTRY_BENCHMARK_ENV_CONFIG
121
+ if (!supportsNodeVersion(process.versions.node)) {
122
+ console.error('[gotry] Node.js 22.15 or newer is required')
123
+ process.exit(1)
124
+ }
125
+
126
+ // --- dsh runtime 定位(D-27 清偿,issue #120):仅 root manifest/依赖解析;legacy vendored 回退已移除 ---
127
+ const installedPackageMode = !sourceCheckoutMode
128
+ const selectedDsh = selectDshRuntime({
129
+ repoRoot,
130
+ rootResolver: rootRequire,
131
+ benchmark: Boolean(benchmarkEnvironmentConfig),
132
+ })
133
+ let dshBin = selectedDsh?.bin ?? ''
134
+ const dshSource = selectedDsh?.source ?? ''
135
+ const dshCwd = selectedDsh
136
+ ? selectDshCwd({
137
+ repoRoot,
138
+ invocationCwd: process.cwd(),
139
+ sourceCheckoutMode,
140
+ benchmark: Boolean(benchmarkEnvironmentConfig),
141
+ })
142
+ : ''
143
+
93
144
  if (!dshBin) {
94
- console.error(`[gotry] 找不到 dsh runtime(既无 vendored ${vendoredDsh},依赖里也没有 @deepseek-ai/dsh)。`)
95
- console.error('repo checkout: cd ts/dsh-runtime && pnpm install;npm 安装: npm install(检查 node_modules)。')
145
+ if (benchmarkEnvironmentConfig) {
146
+ console.error('[gotry] benchmark dsh runtime unavailable')
147
+ process.exit(1)
148
+ }
149
+ console.error('[gotry] 找不到 dsh runtime(依赖里没有 @deepseek-ai/dsh;legacy vendored 回退已按 D-27 移除)。')
150
+ console.error('repo checkout: npm ci && npm --prefix ts ci && node scripts/build-dist.mjs;npm 安装: npm install(检查 node_modules)。')
151
+ process.exit(1)
152
+ }
153
+ if (benchmarkEnvironmentConfig && !benchmarkRuntimeSupported(selectedDsh)) {
154
+ console.error('[gotry] benchmark dsh runtime unavailable')
96
155
  process.exit(1)
97
156
  }
98
157
 
99
158
  // --- 运行时 patch:插件路径按本包位置重写为绝对路径 ---
100
- // npm 模式指向 dist/ 纯 JS(Node 拒绝 strip node_modules 下的 .ts);
101
- // repo 检出指向 .ts 源码(vendored runtime 下天然可行)。
159
+ // 安装包始终指向 dist/ 纯 JS(Node 拒绝 strip node_modules 下的 .ts)。
160
+ // repo 检出仅在显式 tsx loader 可用时走 .ts;否则
161
+ // 使用当前 worktree 已构建的 dist,避免 Node strip-only 拒绝参数属性语法。
102
162
  const distEntry = join(repoRoot, 'dist/src/index.js')
103
163
  const tsEntry = join(repoRoot, 'ts/src/index.ts')
104
- const npmMode = !existsSync(vendoredDsh)
105
- const pluginEntry = npmMode && existsSync(distEntry) ? distEntry : tsEntry
164
+ const tsxLoaderActive = /(?:^|\s)--(?:import|loader)(?:=|\s)(?:"[^"]*tsx[^"]*"|'[^']*tsx[^']*'|\S*tsx\S*)/.test(process.env.NODE_OPTIONS ?? '')
165
+ const sourceTypeScriptMode = !installedPackageMode && tsxLoaderActive
166
+ const pluginEntry = !sourceTypeScriptMode && existsSync(distEntry) ? distEntry : tsEntry
167
+ const distModuleMode = pluginEntry === distEntry
106
168
  const staticPatch = join(repoRoot, 'cordis.gotry-patch.yml')
107
169
  if (!existsSync(staticPatch)) {
108
170
  console.error(`[gotry] 找不到 patch 配置: ${staticPatch}`)
@@ -112,16 +174,242 @@ if (!existsSync(pluginEntry)) {
112
174
  console.error(`[gotry] 找不到插件入口: ${pluginEntry}(包不完整?)`)
113
175
  process.exit(1)
114
176
  }
115
- // dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):repo 用 vendored,
116
- // npm 用依赖解析;都找不到就整块剔除 patch 条目(缺地图不挡旅行规划)
177
+
178
+ // Benchmark mode projects the patch before any optional host plugin is
179
+ // resolved/imported. Non-GoTry (including unknown/future) insert entries are
180
+ // safely discarded; a missing/duplicate gotry-tools entry fails closed.
181
+ function projectBenchmarkPatch(raw, entryPath, configPath) {
182
+ const lines = raw.split('\n')
183
+ const rootInsertKey = /(?:^|[,{}]\s*)(?:insert|'insert'|"insert")\s*:/
184
+ const rootInsertLines = lines
185
+ .map((line, index) => {
186
+ const rootItem = line.match(/^-\s+(.*)$/)?.[1]?.trim()
187
+ const directProperty = line.match(/^ {2}(.*)$/)?.[1]?.trim()
188
+ return rootInsertKey.test(rootItem ?? '') || /^(?:insert|'insert'|"insert")\s*:/.test(directProperty ?? '') ? index : -1
189
+ })
190
+ .filter(index => index >= 0)
191
+ const canonicalInsertStarts = lines
192
+ .map((line, index) => /^- insert:\s*(?:#.*)?$/.test(line) ? index : -1)
193
+ .filter(index => index >= 0)
194
+ if (rootInsertLines.length !== 1 || canonicalInsertStarts.length !== 1 || rootInsertLines[0] !== canonicalInsertStarts[0]) {
195
+ throw new Error('benchmark insert block violation')
196
+ }
197
+ const start = canonicalInsertStarts[0]
198
+ let end = start + 1
199
+ while (end < lines.length) {
200
+ const line = lines[end]
201
+ if (line.trim() !== '' && !/^\s/.test(line) && !line.trim().startsWith('#')) break
202
+ end += 1
203
+ }
204
+ const body = lines.slice(start + 1, end)
205
+ const firstItem = body.find(line => line.trim() !== '' && !line.trim().startsWith('#'))
206
+ const firstItemMatch = firstItem?.match(/^(\s*)-\s+/)
207
+ if (!firstItemMatch) throw new Error('invalid insert sequence')
208
+ const itemIndent = firstItemMatch[1].length
209
+ const itemStarts = []
210
+ for (let index = 0; index < body.length; index += 1) {
211
+ const line = body[index]
212
+ if (line.trim() === '' || line.trim().startsWith('#')) continue
213
+ const indent = line.match(/^\s*/)?.[0].length ?? 0
214
+ if (indent < itemIndent || (indent === itemIndent && !/^\s*-\s+/.test(line))) {
215
+ throw new Error('invalid insert sequence')
216
+ }
217
+ if (indent === itemIndent) itemStarts.push(index)
218
+ }
219
+ if (itemStarts.length === 0) throw new Error('empty insert sequence')
220
+
221
+ const parseId = value => {
222
+ const quoted = value.trim().match(/^(['"])([A-Za-z0-9._-]+)\1(?:\s+#.*)?$/)
223
+ if (quoted) return quoted[2]
224
+ return value.trim().match(/^([A-Za-z0-9._-]+)(?:\s+#.*)?$/)?.[1] ?? null
225
+ }
226
+ const itemId = item => {
227
+ const header = item[0].slice(itemIndent + 2).trim()
228
+ const canonical = header.match(/^id:\s*(.+)$/)
229
+ if (canonical) return parseId(canonical[1])
230
+ if (header.startsWith('{')) {
231
+ const inline = header.match(/(?:^\{\s*|,\s*)id\s*:\s*(['"]?)([A-Za-z0-9._-]+)\1(?=\s*[,}])/)
232
+ if (inline) return inline[2]
233
+ }
234
+ const propertyIndent = ' '.repeat(itemIndent + 2)
235
+ const directIds = item.slice(1)
236
+ .map(line => line.startsWith(`${propertyIndent}id:`) ? parseId(line.slice(propertyIndent.length + 3)) : null)
237
+ .filter(Boolean)
238
+ if (directIds.length > 1) throw new Error('duplicate insert id')
239
+ return directIds[0] ?? null
240
+ }
241
+ const items = itemStarts.map((itemStart, index) => body.slice(itemStart, itemStarts[index + 1] ?? body.length))
242
+ const gotryItems = items.filter(item => itemId(item) === 'gotry-tools')
243
+ if (gotryItems.length !== 1) throw new Error('benchmark insert allowlist violation')
244
+ let gotry = gotryItems[0].join('\n')
245
+ gotry = rewriteBenchmarkPluginEntry(gotry, entryPath)
246
+ gotry = injectBenchmarkEnvironmentConfig(gotry, configPath)
247
+ const projected = [...lines.slice(0, start + 1), ...gotry.split('\n'), ...lines.slice(end)]
248
+ return projectBenchmarkSystemPrompt(projected).join('\n')
249
+ }
250
+
251
+ // The normal persona contains dynamic variables and product-only tool
252
+ // instructions. They are not meaningful in the minimal benchmark kernel and
253
+ // can make dsh fail while assembling the first request. Require the exact
254
+ // source shape before replacing it; malformed or ambiguous prompt config must
255
+ // fail closed rather than being partially interpreted.
256
+ // Target dsh-system-prompt Config exposes only personaPrefix / personaSuffix;
257
+ // the legacy `persona:` key does not project through. Validate against the
258
+ // current personaPrefix key.
259
+ function projectBenchmarkSystemPrompt(lines) {
260
+ const rootStarts = lines.map((line, index) => /^-\s+/.test(line) ? index : -1).filter(index => index >= 0)
261
+ const rootItems = rootStarts.map((itemStart, index) => lines.slice(itemStart, rootStarts[index + 1] ?? lines.length))
262
+ if (rootItems.length !== 2) throw new Error('benchmark system-prompt root shape violation')
263
+ const insertItems = rootItems.filter(item => /^- insert:\s*(?:#.*)?$/.test(item[0]))
264
+ const systemItems = rootItems.filter(item => /^- id:\s*system-prompt\s*(?:#.*)?$/.test(item[0]))
265
+ if (insertItems.length !== 1 || systemItems.length !== 1) throw new Error('benchmark system-prompt canonical shape violation')
266
+ const start = rootStarts[rootItems.indexOf(systemItems[0])]
267
+ let end = start + 1
268
+ while (end < lines.length && (lines[end].trim() === '' || /^\s/.test(lines[end]) || lines[end].trim().startsWith('#'))) end += 1
269
+ const item = lines.slice(start, end)
270
+ const configIndexes = item.map((line, index) => /^ {2}config:\s*$/.test(line) ? index : -1).filter(index => index >= 0)
271
+ const personaIndexes = item.map((line, index) => /^ {4}personaPrefix:\s*>-\s*$/.test(line) ? index : -1).filter(index => index >= 0)
272
+ // Reject any leftover legacy `persona:` field under config: — it would be
273
+ // silently dropped by the target Config and leave the kernel persona-less.
274
+ // The minimal kernel must project through personaPrefix only.
275
+ const legacyPersonaIndexes = item.map((line, index) => /^ {4}persona:\s*>-\s*$/.test(line) ? index : -1).filter(index => index >= 0)
276
+ if (legacyPersonaIndexes.length !== 0) throw new Error('benchmark system-prompt legacy persona key not allowed under target 0.2.0-rc.2')
277
+ if (configIndexes.length !== 1 || personaIndexes.length !== 1 || personaIndexes[0] <= configIndexes[0]) {
278
+ throw new Error('benchmark system-prompt config violation')
279
+ }
280
+ const persona = personaIndexes[0]
281
+ const blockEnd = item.slice(persona + 1).findIndex(line => line.trim() !== '' && !/^\s{6,}/.test(line))
282
+ const contentEnd = blockEnd < 0 ? item.length : persona + 1 + blockEnd
283
+ if (contentEnd === persona + 1 || item.slice(persona + 1, contentEnd).some(line => line.trim() !== '' && !/^\s{6,}/.test(line))) {
284
+ throw new Error('benchmark system-prompt personaPrefix block violation')
285
+ }
286
+ const before = item.slice(1, configIndexes[0]).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
287
+ const between = item.slice(configIndexes[0] + 1, persona).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
288
+ const after = item.slice(contentEnd).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
289
+ if (before.length !== 0 || between.length !== 0 || after.length !== 0) throw new Error('benchmark system-prompt shape violation')
290
+ const replacement = [
291
+ '- id: system-prompt',
292
+ ' config:',
293
+ ' personaPrefix: >-',
294
+ ' You are GoTry, a task-agnostic travel planning assistant.',
295
+ ' Use only the current conversation and tools available in this benchmark session.',
296
+ ]
297
+ return [...lines.slice(0, start), ...replacement, ...lines.slice(end)]
298
+ }
299
+
300
+ function rewriteBenchmarkPluginEntry(raw, entryPath) {
301
+ const itemIndent = raw.match(/^(\s*)-\s+/)?.[1].length
302
+ if (itemIndent === undefined) throw new Error('benchmark plugin item unavailable')
303
+ const propertyIndent = ' '.repeat(itemIndent + 2)
304
+ const anchorPattern = new RegExp(`^(${propertyIndent}name:\\s*)'[^'\\r\\n]*ts\\/src\\/index\\.ts'(\\s*(?:#.*)?)$`, 'gm')
305
+ const anchors = [...raw.matchAll(anchorPattern)]
306
+ if (anchors.length !== 1 || anchors[0].index === undefined) throw new Error('benchmark plugin anchor unavailable')
307
+ const anchor = anchors[0]
308
+ const escapedEntry = entryPath.replace(/'/g, "''")
309
+ const replacement = `${anchor[1]}'${escapedEntry}'${anchor[2]}`
310
+ const rewritten = `${raw.slice(0, anchor.index)}${replacement}${raw.slice(anchor.index + anchor[0].length)}`
311
+ if (!rewritten.includes(replacement)) throw new Error('benchmark plugin rewrite unavailable')
312
+ return rewritten
313
+ }
314
+
315
+ function injectBenchmarkEnvironmentConfig(raw, configPath) {
316
+ const itemIndent = raw.match(/^(\s*)-\s+/)?.[1].length
317
+ if (itemIndent === undefined) throw new Error('benchmark config item unavailable')
318
+ const propertyIndent = ' '.repeat(itemIndent + 2)
319
+ const configIndent = ' '.repeat(itemIndent + 4)
320
+ const configAnchors = raw.match(new RegExp(`^${propertyIndent}config:\\s*(?:#.*)?$`, 'gm')) ?? []
321
+ if (configAnchors.length !== 1) throw new Error('benchmark config block unavailable')
322
+ if (new RegExp(`^${propertyIndent}(?:inject|'inject'|"inject")\\s*:`, 'm').test(raw)) {
323
+ throw new Error('benchmark service injection already declared')
324
+ }
325
+ if ((raw.match(new RegExp(`^${configIndent}benchmarkEnvironmentConfigPath\\s*:`, 'gm')) ?? []).length !== 0) {
326
+ throw new Error('duplicate benchmark config path')
327
+ }
328
+ const anchorPattern = new RegExp(`^(${configIndent})hbcliBin:\\s*'[^'\\r\\n]*'\\s*(?:#.*)?$`, 'gm')
329
+ const anchors = [...raw.matchAll(anchorPattern)]
330
+ if (anchors.length !== 1 || anchors[0].index === undefined) throw new Error('benchmark config anchor unavailable')
331
+ const anchor = anchors[0]
332
+ const escapedPath = configPath.replace(/'/g, "''")
333
+ const insertAt = anchor.index + anchor[0].length
334
+ const injected = `${raw.slice(0, insertAt)}\n${anchor[1]}benchmarkEnvironmentConfigPath: '${escapedPath}'${raw.slice(insertAt)}`
335
+ if ((injected.match(new RegExp(`^${configIndent}benchmarkEnvironmentConfigPath\\s*:`, 'gm')) ?? []).length !== 1) {
336
+ throw new Error('benchmark config injection unavailable')
337
+ }
338
+ // Only benchmark opt-in requires this service. The loader merges this entry
339
+ // dependency with the plugin's tools/systemPrompt dependencies before apply.
340
+ // A one-time ctx.get() without this edge races the parallel profile loader.
341
+ return injected.replace(new RegExp(`^${propertyIndent}config:`, 'm'), `${propertyIndent}inject: [subprocess]\n${propertyIndent}config:`)
342
+ }
343
+
344
+ let benchmarkTerminalConfig = null
345
+ let benchmarkPatchProjection = null
346
+ let benchmarkChildDiagnostics = null
347
+ if (benchmarkEnvironmentConfig !== undefined && benchmarkEnvironmentConfig !== '') {
348
+ let validBenchmarkEnvironmentConfig = false
349
+ try {
350
+ const bridgeModule = distModuleMode && existsSync(join(repoRoot, 'dist/src/benchmark-environment-bridge.js'))
351
+ ? join(repoRoot, 'dist/src/benchmark-environment-bridge.js')
352
+ : join(repoRoot, 'ts/src/benchmark-environment-bridge.ts')
353
+ const { loadBenchmarkEnvironmentConfig } = await import(pathToFileURL(bridgeModule).href)
354
+ const loadedConfig = benchmarkEnvironmentConfig.length > 0
355
+ && benchmarkEnvironmentConfig.length <= 4096
356
+ && !benchmarkEnvironmentConfig.includes('\0')
357
+ && !benchmarkEnvironmentConfig.includes('\r')
358
+ && !benchmarkEnvironmentConfig.includes('\n')
359
+ ? loadBenchmarkEnvironmentConfig(benchmarkEnvironmentConfig)
360
+ : null
361
+ validBenchmarkEnvironmentConfig = loadedConfig !== null
362
+ benchmarkTerminalConfig = loadedConfig?.terminal_output ?? null
363
+ if (validBenchmarkEnvironmentConfig) {
364
+ benchmarkPatchProjection = projectBenchmarkPatch(readFileSync(staticPatch, 'utf-8'), pluginEntry, benchmarkEnvironmentConfig)
365
+ }
366
+ } catch { validBenchmarkEnvironmentConfig = false }
367
+ if (!validBenchmarkEnvironmentConfig) {
368
+ console.error('[gotry] benchmark environment configuration unavailable')
369
+ process.exit(1)
370
+ }
371
+ }
372
+ if (benchmarkEnvironmentConfig && mode !== 'headless') {
373
+ console.error('[gotry] benchmark environment requires headless mode')
374
+ process.exit(1)
375
+ }
376
+ if (benchmarkEnvironmentConfig) {
377
+ try {
378
+ const diagnosticModule = distModuleMode && existsSync(join(repoRoot, 'dist/src/benchmark-headless-child-diagnostics.js'))
379
+ ? join(repoRoot, 'dist/src/benchmark-headless-child-diagnostics.js')
380
+ : join(repoRoot, 'ts/src/benchmark-headless-child-diagnostics.ts')
381
+ benchmarkChildDiagnostics = await import(pathToFileURL(diagnosticModule).href)
382
+ } catch {
383
+ console.error('[gotry] benchmark environment configuration unavailable')
384
+ process.exit(1)
385
+ }
386
+ }
387
+ // dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):tarball 随包 vendor,
388
+ // repo 工作副本走 runtime workspace 链接/依赖解析;都找不到就整块剔除 patch 条目
389
+ // (缺地图不挡旅行规划)。保持 vendored 形态而非 npm 依赖:历史上游 peerDependencies
390
+ // (dsh-settings/dsh-tools >=0.1.2-rc.1)与锁定的 0.1.2-alpha.3 家族在 npm 严格 peer
391
+ // 解析下 ERESOLVE;当前锁定 0.2.0-rc.2 家族;vendored 0.7.3 保留上游开区间
392
+ // peers(>=0.1.2-rc.1),npm 解析与 0.2.0 起的运行时 peer 兼容闸均直接通过,继续以
393
+ // vendored 形态随 tarball 分发并复用同一份七工具接线。
117
394
  let mapEntry = ''
118
- const vendoredMap = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js')
119
- if (existsSync(vendoredMap)) {
120
- mapEntry = vendoredMap
121
- } else {
122
- // npm 布局:子路径可能被 exports 挡(resolve 抛错不能留下旧值),裸包名返回真实入口
123
- try { mapEntry = require_.resolve('dsh-map-tools/lib/index.js') } catch { mapEntry = '' }
124
- if (!mapEntry) { try { mapEntry = require_.resolve('dsh-map-tools') } catch { mapEntry = '' } }
395
+ if (!benchmarkEnvironmentConfig) {
396
+ const vendoredMap = join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js')
397
+ // (随 tarball 分发;source 布局同一份文件)
398
+ const runtimeMap = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js')
399
+ // (ts/dsh-runtime pnpm workspace vendor/* 链接形态)
400
+ if (existsSync(vendoredMap)) {
401
+ mapEntry = vendoredMap
402
+ } else if (existsSync(runtimeMap)) {
403
+ mapEntry = runtimeMap
404
+ } else {
405
+ // npm 布局:子路径可能被 exports 挡(resolve 抛错不能留下旧值),裸包名返回真实入口
406
+ try { mapEntry = require_.resolve('dsh-map-tools/lib/index.js') } catch { mapEntry = '' }
407
+ if (!mapEntry) { try { mapEntry = require_.resolve('dsh-map-tools') } catch { mapEntry = '' } }
408
+ if (!mapEntry) {
409
+ // ts/node_modules 回退(source 模式:dsh-map-tools 安装在 ts/package.json)
410
+ try { mapEntry = require_.resolve(join(repoRoot, 'ts/node_modules/dsh-map-tools')) } catch { mapEntry = '' }
411
+ }
412
+ }
125
413
  }
126
414
  // 结构化澄清卡(T2):ask_user_question 工具 + user-questions 服务,从 dsh 包
127
415
  // 上下文解析(pnpm 嵌套布局下只有 dsh 自己看得见这些依赖);失败整块剔除
@@ -131,7 +419,7 @@ if (existsSync(vendoredMap)) {
131
419
  const stdioAsk = mode === 'headless' && (process.stdin.isTTY || process.env.GOTRY_ASK_STDIO === '1')
132
420
  ? join(here, 'gotry-stdio-ask.js') : ''
133
421
  let askUserInsert = ''
134
- if (mode === 'web' || stdioAsk) try {
422
+ if (!benchmarkEnvironmentConfig && (mode === 'web' || stdioAsk)) try {
135
423
  const reqFromDsh = createRequire(dshBin)
136
424
  // 裸包名解析(这些包的 exports 不暴露 ./lib/index.js 子路径,但裸名返回真实入口)
137
425
  // userQuestions 服务默认树已注册(重复插入会崩),只插工具消费者
@@ -140,75 +428,437 @@ if (mode === 'web' || stdioAsk) try {
140
428
  + ` - id: dsh-tool-ask-user\n name: '${at}'`
141
429
  } catch { /* 缺件不挡启动 */ }
142
430
 
143
- let patchRaw = readFileSync(staticPatch, 'utf-8')
144
- .replace(/(name:\s*)'[^']*ts\/src\/index\.ts'/, `$1'${pluginEntry}'`)
145
- .replace(/^\s*# \{ask-user-insert\}.*\n(\s*# .*\n)?/m, askUserInsert ? askUserInsert + '\n' : '')
431
+ const unboundPatch = benchmarkPatchProjection ?? readFileSync(staticPatch, 'utf-8')
432
+ let patchRaw = benchmarkPatchProjection ?? unboundPatch.replace(/(name:\s*)'[^']*ts\/src\/index\.ts'/, `$1'${pluginEntry}'`)
433
+ patchRaw = patchRaw.replace(/^\s*# \{ask-user-insert\}.*\n(\s*# .*\n)?/m, askUserInsert ? askUserInsert + '\n' : '')
434
+
435
+ // Owner-local benchmark environment opt-in. Keep the path out of tool input
436
+ // and logs; it is only injected into the local plugin config as YAML data.
146
437
  if (mapEntry) {
147
438
  patchRaw = patchRaw.replace(/(name:\s*)'placeholder\/dsh-map-tools'/, `$1'${mapEntry}'`)
148
439
  } else {
149
440
  patchRaw = patchRaw.replace(/\n\s*- id: dsh-map-tools\n\s*name: 'placeholder\/dsh-map-tools'\n/, '\n')
150
441
  }
151
442
 
152
- // dsh-calendar 宿主插件(CalDAV 工作窗口读取;未配置时工具报错降级,不挡启动)
443
+ // dsh-calendar 宿主插件(CalDAV 工作窗口读取)——D-9 拍板(issue #106):默认不挂载。
444
+ // 未配置的日历工具是纯负资产(会话中段才撞「未配置 username」报错),gotry 对它的
445
+ // 唯一诉求(工作窗口)由访谈首轮覆盖。挂载与否由 **setup 状态面**决定(founder
446
+ // 2026-09-03 纠偏:禁止环境变量控制产品行为;可选依赖进 setup 状态管理,与扩展
447
+ // manifest 同居 ~/.gotry)——`npx @danceiny/gotry setup calendar` 写 ~/.gotry/calendar.json,
448
+ // `--off` 删除恢复默认;doctor 报告三态。
449
+ let calEnabled = false
450
+ try {
451
+ calEnabled = JSON.parse(readFileSync(join(homedir(), '.gotry', 'calendar.json'), 'utf-8'))?.enabled === true
452
+ } catch { /* 缺文件/坏文件 = 默认不挂载 */ }
153
453
  let calEntry = ''
154
- const vendoredCal = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-calendar/lib/index.js')
155
- if (existsSync(vendoredCal)) {
156
- calEntry = vendoredCal
157
- } else {
158
- try { calEntry = require_.resolve('dsh-calendar/lib/index.js') } catch { calEntry = '' }
159
- if (!calEntry) { try { calEntry = require_.resolve('dsh-calendar') } catch { calEntry = '' } }
454
+ if (!benchmarkEnvironmentConfig) {
455
+ const vendoredCal = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-calendar/lib/index.js')
456
+ if (existsSync(vendoredCal)) {
457
+ calEntry = vendoredCal
458
+ } else {
459
+ try { calEntry = require_.resolve('dsh-calendar/lib/index.js') } catch { calEntry = '' }
460
+ if (!calEntry) { try { calEntry = require_.resolve('dsh-calendar') } catch { calEntry = '' } }
461
+ }
160
462
  }
161
- if (calEntry) {
463
+ if (calEntry && calEnabled) {
162
464
  patchRaw = patchRaw.replace(/(name:\s*)'placeholder\/dsh-calendar'/, `$1'${calEntry}'`)
163
465
  } else {
164
466
  patchRaw = patchRaw.replace(/\n\s*- id: dsh-calendar\n\s*name: 'placeholder\/dsh-calendar'\n/, '\n')
165
467
  }
166
- const patchPath = join(tmpdir(), `cordis.gotry.${process.pid}.yml`)
167
- writeFileSync(patchPath, patchRaw)
168
- if (!process.env.DEEPSEEK_API_KEY && mode !== 'help') {
169
- console.error('[gotry] 缺少 LLM API key —— 两种方式任选其一后重跑:')
170
- console.error(` 1) 在当前目录创建 .env 写入一行: LLM_API_KEY=<你的 DeepSeek key>(key 从 https://platform.deepseek.com 获取)`)
171
- console.error(' 2) 或临时环境变量: export LLM_API_KEY=<key>')
172
- process.exit(1)
468
+
469
+ // LLM_MODEL 指定模型(issue #77)②:composition 层 by-id 覆盖。cordis patch 语义
470
+ // (dsh-app-boot applyEntryPatches):非 insert 的 {id, ...overrides} 对目标条目做浅层
471
+ // 键赋值,config 整体替换;id 不存在只 warn+skip 不崩(上游改名时优雅退化)。
472
+ // 两条目标在 headless/web profile 均有(dsh-base 声明 llm-deepseek、两 profile 均声明
473
+ // agent-default-model)。llm-deepseek 目录整体替换为单条目:显式指定模型的场景多为
474
+ // 中转/兼容端点,默认 v4-* 目录对其是误导;不硬编码上游 DEFAULT_MODELS 防漂移。
475
+ let llmMaxTokens
476
+ if (process.env.GOTRY_LLM_MODEL) {
477
+ const rawMax = process.env.LLM_MAX_TOKENS
478
+ if (rawMax !== undefined && rawMax !== '') {
479
+ llmMaxTokens = Number.parseInt(rawMax, 10)
480
+ if (!Number.isSafeInteger(llmMaxTokens) || llmMaxTokens <= 0) {
481
+ throw new Error(`invalid LLM_MAX_TOKENS: ${JSON.stringify(rawMax)} (expected a positive safe integer)`)
482
+ }
483
+ }
484
+ const modelYaml = `'${process.env.GOTRY_LLM_MODEL.replace(/'/g, "''")}'`
485
+ // LLM_MAX_TOKENS(Round 9,#100/#102):中转/兼容端点模型常带低于 dsh 未知模型
486
+ // 默认 256K 预算的输出上限(MiniMax 2013 实测);目录条目 maxTokens 元数据是
487
+ // dsh-llm-deepseek defaultMaxTokens 的官方通道(web UI 模型同源)。
488
+ const maxTokensYaml = llmMaxTokens === undefined ? '' : `\n maxTokens: ${llmMaxTokens}`
489
+ patchRaw += `\n# LLM_MODEL 指定模型(issue #77):dsh 会话面默认模型 + 模型目录\n- id: agent-default-model\n config:\n provider: deepseek-official\n model: ${modelYaml}\n- id: llm-deepseek\n config:\n models:\n - id: ${modelYaml}\n name: ${modelYaml}${maxTokensYaml}\n`
490
+ }
491
+ // Keep the patch private and short-lived. mkdtempSync creates a 0700 directory;
492
+ // wx prevents accidental reuse/races if a process is started concurrently.
493
+ const patchDir = mkdtempSync(join(tmpdir(), 'gotry-cordis-'))
494
+ const patchPath = join(patchDir, 'patch.yml')
495
+ writeFileSync(patchPath, patchRaw, { mode: 0o600, flag: 'wx' })
496
+ let patchCleaned = false
497
+ const cleanupPatch = () => {
498
+ if (patchCleaned) return
499
+ patchCleaned = true
500
+ rmSync(patchDir, { recursive: true, force: true })
501
+ }
502
+ process.once('exit', cleanupPatch)
503
+ // 信号转发(issue #267 P1):spawnSync 阻塞 JS,信号处理器无法服务、finally 也跑不到。
504
+ // 故 web-onboarding 子调用改 awaited spawn。SIGINT/SIGTERM 转发在 onboarding **之前**注册一次,
505
+ // 并贯穿后续 dsh web 子进程的整个生命周期——服务管理器只向父进程发 SIGTERM 时,无论当前活跃的是
506
+ // onboarding 子进程组还是 dsh 子,都收到同一信号。收到信号时:终止当前活跃子进程/进程组,
507
+ // 等待 bounded close/reap,清 patch + 私有 result 目录(均幂等),移除本监听器,再向自己重发信号恢复原生终止语义。
508
+ // signalHandling 护栏防递归/重复处理(重发信号不再二次进入本回调)。setup/doctor 的 spawnSync 不在此窗口,保持不变。
509
+ let child = null // dsh web 子进程(launchWeb 后)
510
+ let childGroupPid = null // POSIX dsh child process-group leader
511
+ let onboardingChild = null // onboarding 子进程(运行中时);POSIX 下独立进程组
512
+ let onboardingChildGroupPid = null
513
+ let onboardingResultDir = null // 私有结果目录;信号/正常/错误路径都清
514
+ // outer grace 必须大于 bootstrap installer cleanup budget(2s TERM + 1s SIGKILL wait)。
515
+ // 否则 stubborn installer 忽略 TERM 时,inner 可能先杀 bootstrap,导致 bootstrap 来不及 SIGKILL
516
+ // 它自己 detached 出去的 installer 进程组。
517
+ const ONBOARDING_TERM_GRACE_MS = 5_000
518
+ const ONBOARDING_KILL_WAIT_MS = 1_000
519
+ const cleanupOnboardingResult = () => {
520
+ if (!onboardingResultDir) return
521
+ const d = onboardingResultDir
522
+ onboardingResultDir = null
523
+ try { rmSync(d, { recursive: true, force: true }) } catch { /* ignore */ }
524
+ }
525
+ const onboardingChildTimeoutMs = (() => {
526
+ const raw = process.env.GOTRY_ONBOARDING_CHILD_TIMEOUT_MS
527
+ if (raw === undefined || raw === '') return 300_000
528
+ if (!/^[1-9]\d*$/.test(raw)) return 300_000
529
+ const n = Number(raw)
530
+ return Number.isSafeInteger(n) && n > 0 ? n : 300_000
531
+ })()
532
+ let signalHandling = false
533
+ const signalListeners = new Map()
534
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
535
+ const signalChild = (proc, sig, groupPid = null) => {
536
+ if (!proc) return
537
+ if (process.platform !== 'win32' && groupPid) {
538
+ try { process.kill(-groupPid, sig); return } catch { /* fallback */ }
539
+ }
540
+ try { proc.kill(sig) } catch { /* ignore */ }
541
+ }
542
+ const terminateChildBounded = async (proc, sig, groupPid = null, graceMs = ONBOARDING_TERM_GRACE_MS, killMs = ONBOARDING_KILL_WAIT_MS) => {
543
+ await terminateOwnedChild({ child: proc, groupPid, signal: sig, termGraceMs: graceMs, killWaitMs: killMs })
544
+ }
545
+ const registerSignalForwarding = () => {
546
+ if (signalListeners.size) return
547
+ for (const sig of ['SIGINT', 'SIGTERM']) {
548
+ const listener = () => {
549
+ if (signalHandling) return // 防递归:重发信号不再二次进入
550
+ signalHandling = true
551
+ void (async () => {
552
+ const active = onboardingChild || child
553
+ const groupPid = onboardingChild ? onboardingChildGroupPid : childGroupPid
554
+ cleanupPatch()
555
+ cleanupOnboardingResult()
556
+ await terminateChildBounded(active, sig, groupPid)
557
+ for (const [s, l] of signalListeners) process.off(s, l)
558
+ signalListeners.clear()
559
+ process.kill(process.pid, sig) // 移除监听器后重发 → 原生终止语义
560
+ await sleep(2_000)
561
+ process.exit(128 + (sig === 'SIGINT' ? 2 : 15))
562
+ })()
563
+ }
564
+ process.on(sig, listener)
565
+ signalListeners.set(sig, listener)
566
+ }
173
567
  }
174
568
 
175
- // --- 调 vendored dsh 二进制(不走 npx)---
176
- // headless: 第一个非 -- 之后的参数是 task —— gotry argv 0 是 headless 触发,其余都算 task;
569
+ const unregisterSignalForwarding = () => {
570
+ for (const [s, l] of signalListeners) process.off(s, l)
571
+ signalListeners.clear()
572
+ }
573
+ // LLM key 由 dsh 宿主管理(凭证是用户资产,UI 在 dsh 里;gotry 不拦截启动期,
574
+ // 不在用户面前展示任何 key 配置引导),此处直接放手 spawn dsh。
575
+ const webPortArgs = (() => {
576
+ if (mode !== 'web') return []
577
+ const portIndex = rest.indexOf('--port')
578
+ const port = portIndex >= 0 ? rest[portIndex + 1] : undefined
579
+ return port !== undefined && /^(?:0|[1-9]\d*)$/.test(port) ? ['--port', port] : []
580
+ })()
177
581
  const binJs = mode === 'web'
178
- ? ['web', '--patch', patchPath, ...(process.argv.includes('--no-open') ? ['--no-open'] : [])]
582
+ ? ['web', '--patch', patchPath, ...(process.argv.includes('--no-open') ? ['--no-open'] : []), ...webPortArgs]
179
583
  : ['--profile', 'headless', '--patch', patchPath, ...rest]
180
584
 
181
- const child = spawn(process.execPath, [dshBin, ...binJs], {
182
- stdio: 'inherit',
585
+ const childEnv = { ...process.env }
586
+ // The benchmark config is parent-only metadata. Never make its path visible to
587
+ // dsh or any plugin loaded by the child.
588
+ delete childEnv.GOTRY_BENCHMARK_ENV_CONFIG
589
+ if (benchmarkEnvironmentConfig) {
590
+ for (const key of [
591
+ 'GOTRY_BENCHMARK_BRIDGE_PARENT_SECRET',
592
+ 'DATABASE_URL',
593
+ 'SSH_AUTH_SOCK',
594
+ 'AWS_PROFILE',
595
+ 'HTTPS_PROXY',
596
+ ]) delete childEnv[key]
597
+ }
598
+ const benchmarkStdout = benchmarkEnvironmentConfig ? [] : null
599
+ if (benchmarkEnvironmentConfig) childEnv.GOTRY_BENCHMARK_DIAGNOSTIC_FD = '3'
600
+ const childStdio = mode === 'web'
601
+ ? 'inherit'
602
+ : benchmarkStdout
603
+ ? ['pipe', 'pipe', 'pipe', 'pipe']
604
+ : process.stdin.isTTY
605
+ ? 'inherit'
606
+ : ['pipe', 'inherit', 'inherit']
607
+ let benchmarkCapturedBytes = 0
608
+ let benchmarkDiagnosticBuffer = Buffer.alloc(0)
609
+ let benchmarkOutputTruncated = false
610
+ // issue #258:`gotry web` 启动前的交互式 onboarding——每次符合条件的启动评估一次、至多问一次,
611
+ // 无跨启动持久确认(每次 web 启动独立判定;不写「已问过」标记)。仅交互式 TTY + 有可自动安装
612
+ // 缺项时问一次;y 复用 doctor --fix 幂等安装器,不建第二套;n 立即继续 web)。
613
+ // CI/benchmark/非 TTY/全健康/GOTRY_SETUP_SKIP=1/GOTRY_ONBOARDING_SKIP=1/--no-onboarding
614
+ // 均零 prompt 零安装,仍启 web;永不 postinstall 或后台任务里安装。无 auto 但有需用户操作/
615
+ // 不可用缺项时(如 win32 平台不支持自动安装),onboarding 渲染分类计划与原因、不 prompt 不安装,
616
+ // 标记 reported;orchestrateWebLaunch 据此抑制重复的 detached doctor 摘要(design §3.1③,#114)。
617
+ // 编排走 orchestrateWebLaunch(显式注入依赖的 E2E 边界缝,bootstrap-tests 跨边界单测同款):
618
+ // bootstrap 以 `onboarding --result-file=…` 异步 inherit 跑(awaited spawn,POSIX 下独立进程组——
619
+ // 使 JS 能服务 SIGINT/SIGTERM 并在信号路径清 patch+result 目录、reap installer 子树);result JSON 告知是否 prompt /
620
+ // reported,用以抑制重复的启动摘要行。yes/no/失败/跳过/已 reported 均继续到 launchWeb(spawn dsh web)。
621
+ const onboardingSkip = onboardingSkipReason({
622
+ mode,
623
+ benchmark: Boolean(benchmarkEnvironmentConfig),
183
624
  env: process.env,
184
- cwd: dshCwd,
625
+ argv: process.argv,
626
+ isTTY: process.stdin.isTTY,
185
627
  })
628
+ // 一次性注册信号转发,覆盖 onboarding + dsh child 全生命周期(dsh close/error 时注销)。
629
+ registerSignalForwarding()
630
+ await orchestrateWebLaunch({
631
+ onboardingSkip,
632
+ runOnboarding: async () => {
633
+ // result 通道硬化:不在 os.tmpdir 下放可预测文件名——mkdtempSync 建 0700 私有目录,
634
+ // result.json 放其内;bootstrap 以 mode 0600 + flag wx 写(存在路径/符号链接永不覆盖,
635
+ // 防 symlink 互换攻击)。子调用改 awaited spawn(非 spawnSync)使 JS 能服务信号;
636
+ // 信号/正常/错误路径都清目录(幂等),详见上方 registerSignalForwarding/cleanupOnboardingResult。
637
+ let resultDir = null
638
+ try {
639
+ resultDir = mkdtempSync(join(tmpdir(), 'gotry-onboarding-'))
640
+ onboardingResultDir = resultDir
641
+ const resultFile = join(resultDir, 'result.json')
642
+ // 信号转发在 onboarding 之前注册一次,贯穿后续 dsh web 整个生命周期(见上方 registerSignalForwarding);
643
+ // onboarding 正常结束后不注销——服务管理器只向父进程发信号时,当前活跃子进程(onboarding 或 dsh)均收到。
644
+ registerSignalForwarding()
645
+ const detachedOnboarding = process.platform !== 'win32'
646
+ const oc = spawn(process.execPath, [join(here, 'gotry-bootstrap.js'), 'onboarding', `--result-file=${resultFile}`], {
647
+ stdio: 'inherit',
648
+ detached: detachedOnboarding,
649
+ })
650
+ onboardingChild = oc
651
+ onboardingChildGroupPid = detachedOnboarding ? oc.pid : null
652
+ // onboarding 永不挡 web:保留 spawnSync 原有的 300s 上限(可经 GOTRY_ONBOARDING_CHILD_TIMEOUT_MS 注入以供
653
+ // 确定性测试,生产默认 300_000)。超时即 SIGTERM(2s 后 SIGKILL 兜底),settle 恰好一次于 close/error/timeout;
654
+ // 之后继续 web(prompted:false),不留进程/目录(finally 清 result,信号路径清 patch+result)。
655
+ await new Promise((resolve) => {
656
+ let done = false
657
+ let killTimer = null
658
+ const timer = setTimeout(() => {
659
+ process.stderr.write(`[gotry] onboarding child timeout after ${onboardingChildTimeoutMs}ms; continuing to web\n`)
660
+ signalChild(oc, 'SIGTERM', onboardingChildGroupPid)
661
+ killTimer = setTimeout(() => { if (!done) signalChild(oc, 'SIGKILL', onboardingChildGroupPid) }, ONBOARDING_TERM_GRACE_MS)
662
+ }, onboardingChildTimeoutMs)
663
+ const finish = () => { if (done) return; done = true; clearTimeout(timer); if (killTimer) clearTimeout(killTimer); resolve() }
664
+ oc.once('close', finish)
665
+ oc.once('error', (error) => {
666
+ process.stderr.write(`[gotry] onboarding child spawn failed: ${error.code || error.name || 'error'}; continuing to web\n`)
667
+ finish()
668
+ })
669
+ })
670
+ if (signalHandling) await new Promise(() => {})
671
+ onboardingChild = null
672
+ onboardingChildGroupPid = null
673
+ if (existsSync(resultFile)) {
674
+ try { return JSON.parse(readFileSync(resultFile, 'utf-8')) }
675
+ catch { return { prompted: false } }
676
+ }
677
+ return { prompted: false }
678
+ } catch {
679
+ process.stderr.write('[gotry] onboarding setup failed; continuing to web\n')
680
+ return { prompted: false }
681
+ } finally {
682
+ onboardingChild = null
683
+ onboardingChildGroupPid = null
684
+ if (resultDir) { try { rmSync(resultDir, { recursive: true, force: true }) } catch { /* ignore */ } }
685
+ onboardingResultDir = null
686
+ }
687
+ },
688
+ // 启动一次性 doctor 摘要(issue #114,design §3.1③):分离子进程后台跑只读体检,
689
+ // 有待处理项打一行 stderr(stderr 继承,不污染 stdout;benchmark 面保持零杂音)。
690
+ // onboarding 已 prompt 时 orchestrateWebLaunch 已抑制本回调(不重复)。
691
+ runSummary: () => {
692
+ if (benchmarkEnvironmentConfig) return
693
+ try {
694
+ spawn(process.execPath, [join(here, 'gotry-bootstrap.js'), 'doctor', '--summary'], {
695
+ detached: true,
696
+ stdio: ['ignore', 'ignore', 'inherit'],
697
+ }).unref()
698
+ } catch { /* 体检不可用不挡启动 */ }
699
+ },
700
+ // dsh web 启动边界:launchWeb 即 inner 跨过 onboarding 后到达的 web 启动标记。
701
+ launchWeb: async () => {
702
+ const owned = spawnOwnedChild(process.execPath, [dshBin, ...binJs], {
703
+ stdio: childStdio,
704
+ env: childEnv,
705
+ cwd: dshCwd,
706
+ })
707
+ child = owned.child
708
+ childGroupPid = owned.groupPid
709
+ },
710
+ })
711
+ if (benchmarkStdout) {
712
+ const maxBytes = Math.max(1, Number(benchmarkTerminalConfig?.max_bytes ?? 1))
713
+ child.stdout?.on('data', chunk => {
714
+ if (benchmarkCapturedBytes + Buffer.byteLength(chunk) > maxBytes) benchmarkOutputTruncated = true
715
+ if (benchmarkCapturedBytes < maxBytes) {
716
+ const bytes = Buffer.from(chunk)
717
+ benchmarkStdout.push(bytes.subarray(0, Math.max(0, maxBytes - benchmarkCapturedBytes)))
718
+ benchmarkCapturedBytes += Math.min(bytes.length, maxBytes - benchmarkCapturedBytes)
719
+ }
720
+ })
721
+ child.stderr?.resume()
722
+ child.stdio?.[3]?.on('data', chunk => {
723
+ benchmarkDiagnosticBuffer = benchmarkChildDiagnostics.appendBoundedChildDiagnostic(
724
+ benchmarkDiagnosticBuffer,
725
+ Buffer.from(chunk),
726
+ )
727
+ })
728
+ }
186
729
  if (process.env.GOTRY_DEBUG) {
187
- console.error('[gotry-debug] exec:', process.execPath, dshBin)
188
- console.error('[gotry-debug] argv:', binJs)
189
- console.error('[gotry-debug] cwd:', dshCwd)
190
- console.error('[gotry-debug] patch exists:', existsSync(patchPath), patchPath)
730
+ if (benchmarkEnvironmentConfig) {
731
+ console.error('[gotry-debug] benchmark headless child configured(details redacted)')
732
+ } else {
733
+ console.error('[gotry-debug] exec:', process.execPath, dshBin)
734
+ console.error('[gotry-debug] argv:', binJs)
735
+ console.error('[gotry-debug] cwd:', dshCwd)
736
+ console.error('[gotry-debug] patch exists:', existsSync(patchPath), patchPath)
737
+ }
738
+ }
739
+ let benchmarkFailureReported = false
740
+ const reportBenchmarkFailure = reason => {
741
+ if (benchmarkFailureReported) return
742
+ benchmarkFailureReported = true
743
+ process.exitCode = 1
744
+ process.stderr.write(`[gotry] benchmark terminal output unavailable (${reason})\n`)
745
+ }
746
+
747
+ let childLifecycleHandled = false
748
+ let childCleanupPromise = null
749
+ // `close` may be delayed by a descendant which inherited dsh's stdio pipe.
750
+ // Always start bounded TERM→KILL cleanup from `exit` so the owned group is
751
+ // reaped before any close/parser wait. The benchmark zero-success close path
752
+ // still parses the already-captured stdout buffer unchanged; cleanup runs
753
+ // fire-and-forget in the background and stays bounded by ONBOARDING bounds.
754
+ const startChildCleanup = () => {
755
+ if (childCleanupPromise || !child) return childCleanupPromise
756
+ childCleanupPromise = terminateOwnedChild({
757
+ child,
758
+ groupPid: childGroupPid,
759
+ signal: 'SIGTERM',
760
+ termGraceMs: ONBOARDING_TERM_GRACE_MS,
761
+ killWaitMs: ONBOARDING_KILL_WAIT_MS,
762
+ })
763
+ return childCleanupPromise
191
764
  }
192
765
  child.on('exit', (code, signal) => {
766
+ void startChildCleanup()
767
+ })
768
+ child.on('close', async (code, signal) => {
769
+ if (childLifecycleHandled) return
770
+ childLifecycleHandled = true
771
+ cleanupPatch()
772
+ if (signalHandling) return
773
+ unregisterSignalForwarding()
774
+ // Non-benchmark web/headless children are owned through both normal and
775
+ // anomalous exits. The promise was normally started by `exit`; retaining
776
+ // this close fallback covers runtimes that report close without exit.
777
+ if (!benchmarkStdout || code !== 0 || signal) await startChildCleanup()
778
+ if (benchmarkStdout) {
779
+ const captured = Buffer.concat(benchmarkStdout).toString('utf8')
780
+ if (code !== 0 || signal || benchmarkOutputTruncated) {
781
+ const reason = benchmarkChildDiagnostics.classifyBenchmarkChildFailure({
782
+ code,
783
+ signal,
784
+ diagnostic: benchmarkDiagnosticBuffer.toString('utf8'),
785
+ outputTruncated: benchmarkOutputTruncated,
786
+ })
787
+ reportBenchmarkFailure(reason)
788
+ return
789
+ }
790
+ const parserModule = distModuleMode && existsSync(join(repoRoot, 'dist/src/benchmark-agent-conformance.js'))
791
+ ? join(repoRoot, 'dist/src/benchmark-agent-conformance.js')
792
+ : join(repoRoot, 'ts/src/benchmark-agent-conformance.ts')
793
+ import(pathToFileURL(parserModule).href).then(async ({ parseBenchmarkTerminal }) => {
794
+ const parsed = parseBenchmarkTerminal(captured, benchmarkTerminalConfig)
795
+ if (!parsed || parsed.ok !== true) {
796
+ reportBenchmarkFailure('child_terminal_invalid')
797
+ return
798
+ }
799
+ await new Promise((resolve, reject) => {
800
+ const onError = error => {
801
+ process.stdout.off('error', onError)
802
+ reject(error)
803
+ }
804
+ process.stdout.once('error', onError)
805
+ process.stdout.write(captured, () => {
806
+ process.stdout.off('error', onError)
807
+ resolve()
808
+ })
809
+ })
810
+ process.exitCode = 0
811
+ }).catch(() => {
812
+ reportBenchmarkFailure('child_lifecycle_failure')
813
+ })
814
+ return
815
+ }
193
816
  if (code !== 0 || signal) {
194
817
  // D-NEW 护栏:dsh 异常退出也写一条 incident,留现场而非沉默
195
- // npm 模式 import dist JS(node_modules 下的 .ts 会被 Node 拒 strip)
196
- const incidentModule = npmMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
197
- ? '../dist/capabilities/incident-log.js'
198
- : '../ts/capabilities/incident-log.ts'
199
- import(incidentModule).then(({ recordIncident }) => {
818
+ // dist 模式 import JS(node_modules 下的 .ts 会被 Node 拒 strip)
819
+ const incidentModule = distModuleMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
820
+ ? join(repoRoot, 'dist/capabilities/incident-log.js')
821
+ : join(repoRoot, 'ts/capabilities/incident-log.ts')
822
+ try {
823
+ const { recordIncident } = await import(pathToFileURL(incidentModule).href)
200
824
  recordIncident({
201
825
  ts: new Date().toISOString(),
202
826
  kind: 'plugin_error',
203
827
  message: `gotry spawn exit: ${mode} code=${code} signal=${signal}`,
204
828
  source: 'gotry-cli',
205
- }, repoRoot).catch(() => {})
206
- }).catch(() => {}) // module 找不到时静默
829
+ }, repoRoot)
830
+ } catch { /* diagnostics remain best-effort; fatal exit is preserved */ }
831
+ }
832
+ if (signal) {
833
+ try { process.kill(process.pid, signal) } catch { process.exit(1) }
834
+ return
207
835
  }
208
836
  process.exit(code ?? 1)
209
837
  })
210
- child.on('error', (e) => {
838
+ child.on('error', async (e) => {
839
+ if (childLifecycleHandled) return
840
+ childLifecycleHandled = true
841
+ cleanupPatch()
842
+ if (signalHandling) return
843
+ unregisterSignalForwarding()
844
+ await startChildCleanup()
845
+ try {
846
+ const incidentModule = distModuleMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
847
+ ? join(repoRoot, 'dist/capabilities/incident-log.js')
848
+ : join(repoRoot, 'ts/capabilities/incident-log.ts')
849
+ const { recordIncident } = await import(pathToFileURL(incidentModule).href)
850
+ recordIncident({
851
+ ts: new Date().toISOString(),
852
+ kind: 'plugin_error',
853
+ message: `gotry spawn error: ${e instanceof Error ? e.message : String(e)}`,
854
+ source: 'gotry-cli',
855
+ }, repoRoot)
856
+ } catch { /* diagnostics remain best-effort; fatal exit is preserved */ }
857
+ if (benchmarkEnvironmentConfig) {
858
+ reportBenchmarkFailure('child_spawn_failure')
859
+ return
860
+ }
211
861
  console.error(`[gotry] dsh 启动失败: ${e.message}`)
212
- console.error('尝试: node -v 看 Node 版本(需 22+),或查看 ts/dsh-runtime/node_modules/。')
862
+ console.error('尝试: node -v 看 Node 版本(需 22.15+),或查看 ts/dsh-runtime/node_modules/。')
213
863
  process.exit(1)
214
864
  })