@kortix/sdk 0.13.3 → 0.13.5

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 (204) hide show
  1. package/dist/browser/cache/idb-sync-cache.d.ts +19 -1
  2. package/dist/browser/cache/idb-sync-cache.d.ts.map +1 -1
  3. package/dist/browser/cache/idb-sync-cache.js +39 -4
  4. package/dist/browser/cache/idb-sync-cache.js.map +1 -1
  5. package/dist/browser/session-sync/session-sync-registry.d.ts +20 -0
  6. package/dist/browser/session-sync/session-sync-registry.d.ts.map +1 -1
  7. package/dist/browser/session-sync/session-sync-registry.js +69 -6
  8. package/dist/browser/session-sync/session-sync-registry.js.map +1 -1
  9. package/dist/browser/session-sync/visibility.d.ts +19 -0
  10. package/dist/browser/session-sync/visibility.d.ts.map +1 -0
  11. package/dist/browser/session-sync/visibility.js +16 -0
  12. package/dist/browser/session-sync/visibility.js.map +1 -0
  13. package/dist/browser/stores/sandbox-connection-store.js +1 -1
  14. package/dist/browser/stores/sandbox-connection-store.js.map +1 -1
  15. package/dist/browser/stores/sync-store.d.ts +27 -1
  16. package/dist/browser/stores/sync-store.d.ts.map +1 -1
  17. package/dist/browser/stores/sync-store.js +198 -41
  18. package/dist/browser/stores/sync-store.js.map +1 -1
  19. package/dist/core/client/kortix.d.ts +0 -11
  20. package/dist/core/client/kortix.d.ts.map +1 -1
  21. package/dist/core/client/kortix.js +14 -8
  22. package/dist/core/client/kortix.js.map +1 -1
  23. package/dist/core/rest/projects-client/channels.d.ts +0 -3
  24. package/dist/core/rest/projects-client/channels.d.ts.map +1 -1
  25. package/dist/core/rest/projects-client/channels.js +0 -6
  26. package/dist/core/rest/projects-client/channels.js.map +1 -1
  27. package/dist/core/rest/projects-client/index.d.ts +0 -2
  28. package/dist/core/rest/projects-client/index.d.ts.map +1 -1
  29. package/dist/core/rest/projects-client/index.js +0 -2
  30. package/dist/core/rest/projects-client/index.js.map +1 -1
  31. package/dist/core/rest/projects-client/projects.d.ts +1 -1
  32. package/dist/core/rest/projects-client/projects.d.ts.map +1 -1
  33. package/dist/core/rest/projects-client/projects.js +5 -3
  34. package/dist/core/rest/projects-client/projects.js.map +1 -1
  35. package/dist/core/rest/projects-client/sessions.d.ts +8 -33
  36. package/dist/core/rest/projects-client/sessions.d.ts.map +1 -1
  37. package/dist/core/rest/projects-client/sessions.js +1 -18
  38. package/dist/core/rest/projects-client/sessions.js.map +1 -1
  39. package/dist/core/runtime/attachment-part.d.ts +26 -0
  40. package/dist/core/runtime/attachment-part.d.ts.map +1 -0
  41. package/dist/core/runtime/attachment-part.js +41 -0
  42. package/dist/core/runtime/attachment-part.js.map +1 -0
  43. package/dist/core/runtime/client.d.ts +4 -2
  44. package/dist/core/runtime/client.d.ts.map +1 -1
  45. package/dist/core/runtime/client.js +3 -2
  46. package/dist/core/runtime/client.js.map +1 -1
  47. package/dist/core/runtime/opencode-v2-event-aliases.d.ts +184 -0
  48. package/dist/core/runtime/opencode-v2-event-aliases.d.ts.map +1 -0
  49. package/dist/core/runtime/opencode-v2-event-aliases.js +2 -0
  50. package/dist/core/runtime/opencode-v2-event-aliases.js.map +1 -0
  51. package/dist/core/runtime/pty.d.ts +11 -1
  52. package/dist/core/runtime/pty.d.ts.map +1 -1
  53. package/dist/core/runtime/pty.js +19 -6
  54. package/dist/core/runtime/pty.js.map +1 -1
  55. package/dist/core/session/connection.d.ts +64 -0
  56. package/dist/core/session/connection.d.ts.map +1 -0
  57. package/dist/core/session/connection.js +36 -0
  58. package/dist/core/session/connection.js.map +1 -0
  59. package/dist/core/session/index.d.ts +1 -0
  60. package/dist/core/session/index.d.ts.map +1 -1
  61. package/dist/core/session/index.js +1 -0
  62. package/dist/core/session/index.js.map +1 -1
  63. package/dist/core/session/preview-auth.d.ts +25 -0
  64. package/dist/core/session/preview-auth.d.ts.map +1 -1
  65. package/dist/core/session/preview-auth.js +31 -1
  66. package/dist/core/session/preview-auth.js.map +1 -1
  67. package/dist/core/session/preview-config.d.ts +26 -0
  68. package/dist/core/session/preview-config.d.ts.map +1 -0
  69. package/dist/core/session/preview-config.js +95 -0
  70. package/dist/core/session/preview-config.js.map +1 -0
  71. package/dist/core/session/preview-options.d.ts +25 -0
  72. package/dist/core/session/preview-options.d.ts.map +1 -0
  73. package/dist/core/session/preview-options.js +45 -0
  74. package/dist/core/session/preview-options.js.map +1 -0
  75. package/dist/core/session/preview.d.ts +8 -3
  76. package/dist/core/session/preview.d.ts.map +1 -1
  77. package/dist/core/session/preview.js +11 -4
  78. package/dist/core/session/preview.js.map +1 -1
  79. package/dist/core/session/rewind.d.ts +67 -24
  80. package/dist/core/session/rewind.d.ts.map +1 -1
  81. package/dist/core/session/rewind.js +74 -26
  82. package/dist/core/session/rewind.js.map +1 -1
  83. package/dist/core/session/server-store/active.d.ts +10 -12
  84. package/dist/core/session/server-store/active.d.ts.map +1 -1
  85. package/dist/core/session/server-store/active.js +11 -9
  86. package/dist/core/session/server-store/active.js.map +1 -1
  87. package/dist/core/session/server-store/url-helpers.d.ts +11 -2
  88. package/dist/core/session/server-store/url-helpers.d.ts.map +1 -1
  89. package/dist/core/session/server-store/url-helpers.js +11 -2
  90. package/dist/core/session/server-store/url-helpers.js.map +1 -1
  91. package/dist/core/session/url.d.ts +25 -2
  92. package/dist/core/session/url.d.ts.map +1 -1
  93. package/dist/core/session/url.js +87 -11
  94. package/dist/core/session/url.js.map +1 -1
  95. package/dist/core/session/working.d.ts +44 -0
  96. package/dist/core/session/working.d.ts.map +1 -1
  97. package/dist/core/session/working.js +146 -4
  98. package/dist/core/session/working.js.map +1 -1
  99. package/dist/core/session-sync/durable-log.d.ts +96 -0
  100. package/dist/core/session-sync/durable-log.d.ts.map +1 -0
  101. package/dist/core/session-sync/durable-log.js +94 -0
  102. package/dist/core/session-sync/durable-log.js.map +1 -0
  103. package/dist/core/session-sync/fragment.d.ts +27 -0
  104. package/dist/core/session-sync/fragment.d.ts.map +1 -0
  105. package/dist/core/session-sync/fragment.js +24 -0
  106. package/dist/core/session-sync/fragment.js.map +1 -0
  107. package/dist/core/session-sync/session-sync-controller.d.ts +83 -9
  108. package/dist/core/session-sync/session-sync-controller.d.ts.map +1 -1
  109. package/dist/core/session-sync/session-sync-controller.js +183 -17
  110. package/dist/core/session-sync/session-sync-controller.js.map +1 -1
  111. package/dist/core/turns/grouping.d.ts.map +1 -1
  112. package/dist/core/turns/grouping.js +60 -23
  113. package/dist/core/turns/grouping.js.map +1 -1
  114. package/dist/index.d.ts +1 -0
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +1 -0
  117. package/dist/index.js.map +1 -1
  118. package/dist/kortix.esm.min.js +24 -24
  119. package/dist/kortix.global.js +24 -24
  120. package/dist/platform/auth-core.d.ts +13 -1
  121. package/dist/platform/auth-core.d.ts.map +1 -1
  122. package/dist/platform/auth-core.js +13 -1
  123. package/dist/platform/auth-core.js.map +1 -1
  124. package/dist/react/opencode.d.ts +1 -0
  125. package/dist/react/opencode.d.ts.map +1 -1
  126. package/dist/react/opencode.js +3 -0
  127. package/dist/react/opencode.js.map +1 -1
  128. package/dist/react/use-admin-accounts.d.ts +35 -0
  129. package/dist/react/use-admin-accounts.d.ts.map +1 -1
  130. package/dist/react/use-admin-accounts.js +18 -0
  131. package/dist/react/use-admin-accounts.js.map +1 -1
  132. package/dist/react/use-opencode-events/handle-event.d.ts.map +1 -1
  133. package/dist/react/use-opencode-events/handle-event.js +26 -0
  134. package/dist/react/use-opencode-events/handle-event.js.map +1 -1
  135. package/dist/react/use-opencode-events/index.d.ts.map +1 -1
  136. package/dist/react/use-opencode-events/index.js +59 -6
  137. package/dist/react/use-opencode-events/index.js.map +1 -1
  138. package/dist/react/use-opencode-events/rehydrate-targets.d.ts +18 -0
  139. package/dist/react/use-opencode-events/rehydrate-targets.d.ts.map +1 -0
  140. package/dist/react/use-opencode-events/rehydrate-targets.js +20 -0
  141. package/dist/react/use-opencode-events/rehydrate-targets.js.map +1 -0
  142. package/dist/react/use-opencode-events/stream-revival.d.ts +37 -0
  143. package/dist/react/use-opencode-events/stream-revival.d.ts.map +1 -0
  144. package/dist/react/use-opencode-events/stream-revival.js +53 -0
  145. package/dist/react/use-opencode-events/stream-revival.js.map +1 -0
  146. package/dist/react/use-opencode-pty.d.ts +7 -1
  147. package/dist/react/use-opencode-pty.d.ts.map +1 -1
  148. package/dist/react/use-opencode-pty.js +6 -2
  149. package/dist/react/use-opencode-pty.js.map +1 -1
  150. package/dist/react/use-opencode-sessions/agents.d.ts.map +1 -1
  151. package/dist/react/use-opencode-sessions/agents.js +13 -2
  152. package/dist/react/use-opencode-sessions/agents.js.map +1 -1
  153. package/dist/react/use-opencode-sessions/commands.d.ts +12 -0
  154. package/dist/react/use-opencode-sessions/commands.d.ts.map +1 -1
  155. package/dist/react/use-opencode-sessions/commands.js +15 -3
  156. package/dist/react/use-opencode-sessions/commands.js.map +1 -1
  157. package/dist/react/use-opencode-sessions/index.d.ts +1 -0
  158. package/dist/react/use-opencode-sessions/index.d.ts.map +1 -1
  159. package/dist/react/use-opencode-sessions/index.js +1 -0
  160. package/dist/react/use-opencode-sessions/index.js.map +1 -1
  161. package/dist/react/use-opencode-sessions/keys.d.ts +8 -0
  162. package/dist/react/use-opencode-sessions/keys.d.ts.map +1 -1
  163. package/dist/react/use-opencode-sessions/keys.js +8 -0
  164. package/dist/react/use-opencode-sessions/keys.js.map +1 -1
  165. package/dist/react/use-opencode-sessions/providers.d.ts.map +1 -1
  166. package/dist/react/use-opencode-sessions/providers.js +12 -2
  167. package/dist/react/use-opencode-sessions/providers.js.map +1 -1
  168. package/dist/react/use-opencode-sessions/shared.d.ts +23 -0
  169. package/dist/react/use-opencode-sessions/shared.d.ts.map +1 -1
  170. package/dist/react/use-opencode-sessions/shared.js +31 -0
  171. package/dist/react/use-opencode-sessions/shared.js.map +1 -1
  172. package/dist/react/use-opencode-sessions/vcs.d.ts +28 -0
  173. package/dist/react/use-opencode-sessions/vcs.d.ts.map +1 -0
  174. package/dist/react/use-opencode-sessions/vcs.js +42 -0
  175. package/dist/react/use-opencode-sessions/vcs.js.map +1 -0
  176. package/dist/react/use-session-prompts.d.ts +37 -2
  177. package/dist/react/use-session-prompts.d.ts.map +1 -1
  178. package/dist/react/use-session-prompts.js +62 -15
  179. package/dist/react/use-session-prompts.js.map +1 -1
  180. package/dist/react/use-session-send.d.ts +17 -0
  181. package/dist/react/use-session-send.d.ts.map +1 -1
  182. package/dist/react/use-session-send.js +40 -4
  183. package/dist/react/use-session-send.js.map +1 -1
  184. package/dist/react/use-session-sync.d.ts +13 -1
  185. package/dist/react/use-session-sync.d.ts.map +1 -1
  186. package/dist/react/use-session-sync.js +81 -55
  187. package/dist/react/use-session-sync.js.map +1 -1
  188. package/dist/react/use-session-working.d.ts +17 -0
  189. package/dist/react/use-session-working.d.ts.map +1 -1
  190. package/dist/react/use-session-working.js +33 -3
  191. package/dist/react/use-session-working.js.map +1 -1
  192. package/package.json +3 -3
  193. package/dist/browser/session-sync/session-transcript-cache.d.ts +0 -79
  194. package/dist/browser/session-sync/session-transcript-cache.d.ts.map +0 -1
  195. package/dist/browser/session-sync/session-transcript-cache.js +0 -91
  196. package/dist/browser/session-sync/session-transcript-cache.js.map +0 -1
  197. package/dist/core/rest/projects-client/public-voice-join.d.ts +0 -59
  198. package/dist/core/rest/projects-client/public-voice-join.d.ts.map +0 -1
  199. package/dist/core/rest/projects-client/public-voice-join.js +0 -64
  200. package/dist/core/rest/projects-client/public-voice-join.js.map +0 -1
  201. package/dist/core/rest/projects-client/transcription.d.ts +0 -5
  202. package/dist/core/rest/projects-client/transcription.d.ts.map +0 -1
  203. package/dist/core/rest/projects-client/transcription.js +0 -14
  204. package/dist/core/rest/projects-client/transcription.js.map +0 -1
@@ -92,6 +92,30 @@ export declare const STREAM_OBSERVATION_MAX_MS = 45000;
92
92
  * Stop with its reply on screen for up to 45s.
93
93
  */
94
94
  export declare const INBOX_OBSERVATION_MAX_MS = 10000;
95
+ /**
96
+ * How long a runtime `idle` frame may outrank the control plane's turn row.
97
+ *
98
+ * The ledger is closed by a SEPARATE daemon relay (`POST .../turn-stream`
99
+ * `kind:"end"`), so for a moment after the runtime goes idle the row is still
100
+ * open and a `/turn` read taken in that window honestly reports a turn that has
101
+ * finished. MEASURED 2026-08-21 on a healthy stack: the relay POST itself took
102
+ * 1765ms, while the idle frame reaches the tab immediately and triggers its own
103
+ * refetch that lands ~186ms later — squarely inside the gap. Without this the
104
+ * composer's Stop button and the turn shimmer came back ~200ms after the reply
105
+ * and stayed for the whole poll interval.
106
+ *
107
+ * BOUNDED, and this is the important half. An idle frame is NOT proof the turn
108
+ * ended: `session.error` is relayed as `idle` too ("errors terminate the
109
+ * response"), and OpenCode emits it while it is RETRYING a provider — a 429
110
+ * backoff, a transient upstream 5xx — with the turn still very much live. An
111
+ * unbounded rule therefore un-busied the composer mid-turn on every retry and
112
+ * held it that way until the next `busy` frame, which is a worse flicker than
113
+ * the one it fixes. Past this window the ledger is believed again, so a retry
114
+ * blip self-heals in seconds instead of lasting the stream's whole 45s life.
115
+ *
116
+ * Comfortably above the measured relay, far below a provider backoff.
117
+ */
118
+ export declare const TURN_END_LEDGER_LAG_MS = 3000;
95
119
  /**
96
120
  * How long a stop this tab issued may bar a server read from reporting the
97
121
  * turn it is ending.
@@ -159,6 +183,24 @@ export interface WorkingStreamInput {
159
183
  type: 'busy' | 'retry' | 'idle';
160
184
  atMs: number;
161
185
  }
186
+ /**
187
+ * The last moment the RUNTIME'S OWN OUTPUT reached this tab for this session —
188
+ * a streamed part, a message. Stamped on observation, like every other input.
189
+ *
190
+ * Every other input here is an OBSERVER of the runtime: a `/turn` poll, a
191
+ * status frame, a health probe, an inbox read. Each can be dropped, throttled
192
+ * or aged out, and when one is, the projection has historically had to guess.
193
+ * This one is not an observer. Content arriving is not a report that the
194
+ * runtime is working — it IS the runtime working, and no report outranks it.
195
+ *
196
+ * Reported with a screen recording (essentia, 2026-08-23): a tool row with a
197
+ * live spinner and text growing on screen, and a composer showing its send
198
+ * arrow. Every observer had gone quiet; the only thing still speaking was the
199
+ * content, and nothing was listening to it.
200
+ */
201
+ export interface WorkingActivityInput {
202
+ atMs: number;
203
+ }
162
204
  /** One read of the session's durable prompt inbox: how many rows the server is
163
205
  * still going to run, and when that list was read. */
164
206
  export interface WorkingInboxInput {
@@ -173,6 +215,8 @@ export interface WorkingInputs {
173
215
  inbox?: WorkingInboxInput | null;
174
216
  server: WorkingServerInput | null;
175
217
  stream: WorkingStreamInput | null;
218
+ /** The runtime's own output, last seen. See `WorkingActivityInput`. */
219
+ activity?: WorkingActivityInput | null;
176
220
  nowMs: number;
177
221
  }
178
222
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"working.d.ts","sourceRoot":"","sources":["../../../src/core/session/working.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,gBAAgB,EACjB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,wEAAwE;IACxE,MAAM,EAAE,aAAa,CAAC;IACtB,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B;gEAC4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;iFACiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;uDACuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,uEAAuE;IACvE,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAO/E;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,CAmHvE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAetE"}
1
+ {"version":3,"file":"working.d.ts","sourceRoot":"","sources":["../../../src/core/session/working.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,gBAAgB,EACjB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,wEAAwE;IACxE,MAAM,EAAE,aAAa,CAAC;IACtB,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,sBAAsB,OAAQ,CAAC;AAO5C;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B;gEAC4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;iFACiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;uDACuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,uEAAuE;IACvE,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAO/E;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,CA8NvE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAwBtE"}
@@ -45,6 +45,35 @@ export const STREAM_OBSERVATION_MAX_MS = 45_000;
45
45
  * Stop with its reply on screen for up to 45s.
46
46
  */
47
47
  export const INBOX_OBSERVATION_MAX_MS = 10_000;
48
+ /**
49
+ * How long a runtime `idle` frame may outrank the control plane's turn row.
50
+ *
51
+ * The ledger is closed by a SEPARATE daemon relay (`POST .../turn-stream`
52
+ * `kind:"end"`), so for a moment after the runtime goes idle the row is still
53
+ * open and a `/turn` read taken in that window honestly reports a turn that has
54
+ * finished. MEASURED 2026-08-21 on a healthy stack: the relay POST itself took
55
+ * 1765ms, while the idle frame reaches the tab immediately and triggers its own
56
+ * refetch that lands ~186ms later — squarely inside the gap. Without this the
57
+ * composer's Stop button and the turn shimmer came back ~200ms after the reply
58
+ * and stayed for the whole poll interval.
59
+ *
60
+ * BOUNDED, and this is the important half. An idle frame is NOT proof the turn
61
+ * ended: `session.error` is relayed as `idle` too ("errors terminate the
62
+ * response"), and OpenCode emits it while it is RETRYING a provider — a 429
63
+ * backoff, a transient upstream 5xx — with the turn still very much live. An
64
+ * unbounded rule therefore un-busied the composer mid-turn on every retry and
65
+ * held it that way until the next `busy` frame, which is a worse flicker than
66
+ * the one it fixes. Past this window the ledger is believed again, so a retry
67
+ * blip self-heals in seconds instead of lasting the stream's whole 45s life.
68
+ *
69
+ * Comfortably above the measured relay, far below a provider backoff.
70
+ */
71
+ export const TURN_END_LEDGER_LAG_MS = 3_000;
72
+ // RETAINED AS A MEASUREMENT, NOT A RULE. It no longer gates anything: the veto
73
+ // above is causal now, because a row still open past this lag is far more often
74
+ // a dropped `kind:"end"` relay (turn over) than a live retry (turn running), and
75
+ // guessing wrong in the first case is a visible oscillation. Exported because
76
+ // this package's names are a public contract.
48
77
  /**
49
78
  * How long a stop this tab issued may bar a server read from reporting the
50
79
  * turn it is ending.
@@ -119,7 +148,7 @@ function instant(value) {
119
148
  * must re-evaluate at those instants — see `workingExpiryAtMs`.
120
149
  */
121
150
  export function projectWorking(inputs) {
122
- const { optimistic, abort, inbox, server, stream, nowMs } = inputs;
151
+ const { optimistic, abort, inbox, server, stream, activity, nowMs } = inputs;
123
152
  const receiptLive = !!optimistic && nowMs - optimistic.atMs < OPTIMISTIC_RECEIPT_MAX_MS;
124
153
  // TWO floors, because the two server-side observers have different knowledge.
125
154
  //
@@ -151,8 +180,105 @@ export function projectWorking(inputs) {
151
180
  const serverFresh = !!server && nowMs - server.atMs <= SERVER_OBSERVATION_MAX_MS;
152
181
  const streamFresh = !!stream && nowMs - stream.atMs <= STREAM_OBSERVATION_MAX_MS;
153
182
  const inboxFresh = !!inbox && nowMs - inbox.atMs <= INBOX_OBSERVATION_MAX_MS;
154
- const openTurn = serverFresh ? server.turns[0] : undefined;
155
- const serverOpenTurnToken = openTurn?.turn_token ?? null;
183
+ // The runtime's own end-of-turn frame.
184
+ //
185
+ // NOT gated on `streamFresh`, unlike every branch that decides `working` from
186
+ // the stream. The bound exists because an old frame cannot testify to what is
187
+ // happening NOW — but this frame is not asked that. It is asked whether a turn
188
+ // that started BEFORE it has ended, and that answer does not rot: a turn which
189
+ // resumed would have produced a newer, non-idle frame, and then this is not an
190
+ // idle frame at all. Gating it cost exactly what the 3s window cost, 42s later
191
+ // and permanently — at `stream.atMs + STREAM_OBSERVATION_MAX_MS` the veto
192
+ // vanished with no new input, and a row the relay never closed put the
193
+ // composer back on Stop until its deadline (240 MINUTES for an accepted turn).
194
+ const idleFrame = stream && stream.type === 'idle' ? stream : null;
195
+ // CONTENT FIRST. Bounded by the stream's own freshness rule, because it
196
+ // arrives on the same transport and goes stale for the same reasons — but
197
+ // within that window it outranks every observer, including an idle frame it
198
+ // postdates. A runtime that is emitting parts is working, whatever the last
199
+ // status frame said and whatever a poll that has not answered yet will say.
200
+ const activityFresh = !!activity && nowMs - activity.atMs <= STREAM_OBSERVATION_MAX_MS;
201
+ const activityAfterIdle = activityFresh && (!idleFrame || activity.atMs > idleFrame.atMs);
202
+ /**
203
+ * Whether the runtime has already finished this turn.
204
+ *
205
+ * "Newer read wins" is the rule this replaces, and it is wrong here, because
206
+ * the two observers do not learn the same fact at the same time. The idle
207
+ * frame comes straight off the runtime over SSE. The ledger row is closed by
208
+ * a SEPARATE daemon relay (`POST .../turn-stream` `kind:"end"`) — so a `/turn`
209
+ * read ISSUED after the frame is still ABOUT a turn the frame already ended.
210
+ *
211
+ * MEASURED, local stack 2026-08-21, one ordinary composer turn: the idle
212
+ * frame reached the tab at 00:03:59.964, the refetch that frame itself
213
+ * triggers landed at 00:04:00.150 stamped 44ms later and still reported the
214
+ * turn `active`, and the ledger did not record `ended_at` until 00:04:15.132
215
+ * — the relay for that turn never arrived and a reconciliation sweep closed
216
+ * it 15.1s late. The composer's Stop button and the turn's shimmer came back
217
+ * 186ms after they left and stayed for fifteen seconds, with the finished
218
+ * reply already on screen. Even in the healthy case the relay lands ~200ms
219
+ * after the frame, which is still inside the window its own refetch lands in.
220
+ *
221
+ * `started_at` is what separates the two turns the rule has to tell apart: a
222
+ * turn that began BEFORE the frame is the turn that frame ended, and a turn
223
+ * that began after it is a NEW one the frame knows nothing about — a queued
224
+ * prompt draining, a trigger firing, a second device sending. That one keeps
225
+ * the ledger's full authority, with no delay and no window.
226
+ *
227
+ * A row with no start instant (a legacy `activeTurn`) cannot be ranked
228
+ * against the frame at all, and inventing an order there would hide a live
229
+ * turn. The ledger keeps it.
230
+ */
231
+ const endedByRuntime = (candidate) => {
232
+ if (!idleFrame)
233
+ return false;
234
+ // Only a turn that began BEFORE the frame — a turn started after it is a
235
+ // new one the frame knows nothing about.
236
+ const startedAt = instant(candidate.started_at);
237
+ if (startedAt === null || startedAt >= idleFrame.atMs)
238
+ return false;
239
+ // And that is the whole rule. It used to expire after
240
+ // `TURN_END_LEDGER_LAG_MS`, on the theory that a row still open past the
241
+ // relay's lag must mean the frame was a retry's `session.error` rather than
242
+ // the end of anything. But time is not evidence: when the `kind:"end"`
243
+ // relay is simply DROPPED — the documented failure mode, closed by a
244
+ // reconciliation sweep 15.1s late in this file's own measurement — the row
245
+ // stays open for exactly the same reason and the turn is exactly as
246
+ // finished. Expiring the veto therefore announced a finished turn again,
247
+ // and the user watched "Gathering thoughts…" and the Stop button come back
248
+ // for seconds with the answer already on screen (dev, 2026-08-23).
249
+ //
250
+ // A turn that is genuinely still running says so, and that is what returns
251
+ // authority to the ledger: any newer frame is not idle, so `idleFrame` is
252
+ // null on the next evaluation and the row decides again — immediately, with
253
+ // no window to tune. A runtime that goes silent instead is bounded by
254
+ // `STREAM_OBSERVATION_MAX_MS`, after which the frame is too stale to veto
255
+ // anything.
256
+ return true;
257
+ };
258
+ if (activityAfterIdle) {
259
+ return {
260
+ state: 'working',
261
+ source: 'stream',
262
+ turnId: null,
263
+ since: activity.atMs,
264
+ serverOpenTurnToken: serverFresh ? (server.turns[0]?.turn_token ?? null) : null,
265
+ };
266
+ }
267
+ const ledgerTurn = serverFresh ? server.turns[0] : undefined;
268
+ // `serverOpenTurnToken` deliberately keeps reporting the LEDGER's turn even
269
+ // when the runtime's frame has decided the state above — see its docstring.
270
+ // It answers "is the control plane still holding authority", which is what a
271
+ // `/` command must check before going straight at OpenCode with no admission
272
+ // gate in front of it, and that stays true for as long as the row does.
273
+ // Only the WORKING decision moves.
274
+ const serverOpenTurnToken = ledgerTurn?.turn_token ?? null;
275
+ // EVERY row, not just the first. The ledger holds more than one open turn
276
+ // whenever a prompt is forwarded while another is running — measured on the
277
+ // local stack as `turns: [B@00:28:56, A@00:28:22]` — and the list is not
278
+ // ordered newest-first. Testing only `turns[0]` would let one spent row hide
279
+ // a live one behind it, so the projection keeps the first turn the runtime
280
+ // has NOT finished.
281
+ const openTurn = serverFresh ? server.turns.find((t) => !endedByRuntime(t)) : undefined;
156
282
  // A turn the authority is holding open, unless the stream has since said the
157
283
  // session went idle. The stream frame is newer BY OBSERVATION, and the daemon
158
284
  // relays `turn_end` to the control plane at the same moment the frame is
@@ -168,6 +294,13 @@ export function projectWorking(inputs) {
168
294
  }
169
295
  // Durable rows outrank an idle read: the read is right that no TURN is
170
296
  // running and wrong that nothing is happening.
297
+ //
298
+ // Every live row counts, INCLUDING the ones already forwarded to the runtime.
299
+ // A prompt queued behind a running turn is handed to OpenCode early and sits
300
+ // in `delivering` ACROSS the turn boundary, so the idle frame that ends the
301
+ // turn in front of it says nothing about it. Measured on the local stack
302
+ // 2026-08-21: suppressing forwarded rows on that frame left the composer idle
303
+ // for 13.8s with the user's queued prompt still waiting to run.
171
304
  if (inboxFresh && inbox.pending > 0) {
172
305
  return {
173
306
  state: 'working',
@@ -241,13 +374,22 @@ export function projectWorking(inputs) {
241
374
  * skipped, so re-arming from the timer this returns terminates.
242
375
  */
243
376
  export function workingExpiryAtMs(inputs) {
244
- const { optimistic, abort, inbox, server, stream, nowMs } = inputs;
377
+ const { optimistic, abort, inbox, server, stream, activity, nowMs } = inputs;
245
378
  const deadlines = [
379
+ // The instant a runtime idle frame stops outranking the ledger's turn row.
380
+ // Nothing else re-renders then, and a session held `idle` by that rule has
381
+ // to go back to `working` on its own when the frame's window closes.
382
+ // (An idle frame no longer stops outranking the ledger on a timer — see
383
+ // `endedByRuntime` — so there is nothing to schedule for it here.)
384
+ null,
246
385
  optimistic ? optimistic.atMs + OPTIMISTIC_RECEIPT_MAX_MS : null,
247
386
  abort ? abort.atMs + OPTIMISTIC_ABORT_MAX_MS : null,
248
387
  inbox ? inbox.atMs + INBOX_OBSERVATION_MAX_MS : null,
249
388
  server ? server.atMs + SERVER_OBSERVATION_MAX_MS : null,
250
389
  stream ? stream.atMs + STREAM_OBSERVATION_MAX_MS : null,
390
+ // Content stops answering when it goes stale, and nothing else re-renders
391
+ // at that instant.
392
+ activity ? activity.atMs + STREAM_OBSERVATION_MAX_MS : null,
251
393
  ];
252
394
  let next = null;
253
395
  for (const deadline of deadlines) {
@@ -1 +1 @@
1
- {"version":3,"file":"working.js","sourceRoot":"","sources":["../../../src/core/session/working.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"working.js","sourceRoot":"","sources":["../../../src/core/session/working.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The durable per-session event log.
3
+ *
4
+ * OpenCode's runtime keeps an ordered, replayable log per session, and exposes
5
+ * it on two routes that our sandboxes already serve (verified live through the
6
+ * Kortix proxy, dev, 2026-08-24):
7
+ *
8
+ * GET /api/session/{id}/history?limit&after -> { data: SessionDurableEvent[], hasMore }
9
+ * GET /api/session/{id}/event?after=<seq> -> SSE: replay, then tail
10
+ *
11
+ * The SDK's own words for the second one: "Replay durable events after an
12
+ * aggregate sequence, then continue with new durable events."
13
+ *
14
+ * Why this matters more than any repair we have written:
15
+ *
16
+ * Every transcript bug this file's neighbours exist to fix comes from the same
17
+ * shape — the browser holds a COPY assembled from a lossy stream, and has no
18
+ * way to ask "what did I miss?". So we invented answers: re-read the tail on a
19
+ * gap, on turn end, on tab return, on eviction; poll every 10s while working;
20
+ * infer "is it working" from six stamped observations. Each is a guess about
21
+ * loss we could not measure.
22
+ *
23
+ * A sequence cursor replaces all of it with a question the server can answer
24
+ * exactly: everything after `seq`. Missed nothing -> zero events. Missed a
25
+ * turn -> exactly that turn. Cost is proportional to what was MISSED rather
26
+ * than to the size of the transcript, which is the whole reason a 78 MB tail
27
+ * read could never be the right repair.
28
+ *
29
+ * Framework-free on purpose: no DOM, no React, no store. Callers own the cursor.
30
+ */
31
+ /** One page of durable events. `limit` is per request, not a total. */
32
+ export declare const DURABLE_HISTORY_PAGE_SIZE = 200;
33
+ /** A durable event carries an aggregate sequence; a delta frame does not. */
34
+ export interface DurableEventLike {
35
+ durable?: {
36
+ aggregateID?: string;
37
+ seq?: number;
38
+ version?: number;
39
+ };
40
+ }
41
+ export interface DurableHistoryPage {
42
+ events: unknown[];
43
+ hasMore: boolean;
44
+ /**
45
+ * The cursor to pass as `after` next time. Never lower than the cursor this
46
+ * read was given — a rewind would replay events already applied.
47
+ */
48
+ lastSeq: number | null;
49
+ }
50
+ export interface DurableLogClient {
51
+ v2?: {
52
+ session: {
53
+ history: (request: {
54
+ sessionID: string;
55
+ limit?: number;
56
+ after?: number;
57
+ }) => Promise<{
58
+ data?: {
59
+ data?: unknown[];
60
+ hasMore?: boolean;
61
+ };
62
+ }>;
63
+ };
64
+ };
65
+ }
66
+ /**
67
+ * The aggregate sequence of an event, or null if it carries none.
68
+ *
69
+ * Only durable events advance the cursor. The runtime also emits pure deltas
70
+ * (text, reasoning, tool input, compaction) that exist to make typing look
71
+ * live; they are disposable by design and must never move a cursor, or a
72
+ * reconnect would skip the durable event they were painting over.
73
+ */
74
+ export declare function durableSeqOf(event: unknown): number | null;
75
+ /**
76
+ * Read one page of the log after `after` (exclusive).
77
+ *
78
+ * The runtime's own note on this route: "Newly committed events may appear on
79
+ * later pages" — so `hasMore: false` means "caught up as of now", not "the log
80
+ * is finished". The caller keeps the cursor and asks again.
81
+ */
82
+ export declare function readSessionDurableHistory(client: DurableLogClient, sessionId: string, cursor: {
83
+ after?: number;
84
+ limit?: number;
85
+ }): Promise<DurableHistoryPage>;
86
+ /**
87
+ * Does this failure mean "this runtime has no durable log" rather than "the
88
+ * read failed"?
89
+ *
90
+ * Self-hosted installs run older sandbox images. A 404 is a capability answer
91
+ * and must fall back to the message read. Everything else is a real failure and
92
+ * must reach the caller's retry — swallowing a 503 as "unsupported" would
93
+ * permanently downgrade a runtime that was merely asleep.
94
+ */
95
+ export declare function isDurableLogUnsupported(error: unknown): boolean;
96
+ //# sourceMappingURL=durable-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-log.d.ts","sourceRoot":"","sources":["../../../src/core/session-sync/durable-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,EAAE;QACH,OAAO,EAAE;YACP,OAAO,EAAE,CAAC,OAAO,EAAE;gBACjB,SAAS,EAAE,MAAM,CAAC;gBAClB,KAAK,CAAC,EAAE,MAAM,CAAC;gBACf,KAAK,CAAC,EAAE,MAAM,CAAC;aAChB,KAAK,OAAO,CAAC;gBAAE,IAAI,CAAC,EAAE;oBAAE,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;oBAAC,OAAO,CAAC,EAAE,OAAO,CAAA;iBAAE,CAAA;aAAE,CAAC,CAAC;SACnE,CAAC;KACH,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK1D;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,OAAO,CAAC,kBAAkB,CAAC,CAsB7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK/D"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The durable per-session event log.
3
+ *
4
+ * OpenCode's runtime keeps an ordered, replayable log per session, and exposes
5
+ * it on two routes that our sandboxes already serve (verified live through the
6
+ * Kortix proxy, dev, 2026-08-24):
7
+ *
8
+ * GET /api/session/{id}/history?limit&after -> { data: SessionDurableEvent[], hasMore }
9
+ * GET /api/session/{id}/event?after=<seq> -> SSE: replay, then tail
10
+ *
11
+ * The SDK's own words for the second one: "Replay durable events after an
12
+ * aggregate sequence, then continue with new durable events."
13
+ *
14
+ * Why this matters more than any repair we have written:
15
+ *
16
+ * Every transcript bug this file's neighbours exist to fix comes from the same
17
+ * shape — the browser holds a COPY assembled from a lossy stream, and has no
18
+ * way to ask "what did I miss?". So we invented answers: re-read the tail on a
19
+ * gap, on turn end, on tab return, on eviction; poll every 10s while working;
20
+ * infer "is it working" from six stamped observations. Each is a guess about
21
+ * loss we could not measure.
22
+ *
23
+ * A sequence cursor replaces all of it with a question the server can answer
24
+ * exactly: everything after `seq`. Missed nothing -> zero events. Missed a
25
+ * turn -> exactly that turn. Cost is proportional to what was MISSED rather
26
+ * than to the size of the transcript, which is the whole reason a 78 MB tail
27
+ * read could never be the right repair.
28
+ *
29
+ * Framework-free on purpose: no DOM, no React, no store. Callers own the cursor.
30
+ */
31
+ /** One page of durable events. `limit` is per request, not a total. */
32
+ export const DURABLE_HISTORY_PAGE_SIZE = 200;
33
+ /**
34
+ * The aggregate sequence of an event, or null if it carries none.
35
+ *
36
+ * Only durable events advance the cursor. The runtime also emits pure deltas
37
+ * (text, reasoning, tool input, compaction) that exist to make typing look
38
+ * live; they are disposable by design and must never move a cursor, or a
39
+ * reconnect would skip the durable event they were painting over.
40
+ */
41
+ export function durableSeqOf(event) {
42
+ if (!event || typeof event !== 'object')
43
+ return null;
44
+ const durable = event.durable;
45
+ if (!durable || typeof durable.seq !== 'number' || !Number.isFinite(durable.seq))
46
+ return null;
47
+ return durable.seq;
48
+ }
49
+ /**
50
+ * Read one page of the log after `after` (exclusive).
51
+ *
52
+ * The runtime's own note on this route: "Newly committed events may appear on
53
+ * later pages" — so `hasMore: false` means "caught up as of now", not "the log
54
+ * is finished". The caller keeps the cursor and asks again.
55
+ */
56
+ export async function readSessionDurableHistory(client, sessionId, cursor) {
57
+ const history = client.v2?.session.history;
58
+ if (!history)
59
+ throw new Error('durable log unavailable: client exposes no v2 session namespace');
60
+ const result = await history({
61
+ sessionID: sessionId,
62
+ limit: cursor.limit ?? DURABLE_HISTORY_PAGE_SIZE,
63
+ ...(cursor.after === undefined ? {} : { after: cursor.after }),
64
+ });
65
+ const events = result.data?.data ?? [];
66
+ // The HIGHEST sequence in the page, not the last element's. A page is not
67
+ // promised in sequence order, and it may carry frames with no sequence at
68
+ // all; taking the tail element would rewind the cursor on either.
69
+ let lastSeq = cursor.after ?? null;
70
+ for (const event of events) {
71
+ const seq = durableSeqOf(event);
72
+ if (seq === null)
73
+ continue;
74
+ if (lastSeq === null || seq > lastSeq)
75
+ lastSeq = seq;
76
+ }
77
+ return { events, hasMore: result.data?.hasMore === true, lastSeq };
78
+ }
79
+ /**
80
+ * Does this failure mean "this runtime has no durable log" rather than "the
81
+ * read failed"?
82
+ *
83
+ * Self-hosted installs run older sandbox images. A 404 is a capability answer
84
+ * and must fall back to the message read. Everything else is a real failure and
85
+ * must reach the caller's retry — swallowing a 503 as "unsupported" would
86
+ * permanently downgrade a runtime that was merely asleep.
87
+ */
88
+ export function isDurableLogUnsupported(error) {
89
+ if (!error || typeof error !== 'object')
90
+ return false;
91
+ const status = error.status ?? error.statusCode;
92
+ return status === 404;
93
+ }
94
+ //# sourceMappingURL=durable-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-log.js","sourceRoot":"","sources":["../../../src/core/session-sync/durable-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AA6B7C;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,OAAO,GAAI,KAA0B,CAAC,OAAO,CAAC;IACpD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9F,OAAO,OAAO,CAAC,GAAG,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAwB,EACxB,SAAiB,EACjB,MAA0C;IAE1C,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC;IAC3C,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IAEjG,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;QAC3B,SAAS,EAAE,SAAS;QACpB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,yBAAyB;QAChD,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;KAC/D,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;IACvC,0EAA0E;IAC1E,0EAA0E;IAC1E,kEAAkE;IAClE,IAAI,OAAO,GAAG,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,GAAG,KAAK,IAAI;YAAE,SAAS;QAC3B,IAAI,OAAO,KAAK,IAAI,IAAI,GAAG,GAAG,OAAO;YAAE,OAAO,GAAG,GAAG,CAAC;IACvD,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,EAAE,CAAC;AACrE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,MAAM,GACT,KAA8B,CAAC,MAAM,IAAK,KAAkC,CAAC,UAAU,CAAC;IAC3F,OAAO,MAAM,KAAK,GAAG,CAAC;AACxB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Is this session's transcript a FRAGMENT — messages the live stream rebuilt
3
+ * after an eviction, with everything before them missing?
4
+ *
5
+ * The sync store already carries the exact signature, in two pieces:
6
+ *
7
+ * - eviction drops a detached session's messages and MARKS the id
8
+ * - every path that re-establishes the session authoritatively — `hydrate`,
9
+ * `clearSession`, `optimisticAdd` — CLEARS that mark
10
+ * - `applyEvent`, the live stream's own path, does not
11
+ *
12
+ * So messages present while the mark is still set can only have come from
13
+ * frames that arrived after the eviction. The transcript starts mid-conversation
14
+ * and nothing else will correct it: the mount already ran, so no `initial` read
15
+ * is coming, and the liveness poll only runs while the session is working.
16
+ *
17
+ * Named and left open by 5a7a43517f, which removed the IndexedDB mirror that
18
+ * used to repaint underneath such a fragment: "no reconcile is keyed on
19
+ * eviction … can sit on a partial transcript until a reload."
20
+ */
21
+ export declare function transcriptIsFragment(input: {
22
+ /** The store holds at least one message for this session. */
23
+ hasMessages: boolean;
24
+ /** The store still marks this session's transcript as evicted. */
25
+ wasEvicted: boolean;
26
+ }): boolean;
27
+ //# sourceMappingURL=fragment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragment.d.ts","sourceRoot":"","sources":["../../../src/core/session-sync/fragment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,UAAU,EAAE,OAAO,CAAC;CACrB,GAAG,OAAO,CAEV"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Is this session's transcript a FRAGMENT — messages the live stream rebuilt
3
+ * after an eviction, with everything before them missing?
4
+ *
5
+ * The sync store already carries the exact signature, in two pieces:
6
+ *
7
+ * - eviction drops a detached session's messages and MARKS the id
8
+ * - every path that re-establishes the session authoritatively — `hydrate`,
9
+ * `clearSession`, `optimisticAdd` — CLEARS that mark
10
+ * - `applyEvent`, the live stream's own path, does not
11
+ *
12
+ * So messages present while the mark is still set can only have come from
13
+ * frames that arrived after the eviction. The transcript starts mid-conversation
14
+ * and nothing else will correct it: the mount already ran, so no `initial` read
15
+ * is coming, and the liveness poll only runs while the session is working.
16
+ *
17
+ * Named and left open by 5a7a43517f, which removed the IndexedDB mirror that
18
+ * used to repaint underneath such a fragment: "no reconcile is keyed on
19
+ * eviction … can sit on a partial transcript until a reload."
20
+ */
21
+ export function transcriptIsFragment(input) {
22
+ return input.hasMessages && input.wasEvicted;
23
+ }
24
+ //# sourceMappingURL=fragment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragment.js","sourceRoot":"","sources":["../../../src/core/session-sync/fragment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAKpC;IACC,OAAO,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,UAAU,CAAC;AAC/C,CAAC"}