@agentshouse/mdruntime 0.1.0-c09bf842921ff → 0.1.0-c0b4d0492dec3
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 +1 -1
- package/dist/adapter.d.ts +15 -16
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +8 -4
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +206 -76
- package/dist/change.d.ts +1 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/chat-types.d.ts +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +2 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/compile.d.ts +5 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +186 -55
- package/dist/connection.d.ts +8 -2
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1386 -1040
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +47 -0
- package/dist/conversation.d.ts +13 -5
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +354 -192
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +1 -0
- package/dist/figures.d.ts +48 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +3 -3
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/job.d.ts +20 -1
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +26 -0
- package/dist/live.d.ts +4 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +53 -15
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +11 -3
- package/dist/messages.d.ts +25 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +82 -3
- package/dist/model.d.ts +0 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -6
- package/dist/outcome.d.ts +1 -0
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -0
- package/dist/overlay.d.ts +1 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +11 -0
- package/dist/parts.d.ts +59 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +118 -0
- package/dist/query.d.ts +10 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/questions.d.ts +32 -0
- package/dist/questions.d.ts.map +1 -0
- package/dist/questions.js +118 -0
- package/dist/reach.d.ts +5 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +156 -99
- package/dist/read.d.ts +7 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +17 -0
- package/dist/reads.d.ts +35 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +128 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +9 -1
- package/dist/refresh.d.ts +1 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +4 -0
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +23 -6
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +10 -7
- package/dist/source.d.ts +61 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +81 -0
- package/dist/status.d.ts +0 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -6
- package/dist/store.d.ts +85 -25
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +136 -98
- package/dist/stored.d.ts +55 -28
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +187 -74
- package/dist/tables.d.ts +99 -19
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +171 -34
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/wire.d.ts +6 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +3 -0
- package/package.json +2 -2
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
package/dist/conversation.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { cursorOf, decodedCursor } from '@agentshouse/mdmodel';
|
|
2
2
|
import { conversationChangeSet } from './change-set.js';
|
|
3
|
+
import { heldFacts } from './conversation-row.js';
|
|
3
4
|
import { runtimeError } from './failure.js';
|
|
4
5
|
import { refusedForSpace } from './durability.js';
|
|
5
6
|
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
6
7
|
import { OFFLINE } from './outcome.js';
|
|
8
|
+
import { jobsOf, runningLine } from './parts.js';
|
|
9
|
+
import { openQuestion } from './questions.js';
|
|
7
10
|
import { untilAnswered } from './recovery.js';
|
|
8
11
|
import { capped, FROM_START, messageOrder, precedes } from './windows.js';
|
|
9
12
|
const QUIET = {
|
|
@@ -42,11 +45,14 @@ export function unheldView(conversation) {
|
|
|
42
45
|
link: null,
|
|
43
46
|
deliveryUnknown: [],
|
|
44
47
|
chatDraft: null,
|
|
48
|
+
jobs: [],
|
|
45
49
|
};
|
|
46
50
|
}
|
|
47
51
|
export const SESSION_STARTED = 'Session started';
|
|
48
52
|
const NEW_CHAT = 'new:';
|
|
49
53
|
export const HISTORY_CONVERSATIONS = 64;
|
|
54
|
+
// A row is read at a glance and each telling clones the list to every observing tab, while a running line changes at each Kit flush.
|
|
55
|
+
const ROW_INTERVAL_MS = 1_000;
|
|
50
56
|
export function newChat(agent) {
|
|
51
57
|
return `${NEW_CHAT}${agent}`;
|
|
52
58
|
}
|
|
@@ -254,10 +260,6 @@ function returned(draft, submission, others) {
|
|
|
254
260
|
}, others);
|
|
255
261
|
}
|
|
256
262
|
export function createConversationStore(deps) {
|
|
257
|
-
const stored = new Set();
|
|
258
|
-
const reads = new Map();
|
|
259
|
-
const partials = new Set();
|
|
260
|
-
const drafts = new Map();
|
|
261
263
|
const sending = new Map();
|
|
262
264
|
const behind = new Set();
|
|
263
265
|
const catching = new Map();
|
|
@@ -265,15 +267,16 @@ export function createConversationStore(deps) {
|
|
|
265
267
|
const lives = new Map();
|
|
266
268
|
const unsent = new Map();
|
|
267
269
|
const views = new Map();
|
|
268
|
-
const
|
|
270
|
+
const heardNewest = new Map();
|
|
269
271
|
let relisting = null;
|
|
270
|
-
let listCursor = null;
|
|
271
272
|
const creations = new Set();
|
|
272
273
|
const listWatchers = new Set();
|
|
273
274
|
const unlisted = new Set();
|
|
274
275
|
let listKnown = false;
|
|
275
|
-
let
|
|
276
|
-
let
|
|
276
|
+
let listOwed = false;
|
|
277
|
+
let listTelling = false;
|
|
278
|
+
let rowTimer = null;
|
|
279
|
+
const closing = new AbortController();
|
|
277
280
|
const agentWatchers = new Set();
|
|
278
281
|
const unnamed = new Set();
|
|
279
282
|
let agentsShown = null;
|
|
@@ -284,8 +287,7 @@ export function createConversationStore(deps) {
|
|
|
284
287
|
function liveOf(conversation) {
|
|
285
288
|
return lives.get(conversation) ?? QUIET;
|
|
286
289
|
}
|
|
287
|
-
function viewOf(conversation, held, frame) {
|
|
288
|
-
const draft = drafts.get(conversation);
|
|
290
|
+
function viewOf(conversation, held, frame, draft, ended, marked) {
|
|
289
291
|
const facts = liveOf(conversation);
|
|
290
292
|
return {
|
|
291
293
|
conversation,
|
|
@@ -299,16 +301,17 @@ export function createConversationStore(deps) {
|
|
|
299
301
|
turn: structuredClone(facts.turn),
|
|
300
302
|
questions: structuredClone(facts.questions),
|
|
301
303
|
process: facts.process === null ? null : { ...facts.process },
|
|
302
|
-
processEnded:
|
|
304
|
+
processEnded: ended,
|
|
303
305
|
commands: structuredClone(facts.commands),
|
|
304
306
|
options: structuredClone(facts.options),
|
|
305
307
|
starting: facts.starting,
|
|
306
308
|
subagents: structuredClone(facts.subagents),
|
|
307
309
|
link: facts.link === null ? null : { ...facts.link },
|
|
308
310
|
deliveryUnknown: [...facts.deliveryUnknown],
|
|
309
|
-
chatDraft: draft ===
|
|
310
|
-
|
|
311
|
-
|
|
311
|
+
chatDraft: draft === null || unwritten(draft) ? null : { text: draft.text, revision: draft.revision },
|
|
312
|
+
jobs: facts.process?.running === false
|
|
313
|
+
? []
|
|
314
|
+
: jobsOf(marked.filter(({ at }) => ended === null || at > ended), facts.turn),
|
|
312
315
|
};
|
|
313
316
|
}
|
|
314
317
|
function emit(conversation) {
|
|
@@ -320,7 +323,7 @@ export function createConversationStore(deps) {
|
|
|
320
323
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
321
324
|
if (ready.length === 0)
|
|
322
325
|
continue;
|
|
323
|
-
const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
|
|
326
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window), view.draft, view.ended, view.marked);
|
|
324
327
|
for (const watcher of ready)
|
|
325
328
|
watcher.notify(value);
|
|
326
329
|
}
|
|
@@ -330,11 +333,13 @@ export function createConversationStore(deps) {
|
|
|
330
333
|
}
|
|
331
334
|
async function slide(local, conversation, arriving) {
|
|
332
335
|
let count;
|
|
336
|
+
let marked;
|
|
333
337
|
for (const view of views.values()) {
|
|
334
338
|
if (view.conversation !== conversation || view.window === null)
|
|
335
339
|
continue;
|
|
336
340
|
if (arriving === null) {
|
|
337
341
|
view.window = [];
|
|
342
|
+
view.marked = [];
|
|
338
343
|
view.more = false;
|
|
339
344
|
if (view.total !== null)
|
|
340
345
|
view.total = 0;
|
|
@@ -344,6 +349,10 @@ export function createConversationStore(deps) {
|
|
|
344
349
|
const next = windowOf(view.window, joining, view.asked.limit);
|
|
345
350
|
view.window = next.messages;
|
|
346
351
|
view.more ||= next.dropped;
|
|
352
|
+
if (arriving.length > 0) {
|
|
353
|
+
marked ??= await local.jobMarkers(conversation);
|
|
354
|
+
view.marked = marked;
|
|
355
|
+
}
|
|
347
356
|
if (view.total !== null && arriving.length > 0) {
|
|
348
357
|
count ??= await local.messageCount(conversation);
|
|
349
358
|
view.total = count;
|
|
@@ -356,8 +365,24 @@ export function createConversationStore(deps) {
|
|
|
356
365
|
return 'asking';
|
|
357
366
|
return facts.turn === null ? 'idle' : 'working';
|
|
358
367
|
}
|
|
359
|
-
function
|
|
360
|
-
|
|
368
|
+
function rowOf(conversation) {
|
|
369
|
+
const turn = lives.get(conversation)?.turn ?? null;
|
|
370
|
+
return {
|
|
371
|
+
state: stateOf(conversation),
|
|
372
|
+
started: turn?.started ?? null,
|
|
373
|
+
line: turn === null ? null : runningLine(turn.parts),
|
|
374
|
+
question: openQuestion(liveOf(conversation).questions),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
async function entries() {
|
|
378
|
+
const list = await heldList();
|
|
379
|
+
if (list === null)
|
|
380
|
+
return null;
|
|
381
|
+
const held = list.length === 0
|
|
382
|
+
? []
|
|
383
|
+
: await deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
|
|
384
|
+
const rows = new Map(held.map((one) => [one.conversation, one]));
|
|
385
|
+
return list.map((entry) => ({
|
|
361
386
|
conversation: entry.conversation,
|
|
362
387
|
agent: { identity: entry.agent, name: entry.agentName },
|
|
363
388
|
name: entry.name,
|
|
@@ -365,73 +390,153 @@ export function createConversationStore(deps) {
|
|
|
365
390
|
finished: entry.finished,
|
|
366
391
|
outcome: entry.outcome,
|
|
367
392
|
waiting: entry.waiting,
|
|
368
|
-
|
|
369
|
-
|
|
393
|
+
...rowOf(entry.conversation),
|
|
394
|
+
...heldFacts(rows.get(entry.conversation), entry),
|
|
370
395
|
}));
|
|
371
396
|
}
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
397
|
+
async function heldList() {
|
|
398
|
+
const stored = (await (await deps.local()).facts()).conversationList;
|
|
399
|
+
if (stored !== null)
|
|
400
|
+
return stored;
|
|
401
|
+
return !listKnown && deps.stream() !== undefined && deps.online() ? null : [];
|
|
402
|
+
}
|
|
403
|
+
async function endedIn(conversation) {
|
|
404
|
+
const list = await heldList();
|
|
405
|
+
return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
|
|
406
|
+
}
|
|
407
|
+
function listChanged(change) {
|
|
408
|
+
void deps
|
|
409
|
+
.durably(async (local) => {
|
|
410
|
+
const { conversationList, conversationCursor } = await local.facts();
|
|
411
|
+
const kept = change.replacing ? [] : (conversationList ?? []);
|
|
412
|
+
const held = new Map(kept.map((entry) => [entry.conversation, entry]));
|
|
413
|
+
for (const entry of change.set) {
|
|
414
|
+
held.set(entry.conversation, { ...entry });
|
|
415
|
+
creations.delete(entry.conversation);
|
|
416
|
+
}
|
|
417
|
+
if (change.gone !== null)
|
|
418
|
+
held.delete(change.gone);
|
|
419
|
+
const list = [...held.values()];
|
|
420
|
+
if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
|
|
421
|
+
await local.fact('conversationList', list);
|
|
422
|
+
}
|
|
423
|
+
if (change.cursor !== undefined) {
|
|
424
|
+
if (change.cursor !== conversationCursor) {
|
|
425
|
+
await local.fact('conversationCursor', change.cursor);
|
|
426
|
+
}
|
|
427
|
+
deps.stream()?.hold(change.cursor);
|
|
428
|
+
}
|
|
429
|
+
return list;
|
|
430
|
+
})
|
|
431
|
+
.catch(async () => (await (await deps.local()).facts()).conversationList ?? [])
|
|
432
|
+
.then((list) => {
|
|
433
|
+
if (deps.closed() || change.paged === true)
|
|
379
434
|
return;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
435
|
+
endedAnew(list);
|
|
436
|
+
if (change.cursor !== undefined) {
|
|
437
|
+
listKnown = true;
|
|
438
|
+
settleBehind(list);
|
|
439
|
+
openingEnded();
|
|
440
|
+
deps.settled();
|
|
441
|
+
}
|
|
442
|
+
emitList();
|
|
443
|
+
if (!listKnown)
|
|
444
|
+
return;
|
|
445
|
+
retiredDrafts(list);
|
|
446
|
+
if (change.cursor === undefined &&
|
|
447
|
+
(list.length !== replicated.size ||
|
|
448
|
+
list.some(({ conversation }) => !replicated.has(conversation)))) {
|
|
449
|
+
deps.again();
|
|
450
|
+
}
|
|
451
|
+
}, () => { });
|
|
452
|
+
}
|
|
453
|
+
function endedAnew(list) {
|
|
454
|
+
const entries = new Map(list.map((entry) => [entry.conversation, entry]));
|
|
455
|
+
for (const view of views.values()) {
|
|
456
|
+
const ended = entries.get(view.conversation)?.processEnded ?? null;
|
|
457
|
+
if (view.window === null || ended === view.ended)
|
|
458
|
+
continue;
|
|
459
|
+
view.ended = ended;
|
|
460
|
+
emit(view.conversation);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
let listShown = Promise.resolve();
|
|
464
|
+
function shownList(work) {
|
|
465
|
+
const reading = heldList();
|
|
466
|
+
listShown = listShown.then(async () => {
|
|
467
|
+
const list = await reading.catch(() => null);
|
|
468
|
+
if (list === null || deps.closed())
|
|
469
|
+
return;
|
|
470
|
+
work(list);
|
|
388
471
|
});
|
|
389
472
|
}
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
473
|
+
async function told() {
|
|
474
|
+
try {
|
|
475
|
+
while (listOwed && !deps.closed()) {
|
|
476
|
+
listOwed = false;
|
|
477
|
+
const value = await untilAnswered(entries, closing.signal);
|
|
478
|
+
if (deps.closed())
|
|
479
|
+
return;
|
|
480
|
+
if (value === null)
|
|
481
|
+
continue;
|
|
482
|
+
for (const watcher of listWatchers)
|
|
483
|
+
watcher(value);
|
|
484
|
+
for (const watcher of [...unlisted]) {
|
|
485
|
+
unlisted.delete(watcher);
|
|
486
|
+
listWatchers.add(watcher);
|
|
487
|
+
watcher(value);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
listTelling = false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function tellList() {
|
|
496
|
+
if (deps.closed() || (listWatchers.size === 0 && unlisted.size === 0))
|
|
497
|
+
return;
|
|
498
|
+
listOwed = true;
|
|
499
|
+
if (listTelling)
|
|
500
|
+
return;
|
|
501
|
+
listTelling = true;
|
|
502
|
+
void told().catch(() => { });
|
|
503
|
+
}
|
|
504
|
+
function tellSoon() {
|
|
505
|
+
if (rowTimer !== null)
|
|
506
|
+
return;
|
|
507
|
+
rowTimer = setTimeout(() => {
|
|
508
|
+
rowTimer = null;
|
|
509
|
+
tellList();
|
|
510
|
+
}, ROW_INTERVAL_MS);
|
|
396
511
|
}
|
|
397
512
|
function emitList() {
|
|
398
513
|
if (deps.closed())
|
|
399
514
|
return;
|
|
400
|
-
|
|
401
|
-
const value = entries();
|
|
402
|
-
for (const watcher of listWatchers)
|
|
403
|
-
watcher(value);
|
|
404
|
-
}
|
|
515
|
+
tellList();
|
|
405
516
|
emitAgents();
|
|
406
517
|
learned();
|
|
407
518
|
}
|
|
408
519
|
function learned() {
|
|
409
520
|
if (deps.closed())
|
|
410
521
|
return;
|
|
411
|
-
if (
|
|
522
|
+
if (unlisted.size > 0)
|
|
523
|
+
tellList();
|
|
524
|
+
if (unnamed.size === 0)
|
|
412
525
|
return;
|
|
413
|
-
|
|
414
|
-
const value =
|
|
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();
|
|
526
|
+
shownList((list) => {
|
|
527
|
+
const value = agents(list);
|
|
423
528
|
for (const watcher of [...unnamed]) {
|
|
424
529
|
unnamed.delete(watcher);
|
|
425
530
|
agentWatchers.add(watcher);
|
|
426
531
|
watcher(value);
|
|
427
532
|
}
|
|
428
|
-
}
|
|
533
|
+
});
|
|
429
534
|
}
|
|
430
|
-
function agents() {
|
|
535
|
+
function agents(list) {
|
|
431
536
|
const named = new Map();
|
|
432
537
|
for (const agent of listedAgents ?? [])
|
|
433
538
|
named.set(agent.agent, agent);
|
|
434
|
-
for (const entry of
|
|
539
|
+
for (const entry of list) {
|
|
435
540
|
named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
|
|
436
541
|
}
|
|
437
542
|
return [...named]
|
|
@@ -461,31 +566,33 @@ export function createConversationStore(deps) {
|
|
|
461
566
|
function emitAgents() {
|
|
462
567
|
if (deps.closed() || agentWatchers.size === 0)
|
|
463
568
|
return;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
watcher
|
|
569
|
+
shownList((list) => {
|
|
570
|
+
const value = agents(list);
|
|
571
|
+
const shown = JSON.stringify(value);
|
|
572
|
+
if (shown === agentsShown)
|
|
573
|
+
return;
|
|
574
|
+
agentsShown = shown;
|
|
575
|
+
for (const watcher of agentWatchers)
|
|
576
|
+
watcher(value);
|
|
577
|
+
});
|
|
471
578
|
}
|
|
472
579
|
function setLive(conversation, next) {
|
|
473
|
-
const before =
|
|
474
|
-
const began = lives.get(conversation)?.turn?.started;
|
|
580
|
+
const before = rowOf(conversation);
|
|
475
581
|
if (next === null)
|
|
476
582
|
lives.delete(conversation);
|
|
477
583
|
else
|
|
478
584
|
lives.set(conversation, next);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
585
|
+
const after = rowOf(conversation);
|
|
586
|
+
if (after.state !== before.state || after.started !== before.started)
|
|
482
587
|
emitList();
|
|
588
|
+
else if (after.line !== before.line || after.question !== before.question)
|
|
589
|
+
tellSoon();
|
|
483
590
|
}
|
|
484
|
-
function retiredChat(conversation) {
|
|
591
|
+
function retiredChat(conversation, list) {
|
|
485
592
|
if (!conversation.startsWith(NEW_CHAT))
|
|
486
593
|
return false;
|
|
487
594
|
const agent = conversation.slice(NEW_CHAT.length);
|
|
488
|
-
return
|
|
595
|
+
return list.some((entry) => entry.agent === agent && entry.agentRetired);
|
|
489
596
|
}
|
|
490
597
|
function live(conversation, change) {
|
|
491
598
|
const owed = unsent.get(conversation);
|
|
@@ -543,17 +650,23 @@ export function createConversationStore(deps) {
|
|
|
543
650
|
}
|
|
544
651
|
async function remove(leaving) {
|
|
545
652
|
await deps.durably(async (local) => {
|
|
653
|
+
const kept = new Set([
|
|
654
|
+
...(await local.conversations(leaving)).map(({ conversation }) => conversation),
|
|
655
|
+
...(await local.chatDrafts())
|
|
656
|
+
.filter(({ conversation }) => leaving.includes(conversation))
|
|
657
|
+
.map(({ conversation }) => conversation),
|
|
658
|
+
]);
|
|
546
659
|
await local.dropConversations(leaving);
|
|
547
660
|
const changed = [];
|
|
548
661
|
for (const conversation of leaving) {
|
|
549
|
-
if (
|
|
662
|
+
if (kept.has(conversation) || lives.has(conversation))
|
|
550
663
|
changed.push(conversation);
|
|
664
|
+
for (const view of views.values()) {
|
|
665
|
+
if (view.conversation === conversation)
|
|
666
|
+
view.draft = null;
|
|
551
667
|
}
|
|
552
|
-
stored.delete(conversation);
|
|
553
|
-
reads.delete(conversation);
|
|
554
|
-
partials.delete(conversation);
|
|
555
|
-
drafts.delete(conversation);
|
|
556
668
|
behind.delete(conversation);
|
|
669
|
+
heardNewest.delete(conversation);
|
|
557
670
|
unsent.delete(conversation);
|
|
558
671
|
setLive(conversation, null);
|
|
559
672
|
await slide(local, conversation, null);
|
|
@@ -562,19 +675,68 @@ export function createConversationStore(deps) {
|
|
|
562
675
|
deps.publish(conversationChangeSet(changed));
|
|
563
676
|
});
|
|
564
677
|
}
|
|
565
|
-
function
|
|
566
|
-
|
|
567
|
-
|
|
678
|
+
function newestOfListed(entry) {
|
|
679
|
+
if (entry === undefined)
|
|
680
|
+
return undefined;
|
|
681
|
+
const heard = heardNewest.get(entry.conversation);
|
|
682
|
+
if (heard === undefined)
|
|
683
|
+
return entry.newest;
|
|
684
|
+
return entry.newest === null || heard > entry.newest ? heard : entry.newest;
|
|
685
|
+
}
|
|
686
|
+
function missed(conversation, held, entry) {
|
|
687
|
+
const read = held?.read;
|
|
688
|
+
const newest = newestOfListed(entry);
|
|
568
689
|
return (read === undefined ||
|
|
569
690
|
newest === undefined ||
|
|
570
|
-
|
|
691
|
+
held?.partial === true ||
|
|
571
692
|
(newest !== null && (read === null || newest > read)));
|
|
572
693
|
}
|
|
573
|
-
function
|
|
574
|
-
|
|
575
|
-
|
|
694
|
+
async function readsOf(local, conversations) {
|
|
695
|
+
return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
|
|
696
|
+
}
|
|
697
|
+
function retiredDrafts(list) {
|
|
698
|
+
if (!list.some((entry) => entry.agentRetired))
|
|
576
699
|
return;
|
|
577
|
-
|
|
700
|
+
void deps
|
|
701
|
+
.local()
|
|
702
|
+
.then((local) => local.chatDrafts())
|
|
703
|
+
.then((held) => {
|
|
704
|
+
if (!deps.closed() && held.some(({ conversation }) => retiredChat(conversation, list))) {
|
|
705
|
+
deps.again();
|
|
706
|
+
}
|
|
707
|
+
}, () => { });
|
|
708
|
+
}
|
|
709
|
+
function settleBehind(list) {
|
|
710
|
+
void deps
|
|
711
|
+
.durably(async (local) => {
|
|
712
|
+
const entries = new Map(list.map((entry) => [entry.conversation, entry]));
|
|
713
|
+
const named = [...replicated].filter((conversation) => entries.has(conversation));
|
|
714
|
+
const held = await readsOf(local, named);
|
|
715
|
+
let owed = false;
|
|
716
|
+
for (const conversation of named) {
|
|
717
|
+
if (!missed(conversation, held.get(conversation), entries.get(conversation))) {
|
|
718
|
+
behind.delete(conversation);
|
|
719
|
+
}
|
|
720
|
+
else if (!behind.has(conversation)) {
|
|
721
|
+
gap(conversation);
|
|
722
|
+
owed = true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return owed;
|
|
726
|
+
})
|
|
727
|
+
.then((owed) => {
|
|
728
|
+
if (deps.closed())
|
|
729
|
+
return;
|
|
730
|
+
deps.settled();
|
|
731
|
+
if (owed)
|
|
732
|
+
deps.again();
|
|
733
|
+
}, () => { });
|
|
734
|
+
}
|
|
735
|
+
function noted(conversation, message) {
|
|
736
|
+
const heard = heardNewest.get(conversation);
|
|
737
|
+
if (heard === undefined || message.position > heard) {
|
|
738
|
+
heardNewest.set(conversation, message.position);
|
|
739
|
+
}
|
|
578
740
|
}
|
|
579
741
|
function gap(conversation) {
|
|
580
742
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
@@ -584,7 +746,7 @@ export function createConversationStore(deps) {
|
|
|
584
746
|
return deps.durably(async (local) => {
|
|
585
747
|
if (!replicated.has(conversation))
|
|
586
748
|
return;
|
|
587
|
-
const read =
|
|
749
|
+
const read = (await readsOf(local, [conversation])).get(conversation)?.read;
|
|
588
750
|
const advances = read !== undefined &&
|
|
589
751
|
!behind.has(conversation) &&
|
|
590
752
|
(read === null || message.position > read);
|
|
@@ -594,19 +756,18 @@ export function createConversationStore(deps) {
|
|
|
594
756
|
messages: [message],
|
|
595
757
|
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
596
758
|
});
|
|
597
|
-
stored.add(conversation);
|
|
598
|
-
if (advances)
|
|
599
|
-
reads.set(conversation, message.position);
|
|
600
759
|
sealTurns(conversation, new Set([message.turn]));
|
|
601
760
|
await slide(local, conversation, [message]);
|
|
602
761
|
deps.publish(conversationChangeSet([conversation]));
|
|
762
|
+
tellList();
|
|
603
763
|
});
|
|
604
764
|
}
|
|
605
765
|
async function readHistories(conversations, ended) {
|
|
606
766
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
767
|
+
const held = await readsOf(await deps.local(), conversations);
|
|
607
768
|
let waiting = conversations.map((conversation) => ({
|
|
608
769
|
conversation,
|
|
609
|
-
after:
|
|
770
|
+
after: held.get(conversation)?.read ?? undefined,
|
|
610
771
|
}));
|
|
611
772
|
while (waiting.length > 0) {
|
|
612
773
|
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
@@ -662,15 +823,10 @@ export function createConversationStore(deps) {
|
|
|
662
823
|
return;
|
|
663
824
|
const whole = next === undefined;
|
|
664
825
|
const cursor = next ?? newestOf(arriving, from);
|
|
665
|
-
const
|
|
826
|
+
const held = (await readsOf(local, [conversation])).get(conversation);
|
|
827
|
+
const partial = !whole && (held?.partial === true || held?.read === undefined);
|
|
666
828
|
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
667
829
|
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);
|
|
674
830
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
675
831
|
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
676
832
|
behind.delete(conversation);
|
|
@@ -678,6 +834,8 @@ export function createConversationStore(deps) {
|
|
|
678
834
|
if (arriving.length > 0 || sealing || whole) {
|
|
679
835
|
deps.publish(conversationChangeSet([conversation]));
|
|
680
836
|
}
|
|
837
|
+
if (arriving.length > 0)
|
|
838
|
+
tellSoon();
|
|
681
839
|
});
|
|
682
840
|
}
|
|
683
841
|
function heard(event) {
|
|
@@ -744,17 +902,24 @@ export function createConversationStore(deps) {
|
|
|
744
902
|
emit(conversation);
|
|
745
903
|
}
|
|
746
904
|
async function kept(local, conversation, following) {
|
|
747
|
-
|
|
905
|
+
const shown = following === null || empty(following) ? null : following;
|
|
906
|
+
if (shown === null)
|
|
748
907
|
await local.dropChatDraft(conversation);
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
908
|
+
else
|
|
909
|
+
await local.keepChatDrafts([shown]);
|
|
910
|
+
shownDraft(conversation, shown);
|
|
911
|
+
}
|
|
912
|
+
function shownDraft(conversation, draft) {
|
|
913
|
+
for (const view of views.values()) {
|
|
914
|
+
if (view.conversation === conversation)
|
|
915
|
+
view.draft = draft;
|
|
754
916
|
}
|
|
755
917
|
}
|
|
918
|
+
async function draftAt(local, conversation) {
|
|
919
|
+
return (await local.chatDraft(conversation)) ?? undefined;
|
|
920
|
+
}
|
|
756
921
|
async function sealed(local, conversation, turns) {
|
|
757
|
-
const held =
|
|
922
|
+
const held = await draftAt(local, conversation);
|
|
758
923
|
const submitted = held?.submitted ?? [];
|
|
759
924
|
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
760
925
|
if (held === undefined || left.length === submitted.length)
|
|
@@ -765,7 +930,7 @@ export function createConversationStore(deps) {
|
|
|
765
930
|
function moved(agent, operation, to) {
|
|
766
931
|
const from = newChat(agent);
|
|
767
932
|
return deps.durably(async (local) => {
|
|
768
|
-
const held =
|
|
933
|
+
const held = await draftAt(local, from);
|
|
769
934
|
const earlier = held?.moves ?? [];
|
|
770
935
|
if (earlier.includes(operation))
|
|
771
936
|
return;
|
|
@@ -775,18 +940,20 @@ export function createConversationStore(deps) {
|
|
|
775
940
|
revision: chatDraftRevision(),
|
|
776
941
|
};
|
|
777
942
|
const { moves: _answered, ...unsent } = held ?? blank;
|
|
778
|
-
const taken =
|
|
943
|
+
const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
|
|
944
|
+
? []
|
|
945
|
+
: [{ ...unsent, conversation: to }];
|
|
779
946
|
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
780
947
|
await local.keepChatDrafts([left, ...taken]);
|
|
781
|
-
|
|
948
|
+
shownDraft(from, left);
|
|
782
949
|
for (const one of taken)
|
|
783
|
-
|
|
950
|
+
shownDraft(to, one);
|
|
784
951
|
deps.publish(conversationChangeSet([from, to]));
|
|
785
952
|
});
|
|
786
953
|
}
|
|
787
954
|
function withDraft(conversation, next) {
|
|
788
955
|
return deps.durably(async (local) => {
|
|
789
|
-
const following = await next(
|
|
956
|
+
const following = await next(await draftAt(local, conversation), local);
|
|
790
957
|
if (following === undefined)
|
|
791
958
|
return;
|
|
792
959
|
await kept(local, conversation, following);
|
|
@@ -868,7 +1035,8 @@ export function createConversationStore(deps) {
|
|
|
868
1035
|
return deps.durably(async (local) => {
|
|
869
1036
|
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
|
|
870
1037
|
const total = asked.total ? await local.messageCount(conversation, phrase) : null;
|
|
871
|
-
|
|
1038
|
+
const marked = await local.jobMarkers(conversation);
|
|
1039
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(conversation), marked));
|
|
872
1040
|
});
|
|
873
1041
|
},
|
|
874
1042
|
async create(input) {
|
|
@@ -876,8 +1044,10 @@ export function createConversationStore(deps) {
|
|
|
876
1044
|
const conversation = answered(result, 'conversation_id');
|
|
877
1045
|
if (conversation === null)
|
|
878
1046
|
return result;
|
|
879
|
-
|
|
1047
|
+
const listed = (await heldList()) ?? [];
|
|
1048
|
+
if (!listed.some((entry) => entry.conversation === conversation)) {
|
|
880
1049
|
creations.add(conversation);
|
|
1050
|
+
}
|
|
881
1051
|
const creation = creationOf(input);
|
|
882
1052
|
if (creation !== null)
|
|
883
1053
|
await moved(creation.agent, creation.operation, conversation);
|
|
@@ -945,43 +1115,40 @@ export function createConversationStore(deps) {
|
|
|
945
1115
|
return {
|
|
946
1116
|
surface,
|
|
947
1117
|
chatDraft,
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
listCursor = cursor;
|
|
955
|
-
deps.stream()?.hold(cursor);
|
|
956
|
-
}
|
|
957
|
-
stored.clear();
|
|
958
|
-
reads.clear();
|
|
959
|
-
partials.clear();
|
|
960
|
-
for (const entry of held) {
|
|
961
|
-
stored.add(entry.conversation);
|
|
962
|
-
if (entry.read !== undefined)
|
|
963
|
-
reads.set(entry.conversation, entry.read);
|
|
964
|
-
if (entry.partial)
|
|
965
|
-
partials.add(entry.conversation);
|
|
966
|
-
}
|
|
967
|
-
drafts.clear();
|
|
968
|
-
for (const record of kept)
|
|
969
|
-
drafts.set(record.conversation, record);
|
|
970
|
-
if (listHeld)
|
|
971
|
-
learned();
|
|
1118
|
+
async opened(local) {
|
|
1119
|
+
const { conversationList, conversationCursor } = await local.facts();
|
|
1120
|
+
if (conversationList === null || listKnown)
|
|
1121
|
+
return;
|
|
1122
|
+
deps.stream()?.hold(conversationCursor);
|
|
1123
|
+
learned();
|
|
972
1124
|
},
|
|
973
1125
|
async reconcile() {
|
|
974
1126
|
if (!listKnown)
|
|
975
1127
|
return;
|
|
976
|
-
const
|
|
1128
|
+
const local = await deps.local();
|
|
1129
|
+
const [held, kept, { conversationList }] = await Promise.all([
|
|
1130
|
+
local.conversations(),
|
|
1131
|
+
local.chatDrafts(),
|
|
1132
|
+
local.facts(),
|
|
1133
|
+
]);
|
|
1134
|
+
const list = conversationList ?? [];
|
|
1135
|
+
const entries = new Map(list.map((entry) => [entry.conversation, entry]));
|
|
1136
|
+
const named = new Set(entries.keys());
|
|
1137
|
+
const reads = new Map(held.map((one) => [one.conversation, one]));
|
|
977
1138
|
const leaving = [
|
|
978
|
-
...new Set([
|
|
1139
|
+
...new Set([
|
|
1140
|
+
...replicated,
|
|
1141
|
+
...reads.keys(),
|
|
1142
|
+
...kept.map(({ conversation }) => conversation),
|
|
1143
|
+
]),
|
|
979
1144
|
].filter((conversation) => !named.has(conversation) &&
|
|
980
1145
|
!creations.has(conversation) &&
|
|
981
|
-
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
1146
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
|
|
982
1147
|
for (const conversation of named) {
|
|
983
|
-
if (!replicated.has(conversation) &&
|
|
1148
|
+
if (!replicated.has(conversation) &&
|
|
1149
|
+
missed(conversation, reads.get(conversation), entries.get(conversation))) {
|
|
984
1150
|
behind.add(conversation);
|
|
1151
|
+
}
|
|
985
1152
|
}
|
|
986
1153
|
replicated = named;
|
|
987
1154
|
handReplicated();
|
|
@@ -1012,12 +1179,16 @@ export function createConversationStore(deps) {
|
|
|
1012
1179
|
progress.complete = false;
|
|
1013
1180
|
},
|
|
1014
1181
|
async resend(progress) {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1182
|
+
const local = await deps.local();
|
|
1183
|
+
const tried = new Set();
|
|
1184
|
+
for (const { conversation } of await local.chatDrafts()) {
|
|
1185
|
+
for (;;) {
|
|
1186
|
+
const submission = (await local.chatDraft(conversation))?.submitted?.find((one) => one.turn === null && !sending.has(one.operation) && !tried.has(one.operation));
|
|
1187
|
+
if (submission === undefined)
|
|
1188
|
+
break;
|
|
1019
1189
|
if (deps.closed())
|
|
1020
1190
|
return;
|
|
1191
|
+
tried.add(submission.operation);
|
|
1021
1192
|
const result = await dispatched(conversation, submission);
|
|
1022
1193
|
if (result.kind === 'revoked-authorization')
|
|
1023
1194
|
return;
|
|
@@ -1055,6 +1226,9 @@ export function createConversationStore(deps) {
|
|
|
1055
1226
|
asked,
|
|
1056
1227
|
watchers: new Set(),
|
|
1057
1228
|
window: null,
|
|
1229
|
+
draft: null,
|
|
1230
|
+
ended: null,
|
|
1231
|
+
marked: [],
|
|
1058
1232
|
more: false,
|
|
1059
1233
|
total: null,
|
|
1060
1234
|
};
|
|
@@ -1066,7 +1240,10 @@ export function createConversationStore(deps) {
|
|
|
1066
1240
|
if (view.window !== null)
|
|
1067
1241
|
return;
|
|
1068
1242
|
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
1243
|
+
view.draft = (await local.chatDraft(conversation)) ?? null;
|
|
1244
|
+
view.ended = await endedIn(conversation);
|
|
1069
1245
|
view.window = messages;
|
|
1246
|
+
view.marked = await local.jobMarkers(conversation);
|
|
1070
1247
|
view.more = more;
|
|
1071
1248
|
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1072
1249
|
}), reading.signal)
|
|
@@ -1075,7 +1252,7 @@ export function createConversationStore(deps) {
|
|
|
1075
1252
|
return;
|
|
1076
1253
|
watcher.ready = true;
|
|
1077
1254
|
const window = view.window ?? [];
|
|
1078
|
-
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
1255
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
|
|
1079
1256
|
})
|
|
1080
1257
|
.catch(() => { });
|
|
1081
1258
|
return () => {
|
|
@@ -1110,58 +1287,43 @@ export function createConversationStore(deps) {
|
|
|
1110
1287
|
},
|
|
1111
1288
|
receive(event) {
|
|
1112
1289
|
if (deps.closed())
|
|
1113
|
-
return
|
|
1114
|
-
if (event.kind === 'list'
|
|
1115
|
-
if (event.
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
for (const entry of event.entries)
|
|
1119
|
-
|
|
1120
|
-
listed(entry);
|
|
1121
|
-
else
|
|
1122
|
-
relisting.set(entry.conversation, { ...entry });
|
|
1123
|
-
}
|
|
1124
|
-
if (event.more)
|
|
1125
|
-
return false;
|
|
1126
|
-
if (relisting !== null) {
|
|
1127
|
-
listing.clear();
|
|
1128
|
-
for (const entry of relisting.values())
|
|
1129
|
-
listed(entry);
|
|
1130
|
-
relisting = null;
|
|
1131
|
-
}
|
|
1132
|
-
listCursor = event.cursor;
|
|
1133
|
-
deps.stream()?.hold(event.cursor);
|
|
1134
|
-
listKnown = true;
|
|
1135
|
-
for (const conversation of replicated) {
|
|
1136
|
-
if (!listing.has(conversation))
|
|
1137
|
-
continue;
|
|
1138
|
-
if (!missed(conversation))
|
|
1139
|
-
behind.delete(conversation);
|
|
1140
|
-
else if (!behind.has(conversation))
|
|
1141
|
-
gap(conversation);
|
|
1142
|
-
}
|
|
1143
|
-
openingEnded();
|
|
1144
|
-
}
|
|
1145
|
-
else if (event.kind === 'listed') {
|
|
1146
|
-
listed(event.entry);
|
|
1147
|
-
relisting?.set(event.entry.conversation, { ...event.entry });
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
listing.delete(event.conversation);
|
|
1151
|
-
relisting?.delete(event.conversation);
|
|
1290
|
+
return;
|
|
1291
|
+
if (event.kind === 'list') {
|
|
1292
|
+
if (event.whole)
|
|
1293
|
+
relisting ??= new Map();
|
|
1294
|
+
if (relisting !== null) {
|
|
1295
|
+
for (const entry of event.entries)
|
|
1296
|
+
relisting.set(entry.conversation, { ...entry });
|
|
1152
1297
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1298
|
+
if (event.more && relisting !== null)
|
|
1299
|
+
return;
|
|
1300
|
+
const whole = relisting;
|
|
1301
|
+
relisting = null;
|
|
1302
|
+
listChanged({
|
|
1303
|
+
replacing: whole !== null,
|
|
1304
|
+
set: whole === null ? event.entries : [...whole.values()],
|
|
1305
|
+
gone: null,
|
|
1306
|
+
...(event.more ? { paged: true } : { cursor: event.cursor }),
|
|
1307
|
+
});
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (event.kind === 'listed') {
|
|
1311
|
+
relisting?.set(event.entry.conversation, { ...event.entry });
|
|
1312
|
+
listChanged({ replacing: false, set: [event.entry], gone: null });
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (event.kind === 'unlisted') {
|
|
1316
|
+
relisting?.delete(event.conversation);
|
|
1317
|
+
listChanged({ replacing: false, set: [], gone: event.conversation });
|
|
1318
|
+
return;
|
|
1160
1319
|
}
|
|
1161
1320
|
heard(event);
|
|
1162
|
-
return false;
|
|
1163
1321
|
},
|
|
1164
1322
|
close() {
|
|
1323
|
+
closing.abort();
|
|
1324
|
+
if (rowTimer !== null)
|
|
1325
|
+
clearTimeout(rowTimer);
|
|
1326
|
+
rowTimer = null;
|
|
1165
1327
|
views.clear();
|
|
1166
1328
|
lives.clear();
|
|
1167
1329
|
unsent.clear();
|