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