@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -0,0 +1,91 @@
1
+ /**
2
+ * gotry-backend 内核(2026-09-10,founder 口径:「我只允许一个 gotry 包装出来的服务」)。
3
+ *
4
+ * 形态:一个 HTTP 服务面 + 模块注册表。模块(gotry 能力单元)以 {method,path,handle}
5
+ * 精确路由表挂载,内核只做分发/404/异常隔离——鉴权、探活、schema 校验全部归模块
6
+ * 自持(模块行为即其独立部署形态的行为,挂载前后逐字一致)。
7
+ * 现有模块:booking-copilot(booking surface 能力)、session-search(账号会话检索)。
8
+ * gotry-a2a 是本服务的 A2A 协议端口,不是独立服务(迁移归部署收敛批次)。
9
+ */
10
+ import { createServer } from 'node:http';
11
+ export function createBackendServer(options) {
12
+ const table = new Map();
13
+ for (const module of options.modules) {
14
+ for (const route of module.routes) {
15
+ const key = `${route.method} ${route.path}`;
16
+ if (table.has(key))
17
+ throw new Error(`gotry_backend_route_conflict: ${key}`);
18
+ table.set(key, route);
19
+ }
20
+ }
21
+ const server = createServer(async (req, res) => {
22
+ const urlPath = (req.url ?? '').split('?')[0];
23
+ const route = table.get(`${req.method} ${urlPath}`);
24
+ if (!route) {
25
+ res.statusCode = 404;
26
+ res.setHeader('content-type', 'application/json');
27
+ res.end(JSON.stringify({ error: { code: 'not_found' } }));
28
+ return;
29
+ }
30
+ try {
31
+ await route.handle(req, res);
32
+ }
33
+ catch (error) {
34
+ // 模块内部异常不拖垮服务面;若响应已开头只能断连(调用方超时语义兜底)
35
+ if (!res.headersSent) {
36
+ res.statusCode = 500;
37
+ res.setHeader('content-type', 'application/json');
38
+ res.end(JSON.stringify({ error: { code: 'gotry_backend_module_error', module: route ? 'module' : 'kernel' } }));
39
+ }
40
+ else {
41
+ res.destroy();
42
+ }
43
+ process.stderr.write(`[gotry-backend] module=${moduleErrorName(options.modules, route)} route=${req.method} ${urlPath} failed: ${error instanceof Error ? error.message : String(error)}\n`);
44
+ }
45
+ });
46
+ return new Promise((resolve, reject) => {
47
+ server.once('error', reject);
48
+ server.listen(options.port ?? 0, options.host ?? '127.0.0.1', () => {
49
+ server.off('error', reject);
50
+ const address = server.address();
51
+ const port = typeof address === 'object' && address ? address.port : (options.port ?? 0);
52
+ resolve({
53
+ server,
54
+ port,
55
+ moduleNames: options.modules.map((m) => m.name),
56
+ close: () => new Promise((done, fail) => server.close((error) => error ? fail(error) : done())),
57
+ });
58
+ });
59
+ });
60
+ }
61
+ function moduleErrorName(modules, route) {
62
+ if (!route)
63
+ return 'unknown';
64
+ for (const m of modules) {
65
+ if (m.routes.includes(route))
66
+ return m.name;
67
+ }
68
+ return 'unknown';
69
+ }
70
+ /** 关闭全部模块 + 服务面(单模块 close 失败不阻断其余回收,最后聚合抛出) */
71
+ export async function closeBackend(handle, modules) {
72
+ const errors = [];
73
+ for (const module of modules) {
74
+ if (!module.close)
75
+ continue;
76
+ try {
77
+ await module.close();
78
+ }
79
+ catch (error) {
80
+ errors.push(error);
81
+ }
82
+ }
83
+ try {
84
+ await handle.close();
85
+ }
86
+ catch (error) {
87
+ errors.push(error);
88
+ }
89
+ if (errors.length > 0)
90
+ throw new AggregateError(errors, 'gotry_backend_close_failed');
91
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * booking-copilot 模块(gotry-backend 的能力单元之一;booking surface 能力)。
3
+ *
4
+ * 组装 = startup.ts 同款(ensureLedger → TaskRuntime → dsh planner),路由挂载 =
5
+ * server.ts 摘出的 bookingCopilotTrafficHandler(与独立部署形态逐字一致:
6
+ * POST /a2a/booking-copilot/turn + GET /healthz|/status 探活,Bearer apiKey,
7
+ * schema 头校验)。ingress 固定 bound-turn-only(注入 seam 是部署级组合,本模块不携带)。
8
+ */
9
+ import { mkdirSync } from 'node:fs';
10
+ import { ensureLedger } from '../../state-ledger.js';
11
+ import { BookingCopilotTaskRuntime } from '../../booking-surface/runtime.js';
12
+ import { buildDshPlannerEnvironment, createDshEmbeddedBookingPlanner, } from '../../booking-surface/dsh-planner.js';
13
+ import { bookingCopilotTrafficHandler, runtimeIdentity, } from '../../booking-surface/server.js';
14
+ import { resolveBookingCopilotStartupConfig } from '../../booking-surface/startup.js';
15
+ export async function startBookingCopilotModule(env = process.env) {
16
+ const config = resolveBookingCopilotStartupConfig(env);
17
+ mkdirSync(config.stateRoot, { recursive: true });
18
+ const ledger = ensureLedger(config.stateRoot);
19
+ let planner;
20
+ try {
21
+ planner = await createDshEmbeddedBookingPlanner({ stateRoot: config.stateRoot, env: buildDshPlannerEnvironment(env) });
22
+ }
23
+ catch (error) {
24
+ try {
25
+ ledger.close();
26
+ }
27
+ catch { /* 聚合首错优先 */ }
28
+ throw error;
29
+ }
30
+ const traffic = bookingCopilotTrafficHandler({
31
+ apiKey: config.apiKey,
32
+ composition: {
33
+ runtime: new BookingCopilotTaskRuntime(ledger),
34
+ plannerFactory: planner.plannerFactory,
35
+ ingressMode: 'bff-bound-turn-only',
36
+ },
37
+ maxBodyBytes: 1_000_000,
38
+ ...(config.artifactId !== undefined ? { artifactId: config.artifactId } : {}),
39
+ ingressMode: 'bff-bound-turn-only',
40
+ runningIdentity: runtimeIdentity(),
41
+ });
42
+ const respond = (req, res) => traffic(req, res);
43
+ return {
44
+ name: 'booking-copilot',
45
+ routes: [
46
+ { method: 'POST', path: '/a2a/booking-copilot/turn', handle: respond },
47
+ { method: 'GET', path: '/healthz', handle: respond },
48
+ { method: 'GET', path: '/status', handle: respond },
49
+ ],
50
+ async close() {
51
+ await planner.close();
52
+ ledger.close();
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * booking-executor 模块(gotry-backend 能力单元;M1 预订链执行面骨架)。
3
+ *
4
+ * 红线(设计文档 §7 / WriteGate):
5
+ * - 本模块只提供「只读观察 + 截图存证」;填单/提交原语**不存在**——写动作由
6
+ * 后续确认门放行后的受控执行器实现,当前骨架刻意不建。
7
+ * - 观察对象 = dida 页面自己的预订入口(按钮文本与数量),不读取任何凭据/Cookie 值。
8
+ *
9
+ * 路由(鉴权:Bearer GOTRY_BACKEND_BOOKING_API_KEY;缺 key = fail-closed 503):
10
+ * POST /v1/booking/observe → 打开供应商页面,观察预订入口并截图存证
11
+ *
12
+ * 观察器可注入(测试/未来多供应商扩展);缺省实现 = CDP 只读观察
13
+ * (openSession,ReadGuard 照常生效)。
14
+ */
15
+ import { mkdirSync } from 'node:fs';
16
+ import { openSession } from '../../../capabilities/session/transport.js';
17
+ function sendJson(res, status, body) {
18
+ res.statusCode = status;
19
+ res.setHeader('content-type', 'application/json');
20
+ res.end(JSON.stringify(body));
21
+ }
22
+ async function readBody(req, cap = 32 * 1024) {
23
+ return new Promise((resolve, reject) => {
24
+ const chunks = [];
25
+ let size = 0;
26
+ req.on('data', (c) => {
27
+ size += c.length;
28
+ if (size > cap) {
29
+ reject(new Error(`body 超 ${cap} 字节上限`));
30
+ req.destroy();
31
+ return;
32
+ }
33
+ chunks.push(c);
34
+ });
35
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
36
+ req.on('error', reject);
37
+ });
38
+ }
39
+ /** 缺省观察器:CDP 打开页面 → 读预订入口按钮 → 截图存证(只读;零提交原语) */
40
+ export async function defaultObserve(req, evidenceDir) {
41
+ const t = await openSession({ mode: 'cdp', guard: true, newPage: true, closeOwnPage: true });
42
+ if (!t.ok)
43
+ throw new Error(`会话主机 Chrome 不可达: ${t.summary}`);
44
+ try {
45
+ await t.page.goto(req.entryUrl ?? 'https://portal.dida.com/hotel/find', { waitUntil: 'domcontentloaded', timeout: 30_000 });
46
+ const entryPoints = (await t.page.evaluate(() => {
47
+ return Array.from(document.querySelectorAll('button'))
48
+ .filter((b) => b.textContent != null && b.textContent.includes('预订'))
49
+ .slice(0, 10)
50
+ .map((b) => ({ text: (b.textContent ?? '').trim().slice(0, 40), tag: 'button' }));
51
+ }));
52
+ mkdirSync(evidenceDir, { recursive: true });
53
+ const screenshotPath = `${evidenceDir}/observe-${Date.now()}.png`;
54
+ await t.page.screenshot({ path: screenshotPath });
55
+ return {
56
+ ok: true,
57
+ supplier: req.supplier,
58
+ entryUrl: req.entryUrl ?? 'https://portal.dida.com/hotel/find',
59
+ entryPoints,
60
+ screenshotPath,
61
+ observedAt: new Date().toISOString(),
62
+ };
63
+ }
64
+ finally {
65
+ await t.close();
66
+ }
67
+ }
68
+ export function startBookingExecutorModule(options) {
69
+ const authorized = (req, res) => {
70
+ const key = options.apiKey();
71
+ const auth = String(req.headers.authorization ?? '');
72
+ if (!key) {
73
+ sendJson(res, 503, { ok: false, error: 'GOTRY_BACKEND_BOOKING_API_KEY 未配置(fail-closed)' });
74
+ return false;
75
+ }
76
+ if (auth !== `Bearer ${key}`) {
77
+ sendJson(res, 403, { ok: false, error: 'forbidden' });
78
+ return false;
79
+ }
80
+ return true;
81
+ };
82
+ const observe = options.observe ?? (async (req) => defaultObserve(req, '/tmp/booking-evidence'));
83
+ async function handleObserve(req, res) {
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(await readBody(req));
87
+ }
88
+ catch {
89
+ sendJson(res, 400, { ok: false, error: 'body 不是 JSON' });
90
+ return;
91
+ }
92
+ const supplier = (parsed.supplier ?? '').trim();
93
+ if (supplier !== 'dida-portal') {
94
+ sendJson(res, 400, { ok: false, error: `未知供应商通道 ${supplier || '(空)'}` });
95
+ return;
96
+ }
97
+ const entryUrl = (parsed.entryUrl ?? '').trim() || 'https://portal.dida.com/hotel/find';
98
+ if (!/^https:\/\/portal\.dida\.com\//.test(entryUrl)) {
99
+ sendJson(res, 400, { ok: false, error: 'entry URL 必须落在 https://portal.dida.com/ 域内' });
100
+ return;
101
+ }
102
+ try {
103
+ const result = await observe({ supplier, entryUrl });
104
+ sendJson(res, 200, result);
105
+ }
106
+ catch (e) {
107
+ sendJson(res, 502, { ok: false, error: `观察失败: ${e instanceof Error ? e.message.slice(0, 200) : String(e)}` });
108
+ }
109
+ }
110
+ return {
111
+ name: 'booking-executor',
112
+ routes: [
113
+ {
114
+ method: 'POST',
115
+ path: '/v1/booking/observe',
116
+ handle: (req, res) => {
117
+ if (!authorized(req, res))
118
+ return;
119
+ void handleObserve(req, res);
120
+ },
121
+ },
122
+ ],
123
+ };
124
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * session-search 模块(gotry-backend 的能力单元之一;账号会话检索,hotel-be portal
3
+ * 聚合工作台 M0)。travel agent 能力全部落 gotry——本模块是「一个 gotry 服务」里的
4
+ * 会话检索面,hotel-be 只做网关包装与上下文拼接。
5
+ *
6
+ * 执行环境(2026-09-11 founder 定案):**浏览器客户端**,服务端零 Chrome
7
+ * (headless 易风控 + 资源开销大,双双出局)。本模块内嵌桥作业队列
8
+ * (createBridgeJobQueue)并挂载桥协议端点;管理员浏览器里的 GoTry Session
9
+ * Bridge 扩展经 bearer 鉴权远程连入,长轮询取活,在自己浏览器的登录态里
10
+ * 被动嗅探 dida 回包。登录也在管理员自己浏览器完成(login/open 让扩展把
11
+ * dida 登录页置前台打开,人过验证码)——零 Xvfb、零 noVNC、零 SSH 隧道。
12
+ *
13
+ * 路由(鉴权:Bearer GOTRY_BACKEND_SESSION_API_KEY;缺 key = fail-closed 503):
14
+ * GET /v1/session/status → 扩展在线态 + 各供应商登录态(票据 cookie 名级;值零过手)
15
+ * POST /v1/session/search → 会话面检索(dida:multiCollect 嗅探推荐流双接口)
16
+ * POST /v1/session/login/open → 让扩展在管理员浏览器置前台打开 dida 登录入口页
17
+ * GET /v1/session/bridge/health → 桥健康(扩展启动探测)
18
+ * GET /v1/session/bridge/status → 桥诊断(队列/在线态快照)
19
+ * POST /v1/session/bridge/jobs → 扩展长轮询取活(hold ≤20s)
20
+ * POST /v1/session/bridge/results → 扩展回包(?jobId=;内核只支持精确路由)
21
+ *
22
+ * 红线继承:登录在供应商官网由人完成;challenged 即停;节律闸在 sessionDidaSearch
23
+ * 内;本模块再加单飞锁(同供应商串行,防并发打站点)。桥端点在 bearer 之上再强制
24
+ * Origin ∈ 扩展白名单(网页跨域请求必带邪恶 Origin,双保险)。
25
+ */
26
+ import { sessionDidaSearch } from '../../../capabilities/session-search.js';
27
+ import { DIDA_LOGIN_COOKIE_NAMES, DIDA_SITE_DOMAIN } from '../../../capabilities/session/adapters/dida-portal.js';
28
+ import { createBridgeJobQueue } from '../../../capabilities/session/extension-bridge.js';
29
+ import { extensionCookieNames, extensionOpenLogin, classifyBridgeFailure } from '../../../capabilities/session/extension-channel.js';
30
+ function sendJson(res, status, body) {
31
+ res.statusCode = status;
32
+ res.setHeader('content-type', 'application/json');
33
+ res.end(JSON.stringify(body));
34
+ }
35
+ async function readBody(req, cap = 64 * 1024) {
36
+ return new Promise((resolve, reject) => {
37
+ const chunks = [];
38
+ let size = 0;
39
+ req.on('data', (c) => {
40
+ size += c.length;
41
+ if (size > cap) {
42
+ reject(new Error(`body 超 ${cap} 字节上限`));
43
+ req.destroy();
44
+ return;
45
+ }
46
+ chunks.push(c);
47
+ });
48
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
49
+ req.on('error', reject);
50
+ });
51
+ }
52
+ const locks = new Map();
53
+ async function withLock(key, fn) {
54
+ const prev = locks.get(key) ?? Promise.resolve();
55
+ const next = prev.catch(() => undefined).then(fn);
56
+ locks.set(key, next);
57
+ try {
58
+ return await next;
59
+ }
60
+ finally {
61
+ if (locks.get(key) === next)
62
+ locks.delete(key);
63
+ }
64
+ }
65
+ export function startSessionSearchModule(options) {
66
+ const search = options.search ?? sessionDidaSearch;
67
+ const queue = options.jobQueue ?? createBridgeJobQueue();
68
+ const authorized = (req, res) => {
69
+ const key = options.apiKey();
70
+ const auth = String(req.headers.authorization ?? '');
71
+ if (!key) {
72
+ sendJson(res, 503, { ok: false, error: 'GOTRY_BACKEND_SESSION_API_KEY 未配置(fail-closed)' });
73
+ return false;
74
+ }
75
+ if (auth !== `Bearer ${key}`) {
76
+ sendJson(res, 403, { ok: false, error: 'forbidden' });
77
+ return false;
78
+ }
79
+ return true;
80
+ };
81
+ async function handleStatus(res) {
82
+ const checkedAt = new Date().toISOString();
83
+ if (!queue.extensionConnected()) {
84
+ sendJson(res, 200, {
85
+ ok: true,
86
+ suppliers: [{ supplier: 'dida-portal', loggedIn: false, reason: 'extension-not-connected', detail: '会话执行环境=管理员浏览器扩展(GoTry Session Bridge);扩展未连接桥——在管理员浏览器安装扩展并配置本服务桥地址' }],
87
+ bridge: queue.stats(),
88
+ checkedAt,
89
+ });
90
+ return;
91
+ }
92
+ const login = await extensionCookieNames({ site: 'dida-portal', domain: DIDA_SITE_DOMAIN, ticketNames: DIDA_LOGIN_COOKIE_NAMES, timeoutMs: 20_000 }, queue);
93
+ if (!login.ok) {
94
+ const verdict = classifyBridgeFailure(login.kind);
95
+ sendJson(res, 200, { ok: true, suppliers: [{ supplier: 'dida-portal', loggedIn: false, reason: verdict, detail: login.summary }], bridge: queue.stats(), checkedAt });
96
+ return;
97
+ }
98
+ sendJson(res, 200, {
99
+ ok: true,
100
+ suppliers: [{ supplier: 'dida-portal', loggedIn: login.tickets.length > 0, tickets: login.tickets, ...(login.tickets.length === 0 ? { reason: 'needs-login' } : {}) }],
101
+ bridge: queue.stats(),
102
+ checkedAt,
103
+ });
104
+ }
105
+ async function handleSearch(req, res) {
106
+ let parsed;
107
+ try {
108
+ parsed = JSON.parse(await readBody(req));
109
+ }
110
+ catch {
111
+ sendJson(res, 400, { ok: false, error: 'body 不是 JSON' });
112
+ return;
113
+ }
114
+ const supplier = (parsed.supplier ?? '').trim();
115
+ if (supplier !== 'dida-portal') {
116
+ sendJson(res, 400, { ok: false, error: `未知供应商通道 ${supplier || '(空)'}(M0 仅 dida-portal)` });
117
+ return;
118
+ }
119
+ try {
120
+ const result = await withLock(supplier, () => search({
121
+ entryUrl: parsed.query?.entryUrl,
122
+ timeoutMs: parsed.query?.timeoutMs,
123
+ auditPath: options.auditPath,
124
+ bridge: queue,
125
+ }));
126
+ if (result.verdict === 'cooldown') {
127
+ res.setHeader('retry-after', '30');
128
+ sendJson(res, 429, { ok: false, verdict: result.verdict, error: result.error, evidence: result.evidence });
129
+ return;
130
+ }
131
+ sendJson(res, 200, {
132
+ ok: result.ok,
133
+ verdict: result.verdict,
134
+ supplier,
135
+ rates: result.rates ?? [],
136
+ evidence: result.evidence,
137
+ latencyMs: result.latencyMs,
138
+ fetchedAt: new Date().toISOString(),
139
+ ...(result.error ? { error: result.error } : {}),
140
+ });
141
+ }
142
+ catch (e) {
143
+ sendJson(res, 500, { ok: false, error: `会话检索异常: ${e instanceof Error ? e.message.slice(0, 200) : String(e)}` });
144
+ }
145
+ }
146
+ async function handleLoginOpen(req, res) {
147
+ let parsed;
148
+ try {
149
+ parsed = JSON.parse(await readBody(req));
150
+ }
151
+ catch {
152
+ sendJson(res, 400, { ok: false, error: 'body 不是 JSON' });
153
+ return;
154
+ }
155
+ const supplier = (parsed.supplier ?? '').trim();
156
+ if (supplier !== 'dida-portal') {
157
+ sendJson(res, 400, { ok: false, error: `未知供应商通道 ${supplier || '(空)'}` });
158
+ return;
159
+ }
160
+ const url = (parsed.url ?? '').trim() || 'https://portal.dida.com/login';
161
+ if (!/^https:\/\/portal\.dida\.com\//.test(url)) {
162
+ sendJson(res, 400, { ok: false, error: '登录入口必须落在 https://portal.dida.com/ 域内(fail-closed)' });
163
+ return;
164
+ }
165
+ const outcome = await extensionOpenLogin({ site: supplier, url, timeoutMs: 20_000 }, queue);
166
+ if (!outcome.ok) {
167
+ const verdict = classifyBridgeFailure(outcome.kind);
168
+ sendJson(res, verdict === 'needs-extension' ? 503 : 502, { ok: false, verdict, error: outcome.summary });
169
+ return;
170
+ }
171
+ sendJson(res, 200, { ok: true, opened: true, url, note: '登录页已在会话所属浏览器置前台打开(验证码人过);gotry 永不经手密码/cookie 值' });
172
+ }
173
+ return {
174
+ name: 'session-search',
175
+ routes: [
176
+ { method: 'GET', path: '/v1/session/status', handle: (req, res) => { if (authorized(req, res))
177
+ void handleStatus(res); } },
178
+ { method: 'POST', path: '/v1/session/search', handle: (req, res) => { if (authorized(req, res))
179
+ void handleSearch(req, res); } },
180
+ { method: 'POST', path: '/v1/session/login/open', handle: (req, res) => { if (authorized(req, res))
181
+ void handleLoginOpen(req, res); } },
182
+ // 桥协议端点:远程扩展经 bearer 鉴权连入;Origin 白名单在队列处理器内再校验一道
183
+ { method: 'GET', path: '/v1/session/bridge/health', handle: (req, res) => { if (authorized(req, res))
184
+ queue.handleMountedRequest(req, res); } },
185
+ { method: 'GET', path: '/v1/session/bridge/status', handle: (req, res) => { if (authorized(req, res))
186
+ queue.handleMountedRequest(req, res); } },
187
+ { method: 'POST', path: '/v1/session/bridge/jobs', handle: (req, res) => { if (authorized(req, res))
188
+ queue.handleMountedRequest(req, res); } },
189
+ { method: 'POST', path: '/v1/session/bridge/results', handle: (req, res) => { if (authorized(req, res))
190
+ queue.handleMountedRequest(req, res); } },
191
+ ],
192
+ close: () => queue.close(),
193
+ };
194
+ }