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