@agentshouse/mdruntime 0.1.0-cfbf80d8d4644 → 0.1.0-cfcf93be6310b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -11
- package/dist/adapter.d.ts +182 -47
- 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 +53 -6
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +33 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +202 -48
- package/dist/chain.d.ts +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 +38 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +156 -20
- package/dist/collection.d.ts +13 -14
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +52 -56
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +789 -0
- package/dist/connection.d.ts +59 -20
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +4080 -879
- 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 +51 -20
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +732 -242
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +4 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +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/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/host.d.ts +11 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +128 -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 +22 -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 +20 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +253 -92
- 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 +19 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -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/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/phrase.d.ts +3 -0
- package/dist/phrase.d.ts.map +1 -0
- package/dist/phrase.js +8 -0
- package/dist/query.d.ts +75 -5
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +119 -26
- package/dist/reach.d.ts +18 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +234 -69
- package/dist/react.d.ts +32 -19
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +13 -21
- package/dist/readiness.d.ts +50 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +224 -0
- package/dist/refresh.d.ts +20 -7
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +167 -59
- package/dist/replica.d.ts +59 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +324 -157
- package/dist/room-draft.d.ts +59 -11
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +276 -80
- package/dist/rows.d.ts +40 -34
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +202 -80
- package/dist/save.d.ts +73 -4
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +825 -152
- package/dist/search.d.ts +15 -54
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/status.d.ts +7 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -9
- package/dist/store.d.ts +118 -59
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +354 -344
- 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 +104 -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/package.json +26 -15
- package/AGENTS.md +0 -232
- 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 -377
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,
|
|
@@ -14,11 +33,28 @@ export function unheldView(conversation) {
|
|
|
14
33
|
turn: null,
|
|
15
34
|
questions: [],
|
|
16
35
|
process: null,
|
|
36
|
+
processEnded: null,
|
|
17
37
|
commands: [],
|
|
18
38
|
options: [],
|
|
39
|
+
starting: null,
|
|
40
|
+
subagents: [],
|
|
41
|
+
link: null,
|
|
42
|
+
deliveryUnknown: [],
|
|
19
43
|
chatDraft: null,
|
|
20
44
|
};
|
|
21
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
|
+
}
|
|
22
58
|
function startedTurn(turn, operation) {
|
|
23
59
|
if (operation.op === 'begin')
|
|
24
60
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -68,18 +104,28 @@ function appliedTurn(held, operation) {
|
|
|
68
104
|
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
69
105
|
};
|
|
70
106
|
}
|
|
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) {
|
|
107
|
+
function windowOf(held, arriving, limit) {
|
|
79
108
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
80
109
|
for (const message of arriving)
|
|
81
110
|
byIdentity.set(message.identity, message);
|
|
82
|
-
|
|
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);
|
|
83
129
|
}
|
|
84
130
|
function acceptedMessage(value) {
|
|
85
131
|
if (typeof value !== 'object' || value === null)
|
|
@@ -138,86 +184,172 @@ function historyPages(result) {
|
|
|
138
184
|
}
|
|
139
185
|
return pages;
|
|
140
186
|
}
|
|
141
|
-
function
|
|
187
|
+
function answered(result, field) {
|
|
142
188
|
if (result.kind !== 'action')
|
|
143
189
|
return null;
|
|
144
190
|
const answer = result.result;
|
|
145
191
|
if (typeof answer !== 'object' || answer === null)
|
|
146
192
|
return null;
|
|
147
|
-
const
|
|
148
|
-
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];
|
|
207
|
+
}
|
|
208
|
+
function askedOf(options) {
|
|
209
|
+
const { limit, after, total } = options;
|
|
210
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
|
|
211
|
+
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
212
|
+
}
|
|
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;
|
|
149
241
|
}
|
|
150
|
-
function
|
|
151
|
-
const {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
242
|
+
function withSubmitted(draft, submitted) {
|
|
243
|
+
const { submitted: _held, ...unsent } = draft;
|
|
244
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
245
|
+
}
|
|
246
|
+
function prepended(draft, said) {
|
|
247
|
+
return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
|
|
248
|
+
}
|
|
249
|
+
function returned(draft, submission, others) {
|
|
250
|
+
return withSubmitted({
|
|
251
|
+
conversation: draft.conversation,
|
|
252
|
+
...prepended(draft, submission),
|
|
253
|
+
revision: chatDraftRevision(),
|
|
254
|
+
}, others);
|
|
158
255
|
}
|
|
159
256
|
export function createConversationStore(deps) {
|
|
160
|
-
const
|
|
257
|
+
const stored = new Set();
|
|
161
258
|
const reads = new Map();
|
|
259
|
+
const partials = new Set();
|
|
162
260
|
const drafts = new Map();
|
|
163
261
|
const sending = new Map();
|
|
164
262
|
const behind = new Set();
|
|
263
|
+
const catching = new Map();
|
|
165
264
|
const gaps = new Map();
|
|
166
265
|
const lives = new Map();
|
|
266
|
+
const unsent = new Map();
|
|
167
267
|
const views = new Map();
|
|
168
268
|
const listing = new Map();
|
|
269
|
+
let relisting = null;
|
|
270
|
+
let listCursor = null;
|
|
271
|
+
const creations = new Set();
|
|
169
272
|
const listWatchers = new Set();
|
|
273
|
+
const unlisted = new Set();
|
|
170
274
|
let listKnown = false;
|
|
171
|
-
let
|
|
172
|
-
let
|
|
275
|
+
let listHeld = false;
|
|
276
|
+
let listKept = false;
|
|
277
|
+
const agentWatchers = new Set();
|
|
278
|
+
const unnamed = new Set();
|
|
279
|
+
let agentsShown = null;
|
|
280
|
+
let listedAgents = null;
|
|
281
|
+
let agentsReading = false;
|
|
173
282
|
let replicated = new Set();
|
|
174
283
|
let handedReplicated;
|
|
175
284
|
function liveOf(conversation) {
|
|
176
285
|
return lives.get(conversation) ?? QUIET;
|
|
177
286
|
}
|
|
178
|
-
function
|
|
179
|
-
const held = messages.get(conversation) ?? [];
|
|
180
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
181
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
182
|
-
identity,
|
|
183
|
-
turn,
|
|
184
|
-
text,
|
|
185
|
-
message: structuredClone(message),
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
287
|
+
function viewOf(conversation, held, frame) {
|
|
189
288
|
const draft = drafts.get(conversation);
|
|
190
289
|
const facts = liveOf(conversation);
|
|
191
290
|
return {
|
|
192
291
|
conversation,
|
|
193
|
-
messages:
|
|
292
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
293
|
+
identity,
|
|
294
|
+
turn,
|
|
295
|
+
text,
|
|
296
|
+
message,
|
|
297
|
+
})),
|
|
298
|
+
...frame,
|
|
194
299
|
turn: structuredClone(facts.turn),
|
|
195
300
|
questions: structuredClone(facts.questions),
|
|
196
301
|
process: facts.process === null ? null : { ...facts.process },
|
|
302
|
+
processEnded: listing.get(conversation)?.processEnded ?? null,
|
|
197
303
|
commands: structuredClone(facts.commands),
|
|
198
304
|
options: structuredClone(facts.options),
|
|
199
|
-
|
|
305
|
+
starting: facts.starting,
|
|
306
|
+
subagents: structuredClone(facts.subagents),
|
|
307
|
+
link: facts.link === null ? null : { ...facts.link },
|
|
308
|
+
deliveryUnknown: [...facts.deliveryUnknown],
|
|
309
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
200
310
|
? null
|
|
201
|
-
: { text: draft.text,
|
|
311
|
+
: { text: draft.text, revision: draft.revision },
|
|
202
312
|
};
|
|
203
313
|
}
|
|
204
|
-
function emit(conversation
|
|
314
|
+
function emit(conversation) {
|
|
205
315
|
if (deps.closed())
|
|
206
316
|
return;
|
|
207
317
|
for (const view of views.values()) {
|
|
208
|
-
if (view.conversation !== conversation)
|
|
318
|
+
if (view.conversation !== conversation || view.window === null)
|
|
209
319
|
continue;
|
|
210
|
-
if (messagesChanged)
|
|
211
|
-
view.messages = null;
|
|
212
320
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
213
321
|
if (ready.length === 0)
|
|
214
322
|
continue;
|
|
215
|
-
|
|
216
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
323
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
|
|
217
324
|
for (const watcher of ready)
|
|
218
325
|
watcher.notify(value);
|
|
219
326
|
}
|
|
220
327
|
}
|
|
328
|
+
function sharedFrame(view, window) {
|
|
329
|
+
return frameOf(view.asked, window, view.more, view.total);
|
|
330
|
+
}
|
|
331
|
+
async function slide(local, conversation, arriving) {
|
|
332
|
+
let count;
|
|
333
|
+
for (const view of views.values()) {
|
|
334
|
+
if (view.conversation !== conversation || view.window === null)
|
|
335
|
+
continue;
|
|
336
|
+
if (arriving === null) {
|
|
337
|
+
view.window = [];
|
|
338
|
+
view.more = false;
|
|
339
|
+
if (view.total !== null)
|
|
340
|
+
view.total = 0;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const joining = arriving.filter((message) => precedes(message, view.asked.before));
|
|
344
|
+
const next = windowOf(view.window, joining, view.asked.limit);
|
|
345
|
+
view.window = next.messages;
|
|
346
|
+
view.more ||= next.dropped;
|
|
347
|
+
if (view.total !== null && arriving.length > 0) {
|
|
348
|
+
count ??= await local.messageCount(conversation);
|
|
349
|
+
view.total = count;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
221
353
|
function stateOf(conversation) {
|
|
222
354
|
const facts = liveOf(conversation);
|
|
223
355
|
if (facts.questions.length > 0)
|
|
@@ -227,20 +359,114 @@ export function createConversationStore(deps) {
|
|
|
227
359
|
function entries() {
|
|
228
360
|
return [...listing.values()].map((entry) => ({
|
|
229
361
|
conversation: entry.conversation,
|
|
230
|
-
agent: { identity: entry.agent, name:
|
|
362
|
+
agent: { identity: entry.agent, name: entry.agentName },
|
|
231
363
|
name: entry.name,
|
|
232
364
|
created: entry.created,
|
|
233
365
|
finished: entry.finished,
|
|
234
366
|
outcome: entry.outcome,
|
|
367
|
+
waiting: entry.waiting,
|
|
235
368
|
state: stateOf(entry.conversation),
|
|
236
369
|
started: lives.get(entry.conversation)?.turn?.started ?? null,
|
|
237
370
|
}));
|
|
238
371
|
}
|
|
372
|
+
function keepList() {
|
|
373
|
+
if (!listKnown || listKept)
|
|
374
|
+
return;
|
|
375
|
+
listKept = true;
|
|
376
|
+
queueMicrotask(() => {
|
|
377
|
+
listKept = false;
|
|
378
|
+
if (deps.closed())
|
|
379
|
+
return;
|
|
380
|
+
const entries = [...listing.values()];
|
|
381
|
+
const cursor = listCursor;
|
|
382
|
+
void deps
|
|
383
|
+
.durably(async (local) => {
|
|
384
|
+
await local.fact('conversationList', entries);
|
|
385
|
+
await local.fact('conversationCursor', cursor);
|
|
386
|
+
})
|
|
387
|
+
.catch(() => { });
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function listed(entry) {
|
|
391
|
+
const ended = listing.get(entry.conversation)?.processEnded ?? null;
|
|
392
|
+
listing.set(entry.conversation, { ...entry });
|
|
393
|
+
creations.delete(entry.conversation);
|
|
394
|
+
if (entry.processEnded !== ended)
|
|
395
|
+
emit(entry.conversation);
|
|
396
|
+
}
|
|
239
397
|
function emitList() {
|
|
240
|
-
if (deps.closed()
|
|
398
|
+
if (deps.closed())
|
|
399
|
+
return;
|
|
400
|
+
if (listWatchers.size > 0) {
|
|
401
|
+
const value = entries();
|
|
402
|
+
for (const watcher of listWatchers)
|
|
403
|
+
watcher(value);
|
|
404
|
+
}
|
|
405
|
+
emitAgents();
|
|
406
|
+
learned();
|
|
407
|
+
}
|
|
408
|
+
function learned() {
|
|
409
|
+
if (deps.closed())
|
|
410
|
+
return;
|
|
411
|
+
if (!listKnown && !listHeld && deps.stream() !== undefined && deps.online())
|
|
241
412
|
return;
|
|
242
|
-
|
|
243
|
-
|
|
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)
|
|
244
470
|
watcher(value);
|
|
245
471
|
}
|
|
246
472
|
function setLive(conversation, next) {
|
|
@@ -255,34 +481,41 @@ export function createConversationStore(deps) {
|
|
|
255
481
|
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
256
482
|
emitList();
|
|
257
483
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const names = new Map();
|
|
264
|
-
for (const document of documents) {
|
|
265
|
-
if (document.type !== declaration.type)
|
|
266
|
-
continue;
|
|
267
|
-
const identity = document.fields?.[declaration.identity];
|
|
268
|
-
const name = document.fields?.[declaration.name];
|
|
269
|
-
if (typeof identity === 'string' && typeof name === 'string')
|
|
270
|
-
names.set(identity, name);
|
|
271
|
-
}
|
|
272
|
-
if (JSON.stringify([...names]) === JSON.stringify([...agentNames]))
|
|
273
|
-
return;
|
|
274
|
-
agentNames = names;
|
|
275
|
-
emitList();
|
|
276
|
-
}
|
|
277
|
-
function agentsReady() {
|
|
278
|
-
agentsRead ??= readAgents().catch(() => {
|
|
279
|
-
agentsRead = null;
|
|
280
|
-
});
|
|
281
|
-
return agentsRead;
|
|
484
|
+
function retiredChat(conversation) {
|
|
485
|
+
if (!conversation.startsWith(NEW_CHAT))
|
|
486
|
+
return false;
|
|
487
|
+
const agent = conversation.slice(NEW_CHAT.length);
|
|
488
|
+
return [...listing.values()].some((entry) => entry.agent === agent && entry.agentRetired);
|
|
282
489
|
}
|
|
283
490
|
function live(conversation, change) {
|
|
491
|
+
const owed = unsent.get(conversation);
|
|
492
|
+
for (const fact of FACTS.filter((one) => one in change))
|
|
493
|
+
owed?.delete(fact);
|
|
284
494
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
285
|
-
emit(conversation
|
|
495
|
+
emit(conversation);
|
|
496
|
+
}
|
|
497
|
+
function openingEnded() {
|
|
498
|
+
const owed = [...unsent];
|
|
499
|
+
unsent.clear();
|
|
500
|
+
for (const [conversation, facts] of owed) {
|
|
501
|
+
const held = lives.get(conversation);
|
|
502
|
+
if (held === undefined || facts.size === 0)
|
|
503
|
+
continue;
|
|
504
|
+
setLive(conversation, {
|
|
505
|
+
turn: held.turn,
|
|
506
|
+
questions: facts.has('questions') ? QUIET.questions : held.questions,
|
|
507
|
+
process: facts.has('process') ? QUIET.process : held.process,
|
|
508
|
+
commands: facts.has('commands') ? QUIET.commands : held.commands,
|
|
509
|
+
options: facts.has('options') ? QUIET.options : held.options,
|
|
510
|
+
starting: facts.has('starting') ? QUIET.starting : held.starting,
|
|
511
|
+
subagents: facts.has('subagents') ? QUIET.subagents : held.subagents,
|
|
512
|
+
link: facts.has('link') ? QUIET.link : held.link,
|
|
513
|
+
deliveryUnknown: facts.has('deliveryUnknown')
|
|
514
|
+
? QUIET.deliveryUnknown
|
|
515
|
+
: held.deliveryUnknown,
|
|
516
|
+
});
|
|
517
|
+
emit(conversation);
|
|
518
|
+
}
|
|
286
519
|
}
|
|
287
520
|
function relayed(conversation, turn, operation) {
|
|
288
521
|
const held = liveOf(conversation).turn;
|
|
@@ -311,25 +544,38 @@ export function createConversationStore(deps) {
|
|
|
311
544
|
async function remove(leaving) {
|
|
312
545
|
await deps.durably(async (local) => {
|
|
313
546
|
await local.dropConversations(leaving);
|
|
314
|
-
const removed = [];
|
|
315
547
|
const changed = [];
|
|
316
548
|
for (const conversation of leaving) {
|
|
317
|
-
|
|
318
|
-
removed.push({ conversation, identity: message.identity });
|
|
319
|
-
}
|
|
320
|
-
if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
549
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
321
550
|
changed.push(conversation);
|
|
322
551
|
}
|
|
323
|
-
|
|
552
|
+
stored.delete(conversation);
|
|
324
553
|
reads.delete(conversation);
|
|
554
|
+
partials.delete(conversation);
|
|
325
555
|
drafts.delete(conversation);
|
|
326
556
|
behind.delete(conversation);
|
|
557
|
+
unsent.delete(conversation);
|
|
327
558
|
setLive(conversation, null);
|
|
559
|
+
await slide(local, conversation, null);
|
|
328
560
|
}
|
|
329
561
|
if (changed.length > 0)
|
|
330
|
-
deps.publish(conversationChangeSet(
|
|
562
|
+
deps.publish(conversationChangeSet(changed));
|
|
331
563
|
});
|
|
332
564
|
}
|
|
565
|
+
function missed(conversation) {
|
|
566
|
+
const read = reads.get(conversation);
|
|
567
|
+
const newest = listing.get(conversation)?.newest;
|
|
568
|
+
return (read === undefined ||
|
|
569
|
+
newest === undefined ||
|
|
570
|
+
partials.has(conversation) ||
|
|
571
|
+
(newest !== null && (read === null || newest > read)));
|
|
572
|
+
}
|
|
573
|
+
function noted(conversation, message) {
|
|
574
|
+
const entry = listing.get(conversation);
|
|
575
|
+
if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
|
|
576
|
+
return;
|
|
577
|
+
listing.set(conversation, { ...entry, newest: message.position });
|
|
578
|
+
}
|
|
333
579
|
function gap(conversation) {
|
|
334
580
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
335
581
|
behind.add(conversation);
|
|
@@ -342,33 +588,29 @@ export function createConversationStore(deps) {
|
|
|
342
588
|
const advances = read !== undefined &&
|
|
343
589
|
!behind.has(conversation) &&
|
|
344
590
|
(read === null || message.position > read);
|
|
345
|
-
|
|
591
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
346
592
|
await local.keepMessages({
|
|
347
593
|
conversation,
|
|
348
594
|
messages: [message],
|
|
349
|
-
...(advances ? { read: message.position } : {}),
|
|
350
|
-
sealed,
|
|
595
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
351
596
|
});
|
|
352
|
-
|
|
597
|
+
stored.add(conversation);
|
|
353
598
|
if (advances)
|
|
354
599
|
reads.set(conversation, message.position);
|
|
355
|
-
if (sealed)
|
|
356
|
-
drafts.delete(conversation);
|
|
357
600
|
sealTurns(conversation, new Set([message.turn]));
|
|
358
|
-
|
|
601
|
+
await slide(local, conversation, [message]);
|
|
602
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
359
603
|
});
|
|
360
604
|
}
|
|
361
|
-
async function readHistories(conversations) {
|
|
605
|
+
async function readHistories(conversations, ended) {
|
|
362
606
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
363
|
-
|
|
364
|
-
const arriving = new Map();
|
|
365
|
-
const complete = new Set();
|
|
366
|
-
let asking = conversations.map((conversation) => ({
|
|
607
|
+
let waiting = conversations.map((conversation) => ({
|
|
367
608
|
conversation,
|
|
368
|
-
after:
|
|
609
|
+
after: reads.get(conversation) ?? undefined,
|
|
369
610
|
}));
|
|
370
|
-
while (
|
|
371
|
-
const
|
|
611
|
+
while (waiting.length > 0) {
|
|
612
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
613
|
+
const result = await deps.history({
|
|
372
614
|
conversations: asking.map(({ conversation, after }) => ({
|
|
373
615
|
conversation,
|
|
374
616
|
...(after === undefined ? {} : { after }),
|
|
@@ -377,46 +619,64 @@ export function createConversationStore(deps) {
|
|
|
377
619
|
const pages = historyPages(result);
|
|
378
620
|
if (pages === null)
|
|
379
621
|
return result;
|
|
380
|
-
const asked = new
|
|
622
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
381
623
|
const following = [];
|
|
382
624
|
for (const page of pages) {
|
|
383
625
|
if (!asked.has(page.conversation))
|
|
384
626
|
continue;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
...page.messages,
|
|
388
|
-
]);
|
|
389
|
-
if (page.next === undefined)
|
|
390
|
-
complete.add(page.conversation);
|
|
391
|
-
else
|
|
627
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
628
|
+
if (page.next !== undefined) {
|
|
392
629
|
following.push({ conversation: page.conversation, after: page.next });
|
|
630
|
+
}
|
|
393
631
|
}
|
|
394
|
-
asking
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
632
|
+
for (const { conversation } of asking) {
|
|
633
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
634
|
+
ended(conversation);
|
|
635
|
+
}
|
|
636
|
+
waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
|
|
400
637
|
}
|
|
401
638
|
return null;
|
|
402
639
|
}
|
|
403
|
-
function
|
|
640
|
+
function caughtUp(conversations) {
|
|
641
|
+
const ends = new Map();
|
|
642
|
+
for (const conversation of conversations) {
|
|
643
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
644
|
+
catching.set(conversation, held);
|
|
645
|
+
void held.then(() => {
|
|
646
|
+
if (catching.get(conversation) === held)
|
|
647
|
+
catching.delete(conversation);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
651
|
+
const read = readHistories(conversations, ended);
|
|
652
|
+
const endAll = () => {
|
|
653
|
+
for (const conversation of conversations)
|
|
654
|
+
ended(conversation);
|
|
655
|
+
};
|
|
656
|
+
read.then(endAll, endAll);
|
|
657
|
+
return read;
|
|
658
|
+
}
|
|
659
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
404
660
|
return deps.durably(async (local) => {
|
|
405
661
|
if (!replicated.has(conversation))
|
|
406
662
|
return;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
await local
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
663
|
+
const whole = next === undefined;
|
|
664
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
665
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
666
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
667
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
668
|
+
stored.add(conversation);
|
|
669
|
+
reads.set(conversation, cursor);
|
|
670
|
+
if (partial)
|
|
671
|
+
partials.add(conversation);
|
|
672
|
+
else
|
|
673
|
+
partials.delete(conversation);
|
|
415
674
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
416
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
675
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
417
676
|
behind.delete(conversation);
|
|
418
|
-
|
|
419
|
-
|
|
677
|
+
await slide(local, conversation, arriving);
|
|
678
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
679
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
420
680
|
}
|
|
421
681
|
});
|
|
422
682
|
}
|
|
@@ -425,11 +685,8 @@ export function createConversationStore(deps) {
|
|
|
425
685
|
if (event.kind === 'gap') {
|
|
426
686
|
if (replicated.has(conversation))
|
|
427
687
|
gap(conversation);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return;
|
|
431
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
432
|
-
emit(conversation, false);
|
|
688
|
+
if (lives.has(conversation))
|
|
689
|
+
unsent.set(conversation, new Set(FACTS));
|
|
433
690
|
return;
|
|
434
691
|
}
|
|
435
692
|
if (event.kind === 'turn') {
|
|
@@ -452,142 +709,292 @@ export function createConversationStore(deps) {
|
|
|
452
709
|
live(conversation, { options: structuredClone(event.options) });
|
|
453
710
|
return;
|
|
454
711
|
}
|
|
712
|
+
if (event.kind === 'starting') {
|
|
713
|
+
live(conversation, { starting: event.step });
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (event.kind === 'subagents') {
|
|
717
|
+
live(conversation, { subagents: structuredClone(event.subagents) });
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (event.kind === 'link') {
|
|
721
|
+
live(conversation, { link: { connected: event.connected } });
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (event.kind === 'deliveries') {
|
|
725
|
+
live(conversation, { deliveryUnknown: [...event.unknown] });
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
455
728
|
const arriving = acceptedMessage(event.message);
|
|
456
729
|
if (arriving === null)
|
|
457
730
|
return;
|
|
731
|
+
noted(conversation, arriving);
|
|
458
732
|
if (replicated.has(conversation)) {
|
|
459
|
-
|
|
733
|
+
if (deps.full()) {
|
|
734
|
+
gap(conversation);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
void keep(conversation, arriving).catch((error) => {
|
|
738
|
+
if (refusedForSpace(error))
|
|
739
|
+
gap(conversation);
|
|
740
|
+
});
|
|
460
741
|
return;
|
|
461
742
|
}
|
|
462
743
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
463
744
|
emit(conversation);
|
|
464
745
|
}
|
|
746
|
+
async function kept(local, conversation, following) {
|
|
747
|
+
if (following === null || empty(following)) {
|
|
748
|
+
await local.dropChatDraft(conversation);
|
|
749
|
+
drafts.delete(conversation);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
await local.keepChatDrafts([following]);
|
|
753
|
+
drafts.set(conversation, following);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async function sealed(local, conversation, turns) {
|
|
757
|
+
const held = drafts.get(conversation);
|
|
758
|
+
const submitted = held?.submitted ?? [];
|
|
759
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
760
|
+
if (held === undefined || left.length === submitted.length)
|
|
761
|
+
return false;
|
|
762
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
function moved(agent, operation, to) {
|
|
766
|
+
const from = newChat(agent);
|
|
767
|
+
return deps.durably(async (local) => {
|
|
768
|
+
const held = drafts.get(from);
|
|
769
|
+
const earlier = (held?.moves ?? []).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
|
+
}
|
|
465
787
|
function withDraft(conversation, next) {
|
|
466
788
|
return deps.durably(async (local) => {
|
|
467
|
-
const following = next(drafts.get(conversation));
|
|
789
|
+
const following = await next(drafts.get(conversation), local);
|
|
468
790
|
if (following === undefined)
|
|
469
791
|
return;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
792
|
+
await kept(local, conversation, following);
|
|
793
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
async function submitted(conversation, operation, draft) {
|
|
797
|
+
let submission;
|
|
798
|
+
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
|
+
};
|
|
479
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
|
+
}
|
|
480
858
|
}
|
|
481
|
-
function invoke(name, conversation, extra = {}) {
|
|
482
|
-
return deps.call(name, { conversation, ...extra });
|
|
859
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
860
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
483
861
|
}
|
|
484
862
|
const surface = {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
+
});
|
|
488
873
|
},
|
|
489
874
|
async read(conversation, options = {}) {
|
|
490
|
-
const
|
|
491
|
-
|
|
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
|
+
});
|
|
492
883
|
},
|
|
493
|
-
create(input) {
|
|
494
|
-
|
|
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;
|
|
495
895
|
},
|
|
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,
|
|
511
|
-
});
|
|
512
|
-
const turn = turnOf(result);
|
|
513
|
-
if (turn === null || draft === undefined)
|
|
514
|
-
return result;
|
|
515
|
-
await withDraft(conversation, (current) => {
|
|
516
|
-
if (current !== draft)
|
|
517
|
-
return undefined;
|
|
518
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
519
|
-
return sealed ? null : { ...draft, turn };
|
|
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;
|
|
520
904
|
});
|
|
521
|
-
|
|
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;
|
|
522
912
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
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 ?? []));
|
|
530
917
|
}
|
|
918
|
+
return OFFLINE;
|
|
531
919
|
},
|
|
532
920
|
answer(conversation, answer) {
|
|
533
|
-
return invoke('
|
|
921
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
922
|
+
operation_id: changeBatchIdentity(),
|
|
923
|
+
...answer,
|
|
924
|
+
});
|
|
534
925
|
},
|
|
535
926
|
interrupt(conversation) {
|
|
536
|
-
return invoke('
|
|
927
|
+
return invoke('conversations/interrupt', conversation);
|
|
537
928
|
},
|
|
538
929
|
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;
|
|
930
|
+
return invoke('conversations/kill', conversation);
|
|
547
931
|
},
|
|
548
932
|
delete(conversation) {
|
|
549
|
-
return invoke('
|
|
933
|
+
return invoke('conversations/delete', conversation);
|
|
550
934
|
},
|
|
551
935
|
};
|
|
552
936
|
const chatDraft = {
|
|
553
|
-
write(conversation, draft) {
|
|
554
|
-
return withDraft(conversation, () => ({
|
|
937
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
938
|
+
return withDraft(conversation, (current) => ({
|
|
555
939
|
conversation,
|
|
556
940
|
text: draft.text,
|
|
557
|
-
|
|
558
|
-
|
|
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)),
|
|
559
951
|
}));
|
|
560
952
|
},
|
|
561
953
|
discard(conversation) {
|
|
562
|
-
return withDraft(conversation, (current) =>
|
|
954
|
+
return withDraft(conversation, (current) => current === undefined
|
|
955
|
+
? undefined
|
|
956
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
563
957
|
},
|
|
564
958
|
};
|
|
565
959
|
return {
|
|
566
960
|
surface,
|
|
567
961
|
chatDraft,
|
|
568
|
-
hydrate(held,
|
|
569
|
-
|
|
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();
|
|
570
972
|
reads.clear();
|
|
571
|
-
|
|
572
|
-
view.messages = null;
|
|
973
|
+
partials.clear();
|
|
573
974
|
for (const entry of held) {
|
|
574
|
-
|
|
975
|
+
stored.add(entry.conversation);
|
|
575
976
|
if (entry.read !== undefined)
|
|
576
977
|
reads.set(entry.conversation, entry.read);
|
|
978
|
+
if (entry.partial)
|
|
979
|
+
partials.add(entry.conversation);
|
|
577
980
|
}
|
|
578
981
|
drafts.clear();
|
|
579
|
-
for (const record of
|
|
982
|
+
for (const record of kept)
|
|
580
983
|
drafts.set(record.conversation, record);
|
|
984
|
+
if (listHeld)
|
|
985
|
+
learned();
|
|
581
986
|
},
|
|
582
987
|
async reconcile() {
|
|
583
988
|
if (!listKnown)
|
|
584
989
|
return;
|
|
585
990
|
const named = new Set(listing.keys());
|
|
586
991
|
const leaving = [
|
|
587
|
-
...new Set([...replicated, ...
|
|
588
|
-
].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)));
|
|
589
996
|
for (const conversation of named) {
|
|
590
|
-
if (!replicated.has(conversation))
|
|
997
|
+
if (!replicated.has(conversation) && missed(conversation))
|
|
591
998
|
behind.add(conversation);
|
|
592
999
|
}
|
|
593
1000
|
replicated = named;
|
|
@@ -596,44 +1003,101 @@ export function createConversationStore(deps) {
|
|
|
596
1003
|
await remove(leaving);
|
|
597
1004
|
},
|
|
598
1005
|
async catchUp(progress) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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);
|
|
602
1016
|
if (failed?.kind === 'transport-uncertainty')
|
|
603
1017
|
progress.reachable = false;
|
|
604
|
-
}
|
|
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
|
+
]);
|
|
605
1025
|
if (behind.size > 0)
|
|
606
1026
|
progress.complete = false;
|
|
607
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
|
+
},
|
|
608
1053
|
current() {
|
|
609
1054
|
return behind.size === 0;
|
|
610
1055
|
},
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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();
|
|
614
1067
|
},
|
|
615
1068
|
watch(conversation, options, deliver) {
|
|
616
|
-
const
|
|
617
|
-
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]);
|
|
618
1071
|
const view = views.get(key) ?? {
|
|
619
1072
|
conversation,
|
|
620
|
-
|
|
1073
|
+
asked,
|
|
621
1074
|
watchers: new Set(),
|
|
622
|
-
|
|
1075
|
+
window: null,
|
|
1076
|
+
more: false,
|
|
1077
|
+
total: null,
|
|
623
1078
|
};
|
|
624
1079
|
views.set(key, view);
|
|
625
1080
|
const watcher = { notify: deliver, ready: false };
|
|
626
1081
|
view.watchers.add(watcher);
|
|
627
|
-
|
|
628
|
-
|
|
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)
|
|
629
1091
|
.then(() => {
|
|
630
1092
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
631
1093
|
return;
|
|
632
1094
|
watcher.ready = true;
|
|
633
|
-
|
|
1095
|
+
const window = view.window ?? [];
|
|
1096
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
634
1097
|
})
|
|
635
1098
|
.catch(() => { });
|
|
636
1099
|
return () => {
|
|
1100
|
+
reading.abort();
|
|
637
1101
|
if (!view.watchers.delete(watcher))
|
|
638
1102
|
return;
|
|
639
1103
|
if (view.watchers.size === 0)
|
|
@@ -641,62 +1105,88 @@ export function createConversationStore(deps) {
|
|
|
641
1105
|
};
|
|
642
1106
|
},
|
|
643
1107
|
watchList(deliver) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
647
|
-
deliver(entries());
|
|
648
|
-
});
|
|
1108
|
+
unlisted.add(deliver);
|
|
1109
|
+
learned();
|
|
649
1110
|
return () => {
|
|
1111
|
+
unlisted.delete(deliver);
|
|
650
1112
|
listWatchers.delete(deliver);
|
|
651
1113
|
};
|
|
652
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,
|
|
653
1125
|
changed(set) {
|
|
654
1126
|
for (const conversation of set.conversations)
|
|
655
1127
|
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
1128
|
},
|
|
664
1129
|
receive(event) {
|
|
665
1130
|
if (deps.closed())
|
|
666
1131
|
return false;
|
|
667
1132
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
668
1133
|
if (event.kind === 'list') {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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);
|
|
672
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();
|
|
673
1162
|
}
|
|
674
1163
|
else if (event.kind === 'listed') {
|
|
675
|
-
|
|
1164
|
+
listed(event.entry);
|
|
1165
|
+
relisting?.set(event.entry.conversation, { ...event.entry });
|
|
676
1166
|
}
|
|
677
1167
|
else {
|
|
678
1168
|
listing.delete(event.conversation);
|
|
1169
|
+
relisting?.delete(event.conversation);
|
|
679
1170
|
}
|
|
1171
|
+
keepList();
|
|
680
1172
|
emitList();
|
|
681
|
-
return (event.kind !== 'list' &&
|
|
1173
|
+
return ((event.kind !== 'list' &&
|
|
682
1174
|
listKnown &&
|
|
683
1175
|
(listing.size !== replicated.size ||
|
|
684
|
-
[...listing.keys()].some((conversation) => !replicated.has(conversation))))
|
|
1176
|
+
[...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
|
|
1177
|
+
(listKnown && [...drafts.keys()].some(retiredChat)));
|
|
685
1178
|
}
|
|
686
1179
|
heard(event);
|
|
687
1180
|
return false;
|
|
688
1181
|
},
|
|
689
|
-
messages() {
|
|
690
|
-
return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
|
|
691
|
-
},
|
|
692
|
-
message(conversation, identity) {
|
|
693
|
-
const held = messages.get(conversation)?.find((message) => message.identity === identity);
|
|
694
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
695
|
-
},
|
|
696
1182
|
close() {
|
|
697
1183
|
views.clear();
|
|
698
1184
|
lives.clear();
|
|
1185
|
+
unsent.clear();
|
|
699
1186
|
listWatchers.clear();
|
|
1187
|
+
unlisted.clear();
|
|
1188
|
+
agentWatchers.clear();
|
|
1189
|
+
unnamed.clear();
|
|
700
1190
|
},
|
|
701
1191
|
};
|
|
702
1192
|
}
|