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