@agentshouse/mdruntime 0.1.0-c0bed82db8d73 → 0.1.0-c0c1e81da9166
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 +3 -2
- package/dist/adapter.d.ts +101 -71
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +4 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +35 -46
- package/dist/bootstrap.d.ts +16 -15
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +273 -131
- package/dist/chain.d.ts +2 -1
- package/dist/chain.d.ts.map +1 -1
- 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 +15 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +54 -6
- package/dist/collection.d.ts +7 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +24 -19
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +278 -282
- package/dist/connection.d.ts +16 -11
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2312 -1988
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- 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 +27 -18
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +489 -260
- package/dist/durability.d.ts +6 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +3 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +18 -8
- package/dist/figures.d.ts +48 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/file.d.ts +2 -11
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +6 -4
- package/dist/host.d.ts +3 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +26 -29
- package/dist/identity.d.ts +0 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -3
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/job.d.ts +33 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +54 -0
- package/dist/live.d.ts +5 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +62 -20
- 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 +13 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +218 -91
- package/dist/messages.d.ts +27 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +97 -19
- package/dist/model.d.ts +8 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -18
- package/dist/observation.d.ts +3 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +8 -3
- package/dist/outcome.d.ts +13 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +16 -2
- 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/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/query.d.ts +18 -207
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- 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 +18 -11
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +201 -126
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/read.d.ts +7 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +17 -0
- package/dist/readiness.d.ts +3 -17
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +35 -1
- package/dist/refresh.d.ts +7 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +34 -10
- package/dist/replica.d.ts +10 -9
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +53 -21
- package/dist/room-draft.d.ts +21 -5
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +98 -51
- package/dist/rows.d.ts +18 -16
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +45 -153
- package/dist/save.d.ts +2 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +22 -13
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- 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 +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store.d.ts +104 -62
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +159 -161
- package/dist/stored.d.ts +66 -55
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +343 -273
- package/dist/tables.d.ts +110 -65
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +188 -99
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/validation.d.ts +3 -4
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +42 -100
- package/dist/windows.d.ts +1 -12
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +2 -75
- package/dist/wire.d.ts +18 -8
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +6 -2
- package/package.json +2 -3
- package/AGENTS.md +0 -312
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -368
- package/dist/reads.d.ts +0 -11
- package/dist/reads.d.ts.map +0 -1
- package/dist/reads.js +0 -100
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -150
package/dist/conversation.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { cursorOf, decodedCursor } from '@agentshouse/mdmodel';
|
|
1
2
|
import { conversationChangeSet } from './change-set.js';
|
|
3
|
+
import { heldFacts } from './conversation-row.js';
|
|
2
4
|
import { runtimeError } from './failure.js';
|
|
3
5
|
import { refusedForSpace } from './durability.js';
|
|
4
|
-
import { declaredFile } from './file.js';
|
|
5
6
|
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
6
7
|
import { OFFLINE } from './outcome.js';
|
|
7
|
-
import {
|
|
8
|
+
import { jobsOf, runningLine } from './parts.js';
|
|
9
|
+
import { openQuestion } from './questions.js';
|
|
10
|
+
import { untilAnswered } from './recovery.js';
|
|
11
|
+
import { capped, FROM_START, messageOrder, precedes } from './windows.js';
|
|
8
12
|
const QUIET = {
|
|
9
13
|
turn: null,
|
|
10
14
|
questions: [],
|
|
@@ -12,6 +16,7 @@ const QUIET = {
|
|
|
12
16
|
commands: [],
|
|
13
17
|
options: [],
|
|
14
18
|
starting: null,
|
|
19
|
+
subagents: [],
|
|
15
20
|
link: null,
|
|
16
21
|
deliveryUnknown: [],
|
|
17
22
|
};
|
|
@@ -21,6 +26,7 @@ const FACTS = [
|
|
|
21
26
|
'commands',
|
|
22
27
|
'options',
|
|
23
28
|
'starting',
|
|
29
|
+
'subagents',
|
|
24
30
|
'link',
|
|
25
31
|
'deliveryUnknown',
|
|
26
32
|
];
|
|
@@ -31,27 +37,30 @@ export function unheldView(conversation) {
|
|
|
31
37
|
turn: null,
|
|
32
38
|
questions: [],
|
|
33
39
|
process: null,
|
|
40
|
+
processEnded: null,
|
|
34
41
|
commands: [],
|
|
35
42
|
options: [],
|
|
36
43
|
starting: null,
|
|
44
|
+
subagents: [],
|
|
37
45
|
link: null,
|
|
38
46
|
deliveryUnknown: [],
|
|
39
47
|
chatDraft: null,
|
|
48
|
+
jobs: [],
|
|
40
49
|
};
|
|
41
50
|
}
|
|
42
51
|
export const SESSION_STARTED = 'Session started';
|
|
43
52
|
const NEW_CHAT = 'new:';
|
|
44
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;
|
|
45
56
|
export function newChat(agent) {
|
|
46
57
|
return `${NEW_CHAT}${agent}`;
|
|
47
58
|
}
|
|
48
59
|
function creationOf(input) {
|
|
49
60
|
if (typeof input !== 'object' || input === null)
|
|
50
61
|
return null;
|
|
51
|
-
const { agent_id: agent, operation_id: operation
|
|
52
|
-
return typeof agent === 'string' && typeof operation === 'string'
|
|
53
|
-
? { agent, operation }
|
|
54
|
-
: null;
|
|
62
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
63
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
55
64
|
}
|
|
56
65
|
function startedTurn(turn, operation) {
|
|
57
66
|
if (operation.op === 'begin')
|
|
@@ -210,7 +219,14 @@ function askedOf(options) {
|
|
|
210
219
|
}
|
|
211
220
|
return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
|
|
212
221
|
}
|
|
213
|
-
|
|
222
|
+
function phraseOf(text) {
|
|
223
|
+
if (text === undefined)
|
|
224
|
+
return null;
|
|
225
|
+
if (typeof text !== 'string' || text === '') {
|
|
226
|
+
throw runtimeError('invalid-text', 'a conversation is found by a phrase');
|
|
227
|
+
}
|
|
228
|
+
return text;
|
|
229
|
+
}
|
|
214
230
|
const DURABLE_WRITE = {
|
|
215
231
|
kind: 'rejected',
|
|
216
232
|
code: 'durable-write',
|
|
@@ -244,10 +260,6 @@ function returned(draft, submission, others) {
|
|
|
244
260
|
}, others);
|
|
245
261
|
}
|
|
246
262
|
export function createConversationStore(deps) {
|
|
247
|
-
const stored = new Set();
|
|
248
|
-
const reads = new Map();
|
|
249
|
-
const partials = new Set();
|
|
250
|
-
const drafts = new Map();
|
|
251
263
|
const sending = new Map();
|
|
252
264
|
const behind = new Set();
|
|
253
265
|
const catching = new Map();
|
|
@@ -255,21 +267,27 @@ export function createConversationStore(deps) {
|
|
|
255
267
|
const lives = new Map();
|
|
256
268
|
const unsent = new Map();
|
|
257
269
|
const views = new Map();
|
|
258
|
-
const
|
|
270
|
+
const heardNewest = new Map();
|
|
271
|
+
let relisting = null;
|
|
259
272
|
const creations = new Set();
|
|
260
273
|
const listWatchers = new Set();
|
|
274
|
+
const unlisted = new Set();
|
|
261
275
|
let listKnown = false;
|
|
276
|
+
let listOwed = false;
|
|
277
|
+
let listTelling = false;
|
|
278
|
+
let rowTimer = null;
|
|
279
|
+
const closing = new AbortController();
|
|
262
280
|
const agentWatchers = new Set();
|
|
263
|
-
|
|
264
|
-
let
|
|
265
|
-
let
|
|
281
|
+
const unnamed = new Set();
|
|
282
|
+
let agentsShown = null;
|
|
283
|
+
let listedAgents = null;
|
|
284
|
+
let agentsReading = false;
|
|
266
285
|
let replicated = new Set();
|
|
267
286
|
let handedReplicated;
|
|
268
287
|
function liveOf(conversation) {
|
|
269
288
|
return lives.get(conversation) ?? QUIET;
|
|
270
289
|
}
|
|
271
|
-
function viewOf(conversation, held, frame) {
|
|
272
|
-
const draft = drafts.get(conversation);
|
|
290
|
+
function viewOf(conversation, held, frame, draft, ended, marked) {
|
|
273
291
|
const facts = liveOf(conversation);
|
|
274
292
|
return {
|
|
275
293
|
conversation,
|
|
@@ -283,14 +301,17 @@ export function createConversationStore(deps) {
|
|
|
283
301
|
turn: structuredClone(facts.turn),
|
|
284
302
|
questions: structuredClone(facts.questions),
|
|
285
303
|
process: facts.process === null ? null : { ...facts.process },
|
|
304
|
+
processEnded: ended,
|
|
286
305
|
commands: structuredClone(facts.commands),
|
|
287
306
|
options: structuredClone(facts.options),
|
|
288
307
|
starting: facts.starting,
|
|
308
|
+
subagents: structuredClone(facts.subagents),
|
|
289
309
|
link: facts.link === null ? null : { ...facts.link },
|
|
290
310
|
deliveryUnknown: [...facts.deliveryUnknown],
|
|
291
|
-
chatDraft: draft ===
|
|
292
|
-
|
|
293
|
-
|
|
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),
|
|
294
315
|
};
|
|
295
316
|
}
|
|
296
317
|
function emit(conversation) {
|
|
@@ -302,7 +323,7 @@ export function createConversationStore(deps) {
|
|
|
302
323
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
303
324
|
if (ready.length === 0)
|
|
304
325
|
continue;
|
|
305
|
-
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);
|
|
306
327
|
for (const watcher of ready)
|
|
307
328
|
watcher.notify(value);
|
|
308
329
|
}
|
|
@@ -312,11 +333,13 @@ export function createConversationStore(deps) {
|
|
|
312
333
|
}
|
|
313
334
|
async function slide(local, conversation, arriving) {
|
|
314
335
|
let count;
|
|
336
|
+
let marked;
|
|
315
337
|
for (const view of views.values()) {
|
|
316
338
|
if (view.conversation !== conversation || view.window === null)
|
|
317
339
|
continue;
|
|
318
340
|
if (arriving === null) {
|
|
319
341
|
view.window = [];
|
|
342
|
+
view.marked = [];
|
|
320
343
|
view.more = false;
|
|
321
344
|
if (view.total !== null)
|
|
322
345
|
view.total = 0;
|
|
@@ -326,6 +349,10 @@ export function createConversationStore(deps) {
|
|
|
326
349
|
const next = windowOf(view.window, joining, view.asked.limit);
|
|
327
350
|
view.window = next.messages;
|
|
328
351
|
view.more ||= next.dropped;
|
|
352
|
+
if (arriving.length > 0) {
|
|
353
|
+
marked ??= await local.jobMarkers(conversation);
|
|
354
|
+
view.marked = marked;
|
|
355
|
+
}
|
|
329
356
|
if (view.total !== null && arriving.length > 0) {
|
|
330
357
|
count ??= await local.messageCount(conversation);
|
|
331
358
|
view.total = count;
|
|
@@ -338,102 +365,234 @@ export function createConversationStore(deps) {
|
|
|
338
365
|
return 'asking';
|
|
339
366
|
return facts.turn === null ? 'idle' : 'working';
|
|
340
367
|
}
|
|
341
|
-
function
|
|
342
|
-
|
|
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) => ({
|
|
343
386
|
conversation: entry.conversation,
|
|
344
|
-
agent: { identity: entry.agent, name:
|
|
387
|
+
agent: { identity: entry.agent, name: entry.agentName },
|
|
345
388
|
name: entry.name,
|
|
346
389
|
created: entry.created,
|
|
347
390
|
finished: entry.finished,
|
|
348
391
|
outcome: entry.outcome,
|
|
349
392
|
waiting: entry.waiting,
|
|
350
|
-
|
|
351
|
-
|
|
393
|
+
...rowOf(entry.conversation),
|
|
394
|
+
...heldFacts(rows.get(entry.conversation), entry),
|
|
352
395
|
}));
|
|
353
396
|
}
|
|
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)
|
|
434
|
+
return;
|
|
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);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
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);
|
|
511
|
+
}
|
|
354
512
|
function emitList() {
|
|
355
|
-
if (deps.closed()
|
|
513
|
+
if (deps.closed())
|
|
356
514
|
return;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
515
|
+
tellList();
|
|
516
|
+
emitAgents();
|
|
517
|
+
learned();
|
|
360
518
|
}
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
519
|
+
function learned() {
|
|
520
|
+
if (deps.closed())
|
|
521
|
+
return;
|
|
522
|
+
if (unlisted.size > 0)
|
|
523
|
+
tellList();
|
|
524
|
+
if (unnamed.size === 0)
|
|
525
|
+
return;
|
|
526
|
+
shownList((list) => {
|
|
527
|
+
const value = agents(list);
|
|
528
|
+
for (const watcher of [...unnamed]) {
|
|
529
|
+
unnamed.delete(watcher);
|
|
530
|
+
agentWatchers.add(watcher);
|
|
531
|
+
watcher(value);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function agents(list) {
|
|
536
|
+
const named = new Map();
|
|
537
|
+
for (const agent of listedAgents ?? [])
|
|
538
|
+
named.set(agent.agent, agent);
|
|
539
|
+
for (const entry of list) {
|
|
540
|
+
named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
|
|
541
|
+
}
|
|
542
|
+
return [...named]
|
|
543
|
+
.filter(([, agent]) => !agent.retired)
|
|
544
|
+
.map(([identity, agent]) => ({ identity, name: agent.name }))
|
|
365
545
|
.sort((left, right) => left.name === right.name
|
|
366
546
|
? left.identity.localeCompare(right.identity)
|
|
367
547
|
: left.name.localeCompare(right.name));
|
|
368
548
|
}
|
|
549
|
+
function readAgents() {
|
|
550
|
+
const stream = deps.stream();
|
|
551
|
+
if (stream?.agents === undefined || !deps.online() || agentsReading)
|
|
552
|
+
return;
|
|
553
|
+
agentsReading = true;
|
|
554
|
+
void stream
|
|
555
|
+
.agents()
|
|
556
|
+
.then((read) => {
|
|
557
|
+
if (read === null)
|
|
558
|
+
return;
|
|
559
|
+
listedAgents = read;
|
|
560
|
+
emitAgents();
|
|
561
|
+
}, () => { })
|
|
562
|
+
.finally(() => {
|
|
563
|
+
agentsReading = false;
|
|
564
|
+
});
|
|
565
|
+
}
|
|
369
566
|
function emitAgents() {
|
|
370
567
|
if (deps.closed() || agentWatchers.size === 0)
|
|
371
568
|
return;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
+
});
|
|
375
578
|
}
|
|
376
579
|
function setLive(conversation, next) {
|
|
377
|
-
const before =
|
|
378
|
-
const began = lives.get(conversation)?.turn?.started;
|
|
580
|
+
const before = rowOf(conversation);
|
|
379
581
|
if (next === null)
|
|
380
582
|
lives.delete(conversation);
|
|
381
583
|
else
|
|
382
584
|
lives.set(conversation, next);
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
585
|
+
const after = rowOf(conversation);
|
|
586
|
+
if (after.state !== before.state || after.started !== before.started)
|
|
386
587
|
emitList();
|
|
588
|
+
else if (after.line !== before.line || after.question !== before.question)
|
|
589
|
+
tellSoon();
|
|
387
590
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const retired = new Set();
|
|
394
|
-
let covered = null;
|
|
395
|
-
for (let after;;) {
|
|
396
|
-
const read = await deps.documents(declaration, after);
|
|
397
|
-
if ('pending' in read)
|
|
398
|
-
return;
|
|
399
|
-
const coverage = JSON.stringify(read.coverage);
|
|
400
|
-
if (covered !== null && coverage !== covered)
|
|
401
|
-
return readAgents();
|
|
402
|
-
covered = coverage;
|
|
403
|
-
for (const document of read.documents) {
|
|
404
|
-
const identity = document.fields?.[declaration.identity];
|
|
405
|
-
const name = document.fields?.[declaration.name];
|
|
406
|
-
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
407
|
-
continue;
|
|
408
|
-
names.set(identity, name);
|
|
409
|
-
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
410
|
-
if (ended !== undefined && ended !== null)
|
|
411
|
-
retired.add(identity);
|
|
412
|
-
}
|
|
413
|
-
if (read.next === undefined)
|
|
414
|
-
break;
|
|
415
|
-
after = read.next;
|
|
416
|
-
}
|
|
417
|
-
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
418
|
-
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
419
|
-
return;
|
|
420
|
-
agentNames = names;
|
|
421
|
-
retiredAgents = retired;
|
|
422
|
-
if (named)
|
|
423
|
-
emitList();
|
|
424
|
-
emitAgents();
|
|
425
|
-
const ended = [...drafts.keys()].filter(retiredChat);
|
|
426
|
-
if (ended.length > 0)
|
|
427
|
-
await remove(ended);
|
|
428
|
-
}
|
|
429
|
-
function retiredChat(conversation) {
|
|
430
|
-
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
431
|
-
}
|
|
432
|
-
function agentsReady() {
|
|
433
|
-
agentsRead ??= readAgents().catch(() => {
|
|
434
|
-
agentsRead = null;
|
|
435
|
-
});
|
|
436
|
-
return agentsRead;
|
|
591
|
+
function retiredChat(conversation, list) {
|
|
592
|
+
if (!conversation.startsWith(NEW_CHAT))
|
|
593
|
+
return false;
|
|
594
|
+
const agent = conversation.slice(NEW_CHAT.length);
|
|
595
|
+
return list.some((entry) => entry.agent === agent && entry.agentRetired);
|
|
437
596
|
}
|
|
438
597
|
function live(conversation, change) {
|
|
439
598
|
const owed = unsent.get(conversation);
|
|
@@ -456,6 +615,7 @@ export function createConversationStore(deps) {
|
|
|
456
615
|
commands: facts.has('commands') ? QUIET.commands : held.commands,
|
|
457
616
|
options: facts.has('options') ? QUIET.options : held.options,
|
|
458
617
|
starting: facts.has('starting') ? QUIET.starting : held.starting,
|
|
618
|
+
subagents: facts.has('subagents') ? QUIET.subagents : held.subagents,
|
|
459
619
|
link: facts.has('link') ? QUIET.link : held.link,
|
|
460
620
|
deliveryUnknown: facts.has('deliveryUnknown')
|
|
461
621
|
? QUIET.deliveryUnknown
|
|
@@ -490,17 +650,23 @@ export function createConversationStore(deps) {
|
|
|
490
650
|
}
|
|
491
651
|
async function remove(leaving) {
|
|
492
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
|
+
]);
|
|
493
659
|
await local.dropConversations(leaving);
|
|
494
660
|
const changed = [];
|
|
495
661
|
for (const conversation of leaving) {
|
|
496
|
-
if (
|
|
662
|
+
if (kept.has(conversation) || lives.has(conversation))
|
|
497
663
|
changed.push(conversation);
|
|
664
|
+
for (const view of views.values()) {
|
|
665
|
+
if (view.conversation === conversation)
|
|
666
|
+
view.draft = null;
|
|
498
667
|
}
|
|
499
|
-
stored.delete(conversation);
|
|
500
|
-
reads.delete(conversation);
|
|
501
|
-
partials.delete(conversation);
|
|
502
|
-
drafts.delete(conversation);
|
|
503
668
|
behind.delete(conversation);
|
|
669
|
+
heardNewest.delete(conversation);
|
|
504
670
|
unsent.delete(conversation);
|
|
505
671
|
setLive(conversation, null);
|
|
506
672
|
await slide(local, conversation, null);
|
|
@@ -509,6 +675,69 @@ export function createConversationStore(deps) {
|
|
|
509
675
|
deps.publish(conversationChangeSet(changed));
|
|
510
676
|
});
|
|
511
677
|
}
|
|
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);
|
|
689
|
+
return (read === undefined ||
|
|
690
|
+
newest === undefined ||
|
|
691
|
+
held?.partial === true ||
|
|
692
|
+
(newest !== null && (read === null || newest > read)));
|
|
693
|
+
}
|
|
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))
|
|
699
|
+
return;
|
|
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
|
+
}
|
|
740
|
+
}
|
|
512
741
|
function gap(conversation) {
|
|
513
742
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
514
743
|
behind.add(conversation);
|
|
@@ -517,7 +746,7 @@ export function createConversationStore(deps) {
|
|
|
517
746
|
return deps.durably(async (local) => {
|
|
518
747
|
if (!replicated.has(conversation))
|
|
519
748
|
return;
|
|
520
|
-
const read =
|
|
749
|
+
const read = (await readsOf(local, [conversation])).get(conversation)?.read;
|
|
521
750
|
const advances = read !== undefined &&
|
|
522
751
|
!behind.has(conversation) &&
|
|
523
752
|
(read === null || message.position > read);
|
|
@@ -527,23 +756,22 @@ export function createConversationStore(deps) {
|
|
|
527
756
|
messages: [message],
|
|
528
757
|
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
529
758
|
});
|
|
530
|
-
stored.add(conversation);
|
|
531
|
-
if (advances)
|
|
532
|
-
reads.set(conversation, message.position);
|
|
533
759
|
sealTurns(conversation, new Set([message.turn]));
|
|
534
760
|
await slide(local, conversation, [message]);
|
|
535
761
|
deps.publish(conversationChangeSet([conversation]));
|
|
762
|
+
tellList();
|
|
536
763
|
});
|
|
537
764
|
}
|
|
538
765
|
async function readHistories(conversations, ended) {
|
|
539
766
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
767
|
+
const held = await readsOf(await deps.local(), conversations);
|
|
540
768
|
let waiting = conversations.map((conversation) => ({
|
|
541
769
|
conversation,
|
|
542
|
-
after:
|
|
770
|
+
after: held.get(conversation)?.read ?? undefined,
|
|
543
771
|
}));
|
|
544
772
|
while (waiting.length > 0) {
|
|
545
773
|
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
546
|
-
const result = await deps.
|
|
774
|
+
const result = await deps.history({
|
|
547
775
|
conversations: asking.map(({ conversation, after }) => ({
|
|
548
776
|
conversation,
|
|
549
777
|
...(after === undefined ? {} : { after }),
|
|
@@ -595,15 +823,10 @@ export function createConversationStore(deps) {
|
|
|
595
823
|
return;
|
|
596
824
|
const whole = next === undefined;
|
|
597
825
|
const cursor = next ?? newestOf(arriving, from);
|
|
598
|
-
const
|
|
826
|
+
const held = (await readsOf(local, [conversation])).get(conversation);
|
|
827
|
+
const partial = !whole && (held?.partial === true || held?.read === undefined);
|
|
599
828
|
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
600
829
|
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
601
|
-
stored.add(conversation);
|
|
602
|
-
reads.set(conversation, cursor);
|
|
603
|
-
if (partial)
|
|
604
|
-
partials.add(conversation);
|
|
605
|
-
else
|
|
606
|
-
partials.delete(conversation);
|
|
607
830
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
608
831
|
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
609
832
|
behind.delete(conversation);
|
|
@@ -611,6 +834,8 @@ export function createConversationStore(deps) {
|
|
|
611
834
|
if (arriving.length > 0 || sealing || whole) {
|
|
612
835
|
deps.publish(conversationChangeSet([conversation]));
|
|
613
836
|
}
|
|
837
|
+
if (arriving.length > 0)
|
|
838
|
+
tellSoon();
|
|
614
839
|
});
|
|
615
840
|
}
|
|
616
841
|
function heard(event) {
|
|
@@ -646,6 +871,10 @@ export function createConversationStore(deps) {
|
|
|
646
871
|
live(conversation, { starting: event.step });
|
|
647
872
|
return;
|
|
648
873
|
}
|
|
874
|
+
if (event.kind === 'subagents') {
|
|
875
|
+
live(conversation, { subagents: structuredClone(event.subagents) });
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
649
878
|
if (event.kind === 'link') {
|
|
650
879
|
live(conversation, { link: { connected: event.connected } });
|
|
651
880
|
return;
|
|
@@ -657,6 +886,7 @@ export function createConversationStore(deps) {
|
|
|
657
886
|
const arriving = acceptedMessage(event.message);
|
|
658
887
|
if (arriving === null)
|
|
659
888
|
return;
|
|
889
|
+
noted(conversation, arriving);
|
|
660
890
|
if (replicated.has(conversation)) {
|
|
661
891
|
if (deps.full()) {
|
|
662
892
|
gap(conversation);
|
|
@@ -672,17 +902,24 @@ export function createConversationStore(deps) {
|
|
|
672
902
|
emit(conversation);
|
|
673
903
|
}
|
|
674
904
|
async function kept(local, conversation, following) {
|
|
675
|
-
|
|
905
|
+
const shown = following === null || empty(following) ? null : following;
|
|
906
|
+
if (shown === null)
|
|
676
907
|
await local.dropChatDraft(conversation);
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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;
|
|
682
916
|
}
|
|
683
917
|
}
|
|
918
|
+
async function draftAt(local, conversation) {
|
|
919
|
+
return (await local.chatDraft(conversation)) ?? undefined;
|
|
920
|
+
}
|
|
684
921
|
async function sealed(local, conversation, turns) {
|
|
685
|
-
const held =
|
|
922
|
+
const held = await draftAt(local, conversation);
|
|
686
923
|
const submitted = held?.submitted ?? [];
|
|
687
924
|
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
688
925
|
if (held === undefined || left.length === submitted.length)
|
|
@@ -693,8 +930,8 @@ export function createConversationStore(deps) {
|
|
|
693
930
|
function moved(agent, operation, to) {
|
|
694
931
|
const from = newChat(agent);
|
|
695
932
|
return deps.durably(async (local) => {
|
|
696
|
-
const held =
|
|
697
|
-
const earlier =
|
|
933
|
+
const held = await draftAt(local, from);
|
|
934
|
+
const earlier = held?.moves ?? [];
|
|
698
935
|
if (earlier.includes(operation))
|
|
699
936
|
return;
|
|
700
937
|
const blank = {
|
|
@@ -703,18 +940,20 @@ export function createConversationStore(deps) {
|
|
|
703
940
|
revision: chatDraftRevision(),
|
|
704
941
|
};
|
|
705
942
|
const { moves: _answered, ...unsent } = held ?? blank;
|
|
706
|
-
const taken =
|
|
943
|
+
const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
|
|
944
|
+
? []
|
|
945
|
+
: [{ ...unsent, conversation: to }];
|
|
707
946
|
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
708
947
|
await local.keepChatDrafts([left, ...taken]);
|
|
709
|
-
|
|
948
|
+
shownDraft(from, left);
|
|
710
949
|
for (const one of taken)
|
|
711
|
-
|
|
950
|
+
shownDraft(to, one);
|
|
712
951
|
deps.publish(conversationChangeSet([from, to]));
|
|
713
952
|
});
|
|
714
953
|
}
|
|
715
954
|
function withDraft(conversation, next) {
|
|
716
955
|
return deps.durably(async (local) => {
|
|
717
|
-
const following = await next(
|
|
956
|
+
const following = await next(await draftAt(local, conversation), local);
|
|
718
957
|
if (following === undefined)
|
|
719
958
|
return;
|
|
720
959
|
await kept(local, conversation, following);
|
|
@@ -723,10 +962,8 @@ export function createConversationStore(deps) {
|
|
|
723
962
|
}
|
|
724
963
|
async function submitted(conversation, operation, draft) {
|
|
725
964
|
let submission;
|
|
726
|
-
let earlier = false;
|
|
727
965
|
await withDraft(conversation, (current) => {
|
|
728
966
|
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
729
|
-
earlier = submission !== undefined;
|
|
730
967
|
if (submission !== undefined)
|
|
731
968
|
return undefined;
|
|
732
969
|
const said = draft ?? current;
|
|
@@ -739,31 +976,24 @@ export function createConversationStore(deps) {
|
|
|
739
976
|
submitted: [...(current?.submitted ?? []), submission],
|
|
740
977
|
};
|
|
741
978
|
});
|
|
742
|
-
return
|
|
743
|
-
}
|
|
744
|
-
function forgotten(conversation, submission) {
|
|
745
|
-
return withDraft(conversation, (current) => {
|
|
746
|
-
const held = current?.submitted ?? [];
|
|
747
|
-
if (current === undefined || !held.includes(submission))
|
|
748
|
-
return undefined;
|
|
749
|
-
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
750
|
-
});
|
|
979
|
+
return submission;
|
|
751
980
|
}
|
|
752
981
|
async function dispatched(conversation, submission) {
|
|
753
|
-
const dispatch = {};
|
|
982
|
+
const dispatch = { answered: false };
|
|
754
983
|
sending.set(submission.operation, dispatch);
|
|
755
984
|
try {
|
|
756
|
-
const result = await invoke('
|
|
757
|
-
text: submission.text,
|
|
985
|
+
const result = await invoke('conversations/send', conversation, {
|
|
758
986
|
operation_id: submission.operation,
|
|
987
|
+
text: submission.text,
|
|
759
988
|
});
|
|
989
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
760
990
|
if (result.kind === 'revoked-authorization')
|
|
761
991
|
return result;
|
|
762
992
|
if (result.kind !== 'action' && result.retryable) {
|
|
763
993
|
deps.unanswered();
|
|
764
994
|
return result;
|
|
765
995
|
}
|
|
766
|
-
const turn = answered(result, '
|
|
996
|
+
const turn = answered(result, 'message_id');
|
|
767
997
|
await withDraft(conversation, async (current, local) => {
|
|
768
998
|
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
769
999
|
if (current === undefined || held === undefined || held.turn !== null)
|
|
@@ -783,29 +1013,41 @@ export function createConversationStore(deps) {
|
|
|
783
1013
|
sending.delete(submission.operation);
|
|
784
1014
|
}
|
|
785
1015
|
}
|
|
786
|
-
function invoke(name, conversation, extra = {}) {
|
|
787
|
-
return deps.call(name, { conversation, ...extra });
|
|
1016
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
1017
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
788
1018
|
}
|
|
789
1019
|
const surface = {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1020
|
+
list() {
|
|
1021
|
+
return new Promise((settle) => {
|
|
1022
|
+
const answer = (listed) => {
|
|
1023
|
+
unlisted.delete(answer);
|
|
1024
|
+
listWatchers.delete(answer);
|
|
1025
|
+
settle(listed);
|
|
1026
|
+
};
|
|
1027
|
+
unlisted.add(answer);
|
|
1028
|
+
learned();
|
|
1029
|
+
});
|
|
793
1030
|
},
|
|
794
1031
|
async read(conversation, options = {}) {
|
|
795
|
-
const
|
|
1032
|
+
const { text, ...windowed } = options;
|
|
1033
|
+
const asked = askedOf(windowed);
|
|
1034
|
+
const phrase = phraseOf(text);
|
|
796
1035
|
return deps.durably(async (local) => {
|
|
797
|
-
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
798
|
-
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
799
|
-
|
|
1036
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
|
|
1037
|
+
const total = asked.total ? await local.messageCount(conversation, phrase) : null;
|
|
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));
|
|
800
1040
|
});
|
|
801
1041
|
},
|
|
802
1042
|
async create(input) {
|
|
803
|
-
const result = await deps.call('
|
|
804
|
-
const conversation = answered(result, '
|
|
1043
|
+
const result = await deps.call('conversations/create', input);
|
|
1044
|
+
const conversation = answered(result, 'conversation_id');
|
|
805
1045
|
if (conversation === null)
|
|
806
1046
|
return result;
|
|
807
|
-
|
|
1047
|
+
const listed = (await heldList()) ?? [];
|
|
1048
|
+
if (!listed.some((entry) => entry.conversation === conversation)) {
|
|
808
1049
|
creations.add(conversation);
|
|
1050
|
+
}
|
|
809
1051
|
const creation = creationOf(input);
|
|
810
1052
|
if (creation !== null)
|
|
811
1053
|
await moved(creation.agent, creation.operation, conversation);
|
|
@@ -822,11 +1064,7 @@ export function createConversationStore(deps) {
|
|
|
822
1064
|
});
|
|
823
1065
|
if (held === null)
|
|
824
1066
|
return DURABLE_WRITE;
|
|
825
|
-
|
|
826
|
-
if (!earlier || !deps.expired(operation))
|
|
827
|
-
return dispatched(conversation, submission);
|
|
828
|
-
await forgotten(conversation, submission);
|
|
829
|
-
return EXPIRED;
|
|
1067
|
+
return dispatched(conversation, held);
|
|
830
1068
|
}
|
|
831
1069
|
if (draft !== undefined) {
|
|
832
1070
|
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
@@ -835,38 +1073,20 @@ export function createConversationStore(deps) {
|
|
|
835
1073
|
}
|
|
836
1074
|
return OFFLINE;
|
|
837
1075
|
},
|
|
838
|
-
async attach(room, file) {
|
|
839
|
-
if (!deps.online())
|
|
840
|
-
return OFFLINE;
|
|
841
|
-
return deps.call('conversation.attach', {
|
|
842
|
-
room,
|
|
843
|
-
...(await declaredFile(file)),
|
|
844
|
-
blob: file.blob,
|
|
845
|
-
});
|
|
846
|
-
},
|
|
847
1076
|
answer(conversation, answer) {
|
|
848
|
-
return invoke('
|
|
1077
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
1078
|
+
operation_id: changeBatchIdentity(),
|
|
1079
|
+
...answer,
|
|
1080
|
+
});
|
|
849
1081
|
},
|
|
850
1082
|
interrupt(conversation) {
|
|
851
|
-
return invoke('
|
|
1083
|
+
return invoke('conversations/interrupt', conversation);
|
|
852
1084
|
},
|
|
853
1085
|
kill(conversation) {
|
|
854
|
-
return invoke('
|
|
855
|
-
},
|
|
856
|
-
async cancel(conversation, turn) {
|
|
857
|
-
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
858
|
-
if (result.kind !== 'action')
|
|
859
|
-
return result;
|
|
860
|
-
await withDraft(conversation, (current) => {
|
|
861
|
-
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
862
|
-
if (current === undefined || held === undefined)
|
|
863
|
-
return undefined;
|
|
864
|
-
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
865
|
-
});
|
|
866
|
-
return result;
|
|
1086
|
+
return invoke('conversations/kill', conversation);
|
|
867
1087
|
},
|
|
868
1088
|
delete(conversation) {
|
|
869
|
-
return invoke('
|
|
1089
|
+
return invoke('conversations/delete', conversation);
|
|
870
1090
|
},
|
|
871
1091
|
};
|
|
872
1092
|
const chatDraft = {
|
|
@@ -895,33 +1115,40 @@ export function createConversationStore(deps) {
|
|
|
895
1115
|
return {
|
|
896
1116
|
surface,
|
|
897
1117
|
chatDraft,
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
if (entry.read !== undefined)
|
|
905
|
-
reads.set(entry.conversation, entry.read);
|
|
906
|
-
if (entry.partial)
|
|
907
|
-
partials.add(entry.conversation);
|
|
908
|
-
}
|
|
909
|
-
drafts.clear();
|
|
910
|
-
for (const record of kept)
|
|
911
|
-
drafts.set(record.conversation, record);
|
|
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();
|
|
912
1124
|
},
|
|
913
1125
|
async reconcile() {
|
|
914
1126
|
if (!listKnown)
|
|
915
1127
|
return;
|
|
916
|
-
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]));
|
|
917
1138
|
const leaving = [
|
|
918
|
-
...new Set([
|
|
1139
|
+
...new Set([
|
|
1140
|
+
...replicated,
|
|
1141
|
+
...reads.keys(),
|
|
1142
|
+
...kept.map(({ conversation }) => conversation),
|
|
1143
|
+
]),
|
|
919
1144
|
].filter((conversation) => !named.has(conversation) &&
|
|
920
1145
|
!creations.has(conversation) &&
|
|
921
|
-
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
1146
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
|
|
922
1147
|
for (const conversation of named) {
|
|
923
|
-
if (!replicated.has(conversation)
|
|
1148
|
+
if (!replicated.has(conversation) &&
|
|
1149
|
+
missed(conversation, reads.get(conversation), entries.get(conversation))) {
|
|
924
1150
|
behind.add(conversation);
|
|
1151
|
+
}
|
|
925
1152
|
}
|
|
926
1153
|
replicated = named;
|
|
927
1154
|
handReplicated();
|
|
@@ -952,16 +1179,16 @@ export function createConversationStore(deps) {
|
|
|
952
1179
|
progress.complete = false;
|
|
953
1180
|
},
|
|
954
1181
|
async resend(progress) {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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;
|
|
959
1189
|
if (deps.closed())
|
|
960
1190
|
return;
|
|
961
|
-
|
|
962
|
-
await forgotten(conversation, submission);
|
|
963
|
-
continue;
|
|
964
|
-
}
|
|
1191
|
+
tried.add(submission.operation);
|
|
965
1192
|
const result = await dispatched(conversation, submission);
|
|
966
1193
|
if (result.kind === 'revoked-authorization')
|
|
967
1194
|
return;
|
|
@@ -982,12 +1209,11 @@ export function createConversationStore(deps) {
|
|
|
982
1209
|
known() {
|
|
983
1210
|
return listKnown;
|
|
984
1211
|
},
|
|
985
|
-
reconnected() {
|
|
986
|
-
for (const conversation of replicated)
|
|
987
|
-
gap(conversation);
|
|
988
|
-
},
|
|
989
1212
|
reopened() {
|
|
990
|
-
|
|
1213
|
+
relisting = null;
|
|
1214
|
+
for (const [operation, dispatch] of sending)
|
|
1215
|
+
if (!dispatch.answered)
|
|
1216
|
+
sending.delete(operation);
|
|
991
1217
|
},
|
|
992
1218
|
lost() {
|
|
993
1219
|
catching.clear();
|
|
@@ -1000,30 +1226,37 @@ export function createConversationStore(deps) {
|
|
|
1000
1226
|
asked,
|
|
1001
1227
|
watchers: new Set(),
|
|
1002
1228
|
window: null,
|
|
1229
|
+
draft: null,
|
|
1230
|
+
ended: null,
|
|
1231
|
+
marked: [],
|
|
1003
1232
|
more: false,
|
|
1004
1233
|
total: null,
|
|
1005
1234
|
};
|
|
1006
1235
|
views.set(key, view);
|
|
1007
1236
|
const watcher = { notify: deliver, ready: false };
|
|
1008
1237
|
view.watchers.add(watcher);
|
|
1009
|
-
|
|
1010
|
-
|
|
1238
|
+
const reading = new AbortController();
|
|
1239
|
+
void untilAnswered(() => deps.durably(async (local) => {
|
|
1011
1240
|
if (view.window !== null)
|
|
1012
1241
|
return;
|
|
1013
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);
|
|
1014
1245
|
view.window = messages;
|
|
1246
|
+
view.marked = await local.jobMarkers(conversation);
|
|
1015
1247
|
view.more = more;
|
|
1016
1248
|
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1017
|
-
})
|
|
1249
|
+
}), reading.signal)
|
|
1018
1250
|
.then(() => {
|
|
1019
1251
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
1020
1252
|
return;
|
|
1021
1253
|
watcher.ready = true;
|
|
1022
1254
|
const window = view.window ?? [];
|
|
1023
|
-
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
1255
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
|
|
1024
1256
|
})
|
|
1025
1257
|
.catch(() => { });
|
|
1026
1258
|
return () => {
|
|
1259
|
+
reading.abort();
|
|
1027
1260
|
if (!view.watchers.delete(watcher))
|
|
1028
1261
|
return;
|
|
1029
1262
|
if (view.watchers.size === 0)
|
|
@@ -1031,77 +1264,73 @@ export function createConversationStore(deps) {
|
|
|
1031
1264
|
};
|
|
1032
1265
|
},
|
|
1033
1266
|
watchList(deliver) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
1037
|
-
deliver(entries());
|
|
1038
|
-
});
|
|
1267
|
+
unlisted.add(deliver);
|
|
1268
|
+
learned();
|
|
1039
1269
|
return () => {
|
|
1270
|
+
unlisted.delete(deliver);
|
|
1040
1271
|
listWatchers.delete(deliver);
|
|
1041
1272
|
};
|
|
1042
1273
|
},
|
|
1043
1274
|
watchAgents(deliver) {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
deliver(agents());
|
|
1048
|
-
});
|
|
1275
|
+
unnamed.add(deliver);
|
|
1276
|
+
learned();
|
|
1277
|
+
readAgents();
|
|
1049
1278
|
return () => {
|
|
1279
|
+
unnamed.delete(deliver);
|
|
1050
1280
|
agentWatchers.delete(deliver);
|
|
1051
1281
|
};
|
|
1052
1282
|
},
|
|
1283
|
+
learned,
|
|
1053
1284
|
changed(set) {
|
|
1054
1285
|
for (const conversation of set.conversations)
|
|
1055
1286
|
emit(conversation);
|
|
1056
|
-
const declaration = deps.agents();
|
|
1057
|
-
if (declaration !== undefined &&
|
|
1058
|
-
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
1059
|
-
agentsRead = readAgents().catch(() => {
|
|
1060
|
-
agentsRead = null;
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
1287
|
},
|
|
1064
1288
|
receive(event) {
|
|
1065
1289
|
if (deps.closed())
|
|
1066
|
-
return
|
|
1067
|
-
if (event.kind === 'list'
|
|
1068
|
-
if (event.
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1074
|
-
listKnown = true;
|
|
1075
|
-
openingEnded();
|
|
1076
|
-
}
|
|
1077
|
-
else if (event.kind === 'listed') {
|
|
1078
|
-
listing.set(event.entry.conversation, { ...event.entry });
|
|
1079
|
-
creations.delete(event.entry.conversation);
|
|
1080
|
-
}
|
|
1081
|
-
else {
|
|
1082
|
-
listing.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 });
|
|
1083
1297
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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;
|
|
1089
1319
|
}
|
|
1090
1320
|
heard(event);
|
|
1091
|
-
return false;
|
|
1092
|
-
},
|
|
1093
|
-
searchable() {
|
|
1094
|
-
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
1095
|
-
},
|
|
1096
|
-
acquired(conversation) {
|
|
1097
|
-
return reads.has(conversation) && !partials.has(conversation);
|
|
1098
1321
|
},
|
|
1099
1322
|
close() {
|
|
1323
|
+
closing.abort();
|
|
1324
|
+
if (rowTimer !== null)
|
|
1325
|
+
clearTimeout(rowTimer);
|
|
1326
|
+
rowTimer = null;
|
|
1100
1327
|
views.clear();
|
|
1101
1328
|
lives.clear();
|
|
1102
1329
|
unsent.clear();
|
|
1103
1330
|
listWatchers.clear();
|
|
1331
|
+
unlisted.clear();
|
|
1104
1332
|
agentWatchers.clear();
|
|
1333
|
+
unnamed.clear();
|
|
1105
1334
|
},
|
|
1106
1335
|
};
|
|
1107
1336
|
}
|