@agentshouse/mdruntime 0.1.0-c7274d650bd05 → 0.1.0-c73e437bf1616
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 +122 -31
- 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 +38 -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 +33 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +198 -48
- 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 +33 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +123 -21
- package/dist/collection.d.ts +12 -14
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +52 -56
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +773 -0
- package/dist/connection.d.ts +60 -20
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3632 -811
- package/dist/conversation.d.ts +44 -15
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +693 -193
- 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 +4 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -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 -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 +370 -0
- package/dist/host.d.ts +14 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +137 -23
- 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 +9 -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 +218 -0
- package/dist/memory.d.ts +13 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +136 -34
- 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 +12 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +8 -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/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 +72 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +81 -1
- package/dist/overlay.d.ts +15 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +136 -180
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/query.d.ts +70 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +51 -7
- package/dist/reach.d.ts +18 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +245 -67
- package/dist/react.d.ts +12 -8
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +8 -17
- package/dist/readiness.d.ts +52 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +91 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +224 -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 +58 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +319 -157
- package/dist/room-draft.d.ts +57 -10
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +222 -39
- package/dist/rows.d.ts +39 -35
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +176 -99
- package/dist/save.d.ts +73 -4
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +825 -150
- 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 +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/status.d.ts +7 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -9
- package/dist/store.d.ts +109 -62
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +322 -354
- package/dist/stored.d.ts +130 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +560 -0
- package/dist/tables.d.ts +325 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +578 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- 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 +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -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 +172 -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 +156 -0
- package/package.json +26 -15
- package/AGENTS.md +0 -253
- 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 {
|
|
2
|
+
import { runtimeError } from './failure.js';
|
|
3
|
+
import { refusedForSpace } from './durability.js';
|
|
4
|
+
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
5
|
+
import { OFFLINE } from './outcome.js';
|
|
6
|
+
import { untilAnswered } from './recovery.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,162 @@ 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;
|
|
193
|
+
}
|
|
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
|
+
};
|
|
149
228
|
}
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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();
|
|
261
|
+
const unlisted = new Set();
|
|
170
262
|
let listKnown = false;
|
|
263
|
+
let listHeld = false;
|
|
264
|
+
let listKept = false;
|
|
265
|
+
const agentWatchers = new Set();
|
|
266
|
+
const unnamed = new Set();
|
|
267
|
+
let agentsKnown = false;
|
|
171
268
|
let agentNames = new Map();
|
|
269
|
+
let retiredAgents = new Set();
|
|
172
270
|
let agentsRead = null;
|
|
271
|
+
let agentsFrom;
|
|
173
272
|
let replicated = new Set();
|
|
174
273
|
let handedReplicated;
|
|
175
274
|
function liveOf(conversation) {
|
|
176
275
|
return lives.get(conversation) ?? QUIET;
|
|
177
276
|
}
|
|
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)) {
|
|
277
|
+
function viewOf(conversation, held, frame) {
|
|
189
278
|
const draft = drafts.get(conversation);
|
|
190
279
|
const facts = liveOf(conversation);
|
|
191
280
|
return {
|
|
192
281
|
conversation,
|
|
193
|
-
messages:
|
|
282
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
283
|
+
identity,
|
|
284
|
+
turn,
|
|
285
|
+
text,
|
|
286
|
+
message,
|
|
287
|
+
})),
|
|
288
|
+
...frame,
|
|
194
289
|
turn: structuredClone(facts.turn),
|
|
195
290
|
questions: structuredClone(facts.questions),
|
|
196
291
|
process: facts.process === null ? null : { ...facts.process },
|
|
197
292
|
commands: structuredClone(facts.commands),
|
|
198
293
|
options: structuredClone(facts.options),
|
|
199
|
-
|
|
294
|
+
starting: facts.starting,
|
|
295
|
+
link: facts.link === null ? null : { ...facts.link },
|
|
296
|
+
deliveryUnknown: [...facts.deliveryUnknown],
|
|
297
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
200
298
|
? null
|
|
201
|
-
: { text: draft.text,
|
|
299
|
+
: { text: draft.text, revision: draft.revision },
|
|
202
300
|
};
|
|
203
301
|
}
|
|
204
|
-
function emit(conversation
|
|
302
|
+
function emit(conversation) {
|
|
205
303
|
if (deps.closed())
|
|
206
304
|
return;
|
|
207
305
|
for (const view of views.values()) {
|
|
208
|
-
if (view.conversation !== conversation)
|
|
306
|
+
if (view.conversation !== conversation || view.window === null)
|
|
209
307
|
continue;
|
|
210
|
-
if (messagesChanged)
|
|
211
|
-
view.messages = null;
|
|
212
308
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
213
309
|
if (ready.length === 0)
|
|
214
310
|
continue;
|
|
215
|
-
|
|
216
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
311
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
|
|
217
312
|
for (const watcher of ready)
|
|
218
313
|
watcher.notify(value);
|
|
219
314
|
}
|
|
220
315
|
}
|
|
316
|
+
function sharedFrame(view, window) {
|
|
317
|
+
return frameOf(view.asked, window, view.more, view.total);
|
|
318
|
+
}
|
|
319
|
+
async function slide(local, conversation, arriving) {
|
|
320
|
+
let count;
|
|
321
|
+
for (const view of views.values()) {
|
|
322
|
+
if (view.conversation !== conversation || view.window === null)
|
|
323
|
+
continue;
|
|
324
|
+
if (arriving === null) {
|
|
325
|
+
view.window = [];
|
|
326
|
+
view.more = false;
|
|
327
|
+
if (view.total !== null)
|
|
328
|
+
view.total = 0;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const joining = arriving.filter((message) => precedes(message, view.asked.before));
|
|
332
|
+
const next = windowOf(view.window, joining, view.asked.limit);
|
|
333
|
+
view.window = next.messages;
|
|
334
|
+
view.more ||= next.dropped;
|
|
335
|
+
if (view.total !== null && arriving.length > 0) {
|
|
336
|
+
count ??= await local.messageCount(conversation);
|
|
337
|
+
view.total = count;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
221
341
|
function stateOf(conversation) {
|
|
222
342
|
const facts = liveOf(conversation);
|
|
223
343
|
if (facts.questions.length > 0)
|
|
@@ -232,15 +352,72 @@ export function createConversationStore(deps) {
|
|
|
232
352
|
created: entry.created,
|
|
233
353
|
finished: entry.finished,
|
|
234
354
|
outcome: entry.outcome,
|
|
355
|
+
waiting: entry.waiting,
|
|
235
356
|
state: stateOf(entry.conversation),
|
|
236
357
|
started: lives.get(entry.conversation)?.turn?.started ?? null,
|
|
237
358
|
}));
|
|
238
359
|
}
|
|
360
|
+
function keepList() {
|
|
361
|
+
if (!listKnown || listKept)
|
|
362
|
+
return;
|
|
363
|
+
listKept = true;
|
|
364
|
+
queueMicrotask(() => {
|
|
365
|
+
listKept = false;
|
|
366
|
+
if (deps.closed())
|
|
367
|
+
return;
|
|
368
|
+
const entries = [...listing.values()];
|
|
369
|
+
void deps.durably((local) => local.fact('conversationList', entries)).catch(() => { });
|
|
370
|
+
});
|
|
371
|
+
}
|
|
239
372
|
function emitList() {
|
|
240
|
-
if (deps.closed()
|
|
373
|
+
if (deps.closed())
|
|
241
374
|
return;
|
|
242
|
-
|
|
243
|
-
|
|
375
|
+
if (listWatchers.size > 0) {
|
|
376
|
+
const value = entries();
|
|
377
|
+
for (const watcher of listWatchers)
|
|
378
|
+
watcher(value);
|
|
379
|
+
}
|
|
380
|
+
learned();
|
|
381
|
+
}
|
|
382
|
+
function unlearnable() {
|
|
383
|
+
return !deps.online() || (deps.listed() && deps.agents() === undefined);
|
|
384
|
+
}
|
|
385
|
+
function learned() {
|
|
386
|
+
if (deps.closed())
|
|
387
|
+
return;
|
|
388
|
+
const named = agentsKnown || unlearnable();
|
|
389
|
+
if (unlisted.size > 0 &&
|
|
390
|
+
named &&
|
|
391
|
+
(listKnown || listHeld || deps.stream() === undefined || unlearnable())) {
|
|
392
|
+
const value = entries();
|
|
393
|
+
for (const watcher of [...unlisted]) {
|
|
394
|
+
unlisted.delete(watcher);
|
|
395
|
+
listWatchers.add(watcher);
|
|
396
|
+
watcher(value);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (unnamed.size > 0 && named) {
|
|
400
|
+
const value = agents();
|
|
401
|
+
for (const watcher of [...unnamed]) {
|
|
402
|
+
unnamed.delete(watcher);
|
|
403
|
+
agentWatchers.add(watcher);
|
|
404
|
+
watcher(value);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function agents() {
|
|
409
|
+
return [...agentNames]
|
|
410
|
+
.filter(([identity]) => !retiredAgents.has(identity))
|
|
411
|
+
.map(([identity, name]) => ({ identity, name }))
|
|
412
|
+
.sort((left, right) => left.name === right.name
|
|
413
|
+
? left.identity.localeCompare(right.identity)
|
|
414
|
+
: left.name.localeCompare(right.name));
|
|
415
|
+
}
|
|
416
|
+
function emitAgents() {
|
|
417
|
+
if (deps.closed() || agentWatchers.size === 0)
|
|
418
|
+
return;
|
|
419
|
+
const value = agents();
|
|
420
|
+
for (const watcher of agentWatchers)
|
|
244
421
|
watcher(value);
|
|
245
422
|
}
|
|
246
423
|
function setLive(conversation, next) {
|
|
@@ -257,22 +434,52 @@ export function createConversationStore(deps) {
|
|
|
257
434
|
}
|
|
258
435
|
async function readAgents() {
|
|
259
436
|
const declaration = deps.agents();
|
|
437
|
+
agentsFrom = JSON.stringify(declaration);
|
|
260
438
|
if (declaration === undefined)
|
|
261
439
|
return;
|
|
262
|
-
const { documents } = await deps.documents(declaration.locator);
|
|
263
440
|
const names = new Map();
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
441
|
+
const retired = new Set();
|
|
442
|
+
let covered = null;
|
|
443
|
+
for (let after;;) {
|
|
444
|
+
const read = await deps.documents(declaration, after);
|
|
445
|
+
if ('pending' in read)
|
|
446
|
+
return;
|
|
447
|
+
const coverage = JSON.stringify(read.coverage);
|
|
448
|
+
if (covered !== null && coverage !== covered)
|
|
449
|
+
return readAgents();
|
|
450
|
+
covered = coverage;
|
|
451
|
+
for (const document of read.documents) {
|
|
452
|
+
const identity = document.fields?.[declaration.identity];
|
|
453
|
+
const name = document.fields?.[declaration.name];
|
|
454
|
+
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
455
|
+
continue;
|
|
270
456
|
names.set(identity, name);
|
|
457
|
+
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
458
|
+
if (ended !== undefined && ended !== null)
|
|
459
|
+
retired.add(identity);
|
|
460
|
+
}
|
|
461
|
+
if (read.next === undefined)
|
|
462
|
+
break;
|
|
463
|
+
after = read.next;
|
|
271
464
|
}
|
|
272
|
-
|
|
465
|
+
agentsKnown = true;
|
|
466
|
+
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
467
|
+
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents])) {
|
|
468
|
+
learned();
|
|
273
469
|
return;
|
|
470
|
+
}
|
|
274
471
|
agentNames = names;
|
|
275
|
-
|
|
472
|
+
retiredAgents = retired;
|
|
473
|
+
emitAgents();
|
|
474
|
+
if (named)
|
|
475
|
+
emitList();
|
|
476
|
+
learned();
|
|
477
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
478
|
+
if (ended.length > 0)
|
|
479
|
+
await remove(ended);
|
|
480
|
+
}
|
|
481
|
+
function retiredChat(conversation) {
|
|
482
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
276
483
|
}
|
|
277
484
|
function agentsReady() {
|
|
278
485
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -281,8 +488,33 @@ export function createConversationStore(deps) {
|
|
|
281
488
|
return agentsRead;
|
|
282
489
|
}
|
|
283
490
|
function live(conversation, change) {
|
|
491
|
+
const owed = unsent.get(conversation);
|
|
492
|
+
for (const fact of FACTS.filter((one) => one in change))
|
|
493
|
+
owed?.delete(fact);
|
|
284
494
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
285
|
-
emit(conversation
|
|
495
|
+
emit(conversation);
|
|
496
|
+
}
|
|
497
|
+
function openingEnded() {
|
|
498
|
+
const owed = [...unsent];
|
|
499
|
+
unsent.clear();
|
|
500
|
+
for (const [conversation, facts] of owed) {
|
|
501
|
+
const held = lives.get(conversation);
|
|
502
|
+
if (held === undefined || facts.size === 0)
|
|
503
|
+
continue;
|
|
504
|
+
setLive(conversation, {
|
|
505
|
+
turn: held.turn,
|
|
506
|
+
questions: facts.has('questions') ? QUIET.questions : held.questions,
|
|
507
|
+
process: facts.has('process') ? QUIET.process : held.process,
|
|
508
|
+
commands: facts.has('commands') ? QUIET.commands : held.commands,
|
|
509
|
+
options: facts.has('options') ? QUIET.options : held.options,
|
|
510
|
+
starting: facts.has('starting') ? QUIET.starting : held.starting,
|
|
511
|
+
link: facts.has('link') ? QUIET.link : held.link,
|
|
512
|
+
deliveryUnknown: facts.has('deliveryUnknown')
|
|
513
|
+
? QUIET.deliveryUnknown
|
|
514
|
+
: held.deliveryUnknown,
|
|
515
|
+
});
|
|
516
|
+
emit(conversation);
|
|
517
|
+
}
|
|
286
518
|
}
|
|
287
519
|
function relayed(conversation, turn, operation) {
|
|
288
520
|
const held = liveOf(conversation).turn;
|
|
@@ -311,25 +543,38 @@ export function createConversationStore(deps) {
|
|
|
311
543
|
async function remove(leaving) {
|
|
312
544
|
await deps.durably(async (local) => {
|
|
313
545
|
await local.dropConversations(leaving);
|
|
314
|
-
const removed = [];
|
|
315
546
|
const changed = [];
|
|
316
547
|
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)) {
|
|
548
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
321
549
|
changed.push(conversation);
|
|
322
550
|
}
|
|
323
|
-
|
|
551
|
+
stored.delete(conversation);
|
|
324
552
|
reads.delete(conversation);
|
|
553
|
+
partials.delete(conversation);
|
|
325
554
|
drafts.delete(conversation);
|
|
326
555
|
behind.delete(conversation);
|
|
556
|
+
unsent.delete(conversation);
|
|
327
557
|
setLive(conversation, null);
|
|
558
|
+
await slide(local, conversation, null);
|
|
328
559
|
}
|
|
329
560
|
if (changed.length > 0)
|
|
330
|
-
deps.publish(conversationChangeSet(
|
|
561
|
+
deps.publish(conversationChangeSet(changed));
|
|
331
562
|
});
|
|
332
563
|
}
|
|
564
|
+
function missed(conversation) {
|
|
565
|
+
const read = reads.get(conversation);
|
|
566
|
+
const newest = listing.get(conversation)?.newest;
|
|
567
|
+
return (read === undefined ||
|
|
568
|
+
newest === undefined ||
|
|
569
|
+
partials.has(conversation) ||
|
|
570
|
+
(newest !== null && (read === null || newest > read)));
|
|
571
|
+
}
|
|
572
|
+
function noted(conversation, message) {
|
|
573
|
+
const entry = listing.get(conversation);
|
|
574
|
+
if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
|
|
575
|
+
return;
|
|
576
|
+
listing.set(conversation, { ...entry, newest: message.position });
|
|
577
|
+
}
|
|
333
578
|
function gap(conversation) {
|
|
334
579
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
335
580
|
behind.add(conversation);
|
|
@@ -342,32 +587,28 @@ export function createConversationStore(deps) {
|
|
|
342
587
|
const advances = read !== undefined &&
|
|
343
588
|
!behind.has(conversation) &&
|
|
344
589
|
(read === null || message.position > read);
|
|
345
|
-
|
|
590
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
346
591
|
await local.keepMessages({
|
|
347
592
|
conversation,
|
|
348
593
|
messages: [message],
|
|
349
|
-
...(advances ? { read: message.position } : {}),
|
|
350
|
-
sealed,
|
|
594
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
351
595
|
});
|
|
352
|
-
|
|
596
|
+
stored.add(conversation);
|
|
353
597
|
if (advances)
|
|
354
598
|
reads.set(conversation, message.position);
|
|
355
|
-
if (sealed)
|
|
356
|
-
drafts.delete(conversation);
|
|
357
599
|
sealTurns(conversation, new Set([message.turn]));
|
|
358
|
-
|
|
600
|
+
await slide(local, conversation, [message]);
|
|
601
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
359
602
|
});
|
|
360
603
|
}
|
|
361
|
-
async function readHistories(conversations) {
|
|
604
|
+
async function readHistories(conversations, ended) {
|
|
362
605
|
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) => ({
|
|
606
|
+
let waiting = conversations.map((conversation) => ({
|
|
367
607
|
conversation,
|
|
368
|
-
after:
|
|
608
|
+
after: reads.get(conversation) ?? undefined,
|
|
369
609
|
}));
|
|
370
|
-
while (
|
|
610
|
+
while (waiting.length > 0) {
|
|
611
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
371
612
|
const result = await deps.call('conversation.history', {
|
|
372
613
|
conversations: asking.map(({ conversation, after }) => ({
|
|
373
614
|
conversation,
|
|
@@ -377,46 +618,64 @@ export function createConversationStore(deps) {
|
|
|
377
618
|
const pages = historyPages(result);
|
|
378
619
|
if (pages === null)
|
|
379
620
|
return result;
|
|
380
|
-
const asked = new
|
|
621
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
381
622
|
const following = [];
|
|
382
623
|
for (const page of pages) {
|
|
383
624
|
if (!asked.has(page.conversation))
|
|
384
625
|
continue;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
...page.messages,
|
|
388
|
-
]);
|
|
389
|
-
if (page.next === undefined)
|
|
390
|
-
complete.add(page.conversation);
|
|
391
|
-
else
|
|
626
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
627
|
+
if (page.next !== undefined) {
|
|
392
628
|
following.push({ conversation: page.conversation, after: page.next });
|
|
629
|
+
}
|
|
393
630
|
}
|
|
394
|
-
asking
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
631
|
+
for (const { conversation } of asking) {
|
|
632
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
633
|
+
ended(conversation);
|
|
634
|
+
}
|
|
635
|
+
waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
|
|
400
636
|
}
|
|
401
637
|
return null;
|
|
402
638
|
}
|
|
403
|
-
function
|
|
639
|
+
function caughtUp(conversations) {
|
|
640
|
+
const ends = new Map();
|
|
641
|
+
for (const conversation of conversations) {
|
|
642
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
643
|
+
catching.set(conversation, held);
|
|
644
|
+
void held.then(() => {
|
|
645
|
+
if (catching.get(conversation) === held)
|
|
646
|
+
catching.delete(conversation);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
650
|
+
const read = readHistories(conversations, ended);
|
|
651
|
+
const endAll = () => {
|
|
652
|
+
for (const conversation of conversations)
|
|
653
|
+
ended(conversation);
|
|
654
|
+
};
|
|
655
|
+
read.then(endAll, endAll);
|
|
656
|
+
return read;
|
|
657
|
+
}
|
|
658
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
404
659
|
return deps.durably(async (local) => {
|
|
405
660
|
if (!replicated.has(conversation))
|
|
406
661
|
return;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
await local
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
662
|
+
const whole = next === undefined;
|
|
663
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
664
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
665
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
666
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
667
|
+
stored.add(conversation);
|
|
668
|
+
reads.set(conversation, cursor);
|
|
669
|
+
if (partial)
|
|
670
|
+
partials.add(conversation);
|
|
671
|
+
else
|
|
672
|
+
partials.delete(conversation);
|
|
415
673
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
416
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
674
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
417
675
|
behind.delete(conversation);
|
|
418
|
-
|
|
419
|
-
|
|
676
|
+
await slide(local, conversation, arriving);
|
|
677
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
678
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
420
679
|
}
|
|
421
680
|
});
|
|
422
681
|
}
|
|
@@ -425,11 +684,8 @@ export function createConversationStore(deps) {
|
|
|
425
684
|
if (event.kind === 'gap') {
|
|
426
685
|
if (replicated.has(conversation))
|
|
427
686
|
gap(conversation);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return;
|
|
431
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
432
|
-
emit(conversation, false);
|
|
687
|
+
if (lives.has(conversation))
|
|
688
|
+
unsent.set(conversation, new Set(FACTS));
|
|
433
689
|
return;
|
|
434
690
|
}
|
|
435
691
|
if (event.kind === 'turn') {
|
|
@@ -452,31 +708,148 @@ export function createConversationStore(deps) {
|
|
|
452
708
|
live(conversation, { options: structuredClone(event.options) });
|
|
453
709
|
return;
|
|
454
710
|
}
|
|
711
|
+
if (event.kind === 'starting') {
|
|
712
|
+
live(conversation, { starting: event.step });
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (event.kind === 'link') {
|
|
716
|
+
live(conversation, { link: { connected: event.connected } });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (event.kind === 'deliveries') {
|
|
720
|
+
live(conversation, { deliveryUnknown: [...event.unknown] });
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
455
723
|
const arriving = acceptedMessage(event.message);
|
|
456
724
|
if (arriving === null)
|
|
457
725
|
return;
|
|
726
|
+
noted(conversation, arriving);
|
|
458
727
|
if (replicated.has(conversation)) {
|
|
459
|
-
|
|
728
|
+
if (deps.full()) {
|
|
729
|
+
gap(conversation);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
void keep(conversation, arriving).catch((error) => {
|
|
733
|
+
if (refusedForSpace(error))
|
|
734
|
+
gap(conversation);
|
|
735
|
+
});
|
|
460
736
|
return;
|
|
461
737
|
}
|
|
462
738
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
463
739
|
emit(conversation);
|
|
464
740
|
}
|
|
741
|
+
async function kept(local, conversation, following) {
|
|
742
|
+
if (following === null || empty(following)) {
|
|
743
|
+
await local.dropChatDraft(conversation);
|
|
744
|
+
drafts.delete(conversation);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
await local.keepChatDrafts([following]);
|
|
748
|
+
drafts.set(conversation, following);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async function sealed(local, conversation, turns) {
|
|
752
|
+
const held = drafts.get(conversation);
|
|
753
|
+
const submitted = held?.submitted ?? [];
|
|
754
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
755
|
+
if (held === undefined || left.length === submitted.length)
|
|
756
|
+
return false;
|
|
757
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
function moved(agent, operation, to) {
|
|
761
|
+
const from = newChat(agent);
|
|
762
|
+
return deps.durably(async (local) => {
|
|
763
|
+
const held = drafts.get(from);
|
|
764
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
765
|
+
if (earlier.includes(operation))
|
|
766
|
+
return;
|
|
767
|
+
const blank = {
|
|
768
|
+
conversation: from,
|
|
769
|
+
text: '',
|
|
770
|
+
revision: chatDraftRevision(),
|
|
771
|
+
};
|
|
772
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
773
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
774
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
775
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
776
|
+
drafts.set(from, left);
|
|
777
|
+
for (const one of taken)
|
|
778
|
+
drafts.set(to, one);
|
|
779
|
+
deps.publish(conversationChangeSet([from, to]));
|
|
780
|
+
});
|
|
781
|
+
}
|
|
465
782
|
function withDraft(conversation, next) {
|
|
466
783
|
return deps.durably(async (local) => {
|
|
467
|
-
const following = next(drafts.get(conversation));
|
|
784
|
+
const following = await next(drafts.get(conversation), local);
|
|
468
785
|
if (following === undefined)
|
|
469
786
|
return;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
787
|
+
await kept(local, conversation, following);
|
|
788
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async function submitted(conversation, operation, draft) {
|
|
792
|
+
let submission;
|
|
793
|
+
let earlier = false;
|
|
794
|
+
await withDraft(conversation, (current) => {
|
|
795
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
796
|
+
earlier = submission !== undefined;
|
|
797
|
+
if (submission !== undefined)
|
|
798
|
+
return undefined;
|
|
799
|
+
const said = draft ?? current;
|
|
800
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
801
|
+
return {
|
|
802
|
+
conversation,
|
|
803
|
+
...(draft === undefined || current === undefined
|
|
804
|
+
? { text: '', revision: chatDraftRevision() }
|
|
805
|
+
: { text: current.text, revision: current.revision }),
|
|
806
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
807
|
+
};
|
|
479
808
|
});
|
|
809
|
+
return { submission: submission, earlier };
|
|
810
|
+
}
|
|
811
|
+
function forgotten(conversation, submission) {
|
|
812
|
+
return withDraft(conversation, (current) => {
|
|
813
|
+
const held = current?.submitted ?? [];
|
|
814
|
+
if (current === undefined || !held.includes(submission))
|
|
815
|
+
return undefined;
|
|
816
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
async function dispatched(conversation, submission) {
|
|
820
|
+
const dispatch = { answered: false };
|
|
821
|
+
sending.set(submission.operation, dispatch);
|
|
822
|
+
try {
|
|
823
|
+
const result = await invoke('conversation.send', conversation, {
|
|
824
|
+
text: submission.text,
|
|
825
|
+
operation_id: submission.operation,
|
|
826
|
+
});
|
|
827
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
828
|
+
if (result.kind === 'revoked-authorization')
|
|
829
|
+
return result;
|
|
830
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
831
|
+
deps.unanswered();
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
const turn = answered(result, 'turn');
|
|
835
|
+
await withDraft(conversation, async (current, local) => {
|
|
836
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
837
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
838
|
+
return undefined;
|
|
839
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
840
|
+
if (result.kind !== 'action')
|
|
841
|
+
return returned(current, held, others);
|
|
842
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
843
|
+
return withSubmitted(current, turn === null || arrived
|
|
844
|
+
? others
|
|
845
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
846
|
+
});
|
|
847
|
+
return result;
|
|
848
|
+
}
|
|
849
|
+
finally {
|
|
850
|
+
if (sending.get(submission.operation) === dispatch)
|
|
851
|
+
sending.delete(submission.operation);
|
|
852
|
+
}
|
|
480
853
|
}
|
|
481
854
|
function invoke(name, conversation, extra = {}) {
|
|
482
855
|
return deps.call(name, { conversation, ...extra });
|
|
@@ -484,50 +857,59 @@ export function createConversationStore(deps) {
|
|
|
484
857
|
const surface = {
|
|
485
858
|
async list() {
|
|
486
859
|
await agentsReady();
|
|
487
|
-
return
|
|
860
|
+
return new Promise((settle) => {
|
|
861
|
+
const answer = (listed) => {
|
|
862
|
+
unlisted.delete(answer);
|
|
863
|
+
listWatchers.delete(answer);
|
|
864
|
+
settle(listed);
|
|
865
|
+
};
|
|
866
|
+
unlisted.add(answer);
|
|
867
|
+
learned();
|
|
868
|
+
});
|
|
488
869
|
},
|
|
489
870
|
async read(conversation, options = {}) {
|
|
490
|
-
const
|
|
491
|
-
return deps.durably(() =>
|
|
871
|
+
const asked = askedOf(options);
|
|
872
|
+
return deps.durably(async (local) => {
|
|
873
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
874
|
+
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
875
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
|
|
876
|
+
});
|
|
492
877
|
},
|
|
493
|
-
create(input) {
|
|
494
|
-
|
|
878
|
+
async create(input) {
|
|
879
|
+
const result = await deps.call('conversation.create', input);
|
|
880
|
+
const conversation = answered(result, 'conversation');
|
|
881
|
+
if (conversation === null)
|
|
882
|
+
return result;
|
|
883
|
+
if (!listing.has(conversation))
|
|
884
|
+
creations.add(conversation);
|
|
885
|
+
const creation = creationOf(input);
|
|
886
|
+
if (creation !== null)
|
|
887
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
888
|
+
return result;
|
|
495
889
|
},
|
|
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 };
|
|
890
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
891
|
+
if (deps.full())
|
|
892
|
+
return DURABLE_WRITE;
|
|
893
|
+
if (deps.online()) {
|
|
894
|
+
const held = await submitted(conversation, operation, draft).catch((error) => {
|
|
895
|
+
if (refusedForSpace(error))
|
|
896
|
+
return null;
|
|
897
|
+
throw error;
|
|
520
898
|
});
|
|
521
|
-
|
|
899
|
+
if (held === null)
|
|
900
|
+
return DURABLE_WRITE;
|
|
901
|
+
const { submission, earlier } = held;
|
|
902
|
+
if (!earlier || !deps.expired(operation))
|
|
903
|
+
return dispatched(conversation, submission);
|
|
904
|
+
await forgotten(conversation, submission);
|
|
905
|
+
return EXPIRED;
|
|
522
906
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
907
|
+
if (draft !== undefined) {
|
|
908
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
909
|
+
? undefined
|
|
910
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
530
911
|
}
|
|
912
|
+
return OFFLINE;
|
|
531
913
|
},
|
|
532
914
|
answer(conversation, answer) {
|
|
533
915
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -542,7 +924,12 @@ export function createConversationStore(deps) {
|
|
|
542
924
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
543
925
|
if (result.kind !== 'action')
|
|
544
926
|
return result;
|
|
545
|
-
await withDraft(conversation, (current) =>
|
|
927
|
+
await withDraft(conversation, (current) => {
|
|
928
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
929
|
+
if (current === undefined || held === undefined)
|
|
930
|
+
return undefined;
|
|
931
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
932
|
+
});
|
|
546
933
|
return result;
|
|
547
934
|
},
|
|
548
935
|
delete(conversation) {
|
|
@@ -550,44 +937,65 @@ export function createConversationStore(deps) {
|
|
|
550
937
|
},
|
|
551
938
|
};
|
|
552
939
|
const chatDraft = {
|
|
553
|
-
write(conversation, draft) {
|
|
554
|
-
return withDraft(conversation, () => ({
|
|
940
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
941
|
+
return withDraft(conversation, (current) => ({
|
|
555
942
|
conversation,
|
|
556
943
|
text: draft.text,
|
|
557
|
-
|
|
558
|
-
|
|
944
|
+
revision,
|
|
945
|
+
...(current === undefined ? {} : pending(current)),
|
|
946
|
+
}));
|
|
947
|
+
},
|
|
948
|
+
prepend(conversation, draft) {
|
|
949
|
+
return withDraft(conversation, (current) => ({
|
|
950
|
+
conversation,
|
|
951
|
+
...(current === undefined ? { text: draft.text } : prepended(current, draft)),
|
|
952
|
+
revision: chatDraftRevision(),
|
|
953
|
+
...(current === undefined ? {} : pending(current)),
|
|
559
954
|
}));
|
|
560
955
|
},
|
|
561
956
|
discard(conversation) {
|
|
562
|
-
return withDraft(conversation, (current) =>
|
|
957
|
+
return withDraft(conversation, (current) => current === undefined
|
|
958
|
+
? undefined
|
|
959
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
563
960
|
},
|
|
564
961
|
};
|
|
565
962
|
return {
|
|
566
963
|
surface,
|
|
567
964
|
chatDraft,
|
|
568
|
-
hydrate(held,
|
|
569
|
-
|
|
965
|
+
hydrate(held, kept, list) {
|
|
966
|
+
if (list !== null && !listKnown) {
|
|
967
|
+
listing.clear();
|
|
968
|
+
for (const entry of list)
|
|
969
|
+
listing.set(entry.conversation, { ...entry });
|
|
970
|
+
listHeld = true;
|
|
971
|
+
}
|
|
972
|
+
stored.clear();
|
|
570
973
|
reads.clear();
|
|
571
|
-
|
|
572
|
-
view.messages = null;
|
|
974
|
+
partials.clear();
|
|
573
975
|
for (const entry of held) {
|
|
574
|
-
|
|
976
|
+
stored.add(entry.conversation);
|
|
575
977
|
if (entry.read !== undefined)
|
|
576
978
|
reads.set(entry.conversation, entry.read);
|
|
979
|
+
if (entry.partial)
|
|
980
|
+
partials.add(entry.conversation);
|
|
577
981
|
}
|
|
578
982
|
drafts.clear();
|
|
579
|
-
for (const record of
|
|
983
|
+
for (const record of kept)
|
|
580
984
|
drafts.set(record.conversation, record);
|
|
985
|
+
if (listHeld)
|
|
986
|
+
learned();
|
|
581
987
|
},
|
|
582
988
|
async reconcile() {
|
|
583
989
|
if (!listKnown)
|
|
584
990
|
return;
|
|
585
991
|
const named = new Set(listing.keys());
|
|
586
992
|
const leaving = [
|
|
587
|
-
...new Set([...replicated, ...
|
|
588
|
-
].filter((conversation) => !named.has(conversation)
|
|
993
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
994
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
995
|
+
!creations.has(conversation) &&
|
|
996
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
589
997
|
for (const conversation of named) {
|
|
590
|
-
if (!replicated.has(conversation))
|
|
998
|
+
if (!replicated.has(conversation) && missed(conversation))
|
|
591
999
|
behind.add(conversation);
|
|
592
1000
|
}
|
|
593
1001
|
replicated = named;
|
|
@@ -596,44 +1004,100 @@ export function createConversationStore(deps) {
|
|
|
596
1004
|
await remove(leaving);
|
|
597
1005
|
},
|
|
598
1006
|
async catchUp(progress) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1007
|
+
if (deps.full()) {
|
|
1008
|
+
if (behind.size > 0)
|
|
1009
|
+
progress.complete = false;
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
1013
|
+
const read = async (conversations) => {
|
|
1014
|
+
if (conversations.length === 0 || deps.closed())
|
|
1015
|
+
return;
|
|
1016
|
+
const failed = await caughtUp(conversations);
|
|
602
1017
|
if (failed?.kind === 'transport-uncertainty')
|
|
603
1018
|
progress.reachable = false;
|
|
604
|
-
}
|
|
1019
|
+
};
|
|
1020
|
+
const asking = [...behind].filter(owed).sort();
|
|
1021
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
1022
|
+
await Promise.all([
|
|
1023
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
1024
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
1025
|
+
]);
|
|
605
1026
|
if (behind.size > 0)
|
|
606
1027
|
progress.complete = false;
|
|
607
1028
|
},
|
|
1029
|
+
async resend(progress) {
|
|
1030
|
+
for (const [conversation, held] of [...drafts]) {
|
|
1031
|
+
for (const submission of held.submitted ?? []) {
|
|
1032
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
1033
|
+
continue;
|
|
1034
|
+
if (deps.closed())
|
|
1035
|
+
return;
|
|
1036
|
+
if (deps.expired(submission.operation)) {
|
|
1037
|
+
await forgotten(conversation, submission);
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
const result = await dispatched(conversation, submission);
|
|
1041
|
+
if (result.kind === 'revoked-authorization')
|
|
1042
|
+
return;
|
|
1043
|
+
if (result.kind === 'transport-uncertainty') {
|
|
1044
|
+
progress.reachable = false;
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
1048
|
+
progress.complete = false;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
608
1054
|
current() {
|
|
609
1055
|
return behind.size === 0;
|
|
610
1056
|
},
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1057
|
+
known() {
|
|
1058
|
+
return listKnown;
|
|
1059
|
+
},
|
|
1060
|
+
reopened() {
|
|
1061
|
+
for (const [operation, dispatch] of sending)
|
|
1062
|
+
if (!dispatch.answered)
|
|
1063
|
+
sending.delete(operation);
|
|
1064
|
+
},
|
|
1065
|
+
lost() {
|
|
1066
|
+
catching.clear();
|
|
614
1067
|
},
|
|
615
1068
|
watch(conversation, options, deliver) {
|
|
616
|
-
const
|
|
617
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
1069
|
+
const asked = askedOf(options);
|
|
1070
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
618
1071
|
const view = views.get(key) ?? {
|
|
619
1072
|
conversation,
|
|
620
|
-
|
|
1073
|
+
asked,
|
|
621
1074
|
watchers: new Set(),
|
|
622
|
-
|
|
1075
|
+
window: null,
|
|
1076
|
+
more: false,
|
|
1077
|
+
total: null,
|
|
623
1078
|
};
|
|
624
1079
|
views.set(key, view);
|
|
625
1080
|
const watcher = { notify: deliver, ready: false };
|
|
626
1081
|
view.watchers.add(watcher);
|
|
627
|
-
|
|
628
|
-
|
|
1082
|
+
const reading = new AbortController();
|
|
1083
|
+
void untilAnswered(() => deps.durably(async (local) => {
|
|
1084
|
+
if (view.window !== null)
|
|
1085
|
+
return;
|
|
1086
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
1087
|
+
view.window = messages;
|
|
1088
|
+
view.more = more;
|
|
1089
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1090
|
+
}), reading.signal)
|
|
629
1091
|
.then(() => {
|
|
630
1092
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
631
1093
|
return;
|
|
632
1094
|
watcher.ready = true;
|
|
633
|
-
|
|
1095
|
+
const window = view.window ?? [];
|
|
1096
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
634
1097
|
})
|
|
635
1098
|
.catch(() => { });
|
|
636
1099
|
return () => {
|
|
1100
|
+
reading.abort();
|
|
637
1101
|
if (!view.watchers.delete(watcher))
|
|
638
1102
|
return;
|
|
639
1103
|
if (view.watchers.size === 0)
|
|
@@ -641,15 +1105,35 @@ export function createConversationStore(deps) {
|
|
|
641
1105
|
};
|
|
642
1106
|
},
|
|
643
1107
|
watchList(deliver) {
|
|
644
|
-
|
|
1108
|
+
let ended = false;
|
|
645
1109
|
void agentsReady().then(() => {
|
|
646
|
-
if (
|
|
647
|
-
|
|
1110
|
+
if (ended)
|
|
1111
|
+
return;
|
|
1112
|
+
unlisted.add(deliver);
|
|
1113
|
+
learned();
|
|
648
1114
|
});
|
|
649
1115
|
return () => {
|
|
1116
|
+
ended = true;
|
|
1117
|
+
unlisted.delete(deliver);
|
|
650
1118
|
listWatchers.delete(deliver);
|
|
651
1119
|
};
|
|
652
1120
|
},
|
|
1121
|
+
watchAgents(deliver) {
|
|
1122
|
+
unnamed.add(deliver);
|
|
1123
|
+
void agentsReady().then(learned);
|
|
1124
|
+
return () => {
|
|
1125
|
+
unnamed.delete(deliver);
|
|
1126
|
+
agentWatchers.delete(deliver);
|
|
1127
|
+
};
|
|
1128
|
+
},
|
|
1129
|
+
learned,
|
|
1130
|
+
declared() {
|
|
1131
|
+
if (JSON.stringify(deps.agents()) === agentsFrom)
|
|
1132
|
+
return;
|
|
1133
|
+
agentsRead = readAgents().catch(() => {
|
|
1134
|
+
agentsRead = null;
|
|
1135
|
+
});
|
|
1136
|
+
},
|
|
653
1137
|
changed(set) {
|
|
654
1138
|
for (const conversation of set.conversations)
|
|
655
1139
|
emit(conversation);
|
|
@@ -667,16 +1151,29 @@ export function createConversationStore(deps) {
|
|
|
667
1151
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
668
1152
|
if (event.kind === 'list') {
|
|
669
1153
|
listing.clear();
|
|
670
|
-
for (const entry of event.entries)
|
|
1154
|
+
for (const entry of event.entries) {
|
|
671
1155
|
listing.set(entry.conversation, { ...entry });
|
|
1156
|
+
creations.delete(entry.conversation);
|
|
1157
|
+
}
|
|
672
1158
|
listKnown = true;
|
|
1159
|
+
for (const conversation of replicated) {
|
|
1160
|
+
if (!listing.has(conversation))
|
|
1161
|
+
continue;
|
|
1162
|
+
if (!missed(conversation))
|
|
1163
|
+
behind.delete(conversation);
|
|
1164
|
+
else if (!behind.has(conversation))
|
|
1165
|
+
gap(conversation);
|
|
1166
|
+
}
|
|
1167
|
+
openingEnded();
|
|
673
1168
|
}
|
|
674
1169
|
else if (event.kind === 'listed') {
|
|
675
1170
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
1171
|
+
creations.delete(event.entry.conversation);
|
|
676
1172
|
}
|
|
677
1173
|
else {
|
|
678
1174
|
listing.delete(event.conversation);
|
|
679
1175
|
}
|
|
1176
|
+
keepList();
|
|
680
1177
|
emitList();
|
|
681
1178
|
return (event.kind !== 'list' &&
|
|
682
1179
|
listKnown &&
|
|
@@ -686,17 +1183,20 @@ export function createConversationStore(deps) {
|
|
|
686
1183
|
heard(event);
|
|
687
1184
|
return false;
|
|
688
1185
|
},
|
|
689
|
-
|
|
690
|
-
return [...
|
|
1186
|
+
searchable() {
|
|
1187
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
691
1188
|
},
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
1189
|
+
acquired(conversation) {
|
|
1190
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
695
1191
|
},
|
|
696
1192
|
close() {
|
|
697
1193
|
views.clear();
|
|
698
1194
|
lives.clear();
|
|
1195
|
+
unsent.clear();
|
|
699
1196
|
listWatchers.clear();
|
|
1197
|
+
unlisted.clear();
|
|
1198
|
+
agentWatchers.clear();
|
|
1199
|
+
unnamed.clear();
|
|
700
1200
|
},
|
|
701
1201
|
};
|
|
702
1202
|
}
|