@agentshouse/mdruntime 0.1.0-c69ca0e865e0b → 0.1.0-c6fd2cbbff187
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 +275 -275
- 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 +50 -9
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +3 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +26 -4
- 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/acts.d.ts +3 -4
- package/dist/chat/acts.d.ts.map +1 -1
- package/dist/chat/acts.js +10 -10
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- 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 +42 -5
- 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 +59 -69
- package/dist/chat/question.d.ts +1 -1
- package/dist/chat/question.d.ts.map +1 -1
- package/dist/chat/question.js +7 -18
- 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/chat/style.js +1 -1
- package/dist/client.d.ts +14 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +17 -6
- 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 +27 -8
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1567 -362
- package/dist/conversation.d.ts +19 -7
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +438 -152
- 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/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +17 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +90 -9
- 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 +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/memory.d.ts +9 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +36 -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 +7 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +29 -12
- package/dist/outcome.d.ts +12 -2
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +4 -0
- 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 +56 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +36 -7
- package/dist/reach.d.ts +6 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +20 -5
- 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 +419 -92
- 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 +6 -2
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +76 -23
- 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 +6 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +10 -13
- package/dist/save.d.ts +6 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +53 -14
- package/dist/search.d.ts +49 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +192 -47
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -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 +56 -11
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +311 -84
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/windows.d.ts +19 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +83 -0
- package/dist/wire.d.ts +144 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +50 -31
- package/package.json +2 -2
package/dist/conversation.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { conversationChangeSet } from './change-set.js';
|
|
2
2
|
import { runtimeError } from './failure.js';
|
|
3
3
|
import { declaredFile } from './file.js';
|
|
4
|
-
import { chatDraftRevision } from './identity.js';
|
|
4
|
+
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
5
|
+
import { OFFLINE } from './outcome.js';
|
|
6
|
+
import { capped, cursorOf, decodedCursor, FROM_START } from './windows.js';
|
|
5
7
|
const QUIET = {
|
|
6
8
|
turn: null,
|
|
7
9
|
questions: [],
|
|
@@ -21,6 +23,17 @@ export function unheldView(conversation) {
|
|
|
21
23
|
chatDraft: null,
|
|
22
24
|
};
|
|
23
25
|
}
|
|
26
|
+
export const SESSION_STARTED = 'Session started';
|
|
27
|
+
const NEW_CHAT = 'new:';
|
|
28
|
+
export function newChat(agent) {
|
|
29
|
+
return `${NEW_CHAT}${agent}`;
|
|
30
|
+
}
|
|
31
|
+
function creationOf(input) {
|
|
32
|
+
if (typeof input !== 'object' || input === null)
|
|
33
|
+
return null;
|
|
34
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
35
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
36
|
+
}
|
|
24
37
|
function startedTurn(turn, operation) {
|
|
25
38
|
if (operation.op === 'begin')
|
|
26
39
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -77,11 +90,33 @@ function ordered(left, right) {
|
|
|
77
90
|
return left.identity < right.identity ? -1 : 1;
|
|
78
91
|
return 0;
|
|
79
92
|
}
|
|
80
|
-
function
|
|
93
|
+
function windowOf(held, arriving, limit) {
|
|
81
94
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
82
95
|
for (const message of arriving)
|
|
83
96
|
byIdentity.set(message.identity, message);
|
|
84
|
-
|
|
97
|
+
const next = [...byIdentity.values()].sort(ordered);
|
|
98
|
+
const start = Math.max(0, next.length - limit);
|
|
99
|
+
return { messages: next.slice(start), dropped: start > 0 };
|
|
100
|
+
}
|
|
101
|
+
function precedes(message, before) {
|
|
102
|
+
return (before === null ||
|
|
103
|
+
message.position < before[0] ||
|
|
104
|
+
(message.position === before[0] && message.identity < before[1]));
|
|
105
|
+
}
|
|
106
|
+
function frameOf(asked, messages, more, total) {
|
|
107
|
+
const oldest = messages[0];
|
|
108
|
+
const next = !more
|
|
109
|
+
? undefined
|
|
110
|
+
: oldest === undefined
|
|
111
|
+
? (asked.after ?? FROM_START)
|
|
112
|
+
: cursorOf([oldest.position, oldest.identity]);
|
|
113
|
+
return {
|
|
114
|
+
...(next === undefined ? {} : { next }),
|
|
115
|
+
...(total === null ? {} : { total }),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function newestOf(arriving, from) {
|
|
119
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
85
120
|
}
|
|
86
121
|
function acceptedMessage(value) {
|
|
87
122
|
if (typeof value !== 'object' || value === null)
|
|
@@ -140,34 +175,72 @@ function historyPages(result) {
|
|
|
140
175
|
}
|
|
141
176
|
return pages;
|
|
142
177
|
}
|
|
143
|
-
function
|
|
178
|
+
function answered(result, field) {
|
|
144
179
|
if (result.kind !== 'action')
|
|
145
180
|
return null;
|
|
146
181
|
const answer = result.result;
|
|
147
182
|
if (typeof answer !== 'object' || answer === null)
|
|
148
183
|
return null;
|
|
149
|
-
const
|
|
150
|
-
return typeof
|
|
184
|
+
const value = answer[field];
|
|
185
|
+
return typeof value === 'string' ? value : null;
|
|
151
186
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
187
|
+
function beforeOf(after) {
|
|
188
|
+
if (after === undefined)
|
|
189
|
+
return null;
|
|
190
|
+
const decoded = typeof after === 'string' ? decodedCursor(after) : null;
|
|
191
|
+
if (decoded?.length === 0)
|
|
192
|
+
return null;
|
|
193
|
+
const [position, identity] = decoded ?? [];
|
|
194
|
+
if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
|
|
195
|
+
throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
|
|
196
|
+
}
|
|
197
|
+
return [position, identity];
|
|
198
|
+
}
|
|
199
|
+
function askedOf(options) {
|
|
200
|
+
const { limit, after, total } = options;
|
|
201
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
|
|
157
202
|
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
158
203
|
}
|
|
159
|
-
return limit;
|
|
204
|
+
return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
|
|
205
|
+
}
|
|
206
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
207
|
+
function unwritten(draft) {
|
|
208
|
+
return draft.text === '' && draft.files.length === 0;
|
|
209
|
+
}
|
|
210
|
+
function pending(draft) {
|
|
211
|
+
return {
|
|
212
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
213
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function empty(draft) {
|
|
217
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
218
|
+
}
|
|
219
|
+
function withSubmitted(draft, submitted) {
|
|
220
|
+
const { submitted: _held, ...unsent } = draft;
|
|
221
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
222
|
+
}
|
|
223
|
+
function returned(draft, submission, others) {
|
|
224
|
+
return withSubmitted({
|
|
225
|
+
conversation: draft.conversation,
|
|
226
|
+
text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
|
|
227
|
+
files: [...submission.files, ...draft.files],
|
|
228
|
+
revision: chatDraftRevision(),
|
|
229
|
+
}, others);
|
|
160
230
|
}
|
|
161
231
|
export function createConversationStore(deps) {
|
|
162
|
-
const
|
|
232
|
+
const stored = new Set();
|
|
163
233
|
const reads = new Map();
|
|
234
|
+
const partials = new Set();
|
|
164
235
|
const drafts = new Map();
|
|
165
236
|
const sending = new Map();
|
|
166
237
|
const behind = new Set();
|
|
238
|
+
const catching = new Map();
|
|
167
239
|
const gaps = new Map();
|
|
168
240
|
const lives = new Map();
|
|
169
241
|
const views = new Map();
|
|
170
242
|
const listing = new Map();
|
|
243
|
+
const creations = new Set();
|
|
171
244
|
const listWatchers = new Set();
|
|
172
245
|
let listKnown = false;
|
|
173
246
|
const agentWatchers = new Set();
|
|
@@ -179,49 +252,70 @@ export function createConversationStore(deps) {
|
|
|
179
252
|
function liveOf(conversation) {
|
|
180
253
|
return lives.get(conversation) ?? QUIET;
|
|
181
254
|
}
|
|
182
|
-
function
|
|
183
|
-
const held = messages.get(conversation) ?? [];
|
|
184
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
185
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
186
|
-
identity,
|
|
187
|
-
turn,
|
|
188
|
-
text,
|
|
189
|
-
message: structuredClone(message),
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
255
|
+
function viewOf(conversation, held, frame) {
|
|
193
256
|
const draft = drafts.get(conversation);
|
|
194
257
|
const facts = liveOf(conversation);
|
|
195
258
|
return {
|
|
196
259
|
conversation,
|
|
197
|
-
messages:
|
|
260
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
261
|
+
identity,
|
|
262
|
+
turn,
|
|
263
|
+
text,
|
|
264
|
+
message,
|
|
265
|
+
})),
|
|
266
|
+
...frame,
|
|
198
267
|
turn: structuredClone(facts.turn),
|
|
199
268
|
questions: structuredClone(facts.questions),
|
|
200
269
|
process: facts.process === null ? null : { ...facts.process },
|
|
201
270
|
commands: structuredClone(facts.commands),
|
|
202
271
|
options: structuredClone(facts.options),
|
|
203
|
-
chatDraft: draft === undefined || draft
|
|
272
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
204
273
|
? null
|
|
205
274
|
: { text: draft.text, files: [...draft.files], revision: draft.revision },
|
|
206
275
|
};
|
|
207
276
|
}
|
|
208
|
-
function emit(conversation
|
|
277
|
+
function emit(conversation) {
|
|
209
278
|
if (deps.closed())
|
|
210
279
|
return;
|
|
211
280
|
for (const view of views.values()) {
|
|
212
|
-
if (view.conversation !== conversation)
|
|
281
|
+
if (view.conversation !== conversation || view.window === null)
|
|
213
282
|
continue;
|
|
214
|
-
if (messagesChanged)
|
|
215
|
-
view.messages = null;
|
|
216
283
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
217
284
|
if (ready.length === 0)
|
|
218
285
|
continue;
|
|
219
|
-
|
|
220
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
286
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
|
|
221
287
|
for (const watcher of ready)
|
|
222
288
|
watcher.notify(value);
|
|
223
289
|
}
|
|
224
290
|
}
|
|
291
|
+
function sharedFrame(view, window) {
|
|
292
|
+
return frameOf(view.asked, window, view.more, view.total);
|
|
293
|
+
}
|
|
294
|
+
async function slide(local, conversation, arriving) {
|
|
295
|
+
let count;
|
|
296
|
+
for (const view of views.values()) {
|
|
297
|
+
if (view.conversation !== conversation || view.window === null)
|
|
298
|
+
continue;
|
|
299
|
+
if (arriving === null) {
|
|
300
|
+
view.window = [];
|
|
301
|
+
view.more = false;
|
|
302
|
+
if (view.total !== null)
|
|
303
|
+
view.total = 0;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const joining = arriving.filter((message) => precedes(message, view.asked.before));
|
|
307
|
+
const next = windowOf(view.window, joining, view.asked.limit);
|
|
308
|
+
view.window = next.messages;
|
|
309
|
+
view.more ||= next.dropped;
|
|
310
|
+
if (view.total !== null && arriving.length > 0) {
|
|
311
|
+
count ??= await local.messageCount(conversation);
|
|
312
|
+
view.total = count;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function changesOf(conversation, arriving) {
|
|
317
|
+
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
318
|
+
}
|
|
225
319
|
function stateOf(conversation) {
|
|
226
320
|
const facts = liveOf(conversation);
|
|
227
321
|
if (facts.questions.length > 0)
|
|
@@ -278,7 +372,10 @@ export function createConversationStore(deps) {
|
|
|
278
372
|
const declaration = deps.agents();
|
|
279
373
|
if (declaration === undefined)
|
|
280
374
|
return;
|
|
281
|
-
const
|
|
375
|
+
const held = await deps.documents(declaration);
|
|
376
|
+
if ('pending' in held)
|
|
377
|
+
return;
|
|
378
|
+
const { documents } = held;
|
|
282
379
|
const names = new Map();
|
|
283
380
|
const retired = new Set();
|
|
284
381
|
for (const document of documents) {
|
|
@@ -301,6 +398,12 @@ export function createConversationStore(deps) {
|
|
|
301
398
|
if (named)
|
|
302
399
|
emitList();
|
|
303
400
|
emitAgents();
|
|
401
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
402
|
+
if (ended.length > 0)
|
|
403
|
+
await remove(ended);
|
|
404
|
+
}
|
|
405
|
+
function retiredChat(conversation) {
|
|
406
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
304
407
|
}
|
|
305
408
|
function agentsReady() {
|
|
306
409
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -310,7 +413,7 @@ export function createConversationStore(deps) {
|
|
|
310
413
|
}
|
|
311
414
|
function live(conversation, change) {
|
|
312
415
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
313
|
-
emit(conversation
|
|
416
|
+
emit(conversation);
|
|
314
417
|
}
|
|
315
418
|
function relayed(conversation, turn, operation) {
|
|
316
419
|
const held = liveOf(conversation).turn;
|
|
@@ -338,21 +441,25 @@ export function createConversationStore(deps) {
|
|
|
338
441
|
}
|
|
339
442
|
async function remove(leaving) {
|
|
340
443
|
await deps.durably(async (local) => {
|
|
341
|
-
await local.dropConversations(leaving);
|
|
342
444
|
const removed = [];
|
|
343
|
-
const changed = [];
|
|
344
445
|
for (const conversation of leaving) {
|
|
345
|
-
for (const
|
|
346
|
-
removed.push({ conversation, identity:
|
|
446
|
+
for (const identity of await local.messageIdentities(conversation)) {
|
|
447
|
+
removed.push({ conversation, identity, text: null });
|
|
347
448
|
}
|
|
348
|
-
|
|
449
|
+
}
|
|
450
|
+
await local.dropConversations(leaving);
|
|
451
|
+
const changed = [];
|
|
452
|
+
for (const conversation of leaving) {
|
|
453
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
349
454
|
changed.push(conversation);
|
|
350
455
|
}
|
|
351
|
-
|
|
456
|
+
stored.delete(conversation);
|
|
352
457
|
reads.delete(conversation);
|
|
458
|
+
partials.delete(conversation);
|
|
353
459
|
drafts.delete(conversation);
|
|
354
460
|
behind.delete(conversation);
|
|
355
461
|
setLive(conversation, null);
|
|
462
|
+
await slide(local, conversation, null);
|
|
356
463
|
}
|
|
357
464
|
if (changed.length > 0)
|
|
358
465
|
deps.publish(conversationChangeSet(removed, changed));
|
|
@@ -370,30 +477,25 @@ export function createConversationStore(deps) {
|
|
|
370
477
|
const advances = read !== undefined &&
|
|
371
478
|
!behind.has(conversation) &&
|
|
372
479
|
(read === null || message.position > read);
|
|
373
|
-
|
|
480
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
374
481
|
await local.keepMessages({
|
|
375
482
|
conversation,
|
|
376
483
|
messages: [message],
|
|
377
|
-
...(advances ? { read: message.position } : {}),
|
|
378
|
-
sealed,
|
|
484
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
379
485
|
});
|
|
380
|
-
|
|
486
|
+
stored.add(conversation);
|
|
381
487
|
if (advances)
|
|
382
488
|
reads.set(conversation, message.position);
|
|
383
|
-
if (sealed)
|
|
384
|
-
drafts.delete(conversation);
|
|
385
489
|
sealTurns(conversation, new Set([message.turn]));
|
|
386
|
-
|
|
490
|
+
await slide(local, conversation, [message]);
|
|
491
|
+
deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
|
|
387
492
|
});
|
|
388
493
|
}
|
|
389
|
-
async function readHistories(conversations) {
|
|
494
|
+
async function readHistories(conversations, ended) {
|
|
390
495
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
391
|
-
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
392
|
-
const arriving = new Map();
|
|
393
|
-
const complete = new Set();
|
|
394
496
|
let asking = conversations.map((conversation) => ({
|
|
395
497
|
conversation,
|
|
396
|
-
after:
|
|
498
|
+
after: reads.get(conversation) ?? undefined,
|
|
397
499
|
}));
|
|
398
500
|
while (asking.length > 0) {
|
|
399
501
|
const result = await deps.call('conversation.history', {
|
|
@@ -405,46 +507,64 @@ export function createConversationStore(deps) {
|
|
|
405
507
|
const pages = historyPages(result);
|
|
406
508
|
if (pages === null)
|
|
407
509
|
return result;
|
|
408
|
-
const asked = new
|
|
510
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
409
511
|
const following = [];
|
|
410
512
|
for (const page of pages) {
|
|
411
513
|
if (!asked.has(page.conversation))
|
|
412
514
|
continue;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
...page.messages,
|
|
416
|
-
]);
|
|
417
|
-
if (page.next === undefined)
|
|
418
|
-
complete.add(page.conversation);
|
|
419
|
-
else
|
|
515
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
516
|
+
if (page.next !== undefined) {
|
|
420
517
|
following.push({ conversation: page.conversation, after: page.next });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
for (const { conversation } of asking) {
|
|
521
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
522
|
+
ended(conversation);
|
|
421
523
|
}
|
|
422
524
|
asking = following;
|
|
423
525
|
}
|
|
424
|
-
for (const [conversation, held] of arriving) {
|
|
425
|
-
if (!complete.has(conversation))
|
|
426
|
-
continue;
|
|
427
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
428
|
-
}
|
|
429
526
|
return null;
|
|
430
527
|
}
|
|
431
|
-
function
|
|
528
|
+
function caughtUp(conversations) {
|
|
529
|
+
const ends = new Map();
|
|
530
|
+
for (const conversation of conversations) {
|
|
531
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
532
|
+
catching.set(conversation, held);
|
|
533
|
+
void held.then(() => {
|
|
534
|
+
if (catching.get(conversation) === held)
|
|
535
|
+
catching.delete(conversation);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
539
|
+
const read = readHistories(conversations, ended);
|
|
540
|
+
const endAll = () => {
|
|
541
|
+
for (const conversation of conversations)
|
|
542
|
+
ended(conversation);
|
|
543
|
+
};
|
|
544
|
+
read.then(endAll, endAll);
|
|
545
|
+
return read;
|
|
546
|
+
}
|
|
547
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
432
548
|
return deps.durably(async (local) => {
|
|
433
549
|
if (!replicated.has(conversation))
|
|
434
550
|
return;
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
await local
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
551
|
+
const whole = next === undefined;
|
|
552
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
553
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
554
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
555
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
556
|
+
stored.add(conversation);
|
|
557
|
+
reads.set(conversation, cursor);
|
|
558
|
+
if (partial)
|
|
559
|
+
partials.add(conversation);
|
|
560
|
+
else
|
|
561
|
+
partials.delete(conversation);
|
|
443
562
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
444
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
563
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
445
564
|
behind.delete(conversation);
|
|
446
|
-
|
|
447
|
-
|
|
565
|
+
await slide(local, conversation, arriving);
|
|
566
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
567
|
+
deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
|
|
448
568
|
}
|
|
449
569
|
});
|
|
450
570
|
}
|
|
@@ -457,7 +577,7 @@ export function createConversationStore(deps) {
|
|
|
457
577
|
if (held === undefined)
|
|
458
578
|
return;
|
|
459
579
|
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
460
|
-
emit(conversation
|
|
580
|
+
emit(conversation);
|
|
461
581
|
return;
|
|
462
582
|
}
|
|
463
583
|
if (event.kind === 'turn') {
|
|
@@ -490,22 +610,126 @@ export function createConversationStore(deps) {
|
|
|
490
610
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
491
611
|
emit(conversation);
|
|
492
612
|
}
|
|
613
|
+
async function kept(local, conversation, following) {
|
|
614
|
+
if (following === null || empty(following)) {
|
|
615
|
+
await local.dropChatDraft(conversation);
|
|
616
|
+
drafts.delete(conversation);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
await local.keepChatDrafts([following]);
|
|
620
|
+
drafts.set(conversation, following);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function sealed(local, conversation, turns) {
|
|
624
|
+
const held = drafts.get(conversation);
|
|
625
|
+
const submitted = held?.submitted ?? [];
|
|
626
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
627
|
+
if (held === undefined || left.length === submitted.length)
|
|
628
|
+
return false;
|
|
629
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
function moved(agent, operation, to) {
|
|
633
|
+
const from = newChat(agent);
|
|
634
|
+
return deps.durably(async (local) => {
|
|
635
|
+
const held = drafts.get(from);
|
|
636
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
637
|
+
if (earlier.includes(operation))
|
|
638
|
+
return;
|
|
639
|
+
const blank = {
|
|
640
|
+
conversation: from,
|
|
641
|
+
text: '',
|
|
642
|
+
files: [],
|
|
643
|
+
revision: chatDraftRevision(),
|
|
644
|
+
};
|
|
645
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
646
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
647
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
648
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
649
|
+
drafts.set(from, left);
|
|
650
|
+
for (const one of taken)
|
|
651
|
+
drafts.set(to, one);
|
|
652
|
+
deps.publish(conversationChangeSet([], [from, to]));
|
|
653
|
+
});
|
|
654
|
+
}
|
|
493
655
|
function withDraft(conversation, next) {
|
|
494
656
|
return deps.durably(async (local) => {
|
|
495
|
-
const following = next(drafts.get(conversation));
|
|
657
|
+
const following = await next(drafts.get(conversation), local);
|
|
496
658
|
if (following === undefined)
|
|
497
659
|
return;
|
|
498
|
-
|
|
499
|
-
await local.dropChatDraft(conversation);
|
|
500
|
-
drafts.delete(conversation);
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
await local.keepChatDraft(following);
|
|
504
|
-
drafts.set(conversation, following);
|
|
505
|
-
}
|
|
660
|
+
await kept(local, conversation, following);
|
|
506
661
|
deps.publish(conversationChangeSet([], [conversation]));
|
|
507
662
|
});
|
|
508
663
|
}
|
|
664
|
+
async function submitted(conversation, operation, draft) {
|
|
665
|
+
let submission;
|
|
666
|
+
let earlier = false;
|
|
667
|
+
await withDraft(conversation, (current) => {
|
|
668
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
669
|
+
earlier = submission !== undefined;
|
|
670
|
+
if (submission !== undefined)
|
|
671
|
+
return undefined;
|
|
672
|
+
const said = draft ?? current;
|
|
673
|
+
submission = {
|
|
674
|
+
operation,
|
|
675
|
+
text: said?.text ?? '',
|
|
676
|
+
files: [...(said?.files ?? [])],
|
|
677
|
+
turn: null,
|
|
678
|
+
};
|
|
679
|
+
return {
|
|
680
|
+
conversation,
|
|
681
|
+
...(draft === undefined || current === undefined
|
|
682
|
+
? { text: '', files: [], revision: chatDraftRevision() }
|
|
683
|
+
: { text: current.text, files: current.files, revision: current.revision }),
|
|
684
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
685
|
+
};
|
|
686
|
+
});
|
|
687
|
+
return { submission: submission, earlier };
|
|
688
|
+
}
|
|
689
|
+
function forgotten(conversation, submission) {
|
|
690
|
+
return withDraft(conversation, (current) => {
|
|
691
|
+
const held = current?.submitted ?? [];
|
|
692
|
+
if (current === undefined || !held.includes(submission))
|
|
693
|
+
return undefined;
|
|
694
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
async function dispatched(conversation, submission) {
|
|
698
|
+
const dispatch = {};
|
|
699
|
+
sending.set(submission.operation, dispatch);
|
|
700
|
+
try {
|
|
701
|
+
const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
|
|
702
|
+
const result = await invoke('conversation.send', conversation, {
|
|
703
|
+
text: submission.text,
|
|
704
|
+
files,
|
|
705
|
+
operation_id: submission.operation,
|
|
706
|
+
});
|
|
707
|
+
if (result.kind === 'revoked-authorization')
|
|
708
|
+
return result;
|
|
709
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
710
|
+
deps.unanswered();
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
const turn = answered(result, 'turn');
|
|
714
|
+
await withDraft(conversation, async (current, local) => {
|
|
715
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
716
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
717
|
+
return undefined;
|
|
718
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
719
|
+
if (result.kind !== 'action')
|
|
720
|
+
return returned(current, held, others);
|
|
721
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
722
|
+
return withSubmitted(current, turn === null || arrived
|
|
723
|
+
? others
|
|
724
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
725
|
+
});
|
|
726
|
+
return result;
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
if (sending.get(submission.operation) === dispatch)
|
|
730
|
+
sending.delete(submission.operation);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
509
733
|
function invoke(name, conversation, extra = {}) {
|
|
510
734
|
return deps.call(name, { conversation, ...extra });
|
|
511
735
|
}
|
|
@@ -515,47 +739,39 @@ export function createConversationStore(deps) {
|
|
|
515
739
|
return entries();
|
|
516
740
|
},
|
|
517
741
|
async read(conversation, options = {}) {
|
|
518
|
-
const
|
|
519
|
-
return deps.durably(() =>
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
742
|
+
const asked = askedOf(options);
|
|
743
|
+
return deps.durably(async (local) => {
|
|
744
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
745
|
+
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
746
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
|
|
747
|
+
});
|
|
523
748
|
},
|
|
524
|
-
async
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
if (
|
|
528
|
-
sending.set(conversation, pending);
|
|
529
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
530
|
-
}
|
|
531
|
-
try {
|
|
532
|
-
const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
|
|
533
|
-
...(await declaredFile(file)),
|
|
534
|
-
blob: file.blob,
|
|
535
|
-
})));
|
|
536
|
-
const result = await invoke('conversation.send', conversation, {
|
|
537
|
-
text: draft?.text ?? '',
|
|
538
|
-
files,
|
|
539
|
-
});
|
|
540
|
-
const turn = turnOf(result);
|
|
541
|
-
if (turn === null || draft === undefined)
|
|
542
|
-
return result;
|
|
543
|
-
await withDraft(conversation, (current) => {
|
|
544
|
-
if (current !== draft)
|
|
545
|
-
return undefined;
|
|
546
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
547
|
-
return sealed ? null : { ...draft, turn };
|
|
548
|
-
});
|
|
749
|
+
async create(input) {
|
|
750
|
+
const result = await deps.call('conversation.create', input);
|
|
751
|
+
const conversation = answered(result, 'conversation');
|
|
752
|
+
if (conversation === null)
|
|
549
753
|
return result;
|
|
754
|
+
if (!listing.has(conversation))
|
|
755
|
+
creations.add(conversation);
|
|
756
|
+
const creation = creationOf(input);
|
|
757
|
+
if (creation !== null)
|
|
758
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
759
|
+
return result;
|
|
760
|
+
},
|
|
761
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
762
|
+
if (deps.online()) {
|
|
763
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
764
|
+
if (!earlier || !deps.expired(operation))
|
|
765
|
+
return dispatched(conversation, submission);
|
|
766
|
+
await forgotten(conversation, submission);
|
|
767
|
+
return EXPIRED;
|
|
550
768
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
556
|
-
}
|
|
557
|
-
}
|
|
769
|
+
if (draft !== undefined) {
|
|
770
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
771
|
+
? undefined
|
|
772
|
+
: returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
558
773
|
}
|
|
774
|
+
return OFFLINE;
|
|
559
775
|
},
|
|
560
776
|
answer(conversation, answer) {
|
|
561
777
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -570,7 +786,12 @@ export function createConversationStore(deps) {
|
|
|
570
786
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
571
787
|
if (result.kind !== 'action')
|
|
572
788
|
return result;
|
|
573
|
-
await withDraft(conversation, (current) =>
|
|
789
|
+
await withDraft(conversation, (current) => {
|
|
790
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
791
|
+
if (current === undefined || held === undefined)
|
|
792
|
+
return undefined;
|
|
793
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
794
|
+
});
|
|
574
795
|
return result;
|
|
575
796
|
},
|
|
576
797
|
delete(conversation) {
|
|
@@ -579,33 +800,42 @@ export function createConversationStore(deps) {
|
|
|
579
800
|
};
|
|
580
801
|
const chatDraft = {
|
|
581
802
|
write(conversation, draft, revision = chatDraftRevision()) {
|
|
582
|
-
return withDraft(conversation, () => ({
|
|
803
|
+
return withDraft(conversation, (current) => ({
|
|
583
804
|
conversation,
|
|
584
805
|
text: draft.text,
|
|
585
806
|
files: [...draft.files],
|
|
586
|
-
turn: null,
|
|
587
807
|
revision,
|
|
808
|
+
...(current === undefined ? {} : pending(current)),
|
|
588
809
|
}));
|
|
589
810
|
},
|
|
590
811
|
discard(conversation) {
|
|
591
|
-
return withDraft(conversation, (current) =>
|
|
812
|
+
return withDraft(conversation, (current) => current === undefined
|
|
813
|
+
? undefined
|
|
814
|
+
: {
|
|
815
|
+
conversation,
|
|
816
|
+
text: '',
|
|
817
|
+
files: [],
|
|
818
|
+
revision: chatDraftRevision(),
|
|
819
|
+
...pending(current),
|
|
820
|
+
});
|
|
592
821
|
},
|
|
593
822
|
};
|
|
594
823
|
return {
|
|
595
824
|
surface,
|
|
596
825
|
chatDraft,
|
|
597
|
-
hydrate(held,
|
|
598
|
-
|
|
826
|
+
hydrate(held, kept) {
|
|
827
|
+
stored.clear();
|
|
599
828
|
reads.clear();
|
|
600
|
-
|
|
601
|
-
view.messages = null;
|
|
829
|
+
partials.clear();
|
|
602
830
|
for (const entry of held) {
|
|
603
|
-
|
|
831
|
+
stored.add(entry.conversation);
|
|
604
832
|
if (entry.read !== undefined)
|
|
605
833
|
reads.set(entry.conversation, entry.read);
|
|
834
|
+
if (entry.partial)
|
|
835
|
+
partials.add(entry.conversation);
|
|
606
836
|
}
|
|
607
837
|
drafts.clear();
|
|
608
|
-
for (const record of
|
|
838
|
+
for (const record of kept)
|
|
609
839
|
drafts.set(record.conversation, record);
|
|
610
840
|
},
|
|
611
841
|
async reconcile() {
|
|
@@ -613,8 +843,10 @@ export function createConversationStore(deps) {
|
|
|
613
843
|
return;
|
|
614
844
|
const named = new Set(listing.keys());
|
|
615
845
|
const leaving = [
|
|
616
|
-
...new Set([...replicated, ...
|
|
617
|
-
].filter((conversation) => !named.has(conversation)
|
|
846
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
847
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
848
|
+
!creations.has(conversation) &&
|
|
849
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
618
850
|
for (const conversation of named) {
|
|
619
851
|
if (!replicated.has(conversation))
|
|
620
852
|
behind.add(conversation);
|
|
@@ -625,41 +857,93 @@ export function createConversationStore(deps) {
|
|
|
625
857
|
await remove(leaving);
|
|
626
858
|
},
|
|
627
859
|
async catchUp(progress) {
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
860
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
861
|
+
const read = async (conversations) => {
|
|
862
|
+
if (conversations.length === 0 || deps.closed())
|
|
863
|
+
return;
|
|
864
|
+
const failed = await caughtUp(conversations);
|
|
631
865
|
if (failed?.kind === 'transport-uncertainty')
|
|
632
866
|
progress.reachable = false;
|
|
633
|
-
}
|
|
867
|
+
};
|
|
868
|
+
const asking = [...behind].filter(owed).sort();
|
|
869
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
870
|
+
await Promise.all([
|
|
871
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
872
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
873
|
+
]);
|
|
634
874
|
if (behind.size > 0)
|
|
635
875
|
progress.complete = false;
|
|
636
876
|
},
|
|
877
|
+
async resend(progress) {
|
|
878
|
+
for (const [conversation, held] of [...drafts]) {
|
|
879
|
+
for (const submission of held.submitted ?? []) {
|
|
880
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
881
|
+
continue;
|
|
882
|
+
if (deps.closed())
|
|
883
|
+
return;
|
|
884
|
+
if (deps.expired(submission.operation)) {
|
|
885
|
+
await forgotten(conversation, submission);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
const result = await dispatched(conversation, submission);
|
|
889
|
+
if (result.kind === 'revoked-authorization')
|
|
890
|
+
return;
|
|
891
|
+
if (result.kind === 'transport-uncertainty') {
|
|
892
|
+
progress.reachable = false;
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
896
|
+
progress.complete = false;
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
},
|
|
637
902
|
current() {
|
|
638
903
|
return behind.size === 0;
|
|
639
904
|
},
|
|
905
|
+
known() {
|
|
906
|
+
return listKnown;
|
|
907
|
+
},
|
|
640
908
|
reconnected() {
|
|
641
909
|
for (const conversation of replicated)
|
|
642
910
|
gap(conversation);
|
|
643
911
|
},
|
|
912
|
+
reopened() {
|
|
913
|
+
sending.clear();
|
|
914
|
+
},
|
|
915
|
+
lost() {
|
|
916
|
+
catching.clear();
|
|
917
|
+
},
|
|
644
918
|
watch(conversation, options, deliver) {
|
|
645
|
-
const
|
|
646
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
919
|
+
const asked = askedOf(options);
|
|
920
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
647
921
|
const view = views.get(key) ?? {
|
|
648
922
|
conversation,
|
|
649
|
-
|
|
923
|
+
asked,
|
|
650
924
|
watchers: new Set(),
|
|
651
|
-
|
|
925
|
+
window: null,
|
|
926
|
+
more: false,
|
|
927
|
+
total: null,
|
|
652
928
|
};
|
|
653
929
|
views.set(key, view);
|
|
654
930
|
const watcher = { notify: deliver, ready: false };
|
|
655
931
|
view.watchers.add(watcher);
|
|
656
932
|
void deps
|
|
657
|
-
.durably(() =>
|
|
933
|
+
.durably(async (local) => {
|
|
934
|
+
if (view.window !== null)
|
|
935
|
+
return;
|
|
936
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
937
|
+
view.window = messages;
|
|
938
|
+
view.more = more;
|
|
939
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
940
|
+
})
|
|
658
941
|
.then(() => {
|
|
659
942
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
660
943
|
return;
|
|
661
944
|
watcher.ready = true;
|
|
662
|
-
|
|
945
|
+
const window = view.window ?? [];
|
|
946
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
663
947
|
})
|
|
664
948
|
.catch(() => { });
|
|
665
949
|
return () => {
|
|
@@ -706,12 +990,15 @@ export function createConversationStore(deps) {
|
|
|
706
990
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
707
991
|
if (event.kind === 'list') {
|
|
708
992
|
listing.clear();
|
|
709
|
-
for (const entry of event.entries)
|
|
993
|
+
for (const entry of event.entries) {
|
|
710
994
|
listing.set(entry.conversation, { ...entry });
|
|
995
|
+
creations.delete(entry.conversation);
|
|
996
|
+
}
|
|
711
997
|
listKnown = true;
|
|
712
998
|
}
|
|
713
999
|
else if (event.kind === 'listed') {
|
|
714
1000
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
1001
|
+
creations.delete(event.entry.conversation);
|
|
715
1002
|
}
|
|
716
1003
|
else {
|
|
717
1004
|
listing.delete(event.conversation);
|
|
@@ -725,12 +1012,11 @@ export function createConversationStore(deps) {
|
|
|
725
1012
|
heard(event);
|
|
726
1013
|
return false;
|
|
727
1014
|
},
|
|
728
|
-
|
|
729
|
-
return [...
|
|
1015
|
+
searchable() {
|
|
1016
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
730
1017
|
},
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
1018
|
+
acquired(conversation) {
|
|
1019
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
734
1020
|
},
|
|
735
1021
|
close() {
|
|
736
1022
|
views.clear();
|