@agentshouse/mdruntime 0.1.0-cfb2ae0107054 → 0.1.0-cfcbb238a5fa0
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.
- package/AGENTS.md +262 -262
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +14 -7
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +4 -0
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +36 -10
- package/dist/chat/kit.d.ts +2 -1
- package/dist/chat/kit.d.ts.map +1 -1
- package/dist/chat/kit.js +3 -3
- package/dist/chat/list.d.ts +2 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +4 -3
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +2 -2
- package/dist/chat/messages.d.ts +1 -0
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +4 -0
- package/dist/chat/provider.d.ts +1 -1
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +26 -17
- package/dist/chat/ready.d.ts +1 -0
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +2 -2
- package/dist/client.d.ts +10 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -2
- package/dist/collection.d.ts +17 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +230 -19
- package/dist/connection.d.ts +23 -7
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1065 -192
- package/dist/conversation.d.ts +11 -5
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +241 -100
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +2 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +3 -3
- package/dist/host.d.ts +16 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +82 -8
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/memory.d.ts +7 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +20 -7
- package/dist/model.d.ts +4 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/observation.d.ts +5 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +22 -11
- package/dist/overlay.d.ts +2 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +58 -55
- package/dist/query.d.ts +35 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +8 -2
- package/dist/reach.d.ts +6 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +16 -4
- package/dist/react.d.ts +7 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +6 -4
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +285 -83
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts +4 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +17 -10
- package/dist/replica.d.ts +1 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +57 -20
- package/dist/room-draft.d.ts +13 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +2 -2
- package/dist/rows.d.ts +5 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +5 -13
- package/dist/save.d.ts +5 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +36 -13
- package/dist/search.d.ts +36 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +132 -46
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +21 -5
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +157 -53
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +136 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +48 -31
- package/package.json +2 -2
package/dist/conversation.js
CHANGED
|
@@ -22,6 +22,17 @@ export function unheldView(conversation) {
|
|
|
22
22
|
chatDraft: null,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
export const SESSION_STARTED = 'Session started';
|
|
26
|
+
const NEW_CHAT = 'new:';
|
|
27
|
+
export function newChat(agent) {
|
|
28
|
+
return `${NEW_CHAT}${agent}`;
|
|
29
|
+
}
|
|
30
|
+
function creationOf(input) {
|
|
31
|
+
if (typeof input !== 'object' || input === null)
|
|
32
|
+
return null;
|
|
33
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
34
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
35
|
+
}
|
|
25
36
|
function startedTurn(turn, operation) {
|
|
26
37
|
if (operation.op === 'begin')
|
|
27
38
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -78,11 +89,15 @@ function ordered(left, right) {
|
|
|
78
89
|
return left.identity < right.identity ? -1 : 1;
|
|
79
90
|
return 0;
|
|
80
91
|
}
|
|
81
|
-
function
|
|
92
|
+
function windowOf(held, arriving, limit) {
|
|
82
93
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
83
94
|
for (const message of arriving)
|
|
84
95
|
byIdentity.set(message.identity, message);
|
|
85
|
-
|
|
96
|
+
const next = [...byIdentity.values()].sort(ordered);
|
|
97
|
+
return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
|
|
98
|
+
}
|
|
99
|
+
function newestOf(arriving, from) {
|
|
100
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
86
101
|
}
|
|
87
102
|
function acceptedMessage(value) {
|
|
88
103
|
if (typeof value !== 'object' || value === null)
|
|
@@ -141,14 +156,14 @@ function historyPages(result) {
|
|
|
141
156
|
}
|
|
142
157
|
return pages;
|
|
143
158
|
}
|
|
144
|
-
function
|
|
159
|
+
function answered(result, field) {
|
|
145
160
|
if (result.kind !== 'action')
|
|
146
161
|
return null;
|
|
147
162
|
const answer = result.result;
|
|
148
163
|
if (typeof answer !== 'object' || answer === null)
|
|
149
164
|
return null;
|
|
150
|
-
const
|
|
151
|
-
return typeof
|
|
165
|
+
const value = answer[field];
|
|
166
|
+
return typeof value === 'string' ? value : null;
|
|
152
167
|
}
|
|
153
168
|
function limitOf(options) {
|
|
154
169
|
const { limit } = options;
|
|
@@ -159,11 +174,18 @@ function limitOf(options) {
|
|
|
159
174
|
}
|
|
160
175
|
return limit;
|
|
161
176
|
}
|
|
177
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
162
178
|
function unwritten(draft) {
|
|
163
179
|
return draft.text === '' && draft.files.length === 0;
|
|
164
180
|
}
|
|
165
181
|
function pending(draft) {
|
|
166
|
-
return
|
|
182
|
+
return {
|
|
183
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
184
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function empty(draft) {
|
|
188
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
167
189
|
}
|
|
168
190
|
function withSubmitted(draft, submitted) {
|
|
169
191
|
const { submitted: _held, ...unsent } = draft;
|
|
@@ -178,15 +200,18 @@ function returned(draft, submission, others) {
|
|
|
178
200
|
}, others);
|
|
179
201
|
}
|
|
180
202
|
export function createConversationStore(deps) {
|
|
181
|
-
const
|
|
203
|
+
const stored = new Set();
|
|
182
204
|
const reads = new Map();
|
|
205
|
+
const partials = new Set();
|
|
183
206
|
const drafts = new Map();
|
|
184
|
-
const sending = new
|
|
207
|
+
const sending = new Map();
|
|
185
208
|
const behind = new Set();
|
|
209
|
+
const catching = new Map();
|
|
186
210
|
const gaps = new Map();
|
|
187
211
|
const lives = new Map();
|
|
188
212
|
const views = new Map();
|
|
189
213
|
const listing = new Map();
|
|
214
|
+
const creations = new Set();
|
|
190
215
|
const listWatchers = new Set();
|
|
191
216
|
let listKnown = false;
|
|
192
217
|
const agentWatchers = new Set();
|
|
@@ -198,22 +223,17 @@ export function createConversationStore(deps) {
|
|
|
198
223
|
function liveOf(conversation) {
|
|
199
224
|
return lives.get(conversation) ?? QUIET;
|
|
200
225
|
}
|
|
201
|
-
function
|
|
202
|
-
const held = messages.get(conversation) ?? [];
|
|
203
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
204
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
205
|
-
identity,
|
|
206
|
-
turn,
|
|
207
|
-
text,
|
|
208
|
-
message: structuredClone(message),
|
|
209
|
-
}));
|
|
210
|
-
}
|
|
211
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
226
|
+
function viewOf(conversation, held) {
|
|
212
227
|
const draft = drafts.get(conversation);
|
|
213
228
|
const facts = liveOf(conversation);
|
|
214
229
|
return {
|
|
215
230
|
conversation,
|
|
216
|
-
messages:
|
|
231
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
232
|
+
identity,
|
|
233
|
+
turn,
|
|
234
|
+
text,
|
|
235
|
+
message,
|
|
236
|
+
})),
|
|
217
237
|
turn: structuredClone(facts.turn),
|
|
218
238
|
questions: structuredClone(facts.questions),
|
|
219
239
|
process: facts.process === null ? null : { ...facts.process },
|
|
@@ -224,23 +244,30 @@ export function createConversationStore(deps) {
|
|
|
224
244
|
: { text: draft.text, files: [...draft.files], revision: draft.revision },
|
|
225
245
|
};
|
|
226
246
|
}
|
|
227
|
-
function emit(conversation
|
|
247
|
+
function emit(conversation) {
|
|
228
248
|
if (deps.closed())
|
|
229
249
|
return;
|
|
230
250
|
for (const view of views.values()) {
|
|
231
|
-
if (view.conversation !== conversation)
|
|
251
|
+
if (view.conversation !== conversation || view.window === null)
|
|
232
252
|
continue;
|
|
233
|
-
if (messagesChanged)
|
|
234
|
-
view.messages = null;
|
|
235
253
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
236
254
|
if (ready.length === 0)
|
|
237
255
|
continue;
|
|
238
|
-
|
|
239
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
256
|
+
const value = viewOf(conversation, view.window);
|
|
240
257
|
for (const watcher of ready)
|
|
241
258
|
watcher.notify(value);
|
|
242
259
|
}
|
|
243
260
|
}
|
|
261
|
+
function slide(conversation, arriving) {
|
|
262
|
+
for (const view of views.values()) {
|
|
263
|
+
if (view.conversation !== conversation || view.window === null)
|
|
264
|
+
continue;
|
|
265
|
+
view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function changesOf(conversation, arriving) {
|
|
269
|
+
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
270
|
+
}
|
|
244
271
|
function stateOf(conversation) {
|
|
245
272
|
const facts = liveOf(conversation);
|
|
246
273
|
if (facts.questions.length > 0)
|
|
@@ -297,7 +324,10 @@ export function createConversationStore(deps) {
|
|
|
297
324
|
const declaration = deps.agents();
|
|
298
325
|
if (declaration === undefined)
|
|
299
326
|
return;
|
|
300
|
-
const
|
|
327
|
+
const held = await deps.documents(declaration);
|
|
328
|
+
if ('pending' in held)
|
|
329
|
+
return;
|
|
330
|
+
const { documents } = held;
|
|
301
331
|
const names = new Map();
|
|
302
332
|
const retired = new Set();
|
|
303
333
|
for (const document of documents) {
|
|
@@ -320,6 +350,12 @@ export function createConversationStore(deps) {
|
|
|
320
350
|
if (named)
|
|
321
351
|
emitList();
|
|
322
352
|
emitAgents();
|
|
353
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
354
|
+
if (ended.length > 0)
|
|
355
|
+
await remove(ended);
|
|
356
|
+
}
|
|
357
|
+
function retiredChat(conversation) {
|
|
358
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
323
359
|
}
|
|
324
360
|
function agentsReady() {
|
|
325
361
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -329,7 +365,7 @@ export function createConversationStore(deps) {
|
|
|
329
365
|
}
|
|
330
366
|
function live(conversation, change) {
|
|
331
367
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
332
|
-
emit(conversation
|
|
368
|
+
emit(conversation);
|
|
333
369
|
}
|
|
334
370
|
function relayed(conversation, turn, operation) {
|
|
335
371
|
const held = liveOf(conversation).turn;
|
|
@@ -357,21 +393,25 @@ export function createConversationStore(deps) {
|
|
|
357
393
|
}
|
|
358
394
|
async function remove(leaving) {
|
|
359
395
|
await deps.durably(async (local) => {
|
|
360
|
-
await local.dropConversations(leaving);
|
|
361
396
|
const removed = [];
|
|
362
|
-
const changed = [];
|
|
363
397
|
for (const conversation of leaving) {
|
|
364
|
-
for (const
|
|
365
|
-
removed.push({ conversation, identity:
|
|
398
|
+
for (const identity of await local.messageIdentities(conversation)) {
|
|
399
|
+
removed.push({ conversation, identity, text: null });
|
|
366
400
|
}
|
|
367
|
-
|
|
401
|
+
}
|
|
402
|
+
await local.dropConversations(leaving);
|
|
403
|
+
const changed = [];
|
|
404
|
+
for (const conversation of leaving) {
|
|
405
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
368
406
|
changed.push(conversation);
|
|
369
407
|
}
|
|
370
|
-
|
|
408
|
+
stored.delete(conversation);
|
|
371
409
|
reads.delete(conversation);
|
|
410
|
+
partials.delete(conversation);
|
|
372
411
|
drafts.delete(conversation);
|
|
373
412
|
behind.delete(conversation);
|
|
374
413
|
setLive(conversation, null);
|
|
414
|
+
slide(conversation, null);
|
|
375
415
|
}
|
|
376
416
|
if (changed.length > 0)
|
|
377
417
|
deps.publish(conversationChangeSet(removed, changed));
|
|
@@ -393,23 +433,21 @@ export function createConversationStore(deps) {
|
|
|
393
433
|
await local.keepMessages({
|
|
394
434
|
conversation,
|
|
395
435
|
messages: [message],
|
|
396
|
-
...(advances ? { read: message.position } : {}),
|
|
436
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
397
437
|
});
|
|
398
|
-
|
|
438
|
+
stored.add(conversation);
|
|
399
439
|
if (advances)
|
|
400
440
|
reads.set(conversation, message.position);
|
|
401
441
|
sealTurns(conversation, new Set([message.turn]));
|
|
402
|
-
|
|
442
|
+
slide(conversation, [message]);
|
|
443
|
+
deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
|
|
403
444
|
});
|
|
404
445
|
}
|
|
405
|
-
async function readHistories(conversations) {
|
|
446
|
+
async function readHistories(conversations, ended) {
|
|
406
447
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
407
|
-
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
408
|
-
const arriving = new Map();
|
|
409
|
-
const complete = new Set();
|
|
410
448
|
let asking = conversations.map((conversation) => ({
|
|
411
449
|
conversation,
|
|
412
|
-
after:
|
|
450
|
+
after: reads.get(conversation) ?? undefined,
|
|
413
451
|
}));
|
|
414
452
|
while (asking.length > 0) {
|
|
415
453
|
const result = await deps.call('conversation.history', {
|
|
@@ -421,43 +459,64 @@ export function createConversationStore(deps) {
|
|
|
421
459
|
const pages = historyPages(result);
|
|
422
460
|
if (pages === null)
|
|
423
461
|
return result;
|
|
424
|
-
const asked = new
|
|
462
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
425
463
|
const following = [];
|
|
426
464
|
for (const page of pages) {
|
|
427
465
|
if (!asked.has(page.conversation))
|
|
428
466
|
continue;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
...page.messages,
|
|
432
|
-
]);
|
|
433
|
-
if (page.next === undefined)
|
|
434
|
-
complete.add(page.conversation);
|
|
435
|
-
else
|
|
467
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
468
|
+
if (page.next !== undefined) {
|
|
436
469
|
following.push({ conversation: page.conversation, after: page.next });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const { conversation } of asking) {
|
|
473
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
474
|
+
ended(conversation);
|
|
437
475
|
}
|
|
438
476
|
asking = following;
|
|
439
477
|
}
|
|
440
|
-
for (const [conversation, held] of arriving) {
|
|
441
|
-
if (!complete.has(conversation))
|
|
442
|
-
continue;
|
|
443
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
444
|
-
}
|
|
445
478
|
return null;
|
|
446
479
|
}
|
|
447
|
-
function
|
|
480
|
+
function caughtUp(conversations) {
|
|
481
|
+
const ends = new Map();
|
|
482
|
+
for (const conversation of conversations) {
|
|
483
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
484
|
+
catching.set(conversation, held);
|
|
485
|
+
void held.then(() => {
|
|
486
|
+
if (catching.get(conversation) === held)
|
|
487
|
+
catching.delete(conversation);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
491
|
+
const read = readHistories(conversations, ended);
|
|
492
|
+
const endAll = () => {
|
|
493
|
+
for (const conversation of conversations)
|
|
494
|
+
ended(conversation);
|
|
495
|
+
};
|
|
496
|
+
read.then(endAll, endAll);
|
|
497
|
+
return read;
|
|
498
|
+
}
|
|
499
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
448
500
|
return deps.durably(async (local) => {
|
|
449
501
|
if (!replicated.has(conversation))
|
|
450
502
|
return;
|
|
451
|
-
const
|
|
503
|
+
const whole = next === undefined;
|
|
504
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
505
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
452
506
|
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
453
|
-
await local.keepMessages({ conversation, messages: arriving, read:
|
|
454
|
-
|
|
455
|
-
reads.set(conversation,
|
|
507
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
508
|
+
stored.add(conversation);
|
|
509
|
+
reads.set(conversation, cursor);
|
|
510
|
+
if (partial)
|
|
511
|
+
partials.add(conversation);
|
|
512
|
+
else
|
|
513
|
+
partials.delete(conversation);
|
|
456
514
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
457
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
515
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
458
516
|
behind.delete(conversation);
|
|
459
|
-
|
|
460
|
-
|
|
517
|
+
slide(conversation, arriving);
|
|
518
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
519
|
+
deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
|
|
461
520
|
}
|
|
462
521
|
});
|
|
463
522
|
}
|
|
@@ -470,7 +529,7 @@ export function createConversationStore(deps) {
|
|
|
470
529
|
if (held === undefined)
|
|
471
530
|
return;
|
|
472
531
|
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
473
|
-
emit(conversation
|
|
532
|
+
emit(conversation);
|
|
474
533
|
return;
|
|
475
534
|
}
|
|
476
535
|
if (event.kind === 'turn') {
|
|
@@ -504,12 +563,12 @@ export function createConversationStore(deps) {
|
|
|
504
563
|
emit(conversation);
|
|
505
564
|
}
|
|
506
565
|
async function kept(local, conversation, following) {
|
|
507
|
-
if (following === null || (
|
|
566
|
+
if (following === null || empty(following)) {
|
|
508
567
|
await local.dropChatDraft(conversation);
|
|
509
568
|
drafts.delete(conversation);
|
|
510
569
|
}
|
|
511
570
|
else {
|
|
512
|
-
await local.
|
|
571
|
+
await local.keepChatDrafts([following]);
|
|
513
572
|
drafts.set(conversation, following);
|
|
514
573
|
}
|
|
515
574
|
}
|
|
@@ -522,9 +581,32 @@ export function createConversationStore(deps) {
|
|
|
522
581
|
await kept(local, conversation, withSubmitted(held, left));
|
|
523
582
|
return true;
|
|
524
583
|
}
|
|
584
|
+
function moved(agent, operation, to) {
|
|
585
|
+
const from = newChat(agent);
|
|
586
|
+
return deps.durably(async (local) => {
|
|
587
|
+
const held = drafts.get(from);
|
|
588
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
589
|
+
if (earlier.includes(operation))
|
|
590
|
+
return;
|
|
591
|
+
const blank = {
|
|
592
|
+
conversation: from,
|
|
593
|
+
text: '',
|
|
594
|
+
files: [],
|
|
595
|
+
revision: chatDraftRevision(),
|
|
596
|
+
};
|
|
597
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
598
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
599
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
600
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
601
|
+
drafts.set(from, left);
|
|
602
|
+
for (const one of taken)
|
|
603
|
+
drafts.set(to, one);
|
|
604
|
+
deps.publish(conversationChangeSet([], [from, to]));
|
|
605
|
+
});
|
|
606
|
+
}
|
|
525
607
|
function withDraft(conversation, next) {
|
|
526
608
|
return deps.durably(async (local) => {
|
|
527
|
-
const following = next(drafts.get(conversation));
|
|
609
|
+
const following = await next(drafts.get(conversation), local);
|
|
528
610
|
if (following === undefined)
|
|
529
611
|
return;
|
|
530
612
|
await kept(local, conversation, following);
|
|
@@ -533,8 +615,10 @@ export function createConversationStore(deps) {
|
|
|
533
615
|
}
|
|
534
616
|
async function submitted(conversation, operation, draft) {
|
|
535
617
|
let submission;
|
|
618
|
+
let earlier = false;
|
|
536
619
|
await withDraft(conversation, (current) => {
|
|
537
620
|
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
621
|
+
earlier = submission !== undefined;
|
|
538
622
|
if (submission !== undefined)
|
|
539
623
|
return undefined;
|
|
540
624
|
const said = draft ?? current;
|
|
@@ -552,10 +636,19 @@ export function createConversationStore(deps) {
|
|
|
552
636
|
submitted: [...(current?.submitted ?? []), submission],
|
|
553
637
|
};
|
|
554
638
|
});
|
|
555
|
-
return submission;
|
|
639
|
+
return { submission: submission, earlier };
|
|
640
|
+
}
|
|
641
|
+
function forgotten(conversation, submission) {
|
|
642
|
+
return withDraft(conversation, (current) => {
|
|
643
|
+
const held = current?.submitted ?? [];
|
|
644
|
+
if (current === undefined || !held.includes(submission))
|
|
645
|
+
return undefined;
|
|
646
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
647
|
+
});
|
|
556
648
|
}
|
|
557
649
|
async function dispatched(conversation, submission) {
|
|
558
|
-
|
|
650
|
+
const dispatch = {};
|
|
651
|
+
sending.set(submission.operation, dispatch);
|
|
559
652
|
try {
|
|
560
653
|
const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
|
|
561
654
|
const result = await invoke('conversation.send', conversation, {
|
|
@@ -565,17 +658,19 @@ export function createConversationStore(deps) {
|
|
|
565
658
|
});
|
|
566
659
|
if (result.kind === 'revoked-authorization')
|
|
567
660
|
return result;
|
|
568
|
-
if (result.kind !== 'action' && result.retryable)
|
|
661
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
662
|
+
deps.unanswered();
|
|
569
663
|
return result;
|
|
570
|
-
|
|
571
|
-
|
|
664
|
+
}
|
|
665
|
+
const turn = answered(result, 'turn');
|
|
666
|
+
await withDraft(conversation, async (current, local) => {
|
|
572
667
|
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
573
668
|
if (current === undefined || held === undefined || held.turn !== null)
|
|
574
669
|
return undefined;
|
|
575
670
|
const others = current.submitted.filter((one) => one !== held);
|
|
576
671
|
if (result.kind !== 'action')
|
|
577
672
|
return returned(current, held, others);
|
|
578
|
-
const arrived =
|
|
673
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
579
674
|
return withSubmitted(current, turn === null || arrived
|
|
580
675
|
? others
|
|
581
676
|
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
@@ -583,7 +678,8 @@ export function createConversationStore(deps) {
|
|
|
583
678
|
return result;
|
|
584
679
|
}
|
|
585
680
|
finally {
|
|
586
|
-
sending.
|
|
681
|
+
if (sending.get(submission.operation) === dispatch)
|
|
682
|
+
sending.delete(submission.operation);
|
|
587
683
|
}
|
|
588
684
|
}
|
|
589
685
|
function invoke(name, conversation, extra = {}) {
|
|
@@ -596,14 +692,27 @@ export function createConversationStore(deps) {
|
|
|
596
692
|
},
|
|
597
693
|
async read(conversation, options = {}) {
|
|
598
694
|
const limit = limitOf(options);
|
|
599
|
-
return deps.durably(() =>
|
|
695
|
+
return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
|
|
600
696
|
},
|
|
601
|
-
create(input) {
|
|
602
|
-
|
|
697
|
+
async create(input) {
|
|
698
|
+
const result = await deps.call('conversation.create', input);
|
|
699
|
+
const conversation = answered(result, 'conversation');
|
|
700
|
+
if (conversation === null)
|
|
701
|
+
return result;
|
|
702
|
+
if (!listing.has(conversation))
|
|
703
|
+
creations.add(conversation);
|
|
704
|
+
const creation = creationOf(input);
|
|
705
|
+
if (creation !== null)
|
|
706
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
707
|
+
return result;
|
|
603
708
|
},
|
|
604
709
|
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
605
710
|
if (deps.online()) {
|
|
606
|
-
|
|
711
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
712
|
+
if (!earlier || !deps.expired(operation))
|
|
713
|
+
return dispatched(conversation, submission);
|
|
714
|
+
await forgotten(conversation, submission);
|
|
715
|
+
return EXPIRED;
|
|
607
716
|
}
|
|
608
717
|
if (draft !== undefined) {
|
|
609
718
|
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
@@ -644,7 +753,7 @@ export function createConversationStore(deps) {
|
|
|
644
753
|
text: draft.text,
|
|
645
754
|
files: [...draft.files],
|
|
646
755
|
revision,
|
|
647
|
-
...(current
|
|
756
|
+
...(current === undefined ? {} : pending(current)),
|
|
648
757
|
}));
|
|
649
758
|
},
|
|
650
759
|
discard(conversation) {
|
|
@@ -662,18 +771,19 @@ export function createConversationStore(deps) {
|
|
|
662
771
|
return {
|
|
663
772
|
surface,
|
|
664
773
|
chatDraft,
|
|
665
|
-
hydrate(held,
|
|
666
|
-
|
|
774
|
+
hydrate(held, kept) {
|
|
775
|
+
stored.clear();
|
|
667
776
|
reads.clear();
|
|
668
|
-
|
|
669
|
-
view.messages = null;
|
|
777
|
+
partials.clear();
|
|
670
778
|
for (const entry of held) {
|
|
671
|
-
|
|
779
|
+
stored.add(entry.conversation);
|
|
672
780
|
if (entry.read !== undefined)
|
|
673
781
|
reads.set(entry.conversation, entry.read);
|
|
782
|
+
if (entry.partial)
|
|
783
|
+
partials.add(entry.conversation);
|
|
674
784
|
}
|
|
675
785
|
drafts.clear();
|
|
676
|
-
for (const record of
|
|
786
|
+
for (const record of kept)
|
|
677
787
|
drafts.set(record.conversation, record);
|
|
678
788
|
},
|
|
679
789
|
async reconcile() {
|
|
@@ -681,8 +791,10 @@ export function createConversationStore(deps) {
|
|
|
681
791
|
return;
|
|
682
792
|
const named = new Set(listing.keys());
|
|
683
793
|
const leaving = [
|
|
684
|
-
...new Set([...replicated, ...
|
|
685
|
-
].filter((conversation) => !named.has(conversation)
|
|
794
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
795
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
796
|
+
!creations.has(conversation) &&
|
|
797
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
686
798
|
for (const conversation of named) {
|
|
687
799
|
if (!replicated.has(conversation))
|
|
688
800
|
behind.add(conversation);
|
|
@@ -693,12 +805,20 @@ export function createConversationStore(deps) {
|
|
|
693
805
|
await remove(leaving);
|
|
694
806
|
},
|
|
695
807
|
async catchUp(progress) {
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
808
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
809
|
+
const read = async (conversations) => {
|
|
810
|
+
if (conversations.length === 0 || deps.closed())
|
|
811
|
+
return;
|
|
812
|
+
const failed = await caughtUp(conversations);
|
|
699
813
|
if (failed?.kind === 'transport-uncertainty')
|
|
700
814
|
progress.reachable = false;
|
|
701
|
-
}
|
|
815
|
+
};
|
|
816
|
+
const asking = [...behind].filter(owed).sort();
|
|
817
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
818
|
+
await Promise.all([
|
|
819
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
820
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
821
|
+
]);
|
|
702
822
|
if (behind.size > 0)
|
|
703
823
|
progress.complete = false;
|
|
704
824
|
},
|
|
@@ -709,6 +829,10 @@ export function createConversationStore(deps) {
|
|
|
709
829
|
continue;
|
|
710
830
|
if (deps.closed())
|
|
711
831
|
return;
|
|
832
|
+
if (deps.expired(submission.operation)) {
|
|
833
|
+
await forgotten(conversation, submission);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
712
836
|
const result = await dispatched(conversation, submission);
|
|
713
837
|
if (result.kind === 'revoked-authorization')
|
|
714
838
|
return;
|
|
@@ -716,16 +840,29 @@ export function createConversationStore(deps) {
|
|
|
716
840
|
progress.reachable = false;
|
|
717
841
|
return;
|
|
718
842
|
}
|
|
843
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
844
|
+
progress.complete = false;
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
719
847
|
}
|
|
720
848
|
}
|
|
721
849
|
},
|
|
722
850
|
current() {
|
|
723
851
|
return behind.size === 0;
|
|
724
852
|
},
|
|
853
|
+
known() {
|
|
854
|
+
return listKnown;
|
|
855
|
+
},
|
|
725
856
|
reconnected() {
|
|
726
857
|
for (const conversation of replicated)
|
|
727
858
|
gap(conversation);
|
|
728
859
|
},
|
|
860
|
+
reopened() {
|
|
861
|
+
sending.clear();
|
|
862
|
+
},
|
|
863
|
+
lost() {
|
|
864
|
+
catching.clear();
|
|
865
|
+
},
|
|
729
866
|
watch(conversation, options, deliver) {
|
|
730
867
|
const limit = limitOf(options);
|
|
731
868
|
const key = JSON.stringify([conversation, limit ?? null]);
|
|
@@ -733,18 +870,20 @@ export function createConversationStore(deps) {
|
|
|
733
870
|
conversation,
|
|
734
871
|
limit,
|
|
735
872
|
watchers: new Set(),
|
|
736
|
-
|
|
873
|
+
window: null,
|
|
737
874
|
};
|
|
738
875
|
views.set(key, view);
|
|
739
876
|
const watcher = { notify: deliver, ready: false };
|
|
740
877
|
view.watchers.add(watcher);
|
|
741
878
|
void deps
|
|
742
|
-
.durably(() =>
|
|
879
|
+
.durably(async (local) => {
|
|
880
|
+
view.window ??= await local.messageWindow(conversation, limit);
|
|
881
|
+
})
|
|
743
882
|
.then(() => {
|
|
744
883
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
745
884
|
return;
|
|
746
885
|
watcher.ready = true;
|
|
747
|
-
watcher.notify(viewOf(conversation,
|
|
886
|
+
watcher.notify(viewOf(conversation, view.window ?? []));
|
|
748
887
|
})
|
|
749
888
|
.catch(() => { });
|
|
750
889
|
return () => {
|
|
@@ -791,12 +930,15 @@ export function createConversationStore(deps) {
|
|
|
791
930
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
792
931
|
if (event.kind === 'list') {
|
|
793
932
|
listing.clear();
|
|
794
|
-
for (const entry of event.entries)
|
|
933
|
+
for (const entry of event.entries) {
|
|
795
934
|
listing.set(entry.conversation, { ...entry });
|
|
935
|
+
creations.delete(entry.conversation);
|
|
936
|
+
}
|
|
796
937
|
listKnown = true;
|
|
797
938
|
}
|
|
798
939
|
else if (event.kind === 'listed') {
|
|
799
940
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
941
|
+
creations.delete(event.entry.conversation);
|
|
800
942
|
}
|
|
801
943
|
else {
|
|
802
944
|
listing.delete(event.conversation);
|
|
@@ -810,12 +952,11 @@ export function createConversationStore(deps) {
|
|
|
810
952
|
heard(event);
|
|
811
953
|
return false;
|
|
812
954
|
},
|
|
813
|
-
|
|
814
|
-
return [...
|
|
955
|
+
searchable() {
|
|
956
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
815
957
|
},
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
958
|
+
acquired(conversation) {
|
|
959
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
819
960
|
},
|
|
820
961
|
close() {
|
|
821
962
|
views.clear();
|