@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca8707258fdde
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 +2 -2
- package/dist/acceptance.d.ts +26 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +384 -0
- package/dist/adapter.d.ts +102 -70
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +29 -47
- package/dist/bootstrap.d.ts +16 -14
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +272 -129
- 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 +20 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +55 -6
- package/dist/collection.d.ts +7 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +23 -17
- 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 +22 -44
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1600 -3577
- 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 +484 -258
- 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 +29 -32
- 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 +20 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/install.d.ts +86 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +537 -0
- 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/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- 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 +12 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +216 -85
- 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 +5 -17
- 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 +14 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +19 -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 +206 -126
- package/dist/react.d.ts +5 -2
- 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/reads.d.ts +34 -10
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +114 -86
- 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 +33 -5
- package/dist/replica.d.ts +10 -11
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +52 -20
- 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/room-drafts.d.ts +29 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +216 -0
- 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/saves.d.ts +96 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +999 -0
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/seed.d.ts +16 -2
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +66 -0
- package/dist/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket.d.ts +43 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +96 -0
- 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/fences.d.ts +16 -0
- package/dist/store/fences.d.ts.map +1 -0
- package/dist/store/fences.js +26 -0
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/records.d.ts +212 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +408 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/watchers.d.ts +15 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +36 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +76 -158
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +117 -403
- 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 +118 -65
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +199 -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 +23 -8
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +8 -2
- 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/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -368
- 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,23 +976,15 @@ 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
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
|
});
|
|
760
989
|
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
761
990
|
if (result.kind === 'revoked-authorization')
|
|
@@ -764,7 +993,7 @@ export function createConversationStore(deps) {
|
|
|
764
993
|
deps.unanswered();
|
|
765
994
|
return result;
|
|
766
995
|
}
|
|
767
|
-
const turn = answered(result, '
|
|
996
|
+
const turn = answered(result, 'message_id');
|
|
768
997
|
await withDraft(conversation, async (current, local) => {
|
|
769
998
|
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
770
999
|
if (current === undefined || held === undefined || held.turn !== null)
|
|
@@ -784,29 +1013,41 @@ export function createConversationStore(deps) {
|
|
|
784
1013
|
sending.delete(submission.operation);
|
|
785
1014
|
}
|
|
786
1015
|
}
|
|
787
|
-
function invoke(name, conversation, extra = {}) {
|
|
788
|
-
return deps.call(name, { conversation, ...extra });
|
|
1016
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
1017
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
789
1018
|
}
|
|
790
1019
|
const surface = {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
+
});
|
|
794
1030
|
},
|
|
795
1031
|
async read(conversation, options = {}) {
|
|
796
|
-
const
|
|
1032
|
+
const { text, ...windowed } = options;
|
|
1033
|
+
const asked = askedOf(windowed);
|
|
1034
|
+
const phrase = phraseOf(text);
|
|
797
1035
|
return deps.durably(async (local) => {
|
|
798
|
-
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
799
|
-
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
800
|
-
|
|
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));
|
|
801
1040
|
});
|
|
802
1041
|
},
|
|
803
1042
|
async create(input) {
|
|
804
|
-
const result = await deps.call('
|
|
805
|
-
const conversation = answered(result, '
|
|
1043
|
+
const result = await deps.call('conversations/create', input);
|
|
1044
|
+
const conversation = answered(result, 'conversation_id');
|
|
806
1045
|
if (conversation === null)
|
|
807
1046
|
return result;
|
|
808
|
-
|
|
1047
|
+
const listed = (await heldList()) ?? [];
|
|
1048
|
+
if (!listed.some((entry) => entry.conversation === conversation)) {
|
|
809
1049
|
creations.add(conversation);
|
|
1050
|
+
}
|
|
810
1051
|
const creation = creationOf(input);
|
|
811
1052
|
if (creation !== null)
|
|
812
1053
|
await moved(creation.agent, creation.operation, conversation);
|
|
@@ -823,11 +1064,7 @@ export function createConversationStore(deps) {
|
|
|
823
1064
|
});
|
|
824
1065
|
if (held === null)
|
|
825
1066
|
return DURABLE_WRITE;
|
|
826
|
-
|
|
827
|
-
if (!earlier || !deps.expired(operation))
|
|
828
|
-
return dispatched(conversation, submission);
|
|
829
|
-
await forgotten(conversation, submission);
|
|
830
|
-
return EXPIRED;
|
|
1067
|
+
return dispatched(conversation, held);
|
|
831
1068
|
}
|
|
832
1069
|
if (draft !== undefined) {
|
|
833
1070
|
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
@@ -836,38 +1073,20 @@ export function createConversationStore(deps) {
|
|
|
836
1073
|
}
|
|
837
1074
|
return OFFLINE;
|
|
838
1075
|
},
|
|
839
|
-
async attach(room, file) {
|
|
840
|
-
if (!deps.online())
|
|
841
|
-
return OFFLINE;
|
|
842
|
-
return deps.call('conversation.attach', {
|
|
843
|
-
room,
|
|
844
|
-
...(await declaredFile(file)),
|
|
845
|
-
blob: file.blob,
|
|
846
|
-
});
|
|
847
|
-
},
|
|
848
1076
|
answer(conversation, answer) {
|
|
849
|
-
return invoke('
|
|
1077
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
1078
|
+
operation_id: changeBatchIdentity(),
|
|
1079
|
+
...answer,
|
|
1080
|
+
});
|
|
850
1081
|
},
|
|
851
1082
|
interrupt(conversation) {
|
|
852
|
-
return invoke('
|
|
1083
|
+
return invoke('conversations/interrupt', conversation);
|
|
853
1084
|
},
|
|
854
1085
|
kill(conversation) {
|
|
855
|
-
return invoke('
|
|
856
|
-
},
|
|
857
|
-
async cancel(conversation, turn) {
|
|
858
|
-
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
859
|
-
if (result.kind !== 'action')
|
|
860
|
-
return result;
|
|
861
|
-
await withDraft(conversation, (current) => {
|
|
862
|
-
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
863
|
-
if (current === undefined || held === undefined)
|
|
864
|
-
return undefined;
|
|
865
|
-
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
866
|
-
});
|
|
867
|
-
return result;
|
|
1086
|
+
return invoke('conversations/kill', conversation);
|
|
868
1087
|
},
|
|
869
1088
|
delete(conversation) {
|
|
870
|
-
return invoke('
|
|
1089
|
+
return invoke('conversations/delete', conversation);
|
|
871
1090
|
},
|
|
872
1091
|
};
|
|
873
1092
|
const chatDraft = {
|
|
@@ -896,33 +1115,40 @@ export function createConversationStore(deps) {
|
|
|
896
1115
|
return {
|
|
897
1116
|
surface,
|
|
898
1117
|
chatDraft,
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (entry.read !== undefined)
|
|
906
|
-
reads.set(entry.conversation, entry.read);
|
|
907
|
-
if (entry.partial)
|
|
908
|
-
partials.add(entry.conversation);
|
|
909
|
-
}
|
|
910
|
-
drafts.clear();
|
|
911
|
-
for (const record of kept)
|
|
912
|
-
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();
|
|
913
1124
|
},
|
|
914
1125
|
async reconcile() {
|
|
915
1126
|
if (!listKnown)
|
|
916
1127
|
return;
|
|
917
|
-
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]));
|
|
918
1138
|
const leaving = [
|
|
919
|
-
...new Set([
|
|
1139
|
+
...new Set([
|
|
1140
|
+
...replicated,
|
|
1141
|
+
...reads.keys(),
|
|
1142
|
+
...kept.map(({ conversation }) => conversation),
|
|
1143
|
+
]),
|
|
920
1144
|
].filter((conversation) => !named.has(conversation) &&
|
|
921
1145
|
!creations.has(conversation) &&
|
|
922
|
-
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
1146
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
|
|
923
1147
|
for (const conversation of named) {
|
|
924
|
-
if (!replicated.has(conversation)
|
|
1148
|
+
if (!replicated.has(conversation) &&
|
|
1149
|
+
missed(conversation, reads.get(conversation), entries.get(conversation))) {
|
|
925
1150
|
behind.add(conversation);
|
|
1151
|
+
}
|
|
926
1152
|
}
|
|
927
1153
|
replicated = named;
|
|
928
1154
|
handReplicated();
|
|
@@ -953,16 +1179,16 @@ export function createConversationStore(deps) {
|
|
|
953
1179
|
progress.complete = false;
|
|
954
1180
|
},
|
|
955
1181
|
async resend(progress) {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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;
|
|
960
1189
|
if (deps.closed())
|
|
961
1190
|
return;
|
|
962
|
-
|
|
963
|
-
await forgotten(conversation, submission);
|
|
964
|
-
continue;
|
|
965
|
-
}
|
|
1191
|
+
tried.add(submission.operation);
|
|
966
1192
|
const result = await dispatched(conversation, submission);
|
|
967
1193
|
if (result.kind === 'revoked-authorization')
|
|
968
1194
|
return;
|
|
@@ -983,11 +1209,8 @@ export function createConversationStore(deps) {
|
|
|
983
1209
|
known() {
|
|
984
1210
|
return listKnown;
|
|
985
1211
|
},
|
|
986
|
-
reconnected() {
|
|
987
|
-
for (const conversation of replicated)
|
|
988
|
-
gap(conversation);
|
|
989
|
-
},
|
|
990
1212
|
reopened() {
|
|
1213
|
+
relisting = null;
|
|
991
1214
|
for (const [operation, dispatch] of sending)
|
|
992
1215
|
if (!dispatch.answered)
|
|
993
1216
|
sending.delete(operation);
|
|
@@ -1003,30 +1226,37 @@ export function createConversationStore(deps) {
|
|
|
1003
1226
|
asked,
|
|
1004
1227
|
watchers: new Set(),
|
|
1005
1228
|
window: null,
|
|
1229
|
+
draft: null,
|
|
1230
|
+
ended: null,
|
|
1231
|
+
marked: [],
|
|
1006
1232
|
more: false,
|
|
1007
1233
|
total: null,
|
|
1008
1234
|
};
|
|
1009
1235
|
views.set(key, view);
|
|
1010
1236
|
const watcher = { notify: deliver, ready: false };
|
|
1011
1237
|
view.watchers.add(watcher);
|
|
1012
|
-
|
|
1013
|
-
|
|
1238
|
+
const reading = new AbortController();
|
|
1239
|
+
void untilAnswered(() => deps.durably(async (local) => {
|
|
1014
1240
|
if (view.window !== null)
|
|
1015
1241
|
return;
|
|
1016
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);
|
|
1017
1245
|
view.window = messages;
|
|
1246
|
+
view.marked = await local.jobMarkers(conversation);
|
|
1018
1247
|
view.more = more;
|
|
1019
1248
|
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1020
|
-
})
|
|
1249
|
+
}), reading.signal)
|
|
1021
1250
|
.then(() => {
|
|
1022
1251
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
1023
1252
|
return;
|
|
1024
1253
|
watcher.ready = true;
|
|
1025
1254
|
const window = view.window ?? [];
|
|
1026
|
-
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
1255
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
|
|
1027
1256
|
})
|
|
1028
1257
|
.catch(() => { });
|
|
1029
1258
|
return () => {
|
|
1259
|
+
reading.abort();
|
|
1030
1260
|
if (!view.watchers.delete(watcher))
|
|
1031
1261
|
return;
|
|
1032
1262
|
if (view.watchers.size === 0)
|
|
@@ -1034,77 +1264,73 @@ export function createConversationStore(deps) {
|
|
|
1034
1264
|
};
|
|
1035
1265
|
},
|
|
1036
1266
|
watchList(deliver) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
1040
|
-
deliver(entries());
|
|
1041
|
-
});
|
|
1267
|
+
unlisted.add(deliver);
|
|
1268
|
+
learned();
|
|
1042
1269
|
return () => {
|
|
1270
|
+
unlisted.delete(deliver);
|
|
1043
1271
|
listWatchers.delete(deliver);
|
|
1044
1272
|
};
|
|
1045
1273
|
},
|
|
1046
1274
|
watchAgents(deliver) {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
deliver(agents());
|
|
1051
|
-
});
|
|
1275
|
+
unnamed.add(deliver);
|
|
1276
|
+
learned();
|
|
1277
|
+
readAgents();
|
|
1052
1278
|
return () => {
|
|
1279
|
+
unnamed.delete(deliver);
|
|
1053
1280
|
agentWatchers.delete(deliver);
|
|
1054
1281
|
};
|
|
1055
1282
|
},
|
|
1283
|
+
learned,
|
|
1056
1284
|
changed(set) {
|
|
1057
1285
|
for (const conversation of set.conversations)
|
|
1058
1286
|
emit(conversation);
|
|
1059
|
-
const declaration = deps.agents();
|
|
1060
|
-
if (declaration !== undefined &&
|
|
1061
|
-
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
1062
|
-
agentsRead = readAgents().catch(() => {
|
|
1063
|
-
agentsRead = null;
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
1287
|
},
|
|
1067
1288
|
receive(event) {
|
|
1068
1289
|
if (deps.closed())
|
|
1069
|
-
return
|
|
1070
|
-
if (event.kind === 'list'
|
|
1071
|
-
if (event.
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
listKnown = true;
|
|
1078
|
-
openingEnded();
|
|
1079
|
-
}
|
|
1080
|
-
else if (event.kind === 'listed') {
|
|
1081
|
-
listing.set(event.entry.conversation, { ...event.entry });
|
|
1082
|
-
creations.delete(event.entry.conversation);
|
|
1083
|
-
}
|
|
1084
|
-
else {
|
|
1085
|
-
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 });
|
|
1086
1297
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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;
|
|
1092
1319
|
}
|
|
1093
1320
|
heard(event);
|
|
1094
|
-
return false;
|
|
1095
|
-
},
|
|
1096
|
-
searchable() {
|
|
1097
|
-
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
1098
|
-
},
|
|
1099
|
-
acquired(conversation) {
|
|
1100
|
-
return reads.has(conversation) && !partials.has(conversation);
|
|
1101
1321
|
},
|
|
1102
1322
|
close() {
|
|
1323
|
+
closing.abort();
|
|
1324
|
+
if (rowTimer !== null)
|
|
1325
|
+
clearTimeout(rowTimer);
|
|
1326
|
+
rowTimer = null;
|
|
1103
1327
|
views.clear();
|
|
1104
1328
|
lives.clear();
|
|
1105
1329
|
unsent.clear();
|
|
1106
1330
|
listWatchers.clear();
|
|
1331
|
+
unlisted.clear();
|
|
1107
1332
|
agentWatchers.clear();
|
|
1333
|
+
unnamed.clear();
|
|
1108
1334
|
},
|
|
1109
1335
|
};
|
|
1110
1336
|
}
|