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