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