@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ce5c5bb29be5a
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/README.md +24 -11
- package/dist/adapter.d.ts +172 -48
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +15 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +81 -0
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +36 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +199 -48
- package/dist/chain.d.ts +11 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -56
- package/dist/change.d.ts +45 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +34 -0
- package/dist/client.d.ts +37 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +159 -29
- package/dist/collection.d.ts +12 -14
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +52 -56
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +773 -0
- package/dist/connection.d.ts +67 -28
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3911 -1044
- package/dist/conversation.d.ts +59 -18
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +849 -248
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +4 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +151 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/fulltext.d.ts +27 -0
- package/dist/fulltext.d.ts.map +1 -0
- package/dist/fulltext.js +370 -0
- package/dist/host.d.ts +20 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +166 -48
- package/dist/identity.d.ts +4 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +16 -1
- package/dist/index.d.ts +20 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/live.d.ts +22 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +124 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +22 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +208 -47
- package/dist/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +90 -0
- package/dist/model.d.ts +22 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +13 -4
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +21 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +128 -17
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +72 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +81 -1
- package/dist/overlay.d.ts +15 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +136 -180
- package/dist/paths.d.ts +1 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -16
- package/dist/query.d.ts +78 -7
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +126 -43
- package/dist/reach.d.ts +54 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +448 -0
- package/dist/react.d.ts +43 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +54 -0
- package/dist/readiness.d.ts +52 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +224 -0
- package/dist/refresh.d.ts +19 -7
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +125 -53
- package/dist/replica.d.ts +58 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +320 -158
- package/dist/room-draft.d.ts +60 -10
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +227 -42
- package/dist/rows.d.ts +39 -34
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +195 -81
- package/dist/save.d.ts +74 -6
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +898 -181
- package/dist/search.d.ts +41 -29
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +37 -105
- 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 +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/status.d.ts +7 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -9
- package/dist/store.d.ts +109 -59
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +323 -344
- package/dist/stored.d.ts +130 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +560 -0
- package/dist/tables.d.ts +325 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +578 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -0
- package/dist/windows.d.ts +22 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +89 -0
- package/dist/wire.d.ts +194 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +90 -61
- package/dist/words.d.ts +19 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +156 -0
- package/package.json +33 -10
- package/AGENTS.md +0 -207
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/read-side.d.ts +0 -14
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -371
package/dist/conversation.js
CHANGED
|
@@ -1,18 +1,71 @@
|
|
|
1
1
|
import { conversationChangeSet } from './change-set.js';
|
|
2
|
-
import {
|
|
2
|
+
import { runtimeError } from './failure.js';
|
|
3
|
+
import { refusedForSpace } from './durability.js';
|
|
4
|
+
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
5
|
+
import { OFFLINE } from './outcome.js';
|
|
6
|
+
import { untilAnswered } from './recovery.js';
|
|
7
|
+
import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
|
|
3
8
|
const QUIET = {
|
|
4
9
|
turn: null,
|
|
5
10
|
questions: [],
|
|
6
11
|
process: null,
|
|
7
12
|
commands: [],
|
|
8
13
|
options: [],
|
|
14
|
+
starting: null,
|
|
15
|
+
link: null,
|
|
16
|
+
deliveryUnknown: [],
|
|
9
17
|
};
|
|
18
|
+
const FACTS = [
|
|
19
|
+
'questions',
|
|
20
|
+
'process',
|
|
21
|
+
'commands',
|
|
22
|
+
'options',
|
|
23
|
+
'starting',
|
|
24
|
+
'link',
|
|
25
|
+
'deliveryUnknown',
|
|
26
|
+
];
|
|
27
|
+
export function unheldView(conversation) {
|
|
28
|
+
return {
|
|
29
|
+
conversation,
|
|
30
|
+
messages: [],
|
|
31
|
+
turn: null,
|
|
32
|
+
questions: [],
|
|
33
|
+
process: null,
|
|
34
|
+
commands: [],
|
|
35
|
+
options: [],
|
|
36
|
+
starting: null,
|
|
37
|
+
link: null,
|
|
38
|
+
deliveryUnknown: [],
|
|
39
|
+
chatDraft: null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export const SESSION_STARTED = 'Session started';
|
|
43
|
+
const NEW_CHAT = 'new:';
|
|
44
|
+
export const HISTORY_CONVERSATIONS = 64;
|
|
45
|
+
export function newChat(agent) {
|
|
46
|
+
return `${NEW_CHAT}${agent}`;
|
|
47
|
+
}
|
|
48
|
+
function creationOf(input) {
|
|
49
|
+
if (typeof input !== 'object' || input === null)
|
|
50
|
+
return null;
|
|
51
|
+
const { agent_id: agent, operation_id: operation, replaces, } = input;
|
|
52
|
+
return typeof agent === 'string' && typeof operation === 'string' && replaces === undefined
|
|
53
|
+
? { agent, operation }
|
|
54
|
+
: null;
|
|
55
|
+
}
|
|
10
56
|
function startedTurn(turn, operation) {
|
|
57
|
+
if (operation.op === 'begin')
|
|
58
|
+
return { turn, started: operation.at, parts: [], context: null };
|
|
11
59
|
if (operation.op === 'context') {
|
|
12
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
turn,
|
|
62
|
+
started: null,
|
|
63
|
+
parts: [],
|
|
64
|
+
context: { used: operation.used, window: operation.window },
|
|
65
|
+
};
|
|
13
66
|
}
|
|
14
67
|
return operation.op === 'start' && operation.index === 0
|
|
15
|
-
? { turn, parts: [structuredClone(operation.part)], context: null }
|
|
68
|
+
? { turn, started: null, parts: [structuredClone(operation.part)], context: null }
|
|
16
69
|
: null;
|
|
17
70
|
}
|
|
18
71
|
function changedFields(part, operation) {
|
|
@@ -27,6 +80,8 @@ function changedFields(part, operation) {
|
|
|
27
80
|
return { entries: structuredClone(operation.entries) };
|
|
28
81
|
}
|
|
29
82
|
function appliedTurn(held, operation) {
|
|
83
|
+
if (operation.op === 'begin')
|
|
84
|
+
return { ...held, started: operation.at };
|
|
30
85
|
if (operation.op === 'context') {
|
|
31
86
|
return { ...held, context: { used: operation.used, window: operation.window } };
|
|
32
87
|
}
|
|
@@ -47,18 +102,28 @@ function appliedTurn(held, operation) {
|
|
|
47
102
|
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
48
103
|
};
|
|
49
104
|
}
|
|
50
|
-
function
|
|
51
|
-
if (left.position !== right.position)
|
|
52
|
-
return left.position < right.position ? -1 : 1;
|
|
53
|
-
if (left.identity !== right.identity)
|
|
54
|
-
return left.identity < right.identity ? -1 : 1;
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
function merged(held, arriving) {
|
|
105
|
+
function windowOf(held, arriving, limit) {
|
|
58
106
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
59
107
|
for (const message of arriving)
|
|
60
108
|
byIdentity.set(message.identity, message);
|
|
61
|
-
|
|
109
|
+
const next = [...byIdentity.values()].sort(messageOrder);
|
|
110
|
+
const start = Math.max(0, next.length - limit);
|
|
111
|
+
return { messages: next.slice(start), dropped: start > 0 };
|
|
112
|
+
}
|
|
113
|
+
function frameOf(asked, messages, more, total) {
|
|
114
|
+
const oldest = messages[0];
|
|
115
|
+
const next = !more
|
|
116
|
+
? undefined
|
|
117
|
+
: oldest === undefined
|
|
118
|
+
? (asked.after ?? FROM_START)
|
|
119
|
+
: cursorOf([oldest.position, oldest.identity]);
|
|
120
|
+
return {
|
|
121
|
+
...(next === undefined ? {} : { next }),
|
|
122
|
+
...(total === null ? {} : { total }),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function newestOf(arriving, from) {
|
|
126
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
62
127
|
}
|
|
63
128
|
function acceptedMessage(value) {
|
|
64
129
|
if (typeof value !== 'object' || value === null)
|
|
@@ -117,84 +182,339 @@ function historyPages(result) {
|
|
|
117
182
|
}
|
|
118
183
|
return pages;
|
|
119
184
|
}
|
|
120
|
-
function
|
|
185
|
+
function answered(result, field) {
|
|
121
186
|
if (result.kind !== 'action')
|
|
122
187
|
return null;
|
|
123
188
|
const answer = result.result;
|
|
124
189
|
if (typeof answer !== 'object' || answer === null)
|
|
125
190
|
return null;
|
|
126
|
-
const
|
|
127
|
-
return typeof
|
|
191
|
+
const value = answer[field];
|
|
192
|
+
return typeof value === 'string' ? value : null;
|
|
193
|
+
}
|
|
194
|
+
function beforeOf(after) {
|
|
195
|
+
if (after === undefined)
|
|
196
|
+
return null;
|
|
197
|
+
const decoded = typeof after === 'string' ? decodedCursor(after) : null;
|
|
198
|
+
if (decoded?.length === 0)
|
|
199
|
+
return null;
|
|
200
|
+
const [position, identity] = decoded ?? [];
|
|
201
|
+
if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
|
|
202
|
+
throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
|
|
203
|
+
}
|
|
204
|
+
return [position, identity];
|
|
205
|
+
}
|
|
206
|
+
function askedOf(options) {
|
|
207
|
+
const { limit, after, total } = options;
|
|
208
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
|
|
209
|
+
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
210
|
+
}
|
|
211
|
+
return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
|
|
212
|
+
}
|
|
213
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
214
|
+
const DURABLE_WRITE = {
|
|
215
|
+
kind: 'rejected',
|
|
216
|
+
code: 'durable-write',
|
|
217
|
+
fields: {},
|
|
218
|
+
retryable: true,
|
|
219
|
+
};
|
|
220
|
+
function unwritten(draft) {
|
|
221
|
+
return draft.text === '';
|
|
222
|
+
}
|
|
223
|
+
function pending(draft) {
|
|
224
|
+
return {
|
|
225
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
226
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function empty(draft) {
|
|
230
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
231
|
+
}
|
|
232
|
+
function withSubmitted(draft, submitted) {
|
|
233
|
+
const { submitted: _held, ...unsent } = draft;
|
|
234
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
235
|
+
}
|
|
236
|
+
function prepended(draft, said) {
|
|
237
|
+
return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
|
|
128
238
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
return limit;
|
|
239
|
+
function returned(draft, submission, others) {
|
|
240
|
+
return withSubmitted({
|
|
241
|
+
conversation: draft.conversation,
|
|
242
|
+
...prepended(draft, submission),
|
|
243
|
+
revision: chatDraftRevision(),
|
|
244
|
+
}, others);
|
|
137
245
|
}
|
|
138
246
|
export function createConversationStore(deps) {
|
|
139
|
-
const
|
|
247
|
+
const stored = new Set();
|
|
140
248
|
const reads = new Map();
|
|
249
|
+
const partials = new Set();
|
|
141
250
|
const drafts = new Map();
|
|
142
251
|
const sending = new Map();
|
|
143
252
|
const behind = new Set();
|
|
253
|
+
const catching = new Map();
|
|
144
254
|
const gaps = new Map();
|
|
145
255
|
const lives = new Map();
|
|
256
|
+
const unsent = new Map();
|
|
146
257
|
const views = new Map();
|
|
258
|
+
const listing = new Map();
|
|
259
|
+
const creations = new Set();
|
|
260
|
+
const listWatchers = new Set();
|
|
261
|
+
const unlisted = new Set();
|
|
262
|
+
let listKnown = false;
|
|
263
|
+
let listHeld = false;
|
|
264
|
+
let listKept = false;
|
|
265
|
+
const agentWatchers = new Set();
|
|
266
|
+
const unnamed = new Set();
|
|
267
|
+
let agentsKnown = false;
|
|
268
|
+
let agentNames = new Map();
|
|
269
|
+
let retiredAgents = new Set();
|
|
270
|
+
let agentsRead = null;
|
|
271
|
+
let agentsFrom;
|
|
147
272
|
let replicated = new Set();
|
|
148
273
|
let handedReplicated;
|
|
149
274
|
function liveOf(conversation) {
|
|
150
275
|
return lives.get(conversation) ?? QUIET;
|
|
151
276
|
}
|
|
152
|
-
function
|
|
153
|
-
const held = messages.get(conversation) ?? [];
|
|
154
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
155
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
156
|
-
identity,
|
|
157
|
-
turn,
|
|
158
|
-
text,
|
|
159
|
-
message: structuredClone(message),
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
277
|
+
function viewOf(conversation, held, frame) {
|
|
163
278
|
const draft = drafts.get(conversation);
|
|
164
279
|
const facts = liveOf(conversation);
|
|
165
280
|
return {
|
|
166
281
|
conversation,
|
|
167
|
-
messages:
|
|
282
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
283
|
+
identity,
|
|
284
|
+
turn,
|
|
285
|
+
text,
|
|
286
|
+
message,
|
|
287
|
+
})),
|
|
288
|
+
...frame,
|
|
168
289
|
turn: structuredClone(facts.turn),
|
|
169
290
|
questions: structuredClone(facts.questions),
|
|
170
291
|
process: facts.process === null ? null : { ...facts.process },
|
|
171
292
|
commands: structuredClone(facts.commands),
|
|
172
293
|
options: structuredClone(facts.options),
|
|
173
|
-
|
|
294
|
+
starting: facts.starting,
|
|
295
|
+
link: facts.link === null ? null : { ...facts.link },
|
|
296
|
+
deliveryUnknown: [...facts.deliveryUnknown],
|
|
297
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
174
298
|
? null
|
|
175
|
-
: { text: draft.text,
|
|
299
|
+
: { text: draft.text, revision: draft.revision },
|
|
176
300
|
};
|
|
177
301
|
}
|
|
178
|
-
function emit(conversation
|
|
302
|
+
function emit(conversation) {
|
|
179
303
|
if (deps.closed())
|
|
180
304
|
return;
|
|
181
305
|
for (const view of views.values()) {
|
|
182
|
-
if (view.conversation !== conversation)
|
|
306
|
+
if (view.conversation !== conversation || view.window === null)
|
|
183
307
|
continue;
|
|
184
|
-
if (messagesChanged)
|
|
185
|
-
view.messages = null;
|
|
186
308
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
187
309
|
if (ready.length === 0)
|
|
188
310
|
continue;
|
|
189
|
-
|
|
190
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
311
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
|
|
191
312
|
for (const watcher of ready)
|
|
192
313
|
watcher.notify(value);
|
|
193
314
|
}
|
|
194
315
|
}
|
|
316
|
+
function sharedFrame(view, window) {
|
|
317
|
+
return frameOf(view.asked, window, view.more, view.total);
|
|
318
|
+
}
|
|
319
|
+
async function slide(local, conversation, arriving) {
|
|
320
|
+
let count;
|
|
321
|
+
for (const view of views.values()) {
|
|
322
|
+
if (view.conversation !== conversation || view.window === null)
|
|
323
|
+
continue;
|
|
324
|
+
if (arriving === null) {
|
|
325
|
+
view.window = [];
|
|
326
|
+
view.more = false;
|
|
327
|
+
if (view.total !== null)
|
|
328
|
+
view.total = 0;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const joining = arriving.filter((message) => precedes(message, view.asked.before));
|
|
332
|
+
const next = windowOf(view.window, joining, view.asked.limit);
|
|
333
|
+
view.window = next.messages;
|
|
334
|
+
view.more ||= next.dropped;
|
|
335
|
+
if (view.total !== null && arriving.length > 0) {
|
|
336
|
+
count ??= await local.messageCount(conversation);
|
|
337
|
+
view.total = count;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function stateOf(conversation) {
|
|
342
|
+
const facts = liveOf(conversation);
|
|
343
|
+
if (facts.questions.length > 0)
|
|
344
|
+
return 'asking';
|
|
345
|
+
return facts.turn === null ? 'idle' : 'working';
|
|
346
|
+
}
|
|
347
|
+
function entries() {
|
|
348
|
+
return [...listing.values()].map((entry) => ({
|
|
349
|
+
conversation: entry.conversation,
|
|
350
|
+
agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
|
|
351
|
+
name: entry.name,
|
|
352
|
+
created: entry.created,
|
|
353
|
+
finished: entry.finished,
|
|
354
|
+
outcome: entry.outcome,
|
|
355
|
+
waiting: entry.waiting,
|
|
356
|
+
state: stateOf(entry.conversation),
|
|
357
|
+
started: lives.get(entry.conversation)?.turn?.started ?? null,
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
function keepList() {
|
|
361
|
+
if (!listKnown || listKept)
|
|
362
|
+
return;
|
|
363
|
+
listKept = true;
|
|
364
|
+
queueMicrotask(() => {
|
|
365
|
+
listKept = false;
|
|
366
|
+
if (deps.closed())
|
|
367
|
+
return;
|
|
368
|
+
const entries = [...listing.values()];
|
|
369
|
+
void deps.durably((local) => local.fact('conversationList', entries)).catch(() => { });
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function emitList() {
|
|
373
|
+
if (deps.closed())
|
|
374
|
+
return;
|
|
375
|
+
if (listWatchers.size > 0) {
|
|
376
|
+
const value = entries();
|
|
377
|
+
for (const watcher of listWatchers)
|
|
378
|
+
watcher(value);
|
|
379
|
+
}
|
|
380
|
+
learned();
|
|
381
|
+
}
|
|
382
|
+
function unlearnable() {
|
|
383
|
+
return !deps.online() || (deps.listed() && deps.agents() === undefined);
|
|
384
|
+
}
|
|
385
|
+
function learned() {
|
|
386
|
+
if (deps.closed())
|
|
387
|
+
return;
|
|
388
|
+
const named = agentsKnown || unlearnable();
|
|
389
|
+
if (unlisted.size > 0 &&
|
|
390
|
+
named &&
|
|
391
|
+
(listKnown || listHeld || deps.stream() === undefined || unlearnable())) {
|
|
392
|
+
const value = entries();
|
|
393
|
+
for (const watcher of [...unlisted]) {
|
|
394
|
+
unlisted.delete(watcher);
|
|
395
|
+
listWatchers.add(watcher);
|
|
396
|
+
watcher(value);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (unnamed.size > 0 && named) {
|
|
400
|
+
const value = agents();
|
|
401
|
+
for (const watcher of [...unnamed]) {
|
|
402
|
+
unnamed.delete(watcher);
|
|
403
|
+
agentWatchers.add(watcher);
|
|
404
|
+
watcher(value);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function agents() {
|
|
409
|
+
return [...agentNames]
|
|
410
|
+
.filter(([identity]) => !retiredAgents.has(identity))
|
|
411
|
+
.map(([identity, name]) => ({ identity, name }))
|
|
412
|
+
.sort((left, right) => left.name === right.name
|
|
413
|
+
? left.identity.localeCompare(right.identity)
|
|
414
|
+
: left.name.localeCompare(right.name));
|
|
415
|
+
}
|
|
416
|
+
function emitAgents() {
|
|
417
|
+
if (deps.closed() || agentWatchers.size === 0)
|
|
418
|
+
return;
|
|
419
|
+
const value = agents();
|
|
420
|
+
for (const watcher of agentWatchers)
|
|
421
|
+
watcher(value);
|
|
422
|
+
}
|
|
423
|
+
function setLive(conversation, next) {
|
|
424
|
+
const before = stateOf(conversation);
|
|
425
|
+
const began = lives.get(conversation)?.turn?.started;
|
|
426
|
+
if (next === null)
|
|
427
|
+
lives.delete(conversation);
|
|
428
|
+
else
|
|
429
|
+
lives.set(conversation, next);
|
|
430
|
+
if (!listing.has(conversation))
|
|
431
|
+
return;
|
|
432
|
+
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
433
|
+
emitList();
|
|
434
|
+
}
|
|
435
|
+
async function readAgents() {
|
|
436
|
+
const declaration = deps.agents();
|
|
437
|
+
agentsFrom = JSON.stringify(declaration);
|
|
438
|
+
if (declaration === undefined)
|
|
439
|
+
return;
|
|
440
|
+
const names = new Map();
|
|
441
|
+
const retired = new Set();
|
|
442
|
+
let covered = null;
|
|
443
|
+
for (let after;;) {
|
|
444
|
+
const read = await deps.documents(declaration, after);
|
|
445
|
+
if ('pending' in read)
|
|
446
|
+
return;
|
|
447
|
+
const coverage = JSON.stringify(read.coverage);
|
|
448
|
+
if (covered !== null && coverage !== covered)
|
|
449
|
+
return readAgents();
|
|
450
|
+
covered = coverage;
|
|
451
|
+
for (const document of read.documents) {
|
|
452
|
+
const identity = document.fields?.[declaration.identity];
|
|
453
|
+
const name = document.fields?.[declaration.name];
|
|
454
|
+
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
455
|
+
continue;
|
|
456
|
+
names.set(identity, name);
|
|
457
|
+
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
458
|
+
if (ended !== undefined && ended !== null)
|
|
459
|
+
retired.add(identity);
|
|
460
|
+
}
|
|
461
|
+
if (read.next === undefined)
|
|
462
|
+
break;
|
|
463
|
+
after = read.next;
|
|
464
|
+
}
|
|
465
|
+
agentsKnown = true;
|
|
466
|
+
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
467
|
+
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents])) {
|
|
468
|
+
learned();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
agentNames = names;
|
|
472
|
+
retiredAgents = retired;
|
|
473
|
+
emitAgents();
|
|
474
|
+
if (named)
|
|
475
|
+
emitList();
|
|
476
|
+
learned();
|
|
477
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
478
|
+
if (ended.length > 0)
|
|
479
|
+
await remove(ended);
|
|
480
|
+
}
|
|
481
|
+
function retiredChat(conversation) {
|
|
482
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
483
|
+
}
|
|
484
|
+
function agentsReady() {
|
|
485
|
+
agentsRead ??= readAgents().catch(() => {
|
|
486
|
+
agentsRead = null;
|
|
487
|
+
});
|
|
488
|
+
return agentsRead;
|
|
489
|
+
}
|
|
195
490
|
function live(conversation, change) {
|
|
196
|
-
|
|
197
|
-
|
|
491
|
+
const owed = unsent.get(conversation);
|
|
492
|
+
for (const fact of FACTS.filter((one) => one in change))
|
|
493
|
+
owed?.delete(fact);
|
|
494
|
+
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
495
|
+
emit(conversation);
|
|
496
|
+
}
|
|
497
|
+
function openingEnded() {
|
|
498
|
+
const owed = [...unsent];
|
|
499
|
+
unsent.clear();
|
|
500
|
+
for (const [conversation, facts] of owed) {
|
|
501
|
+
const held = lives.get(conversation);
|
|
502
|
+
if (held === undefined || facts.size === 0)
|
|
503
|
+
continue;
|
|
504
|
+
setLive(conversation, {
|
|
505
|
+
turn: held.turn,
|
|
506
|
+
questions: facts.has('questions') ? QUIET.questions : held.questions,
|
|
507
|
+
process: facts.has('process') ? QUIET.process : held.process,
|
|
508
|
+
commands: facts.has('commands') ? QUIET.commands : held.commands,
|
|
509
|
+
options: facts.has('options') ? QUIET.options : held.options,
|
|
510
|
+
starting: facts.has('starting') ? QUIET.starting : held.starting,
|
|
511
|
+
link: facts.has('link') ? QUIET.link : held.link,
|
|
512
|
+
deliveryUnknown: facts.has('deliveryUnknown')
|
|
513
|
+
? QUIET.deliveryUnknown
|
|
514
|
+
: held.deliveryUnknown,
|
|
515
|
+
});
|
|
516
|
+
emit(conversation);
|
|
517
|
+
}
|
|
198
518
|
}
|
|
199
519
|
function relayed(conversation, turn, operation) {
|
|
200
520
|
const held = liveOf(conversation).turn;
|
|
@@ -206,7 +526,7 @@ export function createConversationStore(deps) {
|
|
|
206
526
|
const held = lives.get(conversation);
|
|
207
527
|
if (held?.turn == null || !turns.has(held.turn.turn))
|
|
208
528
|
return false;
|
|
209
|
-
|
|
529
|
+
setLive(conversation, { ...held, turn: null });
|
|
210
530
|
return true;
|
|
211
531
|
}
|
|
212
532
|
function handReplicated() {
|
|
@@ -220,49 +540,41 @@ export function createConversationStore(deps) {
|
|
|
220
540
|
handedReplicated = named;
|
|
221
541
|
stream.replicate(held);
|
|
222
542
|
}
|
|
223
|
-
async function documented() {
|
|
224
|
-
const declaration = deps.cards();
|
|
225
|
-
if (declaration === undefined)
|
|
226
|
-
return [];
|
|
227
|
-
const { documents } = await deps.documents(declaration.locator);
|
|
228
|
-
return documents.filter((document) => typeof document.type === 'string');
|
|
229
|
-
}
|
|
230
|
-
function carded(documents) {
|
|
231
|
-
const declaration = deps.cards();
|
|
232
|
-
if (declaration === undefined)
|
|
233
|
-
return [];
|
|
234
|
-
const cards = [];
|
|
235
|
-
for (const card of documents) {
|
|
236
|
-
if (card.type !== declaration.type)
|
|
237
|
-
continue;
|
|
238
|
-
const identity = card.fields[declaration.identity];
|
|
239
|
-
if (typeof identity === 'string')
|
|
240
|
-
cards.push({ conversation: identity, card });
|
|
241
|
-
}
|
|
242
|
-
return cards;
|
|
243
|
-
}
|
|
244
543
|
async function remove(leaving) {
|
|
245
544
|
await deps.durably(async (local) => {
|
|
246
545
|
await local.dropConversations(leaving);
|
|
247
|
-
const removed = [];
|
|
248
546
|
const changed = [];
|
|
249
547
|
for (const conversation of leaving) {
|
|
250
|
-
|
|
251
|
-
removed.push({ conversation, identity: message.identity });
|
|
252
|
-
}
|
|
253
|
-
if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
548
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
254
549
|
changed.push(conversation);
|
|
255
550
|
}
|
|
256
|
-
|
|
551
|
+
stored.delete(conversation);
|
|
257
552
|
reads.delete(conversation);
|
|
553
|
+
partials.delete(conversation);
|
|
258
554
|
drafts.delete(conversation);
|
|
259
555
|
behind.delete(conversation);
|
|
260
|
-
|
|
556
|
+
unsent.delete(conversation);
|
|
557
|
+
setLive(conversation, null);
|
|
558
|
+
await slide(local, conversation, null);
|
|
261
559
|
}
|
|
262
560
|
if (changed.length > 0)
|
|
263
|
-
deps.publish(conversationChangeSet(
|
|
561
|
+
deps.publish(conversationChangeSet(changed));
|
|
264
562
|
});
|
|
265
563
|
}
|
|
564
|
+
function missed(conversation) {
|
|
565
|
+
const read = reads.get(conversation);
|
|
566
|
+
const newest = listing.get(conversation)?.newest;
|
|
567
|
+
return (read === undefined ||
|
|
568
|
+
newest === undefined ||
|
|
569
|
+
partials.has(conversation) ||
|
|
570
|
+
(newest !== null && (read === null || newest > read)));
|
|
571
|
+
}
|
|
572
|
+
function noted(conversation, message) {
|
|
573
|
+
const entry = listing.get(conversation);
|
|
574
|
+
if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
|
|
575
|
+
return;
|
|
576
|
+
listing.set(conversation, { ...entry, newest: message.position });
|
|
577
|
+
}
|
|
266
578
|
function gap(conversation) {
|
|
267
579
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
268
580
|
behind.add(conversation);
|
|
@@ -275,32 +587,28 @@ export function createConversationStore(deps) {
|
|
|
275
587
|
const advances = read !== undefined &&
|
|
276
588
|
!behind.has(conversation) &&
|
|
277
589
|
(read === null || message.position > read);
|
|
278
|
-
|
|
590
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
279
591
|
await local.keepMessages({
|
|
280
592
|
conversation,
|
|
281
593
|
messages: [message],
|
|
282
|
-
...(advances ? { read: message.position } : {}),
|
|
283
|
-
sealed,
|
|
594
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
284
595
|
});
|
|
285
|
-
|
|
596
|
+
stored.add(conversation);
|
|
286
597
|
if (advances)
|
|
287
598
|
reads.set(conversation, message.position);
|
|
288
|
-
if (sealed)
|
|
289
|
-
drafts.delete(conversation);
|
|
290
599
|
sealTurns(conversation, new Set([message.turn]));
|
|
291
|
-
|
|
600
|
+
await slide(local, conversation, [message]);
|
|
601
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
292
602
|
});
|
|
293
603
|
}
|
|
294
|
-
async function readHistories(conversations) {
|
|
604
|
+
async function readHistories(conversations, ended) {
|
|
295
605
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
296
|
-
|
|
297
|
-
const arriving = new Map();
|
|
298
|
-
const complete = new Set();
|
|
299
|
-
let asking = conversations.map((conversation) => ({
|
|
606
|
+
let waiting = conversations.map((conversation) => ({
|
|
300
607
|
conversation,
|
|
301
|
-
after:
|
|
608
|
+
after: reads.get(conversation) ?? undefined,
|
|
302
609
|
}));
|
|
303
|
-
while (
|
|
610
|
+
while (waiting.length > 0) {
|
|
611
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
304
612
|
const result = await deps.call('conversation.history', {
|
|
305
613
|
conversations: asking.map(({ conversation, after }) => ({
|
|
306
614
|
conversation,
|
|
@@ -310,124 +618,298 @@ export function createConversationStore(deps) {
|
|
|
310
618
|
const pages = historyPages(result);
|
|
311
619
|
if (pages === null)
|
|
312
620
|
return result;
|
|
313
|
-
const asked = new
|
|
621
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
314
622
|
const following = [];
|
|
315
623
|
for (const page of pages) {
|
|
316
624
|
if (!asked.has(page.conversation))
|
|
317
625
|
continue;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
...page.messages,
|
|
321
|
-
]);
|
|
322
|
-
if (page.next === undefined)
|
|
323
|
-
complete.add(page.conversation);
|
|
324
|
-
else
|
|
626
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
627
|
+
if (page.next !== undefined) {
|
|
325
628
|
following.push({ conversation: page.conversation, after: page.next });
|
|
629
|
+
}
|
|
326
630
|
}
|
|
327
|
-
asking
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
631
|
+
for (const { conversation } of asking) {
|
|
632
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
633
|
+
ended(conversation);
|
|
634
|
+
}
|
|
635
|
+
waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
|
|
333
636
|
}
|
|
334
637
|
return null;
|
|
335
638
|
}
|
|
336
|
-
function
|
|
639
|
+
function caughtUp(conversations) {
|
|
640
|
+
const ends = new Map();
|
|
641
|
+
for (const conversation of conversations) {
|
|
642
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
643
|
+
catching.set(conversation, held);
|
|
644
|
+
void held.then(() => {
|
|
645
|
+
if (catching.get(conversation) === held)
|
|
646
|
+
catching.delete(conversation);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
650
|
+
const read = readHistories(conversations, ended);
|
|
651
|
+
const endAll = () => {
|
|
652
|
+
for (const conversation of conversations)
|
|
653
|
+
ended(conversation);
|
|
654
|
+
};
|
|
655
|
+
read.then(endAll, endAll);
|
|
656
|
+
return read;
|
|
657
|
+
}
|
|
658
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
337
659
|
return deps.durably(async (local) => {
|
|
338
660
|
if (!replicated.has(conversation))
|
|
339
661
|
return;
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
await local
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
662
|
+
const whole = next === undefined;
|
|
663
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
664
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
665
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
666
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
667
|
+
stored.add(conversation);
|
|
668
|
+
reads.set(conversation, cursor);
|
|
669
|
+
if (partial)
|
|
670
|
+
partials.add(conversation);
|
|
671
|
+
else
|
|
672
|
+
partials.delete(conversation);
|
|
348
673
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
349
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
674
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
350
675
|
behind.delete(conversation);
|
|
351
|
-
|
|
352
|
-
|
|
676
|
+
await slide(local, conversation, arriving);
|
|
677
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
678
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
function heard(event) {
|
|
683
|
+
const { conversation } = event;
|
|
684
|
+
if (event.kind === 'gap') {
|
|
685
|
+
if (replicated.has(conversation))
|
|
686
|
+
gap(conversation);
|
|
687
|
+
if (lives.has(conversation))
|
|
688
|
+
unsent.set(conversation, new Set(FACTS));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (event.kind === 'turn') {
|
|
692
|
+
relayed(conversation, event.turn, event.operation);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (event.kind === 'questions') {
|
|
696
|
+
live(conversation, { questions: structuredClone(event.questions) });
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (event.kind === 'process') {
|
|
700
|
+
live(conversation, { process: { running: event.running } });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (event.kind === 'commands') {
|
|
704
|
+
live(conversation, { commands: structuredClone(event.commands) });
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (event.kind === 'options') {
|
|
708
|
+
live(conversation, { options: structuredClone(event.options) });
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (event.kind === 'starting') {
|
|
712
|
+
live(conversation, { starting: event.step });
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (event.kind === 'link') {
|
|
716
|
+
live(conversation, { link: { connected: event.connected } });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (event.kind === 'deliveries') {
|
|
720
|
+
live(conversation, { deliveryUnknown: [...event.unknown] });
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const arriving = acceptedMessage(event.message);
|
|
724
|
+
if (arriving === null)
|
|
725
|
+
return;
|
|
726
|
+
noted(conversation, arriving);
|
|
727
|
+
if (replicated.has(conversation)) {
|
|
728
|
+
if (deps.full()) {
|
|
729
|
+
gap(conversation);
|
|
730
|
+
return;
|
|
353
731
|
}
|
|
732
|
+
void keep(conversation, arriving).catch((error) => {
|
|
733
|
+
if (refusedForSpace(error))
|
|
734
|
+
gap(conversation);
|
|
735
|
+
});
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
739
|
+
emit(conversation);
|
|
740
|
+
}
|
|
741
|
+
async function kept(local, conversation, following) {
|
|
742
|
+
if (following === null || empty(following)) {
|
|
743
|
+
await local.dropChatDraft(conversation);
|
|
744
|
+
drafts.delete(conversation);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
await local.keepChatDrafts([following]);
|
|
748
|
+
drafts.set(conversation, following);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async function sealed(local, conversation, turns) {
|
|
752
|
+
const held = drafts.get(conversation);
|
|
753
|
+
const submitted = held?.submitted ?? [];
|
|
754
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
755
|
+
if (held === undefined || left.length === submitted.length)
|
|
756
|
+
return false;
|
|
757
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
function moved(agent, operation, to) {
|
|
761
|
+
const from = newChat(agent);
|
|
762
|
+
return deps.durably(async (local) => {
|
|
763
|
+
const held = drafts.get(from);
|
|
764
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
765
|
+
if (earlier.includes(operation))
|
|
766
|
+
return;
|
|
767
|
+
const blank = {
|
|
768
|
+
conversation: from,
|
|
769
|
+
text: '',
|
|
770
|
+
revision: chatDraftRevision(),
|
|
771
|
+
};
|
|
772
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
773
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
774
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
775
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
776
|
+
drafts.set(from, left);
|
|
777
|
+
for (const one of taken)
|
|
778
|
+
drafts.set(to, one);
|
|
779
|
+
deps.publish(conversationChangeSet([from, to]));
|
|
354
780
|
});
|
|
355
781
|
}
|
|
356
782
|
function withDraft(conversation, next) {
|
|
357
783
|
return deps.durably(async (local) => {
|
|
358
|
-
const following = next(drafts.get(conversation));
|
|
784
|
+
const following = await next(drafts.get(conversation), local);
|
|
359
785
|
if (following === undefined)
|
|
360
786
|
return;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
787
|
+
await kept(local, conversation, following);
|
|
788
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async function submitted(conversation, operation, draft) {
|
|
792
|
+
let submission;
|
|
793
|
+
let earlier = false;
|
|
794
|
+
await withDraft(conversation, (current) => {
|
|
795
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
796
|
+
earlier = submission !== undefined;
|
|
797
|
+
if (submission !== undefined)
|
|
798
|
+
return undefined;
|
|
799
|
+
const said = draft ?? current;
|
|
800
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
801
|
+
return {
|
|
802
|
+
conversation,
|
|
803
|
+
...(draft === undefined || current === undefined
|
|
804
|
+
? { text: '', revision: chatDraftRevision() }
|
|
805
|
+
: { text: current.text, revision: current.revision }),
|
|
806
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
807
|
+
};
|
|
808
|
+
});
|
|
809
|
+
return { submission: submission, earlier };
|
|
810
|
+
}
|
|
811
|
+
function forgotten(conversation, submission) {
|
|
812
|
+
return withDraft(conversation, (current) => {
|
|
813
|
+
const held = current?.submitted ?? [];
|
|
814
|
+
if (current === undefined || !held.includes(submission))
|
|
815
|
+
return undefined;
|
|
816
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
370
817
|
});
|
|
371
818
|
}
|
|
819
|
+
async function dispatched(conversation, submission) {
|
|
820
|
+
const dispatch = { answered: false };
|
|
821
|
+
sending.set(submission.operation, dispatch);
|
|
822
|
+
try {
|
|
823
|
+
const result = await invoke('conversation.send', conversation, {
|
|
824
|
+
text: submission.text,
|
|
825
|
+
operation_id: submission.operation,
|
|
826
|
+
});
|
|
827
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
828
|
+
if (result.kind === 'revoked-authorization')
|
|
829
|
+
return result;
|
|
830
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
831
|
+
deps.unanswered();
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
const turn = answered(result, 'turn');
|
|
835
|
+
await withDraft(conversation, async (current, local) => {
|
|
836
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
837
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
838
|
+
return undefined;
|
|
839
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
840
|
+
if (result.kind !== 'action')
|
|
841
|
+
return returned(current, held, others);
|
|
842
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
843
|
+
return withSubmitted(current, turn === null || arrived
|
|
844
|
+
? others
|
|
845
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
846
|
+
});
|
|
847
|
+
return result;
|
|
848
|
+
}
|
|
849
|
+
finally {
|
|
850
|
+
if (sending.get(submission.operation) === dispatch)
|
|
851
|
+
sending.delete(submission.operation);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
372
854
|
function invoke(name, conversation, extra = {}) {
|
|
373
855
|
return deps.call(name, { conversation, ...extra });
|
|
374
856
|
}
|
|
375
857
|
const surface = {
|
|
376
858
|
async list() {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return entries;
|
|
859
|
+
await agentsReady();
|
|
860
|
+
return new Promise((settle) => {
|
|
861
|
+
const answer = (listed) => {
|
|
862
|
+
unlisted.delete(answer);
|
|
863
|
+
listWatchers.delete(answer);
|
|
864
|
+
settle(listed);
|
|
865
|
+
};
|
|
866
|
+
unlisted.add(answer);
|
|
867
|
+
learned();
|
|
868
|
+
});
|
|
388
869
|
},
|
|
389
870
|
async read(conversation, options = {}) {
|
|
390
|
-
const
|
|
391
|
-
return deps.durably(() =>
|
|
871
|
+
const asked = askedOf(options);
|
|
872
|
+
return deps.durably(async (local) => {
|
|
873
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
874
|
+
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
875
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
|
|
876
|
+
});
|
|
392
877
|
},
|
|
393
|
-
create(input) {
|
|
394
|
-
|
|
878
|
+
async create(input) {
|
|
879
|
+
const result = await deps.call('conversation.create', input);
|
|
880
|
+
const conversation = answered(result, 'conversation');
|
|
881
|
+
if (conversation === null)
|
|
882
|
+
return result;
|
|
883
|
+
if (!listing.has(conversation))
|
|
884
|
+
creations.add(conversation);
|
|
885
|
+
const creation = creationOf(input);
|
|
886
|
+
if (creation !== null)
|
|
887
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
888
|
+
return result;
|
|
395
889
|
},
|
|
396
|
-
async send(conversation) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
|
|
405
|
-
...(await declaredFile(file)),
|
|
406
|
-
blob: file.blob,
|
|
407
|
-
})));
|
|
408
|
-
const result = await invoke('conversation.send', conversation, {
|
|
409
|
-
text: draft?.text ?? '',
|
|
410
|
-
files,
|
|
890
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
891
|
+
if (deps.full())
|
|
892
|
+
return DURABLE_WRITE;
|
|
893
|
+
if (deps.online()) {
|
|
894
|
+
const held = await submitted(conversation, operation, draft).catch((error) => {
|
|
895
|
+
if (refusedForSpace(error))
|
|
896
|
+
return null;
|
|
897
|
+
throw error;
|
|
411
898
|
});
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return sealed ? null : { ...draft, turn };
|
|
420
|
-
});
|
|
421
|
-
return result;
|
|
899
|
+
if (held === null)
|
|
900
|
+
return DURABLE_WRITE;
|
|
901
|
+
const { submission, earlier } = held;
|
|
902
|
+
if (!earlier || !deps.expired(operation))
|
|
903
|
+
return dispatched(conversation, submission);
|
|
904
|
+
await forgotten(conversation, submission);
|
|
905
|
+
return EXPIRED;
|
|
422
906
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
907
|
+
if (draft !== undefined) {
|
|
908
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
909
|
+
? undefined
|
|
910
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
430
911
|
}
|
|
912
|
+
return OFFLINE;
|
|
431
913
|
},
|
|
432
914
|
answer(conversation, answer) {
|
|
433
915
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -442,7 +924,12 @@ export function createConversationStore(deps) {
|
|
|
442
924
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
443
925
|
if (result.kind !== 'action')
|
|
444
926
|
return result;
|
|
445
|
-
await withDraft(conversation, (current) =>
|
|
927
|
+
await withDraft(conversation, (current) => {
|
|
928
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
929
|
+
if (current === undefined || held === undefined)
|
|
930
|
+
return undefined;
|
|
931
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
932
|
+
});
|
|
446
933
|
return result;
|
|
447
934
|
},
|
|
448
935
|
delete(conversation) {
|
|
@@ -450,42 +937,65 @@ export function createConversationStore(deps) {
|
|
|
450
937
|
},
|
|
451
938
|
};
|
|
452
939
|
const chatDraft = {
|
|
453
|
-
write(conversation, draft) {
|
|
454
|
-
return withDraft(conversation, () => ({
|
|
940
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
941
|
+
return withDraft(conversation, (current) => ({
|
|
455
942
|
conversation,
|
|
456
943
|
text: draft.text,
|
|
457
|
-
|
|
458
|
-
|
|
944
|
+
revision,
|
|
945
|
+
...(current === undefined ? {} : pending(current)),
|
|
946
|
+
}));
|
|
947
|
+
},
|
|
948
|
+
prepend(conversation, draft) {
|
|
949
|
+
return withDraft(conversation, (current) => ({
|
|
950
|
+
conversation,
|
|
951
|
+
...(current === undefined ? { text: draft.text } : prepended(current, draft)),
|
|
952
|
+
revision: chatDraftRevision(),
|
|
953
|
+
...(current === undefined ? {} : pending(current)),
|
|
459
954
|
}));
|
|
460
955
|
},
|
|
461
956
|
discard(conversation) {
|
|
462
|
-
return withDraft(conversation, (current) =>
|
|
957
|
+
return withDraft(conversation, (current) => current === undefined
|
|
958
|
+
? undefined
|
|
959
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
463
960
|
},
|
|
464
961
|
};
|
|
465
962
|
return {
|
|
466
963
|
surface,
|
|
467
964
|
chatDraft,
|
|
468
|
-
hydrate(held,
|
|
469
|
-
|
|
965
|
+
hydrate(held, kept, list) {
|
|
966
|
+
if (list !== null && !listKnown) {
|
|
967
|
+
listing.clear();
|
|
968
|
+
for (const entry of list)
|
|
969
|
+
listing.set(entry.conversation, { ...entry });
|
|
970
|
+
listHeld = true;
|
|
971
|
+
}
|
|
972
|
+
stored.clear();
|
|
470
973
|
reads.clear();
|
|
471
|
-
|
|
472
|
-
view.messages = null;
|
|
974
|
+
partials.clear();
|
|
473
975
|
for (const entry of held) {
|
|
474
|
-
|
|
976
|
+
stored.add(entry.conversation);
|
|
475
977
|
if (entry.read !== undefined)
|
|
476
978
|
reads.set(entry.conversation, entry.read);
|
|
979
|
+
if (entry.partial)
|
|
980
|
+
partials.add(entry.conversation);
|
|
477
981
|
}
|
|
478
982
|
drafts.clear();
|
|
479
|
-
for (const record of
|
|
983
|
+
for (const record of kept)
|
|
480
984
|
drafts.set(record.conversation, record);
|
|
985
|
+
if (listHeld)
|
|
986
|
+
learned();
|
|
481
987
|
},
|
|
482
988
|
async reconcile() {
|
|
483
|
-
|
|
989
|
+
if (!listKnown)
|
|
990
|
+
return;
|
|
991
|
+
const named = new Set(listing.keys());
|
|
484
992
|
const leaving = [
|
|
485
|
-
...new Set([...replicated, ...
|
|
486
|
-
].filter((conversation) => !named.has(conversation)
|
|
993
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
994
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
995
|
+
!creations.has(conversation) &&
|
|
996
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
487
997
|
for (const conversation of named) {
|
|
488
|
-
if (!replicated.has(conversation))
|
|
998
|
+
if (!replicated.has(conversation) && missed(conversation))
|
|
489
999
|
behind.add(conversation);
|
|
490
1000
|
}
|
|
491
1001
|
replicated = named;
|
|
@@ -494,108 +1004,199 @@ export function createConversationStore(deps) {
|
|
|
494
1004
|
await remove(leaving);
|
|
495
1005
|
},
|
|
496
1006
|
async catchUp(progress) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
1007
|
+
if (deps.full()) {
|
|
1008
|
+
if (behind.size > 0)
|
|
1009
|
+
progress.complete = false;
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
1013
|
+
const read = async (conversations) => {
|
|
1014
|
+
if (conversations.length === 0 || deps.closed())
|
|
1015
|
+
return;
|
|
1016
|
+
const failed = await caughtUp(conversations);
|
|
500
1017
|
if (failed?.kind === 'transport-uncertainty')
|
|
501
1018
|
progress.reachable = false;
|
|
502
|
-
}
|
|
1019
|
+
};
|
|
1020
|
+
const asking = [...behind].filter(owed).sort();
|
|
1021
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
1022
|
+
await Promise.all([
|
|
1023
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
1024
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
1025
|
+
]);
|
|
503
1026
|
if (behind.size > 0)
|
|
504
1027
|
progress.complete = false;
|
|
505
1028
|
},
|
|
1029
|
+
async resend(progress) {
|
|
1030
|
+
for (const [conversation, held] of [...drafts]) {
|
|
1031
|
+
for (const submission of held.submitted ?? []) {
|
|
1032
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
1033
|
+
continue;
|
|
1034
|
+
if (deps.closed())
|
|
1035
|
+
return;
|
|
1036
|
+
if (deps.expired(submission.operation)) {
|
|
1037
|
+
await forgotten(conversation, submission);
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
const result = await dispatched(conversation, submission);
|
|
1041
|
+
if (result.kind === 'revoked-authorization')
|
|
1042
|
+
return;
|
|
1043
|
+
if (result.kind === 'transport-uncertainty') {
|
|
1044
|
+
progress.reachable = false;
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
1048
|
+
progress.complete = false;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
506
1054
|
current() {
|
|
507
1055
|
return behind.size === 0;
|
|
508
1056
|
},
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
1057
|
+
known() {
|
|
1058
|
+
return listKnown;
|
|
1059
|
+
},
|
|
1060
|
+
reopened() {
|
|
1061
|
+
for (const [operation, dispatch] of sending)
|
|
1062
|
+
if (!dispatch.answered)
|
|
1063
|
+
sending.delete(operation);
|
|
1064
|
+
},
|
|
1065
|
+
lost() {
|
|
1066
|
+
catching.clear();
|
|
512
1067
|
},
|
|
513
1068
|
watch(conversation, options, deliver) {
|
|
514
|
-
const
|
|
515
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
1069
|
+
const asked = askedOf(options);
|
|
1070
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
516
1071
|
const view = views.get(key) ?? {
|
|
517
1072
|
conversation,
|
|
518
|
-
|
|
1073
|
+
asked,
|
|
519
1074
|
watchers: new Set(),
|
|
520
|
-
|
|
1075
|
+
window: null,
|
|
1076
|
+
more: false,
|
|
1077
|
+
total: null,
|
|
521
1078
|
};
|
|
522
1079
|
views.set(key, view);
|
|
523
1080
|
const watcher = { notify: deliver, ready: false };
|
|
524
1081
|
view.watchers.add(watcher);
|
|
525
|
-
|
|
526
|
-
|
|
1082
|
+
const reading = new AbortController();
|
|
1083
|
+
void untilAnswered(() => deps.durably(async (local) => {
|
|
1084
|
+
if (view.window !== null)
|
|
1085
|
+
return;
|
|
1086
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
1087
|
+
view.window = messages;
|
|
1088
|
+
view.more = more;
|
|
1089
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1090
|
+
}), reading.signal)
|
|
527
1091
|
.then(() => {
|
|
528
1092
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
529
1093
|
return;
|
|
530
1094
|
watcher.ready = true;
|
|
531
|
-
|
|
1095
|
+
const window = view.window ?? [];
|
|
1096
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
532
1097
|
})
|
|
533
1098
|
.catch(() => { });
|
|
534
1099
|
return () => {
|
|
1100
|
+
reading.abort();
|
|
535
1101
|
if (!view.watchers.delete(watcher))
|
|
536
1102
|
return;
|
|
537
1103
|
if (view.watchers.size === 0)
|
|
538
1104
|
views.delete(key);
|
|
539
1105
|
};
|
|
540
1106
|
},
|
|
1107
|
+
watchList(deliver) {
|
|
1108
|
+
let ended = false;
|
|
1109
|
+
void agentsReady().then(() => {
|
|
1110
|
+
if (ended)
|
|
1111
|
+
return;
|
|
1112
|
+
unlisted.add(deliver);
|
|
1113
|
+
learned();
|
|
1114
|
+
});
|
|
1115
|
+
return () => {
|
|
1116
|
+
ended = true;
|
|
1117
|
+
unlisted.delete(deliver);
|
|
1118
|
+
listWatchers.delete(deliver);
|
|
1119
|
+
};
|
|
1120
|
+
},
|
|
1121
|
+
watchAgents(deliver) {
|
|
1122
|
+
unnamed.add(deliver);
|
|
1123
|
+
void agentsReady().then(learned);
|
|
1124
|
+
return () => {
|
|
1125
|
+
unnamed.delete(deliver);
|
|
1126
|
+
agentWatchers.delete(deliver);
|
|
1127
|
+
};
|
|
1128
|
+
},
|
|
1129
|
+
learned,
|
|
1130
|
+
declared() {
|
|
1131
|
+
if (JSON.stringify(deps.agents()) === agentsFrom)
|
|
1132
|
+
return;
|
|
1133
|
+
agentsRead = readAgents().catch(() => {
|
|
1134
|
+
agentsRead = null;
|
|
1135
|
+
});
|
|
1136
|
+
},
|
|
541
1137
|
changed(set) {
|
|
542
1138
|
for (const conversation of set.conversations)
|
|
543
1139
|
emit(conversation);
|
|
1140
|
+
const declaration = deps.agents();
|
|
1141
|
+
if (declaration !== undefined &&
|
|
1142
|
+
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
1143
|
+
agentsRead = readAgents().catch(() => {
|
|
1144
|
+
agentsRead = null;
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
544
1147
|
},
|
|
545
1148
|
receive(event) {
|
|
546
1149
|
if (deps.closed())
|
|
547
|
-
return;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const arriving = acceptedMessage(event.message);
|
|
580
|
-
if (arriving === null)
|
|
581
|
-
return;
|
|
582
|
-
if (replicated.has(conversation)) {
|
|
583
|
-
void keep(conversation, arriving).catch(() => { });
|
|
584
|
-
return;
|
|
1150
|
+
return false;
|
|
1151
|
+
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
1152
|
+
if (event.kind === 'list') {
|
|
1153
|
+
listing.clear();
|
|
1154
|
+
for (const entry of event.entries) {
|
|
1155
|
+
listing.set(entry.conversation, { ...entry });
|
|
1156
|
+
creations.delete(entry.conversation);
|
|
1157
|
+
}
|
|
1158
|
+
listKnown = true;
|
|
1159
|
+
for (const conversation of replicated) {
|
|
1160
|
+
if (!listing.has(conversation))
|
|
1161
|
+
continue;
|
|
1162
|
+
if (!missed(conversation))
|
|
1163
|
+
behind.delete(conversation);
|
|
1164
|
+
else if (!behind.has(conversation))
|
|
1165
|
+
gap(conversation);
|
|
1166
|
+
}
|
|
1167
|
+
openingEnded();
|
|
1168
|
+
}
|
|
1169
|
+
else if (event.kind === 'listed') {
|
|
1170
|
+
listing.set(event.entry.conversation, { ...event.entry });
|
|
1171
|
+
creations.delete(event.entry.conversation);
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
listing.delete(event.conversation);
|
|
1175
|
+
}
|
|
1176
|
+
keepList();
|
|
1177
|
+
emitList();
|
|
1178
|
+
return (event.kind !== 'list' &&
|
|
1179
|
+
listKnown &&
|
|
1180
|
+
(listing.size !== replicated.size ||
|
|
1181
|
+
[...listing.keys()].some((conversation) => !replicated.has(conversation))));
|
|
585
1182
|
}
|
|
586
|
-
|
|
587
|
-
|
|
1183
|
+
heard(event);
|
|
1184
|
+
return false;
|
|
588
1185
|
},
|
|
589
|
-
|
|
590
|
-
return [...
|
|
1186
|
+
searchable() {
|
|
1187
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
591
1188
|
},
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
1189
|
+
acquired(conversation) {
|
|
1190
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
595
1191
|
},
|
|
596
1192
|
close() {
|
|
597
1193
|
views.clear();
|
|
598
1194
|
lives.clear();
|
|
1195
|
+
unsent.clear();
|
|
1196
|
+
listWatchers.clear();
|
|
1197
|
+
unlisted.clear();
|
|
1198
|
+
agentWatchers.clear();
|
|
1199
|
+
unnamed.clear();
|
|
599
1200
|
},
|
|
600
1201
|
};
|
|
601
1202
|
}
|