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