@agentshouse/mdruntime 0.1.0-c99e5271060cd → 0.1.0-c99ee6ff87175
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 +173 -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 +50 -17
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3990 -922
- 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 +49 -19
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +729 -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 +11 -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 +21 -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 +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 +22 -4
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +120 -14
- 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 +18 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +225 -62
- 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 +53 -5
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +265 -60
- 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 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -9
- 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,
|
|
@@ -15,11 +33,28 @@ export function unheldView(conversation) {
|
|
|
15
33
|
turn: null,
|
|
16
34
|
questions: [],
|
|
17
35
|
process: null,
|
|
36
|
+
processEnded: null,
|
|
18
37
|
commands: [],
|
|
19
38
|
options: [],
|
|
39
|
+
starting: null,
|
|
40
|
+
subagents: [],
|
|
41
|
+
link: null,
|
|
42
|
+
deliveryUnknown: [],
|
|
20
43
|
chatDraft: null,
|
|
21
44
|
};
|
|
22
45
|
}
|
|
46
|
+
export const SESSION_STARTED = 'Session started';
|
|
47
|
+
const NEW_CHAT = 'new:';
|
|
48
|
+
export const HISTORY_CONVERSATIONS = 64;
|
|
49
|
+
export function newChat(agent) {
|
|
50
|
+
return `${NEW_CHAT}${agent}`;
|
|
51
|
+
}
|
|
52
|
+
function creationOf(input) {
|
|
53
|
+
if (typeof input !== 'object' || input === null)
|
|
54
|
+
return null;
|
|
55
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
56
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
57
|
+
}
|
|
23
58
|
function startedTurn(turn, operation) {
|
|
24
59
|
if (operation.op === 'begin')
|
|
25
60
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -69,18 +104,28 @@ function appliedTurn(held, operation) {
|
|
|
69
104
|
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
70
105
|
};
|
|
71
106
|
}
|
|
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) {
|
|
107
|
+
function windowOf(held, arriving, limit) {
|
|
80
108
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
81
109
|
for (const message of arriving)
|
|
82
110
|
byIdentity.set(message.identity, message);
|
|
83
|
-
|
|
111
|
+
const next = [...byIdentity.values()].sort(messageOrder);
|
|
112
|
+
const start = Math.max(0, next.length - limit);
|
|
113
|
+
return { messages: next.slice(start), dropped: start > 0 };
|
|
114
|
+
}
|
|
115
|
+
function frameOf(asked, messages, more, total) {
|
|
116
|
+
const oldest = messages[0];
|
|
117
|
+
const next = !more
|
|
118
|
+
? undefined
|
|
119
|
+
: oldest === undefined
|
|
120
|
+
? (asked.after ?? FROM_START)
|
|
121
|
+
: cursorOf([oldest.position, oldest.identity]);
|
|
122
|
+
return {
|
|
123
|
+
...(next === undefined ? {} : { next }),
|
|
124
|
+
...(total === null ? {} : { total }),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function newestOf(arriving, from) {
|
|
128
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
84
129
|
}
|
|
85
130
|
function acceptedMessage(value) {
|
|
86
131
|
if (typeof value !== 'object' || value === null)
|
|
@@ -139,86 +184,172 @@ function historyPages(result) {
|
|
|
139
184
|
}
|
|
140
185
|
return pages;
|
|
141
186
|
}
|
|
142
|
-
function
|
|
187
|
+
function answered(result, field) {
|
|
143
188
|
if (result.kind !== 'action')
|
|
144
189
|
return null;
|
|
145
190
|
const answer = result.result;
|
|
146
191
|
if (typeof answer !== 'object' || answer === null)
|
|
147
192
|
return null;
|
|
148
|
-
const
|
|
149
|
-
return typeof
|
|
193
|
+
const value = answer[field];
|
|
194
|
+
return typeof value === 'string' ? value : null;
|
|
195
|
+
}
|
|
196
|
+
function beforeOf(after) {
|
|
197
|
+
if (after === undefined)
|
|
198
|
+
return null;
|
|
199
|
+
const decoded = typeof after === 'string' ? decodedCursor(after) : null;
|
|
200
|
+
if (decoded?.length === 0)
|
|
201
|
+
return null;
|
|
202
|
+
const [position, identity] = decoded ?? [];
|
|
203
|
+
if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
|
|
204
|
+
throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
|
|
205
|
+
}
|
|
206
|
+
return [position, identity];
|
|
150
207
|
}
|
|
151
|
-
function
|
|
152
|
-
const { limit } = options;
|
|
153
|
-
if (limit
|
|
154
|
-
return undefined;
|
|
155
|
-
if (!Number.isInteger(limit) || limit < 0) {
|
|
208
|
+
function askedOf(options) {
|
|
209
|
+
const { limit, after, total } = options;
|
|
210
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
|
|
156
211
|
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
157
212
|
}
|
|
158
|
-
return limit;
|
|
213
|
+
return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
|
|
214
|
+
}
|
|
215
|
+
function phraseOf(text) {
|
|
216
|
+
if (text === undefined)
|
|
217
|
+
return null;
|
|
218
|
+
if (typeof text !== 'string' || text === '') {
|
|
219
|
+
throw runtimeError('invalid-text', 'a conversation is found by a phrase');
|
|
220
|
+
}
|
|
221
|
+
return text;
|
|
222
|
+
}
|
|
223
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
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
|
+
};
|
|
238
|
+
}
|
|
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);
|
|
159
255
|
}
|
|
160
256
|
export function createConversationStore(deps) {
|
|
161
|
-
const
|
|
257
|
+
const stored = new Set();
|
|
162
258
|
const reads = new Map();
|
|
259
|
+
const partials = new Set();
|
|
163
260
|
const drafts = new Map();
|
|
164
261
|
const sending = new Map();
|
|
165
262
|
const behind = new Set();
|
|
263
|
+
const catching = new Map();
|
|
166
264
|
const gaps = new Map();
|
|
167
265
|
const lives = new Map();
|
|
266
|
+
const unsent = new Map();
|
|
168
267
|
const views = new Map();
|
|
169
268
|
const listing = new Map();
|
|
269
|
+
let relisting = null;
|
|
270
|
+
let listCursor = null;
|
|
271
|
+
const creations = new Set();
|
|
170
272
|
const listWatchers = new Set();
|
|
273
|
+
const unlisted = new Set();
|
|
171
274
|
let listKnown = false;
|
|
172
|
-
let
|
|
173
|
-
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;
|
|
174
282
|
let replicated = new Set();
|
|
175
283
|
let handedReplicated;
|
|
176
284
|
function liveOf(conversation) {
|
|
177
285
|
return lives.get(conversation) ?? QUIET;
|
|
178
286
|
}
|
|
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)) {
|
|
287
|
+
function viewOf(conversation, held, frame) {
|
|
190
288
|
const draft = drafts.get(conversation);
|
|
191
289
|
const facts = liveOf(conversation);
|
|
192
290
|
return {
|
|
193
291
|
conversation,
|
|
194
|
-
messages:
|
|
292
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
293
|
+
identity,
|
|
294
|
+
turn,
|
|
295
|
+
text,
|
|
296
|
+
message,
|
|
297
|
+
})),
|
|
298
|
+
...frame,
|
|
195
299
|
turn: structuredClone(facts.turn),
|
|
196
300
|
questions: structuredClone(facts.questions),
|
|
197
301
|
process: facts.process === null ? null : { ...facts.process },
|
|
302
|
+
processEnded: listing.get(conversation)?.processEnded ?? null,
|
|
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,114 @@ 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
|
+
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
|
+
}
|
|
240
397
|
function emitList() {
|
|
241
|
-
if (deps.closed()
|
|
398
|
+
if (deps.closed())
|
|
242
399
|
return;
|
|
243
|
-
|
|
244
|
-
|
|
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)
|
|
447
|
+
return;
|
|
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)
|
|
245
470
|
watcher(value);
|
|
246
471
|
}
|
|
247
472
|
function setLive(conversation, next) {
|
|
@@ -256,34 +481,41 @@ export function createConversationStore(deps) {
|
|
|
256
481
|
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
257
482
|
emitList();
|
|
258
483
|
}
|
|
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;
|
|
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);
|
|
283
489
|
}
|
|
284
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);
|
|
285
494
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
286
|
-
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
|
+
}
|
|
287
519
|
}
|
|
288
520
|
function relayed(conversation, turn, operation) {
|
|
289
521
|
const held = liveOf(conversation).turn;
|
|
@@ -312,25 +544,38 @@ export function createConversationStore(deps) {
|
|
|
312
544
|
async function remove(leaving) {
|
|
313
545
|
await deps.durably(async (local) => {
|
|
314
546
|
await local.dropConversations(leaving);
|
|
315
|
-
const removed = [];
|
|
316
547
|
const changed = [];
|
|
317
548
|
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)) {
|
|
549
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
322
550
|
changed.push(conversation);
|
|
323
551
|
}
|
|
324
|
-
|
|
552
|
+
stored.delete(conversation);
|
|
325
553
|
reads.delete(conversation);
|
|
554
|
+
partials.delete(conversation);
|
|
326
555
|
drafts.delete(conversation);
|
|
327
556
|
behind.delete(conversation);
|
|
557
|
+
unsent.delete(conversation);
|
|
328
558
|
setLive(conversation, null);
|
|
559
|
+
await slide(local, conversation, null);
|
|
329
560
|
}
|
|
330
561
|
if (changed.length > 0)
|
|
331
|
-
deps.publish(conversationChangeSet(
|
|
562
|
+
deps.publish(conversationChangeSet(changed));
|
|
332
563
|
});
|
|
333
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
|
+
}
|
|
334
579
|
function gap(conversation) {
|
|
335
580
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
336
581
|
behind.add(conversation);
|
|
@@ -343,33 +588,29 @@ export function createConversationStore(deps) {
|
|
|
343
588
|
const advances = read !== undefined &&
|
|
344
589
|
!behind.has(conversation) &&
|
|
345
590
|
(read === null || message.position > read);
|
|
346
|
-
|
|
591
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
347
592
|
await local.keepMessages({
|
|
348
593
|
conversation,
|
|
349
594
|
messages: [message],
|
|
350
|
-
...(advances ? { read: message.position } : {}),
|
|
351
|
-
sealed,
|
|
595
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
352
596
|
});
|
|
353
|
-
|
|
597
|
+
stored.add(conversation);
|
|
354
598
|
if (advances)
|
|
355
599
|
reads.set(conversation, message.position);
|
|
356
|
-
if (sealed)
|
|
357
|
-
drafts.delete(conversation);
|
|
358
600
|
sealTurns(conversation, new Set([message.turn]));
|
|
359
|
-
|
|
601
|
+
await slide(local, conversation, [message]);
|
|
602
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
360
603
|
});
|
|
361
604
|
}
|
|
362
|
-
async function readHistories(conversations) {
|
|
605
|
+
async function readHistories(conversations, ended) {
|
|
363
606
|
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) => ({
|
|
607
|
+
let waiting = conversations.map((conversation) => ({
|
|
368
608
|
conversation,
|
|
369
|
-
after:
|
|
609
|
+
after: reads.get(conversation) ?? undefined,
|
|
370
610
|
}));
|
|
371
|
-
while (
|
|
372
|
-
const
|
|
611
|
+
while (waiting.length > 0) {
|
|
612
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
613
|
+
const result = await deps.history({
|
|
373
614
|
conversations: asking.map(({ conversation, after }) => ({
|
|
374
615
|
conversation,
|
|
375
616
|
...(after === undefined ? {} : { after }),
|
|
@@ -378,46 +619,64 @@ export function createConversationStore(deps) {
|
|
|
378
619
|
const pages = historyPages(result);
|
|
379
620
|
if (pages === null)
|
|
380
621
|
return result;
|
|
381
|
-
const asked = new
|
|
622
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
382
623
|
const following = [];
|
|
383
624
|
for (const page of pages) {
|
|
384
625
|
if (!asked.has(page.conversation))
|
|
385
626
|
continue;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
...page.messages,
|
|
389
|
-
]);
|
|
390
|
-
if (page.next === undefined)
|
|
391
|
-
complete.add(page.conversation);
|
|
392
|
-
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) {
|
|
393
629
|
following.push({ conversation: page.conversation, after: page.next });
|
|
630
|
+
}
|
|
394
631
|
}
|
|
395
|
-
asking
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
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];
|
|
401
637
|
}
|
|
402
638
|
return null;
|
|
403
639
|
}
|
|
404
|
-
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) {
|
|
405
660
|
return deps.durably(async (local) => {
|
|
406
661
|
if (!replicated.has(conversation))
|
|
407
662
|
return;
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
await local
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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);
|
|
416
674
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
417
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
675
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
418
676
|
behind.delete(conversation);
|
|
419
|
-
|
|
420
|
-
|
|
677
|
+
await slide(local, conversation, arriving);
|
|
678
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
679
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
421
680
|
}
|
|
422
681
|
});
|
|
423
682
|
}
|
|
@@ -426,11 +685,8 @@ export function createConversationStore(deps) {
|
|
|
426
685
|
if (event.kind === 'gap') {
|
|
427
686
|
if (replicated.has(conversation))
|
|
428
687
|
gap(conversation);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return;
|
|
432
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
433
|
-
emit(conversation, false);
|
|
688
|
+
if (lives.has(conversation))
|
|
689
|
+
unsent.set(conversation, new Set(FACTS));
|
|
434
690
|
return;
|
|
435
691
|
}
|
|
436
692
|
if (event.kind === 'turn') {
|
|
@@ -453,142 +709,292 @@ export function createConversationStore(deps) {
|
|
|
453
709
|
live(conversation, { options: structuredClone(event.options) });
|
|
454
710
|
return;
|
|
455
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
|
+
}
|
|
456
728
|
const arriving = acceptedMessage(event.message);
|
|
457
729
|
if (arriving === null)
|
|
458
730
|
return;
|
|
731
|
+
noted(conversation, arriving);
|
|
459
732
|
if (replicated.has(conversation)) {
|
|
460
|
-
|
|
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
|
+
});
|
|
461
741
|
return;
|
|
462
742
|
}
|
|
463
743
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
464
744
|
emit(conversation);
|
|
465
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 ?? []).filter((one) => !deps.expired(one));
|
|
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
|
+
}
|
|
466
787
|
function withDraft(conversation, next) {
|
|
467
788
|
return deps.durably(async (local) => {
|
|
468
|
-
const following = next(drafts.get(conversation));
|
|
789
|
+
const following = await next(drafts.get(conversation), local);
|
|
469
790
|
if (following === undefined)
|
|
470
791
|
return;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
792
|
+
await kept(local, conversation, following);
|
|
793
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
async function submitted(conversation, operation, draft) {
|
|
797
|
+
let submission;
|
|
798
|
+
let earlier = false;
|
|
799
|
+
await withDraft(conversation, (current) => {
|
|
800
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
801
|
+
earlier = submission !== undefined;
|
|
802
|
+
if (submission !== undefined)
|
|
803
|
+
return undefined;
|
|
804
|
+
const said = draft ?? current;
|
|
805
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
806
|
+
return {
|
|
807
|
+
conversation,
|
|
808
|
+
...(draft === undefined || current === undefined
|
|
809
|
+
? { text: '', revision: chatDraftRevision() }
|
|
810
|
+
: { text: current.text, revision: current.revision }),
|
|
811
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
812
|
+
};
|
|
480
813
|
});
|
|
814
|
+
return { submission: submission, earlier };
|
|
815
|
+
}
|
|
816
|
+
function forgotten(conversation, submission) {
|
|
817
|
+
return withDraft(conversation, (current) => {
|
|
818
|
+
const held = current?.submitted ?? [];
|
|
819
|
+
if (current === undefined || !held.includes(submission))
|
|
820
|
+
return undefined;
|
|
821
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
async function dispatched(conversation, submission) {
|
|
825
|
+
const dispatch = { answered: false };
|
|
826
|
+
sending.set(submission.operation, dispatch);
|
|
827
|
+
try {
|
|
828
|
+
const result = await invoke('conversations/send', conversation, {
|
|
829
|
+
operation_id: submission.operation,
|
|
830
|
+
text: submission.text,
|
|
831
|
+
});
|
|
832
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
833
|
+
if (result.kind === 'revoked-authorization')
|
|
834
|
+
return result;
|
|
835
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
836
|
+
deps.unanswered();
|
|
837
|
+
return result;
|
|
838
|
+
}
|
|
839
|
+
const turn = answered(result, 'message_id');
|
|
840
|
+
await withDraft(conversation, async (current, local) => {
|
|
841
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
842
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
843
|
+
return undefined;
|
|
844
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
845
|
+
if (result.kind !== 'action')
|
|
846
|
+
return returned(current, held, others);
|
|
847
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
848
|
+
return withSubmitted(current, turn === null || arrived
|
|
849
|
+
? others
|
|
850
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
851
|
+
});
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
finally {
|
|
855
|
+
if (sending.get(submission.operation) === dispatch)
|
|
856
|
+
sending.delete(submission.operation);
|
|
857
|
+
}
|
|
481
858
|
}
|
|
482
|
-
function invoke(name, conversation, extra = {}) {
|
|
483
|
-
return deps.call(name, { conversation, ...extra });
|
|
859
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
860
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
484
861
|
}
|
|
485
862
|
const surface = {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
863
|
+
list() {
|
|
864
|
+
return new Promise((settle) => {
|
|
865
|
+
const answer = (listed) => {
|
|
866
|
+
unlisted.delete(answer);
|
|
867
|
+
listWatchers.delete(answer);
|
|
868
|
+
settle(listed);
|
|
869
|
+
};
|
|
870
|
+
unlisted.add(answer);
|
|
871
|
+
learned();
|
|
872
|
+
});
|
|
489
873
|
},
|
|
490
874
|
async read(conversation, options = {}) {
|
|
491
|
-
const
|
|
492
|
-
|
|
875
|
+
const { text, ...windowed } = options;
|
|
876
|
+
const asked = askedOf(windowed);
|
|
877
|
+
const phrase = phraseOf(text);
|
|
878
|
+
return deps.durably(async (local) => {
|
|
879
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
|
|
880
|
+
const total = asked.total ? await local.messageCount(conversation, phrase) : null;
|
|
881
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
|
|
882
|
+
});
|
|
493
883
|
},
|
|
494
|
-
create(input) {
|
|
495
|
-
|
|
884
|
+
async create(input) {
|
|
885
|
+
const result = await deps.call('conversations/create', input);
|
|
886
|
+
const conversation = answered(result, 'conversation_id');
|
|
887
|
+
if (conversation === null)
|
|
888
|
+
return result;
|
|
889
|
+
if (!listing.has(conversation))
|
|
890
|
+
creations.add(conversation);
|
|
891
|
+
const creation = creationOf(input);
|
|
892
|
+
if (creation !== null)
|
|
893
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
894
|
+
return result;
|
|
496
895
|
},
|
|
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 };
|
|
896
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
897
|
+
if (deps.full())
|
|
898
|
+
return DURABLE_WRITE;
|
|
899
|
+
if (deps.online()) {
|
|
900
|
+
const held = await submitted(conversation, operation, draft).catch((error) => {
|
|
901
|
+
if (refusedForSpace(error))
|
|
902
|
+
return null;
|
|
903
|
+
throw error;
|
|
521
904
|
});
|
|
522
|
-
|
|
905
|
+
if (held === null)
|
|
906
|
+
return DURABLE_WRITE;
|
|
907
|
+
const { submission, earlier } = held;
|
|
908
|
+
if (!earlier || !deps.expired(operation))
|
|
909
|
+
return dispatched(conversation, submission);
|
|
910
|
+
await forgotten(conversation, submission);
|
|
911
|
+
return EXPIRED;
|
|
523
912
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
529
|
-
}
|
|
530
|
-
}
|
|
913
|
+
if (draft !== undefined) {
|
|
914
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
915
|
+
? undefined
|
|
916
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
531
917
|
}
|
|
918
|
+
return OFFLINE;
|
|
532
919
|
},
|
|
533
920
|
answer(conversation, answer) {
|
|
534
|
-
return invoke('
|
|
921
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
922
|
+
operation_id: changeBatchIdentity(),
|
|
923
|
+
...answer,
|
|
924
|
+
});
|
|
535
925
|
},
|
|
536
926
|
interrupt(conversation) {
|
|
537
|
-
return invoke('
|
|
927
|
+
return invoke('conversations/interrupt', conversation);
|
|
538
928
|
},
|
|
539
929
|
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;
|
|
930
|
+
return invoke('conversations/kill', conversation);
|
|
548
931
|
},
|
|
549
932
|
delete(conversation) {
|
|
550
|
-
return invoke('
|
|
933
|
+
return invoke('conversations/delete', conversation);
|
|
551
934
|
},
|
|
552
935
|
};
|
|
553
936
|
const chatDraft = {
|
|
554
|
-
write(conversation, draft) {
|
|
555
|
-
return withDraft(conversation, () => ({
|
|
937
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
938
|
+
return withDraft(conversation, (current) => ({
|
|
556
939
|
conversation,
|
|
557
940
|
text: draft.text,
|
|
558
|
-
|
|
559
|
-
|
|
941
|
+
revision,
|
|
942
|
+
...(current === undefined ? {} : pending(current)),
|
|
943
|
+
}));
|
|
944
|
+
},
|
|
945
|
+
prepend(conversation, draft) {
|
|
946
|
+
return withDraft(conversation, (current) => ({
|
|
947
|
+
conversation,
|
|
948
|
+
...(current === undefined ? { text: draft.text } : prepended(current, draft)),
|
|
949
|
+
revision: chatDraftRevision(),
|
|
950
|
+
...(current === undefined ? {} : pending(current)),
|
|
560
951
|
}));
|
|
561
952
|
},
|
|
562
953
|
discard(conversation) {
|
|
563
|
-
return withDraft(conversation, (current) =>
|
|
954
|
+
return withDraft(conversation, (current) => current === undefined
|
|
955
|
+
? undefined
|
|
956
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
564
957
|
},
|
|
565
958
|
};
|
|
566
959
|
return {
|
|
567
960
|
surface,
|
|
568
961
|
chatDraft,
|
|
569
|
-
hydrate(held,
|
|
570
|
-
|
|
962
|
+
hydrate(held, kept, list, cursor) {
|
|
963
|
+
if (list !== null && !listKnown) {
|
|
964
|
+
listing.clear();
|
|
965
|
+
for (const entry of list)
|
|
966
|
+
listing.set(entry.conversation, { ...entry });
|
|
967
|
+
listHeld = true;
|
|
968
|
+
listCursor = cursor;
|
|
969
|
+
deps.stream()?.hold(cursor);
|
|
970
|
+
}
|
|
971
|
+
stored.clear();
|
|
571
972
|
reads.clear();
|
|
572
|
-
|
|
573
|
-
view.messages = null;
|
|
973
|
+
partials.clear();
|
|
574
974
|
for (const entry of held) {
|
|
575
|
-
|
|
975
|
+
stored.add(entry.conversation);
|
|
576
976
|
if (entry.read !== undefined)
|
|
577
977
|
reads.set(entry.conversation, entry.read);
|
|
978
|
+
if (entry.partial)
|
|
979
|
+
partials.add(entry.conversation);
|
|
578
980
|
}
|
|
579
981
|
drafts.clear();
|
|
580
|
-
for (const record of
|
|
982
|
+
for (const record of kept)
|
|
581
983
|
drafts.set(record.conversation, record);
|
|
984
|
+
if (listHeld)
|
|
985
|
+
learned();
|
|
582
986
|
},
|
|
583
987
|
async reconcile() {
|
|
584
988
|
if (!listKnown)
|
|
585
989
|
return;
|
|
586
990
|
const named = new Set(listing.keys());
|
|
587
991
|
const leaving = [
|
|
588
|
-
...new Set([...replicated, ...
|
|
589
|
-
].filter((conversation) => !named.has(conversation)
|
|
992
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
993
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
994
|
+
!creations.has(conversation) &&
|
|
995
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
590
996
|
for (const conversation of named) {
|
|
591
|
-
if (!replicated.has(conversation))
|
|
997
|
+
if (!replicated.has(conversation) && missed(conversation))
|
|
592
998
|
behind.add(conversation);
|
|
593
999
|
}
|
|
594
1000
|
replicated = named;
|
|
@@ -597,44 +1003,101 @@ export function createConversationStore(deps) {
|
|
|
597
1003
|
await remove(leaving);
|
|
598
1004
|
},
|
|
599
1005
|
async catchUp(progress) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
1006
|
+
if (deps.full()) {
|
|
1007
|
+
if (behind.size > 0)
|
|
1008
|
+
progress.complete = false;
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
1012
|
+
const read = async (conversations) => {
|
|
1013
|
+
if (conversations.length === 0 || deps.closed())
|
|
1014
|
+
return;
|
|
1015
|
+
const failed = await caughtUp(conversations);
|
|
603
1016
|
if (failed?.kind === 'transport-uncertainty')
|
|
604
1017
|
progress.reachable = false;
|
|
605
|
-
}
|
|
1018
|
+
};
|
|
1019
|
+
const asking = [...behind].filter(owed).sort();
|
|
1020
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
1021
|
+
await Promise.all([
|
|
1022
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
1023
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
1024
|
+
]);
|
|
606
1025
|
if (behind.size > 0)
|
|
607
1026
|
progress.complete = false;
|
|
608
1027
|
},
|
|
1028
|
+
async resend(progress) {
|
|
1029
|
+
for (const [conversation, held] of [...drafts]) {
|
|
1030
|
+
for (const submission of held.submitted ?? []) {
|
|
1031
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
1032
|
+
continue;
|
|
1033
|
+
if (deps.closed())
|
|
1034
|
+
return;
|
|
1035
|
+
if (deps.expired(submission.operation)) {
|
|
1036
|
+
await forgotten(conversation, submission);
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
const result = await dispatched(conversation, submission);
|
|
1040
|
+
if (result.kind === 'revoked-authorization')
|
|
1041
|
+
return;
|
|
1042
|
+
if (result.kind === 'transport-uncertainty') {
|
|
1043
|
+
progress.reachable = false;
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
1047
|
+
progress.complete = false;
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
609
1053
|
current() {
|
|
610
1054
|
return behind.size === 0;
|
|
611
1055
|
},
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1056
|
+
known() {
|
|
1057
|
+
return listKnown;
|
|
1058
|
+
},
|
|
1059
|
+
reopened() {
|
|
1060
|
+
relisting = null;
|
|
1061
|
+
for (const [operation, dispatch] of sending)
|
|
1062
|
+
if (!dispatch.answered)
|
|
1063
|
+
sending.delete(operation);
|
|
1064
|
+
},
|
|
1065
|
+
lost() {
|
|
1066
|
+
catching.clear();
|
|
615
1067
|
},
|
|
616
1068
|
watch(conversation, options, deliver) {
|
|
617
|
-
const
|
|
618
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
1069
|
+
const asked = askedOf(options);
|
|
1070
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
619
1071
|
const view = views.get(key) ?? {
|
|
620
1072
|
conversation,
|
|
621
|
-
|
|
1073
|
+
asked,
|
|
622
1074
|
watchers: new Set(),
|
|
623
|
-
|
|
1075
|
+
window: null,
|
|
1076
|
+
more: false,
|
|
1077
|
+
total: null,
|
|
624
1078
|
};
|
|
625
1079
|
views.set(key, view);
|
|
626
1080
|
const watcher = { notify: deliver, ready: false };
|
|
627
1081
|
view.watchers.add(watcher);
|
|
628
|
-
|
|
629
|
-
|
|
1082
|
+
const reading = new AbortController();
|
|
1083
|
+
void untilAnswered(() => deps.durably(async (local) => {
|
|
1084
|
+
if (view.window !== null)
|
|
1085
|
+
return;
|
|
1086
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
1087
|
+
view.window = messages;
|
|
1088
|
+
view.more = more;
|
|
1089
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1090
|
+
}), reading.signal)
|
|
630
1091
|
.then(() => {
|
|
631
1092
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
632
1093
|
return;
|
|
633
1094
|
watcher.ready = true;
|
|
634
|
-
|
|
1095
|
+
const window = view.window ?? [];
|
|
1096
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
635
1097
|
})
|
|
636
1098
|
.catch(() => { });
|
|
637
1099
|
return () => {
|
|
1100
|
+
reading.abort();
|
|
638
1101
|
if (!view.watchers.delete(watcher))
|
|
639
1102
|
return;
|
|
640
1103
|
if (view.watchers.size === 0)
|
|
@@ -642,62 +1105,88 @@ export function createConversationStore(deps) {
|
|
|
642
1105
|
};
|
|
643
1106
|
},
|
|
644
1107
|
watchList(deliver) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
648
|
-
deliver(entries());
|
|
649
|
-
});
|
|
1108
|
+
unlisted.add(deliver);
|
|
1109
|
+
learned();
|
|
650
1110
|
return () => {
|
|
1111
|
+
unlisted.delete(deliver);
|
|
651
1112
|
listWatchers.delete(deliver);
|
|
652
1113
|
};
|
|
653
1114
|
},
|
|
1115
|
+
watchAgents(deliver) {
|
|
1116
|
+
unnamed.add(deliver);
|
|
1117
|
+
learned();
|
|
1118
|
+
readAgents();
|
|
1119
|
+
return () => {
|
|
1120
|
+
unnamed.delete(deliver);
|
|
1121
|
+
agentWatchers.delete(deliver);
|
|
1122
|
+
};
|
|
1123
|
+
},
|
|
1124
|
+
learned,
|
|
654
1125
|
changed(set) {
|
|
655
1126
|
for (const conversation of set.conversations)
|
|
656
1127
|
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
1128
|
},
|
|
665
1129
|
receive(event) {
|
|
666
1130
|
if (deps.closed())
|
|
667
1131
|
return false;
|
|
668
1132
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
669
1133
|
if (event.kind === 'list') {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1134
|
+
if (event.whole)
|
|
1135
|
+
relisting ??= new Map();
|
|
1136
|
+
for (const entry of event.entries) {
|
|
1137
|
+
if (relisting === null)
|
|
1138
|
+
listed(entry);
|
|
1139
|
+
else
|
|
1140
|
+
relisting.set(entry.conversation, { ...entry });
|
|
1141
|
+
}
|
|
1142
|
+
if (event.more)
|
|
1143
|
+
return false;
|
|
1144
|
+
if (relisting !== null) {
|
|
1145
|
+
listing.clear();
|
|
1146
|
+
for (const entry of relisting.values())
|
|
1147
|
+
listed(entry);
|
|
1148
|
+
relisting = null;
|
|
1149
|
+
}
|
|
1150
|
+
listCursor = event.cursor;
|
|
1151
|
+
deps.stream()?.hold(event.cursor);
|
|
673
1152
|
listKnown = true;
|
|
1153
|
+
for (const conversation of replicated) {
|
|
1154
|
+
if (!listing.has(conversation))
|
|
1155
|
+
continue;
|
|
1156
|
+
if (!missed(conversation))
|
|
1157
|
+
behind.delete(conversation);
|
|
1158
|
+
else if (!behind.has(conversation))
|
|
1159
|
+
gap(conversation);
|
|
1160
|
+
}
|
|
1161
|
+
openingEnded();
|
|
674
1162
|
}
|
|
675
1163
|
else if (event.kind === 'listed') {
|
|
676
|
-
|
|
1164
|
+
listed(event.entry);
|
|
1165
|
+
relisting?.set(event.entry.conversation, { ...event.entry });
|
|
677
1166
|
}
|
|
678
1167
|
else {
|
|
679
1168
|
listing.delete(event.conversation);
|
|
1169
|
+
relisting?.delete(event.conversation);
|
|
680
1170
|
}
|
|
1171
|
+
keepList();
|
|
681
1172
|
emitList();
|
|
682
|
-
return (event.kind !== 'list' &&
|
|
1173
|
+
return ((event.kind !== 'list' &&
|
|
683
1174
|
listKnown &&
|
|
684
1175
|
(listing.size !== replicated.size ||
|
|
685
|
-
[...listing.keys()].some((conversation) => !replicated.has(conversation))))
|
|
1176
|
+
[...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
|
|
1177
|
+
(listKnown && [...drafts.keys()].some(retiredChat)));
|
|
686
1178
|
}
|
|
687
1179
|
heard(event);
|
|
688
1180
|
return false;
|
|
689
1181
|
},
|
|
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
1182
|
close() {
|
|
698
1183
|
views.clear();
|
|
699
1184
|
lives.clear();
|
|
1185
|
+
unsent.clear();
|
|
700
1186
|
listWatchers.clear();
|
|
1187
|
+
unlisted.clear();
|
|
1188
|
+
agentWatchers.clear();
|
|
1189
|
+
unnamed.clear();
|
|
701
1190
|
},
|
|
702
1191
|
};
|
|
703
1192
|
}
|