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