experimental-a2 0.17.0 → 0.18.0

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 (211) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-DBbe5xeE.d.ts → actor-Bjce9_5U.d.ts} +5 -5
  4. package/dist/{actor-DBbe5xeE.d.ts.map → actor-Bjce9_5U.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-react.d.ts +3 -3
  7. package/dist/actor-react.js +1 -1
  8. package/dist/actor.d.ts +1 -1
  9. package/dist/actor.js +2 -2
  10. package/dist/{ai-UrWfKeuv.js → ai-BpOGm-zB.js} +2 -2
  11. package/dist/{ai-UrWfKeuv.js.map → ai-BpOGm-zB.js.map} +1 -1
  12. package/dist/{ai-vpGtcaCB.d.ts → ai-DrUaMyRt.d.ts} +3 -3
  13. package/dist/{ai-vpGtcaCB.d.ts.map → ai-DrUaMyRt.d.ts.map} +1 -1
  14. package/dist/ai-client.d.ts +2 -2
  15. package/dist/ai-client.js +2 -2
  16. package/dist/ai-fx.browser.d.ts +1 -0
  17. package/dist/ai-fx.browser.js +6 -0
  18. package/dist/ai-fx.browser.js.map +1 -0
  19. package/dist/ai-fx.d.ts +10 -0
  20. package/dist/ai-fx.d.ts.map +1 -0
  21. package/dist/ai-fx.js +634 -0
  22. package/dist/ai-fx.js.map +1 -0
  23. package/dist/ai-harness-BuLh_EYi.js +273 -0
  24. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  25. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  26. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  27. package/dist/ai-pi.browser.d.ts +1 -0
  28. package/dist/ai-pi.browser.js +6 -0
  29. package/dist/ai-pi.browser.js.map +1 -0
  30. package/dist/ai-pi.d.ts +17 -0
  31. package/dist/ai-pi.d.ts.map +1 -0
  32. package/dist/ai-pi.js +904 -0
  33. package/dist/ai-pi.js.map +1 -0
  34. package/dist/ai-react.d.ts +5 -5
  35. package/dist/ai-react.js +2 -2
  36. package/dist/ai-server.d.ts +47 -25
  37. package/dist/ai-server.d.ts.map +1 -1
  38. package/dist/ai-server.js +106 -37
  39. package/dist/ai-server.js.map +1 -1
  40. package/dist/ai.d.ts +1 -1
  41. package/dist/ai.js +1 -1
  42. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  43. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  44. package/dist/{client-COaiaDub.js → client-BReKqwZC.js} +3 -3
  45. package/dist/{client-COaiaDub.js.map → client-BReKqwZC.js.map} +1 -1
  46. package/dist/{client-qRnRUzBS.d.ts → client-h_DKFAjj.d.ts} +3 -3
  47. package/dist/{client-qRnRUzBS.d.ts.map → client-h_DKFAjj.d.ts.map} +1 -1
  48. package/dist/client.d.ts +3 -3
  49. package/dist/client.js +1 -1
  50. package/dist/index.d.ts +5 -5
  51. package/dist/{initial-snapshot-BrLso5x_.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +2 -2
  52. package/dist/{initial-snapshot-BrLso5x_.d.ts.map → initial-snapshot-Bx-c0pDP.d.ts.map} +1 -1
  53. package/dist/{internal-CbVvEwad.js → internal-B2T3U4Rq.js} +8 -2
  54. package/dist/internal-B2T3U4Rq.js.map +1 -0
  55. package/dist/otel.d.ts +1 -1
  56. package/dist/{react-BbJ-g6Uu.d.ts → react-BBXgHWFc.d.ts} +4 -4
  57. package/dist/{react-BbJ-g6Uu.d.ts.map → react-BBXgHWFc.d.ts.map} +1 -1
  58. package/dist/react.d.ts +1 -1
  59. package/dist/react.js +1 -1
  60. package/dist/reducer-BAXc8JyF.js.map +1 -1
  61. package/dist/{reducer-BYu6DT7h.d.ts → reducer-CqA59Q6U.d.ts} +2 -2
  62. package/dist/{reducer-BYu6DT7h.d.ts.map → reducer-CqA59Q6U.d.ts.map} +1 -1
  63. package/dist/scheduler-qstash.d.ts +2 -2
  64. package/dist/scheduler-qstash.js +2 -2
  65. package/dist/scheduler-vercel.d.ts +2 -2
  66. package/dist/scheduler-vercel.js +2 -2
  67. package/dist/{server-Rj64fNvX.js → server-Dw_k_7kO.js} +93 -60
  68. package/dist/server-Dw_k_7kO.js.map +1 -0
  69. package/dist/{server-CGM8WzRG.d.ts → server-En7cbi3v.d.ts} +36 -7
  70. package/dist/server-En7cbi3v.d.ts.map +1 -0
  71. package/dist/server.d.ts +4 -4
  72. package/dist/server.js +1 -1
  73. package/dist/{session-clear-Dshpuhff.d.ts → session-clear-Bo-XTFEy.d.ts} +2 -2
  74. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  75. package/dist/{store-Gpkq7WBo.d.ts → store-Djdv0bnZ.d.ts} +31 -2
  76. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  77. package/dist/store-N8PXxDAS.js.map +1 -1
  78. package/dist/store-memory.d.ts +1 -1
  79. package/dist/store-memory.js +32 -6
  80. package/dist/store-memory.js.map +1 -1
  81. package/dist/{store-polling-4_y8lETm.js → store-polling-B5FcwJfo.js} +2 -2
  82. package/dist/{store-polling-4_y8lETm.js.map → store-polling-B5FcwJfo.js.map} +1 -1
  83. package/dist/store-postgres.d.ts +1 -1
  84. package/dist/store-postgres.js +54 -3
  85. package/dist/store-postgres.js.map +1 -1
  86. package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-BAwS8RWn.js} +3 -3
  87. package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  88. package/dist/store-redis-http.d.ts +1 -1
  89. package/dist/store-redis-http.js +3 -3
  90. package/dist/{store-redis-notify-tqWNnMRu.js → store-redis-notify-BRITti_o.js} +187 -68
  91. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  92. package/dist/store-redis.d.ts +1 -1
  93. package/dist/store-redis.js +1 -1
  94. package/dist/store-sqlite.d.ts +1 -1
  95. package/dist/store-sqlite.js +32 -2
  96. package/dist/store-sqlite.js.map +1 -1
  97. package/dist/{telemetry-cZH-cibj.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  98. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  99. package/dist/{wire-DjfHOvdW.js → wire-rSUglYo8.js} +2 -2
  100. package/dist/{wire-DjfHOvdW.js.map → wire-rSUglYo8.js.map} +1 -1
  101. package/docs/01-quickstart.mdx +49 -83
  102. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  103. package/docs/actors/01-introduction.mdx +48 -86
  104. package/docs/actors/02-concurrency.mdx +43 -70
  105. package/docs/actors/03-timers.mdx +28 -42
  106. package/docs/actors/04-routes.mdx +110 -150
  107. package/docs/actors/meta.ts +1 -1
  108. package/docs/agents/01-introduction.mdx +257 -0
  109. package/docs/agents/02-tools.mdx +247 -0
  110. package/docs/agents/03-conversations.mdx +538 -0
  111. package/docs/agents/04-harnesses.mdx +136 -0
  112. package/docs/agents/meta.ts +1 -0
  113. package/docs/client/01-react.mdx +294 -0
  114. package/docs/client/02-transports.mdx +86 -0
  115. package/docs/client/03-presence.mdx +224 -0
  116. package/docs/client/meta.ts +1 -0
  117. package/docs/contract/01-events.mdx +57 -0
  118. package/docs/contract/02-state.mdx +142 -0
  119. package/docs/contract/03-sessions.mdx +175 -0
  120. package/docs/index.mdx +108 -144
  121. package/docs/reference/01-core.mdx +139 -0
  122. package/docs/reference/02-server.mdx +855 -0
  123. package/docs/reference/03-client.mdx +301 -0
  124. package/docs/reference/04-ai.mdx +722 -0
  125. package/docs/reference/05-adapters.mdx +351 -0
  126. package/docs/reference/06-errors.mdx +138 -0
  127. package/docs/reference/meta.ts +1 -1
  128. package/docs/server/01-handlers.mdx +200 -0
  129. package/docs/server/02-timers.mdx +130 -0
  130. package/docs/server/03-cancellation.mdx +141 -0
  131. package/docs/server/04-routes.mdx +243 -0
  132. package/docs/server/05-production.mdx +322 -0
  133. package/docs/server/06-guarantees.mdx +123 -0
  134. package/examples/playground/DEPLOY.md +1 -1
  135. package/examples/playground/app/a0/[appId]/page.tsx +11 -6
  136. package/examples/playground/app/a0/app-list.tsx +9 -2
  137. package/examples/playground/app/a0/app-view.tsx +9 -1
  138. package/examples/playground/app/a0/apps/[appId]/route.ts +10 -5
  139. package/examples/playground/app/a0/apps.test.ts +12 -0
  140. package/examples/playground/app/a0/apps.ts +22 -7
  141. package/examples/playground/app/a0/chat.tsx +47 -1
  142. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  143. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  144. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  145. package/examples/playground/app/a0/model.test.ts +18 -1
  146. package/examples/playground/app/a0/model.ts +47 -10
  147. package/examples/playground/app/a0/no-photon.cjs +4 -0
  148. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  149. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  150. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  151. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  152. package/examples/playground/app/a0/sandbox-log.ts +10 -4
  153. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  154. package/examples/playground/app/a0/scheduler/route.ts +2 -2
  155. package/examples/playground/app/a0/server.ts +100 -45
  156. package/examples/playground/app/a0/session.ts +17 -5
  157. package/examples/playground/app/a0/shell.tsx +11 -3
  158. package/examples/playground/app/agent/ingress.test.ts +6 -3
  159. package/examples/playground/app/agent/server.ts +9 -6
  160. package/examples/playground/app/agent/telemetry.test.ts +71 -50
  161. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +1 -1
  162. package/examples/playground/app/chat/server.test.ts +30 -26
  163. package/examples/playground/app/chat/server.ts +46 -46
  164. package/examples/playground/next.config.ts +17 -5
  165. package/examples/playground/package.json +4 -1
  166. package/examples/playground/vercel.json +18 -0
  167. package/package.json +27 -3
  168. package/src/ai-agent.ts +261 -87
  169. package/src/ai-fx.browser.ts +5 -0
  170. package/src/ai-fx.ts +833 -0
  171. package/src/ai-harness.ts +518 -0
  172. package/src/ai-model-metadata.ts +11 -6
  173. package/src/ai-pi.browser.ts +5 -0
  174. package/src/ai-pi.ts +1263 -0
  175. package/src/ai-sdk-step.ts +2 -2
  176. package/src/ai-server.ts +3 -1
  177. package/src/internal.ts +7 -0
  178. package/src/libfx.d.ts +34 -0
  179. package/src/reducer.ts +1 -1
  180. package/src/server.ts +177 -19
  181. package/src/store-memory.ts +42 -7
  182. package/src/store-postgres.ts +80 -0
  183. package/src/store-redis-core.ts +210 -79
  184. package/src/store-redis-notify.ts +2 -0
  185. package/src/store-sqlite.ts +60 -0
  186. package/src/store.ts +33 -0
  187. package/src/telemetry.ts +1 -0
  188. package/dist/internal-CbVvEwad.js.map +0 -1
  189. package/dist/server-CGM8WzRG.d.ts.map +0 -1
  190. package/dist/server-Rj64fNvX.js.map +0 -1
  191. package/dist/session-clear-Dshpuhff.d.ts.map +0 -1
  192. package/dist/store-Gpkq7WBo.d.ts.map +0 -1
  193. package/dist/store-redis-notify-tqWNnMRu.js.map +0 -1
  194. package/dist/telemetry-cZH-cibj.d.ts.map +0 -1
  195. package/docs/concepts/01-contracts.mdx +0 -237
  196. package/docs/concepts/02-handlers.mdx +0 -284
  197. package/docs/concepts/03-durability.mdx +0 -314
  198. package/docs/concepts/04-state.mdx +0 -209
  199. package/docs/guides/01-timers.mdx +0 -193
  200. package/docs/guides/02-cancellation.mdx +0 -161
  201. package/docs/guides/03-react.mdx +0 -324
  202. package/docs/guides/04-local-first.mdx +0 -47
  203. package/docs/guides/05-production.mdx +0 -534
  204. package/docs/guides/06-ai-agents.mdx +0 -1408
  205. package/docs/guides/08-application-data.mdx +0 -113
  206. package/docs/guides/09-presence.mdx +0 -262
  207. package/docs/guides/10-transports.mdx +0 -221
  208. package/docs/reference/01-api.mdx +0 -2358
  209. package/docs/reference/02-errors.mdx +0 -150
  210. /package/docs/{concepts → contract}/meta.ts +0 -0
  211. /package/docs/{guides → server}/meta.ts +0 -0
@@ -123,6 +123,23 @@ const READ_CONCURRENCY = 16
123
123
 
124
124
  const META_CHUNK_SIZE = 1000
125
125
 
126
+ /** The metadata columns of a full stored row. */
127
+ const STORED_EVENT_META_SUFFIXES = [
128
+ 'p',
129
+ 'pa',
130
+ 'ri',
131
+ 'ch',
132
+ 'ce',
133
+ 'f',
134
+ 'fc',
135
+ 'lc',
136
+ 'lf',
137
+ 'lfa',
138
+ 'd',
139
+ 'a',
140
+ 'e',
141
+ ] as const
142
+
126
143
  /**
127
144
  * Upstash bills every call a script makes, so scripts read the counter and
128
145
  * split with one MGET, read metadata with one HMGET per hash (`read_fields`),
@@ -278,6 +295,33 @@ local function flush_queue()
278
295
  end
279
296
  `
280
297
 
298
+ /**
299
+ * Settles one unfinished event for scripts that include ENQUEUE_LUA: it
300
+ * leaves the pending and ready sets, and a lane head hands its lane to the
301
+ * next unfinished event. A dead-lettered successor stays out of the ready set.
302
+ */
303
+ const SETTLE_LUA = `
304
+ local function settle(field, lane, next_field)
305
+ redis.call('ZREM', PENDING, field)
306
+ redis.call('ZREM', READY, field)
307
+ if lane == '' then return end
308
+ local head_key = lane_field(lane, 'head')
309
+ if redis.call('HGET', META, head_key) ~= field then return end
310
+ while next_field do
311
+ local row = read_fields({ next_field }, { 'p', 'f', 'next' })[next_field]
312
+ if not row[1] then
313
+ redis.call('HSET', META, head_key, next_field)
314
+ if not row[2] then
315
+ redis.call('ZADD', READY, tonumber(next_field), next_field)
316
+ end
317
+ return
318
+ end
319
+ next_field = row[3]
320
+ end
321
+ clear_lane(lane)
322
+ end
323
+ `
324
+
281
325
  /**
282
326
  * Event-id ownership for scripts that add events. Each script reads the
283
327
  * counter, split, clear boundary, and per-id keys with one MGET and applies
@@ -611,25 +655,7 @@ ${ENQUEUE_LUA}
611
655
  ${ID_OWNERS_LUA}
612
656
  ${RELEASE_PINS_LUA}
613
657
  ${WAKE_LUA}
614
- local function settle(field, lane, next_field)
615
- redis.call('ZREM', PENDING, field)
616
- redis.call('ZREM', READY, field)
617
- if lane == '' then return end
618
- local head_key = lane_field(lane, 'head')
619
- if redis.call('HGET', META, head_key) ~= field then return end
620
- while next_field do
621
- local row = read_fields({ next_field }, { 'p', 'f', 'next' })[next_field]
622
- if not row[1] then
623
- redis.call('HSET', META, head_key, next_field)
624
- if not row[2] then
625
- redis.call('ZADD', READY, tonumber(next_field), next_field)
626
- end
627
- return
628
- end
629
- next_field = row[3]
630
- end
631
- clear_lane(lane)
632
- end
658
+ ${SETTLE_LUA}
633
659
  local parent = ARGV[3]
634
660
  local attempt = tonumber(ARGV[4])
635
661
  local n = tonumber(ARGV[5])
@@ -715,6 +741,78 @@ if n > 0 then wake(ARGV[6 + n * 5], tostring(total + n), ARGV[2]) end
715
741
  return { 'ok', tostring(total + 1) }
716
742
  `
717
743
 
744
+ /**
745
+ * KEYS: counter, meta, pending, ready, metadata split.
746
+ * ARGV: idx, action ('retry' | 'skip'), nowMs.
747
+ * Returns [redriven, hasPending]. Acts only on an unfinished, dead-lettered
748
+ * event above the clear boundary. A retry keeps the event pending and makes
749
+ * it ready again when it is unlaned or its lane's head.
750
+ */
751
+ const REDRIVE_LUA = `
752
+ local COUNTER, META, SPLIT, PENDING, READY = KEYS[1], KEYS[2], KEYS[5], KEYS[3], KEYS[4]
753
+ ${META_LUA}
754
+ ${ENQUEUE_LUA}
755
+ ${SETTLE_LUA}
756
+ local function result(redriven)
757
+ return { redriven, redis.call('ZCARD', PENDING) > 0 and '1' or '0' }
758
+ end
759
+ local counts = redis.call('MGET', COUNTER, SPLIT, CLEAR)
760
+ local total = tonumber(counts[1] or '0')
761
+ use_split(counts[2], counts[1], counts[3])
762
+ local idx = tonumber(ARGV[1])
763
+ if idx <= boundary or idx > total then return result('0') end
764
+ local field = tostring(idx)
765
+ local row = read_fields({ field }, { 'p', 'f', 'l', 'next' })[field]
766
+ if row[1] or not row[2] then return result('0') end
767
+ local meta_key = mkey(field)
768
+ local lane = row[3] or ''
769
+ if ARGV[2] == 'skip' then
770
+ put(meta_key, field .. ':p', ARGV[3])
771
+ settle(field, lane, row[4])
772
+ flush_queue()
773
+ flush()
774
+ else
775
+ redis.call('HDEL', meta_key, field .. ':f', field .. ':a')
776
+ if lane == '' or redis.call('HGET', META, lane_field(lane, 'head')) == field then
777
+ redis.call('ZADD', READY, idx, field)
778
+ end
779
+ end
780
+ return result('1')
781
+ `
782
+
783
+ /**
784
+ * KEYS: store, counter, meta, pending, metadata split.
785
+ * Returns the dead-lettered events above the clear boundary, oldest first,
786
+ * as pairs of a stream entry and its STORED_EVENT_META_SUFFIXES columns.
787
+ * Every unfinished event is in the pending set, so the scan is bounded by
788
+ * unfinished work, not history; only dead letters read their full columns.
789
+ */
790
+ const DEAD_LETTERS_LUA = `
791
+ local COUNTER, META, SPLIT = KEYS[2], KEYS[3], KEYS[5]
792
+ ${META_LUA}
793
+ local counts = redis.call('MGET', COUNTER, SPLIT, CLEAR)
794
+ use_split(counts[2], counts[1], counts[3])
795
+ local candidates = {}
796
+ for _, field in ipairs(redis.call('ZRANGE', KEYS[4], 0, -1)) do
797
+ if tonumber(field) > boundary then candidates[#candidates + 1] = field end
798
+ end
799
+ local status = read_fields(candidates, { 'p', 'f' })
800
+ local dead = {}
801
+ for _, field in ipairs(candidates) do
802
+ if status[field][2] and not status[field][1] then dead[#dead + 1] = field end
803
+ end
804
+ local rows = read_fields(dead, { ${STORED_EVENT_META_SUFFIXES.map((suffix) => `'${suffix}'`).join(', ')} })
805
+ local result = {}
806
+ for _, field in ipairs(dead) do
807
+ local entries = redis.call('XRANGE', KEYS[1], field .. '-0', field .. '-0')
808
+ if #entries > 0 then
809
+ result[#result + 1] = entries[1]
810
+ result[#result + 1] = rows[field]
811
+ end
812
+ end
813
+ return result
814
+ `
815
+
718
816
  /**
719
817
  * KEYS: head, history, event counter, event meta, pin counts, metadata split.
720
818
  * ARGV: nowMs, event pin key prefix, write count, then per write:
@@ -1117,22 +1215,6 @@ const fieldMap = (fields: string[]): Record<string, string> => {
1117
1215
  return map
1118
1216
  }
1119
1217
 
1120
- const STORED_EVENT_META_SUFFIXES = [
1121
- 'p',
1122
- 'pa',
1123
- 'ri',
1124
- 'ch',
1125
- 'ce',
1126
- 'f',
1127
- 'fc',
1128
- 'lc',
1129
- 'lf',
1130
- 'lfa',
1131
- 'd',
1132
- 'a',
1133
- 'e',
1134
- ] as const
1135
-
1136
1218
  const entryIndex = (entryId: string): number =>
1137
1219
  Number(entryId.slice(0, entryId.indexOf('-')))
1138
1220
 
@@ -1240,6 +1322,46 @@ const toCause = (raw: string | undefined): EventCause | null => {
1240
1322
  }
1241
1323
  }
1242
1324
 
1325
+ type MetaSuffix = (typeof STORED_EVENT_META_SUFFIXES)[number]
1326
+
1327
+ /** A stream entry joined with its metadata columns; absent columns are unset. */
1328
+ const toStoredEvent = (
1329
+ sessionId: string,
1330
+ entry: StreamEntry,
1331
+ column: (suffix: MetaSuffix) => string | undefined,
1332
+ ): StoredEvent => {
1333
+ const fields = fieldMap(entry[1])
1334
+ const date = (suffix: MetaSuffix): Date | null => {
1335
+ const value = column(suffix)
1336
+ return value === undefined ? null : new Date(Number(value))
1337
+ }
1338
+ const count = (suffix: MetaSuffix): number | null => {
1339
+ const value = column(suffix)
1340
+ return value === undefined ? null : Number(value)
1341
+ }
1342
+ const returnedEventIds = column('ri')
1343
+ return Object.assign(toEvent(sessionId, entry), {
1344
+ cause: toCause(fields['cause']),
1345
+ lane: fields['lane'] === '' ? null : fields['lane']!,
1346
+ processedAt: date('p'),
1347
+ processedByAttempt: count('pa'),
1348
+ returnedEventIds:
1349
+ returnedEventIds === undefined
1350
+ ? null
1351
+ : decodeReturnedEventIds(returnedEventIds),
1352
+ firstClaimedAt: date('fc'),
1353
+ lastClaimedAt: date('lc'),
1354
+ attemptCount: count('d') ?? 0,
1355
+ claimHolder: column('ch') ?? null,
1356
+ claimExpiresAt: date('ce'),
1357
+ failureCount: count('a') ?? 0,
1358
+ lastFailedAt: date('lf'),
1359
+ lastFailedAttempt: count('lfa'),
1360
+ lastError: column('e') ?? null,
1361
+ failedAt: date('f'),
1362
+ })
1363
+ }
1364
+
1243
1365
  const toClaimAvailableResult = (
1244
1366
  sessionId: string,
1245
1367
  reply: unknown[],
@@ -1471,50 +1593,12 @@ export function createRedisStoreCore(
1471
1593
  }
1472
1594
  }
1473
1595
  return entries.map((entry) => {
1474
- const event = toEvent(sessionId, entry)
1475
- const idx = event.index
1476
- const fields = fieldMap(entry[1])
1477
- const processedAt = meta[`${idx}:p`]
1478
- const processedByAttempt = meta[`${idx}:pa`]
1479
- const returnedEventIds = meta[`${idx}:ri`]
1480
- const claimHolder = meta[`${idx}:ch`]
1481
- const claimExpiresAt = meta[`${idx}:ce`]
1482
- const failedAt = meta[`${idx}:f`]
1483
- const firstClaimedAt = meta[`${idx}:fc`]
1484
- const lastClaimedAt = meta[`${idx}:lc`]
1485
- const lastFailedAt = meta[`${idx}:lf`]
1486
- const lastFailedAttempt = meta[`${idx}:lfa`]
1487
- return Object.assign(event, {
1488
- cause: toCause(fields['cause']),
1489
- lane: fields['lane'] === '' ? null : fields['lane']!,
1490
- processedAt:
1491
- processedAt === undefined ? null : new Date(Number(processedAt)),
1492
- processedByAttempt:
1493
- processedByAttempt === undefined ? null : Number(processedByAttempt),
1494
- returnedEventIds:
1495
- returnedEventIds === undefined
1496
- ? null
1497
- : decodeReturnedEventIds(returnedEventIds),
1498
- firstClaimedAt:
1499
- firstClaimedAt === undefined
1500
- ? null
1501
- : new Date(Number(firstClaimedAt)),
1502
- lastClaimedAt:
1503
- lastClaimedAt === undefined ? null : new Date(Number(lastClaimedAt)),
1504
- attemptCount: Number(meta[`${idx}:d`] ?? 0),
1505
- claimHolder: claimHolder ?? null,
1506
- claimExpiresAt:
1507
- claimExpiresAt === undefined
1508
- ? null
1509
- : new Date(Number(claimExpiresAt)),
1510
- failureCount: Number(meta[`${idx}:a`] ?? 0),
1511
- lastFailedAt:
1512
- lastFailedAt === undefined ? null : new Date(Number(lastFailedAt)),
1513
- lastFailedAttempt:
1514
- lastFailedAttempt === undefined ? null : Number(lastFailedAttempt),
1515
- lastError: meta[`${idx}:e`] ?? null,
1516
- failedAt: failedAt === undefined ? null : new Date(Number(failedAt)),
1517
- })
1596
+ const idx = entryIndex(entry[0])
1597
+ return toStoredEvent(
1598
+ sessionId,
1599
+ entry,
1600
+ (suffix) => meta[`${idx}:${suffix}`],
1601
+ )
1518
1602
  })
1519
1603
  }
1520
1604
 
@@ -2039,6 +2123,53 @@ export function createRedisStoreCore(
2039
2123
  })
2040
2124
  },
2041
2125
 
2126
+ async deadLetters(sessionId) {
2127
+ return wrap(async () => {
2128
+ const reply = (await evalScript(
2129
+ DEAD_LETTERS_LUA,
2130
+ [
2131
+ storeKey(sessionId),
2132
+ countKey(sessionId),
2133
+ metaKey(sessionId),
2134
+ pendingKey(sessionId),
2135
+ metaSplitKey(sessionId),
2136
+ ],
2137
+ [],
2138
+ )) as unknown[]
2139
+ const events: StoredEvent[] = []
2140
+ for (let i = 0; i + 1 < reply.length; i += 2) {
2141
+ const columns = reply[i + 1] as Array<string | null>
2142
+ events.push(
2143
+ toStoredEvent(
2144
+ sessionId,
2145
+ reply[i] as StreamEntry,
2146
+ (suffix) =>
2147
+ columns[STORED_EVENT_META_SUFFIXES.indexOf(suffix)] ??
2148
+ undefined,
2149
+ ),
2150
+ )
2151
+ }
2152
+ return events
2153
+ })
2154
+ },
2155
+
2156
+ async redrive({ sessionId, index, action }) {
2157
+ return wrap(async () => {
2158
+ const [redriven, hasPending] = (await evalScript(
2159
+ REDRIVE_LUA,
2160
+ [
2161
+ countKey(sessionId),
2162
+ metaKey(sessionId),
2163
+ pendingKey(sessionId),
2164
+ readyKey(sessionId),
2165
+ metaSplitKey(sessionId),
2166
+ ],
2167
+ [index, action, clock.now().getTime()],
2168
+ )) as [string, string]
2169
+ return { redriven: redriven === '1', hasPending: hasPending === '1' }
2170
+ })
2171
+ },
2172
+
2042
2173
  async readState(sessionId, reducerName, stateOptions) {
2043
2174
  return wrap(() =>
2044
2175
  retryAfterGap(async () => {
@@ -292,6 +292,8 @@ export function createRedisNotifyStore(options: {
292
292
  putSnapshots: core.putSnapshots,
293
293
  clear: core.clear!,
294
294
  reclaim: core.reclaim!,
295
+ deadLetters: core.deadLetters!,
296
+ redrive: core.redrive!,
295
297
 
296
298
  presence: {
297
299
  async set(ns, participant, values, meta) {
@@ -988,6 +988,66 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
988
988
  )
989
989
  },
990
990
 
991
+ async deadLetters(sessionId) {
992
+ return wrap(() => {
993
+ const rows = db
994
+ .prepare(
995
+ `select * from a2_events
996
+ where session_id = ?
997
+ and processed_at is null
998
+ and failed_at is not null
999
+ and idx > coalesce(
1000
+ (select clear_index from a2_clears where session_id = ?), 0
1001
+ )
1002
+ order by idx`,
1003
+ )
1004
+ .all(sessionId, sessionId) as unknown as EventRow[]
1005
+ return rows.map(toStored)
1006
+ })
1007
+ },
1008
+
1009
+ async redrive({ sessionId, index, action }) {
1010
+ return wrap(() =>
1011
+ tx(() => {
1012
+ const clearIndex = clearIndexOf(sessionId)
1013
+ const row =
1014
+ index > clearIndex
1015
+ ? (db
1016
+ .prepare(
1017
+ 'select lane, processed_at, failed_at from a2_events where session_id = ? and idx = ?',
1018
+ )
1019
+ .get(sessionId, index) as
1020
+ | Pick<EventRow, 'lane' | 'processed_at' | 'failed_at'>
1021
+ | undefined)
1022
+ : undefined
1023
+ const redriven =
1024
+ row !== undefined &&
1025
+ row.processed_at === null &&
1026
+ row.failed_at !== null
1027
+ if (redriven && action === 'skip') {
1028
+ db.prepare(
1029
+ `update a2_events set processed_at = ?, lane_ready = 0
1030
+ where session_id = ? and idx = ?`,
1031
+ ).run(clock.now().getTime(), sessionId, index)
1032
+ if (row.lane !== null) {
1033
+ promoteLaneHead.run(sessionId, sessionId, row.lane, clearIndex)
1034
+ }
1035
+ } else if (redriven) {
1036
+ db.prepare(
1037
+ `update a2_events set failed_at = null, failure_count = 0
1038
+ where session_id = ? and idx = ?`,
1039
+ ).run(sessionId, index)
1040
+ }
1041
+ const status = appendStatus.get(
1042
+ sessionId,
1043
+ sessionId,
1044
+ clearIndex,
1045
+ ) as AppendStatusRow
1046
+ return { redriven, hasPending: Number(status.has_pending) === 1 }
1047
+ }),
1048
+ )
1049
+ },
1050
+
991
1051
  async readState(sessionId, reducerName, stateOptions) {
992
1052
  return wrap(() => {
993
1053
  const throughIndex = stateOptions?.throughIndex ?? null
package/src/store.ts CHANGED
@@ -174,6 +174,20 @@ export type StoreClearResult = {
174
174
 
175
175
  export type StoreReclaimResult = { deleted: number; done: boolean }
176
176
 
177
+ export type StoreRedriveOptions = {
178
+ sessionId: string
179
+ index: number
180
+ /** `retry` clears the dead letter; `skip` settles it without a handler. */
181
+ action: 'retry' | 'skip'
182
+ }
183
+
184
+ export type StoreRedriveResult = {
185
+ /** Whether the event was dead-lettered and this operation resolved it. */
186
+ redriven: boolean
187
+ /** Whether the session contains an event without a completion marker. */
188
+ hasPending: boolean
189
+ }
190
+
177
191
  /**
178
192
  * An injectable clock. Adapters take one so tests can drive claim
179
193
  * expiry, failure timestamps, and (later) stuck-session detection
@@ -241,6 +255,25 @@ export interface A2Store {
241
255
  */
242
256
  reclaim?(sessionId: string): Promise<StoreReclaimResult>
243
257
 
258
+ /**
259
+ * Optional with `redrive`. The session's dead-lettered events above the
260
+ * latest clear marker, oldest first.
261
+ */
262
+ deadLetters?(sessionId: string): Promise<StoredEvent[]>
263
+
264
+ /**
265
+ * Optional with `deadLetters`. Atomically resolves one dead-lettered event
266
+ * above the latest clear marker; any other event is untouched and reports
267
+ * `redriven: false`. `retry` clears `failedAt` and `failureCount`, so the
268
+ * event is claimable again with a fresh failure budget, keeping its claim
269
+ * history and last failure. `skip` records `processedAt` with no
270
+ * `processedByAttempt` or `returnedEventIds` and exposes the next event in
271
+ * its lane. Both serialize with `claimAvailable` and the handler-append
272
+ * parent fence; `skip` also serializes with lane promotion in `append` and
273
+ * `completeAttempt`. `hasPending` is read in the same step.
274
+ */
275
+ redrive?(options: StoreRedriveOptions): Promise<StoreRedriveResult>
276
+
244
277
  /** Events for one session, oldest first. Bounds form `(afterIndex, throughIndex]`. */
245
278
  read(
246
279
  sessionId: string,
package/src/telemetry.ts CHANGED
@@ -17,6 +17,7 @@ export type A2SpanName =
17
17
  | 'a2.commit'
18
18
  | 'a2.state'
19
19
  | 'a2.snapshot'
20
+ | 'a2.redrive'
20
21
  | 'a2.ai.control'
21
22
  | 'a2.ai.ack'
22
23
  | 'a2.ai.generate'
@@ -1 +0,0 @@
1
- {"version":3,"file":"internal-CbVvEwad.js","names":[],"sources":["../src/internal.ts"],"sourcesContent":["/**\n * Private seams shared by a2 entry points and a2's own tests. Not exported\n * from the package. Nothing here is public API.\n */\n\nimport type { ScheduledEvent } from './scheduler-task.ts'\nimport type { DrainableServer } from './server.ts'\nimport type { Event } from './store.ts'\n\nexport type DrainOutcome = 'busy' | 'stalled' | 'settled'\n\n/**\n * A null-prototype record for maps keyed by caller-controlled strings\n * (participant ids, presence field names). On a normal object,\n * assigning to '__proto__' rewrites the prototype instead of creating\n * an own property — the prototype-pollution class. Null-prototype\n * objects still satisfy Record types and JSON.stringify cleanly.\n */\nexport function nullProtoRecord<T extends object>(): T {\n return Object.create(null) as T\n}\n\n/**\n * ECMA-262's maximum Date value (±8.64e15 ms). A presence stamp past\n * it becomes an Invalid Date whose toISOString throws — one hostile\n * stamp would poison every subscriber's snapshot frame — so both\n * seams bound `at` to the representable range.\n */\nexport const MAX_DATE_MS = 8_640_000_000_000_000\n\n/**\n * Participant ids that collide with Object.prototype plumbing.\n * Null-prototype construction already defuses them, but they are\n * rejected at both seams (`parsePushBody`, `setPresence`) so a hostile\n * id never propagates into userland maps either.\n */\nexport const RESERVED_PARTICIPANT_IDS: ReadonlySet<string> = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n])\n\nexport type DrainResult = {\n settled: boolean\n outcome: DrainOutcome\n /** Existing successor arm a scheduler callback may join before acking. */\n schedulerArm?: Promise<void>\n}\n\nexport type ServerInternals = {\n /**\n * Await every in-flight background drain this machine has scheduled,\n * including ones scheduled while waiting. Lets tests flush the\n * fire-and-forget inline path deterministically — no sleeps.\n */\n settle(): Promise<void>\n /** The public `server.drain()` result deliberately remains `{ settled }`. */\n schedulerDrain(\n sessionId: string,\n opts?: { watchdogDueAt?: number },\n ): Promise<DrainResult>\n /** Deliver a scheduler-owned delayed append through the normal root path. */\n schedulerAppend(\n sessionId: string,\n events: readonly ScheduledEvent[],\n originIndex?: number,\n ): Promise<void>\n /** Deliver a scheduler-owned conditional clear, deletion included. */\n schedulerClear(sessionId: string, ifLastIndex: number): Promise<void>\n /** `session.history` with clear markers kept; reads start at the latest one. */\n historyWithMarkers(sessionId: string, gte: number): Promise<Event[]>\n}\n\nexport const serverInternals: WeakMap<object, ServerInternals> = new WeakMap()\n\nconst objectLike = (value: unknown): value is object =>\n (typeof value === 'object' && value !== null) || typeof value === 'function'\n\n/** The ambient value delivered to `handlerContext` during a tool execution. */\nexport type AmbientToolScope = {\n /** Executing server identity; a test scope from `runWithHandlerContext` has none. */\n contract?: { readonly name: string }\n context: unknown\n}\n\n/**\n * Structural AsyncLocalStorage surface for the ambient tool scope. The\n * instance is created by the server-only AI entry point (the only place\n * `node:async_hooks` may be imported) and shared through a global\n * symbol slot, so `handlerContext` in the isomorphic AI entry point and\n * independently bundled A2 copies all read the same scope.\n */\nexport type AmbientToolScopeStorage = {\n getStore(): AmbientToolScope | undefined\n run<R>(store: AmbientToolScope, callback: () => R): R\n}\n\nconst TOOL_SCOPE_STORAGE = Symbol.for('experimental-a2.ai.tool-scope')\n\nexport function ambientToolScopeStorage(): AmbientToolScopeStorage | undefined {\n const value = Reflect.get(globalThis, TOOL_SCOPE_STORAGE)\n if (\n !objectLike(value) ||\n typeof Reflect.get(value, 'getStore') !== 'function' ||\n typeof Reflect.get(value, 'run') !== 'function'\n ) {\n return undefined\n }\n return value as AmbientToolScopeStorage\n}\n\nexport function installAmbientToolScopeStorage(\n create: () => AmbientToolScopeStorage,\n): AmbientToolScopeStorage {\n const existing = ambientToolScopeStorage()\n if (existing !== undefined) return existing\n const storage = create()\n Reflect.set(globalThis, TOOL_SCOPE_STORAGE, storage)\n return storage\n}\n\nexport type SchedulerSendFailureDisposition = 'retryable' | 'terminal'\n\ntype SchedulerSendFailureMarker = {\n readonly version: 1\n readonly pending: SchedulerSendFailureDisposition[]\n readonly active: SchedulerSendFailureDisposition[]\n}\n\nconst SCHEDULER_SEND_FAILURE = Symbol.for(\n 'experimental-a2.scheduler.send-failure',\n)\n\nconst schedulerSendFailureMarker = (\n value: unknown,\n): SchedulerSendFailureMarker | undefined => {\n if (!objectLike(value)) return undefined\n const marker = Reflect.get(value, SCHEDULER_SEND_FAILURE)\n if (\n !objectLike(marker) ||\n Reflect.get(marker, 'version') !== 1 ||\n !Array.isArray(Reflect.get(marker, 'pending')) ||\n !Array.isArray(Reflect.get(marker, 'active'))\n ) {\n return undefined\n }\n return marker as SchedulerSendFailureMarker\n}\n\nconst schedulerFailureObject = (error: unknown): object => {\n if (\n objectLike(error) &&\n (schedulerSendFailureMarker(error) !== undefined ||\n Object.isExtensible(error))\n ) {\n return error\n }\n return new Error(String(error), { cause: error })\n}\n\nconst schedulerSendFailureState = (\n error: object,\n): SchedulerSendFailureMarker => {\n const existing = schedulerSendFailureMarker(error)\n if (existing !== undefined) return existing\n const marker: SchedulerSendFailureMarker = {\n version: 1,\n pending: [],\n active: [],\n }\n Object.defineProperty(error, SCHEDULER_SEND_FAILURE, {\n value: marker,\n enumerable: false,\n configurable: true,\n writable: false,\n })\n return marker\n}\n\nconst clearSchedulerSendFailureState = (\n error: object,\n marker: SchedulerSendFailureMarker,\n): void => {\n if (marker.pending.length === 0 && marker.active.length === 0) {\n Reflect.deleteProperty(error, SCHEDULER_SEND_FAILURE)\n }\n}\n\n/**\n * Classify an adapter error before throwing it. Unclassified adapter failures\n * are retryable because the caller cannot know whether a remote send landed.\n */\nexport function classifySchedulerSendFailure(\n error: unknown,\n disposition: SchedulerSendFailureDisposition,\n): unknown {\n const classified = schedulerFailureObject(error)\n schedulerSendFailureState(classified).pending.push(disposition)\n return classified\n}\n\n/** Consume a pending adapter classification and count this rejected send. */\nexport function markSchedulerSendFailure(error: unknown): unknown {\n const marked = schedulerFailureObject(error)\n const marker = schedulerSendFailureState(marked)\n marker.active.push(marker.pending.pop() ?? 'retryable')\n return marked\n}\n\nexport function consumeSchedulerSendFailure(\n error: unknown,\n): SchedulerSendFailureDisposition | undefined {\n if (!objectLike(error)) return undefined\n const marker = schedulerSendFailureMarker(error)\n const disposition = marker?.active.pop()\n if (marker !== undefined) clearSchedulerSendFailureState(error, marker)\n return disposition\n}\n\nconst isDrainableServer = (value: unknown): value is DrainableServer => {\n if (value === null || typeof value !== 'object') return false\n const contract = Reflect.get(value, 'contract')\n return (\n contract !== null &&\n typeof contract === 'object' &&\n typeof Reflect.get(contract, 'name') === 'string' &&\n typeof Reflect.get(value, 'drain') === 'function'\n )\n}\n\n/**\n * Validate the server list captured by a scheduler route at construction time.\n * The seam stays structural so custom scheduler integrations can supply their\n * own drainable targets.\n */\nexport function assertSchedulerTargets(\n servers: readonly unknown[],\n): asserts servers is readonly DrainableServer[] {\n const contracts = new Set<string>()\n for (const target of servers) {\n if (!isDrainableServer(target)) {\n throw new TypeError('a2 scheduler: expected a server from createServer()')\n }\n const name = target.contract.name\n if (contracts.has(name)) {\n throw new TypeError(\n `a2 scheduler: duplicate server contract '${name}' in one handler`,\n )\n }\n contracts.add(name)\n }\n}\n\n/**\n * Drain timing knobs. Mutable only as a white-box test seam. Claim\n * heartbeats run on real timers, so tests shrink these to keep suites\n * fast. Production always uses the defaults.\n */\nexport const DRAIN_TIMINGS: {\n /** How long a claim lives without renewal. */\n claimTtlMs: number\n /** Heartbeat cadence while a handler is running. */\n claimHeartbeatMs: number\n /** Base delay before retrying a failed renewal while the lease lives. */\n renewRetryMs: number\n /** Delay between a claim's expiry and its scheduler watchdog. */\n watchdogGraceMs: number\n /** Maximum time a top-level append waits for its initial scheduler arm. */\n schedulerArmTimeoutMs: number\n} = {\n claimTtlMs: 5_000,\n claimHeartbeatMs: 2_000,\n renewRetryMs: 400,\n watchdogGraceMs: 1_000,\n schedulerArmTimeoutMs: 2_000,\n}\n\n/**\n * Stream liveness knobs — the server-side SSE heartbeat and the\n * client-side stall watchdog. Paired by design: the client declares a\n * connection dead after roughly two missed heartbeats. Mutable only as\n * a white-box test seam; production always uses the defaults.\n */\n/**\n * Poll cadence for the poll-based store streams (sqlite, postgres) —\n * owned, not configurable: adaptive polling removed the operator's\n * reason to tune it (the floor keeps active streams smooth; the\n * ceiling bounds idle cost). Mutable only as a white-box test seam so\n * suites don't wait out real idle gaps.\n */\nexport const POLL_TIMINGS: {\n /** Gap between polls while a session is producing events. */\n activeFloorMs: number\n /** The idle ceiling — the longest gap between polls. */\n idleCeilingMs: number\n} = {\n activeFloorMs: 25,\n idleCeilingMs: 2_000,\n}\n\n/**\n * Safety-read cadence for the notify-driven redis live stream. A\n * parked feed re-reads the store at this interval even without a\n * pub/sub wake-up, so a lost notification (subscriber reconnect gap,\n * dropped PUBLISH) costs latency, never an event. Mutable only as a\n * white-box test seam.\n */\nexport const NOTIFY_TIMINGS: {\n safetyReadMs: number\n} = {\n safetyReadMs: 10_000,\n}\n\n/**\n * Bounds for each server instance's in-memory reducer-state cache. Sizes are\n * encoded JSON lengths, re-measured every `measureEvery` folded events, so\n * the byte budget is approximate. Mutable only as a white-box test seam.\n */\nexport const STATE_CACHE: {\n maxEntries: number\n maxBytes: number\n /** Events a cached state may advance before it writes a snapshot. */\n snapshotEvery: number\n /** Events a warm read may fold; longer tails read the stored snapshot. */\n maxTail: number\n /** Events folded between re-measuring a cached state's encoded size. */\n measureEvery: number\n} = {\n maxEntries: 1_000,\n maxBytes: 32 * 1024 * 1024,\n snapshotEvery: 128,\n maxTail: 255,\n measureEvery: 16,\n}\n\n/**\n * Warm state-read tails for stores that can serve one in a single request:\n * events from `fromIndex` (inclusive) through `throughIndex` or the newest\n * event, or null when that needs more than one request. Like every store\n * read, a tail starts no lower than the latest clear marker. Keyed by the exact\n * store object, so a wrapper built by spreading or proxying a store reads\n * through its own `readHistory` instead.\n */\nexport const stateTailReaders: WeakMap<\n object,\n (\n sessionId: string,\n fromIndex: number,\n throughIndex?: number,\n ) => Promise<Event[] | null>\n> = new WeakMap()\n\nexport const STREAM_TIMINGS: {\n /** Cadence of `: ping` comment frames from `sseResponse`. */\n sseHeartbeatMs: number\n /** How far ahead of an invocation deadline an SSE response closes. */\n sseDeadlineGraceMs: number\n /** Client: silence longer than this means the connection is dead. */\n stallTimeoutMs: number\n /** Client reconnect backoff: first retry delay, doubling per attempt. */\n reconnectBaseMs: number\n /** Client reconnect backoff ceiling. */\n reconnectMaxMs: number\n} = {\n sseHeartbeatMs: 15_000,\n sseDeadlineGraceMs: 1_000,\n stallTimeoutMs: 35_000,\n reconnectBaseMs: 500,\n reconnectMaxMs: 5_000,\n}\n\n/**\n * WebSocket down-pump backpressure — `sessionsSocket` parks its pull\n * loop while the socket reports more buffered bytes than the\n * high-water mark and re-checks on the poll cadence, so a slow reader\n * applies backpressure to the store subscription instead of buffering\n * without bound in the socket. Acks and heartbeats stay exempt: they\n * are small and correctness-relevant. Mutable only as a white-box test\n * seam.\n */\nexport const SOCKET_TIMINGS: {\n highWaterMarkBytes: number\n resumePollMs: number\n /** Up-lane bound: concurrent push/presence handlers per socket. POST\n * gets per-request platform limits for free; the socket must bring\n * its own. Over the cap, pushes are answered with a retryable error\n * ack and presence frames are shed (that plane repaints). */\n maxInFlightMessages: number\n /** Hard ceiling on `bufferedAmount`: past it the peer is a\n * non-reader (acks and pings are park-exempt, so only a peer that\n * never reads can reach it) and the socket closes 1008. */\n disconnectWaterMarkBytes: number\n} = {\n highWaterMarkBytes: 1_048_576,\n resumePollMs: 25,\n maxInFlightMessages: 32,\n disconnectWaterMarkBytes: 8_388_608,\n}\n\n/**\n * Client presence send cadence — a leading send immediately, then at\n * most one merged send per interval (~15Hz). Owned, not configurable\n * (decision #36's rule): coupling the cadence to the POST ack tied\n * remote cursor smoothness to RTT, and the ack serves no correctness\n * purpose on a fire-and-forget LWW plane. Mutable only as a white-box\n * test seam.\n */\nexport const PRESENCE_TIMINGS: {\n sendIntervalMs: number\n} = {\n sendIntervalMs: 66,\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-CGM8WzRG.d.ts","names":[],"sources":["../src/session-socket.ts","../src/server-fetch.ts","../src/scheduler-task.ts","../src/server.ts"],"mappings":"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"}