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