@agentshouse/mdruntime 0.1.0-alpha.9 → 0.1.0-c3903a68dcbbf

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 (124) hide show
  1. package/AGENTS.md +210 -0
  2. package/README.md +44 -0
  3. package/dist/adapter.d.ts +155 -12
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +2 -3
  6. package/dist/attachment.d.ts +3 -0
  7. package/dist/attachment.d.ts.map +1 -0
  8. package/dist/attachment.js +8 -0
  9. package/dist/bootstrap.d.ts +3 -7
  10. package/dist/bootstrap.d.ts.map +1 -1
  11. package/dist/bootstrap.js +29 -27
  12. package/dist/change-set.d.ts +33 -0
  13. package/dist/change-set.d.ts.map +1 -0
  14. package/dist/change-set.js +87 -0
  15. package/dist/change.d.ts +9 -0
  16. package/dist/change.d.ts.map +1 -1
  17. package/dist/change.js +6 -5
  18. package/dist/client.d.ts +40 -28
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +121 -177
  21. package/dist/collection.d.ts +15 -16
  22. package/dist/collection.d.ts.map +1 -1
  23. package/dist/collection.js +54 -87
  24. package/dist/connection.d.ts +57 -35
  25. package/dist/connection.d.ts.map +1 -1
  26. package/dist/connection.js +1475 -964
  27. package/dist/conversation.d.ts +41 -19
  28. package/dist/conversation.d.ts.map +1 -1
  29. package/dist/conversation.js +483 -327
  30. package/dist/durability.js +6 -6
  31. package/dist/file.d.ts +2 -2
  32. package/dist/file.d.ts.map +1 -1
  33. package/dist/file.js +48 -71
  34. package/dist/host.d.ts +7 -2
  35. package/dist/host.d.ts.map +1 -1
  36. package/dist/host.js +100 -102
  37. package/dist/identity.d.ts +1 -0
  38. package/dist/identity.d.ts.map +1 -1
  39. package/dist/identity.js +10 -9
  40. package/dist/index.d.ts +12 -10
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -2
  43. package/dist/log.js +5 -6
  44. package/dist/memory.d.ts +6 -3
  45. package/dist/memory.d.ts.map +1 -1
  46. package/dist/memory.js +206 -210
  47. package/dist/model.d.ts +10 -25
  48. package/dist/model.d.ts.map +1 -1
  49. package/dist/model.js +18 -21
  50. package/dist/observation.d.ts +25 -0
  51. package/dist/observation.d.ts.map +1 -0
  52. package/dist/observation.js +85 -0
  53. package/dist/outcome.d.ts +17 -20
  54. package/dist/outcome.d.ts.map +1 -1
  55. package/dist/outcome.js +1 -23
  56. package/dist/overlay.d.ts +9 -2
  57. package/dist/overlay.d.ts.map +1 -1
  58. package/dist/overlay.js +187 -43
  59. package/dist/paths.d.ts +0 -1
  60. package/dist/paths.d.ts.map +1 -1
  61. package/dist/paths.js +12 -19
  62. package/dist/query.d.ts +210 -10
  63. package/dist/query.d.ts.map +1 -1
  64. package/dist/query.js +420 -15
  65. package/dist/read-side.d.ts +14 -0
  66. package/dist/read-side.d.ts.map +1 -0
  67. package/dist/read-side.js +371 -0
  68. package/dist/refresh.d.ts +4 -5
  69. package/dist/refresh.d.ts.map +1 -1
  70. package/dist/refresh.js +45 -30
  71. package/dist/replica.d.ts +25 -5
  72. package/dist/replica.d.ts.map +1 -1
  73. package/dist/replica.js +232 -38
  74. package/dist/room-draft.d.ts +124 -0
  75. package/dist/room-draft.d.ts.map +1 -0
  76. package/dist/room-draft.js +327 -0
  77. package/dist/rows.d.ts +41 -0
  78. package/dist/rows.d.ts.map +1 -0
  79. package/dist/rows.js +100 -0
  80. package/dist/save.d.ts +8 -4
  81. package/dist/save.d.ts.map +1 -1
  82. package/dist/save.js +265 -350
  83. package/dist/search.d.ts +61 -0
  84. package/dist/search.d.ts.map +1 -0
  85. package/dist/search.js +125 -0
  86. package/dist/status.d.ts +4 -6
  87. package/dist/status.d.ts.map +1 -1
  88. package/dist/status.js +9 -13
  89. package/dist/store.d.ts +68 -30
  90. package/dist/store.d.ts.map +1 -1
  91. package/dist/store.js +360 -134
  92. package/dist/transfer.d.ts +5 -0
  93. package/dist/transfer.d.ts.map +1 -1
  94. package/dist/transfer.js +1 -5
  95. package/dist/wire.d.ts +4 -3
  96. package/dist/wire.d.ts.map +1 -1
  97. package/dist/wire.js +50 -65
  98. package/package.json +6 -12
  99. package/dist/adapter.js.map +0 -1
  100. package/dist/bootstrap.js.map +0 -1
  101. package/dist/change.js.map +0 -1
  102. package/dist/client.js.map +0 -1
  103. package/dist/collection.js.map +0 -1
  104. package/dist/connection.js.map +0 -1
  105. package/dist/conversation.js.map +0 -1
  106. package/dist/durability.js.map +0 -1
  107. package/dist/file.js.map +0 -1
  108. package/dist/host.js.map +0 -1
  109. package/dist/identity.js.map +0 -1
  110. package/dist/index.js.map +0 -1
  111. package/dist/log.js.map +0 -1
  112. package/dist/memory.js.map +0 -1
  113. package/dist/model.js.map +0 -1
  114. package/dist/outcome.js.map +0 -1
  115. package/dist/overlay.js.map +0 -1
  116. package/dist/paths.js.map +0 -1
  117. package/dist/query.js.map +0 -1
  118. package/dist/refresh.js.map +0 -1
  119. package/dist/replica.js.map +0 -1
  120. package/dist/save.js.map +0 -1
  121. package/dist/status.js.map +0 -1
  122. package/dist/store.js.map +0 -1
  123. package/dist/transfer.js.map +0 -1
  124. package/dist/wire.js.map +0 -1
@@ -1,401 +1,557 @@
1
+ import { conversationChangeSet } from './change-set.js';
1
2
  import { declaredFile } from './file.js';
2
- const NO_TURNS = new Set();
3
- function carriedMessage(value) {
4
- if (typeof value !== 'object' || value === null) return null;
3
+ const QUIET = {
4
+ growingText: null,
5
+ plan: null,
6
+ workingNote: null,
7
+ questions: [],
8
+ process: null,
9
+ commands: [],
10
+ options: [],
11
+ };
12
+ function ordered(left, right) {
13
+ if (left.position !== right.position)
14
+ return left.position < right.position ? -1 : 1;
15
+ if (left.identity !== right.identity)
16
+ return left.identity < right.identity ? -1 : 1;
17
+ return 0;
18
+ }
19
+ function merged(held, arriving) {
20
+ const byIdentity = new Map(held.map((message) => [message.identity, message]));
21
+ for (const message of arriving)
22
+ byIdentity.set(message.identity, message);
23
+ return [...byIdentity.values()].sort(ordered);
24
+ }
25
+ function acceptedMessage(value) {
26
+ if (typeof value !== 'object' || value === null)
27
+ return null;
5
28
  const record = value;
6
- if (typeof record.turn !== 'string') return null;
7
- return structuredClone(value);
29
+ if (typeof record.identity !== 'string' ||
30
+ typeof record.turn !== 'string' ||
31
+ typeof record.position !== 'string' ||
32
+ typeof record.text !== 'string') {
33
+ return null;
34
+ }
35
+ return {
36
+ identity: record.identity,
37
+ turn: record.turn,
38
+ position: record.position,
39
+ text: record.text,
40
+ message: structuredClone(record.message),
41
+ };
8
42
  }
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;
43
+ function historyPage(value) {
44
+ if (typeof value !== 'object' || value === null)
45
+ return null;
46
+ const record = value;
47
+ if (typeof record.conversation !== 'string' || !Array.isArray(record.messages))
48
+ return null;
49
+ if (record.next !== undefined && typeof record.next !== 'string')
50
+ return null;
15
51
  const messages = [];
16
- for (const entry of record.messages){
17
- const carried = carriedMessage(entry);
18
- if (carried !== null) messages.push(carried);
52
+ for (const entry of record.messages) {
53
+ const message = acceptedMessage(entry);
54
+ if (message === null)
55
+ return null;
56
+ messages.push(message);
19
57
  }
20
58
  return {
59
+ conversation: record.conversation,
21
60
  messages,
22
- cursor: typeof record.cursor === 'string' ? record.cursor : null
61
+ ...(record.next === undefined ? {} : { next: record.next }),
23
62
  };
24
63
  }
64
+ function historyPages(result) {
65
+ if (result.kind !== 'action')
66
+ return null;
67
+ const answer = result.result;
68
+ if (typeof answer !== 'object' || answer === null)
69
+ return null;
70
+ const { histories } = answer;
71
+ if (!Array.isArray(histories))
72
+ return null;
73
+ const pages = [];
74
+ for (const entry of histories) {
75
+ const page = historyPage(entry);
76
+ if (page === null)
77
+ return null;
78
+ pages.push(page);
79
+ }
80
+ return pages;
81
+ }
25
82
  function turnOf(result) {
26
- if (result.kind !== 'action') return null;
83
+ if (result.kind !== 'action')
84
+ return null;
27
85
  const answer = result.result;
28
- if (typeof answer !== 'object' || answer === null) return null;
86
+ if (typeof answer !== 'object' || answer === null)
87
+ return null;
29
88
  const record = answer;
30
89
  return typeof record.turn === 'string' ? record.turn : null;
31
90
  }
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
- };
91
+ function limitOf(options) {
92
+ const { limit } = options;
93
+ if (limit === undefined)
94
+ return undefined;
95
+ if (!Number.isInteger(limit) || limit < 0) {
96
+ throw new Error(`conversation limit must be a non-negative integer, got ${String(limit)}`);
97
+ }
98
+ return limit;
79
99
  }
80
100
  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
+ const messages = new Map();
102
+ const reads = new Map();
103
+ const drafts = new Map();
104
+ const sending = new Map();
105
+ const behind = new Set();
106
+ const gaps = new Map();
107
+ const lives = new Map();
108
+ const views = new Map();
109
+ let replicated = new Set();
110
+ let handedReplicated;
111
+ function liveOf(conversation) {
112
+ return lives.get(conversation) ?? QUIET;
101
113
  }
102
- function viewOf(conversation) {
103
- const carried = heldOf(conversation);
104
- const unsent = composed.get(conversation);
114
+ function viewOf(conversation, limit) {
115
+ const held = messages.get(conversation) ?? [];
116
+ const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
117
+ const draft = drafts.get(conversation);
118
+ const facts = liveOf(conversation);
105
119
  return {
106
120
  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
- }
121
+ messages: shown.map(({ identity, turn, text, message }) => ({
122
+ identity,
123
+ turn,
124
+ text,
125
+ message: structuredClone(message),
126
+ })),
127
+ growingText: facts.growingText === null ? null : { ...facts.growingText },
128
+ workingNote: facts.workingNote === null ? null : { ...facts.workingNote },
129
+ plan: structuredClone(facts.plan?.plan ?? null),
130
+ questions: structuredClone(facts.questions),
131
+ process: facts.process === null ? null : { ...facts.process },
132
+ commands: structuredClone(facts.commands),
133
+ options: structuredClone(facts.options),
134
+ chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft
135
+ ? null
136
+ : { text: draft.text, files: [...draft.files] },
117
137
  };
118
138
  }
119
139
  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));
140
+ if (deps.closed())
141
+ return;
142
+ for (const view of views.values()) {
143
+ if (view.conversation !== conversation)
144
+ continue;
145
+ const ready = [...view.watchers].filter((watcher) => watcher.ready);
146
+ if (ready.length === 0)
147
+ continue;
148
+ const value = viewOf(conversation, view.limit);
149
+ for (const watcher of ready)
150
+ watcher.notify(value);
123
151
  }
124
152
  }
125
- function viewed() {
126
- return [
127
- ...new Set([
128
- ...viewers
129
- ].map((viewer)=>viewer.conversation))
130
- ].sort();
153
+ function live(conversation, change) {
154
+ lives.set(conversation, { ...liveOf(conversation), ...change });
155
+ emit(conversation);
131
156
  }
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);
157
+ function sealTurns(conversation, turns) {
158
+ const held = lives.get(conversation);
159
+ if (held === undefined)
160
+ return false;
161
+ const growingText = held.growingText !== null && turns.has(held.growingText.turn) ? null : held.growingText;
162
+ const plan = held.plan !== null && turns.has(held.plan.turn) ? null : held.plan;
163
+ if (growingText === held.growingText && plan === held.plan)
164
+ return false;
165
+ lives.set(conversation, { ...held, growingText, plan });
166
+ return true;
140
167
  }
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
- };
168
+ function handReplicated() {
169
+ const stream = deps.stream();
170
+ if (stream === undefined)
171
+ return;
172
+ const held = [...replicated].sort();
173
+ const named = JSON.stringify(held);
174
+ if (named === handedReplicated)
175
+ return;
176
+ handedReplicated = named;
177
+ stream.replicate(held);
149
178
  }
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);
179
+ async function documented() {
180
+ const declaration = deps.cards();
181
+ if (declaration === undefined)
182
+ return [];
183
+ const { documents } = await deps.documents(declaration.locator);
184
+ return documents.filter((document) => typeof document.type === 'string');
185
+ }
186
+ function carded(documents) {
187
+ const declaration = deps.cards();
188
+ if (declaration === undefined)
189
+ return [];
190
+ const cards = [];
191
+ for (const card of documents) {
192
+ if (card.type !== declaration.type)
193
+ continue;
194
+ const identity = card.fields[declaration.identity];
195
+ if (typeof identity === 'string')
196
+ cards.push({ conversation: identity, card });
197
+ }
198
+ return cards;
156
199
  }
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;
200
+ async function remove(leaving) {
201
+ await deps.durably(async (local) => {
202
+ await local.dropConversations(leaving);
203
+ const removed = [];
204
+ const changed = [];
205
+ for (const conversation of leaving) {
206
+ for (const message of messages.get(conversation) ?? []) {
207
+ removed.push({ conversation, identity: message.identity });
208
+ }
209
+ if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
210
+ changed.push(conversation);
211
+ }
212
+ messages.delete(conversation);
213
+ reads.delete(conversation);
214
+ drafts.delete(conversation);
215
+ behind.delete(conversation);
216
+ lives.delete(conversation);
217
+ }
218
+ if (changed.length > 0)
219
+ deps.publish(conversationChangeSet(removed, changed));
165
220
  });
166
- if (kept) emit(conversation);
167
- return kept;
168
221
  }
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({
222
+ function gap(conversation) {
223
+ gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
224
+ behind.add(conversation);
225
+ }
226
+ function keep(conversation, message) {
227
+ return deps.durably(async (local) => {
228
+ if (!replicated.has(conversation))
229
+ return;
230
+ const read = reads.get(conversation);
231
+ const advances = read !== undefined &&
232
+ !behind.has(conversation) &&
233
+ (read === null || message.position > read);
234
+ const sealed = drafts.get(conversation)?.turn === message.turn;
235
+ await local.keepMessages({
174
236
  conversation,
175
- ...stored
237
+ messages: [message],
238
+ ...(advances ? { read: message.position } : {}),
239
+ sealed,
176
240
  });
177
- await settle(local, conversation);
241
+ messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
242
+ if (advances)
243
+ reads.set(conversation, message.position);
244
+ if (sealed)
245
+ drafts.delete(conversation);
246
+ sealTurns(conversation, new Set([message.turn]));
247
+ deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
178
248
  });
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
249
  }
191
- async function fill(conversation, cursor) {
192
- const generation = issued.get(conversation);
193
- const result = await deps.call('conversation.history', {
250
+ async function readHistories(conversations) {
251
+ const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
252
+ const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
253
+ const arriving = new Map();
254
+ const complete = new Set();
255
+ let asking = conversations.map((conversation) => ({
194
256
  conversation,
195
- ...cursor === null ? {} : {
196
- cursor
257
+ after: starts.get(conversation) ?? undefined,
258
+ }));
259
+ while (asking.length > 0) {
260
+ const result = await deps.call('conversation.history', {
261
+ conversations: asking.map(({ conversation, after }) => ({
262
+ conversation,
263
+ ...(after === undefined ? {} : { after }),
264
+ })),
265
+ });
266
+ const pages = historyPages(result);
267
+ if (pages === null)
268
+ return result;
269
+ const asked = new Set(asking.map(({ conversation }) => conversation));
270
+ const following = [];
271
+ for (const page of pages) {
272
+ if (!asked.has(page.conversation))
273
+ continue;
274
+ arriving.set(page.conversation, [
275
+ ...(arriving.get(page.conversation) ?? []),
276
+ ...page.messages,
277
+ ]);
278
+ if (page.next === undefined)
279
+ complete.add(page.conversation);
280
+ else
281
+ following.push({ conversation: page.conversation, after: page.next });
282
+ }
283
+ asking = following;
284
+ }
285
+ for (const [conversation, held] of arriving) {
286
+ if (!complete.has(conversation))
287
+ continue;
288
+ await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
289
+ }
290
+ return null;
291
+ }
292
+ function keepHistory(conversation, arriving, from, generation) {
293
+ return deps.durably(async (local) => {
294
+ if (!replicated.has(conversation))
295
+ return;
296
+ const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
297
+ const turn = drafts.get(conversation)?.turn;
298
+ const sealed = arriving.some((message) => message.turn === turn);
299
+ await local.keepMessages({ conversation, messages: arriving, read: newest, sealed });
300
+ messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
301
+ reads.set(conversation, newest);
302
+ if (sealed)
303
+ drafts.delete(conversation);
304
+ sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
305
+ if ((gaps.get(conversation) ?? 0) === generation)
306
+ behind.delete(conversation);
307
+ if (arriving.length > 0 || sealed) {
308
+ deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
197
309
  }
198
310
  });
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);
311
+ }
312
+ function withDraft(conversation, next) {
313
+ return deps.durably(async (local) => {
314
+ const following = next(drafts.get(conversation));
315
+ if (following === undefined)
316
+ return;
317
+ if (following === null) {
318
+ await local.dropChatDraft(conversation);
319
+ drafts.delete(conversation);
320
+ }
321
+ else {
322
+ await local.keepChatDraft(following);
323
+ drafts.set(conversation, following);
324
+ }
325
+ deps.publish(conversationChangeSet([], [conversation]));
205
326
  });
206
- if (landed && cursor === null) live.delete(conversation);
207
- return result;
208
327
  }
209
328
  function invoke(name, conversation, extra = {}) {
210
- return deps.call(name, {
211
- conversation,
212
- ...extra
213
- });
329
+ return deps.call(name, { conversation, ...extra });
214
330
  }
215
331
  const surface = {
216
- list () {
332
+ async list() {
217
333
  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
- }));
334
+ if (declaration === undefined)
335
+ return [];
336
+ const documents = await documented();
337
+ const entries = carded(documents).map(({ conversation, card }) => ({
338
+ conversation,
339
+ card,
340
+ agent: documents.find((document) => document.backlinks.some((backlink) => backlink.path === card.path && backlink.field === declaration.agent)) ?? null,
341
+ }));
342
+ entries.sort((left, right) => (left.conversation < right.conversation ? -1 : 1));
343
+ return entries;
251
344
  },
252
- discard (conversation) {
253
- return dropInstruction(conversation);
345
+ async read(conversation, options = {}) {
346
+ const limit = limitOf(options);
347
+ return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
254
348
  },
255
- create (input) {
349
+ create(input) {
256
350
  return deps.call('conversation.create', input);
257
351
  },
258
- async send (conversation) {
259
- const unsent = instructionOf(conversation);
260
- const files = await Promise.all(unsent.files.map(declaredFile));
261
- const result = await invoke('conversation.send', conversation, {
262
- text: unsent.text,
263
- files
264
- });
265
- const turn = turnOf(result);
266
- if (turn === null) return result;
267
- await keepInstruction(conversation, (stored)=>({
268
- ...stored,
269
- turn
270
- }));
271
- return result;
352
+ async send(conversation) {
353
+ const draft = drafts.get(conversation);
354
+ const pending = draft === undefined ? undefined : { draft };
355
+ if (pending !== undefined) {
356
+ sending.set(conversation, pending);
357
+ deps.publish(conversationChangeSet([], [conversation]));
358
+ }
359
+ try {
360
+ const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
361
+ ...(await declaredFile(file)),
362
+ blob: file.blob,
363
+ })));
364
+ const result = await invoke('conversation.send', conversation, {
365
+ text: draft?.text ?? '',
366
+ files,
367
+ });
368
+ const turn = turnOf(result);
369
+ if (turn === null || draft === undefined)
370
+ return result;
371
+ await withDraft(conversation, (current) => {
372
+ if (current !== draft)
373
+ return undefined;
374
+ const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
375
+ return sealed ? null : { ...draft, turn };
376
+ });
377
+ return result;
378
+ }
379
+ finally {
380
+ if (pending !== undefined && sending.get(conversation) === pending) {
381
+ sending.delete(conversation);
382
+ if (drafts.get(conversation) === draft) {
383
+ deps.publish(conversationChangeSet([], [conversation]));
384
+ }
385
+ }
386
+ }
272
387
  },
273
- answer (conversation, answer) {
274
- return invoke('conversation.answer', conversation, {
275
- answer
276
- });
388
+ answer(conversation, answer) {
389
+ return invoke('conversation.answer', conversation, { answer });
277
390
  },
278
- interrupt (conversation) {
391
+ interrupt(conversation) {
279
392
  return invoke('conversation.interrupt', conversation);
280
393
  },
281
- kill (conversation) {
394
+ kill(conversation) {
282
395
  return invoke('conversation.kill', conversation);
283
396
  },
284
- async cancel (conversation, turn) {
285
- const result = await invoke('conversation.cancel', conversation, {
286
- turn
287
- });
288
- if (result.kind !== 'action') return result;
289
- if (instructionOf(conversation).turn === turn) {
290
- await keepInstruction(conversation, (stored)=>({
291
- ...stored,
292
- turn: null
293
- }));
294
- }
397
+ async cancel(conversation, turn) {
398
+ const result = await invoke('conversation.cancel', conversation, { turn });
399
+ if (result.kind !== 'action')
400
+ return result;
401
+ await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
295
402
  return result;
296
403
  },
297
- async delete (conversation) {
298
- const result = await invoke('conversation.delete', conversation);
299
- if (result.kind !== 'action') return result;
300
- growing.delete(conversation);
301
- live.delete(conversation);
302
- supersede(conversation);
303
- await deps.durably(async (local)=>{
304
- held.delete(conversation);
305
- composed.delete(conversation);
306
- await local.dropConversation(conversation);
307
- await local.dropInstruction(conversation);
308
- });
309
- emit(conversation);
310
- return result;
311
- }
404
+ delete(conversation) {
405
+ return invoke('conversation.delete', conversation);
406
+ },
407
+ };
408
+ const chatDraft = {
409
+ write(conversation, draft) {
410
+ return withDraft(conversation, () => ({
411
+ conversation,
412
+ text: draft.text,
413
+ files: [...draft.files],
414
+ turn: null,
415
+ }));
416
+ },
417
+ discard(conversation) {
418
+ return withDraft(conversation, (current) => (current === undefined ? undefined : null));
419
+ },
312
420
  };
313
421
  return {
314
422
  surface,
315
- hydrate (conversations, instructions) {
316
- held.clear();
317
- for (const stored of conversations){
318
- held.set(stored.conversation, {
319
- messages: stored.messages,
320
- cursor: stored.cursor,
321
- summary: stored.summary
322
- });
423
+ chatDraft,
424
+ hydrate(held, stored) {
425
+ messages.clear();
426
+ reads.clear();
427
+ for (const entry of held) {
428
+ messages.set(entry.conversation, [...entry.messages].sort(ordered));
429
+ if (entry.read !== undefined)
430
+ reads.set(entry.conversation, entry.read);
323
431
  }
324
- composed.clear();
325
- for (const stored of instructions){
326
- composed.set(stored.conversation, {
327
- text: stored.text,
328
- files: stored.files,
329
- turn: stored.turn
330
- });
432
+ drafts.clear();
433
+ for (const record of stored)
434
+ drafts.set(record.conversation, record);
435
+ },
436
+ async reconcile() {
437
+ const named = new Set(carded(await documented()).map((entry) => entry.conversation));
438
+ const leaving = [
439
+ ...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
440
+ ].filter((conversation) => !named.has(conversation));
441
+ for (const conversation of named) {
442
+ if (!replicated.has(conversation))
443
+ behind.add(conversation);
331
444
  }
445
+ replicated = named;
446
+ handReplicated();
447
+ if (leaving.length > 0)
448
+ await remove(leaving);
332
449
  },
333
- observe (conversation, notify) {
334
- const first = !viewed().includes(conversation);
335
- const viewer = {
336
- conversation,
337
- notify,
338
- ready: false
339
- };
340
- viewers.add(viewer);
341
- if (first) {
342
- live.set(conversation, new Set());
343
- supersede(conversation);
450
+ async catchUp(progress) {
451
+ const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
452
+ if (reading.length > 0 && !deps.closed()) {
453
+ const failed = await readHistories(reading);
454
+ if (failed?.kind === 'transport-uncertainty')
455
+ progress.reachable = false;
344
456
  }
345
- hand();
346
- void deps.durably(()=>Promise.resolve()).then(async ()=>{
347
- if (deps.closed() || !viewers.has(viewer)) return;
348
- viewer.ready = true;
349
- viewer.notify(viewOf(conversation));
350
- if (first) await fill(conversation, null);
351
- }).catch(()=>{});
352
- return ()=>{
353
- if (!viewers.delete(viewer)) return;
354
- if (!viewed().includes(conversation)) {
355
- growing.delete(conversation);
356
- live.delete(conversation);
357
- }
358
- hand();
457
+ if (behind.size > 0)
458
+ progress.complete = false;
459
+ },
460
+ current() {
461
+ return behind.size === 0;
462
+ },
463
+ reconnected() {
464
+ for (const conversation of replicated)
465
+ gap(conversation);
466
+ },
467
+ watch(conversation, options, deliver) {
468
+ const limit = limitOf(options);
469
+ const key = JSON.stringify([conversation, limit ?? null]);
470
+ const view = views.get(key) ?? { conversation, limit, watchers: new Set() };
471
+ views.set(key, view);
472
+ const watcher = { notify: deliver, ready: false };
473
+ view.watchers.add(watcher);
474
+ void deps
475
+ .durably(() => Promise.resolve())
476
+ .then(() => {
477
+ if (deps.closed() || !view.watchers.has(watcher))
478
+ return;
479
+ watcher.ready = true;
480
+ watcher.notify(viewOf(conversation, limit));
481
+ })
482
+ .catch(() => { });
483
+ return () => {
484
+ if (!view.watchers.delete(watcher))
485
+ return;
486
+ if (view.watchers.size === 0)
487
+ views.delete(key);
359
488
  };
360
489
  },
361
- receive (event) {
362
- if (deps.closed() || !viewed().includes(event.conversation)) return;
490
+ changed(set) {
491
+ for (const conversation of set.conversations)
492
+ emit(conversation);
493
+ },
494
+ receive(event) {
495
+ if (deps.closed())
496
+ return;
497
+ const { conversation } = event;
498
+ if (event.kind === 'gap') {
499
+ if (replicated.has(conversation))
500
+ gap(conversation);
501
+ if (lives.delete(conversation))
502
+ emit(conversation);
503
+ return;
504
+ }
363
505
  if (event.kind === 'growing-text') {
364
- growing.set(event.conversation, {
365
- turn: event.turn,
366
- text: event.text
506
+ live(conversation, { growingText: { turn: event.turn, text: event.text } });
507
+ return;
508
+ }
509
+ if (event.kind === 'plan') {
510
+ live(conversation, { plan: { turn: event.turn, plan: structuredClone(event.plan) } });
511
+ return;
512
+ }
513
+ if (event.kind === 'working-note') {
514
+ live(conversation, {
515
+ workingNote: event.note === null ? null : { noteId: event.note.noteId, text: event.note.text },
367
516
  });
368
- emit(event.conversation);
369
517
  return;
370
518
  }
371
- if (event.kind === 'session-summary') {
372
- const summary = structuredClone(event.summary);
373
- void keep(event.conversation, (carried)=>({
374
- ...carried,
375
- summary
376
- })).catch(()=>{});
519
+ if (event.kind === 'questions') {
520
+ live(conversation, { questions: structuredClone(event.questions) });
521
+ return;
522
+ }
523
+ if (event.kind === 'process') {
524
+ live(conversation, { process: { running: event.running } });
377
525
  return;
378
526
  }
379
- const arriving = carriedMessage(event.message);
380
- if (arriving === null) return;
381
- live.get(event.conversation)?.add(arriving.turn);
382
- if (growing.get(event.conversation)?.turn === arriving.turn) {
383
- growing.delete(event.conversation);
527
+ if (event.kind === 'commands') {
528
+ live(conversation, { commands: structuredClone(event.commands) });
529
+ return;
384
530
  }
385
- void deps.durably(async (local)=>{
386
- held.set(event.conversation, sealed(heldOf(event.conversation), arriving));
387
- await local.keepConversation(keptRecord(event.conversation));
388
- await settle(local, event.conversation);
389
- }).then(()=>{
390
- emit(event.conversation);
391
- }).catch(()=>{});
531
+ if (event.kind === 'options') {
532
+ live(conversation, { options: structuredClone(event.options) });
533
+ return;
534
+ }
535
+ const arriving = acceptedMessage(event.message);
536
+ if (arriving === null)
537
+ return;
538
+ if (replicated.has(conversation)) {
539
+ void keep(conversation, arriving).catch(() => { });
540
+ return;
541
+ }
542
+ if (sealTurns(conversation, new Set([arriving.turn])))
543
+ emit(conversation);
544
+ },
545
+ messages() {
546
+ return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
547
+ },
548
+ message(conversation, identity) {
549
+ const held = messages.get(conversation)?.find((message) => message.identity === identity);
550
+ return held === undefined ? null : { conversation, identity, text: held.text };
551
+ },
552
+ close() {
553
+ views.clear();
554
+ lives.clear();
392
555
  },
393
- close () {
394
- viewers.clear();
395
- growing.clear();
396
- live.clear();
397
- }
398
556
  };
399
557
  }
400
-
401
- //# sourceMappingURL=conversation.js.map