@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.17

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 (238) hide show
  1. package/README.md +51 -13
  2. package/README.zh-CN.md +26 -12
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +357 -8
  5. package/bin/gotry-inner.js +442 -60
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +15 -1
  10. package/data/airline-airports.json +39 -0
  11. package/dist/capabilities/agent-reach-deep.js +1 -1
  12. package/dist/capabilities/agent-reach.js +1 -1
  13. package/dist/capabilities/anything.js +1 -1
  14. package/dist/capabilities/artifacts.js +1 -1
  15. package/dist/capabilities/effect.js +293 -0
  16. package/dist/capabilities/fact-log.js +41 -0
  17. package/dist/capabilities/flyai.js +20 -6
  18. package/dist/capabilities/hbcli.js +43 -1
  19. package/dist/capabilities/incident-log.js +1 -1
  20. package/dist/capabilities/model-override.js +18 -0
  21. package/dist/capabilities/opensky.js +1 -1
  22. package/dist/capabilities/resilience.js +90 -0
  23. package/dist/capabilities/session/action-cache.js +1 -1
  24. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  25. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  26. package/dist/capabilities/session/benchmark.js +2 -1
  27. package/dist/capabilities/session/extension-bridge.js +358 -0
  28. package/dist/capabilities/session/extension-channel.js +93 -0
  29. package/dist/capabilities/session/extension-distribution.js +234 -0
  30. package/dist/capabilities/session/extract.js +1 -1
  31. package/dist/capabilities/session/golden-score.js +92 -0
  32. package/dist/capabilities/session/health-watch.js +216 -0
  33. package/dist/capabilities/session/read-guard.js +1 -1
  34. package/dist/capabilities/session/static-flight-golden.js +137 -0
  35. package/dist/capabilities/session/transport.js +1 -1
  36. package/dist/capabilities/session/wizard.js +147 -0
  37. package/dist/capabilities/session-consent.js +1 -1
  38. package/dist/capabilities/session-login.js +83 -1
  39. package/dist/capabilities/session-search.js +111 -2
  40. package/dist/capabilities/weather.js +168 -46
  41. package/dist/data/session-golden-20.json +25 -0
  42. package/dist/data/sf-golden-manifest.json +102 -0
  43. package/dist/data/sf-static-routes.json +91 -0
  44. package/dist/scripts/action-cache-tests.js +1 -1
  45. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  46. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  47. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  48. package/dist/scripts/agent-reach-tests.js +1 -1
  49. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  50. package/dist/scripts/anything-tests.js +1 -1
  51. package/dist/scripts/async-collect.js +1 -1
  52. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  53. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  54. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  55. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  56. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  57. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  58. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  59. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  60. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  61. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  62. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  63. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  64. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  65. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  66. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  67. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  68. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  69. package/dist/scripts/booking-saga-tests.js +1 -1
  70. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  71. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  72. package/dist/scripts/bootstrap-tests.js +59 -3
  73. package/dist/scripts/build-changelog.js +279 -0
  74. package/dist/scripts/changelog-tests.js +165 -0
  75. package/dist/scripts/companion-tests.js +1 -1
  76. package/dist/scripts/diff-test.js +1 -1
  77. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  78. package/dist/scripts/dsh-runtime-closure.js +150 -0
  79. package/dist/scripts/effect-tests.js +474 -0
  80. package/dist/scripts/engine-run.js +1 -1
  81. package/dist/scripts/engine-tests.js +1 -1
  82. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  83. package/dist/scripts/evaluation-contract-tests.js +574 -0
  84. package/dist/scripts/extension-distribution-cli.js +35 -0
  85. package/dist/scripts/extension-distribution-tests.js +341 -0
  86. package/dist/scripts/extension-tests.js +573 -0
  87. package/dist/scripts/fact-gate-tests.js +318 -0
  88. package/dist/scripts/flyai-tests.js +1 -1
  89. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  90. package/dist/scripts/hbcli-tests.js +1 -1
  91. package/dist/scripts/health-watch-cli.js +51 -0
  92. package/dist/scripts/i18n-tests.js +1 -1
  93. package/dist/scripts/incident-tests.js +1 -1
  94. package/dist/scripts/journey-tests.js +1 -1
  95. package/dist/scripts/ledger-tests.js +1 -1
  96. package/dist/scripts/ledger-workflow-crash.js +1 -1
  97. package/dist/scripts/memory-capture-tests.js +1 -1
  98. package/dist/scripts/memory-decay-tests.js +1 -1
  99. package/dist/scripts/memory-metrics.js +1 -1
  100. package/dist/scripts/memory-value-report.js +1 -1
  101. package/dist/scripts/model-override-e2e.js +176 -0
  102. package/dist/scripts/nightly-evidence-tests.js +67 -4
  103. package/dist/scripts/nightly-evidence.js +59 -3
  104. package/dist/scripts/nudge-digest.js +1 -1
  105. package/dist/scripts/onboarding-tests.js +186 -0
  106. package/dist/scripts/opensky-check.js +1 -1
  107. package/dist/scripts/opensky-tests.js +1 -1
  108. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  109. package/dist/scripts/price-drift-tests.js +441 -0
  110. package/dist/scripts/price-drift-watch.js +491 -0
  111. package/dist/scripts/probe-poi-tests.js +1 -1
  112. package/dist/scripts/product-metrics.js +1 -1
  113. package/dist/scripts/publish-preverify.js +40 -4
  114. package/dist/scripts/realtime-pricing-tests.js +1 -1
  115. package/dist/scripts/replay-async.js +1 -1
  116. package/dist/scripts/replay-real.js +1 -1
  117. package/dist/scripts/replay.js +1 -1
  118. package/dist/scripts/session-attach-diagnose.js +1 -1
  119. package/dist/scripts/session-attach-poc.js +1 -1
  120. package/dist/scripts/session-benchmark.js +1 -1
  121. package/dist/scripts/session-extract-tests.js +1 -1
  122. package/dist/scripts/session-login.js +1 -1
  123. package/dist/scripts/session-tests.js +70 -20
  124. package/dist/scripts/sf-live-benchmark.js +338 -0
  125. package/dist/scripts/sf-live-cli-tests.js +21 -0
  126. package/dist/scripts/sf-soft-score-tests.js +108 -0
  127. package/dist/scripts/sf-summary.js +93 -0
  128. package/dist/scripts/skeleton-check.js +1 -1
  129. package/dist/scripts/skeleton-integration-test.js +1 -1
  130. package/dist/scripts/skills-contract-tests.js +1 -1
  131. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  132. package/dist/scripts/smoke.js +154 -33
  133. package/dist/scripts/state-cli-tests.js +1 -1
  134. package/dist/scripts/state-cli.js +1 -1
  135. package/dist/scripts/static-golden-tests.js +299 -0
  136. package/dist/scripts/time-eval-tests.js +1 -1
  137. package/dist/scripts/travel-timeline-tests.js +1 -1
  138. package/dist/scripts/unified-tests.js +1 -1
  139. package/dist/scripts/weather-tests.js +694 -44
  140. package/dist/scripts/z3-race-tests.js +1 -1
  141. package/dist/src/artifact-gate.js +310 -0
  142. package/dist/src/benchmark-agent-conformance.js +370 -0
  143. package/dist/src/benchmark-environment-bridge.js +384 -0
  144. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  145. package/dist/src/benchmark-tool-isolation.js +124 -0
  146. package/dist/src/bookable-facts.js +349 -0
  147. package/dist/src/booking-saga.js +1 -1
  148. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  149. package/dist/src/booking-surface/canonical-schema.js +113 -0
  150. package/dist/src/booking-surface/contracts-v2.js +89 -0
  151. package/dist/src/booking-surface/contracts.js +46 -0
  152. package/dist/src/booking-surface/dsh-planner.js +453 -0
  153. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  154. package/dist/src/booking-surface/error-codes.js +94 -0
  155. package/dist/src/booking-surface/index.js +15 -0
  156. package/dist/src/booking-surface/profile.js +68 -0
  157. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  158. package/dist/src/booking-surface/runtime.js +351 -0
  159. package/dist/src/booking-surface/server-v2.js +334 -0
  160. package/dist/src/booking-surface/server.js +302 -0
  161. package/dist/src/booking-surface/startup.js +159 -0
  162. package/dist/src/booking-surface/validation-v2.js +319 -0
  163. package/dist/src/booking-surface/validation.js +809 -0
  164. package/dist/src/bridge.js +1 -1
  165. package/dist/src/companions.js +1 -1
  166. package/dist/src/contracts.js +1 -1
  167. package/dist/src/dsh-llm.js +1 -1
  168. package/dist/src/engine.js +1 -1
  169. package/dist/src/evaluation-cadence.js +234 -0
  170. package/dist/src/evaluation-contracts.js +906 -0
  171. package/dist/src/i18n.js +1 -1
  172. package/dist/src/index.js +234 -60
  173. package/dist/src/journey.js +1 -1
  174. package/dist/src/loop.js +1 -1
  175. package/dist/src/memory-capture.js +1 -1
  176. package/dist/src/memory-decay.js +1 -1
  177. package/dist/src/memory-utility.js +1 -1
  178. package/dist/src/mock-llm.js +1 -1
  179. package/dist/src/model.js +1 -1
  180. package/dist/src/realtime-pricing.js +14 -7
  181. package/dist/src/slot-spec.js +1 -1
  182. package/dist/src/state-ledger.js +2 -1
  183. package/dist/src/time-anchor.js +1 -1
  184. package/dist/src/tool-budget.js +136 -0
  185. package/dist/src/tool-packet.js +1 -1
  186. package/dist/src/travel-slots.js +1 -1
  187. package/dist/src/travel-timeline.js +1 -1
  188. package/dist/src/unified.js +1 -1
  189. package/dist/src/wish-pool.js +1 -1
  190. package/dist/src/z3-shared.js +1 -1
  191. package/extension/README.md +50 -0
  192. package/extension/background.js +160 -0
  193. package/extension/content-bridge.js +41 -0
  194. package/extension/content-main.js +62 -0
  195. package/extension/manifest.json +32 -0
  196. package/package.json +291 -12
  197. package/schemas/booking.surface.v1.schema.json +927 -0
  198. package/schemas/booking.surface.v2.schema.json +61 -0
  199. package/ts/capabilities/fact-log.ts +54 -0
  200. package/ts/capabilities/flyai.ts +16 -3
  201. package/ts/capabilities/hbcli.ts +46 -0
  202. package/ts/capabilities/session/benchmark.ts +4 -1
  203. package/ts/capabilities/session/extension-bridge.ts +377 -0
  204. package/ts/capabilities/session/extension-channel.ts +95 -0
  205. package/ts/capabilities/session/extension-distribution.ts +264 -0
  206. package/ts/capabilities/session/golden-score.ts +139 -0
  207. package/ts/capabilities/session/health-watch.ts +257 -0
  208. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  209. package/ts/capabilities/session/wizard.ts +140 -0
  210. package/ts/capabilities/session-login.ts +63 -2
  211. package/ts/capabilities/session-search.ts +105 -3
  212. package/ts/capabilities/weather.ts +141 -52
  213. package/ts/package.json +3 -3
  214. package/ts/src/artifact-gate.ts +365 -0
  215. package/ts/src/benchmark-agent-conformance.ts +448 -0
  216. package/ts/src/benchmark-environment-bridge.ts +348 -0
  217. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  218. package/ts/src/benchmark-tool-isolation.ts +166 -0
  219. package/ts/src/bookable-facts.ts +557 -0
  220. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  221. package/ts/src/booking-surface/canonical-schema.js +113 -0
  222. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  223. package/ts/src/booking-surface/contracts.ts +380 -0
  224. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  225. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  226. package/ts/src/booking-surface/error-codes.ts +101 -0
  227. package/ts/src/booking-surface/index.ts +12 -0
  228. package/ts/src/booking-surface/profile.ts +42 -0
  229. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  230. package/ts/src/booking-surface/runtime.ts +483 -0
  231. package/ts/src/booking-surface/server-v2.ts +247 -0
  232. package/ts/src/booking-surface/server.ts +324 -0
  233. package/ts/src/booking-surface/startup.ts +196 -0
  234. package/ts/src/booking-surface/validation-v2.ts +205 -0
  235. package/ts/src/booking-surface/validation.ts +453 -0
  236. package/ts/src/index.ts +161 -14
  237. package/ts/src/state-ledger.ts +1 -0
  238. package/ts/src/tool-budget.ts +165 -0
@@ -0,0 +1,351 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import './contracts.js';
3
+ import { validateBookingCopilotTurnV1, validateActionReceiptV1, validateBookingReadActionV1, validateBookingSurfaceEventV1 } from './validation.js';
4
+ import { normalizeBookingErrorCode, safeBookingErrorMessage } from './error-codes.js';
5
+ const LEDGER_SCHEMA = 'booking.copilot.ledger.v1';
6
+ const LEDGER_ACTOR = 'system:booking-copilot';
7
+ const TASK_STARTED = 'booking.copilot.task.started';
8
+ const USER_TURN_OBSERVED = 'booking.copilot.user.turn.observed';
9
+ const ACTION_ISSUED = 'booking.copilot.action.issued';
10
+ const RECEIPT_OBSERVED = 'booking.copilot.receipt.observed';
11
+ const EVENT_EMITTED = 'booking.copilot.event.emitted';
12
+ function stableValue(value) {
13
+ if (Array.isArray(value)) return value.map(stableValue);
14
+ if (typeof value !== 'object' || value === null) return value;
15
+ const source = value;
16
+ const target = {};
17
+ for (const key of Object.keys(source).sort())target[key] = stableValue(source[key]);
18
+ return target;
19
+ }
20
+ function digest(value) {
21
+ return createHash('sha256').update(JSON.stringify(stableValue(value))).digest('hex');
22
+ }
23
+ function checkpointDigest(action) {
24
+ return digest({
25
+ actionId: action.actionId,
26
+ kind: action.kind,
27
+ contextRef: action.contextRef,
28
+ expectedRevision: action.expectedRevision,
29
+ factRefs: action.factRefs,
30
+ reasonDigest: action.reasonDigest,
31
+ inputDigest: action.inputDigest,
32
+ eventId: action.eventId,
33
+ sequence: action.sequence,
34
+ emittedAt: action.emittedAt
35
+ });
36
+ }
37
+ function actionMatchesCheckpoint(action, checkpoint) {
38
+ return checkpoint.actionDigest === checkpointDigest({
39
+ actionId: checkpoint.actionId,
40
+ kind: action.kind,
41
+ contextRef: action.contextRef,
42
+ expectedRevision: action.expectedRevision,
43
+ factRefs: action.factRefs,
44
+ reasonDigest: digest(action.reason),
45
+ inputDigest: digest(action.input),
46
+ eventId: checkpoint.eventId,
47
+ sequence: checkpoint.sequence,
48
+ emittedAt: checkpoint.emittedAt
49
+ });
50
+ }
51
+ function assertSafeFactRefs(action) {
52
+ const opaque = (ref)=>/^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(ref) && !/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/i.test(ref);
53
+ if (!opaque(action.actionId)) throw new Error('unsafe_action_id');
54
+ if (action.factRefs.some((ref)=>!opaque(ref))) throw new Error('unsafe_fact_ref');
55
+ }
56
+ function validationMessage(result) {
57
+ return result.ok ? '' : result.errors.join('; ');
58
+ }
59
+ function assertTaskId(taskId) {
60
+ if (!taskId) throw new Error('invalid_task_id');
61
+ }
62
+ function sameActionAllowlist(left, right) {
63
+ return left.length === right.length && [
64
+ ...left
65
+ ].sort().every((kind, index)=>kind === [
66
+ ...right
67
+ ].sort()[index]);
68
+ }
69
+ export class BookingCopilotTaskRuntime {
70
+ ledger;
71
+ idFactory;
72
+ now;
73
+ constructor(ledger, options = {}){
74
+ this.ledger = ledger;
75
+ this.idFactory = options.idFactory ?? ((prefix)=>`${prefix}-${randomUUID()}`);
76
+ this.now = options.now ?? (()=>new Date().toISOString());
77
+ }
78
+ startTask(turn) {
79
+ const validation = validateBookingCopilotTurnV1(turn);
80
+ if (!validation.ok || turn.kind !== 'user.turn') throw new Error(`invalid_planner_turn:${validationMessage(validation)}`);
81
+ const taskId = turn.taskId ?? this.idFactory('task');
82
+ assertTaskId(taskId);
83
+ const requestDigest = digest(turn.request.text);
84
+ const apply = this.ledger.db.transaction(()=>{
85
+ const existing = this.resumeTask(taskId);
86
+ if (existing) {
87
+ if (existing.contextRef !== turn.workspace.contextRef) throw new Error('task_conflict:context_mismatch');
88
+ if (existing.surface !== turn.workspace.surface) throw new Error('task_conflict:surface_mismatch');
89
+ if (existing.revision !== turn.workspace.revision) throw new Error('task_conflict:revision_mismatch');
90
+ if (!sameActionAllowlist(existing.allowedActions, turn.workspace.capabilities.allowedActions)) {
91
+ throw new Error('task_conflict:capability_mismatch');
92
+ }
93
+ if (existing.phase === 'waiting_receipt') throw new Error('receipt_required');
94
+ this.observeUserTurn(taskId, existing.contextRef, requestDigest, existing.userTurnCount + 1);
95
+ return this.requireTask(taskId);
96
+ }
97
+ const foreign = this.ledger.db.prepare("SELECT 1 AS present FROM events WHERE tenant_id = ? AND run_id = ? AND kind LIKE 'booking.copilot.v2.%' LIMIT 1").get(this.ledger.tenant, taskId);
98
+ if (foreign) throw new Error('task_conflict:protocol_version');
99
+ const payload = {
100
+ schema: LEDGER_SCHEMA,
101
+ taskId,
102
+ contextRef: turn.workspace.contextRef,
103
+ surface: turn.workspace.surface,
104
+ revision: turn.workspace.revision,
105
+ allowedActions: [
106
+ ...turn.workspace.capabilities.allowedActions
107
+ ]
108
+ };
109
+ this.appendLedgerEvent(TASK_STARTED, taskId, payload, `booking-copilot:task:${taskId}`);
110
+ this.observeUserTurn(taskId, turn.workspace.contextRef, requestDigest, 1);
111
+ return this.requireTask(taskId);
112
+ });
113
+ return apply.immediate();
114
+ }
115
+ resumeTask(taskId) {
116
+ assertTaskId(taskId);
117
+ const rows = this.readTaskRows(taskId);
118
+ if (rows.length === 0) return null;
119
+ let state = null;
120
+ for (const row of rows){
121
+ const payload = JSON.parse(row.payload);
122
+ if (payload.schema !== LEDGER_SCHEMA || payload.taskId !== taskId) throw new Error(`ledger_corrupt:${taskId}:seq=${row.seq}`);
123
+ if (row.kind === TASK_STARTED) {
124
+ const started = payload;
125
+ if (state) throw new Error(`ledger_corrupt:${taskId}:duplicate_start`);
126
+ state = {
127
+ schemaVersion: 'booking.surface.v1',
128
+ taskId,
129
+ contextRef: started.contextRef,
130
+ surface: started.surface,
131
+ revision: started.revision,
132
+ allowedActions: [
133
+ ...started.allowedActions
134
+ ],
135
+ userTurnCount: 0,
136
+ phase: 'planning',
137
+ lastSequence: 0
138
+ };
139
+ continue;
140
+ }
141
+ if (!state) throw new Error(`ledger_corrupt:${taskId}:event_before_start`);
142
+ if (payload.contextRef !== state.contextRef) throw new Error(`ledger_corrupt:${taskId}:context_drift`);
143
+ if (row.kind === USER_TURN_OBSERVED) {
144
+ const observed = payload;
145
+ state.userTurnCount += 1;
146
+ state.lastUserTurnDigest = observed.requestDigest;
147
+ } else if (row.kind === ACTION_ISSUED) {
148
+ const issued = payload;
149
+ if (issued.action.actionDigest !== checkpointDigest(issued.action)) throw new Error(`ledger_corrupt:${taskId}:action`);
150
+ if (state.pendingAction) throw new Error(`ledger_corrupt:${taskId}:parallel_actions`);
151
+ state.pendingAction = issued.action;
152
+ state.phase = 'waiting_receipt';
153
+ state.lastSequence = Math.max(state.lastSequence, issued.action.sequence);
154
+ } else if (row.kind === RECEIPT_OBSERVED) {
155
+ const observed = payload;
156
+ if (observed.receiptDigest !== digest(observed.receipt) || !validateActionReceiptV1(observed.receipt).ok) {
157
+ throw new Error(`ledger_corrupt:${taskId}:receipt`);
158
+ }
159
+ if (!state.pendingAction || state.pendingAction.actionId !== observed.receipt.actionId) {
160
+ throw new Error(`ledger_corrupt:${taskId}:orphan_receipt`);
161
+ }
162
+ state.lastReceipt = observed.receipt;
163
+ state.revision = observed.receipt.revision;
164
+ delete state.pendingAction;
165
+ state.phase = 'planning';
166
+ } else if (row.kind === EVENT_EMITTED) {
167
+ const emitted = payload;
168
+ state.lastSequence = Math.max(state.lastSequence, emitted.sequence);
169
+ }
170
+ }
171
+ return state;
172
+ }
173
+ issueOperation(taskId, action) {
174
+ const validation = validateBookingReadActionV1(action);
175
+ if (!validation.ok) throw new Error(`invalid_action:${validation.errors.join('; ')}`);
176
+ assertSafeFactRefs(action);
177
+ const apply = this.ledger.db.transaction(()=>{
178
+ const state = this.requireTask(taskId);
179
+ const reasonDigest = digest(action.reason);
180
+ const inputDigest = digest(action.input);
181
+ if (state.pendingAction) {
182
+ if (state.pendingAction.actionId !== action.actionId) throw new Error('receipt_required');
183
+ if (!actionMatchesCheckpoint(action, state.pendingAction)) throw new Error('action_conflict');
184
+ return {
185
+ schemaVersion: 'booking.surface.v1',
186
+ eventId: state.pendingAction.eventId,
187
+ taskId,
188
+ contextRef: state.pendingAction.contextRef,
189
+ sequence: state.pendingAction.sequence,
190
+ emittedAt: state.pendingAction.emittedAt,
191
+ kind: 'operation',
192
+ action
193
+ };
194
+ }
195
+ const historicalReceipt = this.ledger.db.prepare(`SELECT payload FROM events WHERE tenant_id = ? AND run_id = ? AND kind = ?`).all(this.ledger.tenant, taskId, RECEIPT_OBSERVED);
196
+ if (historicalReceipt.some((row)=>JSON.parse(row.payload).receipt?.actionId === action.actionId)) throw new Error('action_already_receipted');
197
+ if (state.lastReceipt?.actionId === action.actionId) throw new Error('action_already_receipted');
198
+ if (action.contextRef !== state.contextRef) throw new Error('context_mismatch');
199
+ if (!new Set(state.allowedActions).has(action.kind)) throw new Error('unsupported_action');
200
+ if (action.expectedRevision !== state.revision) throw new Error('stale_revision');
201
+ const event = {
202
+ schemaVersion: 'booking.surface.v1',
203
+ eventId: this.idFactory('event'),
204
+ taskId,
205
+ contextRef: state.contextRef,
206
+ sequence: state.lastSequence + 1,
207
+ emittedAt: this.now(),
208
+ kind: 'operation',
209
+ action
210
+ };
211
+ const eventValidation = validateBookingSurfaceEventV1(event);
212
+ if (!eventValidation.ok) throw new Error(`invalid_operation_event:${eventValidation.errors.join('; ')}`);
213
+ const checkpointBase = {
214
+ actionId: action.actionId,
215
+ kind: action.kind,
216
+ contextRef: action.contextRef,
217
+ expectedRevision: action.expectedRevision,
218
+ factRefs: [
219
+ ...action.factRefs
220
+ ],
221
+ reasonDigest,
222
+ inputDigest,
223
+ eventId: event.eventId,
224
+ sequence: event.sequence,
225
+ emittedAt: event.emittedAt
226
+ };
227
+ const checkpoint = {
228
+ ...checkpointBase,
229
+ actionDigest: checkpointDigest(checkpointBase)
230
+ };
231
+ const payload = {
232
+ schema: LEDGER_SCHEMA,
233
+ taskId,
234
+ contextRef: state.contextRef,
235
+ action: checkpoint
236
+ };
237
+ this.appendActionIssued(taskId, payload, `booking-copilot:action:${taskId.length}:${taskId}:${action.actionId.length}:${action.actionId}`);
238
+ return event;
239
+ });
240
+ return apply.immediate();
241
+ }
242
+ continueWithReceipt(turn) {
243
+ const validation = validateBookingCopilotTurnV1(turn);
244
+ if (!validation.ok || turn.kind !== 'action.receipt.continuation') {
245
+ throw new Error(`invalid_receipt_continuation:${validationMessage(validation)}`);
246
+ }
247
+ const receiptDigest = digest(turn.receipt);
248
+ const apply = this.ledger.db.transaction(()=>{
249
+ const state = this.requireTask(turn.taskId);
250
+ if (turn.workspace.contextRef !== state.contextRef || turn.receipt.contextRef !== state.contextRef) throw new Error('context_mismatch');
251
+ const history = this.ledger.db.prepare(`SELECT payload FROM events WHERE tenant_id = ? AND run_id = ? AND kind = ? ORDER BY seq`).all(this.ledger.tenant, turn.taskId, RECEIPT_OBSERVED);
252
+ for (const row of history){
253
+ const prior = JSON.parse(row.payload);
254
+ if (prior.receipt?.actionId !== turn.receipt.actionId) continue;
255
+ if (prior.schema !== LEDGER_SCHEMA || prior.taskId !== turn.taskId || prior.contextRef !== state.contextRef || prior.receiptDigest !== digest(prior.receipt) || !validateActionReceiptV1(prior.receipt).ok) throw new Error('ledger_corrupt:receipt');
256
+ if (prior.receiptDigest !== receiptDigest) throw new Error('receipt_conflict');
257
+ return state;
258
+ }
259
+ if (!state.pendingAction) throw new Error('unexpected_receipt');
260
+ if (state.pendingAction.actionId !== turn.receipt.actionId) throw new Error('receipt_action_mismatch');
261
+ if (turn.receipt.revision < state.pendingAction.expectedRevision) throw new Error('revision_regression');
262
+ const payload = {
263
+ schema: LEDGER_SCHEMA,
264
+ taskId: turn.taskId,
265
+ contextRef: state.contextRef,
266
+ receipt: turn.receipt,
267
+ receiptDigest
268
+ };
269
+ const idemKey = `booking-copilot:receipt:${turn.taskId.length}:${turn.taskId}:${turn.receipt.actionId.length}:${turn.receipt.actionId}`;
270
+ const info = this.ledger.db.prepare(`INSERT INTO events (tenant_id, ts, actor, kind, subject_id, payload, idem_key, run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(this.ledger.tenant, this.now(), LEDGER_ACTOR, RECEIPT_OBSERVED, turn.taskId, JSON.stringify(payload), idemKey, turn.taskId);
271
+ if (info.changes !== 1) throw new Error('ledger_write_failed:receipt');
272
+ return this.requireTask(turn.taskId);
273
+ });
274
+ return apply.immediate();
275
+ }
276
+ emitEvent(taskId, draft) {
277
+ const apply = this.ledger.db.transaction(()=>{
278
+ const state = this.requireTask(taskId);
279
+ const safeDraft = draft.kind === 'error' ? {
280
+ ...draft,
281
+ error: {
282
+ ...draft.error,
283
+ code: normalizeBookingErrorCode(draft.error.code),
284
+ message: safeBookingErrorMessage(normalizeBookingErrorCode(draft.error.code))
285
+ }
286
+ } : draft;
287
+ const event = {
288
+ schemaVersion: 'booking.surface.v1',
289
+ eventId: this.idFactory('event'),
290
+ taskId,
291
+ contextRef: state.contextRef,
292
+ sequence: state.lastSequence + 1,
293
+ emittedAt: this.now(),
294
+ ...safeDraft
295
+ };
296
+ const validation = validateBookingSurfaceEventV1(event);
297
+ if (!validation.ok) throw new Error(`invalid_event:${validation.errors.join('; ')}`);
298
+ const payload = {
299
+ schema: LEDGER_SCHEMA,
300
+ taskId,
301
+ contextRef: state.contextRef,
302
+ eventId: event.eventId,
303
+ sequence: event.sequence,
304
+ emittedAt: event.emittedAt,
305
+ eventKind: event.kind,
306
+ contentDigest: digest(safeDraft)
307
+ };
308
+ this.appendLedgerEvent(EVENT_EMITTED, taskId, payload, `booking-copilot:event:${taskId}:${event.eventId}`);
309
+ return event;
310
+ });
311
+ return apply.immediate();
312
+ }
313
+ requireTask(taskId) {
314
+ const state = this.resumeTask(taskId);
315
+ if (!state) throw new Error('task_not_found');
316
+ return state;
317
+ }
318
+ readTaskRows(taskId) {
319
+ return this.ledger.db.prepare(`SELECT seq, kind, payload FROM events
320
+ WHERE tenant_id = ? AND run_id = ?
321
+ AND kind IN (?, ?, ?, ?, ?)
322
+ ORDER BY seq`).all(this.ledger.tenant, taskId, TASK_STARTED, USER_TURN_OBSERVED, ACTION_ISSUED, RECEIPT_OBSERVED, EVENT_EMITTED);
323
+ }
324
+ observeUserTurn(taskId, contextRef, requestDigest, ordinal) {
325
+ const payload = {
326
+ schema: LEDGER_SCHEMA,
327
+ taskId,
328
+ contextRef,
329
+ requestDigest
330
+ };
331
+ this.appendLedgerEvent(USER_TURN_OBSERVED, taskId, payload, `booking-copilot:user-turn:${taskId}:${ordinal}`);
332
+ }
333
+ appendLedgerEvent(kind, taskId, payload, idemKey) {
334
+ const result = this.ledger.db.prepare(`INSERT OR IGNORE INTO events
335
+ (tenant_id, ts, actor, kind, subject_id, payload, idem_key, run_id)
336
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(this.ledger.tenant, this.now(), LEDGER_ACTOR, kind, taskId, JSON.stringify(payload), idemKey, taskId);
337
+ if (result.changes > 0) return Number(result.lastInsertRowid);
338
+ const existing = this.ledger.db.prepare('SELECT seq FROM events WHERE tenant_id = ? AND idem_key = ?').get(this.ledger.tenant, idemKey);
339
+ if (!existing) throw new Error(`ledger_write_failed:${kind}`);
340
+ return existing.seq;
341
+ }
342
+ appendActionIssued(taskId, payload, idemKey) {
343
+ const result = this.ledger.db.prepare(`INSERT INTO events (tenant_id, ts, actor, kind, subject_id, payload, idem_key, run_id)
344
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(this.ledger.tenant, this.now(), LEDGER_ACTOR, ACTION_ISSUED, taskId, JSON.stringify(payload), idemKey, taskId);
345
+ if (result.changes !== 1) throw new Error('ledger_write_failed:action');
346
+ return Number(result.lastInsertRowid);
347
+ }
348
+ }
349
+
350
+
351
+ //# sourceURL=ts/src/booking-surface/runtime.ts
@@ -0,0 +1,334 @@
1
+ import { BOOKING_SURFACE_SCHEMA_V2_SHA256, BOOKING_SURFACE_SCHEMA_VERSION_V2, BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS, BOOKING_READ_ACTION_KINDS_V2, BOOKING_SURFACE_ALLOWED_ACTIONS_V2 } from './contracts-v2.js';
2
+ import { BookingCopilotTaskRuntimeV2 } from './runtime-v2.js';
3
+ import { validateBookingSurfaceV2 } from './validation-v2.js';
4
+ import { normalizeBookingErrorCode, safeBookingErrorMessage } from './error-codes.js';
5
+ const sessionsByAdapter = new WeakMap();
6
+ const decisionFlightsByAdapter = new WeakMap();
7
+ function sessionsFor(adapter) {
8
+ let sessions = sessionsByAdapter.get(adapter);
9
+ if (!sessions) {
10
+ sessions = new Map();
11
+ sessionsByAdapter.set(adapter, sessions);
12
+ }
13
+ return sessions;
14
+ }
15
+ function decisionFlightsFor(adapter) {
16
+ let flights = decisionFlightsByAdapter.get(adapter);
17
+ if (!flights) {
18
+ flights = new Map();
19
+ decisionFlightsByAdapter.set(adapter, flights);
20
+ }
21
+ return flights;
22
+ }
23
+ async function runDecisionSingleFlight(adapter, key, work) {
24
+ const flights = decisionFlightsFor(adapter);
25
+ const prior = flights.get(key);
26
+ if (prior) return prior;
27
+ const current = work();
28
+ flights.set(key, current);
29
+ try {
30
+ return await current;
31
+ } finally{
32
+ if (flights.get(key) === current) flights.delete(key);
33
+ }
34
+ }
35
+ function send(res, status, body) {
36
+ res.writeHead(status, {
37
+ 'content-type': 'application/json; charset=utf-8'
38
+ });
39
+ res.end(JSON.stringify(body));
40
+ }
41
+ function readBody(req, max) {
42
+ return new Promise((resolve, reject)=>{
43
+ const chunks = [];
44
+ let size = 0;
45
+ let tooLarge = false;
46
+ req.on('data', (chunk)=>{
47
+ size += chunk.length;
48
+ if (size > max) tooLarge = true;
49
+ else chunks.push(chunk);
50
+ });
51
+ req.on('end', ()=>tooLarge ? reject(new Error('payload_too_large')) : resolve(Buffer.concat(chunks).toString('utf8')));
52
+ req.on('error', reject);
53
+ });
54
+ }
55
+ function write(res, event) {
56
+ res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`);
57
+ }
58
+ export { normalizeBookingErrorCode as bookingPlannerErrorCode, safeBookingErrorMessage as bookingPlannerErrorMessage } from './error-codes.js';
59
+ function sameCapabilities(a, b) {
60
+ return a.length === b.length && [
61
+ ...a
62
+ ].sort().every((kind, index)=>kind === [
63
+ ...b
64
+ ].sort()[index]);
65
+ }
66
+ function exactKeys(value, allowed) {
67
+ return Object.keys(value).length === allowed.length && Object.keys(value).every((key)=>allowed.includes(key));
68
+ }
69
+ const SURFACE_ACTION_MATRIX = BOOKING_SURFACE_ALLOWED_ACTIONS_V2;
70
+ function validSurfaceActions(surface, allowed) {
71
+ if (typeof surface !== 'string' || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, surface)) return false;
72
+ if (!Array.isArray(allowed) || allowed.length < 1 || new Set(allowed).size !== allowed.length) return false;
73
+ const matrix = SURFACE_ACTION_MATRIX[surface];
74
+ return allowed.every((action)=>typeof action === 'string' && matrix.includes(action) && BOOKING_READ_ACTION_KINDS_V2.includes(action));
75
+ }
76
+ function validBoundWorkspaceAuthority(workspace) {
77
+ return workspace.capabilities.surface === workspace.surface && validSurfaceActions(workspace.surface, workspace.capabilities.allowedActions);
78
+ }
79
+ function validIngressBinding(binding, ingress) {
80
+ if (!binding || typeof binding !== 'object' || Array.isArray(binding)) return false;
81
+ const candidate = binding;
82
+ if (!exactKeys(candidate, [
83
+ 'taskId',
84
+ 'turnId',
85
+ 'contextRef',
86
+ 'surface',
87
+ 'allowedActions'
88
+ ])) return false;
89
+ if (typeof candidate.taskId !== 'string' || typeof candidate.turnId !== 'string' || typeof candidate.contextRef !== 'string') return false;
90
+ if (typeof candidate.surface !== 'string' || candidate.surface !== ingress.surfaceHint || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, candidate.surface)) return false;
91
+ return validSurfaceActions(candidate.surface, candidate.allowedActions);
92
+ }
93
+ function fallbackErrorEvent(task, code) {
94
+ return {
95
+ schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION_V2,
96
+ eventId: `error-${task.taskId}-${task.lastSequence + 1}`,
97
+ taskId: task.taskId,
98
+ contextRef: task.contextRef,
99
+ sequence: task.lastSequence + 1,
100
+ emittedAt: new Date().toISOString(),
101
+ kind: 'error',
102
+ error: {
103
+ code: normalizeBookingErrorCode(code),
104
+ message: safeBookingErrorMessage(normalizeBookingErrorCode(code)),
105
+ retryable: false
106
+ }
107
+ };
108
+ }
109
+ function writeTypedError(res, adapter, task, error, requestKey, includeSubmitted = false) {
110
+ const code = normalizeBookingErrorCode(error);
111
+ try {
112
+ const decision = {
113
+ kind: 'error',
114
+ error: {
115
+ code,
116
+ message: safeBookingErrorMessage(code),
117
+ retryable: false
118
+ }
119
+ };
120
+ if (requestKey) {
121
+ const events = adapter.runtime.applyDecisionBatch(task.taskId, requestKey, [
122
+ decision
123
+ ], includeSubmitted);
124
+ for (const event of events)write(res, event);
125
+ return;
126
+ }
127
+ write(res, adapter.runtime.emitEvent(task.taskId, decision));
128
+ } catch {
129
+ write(res, fallbackErrorEvent(task, code));
130
+ }
131
+ }
132
+ export async function handleBookingCopilotV2Request(req, res, adapter, maxBodyBytes) {
133
+ let turn;
134
+ let browserRequestKey;
135
+ let requestBinding;
136
+ try {
137
+ const parsed = JSON.parse(await readBody(req, maxBodyBytes));
138
+ const valid = validateBookingSurfaceV2(parsed);
139
+ if (!valid.ok) {
140
+ send(res, 400, {
141
+ error: {
142
+ code: 'invalid_booking_surface_turn',
143
+ details: valid.errors
144
+ }
145
+ });
146
+ return;
147
+ }
148
+ if (parsed.kind === 'user.turn.ingress') {
149
+ const ingress = parsed;
150
+ browserRequestKey = ingress.requestKey;
151
+ if (adapter.ingressMode !== 'bff-ingress-binding' || !adapter.ingressBinding || !adapter.principal) {
152
+ send(res, 503, {
153
+ error: {
154
+ code: 'trusted_ingress_binding_required',
155
+ mode: adapter.ingressMode ?? 'bff-bound-turn-only',
156
+ acceptedTurnKinds: [
157
+ ...BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS
158
+ ]
159
+ }
160
+ });
161
+ return;
162
+ }
163
+ const binding = await adapter.ingressBinding.bind(ingress, adapter.principal);
164
+ if (!validIngressBinding(binding, ingress)) {
165
+ send(res, 502, {
166
+ error: {
167
+ code: 'invalid_ingress_binding'
168
+ }
169
+ });
170
+ return;
171
+ }
172
+ const boundWorkspace = {
173
+ ...ingress.workspace,
174
+ contextRef: binding.contextRef,
175
+ surface: binding.surface,
176
+ capabilities: {
177
+ surface: binding.surface,
178
+ allowedActions: [
179
+ ...binding.allowedActions
180
+ ]
181
+ }
182
+ };
183
+ turn = {
184
+ schemaVersion: 'booking.surface.v2',
185
+ kind: 'user.turn',
186
+ taskId: binding.taskId,
187
+ turnId: binding.turnId,
188
+ workspace: boundWorkspace,
189
+ request: ingress.request
190
+ };
191
+ requestBinding = {
192
+ requestKey: ingress.requestKey,
193
+ principal: adapter.principal,
194
+ ...ingress.taskHandle ? {
195
+ taskHandle: ingress.taskHandle
196
+ } : {}
197
+ };
198
+ const boundValid = validateBookingSurfaceV2(turn);
199
+ if (!boundValid.ok) {
200
+ send(res, 502, {
201
+ error: {
202
+ code: 'invalid_ingress_binding',
203
+ details: boundValid.errors
204
+ }
205
+ });
206
+ return;
207
+ }
208
+ } else {
209
+ turn = parsed;
210
+ if (!validBoundWorkspaceAuthority(turn.workspace)) {
211
+ send(res, 403, {
212
+ error: {
213
+ code: 'invalid_bound_turn_authority'
214
+ }
215
+ });
216
+ return;
217
+ }
218
+ }
219
+ } catch (error) {
220
+ const code = normalizeBookingErrorCode(error);
221
+ send(res, code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, {
222
+ error: {
223
+ code
224
+ }
225
+ });
226
+ return;
227
+ }
228
+ let task;
229
+ let fresh = false;
230
+ let replayKey;
231
+ let replayEvents = null;
232
+ let activeDecisionKey;
233
+ try {
234
+ fresh = turn.kind === 'action.receipt.continuation' ? false : !turn.taskId || adapter.runtime.resumeTask(turn.taskId) === null;
235
+ if (turn.kind === 'action.receipt.continuation') {
236
+ replayKey = `receipt:${turn.receipt.actionId}:${adapter.runtime.receiptDigest(turn.receipt)}`;
237
+ task = adapter.runtime.continueWithReceipt(turn);
238
+ if (task.awaitingApproval) {
239
+ replayKey = adapter.runtime.approvalPresentationRequestKey(turn.taskId);
240
+ }
241
+ replayEvents = adapter.runtime.readDecisionBatch(turn.taskId, replayKey);
242
+ if (!replayEvents && task.phase === 'terminal') replayEvents = adapter.runtime.terminalDecisionBatch(turn.taskId, replayKey);
243
+ } else {
244
+ const existing = turn.taskId ? adapter.runtime.resumeTask(turn.taskId) : null;
245
+ if (requestBinding && turn.kind === 'user.turn') adapter.runtime.assertRequestBinding(browserRequestKey, turn, requestBinding);
246
+ const requestReplayKey = existing && browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : undefined;
247
+ const directReplayKey = existing && turn.kind === 'user.turn' && turn.turnId ? `turn:${existing.taskId}:${turn.turnId}` : undefined;
248
+ const stableReplayKey = requestReplayKey ?? directReplayKey;
249
+ const stableReplay = stableReplayKey ? adapter.runtime.readDecisionBatch(existing.taskId, stableReplayKey) : null;
250
+ if (stableReplay) {
251
+ if (existing && turn.kind === 'user.turn' && directReplayKey) adapter.runtime.assertTurnBinding(existing.taskId, turn);
252
+ task = existing;
253
+ replayKey = stableReplayKey;
254
+ replayEvents = stableReplay;
255
+ } else if (existing?.phase === 'terminal' || existing?.phase === 'error') {
256
+ throw new Error('task_terminal');
257
+ } else if (existing?.phase === 'waiting_receipt') {
258
+ const workspace = turn.workspace;
259
+ 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');
260
+ if (!turn.turnId || existing.lastTurnId !== turn.turnId) throw new Error('receipt_required');
261
+ replayKey = browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : `turn:${existing.taskId}:${existing.lastTurnId ?? existing.userTurnCount}`;
262
+ replayEvents = adapter.runtime.readDecisionBatch(existing.taskId, replayKey);
263
+ if (!replayEvents) throw new Error('receipt_required');
264
+ task = existing;
265
+ } else {
266
+ task = adapter.runtime.startTask(turn, requestBinding);
267
+ if (turn.kind === 'user.turn' && turn.taskId) {
268
+ replayKey = browserRequestKey ? `ingress:${task.taskId}:${browserRequestKey}` : `turn:${task.taskId}:${turn.turnId ?? task.userTurnCount}`;
269
+ replayEvents = adapter.runtime.readDecisionBatch(task.taskId, replayKey);
270
+ }
271
+ }
272
+ }
273
+ } catch (error) {
274
+ send(res, 409, {
275
+ error: {
276
+ code: normalizeBookingErrorCode(error)
277
+ }
278
+ });
279
+ return;
280
+ }
281
+ res.writeHead(200, {
282
+ 'content-type': 'text/event-stream; charset=utf-8',
283
+ 'cache-control': 'no-cache, no-transform',
284
+ connection: 'keep-alive',
285
+ 'x-content-type-options': 'nosniff',
286
+ 'x-booking-surface-version': BOOKING_SURFACE_SCHEMA_VERSION_V2,
287
+ 'x-booking-surface-schema-sha256': BOOKING_SURFACE_SCHEMA_V2_SHA256
288
+ });
289
+ try {
290
+ if (replayEvents) {
291
+ for (const event of replayEvents)write(res, event);
292
+ } else {
293
+ const requestKey = replayKey ?? `turn:${task.taskId}:${task.userTurnCount}`;
294
+ activeDecisionKey = requestKey;
295
+ const flightKey = JSON.stringify([
296
+ task.taskId,
297
+ requestKey
298
+ ]);
299
+ const decisionEvents = await runDecisionSingleFlight(adapter, flightKey, async ()=>{
300
+ if (turn.kind === 'action.receipt.continuation' && task.awaitingApproval) {
301
+ return adapter.runtime.applyDecisionBatch(task.taskId, requestKey, [
302
+ adapter.runtime.approvalQuestion(task.taskId)
303
+ ], false);
304
+ }
305
+ const sessions = sessionsFor(adapter);
306
+ const session = sessions.get(task.taskId) ?? adapter.plannerFactory(task);
307
+ sessions.set(task.taskId, session);
308
+ const decisions = await session.next({
309
+ turn,
310
+ task: adapter.runtime.resumeTask(task.taskId) ?? task
311
+ });
312
+ const plannerDecisions = decisions.length ? decisions : [
313
+ {
314
+ kind: 'error',
315
+ error: {
316
+ code: 'PLANNER_NO_DECISION',
317
+ message: 'Planner returned no typed decision.',
318
+ retryable: false
319
+ }
320
+ }
321
+ ];
322
+ return adapter.runtime.applyDecisionBatch(task.taskId, requestKey, plannerDecisions, fresh);
323
+ });
324
+ for (const event of decisionEvents)write(res, event);
325
+ }
326
+ } catch (error) {
327
+ writeTypedError(res, adapter, task, error, activeDecisionKey, fresh);
328
+ } finally{
329
+ res.end();
330
+ }
331
+ }
332
+
333
+
334
+ //# sourceURL=ts/src/booking-surface/server-v2.ts