@fungi.computer/shiitake 0.0.0-stage → 0.1.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 (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 +77 -0
  6. package/dist/agent-control.js +434 -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 +313 -0
  26. package/dist/fleet.js +540 -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 +291 -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 +36 -0
  114. package/dist/internal/woodstock/fleet-store.js +100 -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 +884 -0
  156. package/docs/api/extensions.md +2003 -0
  157. package/docs/api/fleet.md +734 -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,512 @@
1
+ import { readSessionAttention } from "./session-attention.js";
2
+ import { ShiitakeError } from "../../errors.js";
3
+ import { decodeWoodstockCursor, encodeWoodstockCursor } from "./cursor.js";
4
+ import { operatorClientMessage, promptCommand } from "./session-entry.js";
5
+ import { lineageEntrySequence, lineageHasSequence, lineageRowLimit, readLineageWindow, readLineageWorkFacts, readSessionEntriesForWork, readSessionLineage, readPendingSessionCommands, readSessionCommandAdmission, } from "./session-store.js";
6
+ const maximumPagePayloadBytes = 2_097_152;
7
+ const replayPageLimit = 200;
8
+ const maximumRowsPerPage = lineageRowLimit;
9
+ const encoder = new TextEncoder();
10
+ function unavailable(message) {
11
+ return new ShiitakeError("store", "unavailable", message);
12
+ }
13
+ function commandIdsByWorkId(owner, sessionId, path) {
14
+ const commandIds = new Map();
15
+ for (const row of path) {
16
+ if (row.entry.kind === "command")
17
+ commandIds.set(row.entry.workId, row.entry.workId);
18
+ }
19
+ const unresolved = path.flatMap(({ entry }) => (entry.kind === "run_terminal" || entry.kind === "work_terminal") &&
20
+ !commandIds.has(entry.workId)
21
+ ? [entry.workId]
22
+ : []);
23
+ for (const workId of unresolved)
24
+ if (readSessionCommandAdmission(owner, sessionId, workId) !== undefined)
25
+ commandIds.set(workId, workId);
26
+ return commandIds;
27
+ }
28
+ function admissionOrigin(owner, sessionId) {
29
+ return (commandId) => {
30
+ const origin = readSessionCommandAdmission(owner, sessionId, commandId)?.origin;
31
+ return origin?.kind === "extension"
32
+ ? { extensionId: origin.extensionId, correlation: origin.correlation }
33
+ : undefined;
34
+ };
35
+ }
36
+ /** Facts from rows already in hand: one work's own entries. */
37
+ function workFacts(owner, sessionId, path) {
38
+ return {
39
+ commandIds: commandIdsByWorkId(owner, sessionId, path),
40
+ diagnostics: diagnosticsByWorkId(path),
41
+ cancellations: new Map(path.flatMap(({ entry }) => entry.kind === "cancellation" && entry.meta !== undefined
42
+ ? [[entry.targetWorkId, entry.meta]]
43
+ : [])),
44
+ origin: admissionOrigin(owner, sessionId),
45
+ };
46
+ }
47
+ /**
48
+ * Facts for one bounded page: only the works its rows show, looked up across
49
+ * the whole lineage without decoding it (`diagnosticsByWorkId`'s rule, in SQL).
50
+ */
51
+ function pageWorkFacts(owner, sessionId, lineage, rows) {
52
+ const shown = new Set(rows.flatMap(({ entry }) => entry.kind === "message" ||
53
+ entry.kind === "run_terminal" ||
54
+ entry.kind === "work_terminal"
55
+ ? [entry.workId]
56
+ : []));
57
+ const facts = readLineageWorkFacts(owner, lineage, shown);
58
+ const commandIds = new Map();
59
+ for (const workId of facts.commands)
60
+ commandIds.set(workId, workId);
61
+ for (const { entry } of rows)
62
+ if ((entry.kind === "run_terminal" || entry.kind === "work_terminal") &&
63
+ !commandIds.has(entry.workId) &&
64
+ readSessionCommandAdmission(owner, sessionId, entry.workId) !== undefined)
65
+ commandIds.set(entry.workId, entry.workId);
66
+ return {
67
+ commandIds,
68
+ diagnostics: facts.diagnostics,
69
+ cancellations: facts.cancellations,
70
+ origin: admissionOrigin(owner, sessionId),
71
+ };
72
+ }
73
+ function projectMessage(row, work) {
74
+ // A compaction request has no message; its compaction marker and its
75
+ // terminal are what clients see of it.
76
+ const prompt = promptCommand(row.entry);
77
+ if (prompt !== undefined) {
78
+ const origin = work.origin(prompt.workId);
79
+ const message = {
80
+ id: row.id,
81
+ sequence: row.sequence,
82
+ commandId: prompt.workId,
83
+ value: prompt.message,
84
+ };
85
+ let projected = message;
86
+ if (prompt.meta !== undefined)
87
+ projected = { ...projected, meta: prompt.meta };
88
+ if (origin !== undefined)
89
+ projected = { ...projected, origin };
90
+ return Object.freeze(projected);
91
+ }
92
+ if (row.entry.kind === "operator") {
93
+ const message = {
94
+ id: row.id,
95
+ sequence: row.sequence,
96
+ commandId: null,
97
+ value: operatorClientMessage(row.entry),
98
+ };
99
+ let projected = message;
100
+ if (row.entry.meta !== undefined)
101
+ projected = { ...projected, meta: row.entry.meta };
102
+ return Object.freeze(projected);
103
+ }
104
+ if (row.entry.kind === "message")
105
+ return Object.freeze({
106
+ id: row.entry.message.value,
107
+ sequence: row.sequence,
108
+ commandId: work.commandIds.get(row.entry.workId) ?? null,
109
+ value: row.entry.value,
110
+ });
111
+ return undefined;
112
+ }
113
+ /**
114
+ * Newest failure message each work recorded: a work-terminal diagnostic, an
115
+ * assistant error or a final retry error. Raw text; clients and the reply
116
+ * fan-out decide how to present and bound it.
117
+ */
118
+ export function diagnosticsByWorkId(path) {
119
+ const diagnostics = new Map();
120
+ for (const { entry } of path) {
121
+ if (entry.kind === "work_terminal" && entry.diagnostic !== undefined) {
122
+ diagnostics.set(entry.workId, entry.diagnostic);
123
+ }
124
+ else if (entry.kind === "message" && entry.value.role === "assistant") {
125
+ if (entry.value.errorMessage !== undefined)
126
+ diagnostics.set(entry.workId, entry.value.errorMessage);
127
+ }
128
+ else if (entry.kind === "live_retry" &&
129
+ entry.event.type === "auto_retry_end" &&
130
+ entry.event.finalError !== undefined)
131
+ diagnostics.set(entry.workId, entry.event.finalError);
132
+ }
133
+ return diagnostics;
134
+ }
135
+ function projectTerminal(row, { commandIds, diagnostics, cancellations }) {
136
+ if (row.entry.kind !== "run_terminal" && row.entry.kind !== "work_terminal")
137
+ return undefined;
138
+ const commandId = commandIds.get(row.entry.workId);
139
+ if (commandId === undefined)
140
+ return undefined;
141
+ const terminal = {
142
+ commandId,
143
+ outcome: row.entry.outcome,
144
+ sequence: row.sequence,
145
+ diagnostic: diagnostics.get(row.entry.workId) ?? null,
146
+ };
147
+ const meta = row.entry.outcome === "cancelled"
148
+ ? cancellations.get(row.entry.workId)
149
+ : undefined;
150
+ return Object.freeze(meta === undefined ? terminal : { ...terminal, meta });
151
+ }
152
+ /**
153
+ * Full-history client projections show every durable entry, including what a
154
+ * compaction summarized away. Each compaction entry in `rows` contributes one
155
+ * minimal marker, positioned to sort immediately before the first entry its
156
+ * compaction kept (the same boundary `buildSessionContext` resumes from) so
157
+ * the client can render "the agent's memory starts below" accurately even
158
+ * when the kept entries are not the compaction row's immediate predecessors.
159
+ */
160
+ function compactionMarkersIn(owner, lineage, rows) {
161
+ const markers = [];
162
+ for (const row of rows) {
163
+ const entry = row.entry;
164
+ if (entry.kind !== "compaction")
165
+ continue;
166
+ const kept = lineageEntrySequence(owner, lineage, entry.firstKeptEntryId);
167
+ if (kept === undefined)
168
+ throw unavailable("Session compaction horizon is unavailable");
169
+ markers.push(Object.freeze({
170
+ id: row.id,
171
+ sequence: kept,
172
+ at: row.timestamp,
173
+ tokensBefore: entry.tokensBefore,
174
+ tokensAfter: entry.tokensAfter ?? null,
175
+ trigger: entry.trigger ?? null,
176
+ }));
177
+ }
178
+ return Object.freeze(markers);
179
+ }
180
+ /** Each compaction that changed nothing, at its own place in the page. */
181
+ function compactionNoticesIn(rows) {
182
+ return Object.freeze(rows.flatMap(({ id, sequence, timestamp, entry }) => entry.kind === "compaction_notice"
183
+ ? [
184
+ Object.freeze({
185
+ id,
186
+ sequence,
187
+ at: timestamp,
188
+ trigger: entry.trigger,
189
+ result: entry.result,
190
+ diagnostic: entry.diagnostic ?? null,
191
+ }),
192
+ ]
193
+ : []));
194
+ }
195
+ function payloadBytes(value) {
196
+ return encoder.encode(JSON.stringify(value)).byteLength;
197
+ }
198
+ function cursorFor(generation, sequence) {
199
+ const cursor = encodeWoodstockCursor({ generation, sequence });
200
+ if (cursor === undefined)
201
+ throw unavailable("Session cursor is unavailable");
202
+ return cursor;
203
+ }
204
+ function admitProjectedRow(state, row, maximumMessages, work, includeSandboxFailures = false) {
205
+ const message = projectMessage(row, work);
206
+ const terminal = projectTerminal(row, work);
207
+ const sandboxFailure = includeSandboxFailures && row.entry.kind === "sandbox_failure"
208
+ ? row.entry.event
209
+ : undefined;
210
+ if (message !== undefined && state.messages.length === maximumMessages)
211
+ return false;
212
+ const nextBytes = (message === undefined ? 0 : payloadBytes(message)) +
213
+ (terminal === undefined ? 0 : payloadBytes(terminal)) +
214
+ (sandboxFailure === undefined ? 0 : payloadBytes(sandboxFailure));
215
+ if (nextBytes > maximumPagePayloadBytes)
216
+ throw unavailable("Session projection entry exceeds the read bound");
217
+ if (state.bytes + nextBytes > maximumPagePayloadBytes)
218
+ return false;
219
+ if (message !== undefined)
220
+ state.messages.push(message);
221
+ if (terminal !== undefined)
222
+ state.terminals.push(terminal);
223
+ if (sandboxFailure !== undefined)
224
+ state.sandboxFailures.push(sandboxFailure);
225
+ state.bytes += nextBytes;
226
+ return true;
227
+ }
228
+ function readBoundedPage(rows,
229
+ /** Whether the lineage holds candidate rows beyond `rows`. */
230
+ more, maximumMessages, work, direction, includeSandboxFailures = false) {
231
+ const state = {
232
+ messages: [],
233
+ terminals: [],
234
+ sandboxFailures: [],
235
+ bytes: 0,
236
+ };
237
+ let consumed = 0;
238
+ for (let offset = 0; offset < rows.length; offset += 1) {
239
+ if (consumed === maximumRowsPerPage)
240
+ break;
241
+ const index = direction === "forward" ? offset : rows.length - 1 - offset;
242
+ const row = rows[index];
243
+ if (row === undefined ||
244
+ !admitProjectedRow(state, row, maximumMessages, work, includeSandboxFailures))
245
+ break;
246
+ consumed += 1;
247
+ }
248
+ if (consumed === 0 && rows.length > 0)
249
+ throw unavailable("Session page cannot make bounded progress");
250
+ const messages = direction === "backward" ? [...state.messages].reverse() : state.messages;
251
+ const terminals = direction === "backward" ? [...state.terminals].reverse() : state.terminals;
252
+ const sandboxFailures = direction === "backward"
253
+ ? [...state.sandboxFailures].reverse()
254
+ : state.sandboxFailures;
255
+ return Object.freeze({
256
+ messages: Object.freeze(messages),
257
+ terminals: Object.freeze(terminals),
258
+ sandboxFailures: Object.freeze(sandboxFailures),
259
+ consumed,
260
+ hasMore: more || consumed < rows.length,
261
+ });
262
+ }
263
+ function isPromptParts(content) {
264
+ return Array.isArray(content);
265
+ }
266
+ function readPendingPrompts(owner, sessionId) {
267
+ const { commands: rows, total } = readPendingSessionCommands(owner, sessionId, maximumRowsPerPage + 1, "prompts");
268
+ const prompts = [];
269
+ let bytes = encoder.encode(JSON.stringify({ prompts: [], hasMore: false, total })).byteLength;
270
+ for (const row of rows) {
271
+ const entry = promptCommand(row.entry);
272
+ if (entry === undefined)
273
+ throw unavailable("Pending Session prompt is unavailable");
274
+ const content = entry.message.content;
275
+ const prompt = isPromptParts(content)
276
+ ? {
277
+ commandId: row.entry.workId,
278
+ text: content
279
+ .filter((part) => part.type === "text")
280
+ .map((part) => part.text)
281
+ .join(""),
282
+ images: content.filter((part) => part.type === "image"),
283
+ delivery: entry.delivery ?? "followUp",
284
+ }
285
+ : {
286
+ commandId: row.entry.workId,
287
+ text: content,
288
+ delivery: entry.delivery ?? "followUp",
289
+ };
290
+ const withMeta = row.entry.meta === undefined
291
+ ? prompt
292
+ : { ...prompt, meta: row.entry.meta };
293
+ const attributed = row.origin.kind === "extension"
294
+ ? {
295
+ ...withMeta,
296
+ origin: {
297
+ extensionId: row.origin.extensionId,
298
+ correlation: row.origin.correlation,
299
+ },
300
+ }
301
+ : withMeta;
302
+ const size = encoder.encode(JSON.stringify(attributed)).byteLength +
303
+ (prompts.length === 0 ? 0 : 1);
304
+ if (prompts.length === maximumRowsPerPage ||
305
+ bytes + size > maximumPagePayloadBytes)
306
+ return { prompts, hasMore: true, total };
307
+ prompts.push(attributed);
308
+ bytes += size;
309
+ }
310
+ return { prompts, hasMore: false, total };
311
+ }
312
+ /**
313
+ * The exclusive upper sequence a baseline read pages back from: none for the
314
+ * latest page, or exactly the entry a `before` cursor names. Undefined means
315
+ * the cursor cannot be honored (another lineage, or an entry no longer on
316
+ * the selected path).
317
+ */
318
+ function resolveHistoryAnchor(owner, lineage, before) {
319
+ const position = decodeWoodstockCursor(before);
320
+ if (position?.generation !== lineage.rootId)
321
+ return undefined;
322
+ return lineageHasSequence(owner, lineage, position.sequence)
323
+ ? position.sequence
324
+ : undefined;
325
+ }
326
+ /**
327
+ * Read one bounded public baseline from the selected Session lineage. Only
328
+ * the page's rows are decoded; `before` pages back through the whole saved
329
+ * history, including what compaction summarized away.
330
+ */
331
+ export function readClientSnapshot(owner, sessionId, options, activity, projectExtensions = () => ({})) {
332
+ return owner.transactionSync(() => {
333
+ const pending = readPendingPrompts(owner, sessionId);
334
+ const lineage = readSessionLineage(owner, { sessionId });
335
+ if (lineage === undefined)
336
+ return Object.freeze({
337
+ sessionId,
338
+ cursor: null,
339
+ hasMore: false,
340
+ oldestCursor: null,
341
+ messages: Object.freeze([]),
342
+ terminals: Object.freeze([]),
343
+ compactions: Object.freeze([]),
344
+ compactionNotices: Object.freeze([]),
345
+ pending,
346
+ activity: activity.facts,
347
+ progress: activity.progress,
348
+ extensions: projectExtensions(),
349
+ attention: readSessionAttention(owner, sessionId),
350
+ });
351
+ const generation = lineage.rootId;
352
+ const anchor = options.before === undefined
353
+ ? Number.MAX_SAFE_INTEGER
354
+ : resolveHistoryAnchor(owner, lineage, options.before);
355
+ if (anchor === undefined)
356
+ throw unavailable("Session read cursor is unavailable");
357
+ const candidates = readLineageWindow(owner, lineage, {
358
+ before: anchor,
359
+ from: "newest",
360
+ rows: maximumRowsPerPage,
361
+ });
362
+ const page = readBoundedPage(candidates.rows, candidates.more, options.limit, pageWorkFacts(owner, sessionId, lineage, candidates.rows), "backward", options.includeSandboxFailures === true);
363
+ const consumedRows = candidates.rows.slice(candidates.rows.length - page.consumed);
364
+ const newestIncluded = candidates.rows.at(-1);
365
+ const oldestIncluded = consumedRows[0];
366
+ const snapshot = {
367
+ sessionId,
368
+ cursor: newestIncluded === undefined
369
+ ? null
370
+ : cursorFor(generation, newestIncluded.sequence),
371
+ hasMore: page.hasMore,
372
+ oldestCursor: page.hasMore && oldestIncluded !== undefined
373
+ ? cursorFor(generation, oldestIncluded.sequence)
374
+ : null,
375
+ messages: page.messages,
376
+ terminals: page.terminals,
377
+ compactions: compactionMarkersIn(owner, lineage, consumedRows),
378
+ compactionNotices: compactionNoticesIn(consumedRows),
379
+ pending,
380
+ activity: activity.facts,
381
+ progress: options.before === undefined ? activity.progress : null,
382
+ extensions: projectExtensions(),
383
+ attention: readSessionAttention(owner, sessionId),
384
+ };
385
+ return Object.freeze(options.includeSandboxFailures === true
386
+ ? { ...snapshot, sandboxFailures: page.sandboxFailures }
387
+ : snapshot);
388
+ });
389
+ }
390
+ const resetReplayPage = Object.freeze({ kind: "reset" });
391
+ /** The exclusive lower sequence a replay resumes after; undefined resets. */
392
+ function replayStart(owner, lineage, position) {
393
+ if (position === undefined)
394
+ return 0;
395
+ if (position.generation !== lineage.rootId)
396
+ return undefined;
397
+ return lineageHasSequence(owner, lineage, position.sequence)
398
+ ? position.sequence
399
+ : undefined;
400
+ }
401
+ function replayFrames(owner, lineage, start, delivery, activity, extensions, attention, pending, includeSandboxFailures) {
402
+ const candidates = readLineageWindow(owner, lineage, {
403
+ after: start,
404
+ from: "oldest",
405
+ rows: maximumRowsPerPage,
406
+ });
407
+ const page = readBoundedPage(candidates.rows, candidates.more, replayPageLimit, pageWorkFacts(owner, lineage.sessionId, lineage, candidates.rows), "forward", includeSandboxFailures);
408
+ const consumedRows = candidates.rows.slice(0, page.consumed);
409
+ const observationSequence = consumedRows.at(-1)?.sequence ?? lineage.newestSequence;
410
+ const frame = {
411
+ kind: "update",
412
+ delivery,
413
+ cursor: cursorFor(lineage.rootId, observationSequence),
414
+ messages: page.messages,
415
+ terminals: page.terminals,
416
+ compactions: compactionMarkersIn(owner, lineage, consumedRows),
417
+ compactionNotices: compactionNoticesIn(consumedRows),
418
+ pending,
419
+ activity: activity.facts,
420
+ extensions,
421
+ attention,
422
+ };
423
+ const frames = [
424
+ Object.freeze(includeSandboxFailures
425
+ ? { ...frame, sandboxFailures: page.sandboxFailures }
426
+ : frame),
427
+ ];
428
+ if (delivery === "live" && !page.hasMore && activity.progress !== null)
429
+ frames.push(Object.freeze({
430
+ kind: "progress",
431
+ progress: activity.progress,
432
+ activity: activity.facts,
433
+ }));
434
+ return Object.freeze({
435
+ kind: "page",
436
+ frames: Object.freeze(frames),
437
+ hasMore: page.hasMore,
438
+ });
439
+ }
440
+ /** Read strict-after history plus the current waiting-admission view. */
441
+ export function readClientReplayPage(owner, sessionId, after, delivery, activity, projectExtensions = () => ({}), includeSandboxFailures = false) {
442
+ const position = after === undefined ? undefined : decodeWoodstockCursor(after);
443
+ if (after !== undefined && position === undefined)
444
+ return resetReplayPage;
445
+ return owner.transactionSync(() => {
446
+ const pending = readPendingPrompts(owner, sessionId);
447
+ const lineage = readSessionLineage(owner, { sessionId });
448
+ if (lineage === undefined) {
449
+ if (after !== undefined)
450
+ return resetReplayPage;
451
+ return {
452
+ kind: "page",
453
+ frames: [
454
+ {
455
+ kind: "update",
456
+ delivery,
457
+ cursor: null,
458
+ messages: [],
459
+ terminals: [],
460
+ compactions: [],
461
+ compactionNotices: [],
462
+ activity: activity.facts,
463
+ extensions: projectExtensions(),
464
+ attention: readSessionAttention(owner, sessionId),
465
+ pending,
466
+ },
467
+ ],
468
+ hasMore: false,
469
+ };
470
+ }
471
+ const start = replayStart(owner, lineage, position);
472
+ return start === undefined
473
+ ? resetReplayPage
474
+ : replayFrames(owner, lineage, start, delivery, activity, projectExtensions(), readSessionAttention(owner, sessionId), pending, includeSandboxFailures);
475
+ });
476
+ }
477
+ /** Project one exact command's durable facts without selecting a transcript lineage. */
478
+ export function projectCommandSettlement(owner, sessionId, commandId) {
479
+ const admission = readSessionCommandAdmission(owner, sessionId, commandId);
480
+ if (admission === undefined)
481
+ throw unavailable("Accepted Session command is unavailable");
482
+ const rows = readSessionEntriesForWork(owner, sessionId, admission.entry.workId);
483
+ const work = workFacts(owner, sessionId, rows);
484
+ const terminal = rows
485
+ .flatMap((row) => {
486
+ const value = projectTerminal(row, work);
487
+ return value === undefined ? [] : [value];
488
+ })
489
+ .at(-1);
490
+ if (terminal === undefined)
491
+ return undefined;
492
+ const answers = rows.flatMap(({ entry }) => entry.kind === "message" && entry.value.role === "assistant"
493
+ ? [entry.value]
494
+ : []);
495
+ const last = answers.at(-1);
496
+ const meta = terminal.outcome === "cancelled" ? terminal.meta : admission.entry.meta;
497
+ return {
498
+ terminal: meta === undefined ? terminal : { ...terminal, meta },
499
+ content: terminal.outcome === "cancelled"
500
+ ? ""
501
+ : (last?.content
502
+ .flatMap((part) => (part.type === "text" ? [part.text] : []))
503
+ .join("")
504
+ .trim() ?? ""),
505
+ usage: answers.map(({ provider, model, usage }) => ({
506
+ provider,
507
+ model,
508
+ usage,
509
+ })),
510
+ toolCalls: new Set(rows.flatMap(({ entry }) => entry.kind === "tool_intent" ? [entry.call.id] : [])).size,
511
+ };
512
+ }
@@ -0,0 +1,17 @@
1
+ import type { SessionActivity } from "../../client.js";
2
+ import type { SqliteOwner } from "../contracts.js";
3
+ export type CompactionActivity = NonNullable<SessionActivity["compaction"]>;
4
+ /**
5
+ * Read the current process-local compaction activity for one addressed
6
+ * Session; null while no compaction is running on it.
7
+ */
8
+ export declare function readProcessCompaction(owner: SqliteOwner, sessionId: string): CompactionActivity | null;
9
+ /**
10
+ * Mark a compaction as running now, cleared by `endCompactionActivity`.
11
+ *
12
+ * Process-local only, like `openProgress`: the running state is never
13
+ * durable, so a restarted process shows no running compaction.
14
+ */
15
+ export declare function beginCompactionActivity(owner: SqliteOwner, sessionId: string, trigger: CompactionActivity["trigger"]): void;
16
+ /** Clear a running compaction's activity fact, however the commit ended. */
17
+ export declare function endCompactionActivity(owner: SqliteOwner, sessionId: string): void;
@@ -0,0 +1,32 @@
1
+ import { publishOwnerObservation } from "../observation-kernel.js";
2
+ const compactionByOwner = new WeakMap();
3
+ function compactionFor(owner) {
4
+ const existing = compactionByOwner.get(owner);
5
+ if (existing !== undefined)
6
+ return existing;
7
+ const created = new Map();
8
+ compactionByOwner.set(owner, created);
9
+ return created;
10
+ }
11
+ /**
12
+ * Read the current process-local compaction activity for one addressed
13
+ * Session; null while no compaction is running on it.
14
+ */
15
+ export function readProcessCompaction(owner, sessionId) {
16
+ return compactionFor(owner).get(sessionId) ?? null;
17
+ }
18
+ /**
19
+ * Mark a compaction as running now, cleared by `endCompactionActivity`.
20
+ *
21
+ * Process-local only, like `openProgress`: the running state is never
22
+ * durable, so a restarted process shows no running compaction.
23
+ */
24
+ export function beginCompactionActivity(owner, sessionId, trigger) {
25
+ compactionFor(owner).set(sessionId, Object.freeze({ trigger }));
26
+ publishOwnerObservation(owner, sessionId, "progress");
27
+ }
28
+ /** Clear a running compaction's activity fact, however the commit ended. */
29
+ export function endCompactionActivity(owner, sessionId) {
30
+ if (compactionFor(owner).delete(sessionId))
31
+ publishOwnerObservation(owner, sessionId, "progress");
32
+ }
@@ -0,0 +1,9 @@
1
+ /** Private durable position carried opaquely by the public cursor. */
2
+ export type WoodstockCursorPosition = Readonly<{
3
+ generation: string;
4
+ sequence: number;
5
+ }>;
6
+ /** Encode one validated durable position in the canonical public wire form. */
7
+ export declare function encodeWoodstockCursor(position: WoodstockCursorPosition): string | undefined;
8
+ /** Decode only the strict canonical cursor spelling emitted by Woodstock. */
9
+ export declare function decodeWoodstockCursor(encoded: string): WoodstockCursorPosition | undefined;
@@ -0,0 +1,44 @@
1
+ import * as Base64Url from "effect/encoding/Base64Url";
2
+ import * as Schema from "effect/Schema";
3
+ const encoder = new TextEncoder();
4
+ const decoder = new TextDecoder();
5
+ const canonicalBase64Url = /^[A-Za-z0-9_-]+$/u;
6
+ const CursorValueSchema = Schema.Struct({
7
+ v: Schema.Literal(1),
8
+ generation: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(128)), Schema.check(Schema.makeFilter((value) => encoder.encode(value).byteLength <= 128))),
9
+ sequence: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER))),
10
+ });
11
+ const closed = { onExcessProperty: "error" };
12
+ const decodeCursorValue = Schema.decodeUnknownResult(CursorValueSchema, closed);
13
+ const decodeCursorJson = Schema.decodeUnknownResult(Schema.fromJsonString(CursorValueSchema), closed);
14
+ /** Encode one validated durable position in the canonical public wire form. */
15
+ export function encodeWoodstockCursor(position) {
16
+ const decoded = decodeCursorValue({ v: 1, ...position });
17
+ if (decoded._tag === "Failure")
18
+ return undefined;
19
+ const json = JSON.stringify(decoded.success);
20
+ if (encoder.encode(json).byteLength > 384)
21
+ return undefined;
22
+ const encoded = Base64Url.encode(json);
23
+ return encoder.encode(encoded).byteLength <= 512 ? encoded : undefined;
24
+ }
25
+ /** Decode only the strict canonical cursor spelling emitted by Woodstock. */
26
+ export function decodeWoodstockCursor(encoded) {
27
+ if (encoder.encode(encoded).byteLength > 512 ||
28
+ !canonicalBase64Url.test(encoded))
29
+ return undefined;
30
+ const bytes = Base64Url.decode(encoded);
31
+ if (bytes._tag === "Failure" || bytes.success.byteLength > 384)
32
+ return undefined;
33
+ const json = decoder.decode(bytes.success);
34
+ const decoded = decodeCursorJson(json);
35
+ if (decoded._tag === "Failure")
36
+ return undefined;
37
+ const canonical = JSON.stringify(decoded.success);
38
+ if (canonical !== json || Base64Url.encode(canonical) !== encoded)
39
+ return undefined;
40
+ return Object.freeze({
41
+ generation: decoded.success.generation,
42
+ sequence: decoded.success.sequence,
43
+ });
44
+ }