@agentshouse/mdruntime 0.1.0-cdf82f77fae68 → 0.1.0-ceb97106cc2fd
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/AGENTS.md +259 -272
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +37 -9
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- package/dist/bootstrap.d.ts +2 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +32 -6
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +36 -10
- package/dist/chat/acts.d.ts +3 -4
- package/dist/chat/acts.d.ts.map +1 -1
- package/dist/chat/acts.js +10 -10
- package/dist/chat/kit.d.ts +2 -1
- package/dist/chat/kit.d.ts.map +1 -1
- package/dist/chat/kit.js +3 -3
- package/dist/chat/list.d.ts +8 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +10 -6
- package/dist/chat/markdown.d.ts +2 -1
- package/dist/chat/markdown.d.ts.map +1 -1
- package/dist/chat/markdown.js +7 -6
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +2 -2
- package/dist/chat/messages.d.ts +1 -0
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +4 -0
- package/dist/chat/provider.d.ts +1 -1
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +51 -67
- package/dist/chat/ready.d.ts +1 -0
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +4 -4
- package/dist/chat/style.js +3 -3
- package/dist/client.d.ts +12 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/collection.d.ts +17 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +230 -19
- package/dist/connection.d.ts +24 -7
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1340 -318
- package/dist/conversation.d.ts +14 -7
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +328 -137
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +18 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +89 -12
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/memory.d.ts +4 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +23 -2
- package/dist/model.d.ts +4 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/observation.d.ts +5 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +22 -11
- package/dist/outcome.d.ts +4 -0
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +1 -0
- package/dist/overlay.d.ts +2 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +58 -55
- package/dist/query.d.ts +34 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +7 -1
- package/dist/reach.d.ts +6 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +17 -5
- package/dist/react.d.ts +7 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +6 -4
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +285 -83
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +17 -10
- package/dist/replica.d.ts +1 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +57 -20
- package/dist/room-draft.d.ts +13 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +2 -2
- package/dist/rows.d.ts +5 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +5 -13
- package/dist/save.d.ts +6 -2
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +42 -18
- package/dist/search.d.ts +34 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +116 -45
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +45 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +315 -85
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +136 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +48 -31
- package/package.json +12 -8
package/dist/conversation.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { conversationChangeSet } from './change-set.js';
|
|
2
2
|
import { runtimeError } from './failure.js';
|
|
3
3
|
import { declaredFile } from './file.js';
|
|
4
|
-
import { chatDraftRevision } from './identity.js';
|
|
4
|
+
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
5
|
+
import { OFFLINE } from './outcome.js';
|
|
5
6
|
const QUIET = {
|
|
6
7
|
turn: null,
|
|
7
8
|
questions: [],
|
|
@@ -21,6 +22,17 @@ export function unheldView(conversation) {
|
|
|
21
22
|
chatDraft: null,
|
|
22
23
|
};
|
|
23
24
|
}
|
|
25
|
+
export const SESSION_STARTED = 'Session started';
|
|
26
|
+
const NEW_CHAT = 'new:';
|
|
27
|
+
export function newChat(agent) {
|
|
28
|
+
return `${NEW_CHAT}${agent}`;
|
|
29
|
+
}
|
|
30
|
+
function creationOf(input) {
|
|
31
|
+
if (typeof input !== 'object' || input === null)
|
|
32
|
+
return null;
|
|
33
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
34
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
35
|
+
}
|
|
24
36
|
function startedTurn(turn, operation) {
|
|
25
37
|
if (operation.op === 'begin')
|
|
26
38
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -77,11 +89,15 @@ function ordered(left, right) {
|
|
|
77
89
|
return left.identity < right.identity ? -1 : 1;
|
|
78
90
|
return 0;
|
|
79
91
|
}
|
|
80
|
-
function
|
|
92
|
+
function windowOf(held, arriving, limit) {
|
|
81
93
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
82
94
|
for (const message of arriving)
|
|
83
95
|
byIdentity.set(message.identity, message);
|
|
84
|
-
|
|
96
|
+
const next = [...byIdentity.values()].sort(ordered);
|
|
97
|
+
return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
|
|
98
|
+
}
|
|
99
|
+
function newestOf(arriving, from) {
|
|
100
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
85
101
|
}
|
|
86
102
|
function acceptedMessage(value) {
|
|
87
103
|
if (typeof value !== 'object' || value === null)
|
|
@@ -140,14 +156,14 @@ function historyPages(result) {
|
|
|
140
156
|
}
|
|
141
157
|
return pages;
|
|
142
158
|
}
|
|
143
|
-
function
|
|
159
|
+
function answered(result, field) {
|
|
144
160
|
if (result.kind !== 'action')
|
|
145
161
|
return null;
|
|
146
162
|
const answer = result.result;
|
|
147
163
|
if (typeof answer !== 'object' || answer === null)
|
|
148
164
|
return null;
|
|
149
|
-
const
|
|
150
|
-
return typeof
|
|
165
|
+
const value = answer[field];
|
|
166
|
+
return typeof value === 'string' ? value : null;
|
|
151
167
|
}
|
|
152
168
|
function limitOf(options) {
|
|
153
169
|
const { limit } = options;
|
|
@@ -158,9 +174,35 @@ function limitOf(options) {
|
|
|
158
174
|
}
|
|
159
175
|
return limit;
|
|
160
176
|
}
|
|
177
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
178
|
+
function unwritten(draft) {
|
|
179
|
+
return draft.text === '' && draft.files.length === 0;
|
|
180
|
+
}
|
|
181
|
+
function pending(draft) {
|
|
182
|
+
return {
|
|
183
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
184
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function empty(draft) {
|
|
188
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
189
|
+
}
|
|
190
|
+
function withSubmitted(draft, submitted) {
|
|
191
|
+
const { submitted: _held, ...unsent } = draft;
|
|
192
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
193
|
+
}
|
|
194
|
+
function returned(draft, submission, others) {
|
|
195
|
+
return withSubmitted({
|
|
196
|
+
conversation: draft.conversation,
|
|
197
|
+
text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
|
|
198
|
+
files: [...submission.files, ...draft.files],
|
|
199
|
+
revision: chatDraftRevision(),
|
|
200
|
+
}, others);
|
|
201
|
+
}
|
|
161
202
|
export function createConversationStore(deps) {
|
|
162
|
-
const
|
|
203
|
+
const stored = new Set();
|
|
163
204
|
const reads = new Map();
|
|
205
|
+
const partials = new Set();
|
|
164
206
|
const drafts = new Map();
|
|
165
207
|
const sending = new Map();
|
|
166
208
|
const behind = new Set();
|
|
@@ -168,6 +210,7 @@ export function createConversationStore(deps) {
|
|
|
168
210
|
const lives = new Map();
|
|
169
211
|
const views = new Map();
|
|
170
212
|
const listing = new Map();
|
|
213
|
+
const creations = new Set();
|
|
171
214
|
const listWatchers = new Set();
|
|
172
215
|
let listKnown = false;
|
|
173
216
|
const agentWatchers = new Set();
|
|
@@ -179,49 +222,51 @@ export function createConversationStore(deps) {
|
|
|
179
222
|
function liveOf(conversation) {
|
|
180
223
|
return lives.get(conversation) ?? QUIET;
|
|
181
224
|
}
|
|
182
|
-
function
|
|
183
|
-
const held = messages.get(conversation) ?? [];
|
|
184
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
185
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
186
|
-
identity,
|
|
187
|
-
turn,
|
|
188
|
-
text,
|
|
189
|
-
message: structuredClone(message),
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
225
|
+
function viewOf(conversation, held) {
|
|
193
226
|
const draft = drafts.get(conversation);
|
|
194
227
|
const facts = liveOf(conversation);
|
|
195
228
|
return {
|
|
196
229
|
conversation,
|
|
197
|
-
messages:
|
|
230
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
231
|
+
identity,
|
|
232
|
+
turn,
|
|
233
|
+
text,
|
|
234
|
+
message,
|
|
235
|
+
})),
|
|
198
236
|
turn: structuredClone(facts.turn),
|
|
199
237
|
questions: structuredClone(facts.questions),
|
|
200
238
|
process: facts.process === null ? null : { ...facts.process },
|
|
201
239
|
commands: structuredClone(facts.commands),
|
|
202
240
|
options: structuredClone(facts.options),
|
|
203
|
-
chatDraft: draft === undefined || draft
|
|
241
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
204
242
|
? null
|
|
205
243
|
: { text: draft.text, files: [...draft.files], revision: draft.revision },
|
|
206
244
|
};
|
|
207
245
|
}
|
|
208
|
-
function emit(conversation
|
|
246
|
+
function emit(conversation) {
|
|
209
247
|
if (deps.closed())
|
|
210
248
|
return;
|
|
211
249
|
for (const view of views.values()) {
|
|
212
|
-
if (view.conversation !== conversation)
|
|
250
|
+
if (view.conversation !== conversation || view.window === null)
|
|
213
251
|
continue;
|
|
214
|
-
if (messagesChanged)
|
|
215
|
-
view.messages = null;
|
|
216
252
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
217
253
|
if (ready.length === 0)
|
|
218
254
|
continue;
|
|
219
|
-
|
|
220
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
255
|
+
const value = viewOf(conversation, view.window);
|
|
221
256
|
for (const watcher of ready)
|
|
222
257
|
watcher.notify(value);
|
|
223
258
|
}
|
|
224
259
|
}
|
|
260
|
+
function slide(conversation, arriving) {
|
|
261
|
+
for (const view of views.values()) {
|
|
262
|
+
if (view.conversation !== conversation || view.window === null)
|
|
263
|
+
continue;
|
|
264
|
+
view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function changesOf(conversation, arriving) {
|
|
268
|
+
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
269
|
+
}
|
|
225
270
|
function stateOf(conversation) {
|
|
226
271
|
const facts = liveOf(conversation);
|
|
227
272
|
if (facts.questions.length > 0)
|
|
@@ -278,7 +323,10 @@ export function createConversationStore(deps) {
|
|
|
278
323
|
const declaration = deps.agents();
|
|
279
324
|
if (declaration === undefined)
|
|
280
325
|
return;
|
|
281
|
-
const
|
|
326
|
+
const held = await deps.documents(declaration);
|
|
327
|
+
if ('pending' in held)
|
|
328
|
+
return;
|
|
329
|
+
const { documents } = held;
|
|
282
330
|
const names = new Map();
|
|
283
331
|
const retired = new Set();
|
|
284
332
|
for (const document of documents) {
|
|
@@ -301,6 +349,12 @@ export function createConversationStore(deps) {
|
|
|
301
349
|
if (named)
|
|
302
350
|
emitList();
|
|
303
351
|
emitAgents();
|
|
352
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
353
|
+
if (ended.length > 0)
|
|
354
|
+
await remove(ended);
|
|
355
|
+
}
|
|
356
|
+
function retiredChat(conversation) {
|
|
357
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
304
358
|
}
|
|
305
359
|
function agentsReady() {
|
|
306
360
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -310,7 +364,7 @@ export function createConversationStore(deps) {
|
|
|
310
364
|
}
|
|
311
365
|
function live(conversation, change) {
|
|
312
366
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
313
|
-
emit(conversation
|
|
367
|
+
emit(conversation);
|
|
314
368
|
}
|
|
315
369
|
function relayed(conversation, turn, operation) {
|
|
316
370
|
const held = liveOf(conversation).turn;
|
|
@@ -338,21 +392,25 @@ export function createConversationStore(deps) {
|
|
|
338
392
|
}
|
|
339
393
|
async function remove(leaving) {
|
|
340
394
|
await deps.durably(async (local) => {
|
|
341
|
-
await local.dropConversations(leaving);
|
|
342
395
|
const removed = [];
|
|
343
|
-
const changed = [];
|
|
344
396
|
for (const conversation of leaving) {
|
|
345
|
-
for (const
|
|
346
|
-
removed.push({ conversation, identity:
|
|
397
|
+
for (const identity of await local.messageIdentities(conversation)) {
|
|
398
|
+
removed.push({ conversation, identity, text: null });
|
|
347
399
|
}
|
|
348
|
-
|
|
400
|
+
}
|
|
401
|
+
await local.dropConversations(leaving);
|
|
402
|
+
const changed = [];
|
|
403
|
+
for (const conversation of leaving) {
|
|
404
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
349
405
|
changed.push(conversation);
|
|
350
406
|
}
|
|
351
|
-
|
|
407
|
+
stored.delete(conversation);
|
|
352
408
|
reads.delete(conversation);
|
|
409
|
+
partials.delete(conversation);
|
|
353
410
|
drafts.delete(conversation);
|
|
354
411
|
behind.delete(conversation);
|
|
355
412
|
setLive(conversation, null);
|
|
413
|
+
slide(conversation, null);
|
|
356
414
|
}
|
|
357
415
|
if (changed.length > 0)
|
|
358
416
|
deps.publish(conversationChangeSet(removed, changed));
|
|
@@ -370,30 +428,25 @@ export function createConversationStore(deps) {
|
|
|
370
428
|
const advances = read !== undefined &&
|
|
371
429
|
!behind.has(conversation) &&
|
|
372
430
|
(read === null || message.position > read);
|
|
373
|
-
|
|
431
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
374
432
|
await local.keepMessages({
|
|
375
433
|
conversation,
|
|
376
434
|
messages: [message],
|
|
377
|
-
...(advances ? { read: message.position } : {}),
|
|
378
|
-
sealed,
|
|
435
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
379
436
|
});
|
|
380
|
-
|
|
437
|
+
stored.add(conversation);
|
|
381
438
|
if (advances)
|
|
382
439
|
reads.set(conversation, message.position);
|
|
383
|
-
if (sealed)
|
|
384
|
-
drafts.delete(conversation);
|
|
385
440
|
sealTurns(conversation, new Set([message.turn]));
|
|
386
|
-
|
|
441
|
+
slide(conversation, [message]);
|
|
442
|
+
deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
|
|
387
443
|
});
|
|
388
444
|
}
|
|
389
445
|
async function readHistories(conversations) {
|
|
390
446
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
391
|
-
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
392
|
-
const arriving = new Map();
|
|
393
|
-
const complete = new Set();
|
|
394
447
|
let asking = conversations.map((conversation) => ({
|
|
395
448
|
conversation,
|
|
396
|
-
after:
|
|
449
|
+
after: reads.get(conversation) ?? undefined,
|
|
397
450
|
}));
|
|
398
451
|
while (asking.length > 0) {
|
|
399
452
|
const result = await deps.call('conversation.history', {
|
|
@@ -405,46 +458,41 @@ export function createConversationStore(deps) {
|
|
|
405
458
|
const pages = historyPages(result);
|
|
406
459
|
if (pages === null)
|
|
407
460
|
return result;
|
|
408
|
-
const asked = new
|
|
461
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
409
462
|
const following = [];
|
|
410
463
|
for (const page of pages) {
|
|
411
464
|
if (!asked.has(page.conversation))
|
|
412
465
|
continue;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
...page.messages,
|
|
416
|
-
]);
|
|
417
|
-
if (page.next === undefined)
|
|
418
|
-
complete.add(page.conversation);
|
|
419
|
-
else
|
|
466
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
467
|
+
if (page.next !== undefined) {
|
|
420
468
|
following.push({ conversation: page.conversation, after: page.next });
|
|
469
|
+
}
|
|
421
470
|
}
|
|
422
471
|
asking = following;
|
|
423
472
|
}
|
|
424
|
-
for (const [conversation, held] of arriving) {
|
|
425
|
-
if (!complete.has(conversation))
|
|
426
|
-
continue;
|
|
427
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
428
|
-
}
|
|
429
473
|
return null;
|
|
430
474
|
}
|
|
431
|
-
function
|
|
475
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
432
476
|
return deps.durably(async (local) => {
|
|
433
477
|
if (!replicated.has(conversation))
|
|
434
478
|
return;
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
await local
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
479
|
+
const whole = next === undefined;
|
|
480
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
481
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
482
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
483
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
484
|
+
stored.add(conversation);
|
|
485
|
+
reads.set(conversation, cursor);
|
|
486
|
+
if (partial)
|
|
487
|
+
partials.add(conversation);
|
|
488
|
+
else
|
|
489
|
+
partials.delete(conversation);
|
|
443
490
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
444
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
491
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
445
492
|
behind.delete(conversation);
|
|
446
|
-
|
|
447
|
-
|
|
493
|
+
slide(conversation, arriving);
|
|
494
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
495
|
+
deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
|
|
448
496
|
}
|
|
449
497
|
});
|
|
450
498
|
}
|
|
@@ -457,7 +505,7 @@ export function createConversationStore(deps) {
|
|
|
457
505
|
if (held === undefined)
|
|
458
506
|
return;
|
|
459
507
|
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
460
|
-
emit(conversation
|
|
508
|
+
emit(conversation);
|
|
461
509
|
return;
|
|
462
510
|
}
|
|
463
511
|
if (event.kind === 'turn') {
|
|
@@ -490,22 +538,126 @@ export function createConversationStore(deps) {
|
|
|
490
538
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
491
539
|
emit(conversation);
|
|
492
540
|
}
|
|
541
|
+
async function kept(local, conversation, following) {
|
|
542
|
+
if (following === null || empty(following)) {
|
|
543
|
+
await local.dropChatDraft(conversation);
|
|
544
|
+
drafts.delete(conversation);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
await local.keepChatDrafts([following]);
|
|
548
|
+
drafts.set(conversation, following);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function sealed(local, conversation, turns) {
|
|
552
|
+
const held = drafts.get(conversation);
|
|
553
|
+
const submitted = held?.submitted ?? [];
|
|
554
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
555
|
+
if (held === undefined || left.length === submitted.length)
|
|
556
|
+
return false;
|
|
557
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
function moved(agent, operation, to) {
|
|
561
|
+
const from = newChat(agent);
|
|
562
|
+
return deps.durably(async (local) => {
|
|
563
|
+
const held = drafts.get(from);
|
|
564
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
565
|
+
if (earlier.includes(operation))
|
|
566
|
+
return;
|
|
567
|
+
const blank = {
|
|
568
|
+
conversation: from,
|
|
569
|
+
text: '',
|
|
570
|
+
files: [],
|
|
571
|
+
revision: chatDraftRevision(),
|
|
572
|
+
};
|
|
573
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
574
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
575
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
576
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
577
|
+
drafts.set(from, left);
|
|
578
|
+
for (const one of taken)
|
|
579
|
+
drafts.set(to, one);
|
|
580
|
+
deps.publish(conversationChangeSet([], [from, to]));
|
|
581
|
+
});
|
|
582
|
+
}
|
|
493
583
|
function withDraft(conversation, next) {
|
|
494
584
|
return deps.durably(async (local) => {
|
|
495
|
-
const following = next(drafts.get(conversation));
|
|
585
|
+
const following = await next(drafts.get(conversation), local);
|
|
496
586
|
if (following === undefined)
|
|
497
587
|
return;
|
|
498
|
-
|
|
499
|
-
await local.dropChatDraft(conversation);
|
|
500
|
-
drafts.delete(conversation);
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
await local.keepChatDraft(following);
|
|
504
|
-
drafts.set(conversation, following);
|
|
505
|
-
}
|
|
588
|
+
await kept(local, conversation, following);
|
|
506
589
|
deps.publish(conversationChangeSet([], [conversation]));
|
|
507
590
|
});
|
|
508
591
|
}
|
|
592
|
+
async function submitted(conversation, operation, draft) {
|
|
593
|
+
let submission;
|
|
594
|
+
let earlier = false;
|
|
595
|
+
await withDraft(conversation, (current) => {
|
|
596
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
597
|
+
earlier = submission !== undefined;
|
|
598
|
+
if (submission !== undefined)
|
|
599
|
+
return undefined;
|
|
600
|
+
const said = draft ?? current;
|
|
601
|
+
submission = {
|
|
602
|
+
operation,
|
|
603
|
+
text: said?.text ?? '',
|
|
604
|
+
files: [...(said?.files ?? [])],
|
|
605
|
+
turn: null,
|
|
606
|
+
};
|
|
607
|
+
return {
|
|
608
|
+
conversation,
|
|
609
|
+
...(draft === undefined || current === undefined
|
|
610
|
+
? { text: '', files: [], revision: chatDraftRevision() }
|
|
611
|
+
: { text: current.text, files: current.files, revision: current.revision }),
|
|
612
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
613
|
+
};
|
|
614
|
+
});
|
|
615
|
+
return { submission: submission, earlier };
|
|
616
|
+
}
|
|
617
|
+
function forgotten(conversation, submission) {
|
|
618
|
+
return withDraft(conversation, (current) => {
|
|
619
|
+
const held = current?.submitted ?? [];
|
|
620
|
+
if (current === undefined || !held.includes(submission))
|
|
621
|
+
return undefined;
|
|
622
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
async function dispatched(conversation, submission) {
|
|
626
|
+
const dispatch = {};
|
|
627
|
+
sending.set(submission.operation, dispatch);
|
|
628
|
+
try {
|
|
629
|
+
const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
|
|
630
|
+
const result = await invoke('conversation.send', conversation, {
|
|
631
|
+
text: submission.text,
|
|
632
|
+
files,
|
|
633
|
+
operation_id: submission.operation,
|
|
634
|
+
});
|
|
635
|
+
if (result.kind === 'revoked-authorization')
|
|
636
|
+
return result;
|
|
637
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
638
|
+
deps.unanswered();
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
const turn = answered(result, 'turn');
|
|
642
|
+
await withDraft(conversation, async (current, local) => {
|
|
643
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
644
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
645
|
+
return undefined;
|
|
646
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
647
|
+
if (result.kind !== 'action')
|
|
648
|
+
return returned(current, held, others);
|
|
649
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
650
|
+
return withSubmitted(current, turn === null || arrived
|
|
651
|
+
? others
|
|
652
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
653
|
+
});
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
finally {
|
|
657
|
+
if (sending.get(submission.operation) === dispatch)
|
|
658
|
+
sending.delete(submission.operation);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
509
661
|
function invoke(name, conversation, extra = {}) {
|
|
510
662
|
return deps.call(name, { conversation, ...extra });
|
|
511
663
|
}
|
|
@@ -516,46 +668,34 @@ export function createConversationStore(deps) {
|
|
|
516
668
|
},
|
|
517
669
|
async read(conversation, options = {}) {
|
|
518
670
|
const limit = limitOf(options);
|
|
519
|
-
return deps.durably(() =>
|
|
520
|
-
},
|
|
521
|
-
create(input) {
|
|
522
|
-
return deps.call('conversation.create', input);
|
|
671
|
+
return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
|
|
523
672
|
},
|
|
524
|
-
async
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
if (
|
|
528
|
-
sending.set(conversation, pending);
|
|
529
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
530
|
-
}
|
|
531
|
-
try {
|
|
532
|
-
const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
|
|
533
|
-
...(await declaredFile(file)),
|
|
534
|
-
blob: file.blob,
|
|
535
|
-
})));
|
|
536
|
-
const result = await invoke('conversation.send', conversation, {
|
|
537
|
-
text: draft?.text ?? '',
|
|
538
|
-
files,
|
|
539
|
-
});
|
|
540
|
-
const turn = turnOf(result);
|
|
541
|
-
if (turn === null || draft === undefined)
|
|
542
|
-
return result;
|
|
543
|
-
await withDraft(conversation, (current) => {
|
|
544
|
-
if (current !== draft)
|
|
545
|
-
return undefined;
|
|
546
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
547
|
-
return sealed ? null : { ...draft, turn };
|
|
548
|
-
});
|
|
673
|
+
async create(input) {
|
|
674
|
+
const result = await deps.call('conversation.create', input);
|
|
675
|
+
const conversation = answered(result, 'conversation');
|
|
676
|
+
if (conversation === null)
|
|
549
677
|
return result;
|
|
678
|
+
if (!listing.has(conversation))
|
|
679
|
+
creations.add(conversation);
|
|
680
|
+
const creation = creationOf(input);
|
|
681
|
+
if (creation !== null)
|
|
682
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
683
|
+
return result;
|
|
684
|
+
},
|
|
685
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
686
|
+
if (deps.online()) {
|
|
687
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
688
|
+
if (!earlier || !deps.expired(operation))
|
|
689
|
+
return dispatched(conversation, submission);
|
|
690
|
+
await forgotten(conversation, submission);
|
|
691
|
+
return EXPIRED;
|
|
550
692
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
556
|
-
}
|
|
557
|
-
}
|
|
693
|
+
if (draft !== undefined) {
|
|
694
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
695
|
+
? undefined
|
|
696
|
+
: returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
558
697
|
}
|
|
698
|
+
return OFFLINE;
|
|
559
699
|
},
|
|
560
700
|
answer(conversation, answer) {
|
|
561
701
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -570,7 +710,12 @@ export function createConversationStore(deps) {
|
|
|
570
710
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
571
711
|
if (result.kind !== 'action')
|
|
572
712
|
return result;
|
|
573
|
-
await withDraft(conversation, (current) =>
|
|
713
|
+
await withDraft(conversation, (current) => {
|
|
714
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
715
|
+
if (current === undefined || held === undefined)
|
|
716
|
+
return undefined;
|
|
717
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
718
|
+
});
|
|
574
719
|
return result;
|
|
575
720
|
},
|
|
576
721
|
delete(conversation) {
|
|
@@ -579,33 +724,42 @@ export function createConversationStore(deps) {
|
|
|
579
724
|
};
|
|
580
725
|
const chatDraft = {
|
|
581
726
|
write(conversation, draft, revision = chatDraftRevision()) {
|
|
582
|
-
return withDraft(conversation, () => ({
|
|
727
|
+
return withDraft(conversation, (current) => ({
|
|
583
728
|
conversation,
|
|
584
729
|
text: draft.text,
|
|
585
730
|
files: [...draft.files],
|
|
586
|
-
turn: null,
|
|
587
731
|
revision,
|
|
732
|
+
...(current === undefined ? {} : pending(current)),
|
|
588
733
|
}));
|
|
589
734
|
},
|
|
590
735
|
discard(conversation) {
|
|
591
|
-
return withDraft(conversation, (current) =>
|
|
736
|
+
return withDraft(conversation, (current) => current === undefined
|
|
737
|
+
? undefined
|
|
738
|
+
: {
|
|
739
|
+
conversation,
|
|
740
|
+
text: '',
|
|
741
|
+
files: [],
|
|
742
|
+
revision: chatDraftRevision(),
|
|
743
|
+
...pending(current),
|
|
744
|
+
});
|
|
592
745
|
},
|
|
593
746
|
};
|
|
594
747
|
return {
|
|
595
748
|
surface,
|
|
596
749
|
chatDraft,
|
|
597
|
-
hydrate(held,
|
|
598
|
-
|
|
750
|
+
hydrate(held, kept) {
|
|
751
|
+
stored.clear();
|
|
599
752
|
reads.clear();
|
|
600
|
-
|
|
601
|
-
view.messages = null;
|
|
753
|
+
partials.clear();
|
|
602
754
|
for (const entry of held) {
|
|
603
|
-
|
|
755
|
+
stored.add(entry.conversation);
|
|
604
756
|
if (entry.read !== undefined)
|
|
605
757
|
reads.set(entry.conversation, entry.read);
|
|
758
|
+
if (entry.partial)
|
|
759
|
+
partials.add(entry.conversation);
|
|
606
760
|
}
|
|
607
761
|
drafts.clear();
|
|
608
|
-
for (const record of
|
|
762
|
+
for (const record of kept)
|
|
609
763
|
drafts.set(record.conversation, record);
|
|
610
764
|
},
|
|
611
765
|
async reconcile() {
|
|
@@ -613,8 +767,10 @@ export function createConversationStore(deps) {
|
|
|
613
767
|
return;
|
|
614
768
|
const named = new Set(listing.keys());
|
|
615
769
|
const leaving = [
|
|
616
|
-
...new Set([...replicated, ...
|
|
617
|
-
].filter((conversation) => !named.has(conversation)
|
|
770
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
771
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
772
|
+
!creations.has(conversation) &&
|
|
773
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
618
774
|
for (const conversation of named) {
|
|
619
775
|
if (!replicated.has(conversation))
|
|
620
776
|
behind.add(conversation);
|
|
@@ -634,13 +790,44 @@ export function createConversationStore(deps) {
|
|
|
634
790
|
if (behind.size > 0)
|
|
635
791
|
progress.complete = false;
|
|
636
792
|
},
|
|
793
|
+
async resend(progress) {
|
|
794
|
+
for (const [conversation, held] of [...drafts]) {
|
|
795
|
+
for (const submission of held.submitted ?? []) {
|
|
796
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
797
|
+
continue;
|
|
798
|
+
if (deps.closed())
|
|
799
|
+
return;
|
|
800
|
+
if (deps.expired(submission.operation)) {
|
|
801
|
+
await forgotten(conversation, submission);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const result = await dispatched(conversation, submission);
|
|
805
|
+
if (result.kind === 'revoked-authorization')
|
|
806
|
+
return;
|
|
807
|
+
if (result.kind === 'transport-uncertainty') {
|
|
808
|
+
progress.reachable = false;
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
812
|
+
progress.complete = false;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
},
|
|
637
818
|
current() {
|
|
638
819
|
return behind.size === 0;
|
|
639
820
|
},
|
|
821
|
+
known() {
|
|
822
|
+
return listKnown;
|
|
823
|
+
},
|
|
640
824
|
reconnected() {
|
|
641
825
|
for (const conversation of replicated)
|
|
642
826
|
gap(conversation);
|
|
643
827
|
},
|
|
828
|
+
reopened() {
|
|
829
|
+
sending.clear();
|
|
830
|
+
},
|
|
644
831
|
watch(conversation, options, deliver) {
|
|
645
832
|
const limit = limitOf(options);
|
|
646
833
|
const key = JSON.stringify([conversation, limit ?? null]);
|
|
@@ -648,18 +835,20 @@ export function createConversationStore(deps) {
|
|
|
648
835
|
conversation,
|
|
649
836
|
limit,
|
|
650
837
|
watchers: new Set(),
|
|
651
|
-
|
|
838
|
+
window: null,
|
|
652
839
|
};
|
|
653
840
|
views.set(key, view);
|
|
654
841
|
const watcher = { notify: deliver, ready: false };
|
|
655
842
|
view.watchers.add(watcher);
|
|
656
843
|
void deps
|
|
657
|
-
.durably(() =>
|
|
844
|
+
.durably(async (local) => {
|
|
845
|
+
view.window ??= await local.messageWindow(conversation, limit);
|
|
846
|
+
})
|
|
658
847
|
.then(() => {
|
|
659
848
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
660
849
|
return;
|
|
661
850
|
watcher.ready = true;
|
|
662
|
-
watcher.notify(viewOf(conversation,
|
|
851
|
+
watcher.notify(viewOf(conversation, view.window ?? []));
|
|
663
852
|
})
|
|
664
853
|
.catch(() => { });
|
|
665
854
|
return () => {
|
|
@@ -706,12 +895,15 @@ export function createConversationStore(deps) {
|
|
|
706
895
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
707
896
|
if (event.kind === 'list') {
|
|
708
897
|
listing.clear();
|
|
709
|
-
for (const entry of event.entries)
|
|
898
|
+
for (const entry of event.entries) {
|
|
710
899
|
listing.set(entry.conversation, { ...entry });
|
|
900
|
+
creations.delete(entry.conversation);
|
|
901
|
+
}
|
|
711
902
|
listKnown = true;
|
|
712
903
|
}
|
|
713
904
|
else if (event.kind === 'listed') {
|
|
714
905
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
906
|
+
creations.delete(event.entry.conversation);
|
|
715
907
|
}
|
|
716
908
|
else {
|
|
717
909
|
listing.delete(event.conversation);
|
|
@@ -725,12 +917,11 @@ export function createConversationStore(deps) {
|
|
|
725
917
|
heard(event);
|
|
726
918
|
return false;
|
|
727
919
|
},
|
|
728
|
-
|
|
729
|
-
return [...
|
|
920
|
+
searchable() {
|
|
921
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
730
922
|
},
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
923
|
+
acquired(conversation) {
|
|
924
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
734
925
|
},
|
|
735
926
|
close() {
|
|
736
927
|
views.clear();
|