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