@fungi.computer/shiitake 0.0.0-stage → 0.0.1

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 (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -2
  3. package/dist/agar-extension.d.ts +9 -0
  4. package/dist/agar-extension.js +38 -0
  5. package/dist/agent-control.d.ts +72 -0
  6. package/dist/agent-control.js +431 -0
  7. package/dist/client-entry.d.ts +211 -0
  8. package/dist/client-entry.js +101 -0
  9. package/dist/client-wire.d.ts +199 -0
  10. package/dist/client-wire.js +271 -0
  11. package/dist/client.d.ts +439 -0
  12. package/dist/client.js +4 -0
  13. package/dist/cloudflare.d.ts +220 -0
  14. package/dist/cloudflare.js +531 -0
  15. package/dist/effect.d.ts +42 -0
  16. package/dist/effect.js +20 -0
  17. package/dist/errors.d.ts +50 -0
  18. package/dist/errors.js +81 -0
  19. package/dist/extension-client.d.ts +38 -0
  20. package/dist/extension-client.js +56 -0
  21. package/dist/extension-contract.d.ts +73 -0
  22. package/dist/extension-contract.js +11 -0
  23. package/dist/extensions.d.ts +589 -0
  24. package/dist/extensions.js +263 -0
  25. package/dist/fleet.d.ts +297 -0
  26. package/dist/fleet.js +515 -0
  27. package/dist/hosted.d.ts +133 -0
  28. package/dist/hosted.js +265 -0
  29. package/dist/http-routes.d.ts +772 -0
  30. package/dist/http-routes.js +242 -0
  31. package/dist/http.d.ts +1 -0
  32. package/dist/http.js +1 -0
  33. package/dist/index.d.ts +35 -0
  34. package/dist/index.js +13 -0
  35. package/dist/internal/accepted-run-executor.d.ts +23 -0
  36. package/dist/internal/accepted-run-executor.js +129 -0
  37. package/dist/internal/admission-door.d.ts +75 -0
  38. package/dist/internal/admission-door.js +105 -0
  39. package/dist/internal/agent-close.d.ts +4 -0
  40. package/dist/internal/agent-close.js +42 -0
  41. package/dist/internal/browser-sessions.d.ts +31 -0
  42. package/dist/internal/browser-sessions.js +173 -0
  43. package/dist/internal/client-boundary.d.ts +262 -0
  44. package/dist/internal/client-boundary.js +202 -0
  45. package/dist/internal/compaction/compaction.d.ts +54 -0
  46. package/dist/internal/compaction/compaction.js +292 -0
  47. package/dist/internal/compaction/session-compaction.d.ts +69 -0
  48. package/dist/internal/compaction/session-compaction.js +330 -0
  49. package/dist/internal/compaction/settings.d.ts +11 -0
  50. package/dist/internal/compaction/settings.js +40 -0
  51. package/dist/internal/compaction/utils.d.ts +45 -0
  52. package/dist/internal/compaction/utils.js +319 -0
  53. package/dist/internal/contracts.d.ts +235 -0
  54. package/dist/internal/contracts.js +1 -0
  55. package/dist/internal/coordinator.d.ts +18 -0
  56. package/dist/internal/coordinator.js +362 -0
  57. package/dist/internal/document-codec.d.ts +11 -0
  58. package/dist/internal/document-codec.js +39 -0
  59. package/dist/internal/effect-client.d.ts +13 -0
  60. package/dist/internal/effect-client.js +102 -0
  61. package/dist/internal/effect-contracts.d.ts +236 -0
  62. package/dist/internal/effect-contracts.js +2 -0
  63. package/dist/internal/extension-command-registry.d.ts +11 -0
  64. package/dist/internal/extension-command-registry.js +29 -0
  65. package/dist/internal/extension-command-tools.d.ts +17 -0
  66. package/dist/internal/extension-command-tools.js +50 -0
  67. package/dist/internal/extension-context.d.ts +25 -0
  68. package/dist/internal/extension-context.js +35 -0
  69. package/dist/internal/extension-definitions.d.ts +5 -0
  70. package/dist/internal/extension-definitions.js +6 -0
  71. package/dist/internal/extension-hook.d.ts +25 -0
  72. package/dist/internal/extension-hook.js +46 -0
  73. package/dist/internal/extension-roster.d.ts +23 -0
  74. package/dist/internal/extension-roster.js +228 -0
  75. package/dist/internal/extension-runtime.d.ts +63 -0
  76. package/dist/internal/extension-runtime.js +1004 -0
  77. package/dist/internal/failure.d.ts +2 -0
  78. package/dist/internal/failure.js +9 -0
  79. package/dist/internal/host-meta.d.ts +41 -0
  80. package/dist/internal/host-meta.js +39 -0
  81. package/dist/internal/json-value.d.ts +4 -0
  82. package/dist/internal/json-value.js +3 -0
  83. package/dist/internal/model-acquisition.d.ts +21 -0
  84. package/dist/internal/model-acquisition.js +98 -0
  85. package/dist/internal/model-preference.d.ts +21 -0
  86. package/dist/internal/model-preference.js +20 -0
  87. package/dist/internal/observation-kernel.d.ts +28 -0
  88. package/dist/internal/observation-kernel.js +169 -0
  89. package/dist/internal/promise-facade.d.ts +18 -0
  90. package/dist/internal/promise-facade.js +172 -0
  91. package/dist/internal/run-scope.d.ts +27 -0
  92. package/dist/internal/run-scope.js +214 -0
  93. package/dist/internal/runtime-coordinator.d.ts +34 -0
  94. package/dist/internal/runtime-coordinator.js +233 -0
  95. package/dist/internal/runtime-core.d.ts +19 -0
  96. package/dist/internal/runtime-core.js +39 -0
  97. package/dist/internal/runtime-reconciliation.d.ts +18 -0
  98. package/dist/internal/runtime-reconciliation.js +162 -0
  99. package/dist/internal/session-context.d.ts +11 -0
  100. package/dist/internal/session-context.js +67 -0
  101. package/dist/internal/session-status.d.ts +10 -0
  102. package/dist/internal/session-status.js +52 -0
  103. package/dist/internal/woodstock/agent-reply.d.ts +25 -0
  104. package/dist/internal/woodstock/agent-reply.js +67 -0
  105. package/dist/internal/woodstock/client-projection.d.ts +29 -0
  106. package/dist/internal/woodstock/client-projection.js +512 -0
  107. package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
  108. package/dist/internal/woodstock/compaction-activity.js +32 -0
  109. package/dist/internal/woodstock/cursor.d.ts +9 -0
  110. package/dist/internal/woodstock/cursor.js +44 -0
  111. package/dist/internal/woodstock/extension-store.d.ts +72 -0
  112. package/dist/internal/woodstock/extension-store.js +262 -0
  113. package/dist/internal/woodstock/fleet-store.d.ts +32 -0
  114. package/dist/internal/woodstock/fleet-store.js +93 -0
  115. package/dist/internal/woodstock/identities.d.ts +6 -0
  116. package/dist/internal/woodstock/identities.js +11 -0
  117. package/dist/internal/woodstock/index.d.ts +170 -0
  118. package/dist/internal/woodstock/index.js +546 -0
  119. package/dist/internal/woodstock/progress.d.ts +32 -0
  120. package/dist/internal/woodstock/progress.js +89 -0
  121. package/dist/internal/woodstock/run-recovery.d.ts +8 -0
  122. package/dist/internal/woodstock/run-recovery.js +95 -0
  123. package/dist/internal/woodstock/session-attention.d.ts +16 -0
  124. package/dist/internal/woodstock/session-attention.js +88 -0
  125. package/dist/internal/woodstock/session-documents.d.ts +18 -0
  126. package/dist/internal/woodstock/session-documents.js +51 -0
  127. package/dist/internal/woodstock/session-entry.d.ts +478 -0
  128. package/dist/internal/woodstock/session-entry.js +484 -0
  129. package/dist/internal/woodstock/session-grants.d.ts +24 -0
  130. package/dist/internal/woodstock/session-grants.js +84 -0
  131. package/dist/internal/woodstock/session-inbox.d.ts +56 -0
  132. package/dist/internal/woodstock/session-inbox.js +307 -0
  133. package/dist/internal/woodstock/session-store.d.ts +432 -0
  134. package/dist/internal/woodstock/session-store.js +2006 -0
  135. package/dist/internal/woodstock/storage-admission.d.ts +14 -0
  136. package/dist/internal/woodstock/storage-admission.js +44 -0
  137. package/dist/model-preference.d.ts +20 -0
  138. package/dist/model-preference.js +30 -0
  139. package/dist/node.d.ts +64 -0
  140. package/dist/node.js +256 -0
  141. package/dist/remote-client.d.ts +11 -0
  142. package/dist/remote-client.js +298 -0
  143. package/dist/session-attention.d.ts +35 -0
  144. package/dist/session-attention.js +22 -0
  145. package/dist/session-documents.d.ts +28 -0
  146. package/dist/session-documents.js +1 -0
  147. package/dist/testing.d.ts +54 -0
  148. package/dist/testing.js +215 -0
  149. package/docs/api/README.md +26 -0
  150. package/docs/api/agar-extension.md +40 -0
  151. package/docs/api/agent-control.md +151 -0
  152. package/docs/api/client-entry.md +344 -0
  153. package/docs/api/client.md +1112 -0
  154. package/docs/api/cloudflare.md +500 -0
  155. package/docs/api/effect.md +904 -0
  156. package/docs/api/extensions.md +2003 -0
  157. package/docs/api/fleet.md +706 -0
  158. package/docs/api/hosted.md +382 -0
  159. package/docs/api/http.md +111 -0
  160. package/docs/api/index.md +1116 -0
  161. package/docs/api/model-preference.md +151 -0
  162. package/docs/api/node.md +142 -0
  163. package/docs/api/testing.md +250 -0
  164. package/package.json +130 -3
@@ -0,0 +1,307 @@
1
+ import { hasSessionAttention } from "./session-attention.js";
2
+ import * as Schema from "effect/Schema";
3
+ import * as Struct from "effect/Struct";
4
+ import { ShiitakeError } from "../../errors.js";
5
+ /** Rows returned by one bulk page. This bounds work, never saved Sessions. */
6
+ const sessionInboxPageSize = 200;
7
+ const closed = { onExcessProperty: "error" };
8
+ const encoder = new TextEncoder();
9
+ const boundedId = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.makeFilter((value) => encoder.encode(value).byteLength <= 256)));
10
+ const timestamp = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
11
+ const tierSchema = Schema.Literals([0, 1, 2]);
12
+ const stateSchema = Schema.Literals([
13
+ "needs_you",
14
+ "running",
15
+ "errored",
16
+ "done",
17
+ ]);
18
+ const outcomeSchema = Schema.Literals([
19
+ "completed",
20
+ "failed",
21
+ "cancelled",
22
+ "outcome_unknown",
23
+ "interrupted",
24
+ ]);
25
+ const InboxRowSchema = Schema.Struct({
26
+ session_id: boundedId,
27
+ title: Schema.NullOr(Schema.String),
28
+ created_at: timestamp,
29
+ last_activity_at: timestamp,
30
+ settled_at: Schema.NullOr(timestamp),
31
+ state: stateSchema,
32
+ tier: tierSchema,
33
+ });
34
+ const decodeInboxRow = Schema.decodeUnknownResult(InboxRowSchema, closed);
35
+ const decodeInboxChangeRow = Schema.decodeUnknownResult(InboxRowSchema.mapFields(Struct.assign({ revision: timestamp })), closed);
36
+ const decodeClockRow = Schema.decodeUnknownResult(Schema.Struct({ revision: timestamp }), closed);
37
+ const InboxRunRowSchema = Schema.Struct({
38
+ outcome: Schema.NullOr(outcomeSchema),
39
+ });
40
+ const decodeInboxRunRow = Schema.decodeUnknownResult(InboxRunRowSchema, closed);
41
+ const PageCursorSchema = Schema.fromJsonString(Schema.Tuple([tierSchema, timestamp, boundedId]));
42
+ const decodePageCursor = Schema.decodeUnknownResult(PageCursorSchema);
43
+ const encodePageCursor = Schema.encodeSync(PageCursorSchema);
44
+ const inboxColumns = "session_id, title, created_at, last_activity_at, settled_at, state, tier";
45
+ function unavailable(message) {
46
+ return new ShiitakeError("store", "unavailable", message);
47
+ }
48
+ /** Queue order: waiting on a person, then live work, then settled recency. */
49
+ function tierOf(state) {
50
+ switch (state) {
51
+ case "needs_you":
52
+ return 0;
53
+ case "running":
54
+ return 1;
55
+ case "errored":
56
+ case "done":
57
+ return 2;
58
+ }
59
+ }
60
+ /**
61
+ * The one decision from Watchdog authority to inbox state. Live work is
62
+ * `running` whatever its sub-status; an idle queue reports its latest outcome.
63
+ * Open extension attention takes priority over both live and settled work.
64
+ */
65
+ function inboxStateFor(head, outcome, attentionOpen) {
66
+ if (attentionOpen)
67
+ return "needs_you";
68
+ if (head.state !== "idle")
69
+ return "running";
70
+ switch (outcome) {
71
+ case "failed":
72
+ case "interrupted":
73
+ case "outcome_unknown":
74
+ return "errored";
75
+ case "completed":
76
+ case "cancelled":
77
+ case null:
78
+ return "done";
79
+ }
80
+ }
81
+ /** Create the inbox table, its keyset and revision indexes and the revision clock. */
82
+ export function prepareSessionInbox(owner) {
83
+ owner.sql.exec(`
84
+ CREATE TABLE IF NOT EXISTS session_inbox (
85
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id),
86
+ title TEXT,
87
+ created_at INTEGER NOT NULL,
88
+ last_activity_at INTEGER NOT NULL,
89
+ settled_at INTEGER,
90
+ outcome TEXT CHECK (
91
+ outcome IS NULL OR outcome IN (
92
+ 'completed', 'failed', 'cancelled', 'outcome_unknown', 'interrupted'
93
+ )
94
+ ),
95
+ state TEXT NOT NULL
96
+ CHECK (state IN ('needs_you', 'running', 'errored', 'done')),
97
+ tier INTEGER NOT NULL CHECK (tier IN (0, 1, 2)),
98
+ revision INTEGER NOT NULL
99
+ )
100
+ `);
101
+ owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_inbox_order
102
+ ON session_inbox(tier, last_activity_at DESC, session_id)`);
103
+ owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_inbox_revision
104
+ ON session_inbox(revision)`);
105
+ owner.sql.exec(`
106
+ CREATE TABLE IF NOT EXISTS session_inbox_clock (
107
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
108
+ revision INTEGER NOT NULL
109
+ )
110
+ `);
111
+ owner.sql.exec("INSERT OR IGNORE INTO session_inbox_clock (singleton, revision) VALUES (1, 0)");
112
+ owner.sql.exec(`
113
+ CREATE TABLE IF NOT EXISTS session_inbox_tombstones (
114
+ session_id TEXT PRIMARY KEY,
115
+ revision INTEGER NOT NULL
116
+ ) STRICT
117
+ `);
118
+ owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_inbox_tombstones_revision ON session_inbox_tombstones(revision)");
119
+ }
120
+ /** Advance the owner revision stamped on the row this transaction changes. */
121
+ function advanceRevision(owner) {
122
+ owner.sql.exec("UPDATE session_inbox_clock SET revision = revision + 1 WHERE singleton = 1");
123
+ }
124
+ const currentRevision = "(SELECT revision FROM session_inbox_clock WHERE singleton = 1)";
125
+ /**
126
+ * Upsert title and recency in the caller's Session transaction. A new row
127
+ * starts `done`: nothing has run, and Watchdog's projector owns run state.
128
+ */
129
+ export function touchSessionInbox(owner, facts) {
130
+ const initialTitle = facts.title === undefined ? "" : facts.title;
131
+ const replaceTitle = facts.title === undefined ? 0 : 1;
132
+ advanceRevision(owner);
133
+ owner.sql.exec(`INSERT INTO session_inbox
134
+ (session_id, title, created_at, last_activity_at, settled_at, outcome,
135
+ state, tier, revision)
136
+ SELECT id, ?, created_at, COALESCE(?, created_at), NULL, NULL, 'done', 2,
137
+ ${currentRevision}
138
+ FROM sessions
139
+ WHERE id = ?
140
+ ON CONFLICT (session_id) DO UPDATE SET
141
+ title = CASE WHEN ? = 1 THEN excluded.title ELSE title END,
142
+ last_activity_at = MAX(last_activity_at, excluded.last_activity_at),
143
+ revision = excluded.revision`, initialTitle, facts.activityAt ?? null, facts.sessionId, replaceTitle);
144
+ }
145
+ /** Record a deletion in the Session owner's transaction and revision stream. */
146
+ export function recordSessionInboxDeletion(owner, sessionId) {
147
+ advanceRevision(owner);
148
+ owner.sql.exec(`INSERT INTO session_inbox_tombstones (session_id, revision)
149
+ VALUES (?, ${currentRevision})
150
+ ON CONFLICT (session_id) DO UPDATE SET revision = excluded.revision`, sessionId);
151
+ }
152
+ /** Drop tombstones the current member subscribers have all acknowledged. */
153
+ export function pruneSessionInboxTombstones(owner, through) {
154
+ owner.sql.exec("DELETE FROM session_inbox_tombstones WHERE revision <= ?", through);
155
+ }
156
+ /**
157
+ * Watchdog's same-transaction projector. Queues that are not saved Sessions
158
+ * have no row and are left alone.
159
+ */
160
+ export function projectSessionRun(owner, transition, now) {
161
+ const raw = owner.sql
162
+ .exec("SELECT outcome FROM session_inbox WHERE session_id = ?", transition.queue)
163
+ .toArray()[0];
164
+ if (raw === undefined)
165
+ return;
166
+ const prior = decodeInboxRunRow(raw);
167
+ if (prior._tag === "Failure")
168
+ throw unavailable("Stored inbox row is invalid");
169
+ const outcome = transition.settled?.outcome ?? prior.success.outcome;
170
+ const state = inboxStateFor(transition.head, outcome, hasSessionAttention(owner, transition.queue));
171
+ advanceRevision(owner);
172
+ owner.sql.exec(`UPDATE session_inbox
173
+ SET outcome = ?, state = ?, tier = ?, revision = ${currentRevision},
174
+ settled_at = CASE WHEN ? = 1 THEN ? ELSE settled_at END,
175
+ last_activity_at = MAX(last_activity_at, ?)
176
+ WHERE session_id = ?`, outcome, state, tierOf(state), transition.settled === undefined ? 0 : 1, now, now, transition.queue);
177
+ }
178
+ /** Refresh attention using the current queue head in the extension transaction. */
179
+ export function projectSessionAttention(owner, sessionId, head) {
180
+ const raw = owner.sql
181
+ .exec("SELECT outcome FROM session_inbox WHERE session_id = ?", sessionId)
182
+ .toArray()[0];
183
+ if (raw === undefined)
184
+ return;
185
+ const prior = decodeInboxRunRow(raw);
186
+ if (prior._tag === "Failure")
187
+ throw unavailable("Stored inbox row is invalid");
188
+ const state = inboxStateFor(head, prior.success.outcome, hasSessionAttention(owner, sessionId));
189
+ advanceRevision(owner);
190
+ owner.sql.exec(`UPDATE session_inbox SET state = ?, tier = ?, revision = ${currentRevision} WHERE session_id = ?`, state, tierOf(state), sessionId);
191
+ }
192
+ function summaryRow(row) {
193
+ if (row.title === null)
194
+ return Object.freeze({ sessionId: row.session_id, state: "unavailable" });
195
+ const base = {
196
+ sessionId: row.session_id,
197
+ title: row.title,
198
+ createdAt: row.created_at,
199
+ lastActivityAt: row.last_activity_at,
200
+ state: row.state,
201
+ };
202
+ return Object.freeze(row.settled_at === null ? base : { ...base, settledAt: row.settled_at });
203
+ }
204
+ /**
205
+ * A bounded strict-after read of changed rows. Each Session appears once, at
206
+ * its latest revision, so pending delivery is bounded by Session count.
207
+ */
208
+ export function readSessionInboxChanges(owner, after, limit) {
209
+ const clock = decodeClockRow(owner.sql
210
+ .exec("SELECT revision FROM session_inbox_clock WHERE singleton = 1")
211
+ .one());
212
+ if (clock._tag === "Failure")
213
+ throw unavailable("Inbox revision is invalid");
214
+ const rows = owner.sql
215
+ .exec(`SELECT ${inboxColumns}, revision FROM session_inbox
216
+ WHERE revision > ?
217
+ ORDER BY revision ASC
218
+ LIMIT ?`, after, limit)
219
+ .toArray();
220
+ const deleted = owner.sql
221
+ .exec(`SELECT session_id, revision FROM session_inbox_tombstones
222
+ WHERE revision > ? ORDER BY revision ASC LIMIT ?`, after, limit)
223
+ .toArray()
224
+ .map((raw) => {
225
+ const parsed = Schema.decodeUnknownResult(Schema.Struct({ session_id: boundedId, revision: timestamp }), closed)(raw);
226
+ if (parsed._tag === "Failure")
227
+ throw unavailable("Stored inbox tombstone is invalid");
228
+ return {
229
+ revision: parsed.success.revision,
230
+ delta: { sessionId: parsed.success.session_id, deleted: true },
231
+ };
232
+ });
233
+ const changed = rows.map((raw) => {
234
+ const decoded = decodeInboxChangeRow(raw);
235
+ if (decoded._tag === "Failure")
236
+ throw unavailable("Stored inbox row is invalid");
237
+ const { revision, ...row } = decoded.success;
238
+ return { revision, delta: summaryRow(row) };
239
+ });
240
+ const ordered = [...changed, ...deleted]
241
+ .sort((left, right) => left.revision - right.revision)
242
+ .slice(0, limit);
243
+ let through = after;
244
+ const deltas = [];
245
+ for (const change of ordered) {
246
+ through = change.revision;
247
+ deltas.push(change.delta);
248
+ }
249
+ return { rows: deltas, through, latest: clock.success.revision };
250
+ }
251
+ function parseCursor(after) {
252
+ const decoded = decodePageCursor(after);
253
+ if (decoded._tag === "Failure")
254
+ throw new ShiitakeError("contract", "invalid", "Session cursor is invalid");
255
+ return decoded.success;
256
+ }
257
+ /** Indexed lookup using the same row decoder and availability as the inbox listing. */
258
+ export function readSessionInboxSummary(owner, sessionId) {
259
+ const raw = owner.sql
260
+ .exec(`SELECT ${inboxColumns} FROM session_inbox WHERE session_id = ?`, sessionId)
261
+ .toArray()[0];
262
+ if (raw === undefined)
263
+ return undefined;
264
+ const decoded = decodeInboxRow(raw);
265
+ if (decoded._tag === "Failure")
266
+ throw unavailable("Stored inbox row is invalid");
267
+ return summaryRow(decoded.success);
268
+ }
269
+ /**
270
+ * One bounded page in tier, then newest-activity order: a single indexed
271
+ * statement regardless of how many Sessions the Agent holds. A row whose
272
+ * stored facts are unreadable is its own `unavailable` row.
273
+ */
274
+ export function readSessionInboxPage(owner, after) {
275
+ const cursor = after === undefined ? undefined : parseCursor(after);
276
+ const rows = cursor === undefined
277
+ ? owner.sql
278
+ .exec(`SELECT ${inboxColumns} FROM session_inbox
279
+ ORDER BY tier ASC, last_activity_at DESC, session_id ASC
280
+ LIMIT ?`, sessionInboxPageSize)
281
+ .toArray()
282
+ : owner.sql
283
+ .exec(`SELECT ${inboxColumns} FROM session_inbox
284
+ WHERE tier > ?
285
+ OR (tier = ? AND last_activity_at < ?)
286
+ OR (tier = ? AND last_activity_at = ? AND session_id > ?)
287
+ ORDER BY tier ASC, last_activity_at DESC, session_id ASC
288
+ LIMIT ?`, cursor[0], cursor[0], cursor[1], cursor[0], cursor[1], cursor[2], sessionInboxPageSize)
289
+ .toArray();
290
+ const sessions = [];
291
+ let last;
292
+ for (const raw of rows) {
293
+ const decoded = decodeInboxRow(raw);
294
+ // Without a valid identity and position the row cannot be paged past.
295
+ if (decoded._tag === "Failure")
296
+ throw unavailable("Stored inbox row is invalid");
297
+ const row = decoded.success;
298
+ last = [row.tier, row.last_activity_at, row.session_id];
299
+ sessions.push(summaryRow(row));
300
+ }
301
+ return {
302
+ sessions,
303
+ next: rows.length === sessionInboxPageSize && last !== undefined
304
+ ? encodePageCursor(last)
305
+ : undefined,
306
+ };
307
+ }