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