@agentshouse/mdruntime 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (110) hide show
  1. package/AGENTS.md +87 -0
  2. package/dist/adapter.d.ts +145 -28
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js +2 -1
  5. package/dist/adapter.js.map +1 -1
  6. package/dist/bootstrap.d.ts +11 -13
  7. package/dist/bootstrap.d.ts.map +1 -1
  8. package/dist/bootstrap.js +25 -24
  9. package/dist/bootstrap.js.map +1 -1
  10. package/dist/change-set.d.ts +33 -0
  11. package/dist/change-set.d.ts.map +1 -0
  12. package/dist/change-set.js +74 -0
  13. package/dist/change-set.js.map +1 -0
  14. package/dist/change.d.ts +70 -0
  15. package/dist/change.d.ts.map +1 -0
  16. package/dist/change.js +12 -0
  17. package/dist/change.js.map +1 -0
  18. package/dist/client.d.ts +66 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +229 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/collection.d.ts +25 -3
  23. package/dist/collection.d.ts.map +1 -1
  24. package/dist/collection.js +97 -25
  25. package/dist/collection.js.map +1 -1
  26. package/dist/connection.d.ts +87 -5
  27. package/dist/connection.d.ts.map +1 -1
  28. package/dist/connection.js +1653 -70
  29. package/dist/connection.js.map +1 -1
  30. package/dist/conversation.d.ts +57 -0
  31. package/dist/conversation.d.ts.map +1 -0
  32. package/dist/conversation.js +404 -0
  33. package/dist/conversation.js.map +1 -0
  34. package/dist/durability.d.ts +9 -0
  35. package/dist/durability.d.ts.map +1 -0
  36. package/dist/durability.js +11 -0
  37. package/dist/durability.js.map +1 -0
  38. package/dist/file.d.ts +20 -0
  39. package/dist/file.d.ts.map +1 -0
  40. package/dist/file.js +149 -0
  41. package/dist/file.js.map +1 -0
  42. package/dist/host.d.ts +13 -0
  43. package/dist/host.d.ts.map +1 -0
  44. package/dist/host.js +229 -0
  45. package/dist/host.js.map +1 -0
  46. package/dist/identity.d.ts +2 -0
  47. package/dist/identity.d.ts.map +1 -0
  48. package/dist/identity.js +16 -0
  49. package/dist/identity.js.map +1 -0
  50. package/dist/index.d.ts +13 -2
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/log.d.ts +5 -0
  55. package/dist/log.d.ts.map +1 -0
  56. package/dist/log.js +15 -0
  57. package/dist/log.js.map +1 -0
  58. package/dist/memory.d.ts +17 -2
  59. package/dist/memory.d.ts.map +1 -1
  60. package/dist/memory.js +264 -21
  61. package/dist/memory.js.map +1 -1
  62. package/dist/model.d.ts +70 -0
  63. package/dist/model.d.ts.map +1 -0
  64. package/dist/model.js +28 -0
  65. package/dist/model.js.map +1 -0
  66. package/dist/outcome.d.ts +37 -0
  67. package/dist/outcome.d.ts.map +1 -0
  68. package/dist/outcome.js +23 -0
  69. package/dist/outcome.js.map +1 -0
  70. package/dist/overlay.d.ts +10 -0
  71. package/dist/overlay.d.ts.map +1 -0
  72. package/dist/overlay.js +245 -0
  73. package/dist/overlay.js.map +1 -0
  74. package/dist/paths.d.ts +6 -0
  75. package/dist/paths.d.ts.map +1 -0
  76. package/dist/paths.js +47 -0
  77. package/dist/paths.js.map +1 -0
  78. package/dist/query.d.ts +13 -0
  79. package/dist/query.d.ts.map +1 -0
  80. package/dist/query.js +18 -0
  81. package/dist/query.js.map +1 -0
  82. package/dist/refresh.d.ts +14 -0
  83. package/dist/refresh.d.ts.map +1 -0
  84. package/dist/refresh.js +65 -0
  85. package/dist/refresh.js.map +1 -0
  86. package/dist/replica.d.ts +33 -0
  87. package/dist/replica.d.ts.map +1 -0
  88. package/dist/replica.js +134 -0
  89. package/dist/replica.js.map +1 -0
  90. package/dist/save.d.ts +35 -0
  91. package/dist/save.d.ts.map +1 -0
  92. package/dist/save.js +561 -0
  93. package/dist/save.js.map +1 -0
  94. package/dist/status.d.ts +25 -0
  95. package/dist/status.d.ts.map +1 -0
  96. package/dist/status.js +32 -0
  97. package/dist/status.js.map +1 -0
  98. package/dist/store.d.ts +86 -4
  99. package/dist/store.d.ts.map +1 -1
  100. package/dist/store.js +394 -9
  101. package/dist/store.js.map +1 -1
  102. package/dist/transfer.d.ts +5 -0
  103. package/dist/transfer.d.ts.map +1 -0
  104. package/dist/transfer.js +5 -0
  105. package/dist/transfer.js.map +1 -0
  106. package/dist/wire.d.ts +63 -0
  107. package/dist/wire.d.ts.map +1 -0
  108. package/dist/wire.js +142 -0
  109. package/dist/wire.js.map +1 -0
  110. package/package.json +13 -4
@@ -0,0 +1,404 @@
1
+ import { declaredFile } from './file.js';
2
+ const NO_TURNS = new Set();
3
+ function carriedMessage(value) {
4
+ if (typeof value !== 'object' || value === null) return null;
5
+ const record = value;
6
+ if (typeof record.turn !== 'string') return null;
7
+ return structuredClone(value);
8
+ }
9
+ function historyPage(result) {
10
+ if (result.kind !== 'action') return null;
11
+ const answer = result.result;
12
+ if (typeof answer !== 'object' || answer === null) return null;
13
+ const record = answer;
14
+ if (!Array.isArray(record.messages)) return null;
15
+ const messages = [];
16
+ for (const entry of record.messages){
17
+ const carried = carriedMessage(entry);
18
+ if (carried !== null) messages.push(carried);
19
+ }
20
+ return {
21
+ messages,
22
+ cursor: typeof record.cursor === 'string' ? record.cursor : null
23
+ };
24
+ }
25
+ function turnOf(result) {
26
+ if (result.kind !== 'action') return null;
27
+ const answer = result.result;
28
+ if (typeof answer !== 'object' || answer === null) return null;
29
+ const record = answer;
30
+ return typeof record.turn === 'string' ? record.turn : null;
31
+ }
32
+ function newest(held, page, arrived) {
33
+ const arriving = new Set(page.messages.map((entry)=>entry.turn));
34
+ const overlap = held.messages.findIndex((entry)=>arriving.has(entry.turn));
35
+ const carried = overlap === -1 ? [] : held.messages.slice(0, overlap);
36
+ const placed = new Set([
37
+ ...carried,
38
+ ...page.messages
39
+ ].map((entry)=>entry.turn));
40
+ const live = held.messages.filter((entry)=>arrived.has(entry.turn) && !placed.has(entry.turn));
41
+ return {
42
+ ...held,
43
+ messages: [
44
+ ...carried,
45
+ ...page.messages,
46
+ ...live
47
+ ],
48
+ cursor: overlap === -1 ? page.cursor : held.cursor
49
+ };
50
+ }
51
+ function older(held, page) {
52
+ const present = new Set(held.messages.map((entry)=>entry.turn));
53
+ return {
54
+ ...held,
55
+ messages: [
56
+ ...page.messages.filter((entry)=>!present.has(entry.turn)),
57
+ ...held.messages
58
+ ],
59
+ cursor: page.cursor
60
+ };
61
+ }
62
+ function sealed(held, arriving) {
63
+ const at = held.messages.findIndex((entry)=>entry.turn === arriving.turn);
64
+ if (at === -1) return {
65
+ ...held,
66
+ messages: [
67
+ ...held.messages,
68
+ arriving
69
+ ]
70
+ };
71
+ const messages = [
72
+ ...held.messages
73
+ ];
74
+ messages[at] = arriving;
75
+ return {
76
+ ...held,
77
+ messages
78
+ };
79
+ }
80
+ export function createConversationStore(deps) {
81
+ const held = new Map();
82
+ const composed = new Map();
83
+ const growing = new Map();
84
+ const viewers = new Set();
85
+ const issued = new Map();
86
+ const live = new Map();
87
+ let handed;
88
+ function heldOf(conversation) {
89
+ return held.get(conversation) ?? {
90
+ messages: [],
91
+ cursor: null,
92
+ summary: null
93
+ };
94
+ }
95
+ function instructionOf(conversation) {
96
+ return composed.get(conversation) ?? {
97
+ text: '',
98
+ files: [],
99
+ turn: null
100
+ };
101
+ }
102
+ function viewOf(conversation) {
103
+ const carried = heldOf(conversation);
104
+ const unsent = composed.get(conversation);
105
+ return {
106
+ conversation,
107
+ messages: structuredClone(carried.messages),
108
+ olderPages: carried.cursor !== null,
109
+ growingText: growing.get(conversation) ?? null,
110
+ summary: structuredClone(carried.summary),
111
+ instruction: unsent === undefined || unsent.turn !== null ? null : {
112
+ text: unsent.text,
113
+ files: [
114
+ ...unsent.files
115
+ ]
116
+ }
117
+ };
118
+ }
119
+ function emit(conversation) {
120
+ if (deps.closed()) return;
121
+ for (const viewer of viewers){
122
+ if (viewer.conversation === conversation && viewer.ready) viewer.notify(viewOf(conversation));
123
+ }
124
+ }
125
+ function viewed() {
126
+ return [
127
+ ...new Set([
128
+ ...viewers
129
+ ].map((viewer)=>viewer.conversation))
130
+ ].sort();
131
+ }
132
+ function hand() {
133
+ const path = deps.path();
134
+ if (path === undefined) return;
135
+ const open = viewed();
136
+ const named = JSON.stringify(open);
137
+ if (named === handed) return;
138
+ handed = named;
139
+ path.view(open);
140
+ }
141
+ function keptRecord(conversation) {
142
+ const carried = heldOf(conversation);
143
+ return {
144
+ conversation,
145
+ messages: carried.messages,
146
+ cursor: carried.cursor,
147
+ summary: carried.summary
148
+ };
149
+ }
150
+ async function settle(local, conversation) {
151
+ const sent = instructionOf(conversation).turn;
152
+ if (sent === null) return;
153
+ if (!heldOf(conversation).messages.some((entry)=>entry.turn === sent)) return;
154
+ composed.delete(conversation);
155
+ await local.dropInstruction(conversation);
156
+ }
157
+ async function keep(conversation, next) {
158
+ const kept = await deps.durably(async (local)=>{
159
+ const following = next(heldOf(conversation));
160
+ if (following === null) return false;
161
+ held.set(conversation, following);
162
+ await local.keepConversation(keptRecord(conversation));
163
+ await settle(local, conversation);
164
+ return true;
165
+ });
166
+ if (kept) emit(conversation);
167
+ return kept;
168
+ }
169
+ async function keepInstruction(conversation, next) {
170
+ await deps.durably(async (local)=>{
171
+ const stored = next(instructionOf(conversation));
172
+ composed.set(conversation, stored);
173
+ await local.keepInstruction({
174
+ conversation,
175
+ ...stored
176
+ });
177
+ await settle(local, conversation);
178
+ });
179
+ emit(conversation);
180
+ }
181
+ async function dropInstruction(conversation) {
182
+ await deps.durably(async (local)=>{
183
+ composed.delete(conversation);
184
+ await local.dropInstruction(conversation);
185
+ });
186
+ emit(conversation);
187
+ }
188
+ function supersede(conversation) {
189
+ issued.set(conversation, (issued.get(conversation) ?? 0) + 1);
190
+ }
191
+ async function fill(conversation, cursor) {
192
+ const generation = issued.get(conversation);
193
+ const result = await deps.call('conversation.history', {
194
+ conversation,
195
+ ...cursor === null ? {} : {
196
+ cursor
197
+ }
198
+ });
199
+ const page = historyPage(result);
200
+ if (page === null) return result;
201
+ const landed = await keep(conversation, (carried)=>{
202
+ if (issued.get(conversation) !== generation) return null;
203
+ if (cursor !== null) return carried.cursor === cursor ? older(carried, page) : null;
204
+ return newest(carried, page, live.get(conversation) ?? NO_TURNS);
205
+ });
206
+ if (landed && cursor === null) live.delete(conversation);
207
+ return result;
208
+ }
209
+ function invoke(name, conversation, extra = {}) {
210
+ return deps.call(name, {
211
+ conversation,
212
+ ...extra
213
+ });
214
+ }
215
+ const surface = {
216
+ list () {
217
+ const declaration = deps.cards();
218
+ if (declaration === undefined) return Promise.resolve([]);
219
+ const documents = deps.documents().documents.filter((document)=>document.room === declaration.locator);
220
+ const entries = [];
221
+ for (const card of documents){
222
+ if (card.type !== declaration.type) continue;
223
+ const identity = card.fields[declaration.identity];
224
+ if (typeof identity !== 'string') continue;
225
+ const agent = documents.find((document)=>document.backlinks.some((backlink)=>backlink.path === card.path && backlink.field === declaration.agent));
226
+ entries.push({
227
+ conversation: identity,
228
+ card,
229
+ agent: agent ?? null
230
+ });
231
+ }
232
+ entries.sort((left, right)=>left.conversation < right.conversation ? -1 : 1);
233
+ return Promise.resolve(entries);
234
+ },
235
+ read (conversation) {
236
+ return deps.durably(()=>Promise.resolve(viewOf(conversation)));
237
+ },
238
+ older (conversation) {
239
+ const cursor = heldOf(conversation).cursor;
240
+ if (cursor === null) return Promise.resolve(null);
241
+ return fill(conversation, cursor);
242
+ },
243
+ compose (conversation, supplied) {
244
+ return keepInstruction(conversation, ()=>({
245
+ text: supplied.text,
246
+ files: [
247
+ ...supplied.files
248
+ ],
249
+ turn: null
250
+ }));
251
+ },
252
+ discard (conversation) {
253
+ return dropInstruction(conversation);
254
+ },
255
+ create (input) {
256
+ return deps.call('conversation.create', input);
257
+ },
258
+ async send (conversation) {
259
+ const unsent = instructionOf(conversation);
260
+ const files = await Promise.all(unsent.files.map(async (file)=>({
261
+ ...await declaredFile(file),
262
+ blob: file.blob
263
+ })));
264
+ const result = await invoke('conversation.send', conversation, {
265
+ text: unsent.text,
266
+ files
267
+ });
268
+ const turn = turnOf(result);
269
+ if (turn === null) return result;
270
+ await keepInstruction(conversation, (stored)=>({
271
+ ...stored,
272
+ turn
273
+ }));
274
+ return result;
275
+ },
276
+ answer (conversation, answer) {
277
+ return invoke('conversation.answer', conversation, {
278
+ answer
279
+ });
280
+ },
281
+ interrupt (conversation) {
282
+ return invoke('conversation.interrupt', conversation);
283
+ },
284
+ kill (conversation) {
285
+ return invoke('conversation.kill', conversation);
286
+ },
287
+ async cancel (conversation, turn) {
288
+ const result = await invoke('conversation.cancel', conversation, {
289
+ turn
290
+ });
291
+ if (result.kind !== 'action') return result;
292
+ if (instructionOf(conversation).turn === turn) {
293
+ await keepInstruction(conversation, (stored)=>({
294
+ ...stored,
295
+ turn: null
296
+ }));
297
+ }
298
+ return result;
299
+ },
300
+ async delete (conversation) {
301
+ const result = await invoke('conversation.delete', conversation);
302
+ if (result.kind !== 'action') return result;
303
+ growing.delete(conversation);
304
+ live.delete(conversation);
305
+ supersede(conversation);
306
+ await deps.durably(async (local)=>{
307
+ held.delete(conversation);
308
+ composed.delete(conversation);
309
+ await local.dropConversation(conversation);
310
+ await local.dropInstruction(conversation);
311
+ });
312
+ emit(conversation);
313
+ return result;
314
+ }
315
+ };
316
+ return {
317
+ surface,
318
+ hydrate (conversations, instructions) {
319
+ held.clear();
320
+ for (const stored of conversations){
321
+ held.set(stored.conversation, {
322
+ messages: stored.messages,
323
+ cursor: stored.cursor,
324
+ summary: stored.summary
325
+ });
326
+ }
327
+ composed.clear();
328
+ for (const stored of instructions){
329
+ composed.set(stored.conversation, {
330
+ text: stored.text,
331
+ files: stored.files,
332
+ turn: stored.turn
333
+ });
334
+ }
335
+ },
336
+ observe (conversation, notify) {
337
+ const first = !viewed().includes(conversation);
338
+ const viewer = {
339
+ conversation,
340
+ notify,
341
+ ready: false
342
+ };
343
+ viewers.add(viewer);
344
+ if (first) {
345
+ live.set(conversation, new Set());
346
+ supersede(conversation);
347
+ }
348
+ hand();
349
+ void deps.durably(()=>Promise.resolve()).then(async ()=>{
350
+ if (deps.closed() || !viewers.has(viewer)) return;
351
+ viewer.ready = true;
352
+ viewer.notify(viewOf(conversation));
353
+ if (first) await fill(conversation, null);
354
+ }).catch(()=>{});
355
+ return ()=>{
356
+ if (!viewers.delete(viewer)) return;
357
+ if (!viewed().includes(conversation)) {
358
+ growing.delete(conversation);
359
+ live.delete(conversation);
360
+ }
361
+ hand();
362
+ };
363
+ },
364
+ receive (event) {
365
+ if (deps.closed() || !viewed().includes(event.conversation)) return;
366
+ if (event.kind === 'growing-text') {
367
+ growing.set(event.conversation, {
368
+ turn: event.turn,
369
+ text: event.text
370
+ });
371
+ emit(event.conversation);
372
+ return;
373
+ }
374
+ if (event.kind === 'session-summary') {
375
+ const summary = structuredClone(event.summary);
376
+ void keep(event.conversation, (carried)=>({
377
+ ...carried,
378
+ summary
379
+ })).catch(()=>{});
380
+ return;
381
+ }
382
+ const arriving = carriedMessage(event.message);
383
+ if (arriving === null) return;
384
+ live.get(event.conversation)?.add(arriving.turn);
385
+ if (growing.get(event.conversation)?.turn === arriving.turn) {
386
+ growing.delete(event.conversation);
387
+ }
388
+ void deps.durably(async (local)=>{
389
+ held.set(event.conversation, sealed(heldOf(event.conversation), arriving));
390
+ await local.keepConversation(keptRecord(event.conversation));
391
+ await settle(local, event.conversation);
392
+ }).then(()=>{
393
+ emit(event.conversation);
394
+ }).catch(()=>{});
395
+ },
396
+ close () {
397
+ viewers.clear();
398
+ growing.clear();
399
+ live.clear();
400
+ }
401
+ };
402
+ }
403
+
404
+ //# sourceMappingURL=conversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/workspace/packages/mdruntime/src/conversation.ts"],"sourcesContent":["import type { QueryDocument, QueryResult } from '@agentshouse/query';\nimport type {\n ConversationAction,\n ConversationCards,\n ConversationEvent,\n ConversationMessage,\n ViewedConversations,\n} from './adapter.js';\nimport { declaredFile, type InstructionFile } from './file.js';\nimport type { CallResult } from './outcome.js';\nimport type { ConversationRecord, InstructionRecord, RuntimeStore } from './store.js';\n\nexport type ConversationEntry = {\n conversation: string;\n card: QueryDocument;\n agent: QueryDocument | null;\n};\n\nexport type GrowingText = { turn: string; text: string };\n\nexport type UnsentInstruction = {\n text: string;\n files: readonly InstructionFile[];\n};\n\nexport type ConversationView = {\n conversation: string;\n messages: readonly ConversationMessage[];\n olderPages: boolean;\n growingText: GrowingText | null;\n summary: unknown;\n instruction: UnsentInstruction | null;\n};\n\nexport type ConversationSurface = {\n list(): Promise<readonly ConversationEntry[]>;\n read(conversation: string): Promise<ConversationView>;\n older(conversation: string): Promise<CallResult | null>;\n compose(conversation: string, instruction: UnsentInstruction): Promise<void>;\n discard(conversation: string): Promise<void>;\n create(input: unknown): Promise<CallResult>;\n send(conversation: string): Promise<CallResult>;\n answer(conversation: string, answer: unknown): Promise<CallResult>;\n interrupt(conversation: string): Promise<CallResult>;\n kill(conversation: string): Promise<CallResult>;\n cancel(conversation: string, turn: string): Promise<CallResult>;\n delete(conversation: string): Promise<CallResult>;\n};\n\nexport type ConversationDependencies = {\n cards(): ConversationCards | undefined;\n documents(): QueryResult;\n call(name: ConversationAction, input: unknown): Promise<CallResult>;\n durably<T>(work: (local: RuntimeStore) => Promise<T>): Promise<T>;\n path(): ViewedConversations | undefined;\n closed(): boolean;\n};\n\nexport type ConversationStore = {\n surface: ConversationSurface;\n hydrate(\n conversations: readonly ConversationRecord[],\n instructions: readonly InstructionRecord[],\n ): void;\n observe(conversation: string, notify: (view: ConversationView) => void): () => void;\n receive(event: ConversationEvent): void;\n close(): void;\n};\n\ntype Held = {\n messages: readonly ConversationMessage[];\n cursor: string | null;\n summary: unknown;\n};\n\ntype Composed = {\n text: string;\n files: readonly InstructionFile[];\n turn: string | null;\n};\n\ntype Viewing = {\n conversation: string;\n notify: (view: ConversationView) => void;\n ready: boolean;\n};\n\ntype HistoryPage = {\n messages: readonly ConversationMessage[];\n cursor: string | null;\n};\n\nconst NO_TURNS: ReadonlySet<string> = new Set();\n\nfunction carriedMessage(value: unknown): ConversationMessage | null {\n if (typeof value !== 'object' || value === null) return null;\n const record = value as { turn?: unknown };\n if (typeof record.turn !== 'string') return null;\n return structuredClone(value) as ConversationMessage;\n}\n\nfunction historyPage(result: CallResult): HistoryPage | null {\n if (result.kind !== 'action') return null;\n const answer = result.result;\n if (typeof answer !== 'object' || answer === null) return null;\n const record = answer as { messages?: unknown; cursor?: unknown };\n if (!Array.isArray(record.messages)) return null;\n const messages: ConversationMessage[] = [];\n for (const entry of record.messages) {\n const carried = carriedMessage(entry);\n if (carried !== null) messages.push(carried);\n }\n return { messages, cursor: typeof record.cursor === 'string' ? record.cursor : null };\n}\n\nfunction turnOf(result: CallResult): string | null {\n if (result.kind !== 'action') return null;\n const answer = result.result;\n if (typeof answer !== 'object' || answer === null) return null;\n const record = answer as { turn?: unknown };\n return typeof record.turn === 'string' ? record.turn : null;\n}\n\nfunction newest(held: Held, page: HistoryPage, arrived: ReadonlySet<string>): Held {\n const arriving = new Set(page.messages.map((entry) => entry.turn));\n const overlap = held.messages.findIndex((entry) => arriving.has(entry.turn));\n const carried = overlap === -1 ? [] : held.messages.slice(0, overlap);\n const placed = new Set([...carried, ...page.messages].map((entry) => entry.turn));\n const live = held.messages.filter((entry) => arrived.has(entry.turn) && !placed.has(entry.turn));\n return {\n ...held,\n messages: [...carried, ...page.messages, ...live],\n cursor: overlap === -1 ? page.cursor : held.cursor,\n };\n}\n\nfunction older(held: Held, page: HistoryPage): Held {\n const present = new Set(held.messages.map((entry) => entry.turn));\n return {\n ...held,\n messages: [...page.messages.filter((entry) => !present.has(entry.turn)), ...held.messages],\n cursor: page.cursor,\n };\n}\n\nfunction sealed(held: Held, arriving: ConversationMessage): Held {\n const at = held.messages.findIndex((entry) => entry.turn === arriving.turn);\n if (at === -1) return { ...held, messages: [...held.messages, arriving] };\n const messages = [...held.messages];\n messages[at] = arriving;\n return { ...held, messages };\n}\n\nexport function createConversationStore(deps: ConversationDependencies): ConversationStore {\n const held = new Map<string, Held>();\n const composed = new Map<string, Composed>();\n const growing = new Map<string, GrowingText>();\n const viewers = new Set<Viewing>();\n const issued = new Map<string, number>();\n const live = new Map<string, Set<string>>();\n let handed: string | undefined;\n\n function heldOf(conversation: string): Held {\n return held.get(conversation) ?? { messages: [], cursor: null, summary: null };\n }\n\n function instructionOf(conversation: string): Composed {\n return composed.get(conversation) ?? { text: '', files: [], turn: null };\n }\n\n function viewOf(conversation: string): ConversationView {\n const carried = heldOf(conversation);\n const unsent = composed.get(conversation);\n return {\n conversation,\n messages: structuredClone(carried.messages),\n olderPages: carried.cursor !== null,\n growingText: growing.get(conversation) ?? null,\n summary: structuredClone(carried.summary),\n instruction:\n unsent === undefined || unsent.turn !== null\n ? null\n : { text: unsent.text, files: [...unsent.files] },\n };\n }\n\n function emit(conversation: string) {\n if (deps.closed()) return;\n for (const viewer of viewers) {\n if (viewer.conversation === conversation && viewer.ready) viewer.notify(viewOf(conversation));\n }\n }\n\n function viewed(): readonly string[] {\n return [...new Set([...viewers].map((viewer) => viewer.conversation))].sort();\n }\n\n function hand() {\n const path = deps.path();\n if (path === undefined) return;\n const open = viewed();\n const named = JSON.stringify(open);\n if (named === handed) return;\n handed = named;\n path.view(open);\n }\n\n function keptRecord(conversation: string): ConversationRecord {\n const carried = heldOf(conversation);\n return {\n conversation,\n messages: carried.messages,\n cursor: carried.cursor,\n summary: carried.summary,\n };\n }\n\n async function settle(local: RuntimeStore, conversation: string) {\n const sent = instructionOf(conversation).turn;\n if (sent === null) return;\n if (!heldOf(conversation).messages.some((entry) => entry.turn === sent)) return;\n composed.delete(conversation);\n await local.dropInstruction(conversation);\n }\n\n async function keep(\n conversation: string,\n next: (carried: Held) => Held | null,\n ): Promise<boolean> {\n const kept = await deps.durably(async (local) => {\n const following = next(heldOf(conversation));\n if (following === null) return false;\n held.set(conversation, following);\n await local.keepConversation(keptRecord(conversation));\n await settle(local, conversation);\n return true;\n });\n if (kept) emit(conversation);\n return kept;\n }\n\n async function keepInstruction(conversation: string, next: (unsent: Composed) => Composed) {\n await deps.durably(async (local) => {\n const stored = next(instructionOf(conversation));\n composed.set(conversation, stored);\n await local.keepInstruction({ conversation, ...stored });\n await settle(local, conversation);\n });\n emit(conversation);\n }\n\n async function dropInstruction(conversation: string) {\n await deps.durably(async (local) => {\n composed.delete(conversation);\n await local.dropInstruction(conversation);\n });\n emit(conversation);\n }\n\n function supersede(conversation: string) {\n issued.set(conversation, (issued.get(conversation) ?? 0) + 1);\n }\n\n async function fill(conversation: string, cursor: string | null): Promise<CallResult> {\n const generation = issued.get(conversation);\n const result = await deps.call('conversation.history', {\n conversation,\n ...(cursor === null ? {} : { cursor }),\n });\n const page = historyPage(result);\n if (page === null) return result;\n const landed = await keep(conversation, (carried) => {\n if (issued.get(conversation) !== generation) return null;\n if (cursor !== null) return carried.cursor === cursor ? older(carried, page) : null;\n return newest(carried, page, live.get(conversation) ?? NO_TURNS);\n });\n if (landed && cursor === null) live.delete(conversation);\n return result;\n }\n\n function invoke(\n name: ConversationAction,\n conversation: string,\n extra: Readonly<Record<string, unknown>> = {},\n ): Promise<CallResult> {\n return deps.call(name, { conversation, ...extra });\n }\n\n const surface: ConversationSurface = {\n list() {\n const declaration = deps.cards();\n if (declaration === undefined) return Promise.resolve([]);\n const documents = deps\n .documents()\n .documents.filter((document) => document.room === declaration.locator);\n const entries: ConversationEntry[] = [];\n for (const card of documents) {\n if (card.type !== declaration.type) continue;\n const identity = card.fields[declaration.identity];\n if (typeof identity !== 'string') continue;\n const agent = documents.find((document) =>\n document.backlinks.some(\n (backlink) => backlink.path === card.path && backlink.field === declaration.agent,\n ),\n );\n entries.push({ conversation: identity, card, agent: agent ?? null });\n }\n entries.sort((left, right) => (left.conversation < right.conversation ? -1 : 1));\n return Promise.resolve(entries);\n },\n read(conversation: string) {\n return deps.durably(() => Promise.resolve(viewOf(conversation)));\n },\n older(conversation: string) {\n const cursor = heldOf(conversation).cursor;\n if (cursor === null) return Promise.resolve(null);\n return fill(conversation, cursor);\n },\n compose(conversation: string, supplied: UnsentInstruction) {\n return keepInstruction(conversation, () => ({\n text: supplied.text,\n files: [...supplied.files],\n turn: null,\n }));\n },\n discard(conversation: string) {\n return dropInstruction(conversation);\n },\n create(input: unknown) {\n return deps.call('conversation.create', input);\n },\n async send(conversation: string) {\n const unsent = instructionOf(conversation);\n const files = await Promise.all(\n unsent.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })),\n );\n const result = await invoke('conversation.send', conversation, { text: unsent.text, files });\n const turn = turnOf(result);\n if (turn === null) return result;\n await keepInstruction(conversation, (stored) => ({ ...stored, turn }));\n return result;\n },\n answer(conversation: string, answer: unknown) {\n return invoke('conversation.answer', conversation, { answer });\n },\n interrupt(conversation: string) {\n return invoke('conversation.interrupt', conversation);\n },\n kill(conversation: string) {\n return invoke('conversation.kill', conversation);\n },\n async cancel(conversation: string, turn: string) {\n const result = await invoke('conversation.cancel', conversation, { turn });\n if (result.kind !== 'action') return result;\n if (instructionOf(conversation).turn === turn) {\n await keepInstruction(conversation, (stored) => ({ ...stored, turn: null }));\n }\n return result;\n },\n async delete(conversation: string) {\n const result = await invoke('conversation.delete', conversation);\n if (result.kind !== 'action') return result;\n growing.delete(conversation);\n live.delete(conversation);\n supersede(conversation);\n await deps.durably(async (local) => {\n held.delete(conversation);\n composed.delete(conversation);\n await local.dropConversation(conversation);\n await local.dropInstruction(conversation);\n });\n emit(conversation);\n return result;\n },\n };\n\n return {\n surface,\n hydrate(conversations, instructions) {\n held.clear();\n for (const stored of conversations) {\n held.set(stored.conversation, {\n messages: stored.messages,\n cursor: stored.cursor,\n summary: stored.summary,\n });\n }\n composed.clear();\n for (const stored of instructions) {\n composed.set(stored.conversation, {\n text: stored.text,\n files: stored.files,\n turn: stored.turn,\n });\n }\n },\n observe(conversation: string, notify: (view: ConversationView) => void) {\n const first = !viewed().includes(conversation);\n const viewer: Viewing = { conversation, notify, ready: false };\n viewers.add(viewer);\n if (first) {\n live.set(conversation, new Set());\n supersede(conversation);\n }\n hand();\n void deps\n .durably(() => Promise.resolve())\n .then(async () => {\n if (deps.closed() || !viewers.has(viewer)) return;\n viewer.ready = true;\n viewer.notify(viewOf(conversation));\n if (first) await fill(conversation, null);\n })\n .catch(() => {});\n return () => {\n if (!viewers.delete(viewer)) return;\n if (!viewed().includes(conversation)) {\n growing.delete(conversation);\n live.delete(conversation);\n }\n hand();\n };\n },\n receive(event: ConversationEvent) {\n if (deps.closed() || !viewed().includes(event.conversation)) return;\n if (event.kind === 'growing-text') {\n growing.set(event.conversation, { turn: event.turn, text: event.text });\n emit(event.conversation);\n return;\n }\n if (event.kind === 'session-summary') {\n const summary = structuredClone(event.summary);\n void keep(event.conversation, (carried) => ({ ...carried, summary })).catch(() => {});\n return;\n }\n const arriving = carriedMessage(event.message);\n if (arriving === null) return;\n live.get(event.conversation)?.add(arriving.turn);\n if (growing.get(event.conversation)?.turn === arriving.turn) {\n growing.delete(event.conversation);\n }\n void deps\n .durably(async (local) => {\n held.set(event.conversation, sealed(heldOf(event.conversation), arriving));\n await local.keepConversation(keptRecord(event.conversation));\n await settle(local, event.conversation);\n })\n .then(() => {\n emit(event.conversation);\n })\n .catch(() => {});\n },\n close() {\n viewers.clear();\n growing.clear();\n live.clear();\n },\n };\n}\n"],"names":["declaredFile","NO_TURNS","Set","carriedMessage","value","record","turn","structuredClone","historyPage","result","kind","answer","Array","isArray","messages","entry","carried","push","cursor","turnOf","newest","held","page","arrived","arriving","map","overlap","findIndex","has","slice","placed","live","filter","older","present","sealed","at","createConversationStore","deps","Map","composed","growing","viewers","issued","handed","heldOf","conversation","get","summary","instructionOf","text","files","viewOf","unsent","olderPages","growingText","instruction","undefined","emit","closed","viewer","ready","notify","viewed","sort","hand","path","open","named","JSON","stringify","view","keptRecord","settle","local","sent","some","delete","dropInstruction","keep","next","kept","durably","following","set","keepConversation","keepInstruction","stored","supersede","fill","generation","call","landed","invoke","name","extra","surface","list","declaration","cards","Promise","resolve","documents","document","room","locator","entries","card","type","identity","fields","agent","find","backlinks","backlink","field","left","right","read","compose","supplied","discard","create","input","send","all","file","blob","interrupt","kill","cancel","dropConversation","hydrate","conversations","instructions","clear","observe","first","includes","add","then","catch","receive","event","message","close"],"mappings":"AAQA,SAASA,YAAY,QAA8B,YAAY;AAoF/D,MAAMC,WAAgC,IAAIC;AAE1C,SAASC,eAAeC,KAAc;IACpC,IAAI,OAAOA,UAAU,YAAYA,UAAU,MAAM,OAAO;IACxD,MAAMC,SAASD;IACf,IAAI,OAAOC,OAAOC,IAAI,KAAK,UAAU,OAAO;IAC5C,OAAOC,gBAAgBH;AACzB;AAEA,SAASI,YAAYC,MAAkB;IACrC,IAAIA,OAAOC,IAAI,KAAK,UAAU,OAAO;IACrC,MAAMC,SAASF,OAAOA,MAAM;IAC5B,IAAI,OAAOE,WAAW,YAAYA,WAAW,MAAM,OAAO;IAC1D,MAAMN,SAASM;IACf,IAAI,CAACC,MAAMC,OAAO,CAACR,OAAOS,QAAQ,GAAG,OAAO;IAC5C,MAAMA,WAAkC,EAAE;IAC1C,KAAK,MAAMC,SAASV,OAAOS,QAAQ,CAAE;QACnC,MAAME,UAAUb,eAAeY;QAC/B,IAAIC,YAAY,MAAMF,SAASG,IAAI,CAACD;IACtC;IACA,OAAO;QAAEF;QAAUI,QAAQ,OAAOb,OAAOa,MAAM,KAAK,WAAWb,OAAOa,MAAM,GAAG;IAAK;AACtF;AAEA,SAASC,OAAOV,MAAkB;IAChC,IAAIA,OAAOC,IAAI,KAAK,UAAU,OAAO;IACrC,MAAMC,SAASF,OAAOA,MAAM;IAC5B,IAAI,OAAOE,WAAW,YAAYA,WAAW,MAAM,OAAO;IAC1D,MAAMN,SAASM;IACf,OAAO,OAAON,OAAOC,IAAI,KAAK,WAAWD,OAAOC,IAAI,GAAG;AACzD;AAEA,SAASc,OAAOC,IAAU,EAAEC,IAAiB,EAAEC,OAA4B;IACzE,MAAMC,WAAW,IAAItB,IAAIoB,KAAKR,QAAQ,CAACW,GAAG,CAAC,CAACV,QAAUA,MAAMT,IAAI;IAChE,MAAMoB,UAAUL,KAAKP,QAAQ,CAACa,SAAS,CAAC,CAACZ,QAAUS,SAASI,GAAG,CAACb,MAAMT,IAAI;IAC1E,MAAMU,UAAUU,YAAY,CAAC,IAAI,EAAE,GAAGL,KAAKP,QAAQ,CAACe,KAAK,CAAC,GAAGH;IAC7D,MAAMI,SAAS,IAAI5B,IAAI;WAAIc;WAAYM,KAAKR,QAAQ;KAAC,CAACW,GAAG,CAAC,CAACV,QAAUA,MAAMT,IAAI;IAC/E,MAAMyB,OAAOV,KAAKP,QAAQ,CAACkB,MAAM,CAAC,CAACjB,QAAUQ,QAAQK,GAAG,CAACb,MAAMT,IAAI,KAAK,CAACwB,OAAOF,GAAG,CAACb,MAAMT,IAAI;IAC9F,OAAO;QACL,GAAGe,IAAI;QACPP,UAAU;eAAIE;eAAYM,KAAKR,QAAQ;eAAKiB;SAAK;QACjDb,QAAQQ,YAAY,CAAC,IAAIJ,KAAKJ,MAAM,GAAGG,KAAKH,MAAM;IACpD;AACF;AAEA,SAASe,MAAMZ,IAAU,EAAEC,IAAiB;IAC1C,MAAMY,UAAU,IAAIhC,IAAImB,KAAKP,QAAQ,CAACW,GAAG,CAAC,CAACV,QAAUA,MAAMT,IAAI;IAC/D,OAAO;QACL,GAAGe,IAAI;QACPP,UAAU;eAAIQ,KAAKR,QAAQ,CAACkB,MAAM,CAAC,CAACjB,QAAU,CAACmB,QAAQN,GAAG,CAACb,MAAMT,IAAI;eAAOe,KAAKP,QAAQ;SAAC;QAC1FI,QAAQI,KAAKJ,MAAM;IACrB;AACF;AAEA,SAASiB,OAAOd,IAAU,EAAEG,QAA6B;IACvD,MAAMY,KAAKf,KAAKP,QAAQ,CAACa,SAAS,CAAC,CAACZ,QAAUA,MAAMT,IAAI,KAAKkB,SAASlB,IAAI;IAC1E,IAAI8B,OAAO,CAAC,GAAG,OAAO;QAAE,GAAGf,IAAI;QAAEP,UAAU;eAAIO,KAAKP,QAAQ;YAAEU;SAAS;IAAC;IACxE,MAAMV,WAAW;WAAIO,KAAKP,QAAQ;KAAC;IACnCA,QAAQ,CAACsB,GAAG,GAAGZ;IACf,OAAO;QAAE,GAAGH,IAAI;QAAEP;IAAS;AAC7B;AAEA,OAAO,SAASuB,wBAAwBC,IAA8B;IACpE,MAAMjB,OAAO,IAAIkB;IACjB,MAAMC,WAAW,IAAID;IACrB,MAAME,UAAU,IAAIF;IACpB,MAAMG,UAAU,IAAIxC;IACpB,MAAMyC,SAAS,IAAIJ;IACnB,MAAMR,OAAO,IAAIQ;IACjB,IAAIK;IAEJ,SAASC,OAAOC,YAAoB;QAClC,OAAOzB,KAAK0B,GAAG,CAACD,iBAAiB;YAAEhC,UAAU,EAAE;YAAEI,QAAQ;YAAM8B,SAAS;QAAK;IAC/E;IAEA,SAASC,cAAcH,YAAoB;QACzC,OAAON,SAASO,GAAG,CAACD,iBAAiB;YAAEI,MAAM;YAAIC,OAAO,EAAE;YAAE7C,MAAM;QAAK;IACzE;IAEA,SAAS8C,OAAON,YAAoB;QAClC,MAAM9B,UAAU6B,OAAOC;QACvB,MAAMO,SAASb,SAASO,GAAG,CAACD;QAC5B,OAAO;YACLA;YACAhC,UAAUP,gBAAgBS,QAAQF,QAAQ;YAC1CwC,YAAYtC,QAAQE,MAAM,KAAK;YAC/BqC,aAAad,QAAQM,GAAG,CAACD,iBAAiB;YAC1CE,SAASzC,gBAAgBS,QAAQgC,OAAO;YACxCQ,aACEH,WAAWI,aAAaJ,OAAO/C,IAAI,KAAK,OACpC,OACA;gBAAE4C,MAAMG,OAAOH,IAAI;gBAAEC,OAAO;uBAAIE,OAAOF,KAAK;iBAAC;YAAC;QACtD;IACF;IAEA,SAASO,KAAKZ,YAAoB;QAChC,IAAIR,KAAKqB,MAAM,IAAI;QACnB,KAAK,MAAMC,UAAUlB,QAAS;YAC5B,IAAIkB,OAAOd,YAAY,KAAKA,gBAAgBc,OAAOC,KAAK,EAAED,OAAOE,MAAM,CAACV,OAAON;QACjF;IACF;IAEA,SAASiB;QACP,OAAO;eAAI,IAAI7D,IAAI;mBAAIwC;aAAQ,CAACjB,GAAG,CAAC,CAACmC,SAAWA,OAAOd,YAAY;SAAG,CAACkB,IAAI;IAC7E;IAEA,SAASC;QACP,MAAMC,OAAO5B,KAAK4B,IAAI;QACtB,IAAIA,SAAST,WAAW;QACxB,MAAMU,OAAOJ;QACb,MAAMK,QAAQC,KAAKC,SAAS,CAACH;QAC7B,IAAIC,UAAUxB,QAAQ;QACtBA,SAASwB;QACTF,KAAKK,IAAI,CAACJ;IACZ;IAEA,SAASK,WAAW1B,YAAoB;QACtC,MAAM9B,UAAU6B,OAAOC;QACvB,OAAO;YACLA;YACAhC,UAAUE,QAAQF,QAAQ;YAC1BI,QAAQF,QAAQE,MAAM;YACtB8B,SAAShC,QAAQgC,OAAO;QAC1B;IACF;IAEA,eAAeyB,OAAOC,KAAmB,EAAE5B,YAAoB;QAC7D,MAAM6B,OAAO1B,cAAcH,cAAcxC,IAAI;QAC7C,IAAIqE,SAAS,MAAM;QACnB,IAAI,CAAC9B,OAAOC,cAAchC,QAAQ,CAAC8D,IAAI,CAAC,CAAC7D,QAAUA,MAAMT,IAAI,KAAKqE,OAAO;QACzEnC,SAASqC,MAAM,CAAC/B;QAChB,MAAM4B,MAAMI,eAAe,CAAChC;IAC9B;IAEA,eAAeiC,KACbjC,YAAoB,EACpBkC,IAAoC;QAEpC,MAAMC,OAAO,MAAM3C,KAAK4C,OAAO,CAAC,OAAOR;YACrC,MAAMS,YAAYH,KAAKnC,OAAOC;YAC9B,IAAIqC,cAAc,MAAM,OAAO;YAC/B9D,KAAK+D,GAAG,CAACtC,cAAcqC;YACvB,MAAMT,MAAMW,gBAAgB,CAACb,WAAW1B;YACxC,MAAM2B,OAAOC,OAAO5B;YACpB,OAAO;QACT;QACA,IAAImC,MAAMvB,KAAKZ;QACf,OAAOmC;IACT;IAEA,eAAeK,gBAAgBxC,YAAoB,EAAEkC,IAAoC;QACvF,MAAM1C,KAAK4C,OAAO,CAAC,OAAOR;YACxB,MAAMa,SAASP,KAAK/B,cAAcH;YAClCN,SAAS4C,GAAG,CAACtC,cAAcyC;YAC3B,MAAMb,MAAMY,eAAe,CAAC;gBAAExC;gBAAc,GAAGyC,MAAM;YAAC;YACtD,MAAMd,OAAOC,OAAO5B;QACtB;QACAY,KAAKZ;IACP;IAEA,eAAegC,gBAAgBhC,YAAoB;QACjD,MAAMR,KAAK4C,OAAO,CAAC,OAAOR;YACxBlC,SAASqC,MAAM,CAAC/B;YAChB,MAAM4B,MAAMI,eAAe,CAAChC;QAC9B;QACAY,KAAKZ;IACP;IAEA,SAAS0C,UAAU1C,YAAoB;QACrCH,OAAOyC,GAAG,CAACtC,cAAc,AAACH,CAAAA,OAAOI,GAAG,CAACD,iBAAiB,CAAA,IAAK;IAC7D;IAEA,eAAe2C,KAAK3C,YAAoB,EAAE5B,MAAqB;QAC7D,MAAMwE,aAAa/C,OAAOI,GAAG,CAACD;QAC9B,MAAMrC,SAAS,MAAM6B,KAAKqD,IAAI,CAAC,wBAAwB;YACrD7C;YACA,GAAI5B,WAAW,OAAO,CAAC,IAAI;gBAAEA;YAAO,CAAC;QACvC;QACA,MAAMI,OAAOd,YAAYC;QACzB,IAAIa,SAAS,MAAM,OAAOb;QAC1B,MAAMmF,SAAS,MAAMb,KAAKjC,cAAc,CAAC9B;YACvC,IAAI2B,OAAOI,GAAG,CAACD,kBAAkB4C,YAAY,OAAO;YACpD,IAAIxE,WAAW,MAAM,OAAOF,QAAQE,MAAM,KAAKA,SAASe,MAAMjB,SAASM,QAAQ;YAC/E,OAAOF,OAAOJ,SAASM,MAAMS,KAAKgB,GAAG,CAACD,iBAAiB7C;QACzD;QACA,IAAI2F,UAAU1E,WAAW,MAAMa,KAAK8C,MAAM,CAAC/B;QAC3C,OAAOrC;IACT;IAEA,SAASoF,OACPC,IAAwB,EACxBhD,YAAoB,EACpBiD,QAA2C,CAAC,CAAC;QAE7C,OAAOzD,KAAKqD,IAAI,CAACG,MAAM;YAAEhD;YAAc,GAAGiD,KAAK;QAAC;IAClD;IAEA,MAAMC,UAA+B;QACnCC;YACE,MAAMC,cAAc5D,KAAK6D,KAAK;YAC9B,IAAID,gBAAgBzC,WAAW,OAAO2C,QAAQC,OAAO,CAAC,EAAE;YACxD,MAAMC,YAAYhE,KACfgE,SAAS,GACTA,SAAS,CAACtE,MAAM,CAAC,CAACuE,WAAaA,SAASC,IAAI,KAAKN,YAAYO,OAAO;YACvE,MAAMC,UAA+B,EAAE;YACvC,KAAK,MAAMC,QAAQL,UAAW;gBAC5B,IAAIK,KAAKC,IAAI,KAAKV,YAAYU,IAAI,EAAE;gBACpC,MAAMC,WAAWF,KAAKG,MAAM,CAACZ,YAAYW,QAAQ,CAAC;gBAClD,IAAI,OAAOA,aAAa,UAAU;gBAClC,MAAME,QAAQT,UAAUU,IAAI,CAAC,CAACT,WAC5BA,SAASU,SAAS,CAACrC,IAAI,CACrB,CAACsC,WAAaA,SAAShD,IAAI,KAAKyC,KAAKzC,IAAI,IAAIgD,SAASC,KAAK,KAAKjB,YAAYa,KAAK;gBAGrFL,QAAQzF,IAAI,CAAC;oBAAE6B,cAAc+D;oBAAUF;oBAAMI,OAAOA,SAAS;gBAAK;YACpE;YACAL,QAAQ1C,IAAI,CAAC,CAACoD,MAAMC,QAAWD,KAAKtE,YAAY,GAAGuE,MAAMvE,YAAY,GAAG,CAAC,IAAI;YAC7E,OAAOsD,QAAQC,OAAO,CAACK;QACzB;QACAY,MAAKxE,YAAoB;YACvB,OAAOR,KAAK4C,OAAO,CAAC,IAAMkB,QAAQC,OAAO,CAACjD,OAAON;QACnD;QACAb,OAAMa,YAAoB;YACxB,MAAM5B,SAAS2B,OAAOC,cAAc5B,MAAM;YAC1C,IAAIA,WAAW,MAAM,OAAOkF,QAAQC,OAAO,CAAC;YAC5C,OAAOZ,KAAK3C,cAAc5B;QAC5B;QACAqG,SAAQzE,YAAoB,EAAE0E,QAA2B;YACvD,OAAOlC,gBAAgBxC,cAAc,IAAO,CAAA;oBAC1CI,MAAMsE,SAAStE,IAAI;oBACnBC,OAAO;2BAAIqE,SAASrE,KAAK;qBAAC;oBAC1B7C,MAAM;gBACR,CAAA;QACF;QACAmH,SAAQ3E,YAAoB;YAC1B,OAAOgC,gBAAgBhC;QACzB;QACA4E,QAAOC,KAAc;YACnB,OAAOrF,KAAKqD,IAAI,CAAC,uBAAuBgC;QAC1C;QACA,MAAMC,MAAK9E,YAAoB;YAC7B,MAAMO,SAASJ,cAAcH;YAC7B,MAAMK,QAAQ,MAAMiD,QAAQyB,GAAG,CAC7BxE,OAAOF,KAAK,CAAC1B,GAAG,CAAC,OAAOqG,OAAU,CAAA;oBAAE,GAAI,MAAM9H,aAAa8H,KAAK;oBAAGC,MAAMD,KAAKC,IAAI;gBAAC,CAAA;YAErF,MAAMtH,SAAS,MAAMoF,OAAO,qBAAqB/C,cAAc;gBAAEI,MAAMG,OAAOH,IAAI;gBAAEC;YAAM;YAC1F,MAAM7C,OAAOa,OAAOV;YACpB,IAAIH,SAAS,MAAM,OAAOG;YAC1B,MAAM6E,gBAAgBxC,cAAc,CAACyC,SAAY,CAAA;oBAAE,GAAGA,MAAM;oBAAEjF;gBAAK,CAAA;YACnE,OAAOG;QACT;QACAE,QAAOmC,YAAoB,EAAEnC,MAAe;YAC1C,OAAOkF,OAAO,uBAAuB/C,cAAc;gBAAEnC;YAAO;QAC9D;QACAqH,WAAUlF,YAAoB;YAC5B,OAAO+C,OAAO,0BAA0B/C;QAC1C;QACAmF,MAAKnF,YAAoB;YACvB,OAAO+C,OAAO,qBAAqB/C;QACrC;QACA,MAAMoF,QAAOpF,YAAoB,EAAExC,IAAY;YAC7C,MAAMG,SAAS,MAAMoF,OAAO,uBAAuB/C,cAAc;gBAAExC;YAAK;YACxE,IAAIG,OAAOC,IAAI,KAAK,UAAU,OAAOD;YACrC,IAAIwC,cAAcH,cAAcxC,IAAI,KAAKA,MAAM;gBAC7C,MAAMgF,gBAAgBxC,cAAc,CAACyC,SAAY,CAAA;wBAAE,GAAGA,MAAM;wBAAEjF,MAAM;oBAAK,CAAA;YAC3E;YACA,OAAOG;QACT;QACA,MAAMoE,QAAO/B,YAAoB;YAC/B,MAAMrC,SAAS,MAAMoF,OAAO,uBAAuB/C;YACnD,IAAIrC,OAAOC,IAAI,KAAK,UAAU,OAAOD;YACrCgC,QAAQoC,MAAM,CAAC/B;YACff,KAAK8C,MAAM,CAAC/B;YACZ0C,UAAU1C;YACV,MAAMR,KAAK4C,OAAO,CAAC,OAAOR;gBACxBrD,KAAKwD,MAAM,CAAC/B;gBACZN,SAASqC,MAAM,CAAC/B;gBAChB,MAAM4B,MAAMyD,gBAAgB,CAACrF;gBAC7B,MAAM4B,MAAMI,eAAe,CAAChC;YAC9B;YACAY,KAAKZ;YACL,OAAOrC;QACT;IACF;IAEA,OAAO;QACLuF;QACAoC,SAAQC,aAAa,EAAEC,YAAY;YACjCjH,KAAKkH,KAAK;YACV,KAAK,MAAMhD,UAAU8C,cAAe;gBAClChH,KAAK+D,GAAG,CAACG,OAAOzC,YAAY,EAAE;oBAC5BhC,UAAUyE,OAAOzE,QAAQ;oBACzBI,QAAQqE,OAAOrE,MAAM;oBACrB8B,SAASuC,OAAOvC,OAAO;gBACzB;YACF;YACAR,SAAS+F,KAAK;YACd,KAAK,MAAMhD,UAAU+C,aAAc;gBACjC9F,SAAS4C,GAAG,CAACG,OAAOzC,YAAY,EAAE;oBAChCI,MAAMqC,OAAOrC,IAAI;oBACjBC,OAAOoC,OAAOpC,KAAK;oBACnB7C,MAAMiF,OAAOjF,IAAI;gBACnB;YACF;QACF;QACAkI,SAAQ1F,YAAoB,EAAEgB,MAAwC;YACpE,MAAM2E,QAAQ,CAAC1E,SAAS2E,QAAQ,CAAC5F;YACjC,MAAMc,SAAkB;gBAAEd;gBAAcgB;gBAAQD,OAAO;YAAM;YAC7DnB,QAAQiG,GAAG,CAAC/E;YACZ,IAAI6E,OAAO;gBACT1G,KAAKqD,GAAG,CAACtC,cAAc,IAAI5C;gBAC3BsF,UAAU1C;YACZ;YACAmB;YACA,KAAK3B,KACF4C,OAAO,CAAC,IAAMkB,QAAQC,OAAO,IAC7BuC,IAAI,CAAC;gBACJ,IAAItG,KAAKqB,MAAM,MAAM,CAACjB,QAAQd,GAAG,CAACgC,SAAS;gBAC3CA,OAAOC,KAAK,GAAG;gBACfD,OAAOE,MAAM,CAACV,OAAON;gBACrB,IAAI2F,OAAO,MAAMhD,KAAK3C,cAAc;YACtC,GACC+F,KAAK,CAAC,KAAO;YAChB,OAAO;gBACL,IAAI,CAACnG,QAAQmC,MAAM,CAACjB,SAAS;gBAC7B,IAAI,CAACG,SAAS2E,QAAQ,CAAC5F,eAAe;oBACpCL,QAAQoC,MAAM,CAAC/B;oBACff,KAAK8C,MAAM,CAAC/B;gBACd;gBACAmB;YACF;QACF;QACA6E,SAAQC,KAAwB;YAC9B,IAAIzG,KAAKqB,MAAM,MAAM,CAACI,SAAS2E,QAAQ,CAACK,MAAMjG,YAAY,GAAG;YAC7D,IAAIiG,MAAMrI,IAAI,KAAK,gBAAgB;gBACjC+B,QAAQ2C,GAAG,CAAC2D,MAAMjG,YAAY,EAAE;oBAAExC,MAAMyI,MAAMzI,IAAI;oBAAE4C,MAAM6F,MAAM7F,IAAI;gBAAC;gBACrEQ,KAAKqF,MAAMjG,YAAY;gBACvB;YACF;YACA,IAAIiG,MAAMrI,IAAI,KAAK,mBAAmB;gBACpC,MAAMsC,UAAUzC,gBAAgBwI,MAAM/F,OAAO;gBAC7C,KAAK+B,KAAKgE,MAAMjG,YAAY,EAAE,CAAC9B,UAAa,CAAA;wBAAE,GAAGA,OAAO;wBAAEgC;oBAAQ,CAAA,GAAI6F,KAAK,CAAC,KAAO;gBACnF;YACF;YACA,MAAMrH,WAAWrB,eAAe4I,MAAMC,OAAO;YAC7C,IAAIxH,aAAa,MAAM;YACvBO,KAAKgB,GAAG,CAACgG,MAAMjG,YAAY,GAAG6F,IAAInH,SAASlB,IAAI;YAC/C,IAAImC,QAAQM,GAAG,CAACgG,MAAMjG,YAAY,GAAGxC,SAASkB,SAASlB,IAAI,EAAE;gBAC3DmC,QAAQoC,MAAM,CAACkE,MAAMjG,YAAY;YACnC;YACA,KAAKR,KACF4C,OAAO,CAAC,OAAOR;gBACdrD,KAAK+D,GAAG,CAAC2D,MAAMjG,YAAY,EAAEX,OAAOU,OAAOkG,MAAMjG,YAAY,GAAGtB;gBAChE,MAAMkD,MAAMW,gBAAgB,CAACb,WAAWuE,MAAMjG,YAAY;gBAC1D,MAAM2B,OAAOC,OAAOqE,MAAMjG,YAAY;YACxC,GACC8F,IAAI,CAAC;gBACJlF,KAAKqF,MAAMjG,YAAY;YACzB,GACC+F,KAAK,CAAC,KAAO;QAClB;QACAI;YACEvG,QAAQ6F,KAAK;YACb9F,QAAQ8F,KAAK;YACbxG,KAAKwG,KAAK;QACZ;IACF;AACF"}
@@ -0,0 +1,9 @@
1
+ export type RuntimeShell = {
2
+ persistentStorage: boolean;
3
+ generation: string | null;
4
+ keepGeneration(value: string): void | Promise<void>;
5
+ dropGeneration(): void | Promise<void>;
6
+ };
7
+ export declare function detectStorageLoss(stored: string | null, reported: string | null): string | null;
8
+ export declare function nextGeneration(stored: string | null, reported: string | null): string;
9
+ //# sourceMappingURL=durability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,cAAc,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAG/F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGrF"}
@@ -0,0 +1,11 @@
1
+ const STORAGE_LOSS_STATEMENT = 'Unsynchronized Drafts and Pending Changes were lost.';
2
+ export function detectStorageLoss(stored, reported) {
3
+ if (reported === null || reported === stored) return null;
4
+ return STORAGE_LOSS_STATEMENT;
5
+ }
6
+ export function nextGeneration(stored, reported) {
7
+ if (stored !== null && (reported === null || reported === stored)) return stored;
8
+ return crypto.randomUUID();
9
+ }
10
+
11
+ //# sourceMappingURL=durability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/workspace/packages/mdruntime/src/durability.ts"],"sourcesContent":["const STORAGE_LOSS_STATEMENT = 'Unsynchronized Drafts and Pending Changes were lost.';\n\nexport type RuntimeShell = {\n persistentStorage: boolean;\n generation: string | null;\n keepGeneration(value: string): void | Promise<void>;\n dropGeneration(): void | Promise<void>;\n};\n\nexport function detectStorageLoss(stored: string | null, reported: string | null): string | null {\n if (reported === null || reported === stored) return null;\n return STORAGE_LOSS_STATEMENT;\n}\n\nexport function nextGeneration(stored: string | null, reported: string | null): string {\n if (stored !== null && (reported === null || reported === stored)) return stored;\n return crypto.randomUUID();\n}\n"],"names":["STORAGE_LOSS_STATEMENT","detectStorageLoss","stored","reported","nextGeneration","crypto","randomUUID"],"mappings":"AAAA,MAAMA,yBAAyB;AAS/B,OAAO,SAASC,kBAAkBC,MAAqB,EAAEC,QAAuB;IAC9E,IAAIA,aAAa,QAAQA,aAAaD,QAAQ,OAAO;IACrD,OAAOF;AACT;AAEA,OAAO,SAASI,eAAeF,MAAqB,EAAEC,QAAuB;IAC3E,IAAID,WAAW,QAASC,CAAAA,aAAa,QAAQA,aAAaD,MAAK,GAAI,OAAOA;IAC1E,OAAOG,OAAOC,UAAU;AAC1B"}
package/dist/file.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ export type InstructionFile = {
2
+ name: string;
3
+ blob: Blob;
4
+ };
5
+ export type FileDeclaration = {
6
+ name: string;
7
+ mediaType: string;
8
+ byteLength: number;
9
+ contentHash: string;
10
+ width?: number;
11
+ height?: number;
12
+ };
13
+ export type Extent = {
14
+ width: number;
15
+ height: number;
16
+ };
17
+ export declare function extentOf(bytes: Uint8Array): Extent | null;
18
+ export declare function hexDigest(digest: ArrayBuffer): string;
19
+ export declare function declaredFile(file: InstructionFile): Promise<FileDeclaration>;
20
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA2GvD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAQzD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAErD;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAUlF"}
package/dist/file.js ADDED
@@ -0,0 +1,149 @@
1
+ const PNG_SIGNATURE = [
2
+ 0x89,
3
+ 0x50,
4
+ 0x4e,
5
+ 0x47,
6
+ 0x0d,
7
+ 0x0a,
8
+ 0x1a,
9
+ 0x0a
10
+ ];
11
+ const WEBP_SYNC = [
12
+ 0x9d,
13
+ 0x01,
14
+ 0x2a
15
+ ];
16
+ const SHAPELESS_JPEG_MARKERS = new Set([
17
+ 0xc4,
18
+ 0xc8,
19
+ 0xcc
20
+ ]);
21
+ const BMFF_CONTAINERS = new Set([
22
+ 'meta',
23
+ 'iprp',
24
+ 'ipco'
25
+ ]);
26
+ function starts(bytes, signature, at = 0) {
27
+ return signature.every((value, index)=>bytes[at + index] === value);
28
+ }
29
+ function text(bytes, at, length) {
30
+ return String.fromCharCode(...bytes.subarray(at, at + length));
31
+ }
32
+ function bigEndian(bytes, at, length) {
33
+ let value = 0;
34
+ for(let index = 0; index < length; index += 1)value = value * 256 + (bytes[at + index] ?? 0);
35
+ return value;
36
+ }
37
+ function littleEndian(bytes, at, length) {
38
+ let value = 0;
39
+ for(let index = length - 1; index >= 0; index -= 1)value = value * 256 + (bytes[at + index] ?? 0);
40
+ return value;
41
+ }
42
+ function pngExtent(bytes) {
43
+ if (bytes.length < 24 || !starts(bytes, PNG_SIGNATURE)) return null;
44
+ if (text(bytes, 12, 4) !== 'IHDR') return null;
45
+ return {
46
+ width: bigEndian(bytes, 16, 4),
47
+ height: bigEndian(bytes, 20, 4)
48
+ };
49
+ }
50
+ function gifExtent(bytes) {
51
+ if (bytes.length < 10 || text(bytes, 0, 3) !== 'GIF') return null;
52
+ return {
53
+ width: littleEndian(bytes, 6, 2),
54
+ height: littleEndian(bytes, 8, 2)
55
+ };
56
+ }
57
+ function jpegExtent(bytes) {
58
+ if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return null;
59
+ let at = 2;
60
+ while(at + 9 < bytes.length){
61
+ if (bytes[at] !== 0xff) return null;
62
+ const marker = bytes[at + 1] ?? 0;
63
+ if (marker === 0xd8 || marker === 0x01 || marker >= 0xd0 && marker <= 0xd7) {
64
+ at += 2;
65
+ continue;
66
+ }
67
+ const length = bigEndian(bytes, at + 2, 2);
68
+ if (marker >= 0xc0 && marker <= 0xcf && !SHAPELESS_JPEG_MARKERS.has(marker)) {
69
+ return {
70
+ width: bigEndian(bytes, at + 7, 2),
71
+ height: bigEndian(bytes, at + 5, 2)
72
+ };
73
+ }
74
+ if (length < 2) return null;
75
+ at += 2 + length;
76
+ }
77
+ return null;
78
+ }
79
+ function webpExtent(bytes) {
80
+ if (bytes.length < 30 || text(bytes, 0, 4) !== 'RIFF' || text(bytes, 8, 4) !== 'WEBP') return null;
81
+ const chunk = text(bytes, 12, 4);
82
+ if (chunk === 'VP8X') {
83
+ return {
84
+ width: littleEndian(bytes, 24, 3) + 1,
85
+ height: littleEndian(bytes, 27, 3) + 1
86
+ };
87
+ }
88
+ if (chunk === 'VP8 ' && starts(bytes, WEBP_SYNC, 23)) {
89
+ return {
90
+ width: littleEndian(bytes, 26, 2) & 0x3fff,
91
+ height: littleEndian(bytes, 28, 2) & 0x3fff
92
+ };
93
+ }
94
+ if (chunk === 'VP8L' && bytes[20] === 0x2f) {
95
+ const packed = littleEndian(bytes, 21, 4);
96
+ return {
97
+ width: (packed & 0x3fff) + 1,
98
+ height: (packed >>> 14 & 0x3fff) + 1
99
+ };
100
+ }
101
+ return null;
102
+ }
103
+ function spatialExtent(bytes, from, to) {
104
+ let at = from;
105
+ while(at + 8 <= to){
106
+ const size = bigEndian(bytes, at, 4);
107
+ const name = text(bytes, at + 4, 4);
108
+ const end = size === 0 ? to : at + size;
109
+ if (size === 1 || end <= at || end > to) return null;
110
+ if (name === 'ispe') {
111
+ if (end < at + 20) return null;
112
+ return {
113
+ width: bigEndian(bytes, at + 12, 4),
114
+ height: bigEndian(bytes, at + 16, 4)
115
+ };
116
+ }
117
+ if (BMFF_CONTAINERS.has(name)) {
118
+ const inner = spatialExtent(bytes, at + (name === 'meta' ? 12 : 8), end);
119
+ if (inner !== null) return inner;
120
+ }
121
+ at = end;
122
+ }
123
+ return null;
124
+ }
125
+ function bmffExtent(bytes) {
126
+ if (bytes.length < 16 || text(bytes, 4, 4) !== 'ftyp') return null;
127
+ return spatialExtent(bytes, 0, bytes.length);
128
+ }
129
+ export function extentOf(bytes) {
130
+ return pngExtent(bytes) ?? jpegExtent(bytes) ?? gifExtent(bytes) ?? webpExtent(bytes) ?? bmffExtent(bytes);
131
+ }
132
+ export function hexDigest(digest) {
133
+ return [
134
+ ...new Uint8Array(digest)
135
+ ].map((value)=>value.toString(16).padStart(2, '0')).join('');
136
+ }
137
+ export async function declaredFile(file) {
138
+ const bytes = new Uint8Array(await file.blob.arrayBuffer());
139
+ const extent = file.blob.type.startsWith('image/') ? extentOf(bytes) : null;
140
+ return {
141
+ name: file.name,
142
+ mediaType: file.blob.type,
143
+ byteLength: bytes.byteLength,
144
+ contentHash: hexDigest(await crypto.subtle.digest('SHA-256', bytes)),
145
+ ...extent === null ? {} : extent
146
+ };
147
+ }
148
+
149
+ //# sourceMappingURL=file.js.map