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