@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.20

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 (176) hide show
  1. package/README.md +42 -16
  2. package/README.zh-CN.md +37 -15
  3. package/bin/gotry-bootstrap.js +309 -30
  4. package/bin/gotry-inner.js +74 -23
  5. package/bin/gotry-runtime-resolution.d.ts +1 -1
  6. package/bin/gotry-runtime-resolution.js +7 -8
  7. package/cordis.gotry-patch.yml +22 -8
  8. package/data/stations-12306-verify.json +524 -0
  9. package/dist/capabilities/agent-reach.js +4 -4
  10. package/dist/capabilities/anything.js +62 -29
  11. package/dist/capabilities/channel-health.js +103 -0
  12. package/dist/capabilities/channel-registry.js +213 -0
  13. package/dist/capabilities/doctor.js +307 -0
  14. package/dist/capabilities/effect.js +178 -5
  15. package/dist/capabilities/flyai.js +13 -0
  16. package/dist/capabilities/hbcli.js +28 -2
  17. package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
  18. package/dist/capabilities/session/adapters/rail-12306.js +285 -0
  19. package/dist/capabilities/session/extension-bridge.js +14 -1
  20. package/dist/capabilities/session/extension-channel.js +4 -2
  21. package/dist/capabilities/session/extension-distribution.js +1 -1
  22. package/dist/capabilities/session-search.js +311 -0
  23. package/dist/capabilities/visa-policy.js +126 -0
  24. package/dist/scripts/agent-planning-turn-deadline-e2e.js +305 -0
  25. package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
  26. package/dist/scripts/anything-tests.js +70 -21
  27. package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
  28. package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
  29. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
  30. package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
  31. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
  32. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
  33. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
  34. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
  35. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
  36. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
  37. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
  38. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
  39. package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
  40. package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
  41. package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
  42. package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
  43. package/dist/scripts/bootstrap-tests.js +113 -2
  44. package/dist/scripts/build-metrics-report.js +339 -0
  45. package/dist/scripts/channel-probe-tests.js +148 -0
  46. package/dist/scripts/channel-probe.js +252 -0
  47. package/dist/scripts/channel-registry-tests.js +262 -0
  48. package/dist/scripts/doctor-tests.js +175 -0
  49. package/dist/scripts/effect-tests.js +274 -1
  50. package/dist/scripts/extension-tests.js +129 -11
  51. package/dist/scripts/fact-gate-tests.js +91 -2
  52. package/dist/scripts/flyai-tests.js +17 -1
  53. package/dist/scripts/hbcli-e2e-tests.js +1 -3
  54. package/dist/scripts/hbcli-tests.js +38 -1
  55. package/dist/scripts/metrics-report-tests.js +288 -0
  56. package/dist/scripts/persona-surface-guard-tests.js +19 -0
  57. package/dist/scripts/session-tests.js +401 -1
  58. package/dist/scripts/smoke.js +100 -83
  59. package/dist/scripts/turn-handoff-collect-tests.js +216 -0
  60. package/dist/scripts/turn-handoff-collect.js +189 -0
  61. package/dist/scripts/turn-policy-tests.js +56 -0
  62. package/dist/scripts/typed-contract-canary.js +261 -0
  63. package/dist/scripts/visa-policy-tests.js +75 -0
  64. package/dist/scripts/wish-channel-gate-tests.js +111 -0
  65. package/dist/src/artifact-gate.js +91 -3
  66. package/dist/src/benchmark-agent-conformance.js +39 -16
  67. package/dist/src/benchmark-environment-bridge.js +316 -52
  68. package/dist/src/bookable-facts.js +62 -3
  69. package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
  70. package/dist/src/booking-surface/canonical-schema.js +8 -22
  71. package/dist/src/booking-surface/contracts.js +70 -19
  72. package/dist/src/booking-surface/dsh-planner.js +310 -177
  73. package/dist/src/booking-surface/dsh-plugin.js +3 -3
  74. package/dist/src/booking-surface/index.js +1 -5
  75. package/dist/src/booking-surface/profile.js +1 -1
  76. package/dist/src/booking-surface/runtime.js +1682 -247
  77. package/dist/src/booking-surface/server.js +371 -185
  78. package/dist/src/booking-surface/startup.js +17 -27
  79. package/dist/src/booking-surface/validation.js +270 -760
  80. package/dist/src/index.js +739 -150
  81. package/dist/src/turn-deadline.js +292 -0
  82. package/dist/src/turn-policy.js +146 -0
  83. package/dist/src/wish-pool.js +5 -0
  84. package/extension/background.js +23 -4
  85. package/extension/content-main.js +47 -9
  86. package/extension/manifest.json +27 -9
  87. package/package.json +7 -26
  88. package/schemas/booking.surface.schema.json +2593 -0
  89. package/ts/capabilities/agent-reach.ts +4 -4
  90. package/ts/capabilities/anything.ts +89 -38
  91. package/ts/capabilities/flyai.ts +21 -3
  92. package/ts/capabilities/hbcli.ts +53 -4
  93. package/ts/capabilities/session/action-cache.ts +1 -1
  94. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  95. package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
  96. package/ts/capabilities/session/extension-bridge.ts +32 -2
  97. package/ts/capabilities/session/extension-channel.ts +9 -8
  98. package/ts/capabilities/session/extension-distribution.ts +3 -1
  99. package/ts/capabilities/session/health-watch.ts +1 -1
  100. package/ts/capabilities/session/read-guard.ts +1 -1
  101. package/ts/capabilities/session/wizard.ts +1 -1
  102. package/ts/capabilities/session-search.ts +323 -1
  103. package/ts/dsh-runtime/vendor/README.md +60 -0
  104. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  105. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
  106. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
  107. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
  108. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  109. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
  110. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  111. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
  112. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  113. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  114. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
  115. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  116. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
  117. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
  118. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  119. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  120. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
  121. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
  122. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  123. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  124. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  125. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  126. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
  127. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  128. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  129. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  130. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  131. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  132. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
  133. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
  134. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  135. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
  136. package/ts/package.json +9 -0
  137. package/ts/scripts/turn-handoff-collect.ts +178 -0
  138. package/ts/src/artifact-gate.ts +82 -2
  139. package/ts/src/benchmark-agent-conformance.ts +54 -15
  140. package/ts/src/benchmark-environment-bridge.ts +234 -46
  141. package/ts/src/bookable-facts.ts +109 -5
  142. package/ts/src/booking-saga.ts +1 -1
  143. package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
  144. package/ts/src/booking-surface/canonical-schema.js +8 -22
  145. package/ts/src/booking-surface/contracts.ts +278 -239
  146. package/ts/src/booking-surface/dsh-planner.ts +333 -194
  147. package/ts/src/booking-surface/dsh-plugin.js +3 -3
  148. package/ts/src/booking-surface/index.ts +1 -5
  149. package/ts/src/booking-surface/profile.ts +11 -11
  150. package/ts/src/booking-surface/runtime.ts +1351 -351
  151. package/ts/src/booking-surface/server.ts +282 -175
  152. package/ts/src/booking-surface/startup.ts +36 -48
  153. package/ts/src/booking-surface/validation.ts +194 -442
  154. package/ts/src/contracts.ts +1 -1
  155. package/ts/src/index.ts +492 -172
  156. package/ts/src/loop.ts +1 -1
  157. package/ts/src/state-ledger.ts +1 -1
  158. package/ts/src/turn-deadline.ts +365 -0
  159. package/ts/src/turn-policy.ts +154 -0
  160. package/ts/src/wish-pool.ts +17 -4
  161. package/dist/scripts/agent-planning-budget-e2e.js +0 -227
  162. package/dist/scripts/agent-planning-budget-tests.js +0 -173
  163. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
  164. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
  165. package/dist/src/booking-surface/contracts-v2.js +0 -89
  166. package/dist/src/booking-surface/runtime-v2.js +0 -1771
  167. package/dist/src/booking-surface/server-v2.js +0 -334
  168. package/dist/src/booking-surface/validation-v2.js +0 -319
  169. package/dist/src/tool-budget.js +0 -136
  170. package/schemas/booking.surface.v1.schema.json +0 -927
  171. package/schemas/booking.surface.v2.schema.json +0 -61
  172. package/ts/src/booking-surface/contracts-v2.ts +0 -118
  173. package/ts/src/booking-surface/runtime-v2.ts +0 -1466
  174. package/ts/src/booking-surface/server-v2.ts +0 -247
  175. package/ts/src/booking-surface/validation-v2.ts +0 -205
  176. package/ts/src/tool-budget.ts +0 -165
@@ -1,11 +1,39 @@
1
1
  import { timingSafeEqual } from 'node:crypto';
2
2
  import { createServer } from 'node:http';
3
- import { BOOKING_SURFACE_SCHEMA_SHA256, BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_VERSION, BOOKING_SURFACE_VERSION_HEADER } from './contracts.js';
4
- import { BookingCopilotTaskRuntime } from './runtime.js';
5
- import { validateBookingCopilotTurnV1 } from './validation.js';
6
- import { handleBookingCopilotV2Request } from './server-v2.js';
3
+ import { BOOKING_COPILOT_ACCEPTED_TURN_KINDS, BOOKING_COPILOT_INGRESS_MODES, BOOKING_READ_ACTION_KINDS, BOOKING_SURFACE_ALLOWED_ACTIONS, BOOKING_SURFACE_SCHEMA_SHA256, BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_VERSION, BOOKING_SURFACE_VERSION_HEADER } from './contracts.js';
4
+ import { bookingDigest, BookingCopilotTaskRuntime } from './runtime.js';
5
+ import { validateBookingSurface } from './validation.js';
7
6
  import { normalizeBookingErrorCode, safeBookingErrorMessage } from './error-codes.js';
8
- import { BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS, BOOKING_COPILOT_V2_INGRESS_TURN_KIND, BOOKING_COPILOT_V2_INGRESS_MODES, BOOKING_SURFACE_SCHEMA_V2_SHA256, BOOKING_SURFACE_SCHEMA_VERSION_V2 } from './contracts-v2.js';
7
+ const sessionsByComposition = new WeakMap();
8
+ const decisionFlightsByComposition = new WeakMap();
9
+ function sessionsFor(composition) {
10
+ let sessions = sessionsByComposition.get(composition);
11
+ if (!sessions) {
12
+ sessions = new Map();
13
+ sessionsByComposition.set(composition, sessions);
14
+ }
15
+ return sessions;
16
+ }
17
+ function decisionFlightsFor(composition) {
18
+ let flights = decisionFlightsByComposition.get(composition);
19
+ if (!flights) {
20
+ flights = new Map();
21
+ decisionFlightsByComposition.set(composition, flights);
22
+ }
23
+ return flights;
24
+ }
25
+ async function runDecisionSingleFlight(composition, key, work) {
26
+ const flights = decisionFlightsFor(composition);
27
+ const prior = flights.get(key);
28
+ if (prior) return prior;
29
+ const current = work();
30
+ flights.set(key, current);
31
+ try {
32
+ return await current;
33
+ } finally{
34
+ if (flights.get(key) === current) flights.delete(key);
35
+ }
36
+ }
9
37
  function runtimeIdentity() {
10
38
  const report = process.report?.getReport?.();
11
39
  const header = report?.header;
@@ -45,38 +73,341 @@ function readBody(req, maxBodyBytes) {
45
73
  req.on('error', reject);
46
74
  });
47
75
  }
48
- function writeSse(res, event) {
76
+ function write(res, event) {
49
77
  res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`);
50
78
  }
79
+ function sameCapabilities(a, b) {
80
+ return a.length === b.length && [
81
+ ...a
82
+ ].sort().every((kind, index)=>kind === [
83
+ ...b
84
+ ].sort()[index]);
85
+ }
86
+ function exactKeys(value, allowed) {
87
+ return Object.keys(value).length === allowed.length && Object.keys(value).every((key)=>allowed.includes(key));
88
+ }
89
+ const SURFACE_ACTION_MATRIX = BOOKING_SURFACE_ALLOWED_ACTIONS;
90
+ function validSurfaceActions(surface, allowed) {
91
+ if (typeof surface !== 'string' || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, surface)) return false;
92
+ if (!Array.isArray(allowed) || allowed.length < 1 || new Set(allowed).size !== allowed.length) return false;
93
+ const matrix = SURFACE_ACTION_MATRIX[surface];
94
+ return allowed.every((action)=>typeof action === 'string' && matrix.includes(action) && BOOKING_READ_ACTION_KINDS.includes(action));
95
+ }
96
+ function validBoundWorkspaceAuthority(workspace) {
97
+ return workspace.capabilities.surface === workspace.surface && validSurfaceActions(workspace.surface, workspace.capabilities.allowedActions);
98
+ }
99
+ function validIngressBinding(binding, ingress) {
100
+ if (!binding || typeof binding !== 'object' || Array.isArray(binding)) return false;
101
+ const candidate = binding;
102
+ if (!exactKeys(candidate, [
103
+ 'taskId',
104
+ 'turnId',
105
+ 'contextRef',
106
+ 'surface',
107
+ 'allowedActions'
108
+ ])) return false;
109
+ if (typeof candidate.taskId !== 'string' || typeof candidate.turnId !== 'string' || typeof candidate.contextRef !== 'string') return false;
110
+ if (typeof candidate.surface !== 'string' || candidate.surface !== ingress.surfaceHint || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, candidate.surface)) return false;
111
+ return validSurfaceActions(candidate.surface, candidate.allowedActions);
112
+ }
113
+ function validIngressApprovalAuthority(ingress, binding) {
114
+ if (!ingress.request.approval) return true;
115
+ return ingress.request.approval.taskId === binding.taskId && ingress.request.approval.contextRef === binding.contextRef;
116
+ }
117
+ function fallbackErrorEvent(task, code) {
118
+ return {
119
+ schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
120
+ eventId: `error-${task.taskId}-${task.lastSequence + 1}`,
121
+ taskId: task.taskId,
122
+ contextRef: task.contextRef,
123
+ sequence: task.lastSequence + 1,
124
+ emittedAt: new Date().toISOString(),
125
+ kind: 'error',
126
+ error: {
127
+ code: normalizeBookingErrorCode(code),
128
+ message: safeBookingErrorMessage(normalizeBookingErrorCode(code)),
129
+ retryable: false
130
+ }
131
+ };
132
+ }
133
+ function writeTypedError(res, composition, task, error, requestKey, includeSubmitted = false) {
134
+ const code = normalizeBookingErrorCode(error);
135
+ console.error(`[booking-copilot] planner boundary error (${code}):`, error instanceof Error ? error.stack || error.message : error);
136
+ try {
137
+ const decision = {
138
+ kind: 'error',
139
+ error: {
140
+ code,
141
+ message: safeBookingErrorMessage(code),
142
+ retryable: false
143
+ }
144
+ };
145
+ if (requestKey) {
146
+ const events = composition.runtime.applyDecisionBatch(task.taskId, requestKey, [
147
+ decision
148
+ ], includeSubmitted);
149
+ for (const event of events)write(res, event);
150
+ return;
151
+ }
152
+ write(res, composition.runtime.emitEvent(task.taskId, decision));
153
+ } catch {
154
+ write(res, fallbackErrorEvent(task, code));
155
+ }
156
+ }
157
+ async function handleBookingCopilotRequest(req, res, composition, maxBodyBytes) {
158
+ let turn;
159
+ let browserRequestKey;
160
+ let requestBinding;
161
+ try {
162
+ const parsed = JSON.parse(await readBody(req, maxBodyBytes));
163
+ const valid = validateBookingSurface(parsed);
164
+ if (!valid.ok) {
165
+ sendJson(res, 400, {
166
+ error: {
167
+ code: 'invalid_booking_surface_turn',
168
+ details: valid.errors
169
+ }
170
+ });
171
+ return;
172
+ }
173
+ if (parsed.kind === 'user.turn.ingress') {
174
+ const ingress = parsed;
175
+ browserRequestKey = ingress.requestKey;
176
+ if (composition.ingressMode !== 'bff-ingress-binding' || !composition.ingressBinding || !composition.principal) {
177
+ sendJson(res, 503, {
178
+ error: {
179
+ code: 'trusted_ingress_binding_required',
180
+ mode: composition.ingressMode ?? 'bff-bound-turn-only',
181
+ acceptedTurnKinds: [
182
+ ...BOOKING_COPILOT_ACCEPTED_TURN_KINDS
183
+ ]
184
+ }
185
+ });
186
+ return;
187
+ }
188
+ const binding = await composition.ingressBinding.bind(ingress, composition.principal);
189
+ if (!validIngressBinding(binding, ingress)) {
190
+ sendJson(res, 502, {
191
+ error: {
192
+ code: 'invalid_ingress_binding'
193
+ }
194
+ });
195
+ return;
196
+ }
197
+ if (!validIngressApprovalAuthority(ingress, binding)) {
198
+ sendJson(res, 400, {
199
+ error: {
200
+ code: 'invalid_ingress_approval_authority'
201
+ }
202
+ });
203
+ return;
204
+ }
205
+ const boundWorkspace = {
206
+ ...ingress.workspace,
207
+ contextRef: binding.contextRef,
208
+ surface: binding.surface,
209
+ capabilities: {
210
+ surface: binding.surface,
211
+ allowedActions: [
212
+ ...binding.allowedActions
213
+ ]
214
+ }
215
+ };
216
+ const boundRequest = ingress.request.approval ? {
217
+ text: ingress.request.text,
218
+ approval: {
219
+ ...ingress.request.approval
220
+ }
221
+ } : {
222
+ text: ingress.request.text
223
+ };
224
+ turn = {
225
+ schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
226
+ kind: 'user.turn',
227
+ taskId: binding.taskId,
228
+ turnId: binding.turnId,
229
+ workspace: boundWorkspace,
230
+ request: boundRequest
231
+ };
232
+ requestBinding = {
233
+ requestKey: ingress.requestKey,
234
+ principal: composition.principal,
235
+ ...ingress.taskHandle ? {
236
+ taskHandle: ingress.taskHandle
237
+ } : {}
238
+ };
239
+ const boundValid = validateBookingSurface(turn);
240
+ if (!boundValid.ok) {
241
+ sendJson(res, 502, {
242
+ error: {
243
+ code: 'invalid_ingress_binding',
244
+ details: boundValid.errors
245
+ }
246
+ });
247
+ return;
248
+ }
249
+ } else {
250
+ turn = parsed;
251
+ if (!validBoundWorkspaceAuthority(turn.workspace)) {
252
+ sendJson(res, 403, {
253
+ error: {
254
+ code: 'invalid_bound_turn_authority'
255
+ }
256
+ });
257
+ return;
258
+ }
259
+ }
260
+ } catch (error) {
261
+ const code = normalizeBookingErrorCode(error);
262
+ sendJson(res, code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, {
263
+ error: {
264
+ code
265
+ }
266
+ });
267
+ return;
268
+ }
269
+ let task;
270
+ let fresh = false;
271
+ let replayKey;
272
+ let replayEvents = null;
273
+ let activeDecisionKey;
274
+ try {
275
+ fresh = turn.kind === 'action.receipt.continuation' ? false : !turn.taskId || composition.runtime.resumeTask(turn.taskId) === null;
276
+ if (turn.kind === 'action.receipt.continuation') {
277
+ replayKey = `receipt:${turn.receipt.actionId}:${composition.runtime.receiptDigest(turn.receipt)}`;
278
+ task = composition.runtime.continueWithReceipt(turn);
279
+ if (task.awaitingApproval) {
280
+ replayKey = composition.runtime.approvalPresentationRequestKey(turn.taskId);
281
+ }
282
+ replayEvents = composition.runtime.readDecisionBatch(turn.taskId, replayKey);
283
+ if (!replayEvents && task.phase === 'terminal') replayEvents = composition.runtime.terminalDecisionBatch(turn.taskId, replayKey);
284
+ } else {
285
+ const existing = turn.taskId ? composition.runtime.resumeTask(turn.taskId) : null;
286
+ if (requestBinding && turn.kind === 'user.turn') composition.runtime.assertRequestBinding(browserRequestKey, turn, requestBinding);
287
+ const requestReplayKey = existing && browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : undefined;
288
+ const directReplayKey = existing && turn.kind === 'user.turn' && turn.turnId ? `turn:${existing.taskId}:${turn.turnId}` : undefined;
289
+ const stableReplayKey = requestReplayKey ?? directReplayKey;
290
+ const stableReplay = stableReplayKey ? composition.runtime.readDecisionBatch(existing.taskId, stableReplayKey) : null;
291
+ if (stableReplay) {
292
+ if (existing && turn.kind === 'user.turn' && directReplayKey) composition.runtime.assertTurnBinding(existing.taskId, turn);
293
+ task = existing;
294
+ replayKey = stableReplayKey;
295
+ replayEvents = stableReplay;
296
+ } else if (existing?.phase === 'terminal' || existing?.phase === 'error') {
297
+ throw new Error('task_terminal');
298
+ } else if (existing?.phase === 'waiting_receipt') {
299
+ if (turn.kind === 'user.turn' && turn.request.approval) composition.runtime.assertApprovalNotConsumed(existing.taskId, turn.request.approval);
300
+ const workspace = turn.workspace;
301
+ if (workspace.contextRef !== existing.contextRef || workspace.surface !== existing.surface || workspace.revision !== existing.revision || workspace.capabilities.surface !== existing.surface || !sameCapabilities(workspace.capabilities.allowedActions, existing.allowedActions)) throw new Error('task_conflict:workspace_mismatch');
302
+ if (!turn.turnId || existing.lastTurnId !== turn.turnId) throw new Error('receipt_required');
303
+ replayKey = browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : `turn:${existing.taskId}:${existing.lastTurnId ?? existing.userTurnCount}`;
304
+ replayEvents = composition.runtime.readDecisionBatch(existing.taskId, replayKey);
305
+ if (!replayEvents) throw new Error('receipt_required');
306
+ task = existing;
307
+ } else {
308
+ if (existing && turn.kind === 'user.turn' && turn.request.approval) composition.runtime.assertApprovalNotConsumed(existing.taskId, turn.request.approval);
309
+ task = composition.runtime.startTask(turn, requestBinding);
310
+ if (turn.kind === 'user.turn' && turn.taskId) {
311
+ replayKey = browserRequestKey ? `ingress:${task.taskId}:${browserRequestKey}` : `turn:${task.taskId}:${turn.turnId ?? task.userTurnCount}`;
312
+ replayEvents = composition.runtime.readDecisionBatch(task.taskId, replayKey);
313
+ }
314
+ }
315
+ }
316
+ } catch (error) {
317
+ sendJson(res, 409, {
318
+ error: {
319
+ code: normalizeBookingErrorCode(error)
320
+ }
321
+ });
322
+ return;
323
+ }
324
+ res.writeHead(200, {
325
+ 'content-type': 'text/event-stream; charset=utf-8',
326
+ 'cache-control': 'no-cache, no-transform',
327
+ connection: 'keep-alive',
328
+ 'x-content-type-options': 'nosniff',
329
+ [BOOKING_SURFACE_VERSION_HEADER]: BOOKING_SURFACE_SCHEMA_VERSION,
330
+ [BOOKING_SURFACE_SCHEMA_SHA256_HEADER]: BOOKING_SURFACE_SCHEMA_SHA256
331
+ });
332
+ try {
333
+ if (replayEvents) {
334
+ for (const event of replayEvents)write(res, event);
335
+ } else {
336
+ const requestKey = replayKey ?? `turn:${task.taskId}:${task.userTurnCount}`;
337
+ activeDecisionKey = requestKey;
338
+ const approval = turn.kind === 'user.turn' ? turn.request.approval : undefined;
339
+ const flightKey = JSON.stringify([
340
+ task.taskId,
341
+ approval ? `approval:${bookingDigest(approval)}` : requestKey
342
+ ]);
343
+ const decisionEvents = await runDecisionSingleFlight(composition, flightKey, async ()=>{
344
+ if (turn.kind === 'action.receipt.continuation' && task.awaitingApproval) {
345
+ return composition.runtime.applyDecisionBatch(task.taskId, requestKey, [
346
+ composition.runtime.approvalQuestion(task.taskId)
347
+ ], false);
348
+ }
349
+ const sessions = sessionsFor(composition);
350
+ const session = sessions.get(task.taskId) ?? composition.plannerFactory(task);
351
+ sessions.set(task.taskId, session);
352
+ const decisions = await session.next({
353
+ turn,
354
+ task: composition.runtime.resumeTask(task.taskId) ?? task
355
+ });
356
+ const plannerDecisions = decisions.length ? decisions : [
357
+ {
358
+ kind: 'error',
359
+ error: {
360
+ code: 'PLANNER_NO_DECISION',
361
+ message: 'Planner returned no typed decision.',
362
+ retryable: false
363
+ }
364
+ }
365
+ ];
366
+ return composition.runtime.applyDecisionBatch(task.taskId, requestKey, plannerDecisions, fresh);
367
+ });
368
+ for (const event of decisionEvents)write(res, event);
369
+ }
370
+ } catch (error) {
371
+ writeTypedError(res, composition, task, error, activeDecisionKey, fresh);
372
+ } finally{
373
+ res.end();
374
+ }
375
+ }
51
376
  export function startBookingCopilotServer(options) {
52
377
  if (!options.apiKey) return Promise.reject(new Error('booking_copilot_api_key_required'));
53
378
  if (options.artifactId !== undefined && !/^[0-9a-f]{40}$/.test(options.artifactId)) {
54
379
  return Promise.reject(new Error('booking_copilot_artifact_id_invalid'));
55
380
  }
56
- const hasIngressBinding = Boolean(options.v2?.ingressBinding);
57
- const hasPrincipal = Boolean(options.v2?.principal);
58
- if (options.v2 && hasIngressBinding !== hasPrincipal) {
59
- return Promise.reject(new Error('booking_copilot_v2_ingress_binding_pair_required'));
381
+ if (!options.runtime || !options.plannerFactory) {
382
+ return Promise.reject(new Error('booking_copilot_runtime_and_planner_required'));
383
+ }
384
+ const hasIngressBinding = Boolean(options.ingressBinding);
385
+ const hasPrincipal = Boolean(options.principal);
386
+ if (hasIngressBinding !== hasPrincipal) {
387
+ return Promise.reject(new Error('booking_copilot_ingress_binding_pair_required'));
60
388
  }
61
- if (options.v2 && hasIngressBinding && (typeof options.v2.ingressBinding?.bind !== 'function' || typeof options.v2.principal?.subject !== 'string' || options.v2.principal.subject.length === 0 || options.v2.principal.subject.length > 256 || typeof options.v2.principal?.scope !== 'string' || options.v2.principal.scope.length === 0 || options.v2.principal.scope.length > 256)) {
62
- return Promise.reject(new Error('booking_copilot_v2_ingress_binding_invalid'));
389
+ if (hasIngressBinding && (typeof options.ingressBinding?.bind !== 'function' || typeof options.principal?.subject !== 'string' || options.principal.subject.length === 0 || options.principal.subject.length > 256 || typeof options.principal?.scope !== 'string' || options.principal.scope.length === 0 || options.principal.scope.length > 256)) {
390
+ return Promise.reject(new Error('booking_copilot_ingress_binding_invalid'));
63
391
  }
64
- const ingressMode = options.v2 ? options.v2.ingressMode ?? (hasIngressBinding ? 'bff-ingress-binding' : 'bff-bound-turn-only') : undefined;
65
- if (options.v2 && !BOOKING_COPILOT_V2_INGRESS_MODES.includes(ingressMode)) {
66
- return Promise.reject(new Error('booking_copilot_v2_ingress_mode_invalid'));
392
+ const ingressMode = options.ingressMode ?? (hasIngressBinding ? 'bff-ingress-binding' : 'bff-bound-turn-only');
393
+ if (!BOOKING_COPILOT_INGRESS_MODES.includes(ingressMode)) {
394
+ return Promise.reject(new Error('booking_copilot_ingress_mode_invalid'));
67
395
  }
68
- if (options.v2 && ingressMode === 'bff-ingress-binding' && !hasIngressBinding) {
69
- return Promise.reject(new Error('booking_copilot_v2_ingress_binding_required'));
396
+ if (ingressMode === 'bff-ingress-binding' && !hasIngressBinding) {
397
+ return Promise.reject(new Error('booking_copilot_ingress_binding_required'));
70
398
  }
71
- if (options.v2 && ingressMode === 'bff-bound-turn-only' && hasIngressBinding) {
72
- return Promise.reject(new Error('booking_copilot_v2_ingress_mode_conflict'));
399
+ if (ingressMode === 'bff-bound-turn-only' && hasIngressBinding) {
400
+ return Promise.reject(new Error('booking_copilot_ingress_mode_conflict'));
73
401
  }
74
- const v2 = options.v2 ? {
75
- ...options.v2,
402
+ const composition = {
403
+ runtime: options.runtime,
404
+ plannerFactory: options.plannerFactory,
405
+ ...hasIngressBinding ? {
406
+ ingressBinding: options.ingressBinding,
407
+ principal: options.principal
408
+ } : {},
76
409
  ingressMode
77
- } : undefined;
78
- const v2Only = Boolean(v2 && !options.runtime && !options.plannerFactory);
79
- const sessions = new Map();
410
+ };
80
411
  const maxBodyBytes = options.maxBodyBytes ?? 1_000_000;
81
412
  const runningIdentity = runtimeIdentity();
82
413
  const server = createServer(async (req, res)=>{
@@ -99,56 +430,33 @@ export function startBookingCopilotServer(options) {
99
430
  return;
100
431
  }
101
432
  if (isProbe) {
102
- const activeV2Only = Boolean(v2 && !options.runtime && !options.plannerFactory);
103
- res.setHeader(BOOKING_SURFACE_VERSION_HEADER, activeV2Only ? BOOKING_SURFACE_SCHEMA_VERSION_V2 : BOOKING_SURFACE_SCHEMA_VERSION);
104
- res.setHeader(BOOKING_SURFACE_SCHEMA_SHA256_HEADER, activeV2Only ? BOOKING_SURFACE_SCHEMA_V2_SHA256 : BOOKING_SURFACE_SCHEMA_SHA256);
433
+ res.setHeader(BOOKING_SURFACE_VERSION_HEADER, BOOKING_SURFACE_SCHEMA_VERSION);
434
+ res.setHeader(BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_SHA256);
105
435
  if (options.artifactId) res.setHeader('X-GoTry-Artifact-ID', options.artifactId);
106
436
  res.setHeader('X-GoTry-Node-Version', runningIdentity.nodeVersion);
107
437
  res.setHeader('X-GoTry-Node-Modules-ABI', runningIdentity.nodeModulesAbi);
108
438
  res.setHeader('X-GoTry-Release-Tuple', runningIdentity.releaseTuple);
109
439
  if (runningIdentity.glibcVersion) res.setHeader('X-GoTry-Glibc-Version', runningIdentity.glibcVersion);
110
- const healthBody = {
111
- schemaVersion: activeV2Only ? BOOKING_SURFACE_SCHEMA_VERSION_V2 : BOOKING_SURFACE_SCHEMA_VERSION,
112
- schemaSha256: activeV2Only ? BOOKING_SURFACE_SCHEMA_V2_SHA256 : BOOKING_SURFACE_SCHEMA_SHA256,
113
- status: 'ready'
114
- };
115
- if (activeV2Only || v2) healthBody.supportedSchemaVersions = activeV2Only ? [
116
- BOOKING_SURFACE_SCHEMA_VERSION_V2
440
+ const acceptedTurnKinds = ingressMode === 'bff-ingress-binding' ? [
441
+ ...BOOKING_COPILOT_ACCEPTED_TURN_KINDS,
442
+ 'user.turn.ingress'
117
443
  ] : [
118
- BOOKING_SURFACE_SCHEMA_VERSION,
119
- BOOKING_SURFACE_SCHEMA_VERSION_V2
444
+ ...BOOKING_COPILOT_ACCEPTED_TURN_KINDS
120
445
  ];
121
- if (v2) {
122
- const acceptedTurnKinds = v2.ingressMode === 'bff-ingress-binding' ? [
123
- ...BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS,
124
- BOOKING_COPILOT_V2_INGRESS_TURN_KIND
125
- ] : [
126
- ...BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS
127
- ];
128
- healthBody.ingressMode = v2.ingressMode;
129
- healthBody.acceptedTurnKinds = acceptedTurnKinds;
130
- res.setHeader('X-GoTry-Ingress-Mode', v2.ingressMode);
131
- res.setHeader('X-GoTry-Accepted-Turn-Kinds', acceptedTurnKinds.join(','));
132
- }
446
+ const healthBody = {
447
+ schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
448
+ schemaSha256: BOOKING_SURFACE_SCHEMA_SHA256,
449
+ status: 'ready',
450
+ ingressMode,
451
+ acceptedTurnKinds
452
+ };
453
+ res.setHeader('X-GoTry-Ingress-Mode', ingressMode);
454
+ res.setHeader('X-GoTry-Accepted-Turn-Kinds', acceptedTurnKinds.join(','));
133
455
  sendJson(res, 200, healthBody);
134
456
  return;
135
457
  }
136
458
  const schemaVersion = String(req.headers[BOOKING_SURFACE_VERSION_HEADER] ?? '');
137
459
  const schemaHash = String(req.headers[BOOKING_SURFACE_SCHEMA_SHA256_HEADER] ?? '');
138
- if (schemaVersion === BOOKING_SURFACE_SCHEMA_VERSION_V2) {
139
- if (!v2 || schemaHash !== BOOKING_SURFACE_SCHEMA_V2_SHA256) {
140
- sendJson(res, 409, {
141
- error: {
142
- code: 'booking_surface_schema_mismatch',
143
- expectedVersion: BOOKING_SURFACE_SCHEMA_VERSION_V2,
144
- expectedSchemaSha256: BOOKING_SURFACE_SCHEMA_V2_SHA256
145
- }
146
- });
147
- return;
148
- }
149
- await handleBookingCopilotV2Request(req, res, v2, maxBodyBytes);
150
- return;
151
- }
152
460
  if (schemaVersion !== BOOKING_SURFACE_SCHEMA_VERSION || schemaHash !== BOOKING_SURFACE_SCHEMA_SHA256) {
153
461
  sendJson(res, 409, {
154
462
  error: {
@@ -159,129 +467,7 @@ export function startBookingCopilotServer(options) {
159
467
  });
160
468
  return;
161
469
  }
162
- if (!options.runtime || !options.plannerFactory) {
163
- sendJson(res, 503, {
164
- error: {
165
- code: 'booking_surface_v1_not_configured'
166
- }
167
- });
168
- return;
169
- }
170
- let turn;
171
- try {
172
- const raw = await readBody(req, maxBodyBytes);
173
- const parsed = JSON.parse(raw);
174
- const validation = validateBookingCopilotTurnV1(parsed);
175
- if (!validation.ok) {
176
- sendJson(res, 400, {
177
- error: {
178
- code: 'invalid_booking_surface_turn',
179
- details: validation.errors
180
- }
181
- });
182
- return;
183
- }
184
- turn = parsed;
185
- } catch (error) {
186
- const code = normalizeBookingErrorCode(error);
187
- sendJson(res, code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, {
188
- error: {
189
- code
190
- }
191
- });
192
- return;
193
- }
194
- let task;
195
- let isNewTask = false;
196
- try {
197
- const taskId = turn.taskId;
198
- if (v2 && taskId && v2.runtime.resumeTask(taskId)) throw new Error('task_conflict:protocol_version');
199
- if (turn.kind === 'user.turn') {
200
- const suppliedTaskId = turn.taskId;
201
- isNewTask = !suppliedTaskId || options.runtime.resumeTask(suppliedTaskId) === null;
202
- task = options.runtime.startTask(turn);
203
- } else {
204
- task = options.runtime.continueWithReceipt(turn);
205
- }
206
- } catch (error) {
207
- sendJson(res, 409, {
208
- error: {
209
- code: normalizeBookingErrorCode(error)
210
- }
211
- });
212
- return;
213
- }
214
- res.writeHead(200, {
215
- 'content-type': 'text/event-stream; charset=utf-8',
216
- 'cache-control': 'no-cache, no-transform',
217
- connection: 'keep-alive',
218
- 'x-content-type-options': 'nosniff',
219
- [BOOKING_SURFACE_VERSION_HEADER]: BOOKING_SURFACE_SCHEMA_VERSION,
220
- [BOOKING_SURFACE_SCHEMA_SHA256_HEADER]: BOOKING_SURFACE_SCHEMA_SHA256
221
- });
222
- try {
223
- if (isNewTask) writeSse(res, options.runtime.emitEvent(task.taskId, {
224
- kind: 'status',
225
- status: 'submitted'
226
- }));
227
- writeSse(res, options.runtime.emitEvent(task.taskId, {
228
- kind: 'status',
229
- status: 'working'
230
- }));
231
- task = options.runtime.resumeTask(task.taskId) ?? task;
232
- let session = sessions.get(task.taskId);
233
- if (!session) {
234
- session = options.plannerFactory(task);
235
- sessions.set(task.taskId, session);
236
- }
237
- const decisions = await session.next({
238
- turn,
239
- task
240
- });
241
- if (decisions.length === 0) {
242
- writeSse(res, options.runtime.emitEvent(task.taskId, {
243
- kind: 'error',
244
- error: {
245
- code: 'PLANNER_NO_DECISION',
246
- message: 'Planner returned no typed decision.',
247
- retryable: false
248
- }
249
- }));
250
- } else {
251
- for (const decision of decisions){
252
- const event = decision.kind === 'operation' ? options.runtime.issueOperation(task.taskId, decision.action) : options.runtime.emitEvent(task.taskId, decision);
253
- writeSse(res, event);
254
- }
255
- }
256
- } catch (error) {
257
- try {
258
- writeSse(res, options.runtime.emitEvent(task.taskId, {
259
- kind: 'error',
260
- error: {
261
- code: normalizeBookingErrorCode(error),
262
- message: safeBookingErrorMessage(normalizeBookingErrorCode(error)),
263
- retryable: false
264
- }
265
- }));
266
- } catch {
267
- writeSse(res, {
268
- schemaVersion: 'booking.surface.v1',
269
- eventId: `error-${task.taskId}-${task.lastSequence + 1}`,
270
- taskId: task.taskId,
271
- contextRef: task.contextRef,
272
- sequence: task.lastSequence + 1,
273
- emittedAt: new Date().toISOString(),
274
- kind: 'error',
275
- error: {
276
- code: normalizeBookingErrorCode(error),
277
- message: safeBookingErrorMessage(normalizeBookingErrorCode(error)),
278
- retryable: false
279
- }
280
- });
281
- }
282
- } finally{
283
- res.end();
284
- }
470
+ await handleBookingCopilotRequest(req, res, composition, maxBodyBytes);
285
471
  });
286
472
  return new Promise((resolve, reject)=>{
287
473
  server.once('error', reject);