@agentshouse/mdruntime 0.1.0-c8e110144a30a → 0.1.0-c8f07dbee1c62
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 +19 -16
- package/dist/acceptance.d.ts +6 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +389 -0
- package/dist/actions.d.ts +20 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +250 -0
- package/dist/adapter.d.ts +154 -57
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +7 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +40 -41
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- package/dist/binary.d.ts +0 -5
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/bootstrap.d.ts +31 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +323 -89
- package/dist/cell.d.ts +6 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +9 -0
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -63
- package/dist/change.d.ts +32 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +10 -1
- package/dist/chat-types.d.ts +5 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +13 -0
- package/dist/client.d.ts +43 -16
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +101 -12
- package/dist/collection.d.ts +11 -7
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +38 -79
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +717 -0
- package/dist/connection.d.ts +8 -116
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +101 -2410
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +1 -4
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +53 -0
- package/dist/conversation.d.ts +52 -20
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +900 -303
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/discovery.d.ts +10 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +175 -0
- package/dist/durability.d.ts +19 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +161 -0
- 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 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +11 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +128 -18
- package/dist/index.d.ts +32 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/install.d.ts +56 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +560 -0
- package/dist/job.d.ts +33 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +54 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +267 -0
- package/dist/listing.d.ts +53 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +175 -0
- package/dist/live.d.ts +23 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +165 -0
- package/dist/locks.d.ts +9 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +61 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +61 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +19 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +253 -94
- package/dist/messages.d.ts +52 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +168 -0
- package/dist/model.d.ts +11 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -10
- package/dist/models.d.ts +14 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +193 -0
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +22 -4
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +120 -14
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +56 -0
- package/dist/outcome.d.ts +53 -8
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +43 -0
- package/dist/overlay.d.ts +4 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +111 -182
- package/dist/{chat/parts.d.ts → parts.d.ts} +11 -9
- package/dist/parts.d.ts.map +1 -0
- package/dist/{chat/parts.js → parts.js} +45 -46
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/preparation.d.ts +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +226 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +272 -0
- package/dist/query.d.ts +50 -183
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +5 -2
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +12 -1
- package/dist/reach.d.ts +21 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +289 -89
- package/dist/react.d.ts +16 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +50 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/reads.d.ts +36 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +150 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +232 -0
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +143 -51
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +56 -0
- package/dist/replica.d.ts +55 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +333 -154
- package/dist/room-draft.d.ts +37 -6
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +161 -74
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +216 -0
- package/dist/rows.d.ts +30 -37
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +59 -126
- package/dist/runtime.d.ts +42 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +52 -9
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +544 -173
- package/dist/saves.d.ts +70 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +1004 -0
- package/dist/search.d.ts +15 -55
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +25 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +146 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +425 -0
- package/dist/shown.d.ts +31 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +294 -0
- 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/lanes.d.ts +20 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +77 -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/rooms.d.ts +43 -0
- package/dist/socket/rooms.d.ts.map +1 -0
- package/dist/socket/rooms.js +90 -0
- package/dist/socket/session.d.ts +39 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +70 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +175 -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 +5 -2
- 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/readiness.d.ts +10 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +52 -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/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +43 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +57 -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 +106 -119
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +136 -525
- package/dist/stored.d.ts +156 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +637 -0
- package/dist/surface.d.ts +88 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +222 -0
- package/dist/sync.d.ts +20 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +531 -0
- package/dist/tables.d.ts +415 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +740 -0
- 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/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +104 -0
- package/dist/windows.d.ts +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +181 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +62 -31
- package/package.json +22 -19
- package/AGENTS.md +0 -299
- package/dist/chat/acts.d.ts +0 -34
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/input.d.ts +0 -9
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -99
- package/dist/chat/kit.d.ts +0 -60
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -20
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -59
- package/dist/chat/markdown.d.ts +0 -19
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -187
- package/dist/chat/message.d.ts +0 -6
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -192
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/name.d.ts +0 -6
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -11
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -17
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -63
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -645
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -80
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -33
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -91
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -302
- package/dist/chat.d.ts +0 -14
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -440
package/dist/conversation.js
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
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
|
-
import {
|
|
4
|
-
import { chatDraftRevision } from './identity.js';
|
|
5
|
+
import { refusedForSpace } from './durability.js';
|
|
6
|
+
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
7
|
+
import { OFFLINE } from './outcome.js';
|
|
8
|
+
import { jobsOf, runningLine } from './parts.js';
|
|
9
|
+
import { openQuestion, questionAsked } from './questions.js';
|
|
10
|
+
import { untilAnswered } from './recovery.js';
|
|
11
|
+
import { capped, FROM_START, messageOrder, precedes } from './windows.js';
|
|
5
12
|
const QUIET = {
|
|
6
13
|
turn: null,
|
|
7
14
|
questions: [],
|
|
8
15
|
process: null,
|
|
9
16
|
commands: [],
|
|
10
17
|
options: [],
|
|
18
|
+
starting: null,
|
|
19
|
+
subagents: [],
|
|
20
|
+
link: null,
|
|
21
|
+
deliveryUnknown: [],
|
|
11
22
|
};
|
|
23
|
+
const FACTS = [
|
|
24
|
+
'questions',
|
|
25
|
+
'process',
|
|
26
|
+
'commands',
|
|
27
|
+
'options',
|
|
28
|
+
'starting',
|
|
29
|
+
'subagents',
|
|
30
|
+
'link',
|
|
31
|
+
'deliveryUnknown',
|
|
32
|
+
];
|
|
12
33
|
export function unheldView(conversation) {
|
|
13
34
|
return {
|
|
14
35
|
conversation,
|
|
@@ -16,11 +37,31 @@ export function unheldView(conversation) {
|
|
|
16
37
|
turn: null,
|
|
17
38
|
questions: [],
|
|
18
39
|
process: null,
|
|
40
|
+
processEnded: null,
|
|
19
41
|
commands: [],
|
|
20
42
|
options: [],
|
|
43
|
+
starting: null,
|
|
44
|
+
subagents: [],
|
|
45
|
+
link: null,
|
|
46
|
+
deliveryUnknown: [],
|
|
21
47
|
chatDraft: null,
|
|
48
|
+
jobs: [],
|
|
22
49
|
};
|
|
23
50
|
}
|
|
51
|
+
export const SESSION_STARTED = 'Session started';
|
|
52
|
+
const NEW_CHAT = 'new:';
|
|
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;
|
|
56
|
+
export function newChat(agent) {
|
|
57
|
+
return `${NEW_CHAT}${agent}`;
|
|
58
|
+
}
|
|
59
|
+
function creationOf(input) {
|
|
60
|
+
if (typeof input !== 'object' || input === null)
|
|
61
|
+
return null;
|
|
62
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
63
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
64
|
+
}
|
|
24
65
|
function startedTurn(turn, operation) {
|
|
25
66
|
if (operation.op === 'begin')
|
|
26
67
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -70,18 +111,28 @@ function appliedTurn(held, operation) {
|
|
|
70
111
|
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
71
112
|
};
|
|
72
113
|
}
|
|
73
|
-
function
|
|
74
|
-
if (left.position !== right.position)
|
|
75
|
-
return left.position < right.position ? -1 : 1;
|
|
76
|
-
if (left.identity !== right.identity)
|
|
77
|
-
return left.identity < right.identity ? -1 : 1;
|
|
78
|
-
return 0;
|
|
79
|
-
}
|
|
80
|
-
function merged(held, arriving) {
|
|
114
|
+
function windowOf(held, arriving, limit) {
|
|
81
115
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
82
116
|
for (const message of arriving)
|
|
83
117
|
byIdentity.set(message.identity, message);
|
|
84
|
-
|
|
118
|
+
const next = [...byIdentity.values()].sort(messageOrder);
|
|
119
|
+
const start = Math.max(0, next.length - limit);
|
|
120
|
+
return { messages: next.slice(start), dropped: start > 0 };
|
|
121
|
+
}
|
|
122
|
+
function frameOf(asked, messages, more, total) {
|
|
123
|
+
const oldest = messages[0];
|
|
124
|
+
const next = !more
|
|
125
|
+
? undefined
|
|
126
|
+
: oldest === undefined
|
|
127
|
+
? (asked.after ?? FROM_START)
|
|
128
|
+
: cursorOf([oldest.position, oldest.identity]);
|
|
129
|
+
return {
|
|
130
|
+
...(next === undefined ? {} : { next }),
|
|
131
|
+
...(total === null ? {} : { total }),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function newestOf(arriving, from) {
|
|
135
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
85
136
|
}
|
|
86
137
|
function acceptedMessage(value) {
|
|
87
138
|
if (typeof value !== 'object' || value === null)
|
|
@@ -140,177 +191,441 @@ function historyPages(result) {
|
|
|
140
191
|
}
|
|
141
192
|
return pages;
|
|
142
193
|
}
|
|
143
|
-
function
|
|
194
|
+
function answered(result, field) {
|
|
144
195
|
if (result.kind !== 'action')
|
|
145
196
|
return null;
|
|
146
197
|
const answer = result.result;
|
|
147
198
|
if (typeof answer !== 'object' || answer === null)
|
|
148
199
|
return null;
|
|
149
|
-
const
|
|
150
|
-
return typeof
|
|
200
|
+
const value = answer[field];
|
|
201
|
+
return typeof value === 'string' ? value : null;
|
|
151
202
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
203
|
+
function beforeOf(after) {
|
|
204
|
+
if (after === undefined)
|
|
205
|
+
return null;
|
|
206
|
+
const decoded = typeof after === 'string' ? decodedCursor(after) : null;
|
|
207
|
+
if (decoded?.length === 0)
|
|
208
|
+
return null;
|
|
209
|
+
const [position, identity] = decoded ?? [];
|
|
210
|
+
if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
|
|
211
|
+
throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
|
|
212
|
+
}
|
|
213
|
+
return [position, identity];
|
|
214
|
+
}
|
|
215
|
+
function askedOf(options) {
|
|
216
|
+
const { limit, after, total } = options;
|
|
217
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
|
|
157
218
|
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
158
219
|
}
|
|
159
|
-
return limit;
|
|
220
|
+
return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
|
|
221
|
+
}
|
|
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
|
+
}
|
|
230
|
+
const DURABLE_WRITE = {
|
|
231
|
+
kind: 'rejected',
|
|
232
|
+
code: 'durable-write',
|
|
233
|
+
fields: {},
|
|
234
|
+
retryable: true,
|
|
235
|
+
};
|
|
236
|
+
function unwritten(draft) {
|
|
237
|
+
return draft.text === '';
|
|
238
|
+
}
|
|
239
|
+
function pending(draft) {
|
|
240
|
+
return {
|
|
241
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
242
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function empty(draft) {
|
|
246
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
247
|
+
}
|
|
248
|
+
function withSubmitted(draft, submitted) {
|
|
249
|
+
const { submitted: _held, ...unsent } = draft;
|
|
250
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
251
|
+
}
|
|
252
|
+
function prepended(draft, said) {
|
|
253
|
+
return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
|
|
254
|
+
}
|
|
255
|
+
function returned(draft, submission, others) {
|
|
256
|
+
return withSubmitted({
|
|
257
|
+
conversation: draft.conversation,
|
|
258
|
+
...prepended(draft, submission),
|
|
259
|
+
revision: chatDraftRevision(),
|
|
260
|
+
}, others);
|
|
160
261
|
}
|
|
161
262
|
export function createConversationStore(deps) {
|
|
162
|
-
const messages = new Map();
|
|
163
|
-
const reads = new Map();
|
|
164
|
-
const drafts = new Map();
|
|
165
263
|
const sending = new Map();
|
|
166
264
|
const behind = new Set();
|
|
265
|
+
const catching = new Map();
|
|
167
266
|
const gaps = new Map();
|
|
168
267
|
const lives = new Map();
|
|
268
|
+
const unsent = new Map();
|
|
169
269
|
const views = new Map();
|
|
170
|
-
const
|
|
270
|
+
const heardNewest = new Map();
|
|
271
|
+
let relisting = null;
|
|
272
|
+
const creations = new Set();
|
|
171
273
|
const listWatchers = new Set();
|
|
274
|
+
const unlisted = new Set();
|
|
172
275
|
let listKnown = false;
|
|
276
|
+
let listOwed = false;
|
|
277
|
+
let listTelling = false;
|
|
278
|
+
let rowTimer = null;
|
|
279
|
+
const closing = new AbortController();
|
|
173
280
|
const agentWatchers = new Set();
|
|
174
|
-
|
|
175
|
-
let
|
|
176
|
-
let
|
|
281
|
+
const unnamed = new Set();
|
|
282
|
+
let agentsShown = null;
|
|
283
|
+
let listedAgents = null;
|
|
284
|
+
let agentsReading = false;
|
|
177
285
|
let replicated = new Set();
|
|
178
286
|
let handedReplicated;
|
|
179
287
|
function liveOf(conversation) {
|
|
180
288
|
return lives.get(conversation) ?? QUIET;
|
|
181
289
|
}
|
|
182
|
-
function
|
|
183
|
-
const held = messages.get(conversation) ?? [];
|
|
184
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
185
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
186
|
-
identity,
|
|
187
|
-
turn,
|
|
188
|
-
text,
|
|
189
|
-
message: structuredClone(message),
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
193
|
-
const draft = drafts.get(conversation);
|
|
290
|
+
function viewOf(conversation, held, frame, draft, ended, marked) {
|
|
194
291
|
const facts = liveOf(conversation);
|
|
195
292
|
return {
|
|
196
293
|
conversation,
|
|
197
|
-
messages:
|
|
294
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
295
|
+
identity,
|
|
296
|
+
turn,
|
|
297
|
+
text,
|
|
298
|
+
message,
|
|
299
|
+
})),
|
|
300
|
+
...frame,
|
|
198
301
|
turn: structuredClone(facts.turn),
|
|
199
302
|
questions: structuredClone(facts.questions),
|
|
200
303
|
process: facts.process === null ? null : { ...facts.process },
|
|
304
|
+
processEnded: ended,
|
|
201
305
|
commands: structuredClone(facts.commands),
|
|
202
306
|
options: structuredClone(facts.options),
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
307
|
+
starting: facts.starting,
|
|
308
|
+
subagents: structuredClone(facts.subagents),
|
|
309
|
+
link: facts.link === null ? null : { ...facts.link },
|
|
310
|
+
deliveryUnknown: [...facts.deliveryUnknown],
|
|
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),
|
|
206
315
|
};
|
|
207
316
|
}
|
|
208
|
-
function emit(conversation
|
|
317
|
+
function emit(conversation) {
|
|
209
318
|
if (deps.closed())
|
|
210
319
|
return;
|
|
211
320
|
for (const view of views.values()) {
|
|
212
|
-
if (view.conversation !== conversation)
|
|
321
|
+
if (view.conversation !== conversation || view.window === null)
|
|
213
322
|
continue;
|
|
214
|
-
if (messagesChanged)
|
|
215
|
-
view.messages = null;
|
|
216
323
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
217
324
|
if (ready.length === 0)
|
|
218
325
|
continue;
|
|
219
|
-
|
|
220
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
326
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window), view.draft, view.ended, view.marked);
|
|
221
327
|
for (const watcher of ready)
|
|
222
328
|
watcher.notify(value);
|
|
223
329
|
}
|
|
224
330
|
}
|
|
331
|
+
function sharedFrame(view, window) {
|
|
332
|
+
return frameOf(view.asked, window, view.more, view.total);
|
|
333
|
+
}
|
|
334
|
+
async function slide(local, conversation, arriving) {
|
|
335
|
+
let count;
|
|
336
|
+
let marked;
|
|
337
|
+
for (const view of views.values()) {
|
|
338
|
+
if (view.conversation !== conversation || view.window === null)
|
|
339
|
+
continue;
|
|
340
|
+
if (arriving === null) {
|
|
341
|
+
view.window = [];
|
|
342
|
+
view.marked = [];
|
|
343
|
+
view.more = false;
|
|
344
|
+
if (view.total !== null)
|
|
345
|
+
view.total = 0;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const joining = arriving.filter((message) => precedes(message, view.asked.before));
|
|
349
|
+
const next = windowOf(view.window, joining, view.asked.limit);
|
|
350
|
+
view.window = next.messages;
|
|
351
|
+
view.more ||= next.dropped;
|
|
352
|
+
if (arriving.length > 0) {
|
|
353
|
+
marked ??= await local.jobMarkers(conversation);
|
|
354
|
+
view.marked = marked;
|
|
355
|
+
}
|
|
356
|
+
if (view.total !== null && arriving.length > 0) {
|
|
357
|
+
count ??= await local.messageCount(conversation);
|
|
358
|
+
view.total = count;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
225
362
|
function stateOf(conversation) {
|
|
226
363
|
const facts = liveOf(conversation);
|
|
227
364
|
if (facts.questions.length > 0)
|
|
228
365
|
return 'asking';
|
|
229
366
|
return facts.turn === null ? 'idle' : 'working';
|
|
230
367
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
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
|
+
asked: questionAsked(liveOf(conversation).questions),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async function entries() {
|
|
379
|
+
const list = await heldList();
|
|
380
|
+
if (list === null)
|
|
381
|
+
return null;
|
|
382
|
+
const held = list.length === 0
|
|
383
|
+
? []
|
|
384
|
+
: await deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
|
|
385
|
+
const rows = new Map(held.map((one) => [one.conversation, one]));
|
|
386
|
+
return list.map((entry) => ({
|
|
233
387
|
conversation: entry.conversation,
|
|
234
|
-
agent: { identity: entry.agent, name:
|
|
388
|
+
agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
|
|
235
389
|
name: entry.name,
|
|
236
390
|
created: entry.created,
|
|
237
391
|
finished: entry.finished,
|
|
238
392
|
outcome: entry.outcome,
|
|
239
|
-
|
|
240
|
-
|
|
393
|
+
waiting: entry.waiting,
|
|
394
|
+
...rowOf(entry.conversation),
|
|
395
|
+
...heldFacts(rows.get(entry.conversation), entry),
|
|
241
396
|
}));
|
|
242
397
|
}
|
|
398
|
+
async function heldList() {
|
|
399
|
+
const stored = (await (await deps.local()).facts()).conversationList;
|
|
400
|
+
if (stored !== null)
|
|
401
|
+
return stored;
|
|
402
|
+
return !listKnown && deps.stream() !== undefined && deps.online() ? null : [];
|
|
403
|
+
}
|
|
404
|
+
async function endedIn(conversation) {
|
|
405
|
+
const list = await heldList();
|
|
406
|
+
return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
|
|
407
|
+
}
|
|
408
|
+
function listChanged(change) {
|
|
409
|
+
void deps
|
|
410
|
+
.durably(async (local) => {
|
|
411
|
+
const { conversationList, conversationCursor } = await local.facts();
|
|
412
|
+
const kept = change.replacing ? [] : (conversationList ?? []);
|
|
413
|
+
const held = new Map(kept.map((entry) => [entry.conversation, entry]));
|
|
414
|
+
for (const entry of change.set) {
|
|
415
|
+
held.set(entry.conversation, { ...entry });
|
|
416
|
+
creations.delete(entry.conversation);
|
|
417
|
+
}
|
|
418
|
+
if (change.gone !== null)
|
|
419
|
+
held.delete(change.gone);
|
|
420
|
+
const list = [...held.values()];
|
|
421
|
+
if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
|
|
422
|
+
await local.fact('conversationList', list);
|
|
423
|
+
}
|
|
424
|
+
if (change.cursor !== undefined) {
|
|
425
|
+
if (change.cursor !== conversationCursor) {
|
|
426
|
+
await local.fact('conversationCursor', change.cursor);
|
|
427
|
+
}
|
|
428
|
+
deps.stream()?.hold(change.cursor);
|
|
429
|
+
}
|
|
430
|
+
return list;
|
|
431
|
+
})
|
|
432
|
+
.catch(async () => (await (await deps.local()).facts()).conversationList ?? [])
|
|
433
|
+
.then((list) => {
|
|
434
|
+
if (deps.closed() || change.paged === true)
|
|
435
|
+
return;
|
|
436
|
+
endedAnew(list);
|
|
437
|
+
if (change.cursor !== undefined) {
|
|
438
|
+
listKnown = true;
|
|
439
|
+
settleBehind(list);
|
|
440
|
+
openingEnded();
|
|
441
|
+
deps.settled();
|
|
442
|
+
}
|
|
443
|
+
emitList();
|
|
444
|
+
if (!listKnown)
|
|
445
|
+
return;
|
|
446
|
+
retiredDrafts(list);
|
|
447
|
+
if (change.cursor === undefined &&
|
|
448
|
+
(list.length !== replicated.size ||
|
|
449
|
+
list.some(({ conversation }) => !replicated.has(conversation)))) {
|
|
450
|
+
deps.again();
|
|
451
|
+
}
|
|
452
|
+
}, () => { });
|
|
453
|
+
}
|
|
454
|
+
function endedAnew(list) {
|
|
455
|
+
const entries = new Map(list.map((entry) => [entry.conversation, entry]));
|
|
456
|
+
for (const view of views.values()) {
|
|
457
|
+
const ended = entries.get(view.conversation)?.processEnded ?? null;
|
|
458
|
+
if (view.window === null || ended === view.ended)
|
|
459
|
+
continue;
|
|
460
|
+
view.ended = ended;
|
|
461
|
+
emit(view.conversation);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
let listShown = Promise.resolve();
|
|
465
|
+
function shownList(work) {
|
|
466
|
+
const reading = heldList();
|
|
467
|
+
listShown = listShown.then(async () => {
|
|
468
|
+
const list = await reading.catch(() => null);
|
|
469
|
+
if (list === null || deps.closed())
|
|
470
|
+
return;
|
|
471
|
+
work(list);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
async function told() {
|
|
475
|
+
try {
|
|
476
|
+
while (listOwed && !deps.closed()) {
|
|
477
|
+
listOwed = false;
|
|
478
|
+
const value = await untilAnswered(entries, closing.signal);
|
|
479
|
+
if (deps.closed())
|
|
480
|
+
return;
|
|
481
|
+
if (value === null)
|
|
482
|
+
continue;
|
|
483
|
+
for (const watcher of listWatchers)
|
|
484
|
+
watcher(value);
|
|
485
|
+
for (const watcher of [...unlisted]) {
|
|
486
|
+
unlisted.delete(watcher);
|
|
487
|
+
listWatchers.add(watcher);
|
|
488
|
+
watcher(value);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
listTelling = false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function tellList() {
|
|
497
|
+
if (deps.closed() || (listWatchers.size === 0 && unlisted.size === 0))
|
|
498
|
+
return;
|
|
499
|
+
listOwed = true;
|
|
500
|
+
if (listTelling)
|
|
501
|
+
return;
|
|
502
|
+
listTelling = true;
|
|
503
|
+
void told().catch(() => { });
|
|
504
|
+
}
|
|
505
|
+
function tellSoon() {
|
|
506
|
+
if (rowTimer !== null)
|
|
507
|
+
return;
|
|
508
|
+
rowTimer = setTimeout(() => {
|
|
509
|
+
rowTimer = null;
|
|
510
|
+
tellList();
|
|
511
|
+
}, ROW_INTERVAL_MS);
|
|
512
|
+
}
|
|
243
513
|
function emitList() {
|
|
244
|
-
if (deps.closed()
|
|
514
|
+
if (deps.closed())
|
|
515
|
+
return;
|
|
516
|
+
tellList();
|
|
517
|
+
emitAgents();
|
|
518
|
+
learned();
|
|
519
|
+
}
|
|
520
|
+
function learned() {
|
|
521
|
+
if (deps.closed())
|
|
245
522
|
return;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
523
|
+
if (unlisted.size > 0)
|
|
524
|
+
tellList();
|
|
525
|
+
if (unnamed.size === 0)
|
|
526
|
+
return;
|
|
527
|
+
shownList((list) => {
|
|
528
|
+
const value = agents(list);
|
|
529
|
+
for (const watcher of [...unnamed]) {
|
|
530
|
+
unnamed.delete(watcher);
|
|
531
|
+
agentWatchers.add(watcher);
|
|
532
|
+
watcher(value);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
function agents(list) {
|
|
537
|
+
const named = new Map();
|
|
538
|
+
for (const agent of listedAgents ?? [])
|
|
539
|
+
named.set(agent.agent, agent);
|
|
540
|
+
for (const entry of list) {
|
|
541
|
+
named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
|
|
542
|
+
}
|
|
543
|
+
return [...named]
|
|
544
|
+
.filter(([, agent]) => !agent.retired)
|
|
545
|
+
.map(([identity, agent]) => ({ identity, name: agent.name }))
|
|
254
546
|
.sort((left, right) => left.name === right.name
|
|
255
547
|
? left.identity.localeCompare(right.identity)
|
|
256
548
|
: left.name.localeCompare(right.name));
|
|
257
549
|
}
|
|
550
|
+
function readAgents() {
|
|
551
|
+
const stream = deps.stream();
|
|
552
|
+
if (stream?.agents === undefined || !deps.online() || agentsReading)
|
|
553
|
+
return;
|
|
554
|
+
agentsReading = true;
|
|
555
|
+
void stream
|
|
556
|
+
.agents()
|
|
557
|
+
.then((read) => {
|
|
558
|
+
if (read === null)
|
|
559
|
+
return;
|
|
560
|
+
listedAgents = read;
|
|
561
|
+
emitAgents();
|
|
562
|
+
}, () => { })
|
|
563
|
+
.finally(() => {
|
|
564
|
+
agentsReading = false;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
258
567
|
function emitAgents() {
|
|
259
568
|
if (deps.closed() || agentWatchers.size === 0)
|
|
260
569
|
return;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
570
|
+
shownList((list) => {
|
|
571
|
+
const value = agents(list);
|
|
572
|
+
const shown = JSON.stringify(value);
|
|
573
|
+
if (shown === agentsShown)
|
|
574
|
+
return;
|
|
575
|
+
agentsShown = shown;
|
|
576
|
+
for (const watcher of agentWatchers)
|
|
577
|
+
watcher(value);
|
|
578
|
+
});
|
|
264
579
|
}
|
|
265
580
|
function setLive(conversation, next) {
|
|
266
|
-
const before =
|
|
267
|
-
const began = lives.get(conversation)?.turn?.started;
|
|
581
|
+
const before = rowOf(conversation);
|
|
268
582
|
if (next === null)
|
|
269
583
|
lives.delete(conversation);
|
|
270
584
|
else
|
|
271
585
|
lives.set(conversation, next);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
275
|
-
emitList();
|
|
276
|
-
}
|
|
277
|
-
async function readAgents() {
|
|
278
|
-
const declaration = deps.agents();
|
|
279
|
-
if (declaration === undefined)
|
|
280
|
-
return;
|
|
281
|
-
const { documents } = await deps.documents(declaration.locator);
|
|
282
|
-
const names = new Map();
|
|
283
|
-
const retired = new Set();
|
|
284
|
-
for (const document of documents) {
|
|
285
|
-
if (document.type !== declaration.type)
|
|
286
|
-
continue;
|
|
287
|
-
const identity = document.fields?.[declaration.identity];
|
|
288
|
-
const name = document.fields?.[declaration.name];
|
|
289
|
-
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
290
|
-
continue;
|
|
291
|
-
names.set(identity, name);
|
|
292
|
-
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
293
|
-
if (ended !== undefined && ended !== null)
|
|
294
|
-
retired.add(identity);
|
|
295
|
-
}
|
|
296
|
-
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
297
|
-
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
298
|
-
return;
|
|
299
|
-
agentNames = names;
|
|
300
|
-
retiredAgents = retired;
|
|
301
|
-
if (named)
|
|
586
|
+
const after = rowOf(conversation);
|
|
587
|
+
if (after.state !== before.state || after.started !== before.started)
|
|
302
588
|
emitList();
|
|
303
|
-
|
|
589
|
+
else if (after.line !== before.line ||
|
|
590
|
+
after.question !== before.question ||
|
|
591
|
+
after.asked !== before.asked)
|
|
592
|
+
tellSoon();
|
|
304
593
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return
|
|
594
|
+
function retiredChat(conversation, list) {
|
|
595
|
+
if (!conversation.startsWith(NEW_CHAT))
|
|
596
|
+
return false;
|
|
597
|
+
const agent = conversation.slice(NEW_CHAT.length);
|
|
598
|
+
return list.some((entry) => entry.agent === agent && entry.agentRetired);
|
|
310
599
|
}
|
|
311
600
|
function live(conversation, change) {
|
|
601
|
+
const owed = unsent.get(conversation);
|
|
602
|
+
for (const fact of FACTS.filter((one) => one in change))
|
|
603
|
+
owed?.delete(fact);
|
|
312
604
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
313
|
-
emit(conversation
|
|
605
|
+
emit(conversation);
|
|
606
|
+
}
|
|
607
|
+
function openingEnded() {
|
|
608
|
+
const owed = [...unsent];
|
|
609
|
+
unsent.clear();
|
|
610
|
+
for (const [conversation, facts] of owed) {
|
|
611
|
+
const held = lives.get(conversation);
|
|
612
|
+
if (held === undefined || facts.size === 0)
|
|
613
|
+
continue;
|
|
614
|
+
setLive(conversation, {
|
|
615
|
+
turn: held.turn,
|
|
616
|
+
questions: facts.has('questions') ? QUIET.questions : held.questions,
|
|
617
|
+
process: facts.has('process') ? QUIET.process : held.process,
|
|
618
|
+
commands: facts.has('commands') ? QUIET.commands : held.commands,
|
|
619
|
+
options: facts.has('options') ? QUIET.options : held.options,
|
|
620
|
+
starting: facts.has('starting') ? QUIET.starting : held.starting,
|
|
621
|
+
subagents: facts.has('subagents') ? QUIET.subagents : held.subagents,
|
|
622
|
+
link: facts.has('link') ? QUIET.link : held.link,
|
|
623
|
+
deliveryUnknown: facts.has('deliveryUnknown')
|
|
624
|
+
? QUIET.deliveryUnknown
|
|
625
|
+
: held.deliveryUnknown,
|
|
626
|
+
});
|
|
627
|
+
emit(conversation);
|
|
628
|
+
}
|
|
314
629
|
}
|
|
315
630
|
function relayed(conversation, turn, operation) {
|
|
316
631
|
const held = liveOf(conversation).turn;
|
|
@@ -338,26 +653,94 @@ export function createConversationStore(deps) {
|
|
|
338
653
|
}
|
|
339
654
|
async function remove(leaving) {
|
|
340
655
|
await deps.durably(async (local) => {
|
|
656
|
+
const kept = new Set([
|
|
657
|
+
...(await local.conversations(leaving)).map(({ conversation }) => conversation),
|
|
658
|
+
...(await local.chatDrafts())
|
|
659
|
+
.filter(({ conversation }) => leaving.includes(conversation))
|
|
660
|
+
.map(({ conversation }) => conversation),
|
|
661
|
+
]);
|
|
341
662
|
await local.dropConversations(leaving);
|
|
342
|
-
const removed = [];
|
|
343
663
|
const changed = [];
|
|
344
664
|
for (const conversation of leaving) {
|
|
345
|
-
|
|
346
|
-
removed.push({ conversation, identity: message.identity });
|
|
347
|
-
}
|
|
348
|
-
if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
665
|
+
if (kept.has(conversation) || lives.has(conversation))
|
|
349
666
|
changed.push(conversation);
|
|
667
|
+
for (const view of views.values()) {
|
|
668
|
+
if (view.conversation === conversation)
|
|
669
|
+
view.draft = null;
|
|
350
670
|
}
|
|
351
|
-
messages.delete(conversation);
|
|
352
|
-
reads.delete(conversation);
|
|
353
|
-
drafts.delete(conversation);
|
|
354
671
|
behind.delete(conversation);
|
|
672
|
+
heardNewest.delete(conversation);
|
|
673
|
+
unsent.delete(conversation);
|
|
355
674
|
setLive(conversation, null);
|
|
675
|
+
await slide(local, conversation, null);
|
|
356
676
|
}
|
|
357
677
|
if (changed.length > 0)
|
|
358
|
-
deps.publish(conversationChangeSet(
|
|
678
|
+
deps.publish(conversationChangeSet(changed));
|
|
359
679
|
});
|
|
360
680
|
}
|
|
681
|
+
function newestOfListed(entry) {
|
|
682
|
+
if (entry === undefined)
|
|
683
|
+
return undefined;
|
|
684
|
+
const heard = heardNewest.get(entry.conversation);
|
|
685
|
+
if (heard === undefined)
|
|
686
|
+
return entry.newest;
|
|
687
|
+
return entry.newest === null || heard > entry.newest ? heard : entry.newest;
|
|
688
|
+
}
|
|
689
|
+
function missed(conversation, held, entry) {
|
|
690
|
+
const read = held?.read;
|
|
691
|
+
const newest = newestOfListed(entry);
|
|
692
|
+
return (read === undefined ||
|
|
693
|
+
newest === undefined ||
|
|
694
|
+
held?.partial === true ||
|
|
695
|
+
(newest !== null && (read === null || newest > read)));
|
|
696
|
+
}
|
|
697
|
+
async function readsOf(local, conversations) {
|
|
698
|
+
return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
|
|
699
|
+
}
|
|
700
|
+
function retiredDrafts(list) {
|
|
701
|
+
if (!list.some((entry) => entry.agentRetired))
|
|
702
|
+
return;
|
|
703
|
+
void deps
|
|
704
|
+
.local()
|
|
705
|
+
.then((local) => local.chatDrafts())
|
|
706
|
+
.then((held) => {
|
|
707
|
+
if (!deps.closed() && held.some(({ conversation }) => retiredChat(conversation, list))) {
|
|
708
|
+
deps.again();
|
|
709
|
+
}
|
|
710
|
+
}, () => { });
|
|
711
|
+
}
|
|
712
|
+
function settleBehind(list) {
|
|
713
|
+
void deps
|
|
714
|
+
.durably(async (local) => {
|
|
715
|
+
const entries = new Map(list.map((entry) => [entry.conversation, entry]));
|
|
716
|
+
const named = [...replicated].filter((conversation) => entries.has(conversation));
|
|
717
|
+
const held = await readsOf(local, named);
|
|
718
|
+
let owed = false;
|
|
719
|
+
for (const conversation of named) {
|
|
720
|
+
if (!missed(conversation, held.get(conversation), entries.get(conversation))) {
|
|
721
|
+
behind.delete(conversation);
|
|
722
|
+
}
|
|
723
|
+
else if (!behind.has(conversation)) {
|
|
724
|
+
gap(conversation);
|
|
725
|
+
owed = true;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return owed;
|
|
729
|
+
})
|
|
730
|
+
.then((owed) => {
|
|
731
|
+
if (deps.closed())
|
|
732
|
+
return;
|
|
733
|
+
deps.settled();
|
|
734
|
+
if (owed)
|
|
735
|
+
deps.again();
|
|
736
|
+
}, () => { });
|
|
737
|
+
}
|
|
738
|
+
function noted(conversation, message) {
|
|
739
|
+
const heard = heardNewest.get(conversation);
|
|
740
|
+
if (heard === undefined || message.position > heard) {
|
|
741
|
+
heardNewest.set(conversation, message.position);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
361
744
|
function gap(conversation) {
|
|
362
745
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
363
746
|
behind.add(conversation);
|
|
@@ -366,37 +749,32 @@ export function createConversationStore(deps) {
|
|
|
366
749
|
return deps.durably(async (local) => {
|
|
367
750
|
if (!replicated.has(conversation))
|
|
368
751
|
return;
|
|
369
|
-
const read =
|
|
752
|
+
const read = (await readsOf(local, [conversation])).get(conversation)?.read;
|
|
370
753
|
const advances = read !== undefined &&
|
|
371
754
|
!behind.has(conversation) &&
|
|
372
755
|
(read === null || message.position > read);
|
|
373
|
-
|
|
756
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
374
757
|
await local.keepMessages({
|
|
375
758
|
conversation,
|
|
376
759
|
messages: [message],
|
|
377
|
-
...(advances ? { read: message.position } : {}),
|
|
378
|
-
sealed,
|
|
760
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
379
761
|
});
|
|
380
|
-
messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
|
|
381
|
-
if (advances)
|
|
382
|
-
reads.set(conversation, message.position);
|
|
383
|
-
if (sealed)
|
|
384
|
-
drafts.delete(conversation);
|
|
385
762
|
sealTurns(conversation, new Set([message.turn]));
|
|
386
|
-
|
|
763
|
+
await slide(local, conversation, [message]);
|
|
764
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
765
|
+
tellList();
|
|
387
766
|
});
|
|
388
767
|
}
|
|
389
|
-
async function readHistories(conversations) {
|
|
768
|
+
async function readHistories(conversations, ended) {
|
|
390
769
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
const complete = new Set();
|
|
394
|
-
let asking = conversations.map((conversation) => ({
|
|
770
|
+
const held = await readsOf(await deps.local(), conversations);
|
|
771
|
+
let waiting = conversations.map((conversation) => ({
|
|
395
772
|
conversation,
|
|
396
|
-
after:
|
|
773
|
+
after: held.get(conversation)?.read ?? undefined,
|
|
397
774
|
}));
|
|
398
|
-
while (
|
|
399
|
-
const
|
|
775
|
+
while (waiting.length > 0) {
|
|
776
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
777
|
+
const result = await deps.history({
|
|
400
778
|
conversations: asking.map(({ conversation, after }) => ({
|
|
401
779
|
conversation,
|
|
402
780
|
...(after === undefined ? {} : { after }),
|
|
@@ -405,47 +783,62 @@ export function createConversationStore(deps) {
|
|
|
405
783
|
const pages = historyPages(result);
|
|
406
784
|
if (pages === null)
|
|
407
785
|
return result;
|
|
408
|
-
const asked = new
|
|
786
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
409
787
|
const following = [];
|
|
410
788
|
for (const page of pages) {
|
|
411
789
|
if (!asked.has(page.conversation))
|
|
412
790
|
continue;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
...page.messages,
|
|
416
|
-
]);
|
|
417
|
-
if (page.next === undefined)
|
|
418
|
-
complete.add(page.conversation);
|
|
419
|
-
else
|
|
791
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
792
|
+
if (page.next !== undefined) {
|
|
420
793
|
following.push({ conversation: page.conversation, after: page.next });
|
|
794
|
+
}
|
|
421
795
|
}
|
|
422
|
-
asking
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
796
|
+
for (const { conversation } of asking) {
|
|
797
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
798
|
+
ended(conversation);
|
|
799
|
+
}
|
|
800
|
+
waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
|
|
428
801
|
}
|
|
429
802
|
return null;
|
|
430
803
|
}
|
|
431
|
-
function
|
|
804
|
+
function caughtUp(conversations) {
|
|
805
|
+
const ends = new Map();
|
|
806
|
+
for (const conversation of conversations) {
|
|
807
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
808
|
+
catching.set(conversation, held);
|
|
809
|
+
void held.then(() => {
|
|
810
|
+
if (catching.get(conversation) === held)
|
|
811
|
+
catching.delete(conversation);
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
815
|
+
const read = readHistories(conversations, ended);
|
|
816
|
+
const endAll = () => {
|
|
817
|
+
for (const conversation of conversations)
|
|
818
|
+
ended(conversation);
|
|
819
|
+
};
|
|
820
|
+
read.then(endAll, endAll);
|
|
821
|
+
return read;
|
|
822
|
+
}
|
|
823
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
432
824
|
return deps.durably(async (local) => {
|
|
433
825
|
if (!replicated.has(conversation))
|
|
434
826
|
return;
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (sealed)
|
|
442
|
-
drafts.delete(conversation);
|
|
827
|
+
const whole = next === undefined;
|
|
828
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
829
|
+
const held = (await readsOf(local, [conversation])).get(conversation);
|
|
830
|
+
const partial = !whole && (held?.partial === true || held?.read === undefined);
|
|
831
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
832
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
443
833
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
444
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
834
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
445
835
|
behind.delete(conversation);
|
|
446
|
-
|
|
447
|
-
|
|
836
|
+
await slide(local, conversation, arriving);
|
|
837
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
838
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
448
839
|
}
|
|
840
|
+
if (arriving.length > 0)
|
|
841
|
+
tellSoon();
|
|
449
842
|
});
|
|
450
843
|
}
|
|
451
844
|
function heard(event) {
|
|
@@ -453,11 +846,8 @@ export function createConversationStore(deps) {
|
|
|
453
846
|
if (event.kind === 'gap') {
|
|
454
847
|
if (replicated.has(conversation))
|
|
455
848
|
gap(conversation);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
return;
|
|
459
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
460
|
-
emit(conversation, false);
|
|
849
|
+
if (lives.has(conversation))
|
|
850
|
+
unsent.set(conversation, new Set(FACTS));
|
|
461
851
|
return;
|
|
462
852
|
}
|
|
463
853
|
if (event.kind === 'turn') {
|
|
@@ -480,144 +870,288 @@ export function createConversationStore(deps) {
|
|
|
480
870
|
live(conversation, { options: structuredClone(event.options) });
|
|
481
871
|
return;
|
|
482
872
|
}
|
|
873
|
+
if (event.kind === 'starting') {
|
|
874
|
+
live(conversation, { starting: event.step });
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (event.kind === 'subagents') {
|
|
878
|
+
live(conversation, { subagents: structuredClone(event.subagents) });
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (event.kind === 'link') {
|
|
882
|
+
live(conversation, { link: { connected: event.connected } });
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (event.kind === 'deliveries') {
|
|
886
|
+
live(conversation, { deliveryUnknown: [...event.unknown] });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
483
889
|
const arriving = acceptedMessage(event.message);
|
|
484
890
|
if (arriving === null)
|
|
485
891
|
return;
|
|
892
|
+
noted(conversation, arriving);
|
|
486
893
|
if (replicated.has(conversation)) {
|
|
487
|
-
|
|
894
|
+
if (deps.full()) {
|
|
895
|
+
gap(conversation);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
void keep(conversation, arriving).catch((error) => {
|
|
899
|
+
if (refusedForSpace(error))
|
|
900
|
+
gap(conversation);
|
|
901
|
+
});
|
|
488
902
|
return;
|
|
489
903
|
}
|
|
490
904
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
491
905
|
emit(conversation);
|
|
492
906
|
}
|
|
907
|
+
async function kept(local, conversation, following) {
|
|
908
|
+
const shown = following === null || empty(following) ? null : following;
|
|
909
|
+
if (shown === null)
|
|
910
|
+
await local.dropChatDraft(conversation);
|
|
911
|
+
else
|
|
912
|
+
await local.keepChatDrafts([shown]);
|
|
913
|
+
shownDraft(conversation, shown);
|
|
914
|
+
}
|
|
915
|
+
function shownDraft(conversation, draft) {
|
|
916
|
+
for (const view of views.values()) {
|
|
917
|
+
if (view.conversation === conversation)
|
|
918
|
+
view.draft = draft;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
async function draftAt(local, conversation) {
|
|
922
|
+
return (await local.chatDraft(conversation)) ?? undefined;
|
|
923
|
+
}
|
|
924
|
+
async function sealed(local, conversation, turns) {
|
|
925
|
+
const held = await draftAt(local, conversation);
|
|
926
|
+
const submitted = held?.submitted ?? [];
|
|
927
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
928
|
+
if (held === undefined || left.length === submitted.length)
|
|
929
|
+
return false;
|
|
930
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
931
|
+
return true;
|
|
932
|
+
}
|
|
933
|
+
function moved(agent, operation, to) {
|
|
934
|
+
const from = newChat(agent);
|
|
935
|
+
return deps.durably(async (local) => {
|
|
936
|
+
const held = await draftAt(local, from);
|
|
937
|
+
const earlier = held?.moves ?? [];
|
|
938
|
+
if (earlier.includes(operation))
|
|
939
|
+
return;
|
|
940
|
+
const blank = {
|
|
941
|
+
conversation: from,
|
|
942
|
+
text: '',
|
|
943
|
+
revision: chatDraftRevision(),
|
|
944
|
+
};
|
|
945
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
946
|
+
const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
|
|
947
|
+
? []
|
|
948
|
+
: [{ ...unsent, conversation: to }];
|
|
949
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
950
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
951
|
+
shownDraft(from, left);
|
|
952
|
+
for (const one of taken)
|
|
953
|
+
shownDraft(to, one);
|
|
954
|
+
deps.publish(conversationChangeSet([from, to]));
|
|
955
|
+
});
|
|
956
|
+
}
|
|
493
957
|
function withDraft(conversation, next) {
|
|
494
958
|
return deps.durably(async (local) => {
|
|
495
|
-
const following = next(
|
|
959
|
+
const following = await next(await draftAt(local, conversation), local);
|
|
496
960
|
if (following === undefined)
|
|
497
961
|
return;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
962
|
+
await kept(local, conversation, following);
|
|
963
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
async function submitted(conversation, operation, draft) {
|
|
967
|
+
let submission;
|
|
968
|
+
await withDraft(conversation, (current) => {
|
|
969
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
970
|
+
if (submission !== undefined)
|
|
971
|
+
return undefined;
|
|
972
|
+
const said = draft ?? current;
|
|
973
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
974
|
+
return {
|
|
975
|
+
conversation,
|
|
976
|
+
...(draft === undefined || current === undefined
|
|
977
|
+
? { text: '', revision: chatDraftRevision() }
|
|
978
|
+
: { text: current.text, revision: current.revision }),
|
|
979
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
980
|
+
};
|
|
507
981
|
});
|
|
982
|
+
return submission;
|
|
983
|
+
}
|
|
984
|
+
async function dispatched(conversation, submission) {
|
|
985
|
+
const dispatch = { answered: false };
|
|
986
|
+
sending.set(submission.operation, dispatch);
|
|
987
|
+
try {
|
|
988
|
+
const result = await invoke('conversations/send', conversation, {
|
|
989
|
+
operation_id: submission.operation,
|
|
990
|
+
text: submission.text,
|
|
991
|
+
});
|
|
992
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
993
|
+
if (result.kind === 'revoked-authorization')
|
|
994
|
+
return result;
|
|
995
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
996
|
+
deps.unanswered();
|
|
997
|
+
return result;
|
|
998
|
+
}
|
|
999
|
+
const turn = answered(result, 'message_id');
|
|
1000
|
+
await withDraft(conversation, async (current, local) => {
|
|
1001
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
1002
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
1003
|
+
return undefined;
|
|
1004
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
1005
|
+
if (result.kind !== 'action')
|
|
1006
|
+
return returned(current, held, others);
|
|
1007
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
1008
|
+
return withSubmitted(current, turn === null || arrived
|
|
1009
|
+
? others
|
|
1010
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
1011
|
+
});
|
|
1012
|
+
return result;
|
|
1013
|
+
}
|
|
1014
|
+
finally {
|
|
1015
|
+
if (sending.get(submission.operation) === dispatch)
|
|
1016
|
+
sending.delete(submission.operation);
|
|
1017
|
+
}
|
|
508
1018
|
}
|
|
509
|
-
function invoke(name, conversation, extra = {}) {
|
|
510
|
-
return deps.call(name, { conversation, ...extra });
|
|
1019
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
1020
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
511
1021
|
}
|
|
512
1022
|
const surface = {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1023
|
+
list() {
|
|
1024
|
+
return new Promise((settle) => {
|
|
1025
|
+
const answer = (listed) => {
|
|
1026
|
+
unlisted.delete(answer);
|
|
1027
|
+
listWatchers.delete(answer);
|
|
1028
|
+
settle(listed);
|
|
1029
|
+
};
|
|
1030
|
+
unlisted.add(answer);
|
|
1031
|
+
learned();
|
|
1032
|
+
});
|
|
516
1033
|
},
|
|
517
1034
|
async read(conversation, options = {}) {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1035
|
+
const { text, ...windowed } = options;
|
|
1036
|
+
const asked = askedOf(windowed);
|
|
1037
|
+
const phrase = phraseOf(text);
|
|
1038
|
+
return deps.durably(async (local) => {
|
|
1039
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
|
|
1040
|
+
const total = asked.total ? await local.messageCount(conversation, phrase) : null;
|
|
1041
|
+
const marked = await local.jobMarkers(conversation);
|
|
1042
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(conversation), marked));
|
|
1043
|
+
});
|
|
523
1044
|
},
|
|
524
|
-
async
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
if (
|
|
528
|
-
|
|
529
|
-
|
|
1045
|
+
async create(input) {
|
|
1046
|
+
const result = await deps.call('conversations/create', input);
|
|
1047
|
+
const conversation = answered(result, 'conversation_id');
|
|
1048
|
+
if (conversation === null)
|
|
1049
|
+
return result;
|
|
1050
|
+
const listed = (await heldList()) ?? [];
|
|
1051
|
+
if (!listed.some((entry) => entry.conversation === conversation)) {
|
|
1052
|
+
creations.add(conversation);
|
|
530
1053
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (current !== draft)
|
|
545
|
-
return undefined;
|
|
546
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
547
|
-
return sealed ? null : { ...draft, turn };
|
|
1054
|
+
const creation = creationOf(input);
|
|
1055
|
+
if (creation !== null)
|
|
1056
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
1057
|
+
return result;
|
|
1058
|
+
},
|
|
1059
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
1060
|
+
if (deps.full())
|
|
1061
|
+
return DURABLE_WRITE;
|
|
1062
|
+
if (deps.online()) {
|
|
1063
|
+
const held = await submitted(conversation, operation, draft).catch((error) => {
|
|
1064
|
+
if (refusedForSpace(error))
|
|
1065
|
+
return null;
|
|
1066
|
+
throw error;
|
|
548
1067
|
});
|
|
549
|
-
|
|
1068
|
+
if (held === null)
|
|
1069
|
+
return DURABLE_WRITE;
|
|
1070
|
+
return dispatched(conversation, held);
|
|
550
1071
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
556
|
-
}
|
|
557
|
-
}
|
|
1072
|
+
if (draft !== undefined) {
|
|
1073
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
1074
|
+
? undefined
|
|
1075
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
558
1076
|
}
|
|
1077
|
+
return OFFLINE;
|
|
559
1078
|
},
|
|
560
1079
|
answer(conversation, answer) {
|
|
561
|
-
return invoke('
|
|
1080
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
1081
|
+
operation_id: changeBatchIdentity(),
|
|
1082
|
+
...answer,
|
|
1083
|
+
});
|
|
562
1084
|
},
|
|
563
1085
|
interrupt(conversation) {
|
|
564
|
-
return invoke('
|
|
1086
|
+
return invoke('conversations/interrupt', conversation);
|
|
565
1087
|
},
|
|
566
1088
|
kill(conversation) {
|
|
567
|
-
return invoke('
|
|
568
|
-
},
|
|
569
|
-
async cancel(conversation, turn) {
|
|
570
|
-
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
571
|
-
if (result.kind !== 'action')
|
|
572
|
-
return result;
|
|
573
|
-
await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
|
|
574
|
-
return result;
|
|
1089
|
+
return invoke('conversations/kill', conversation);
|
|
575
1090
|
},
|
|
576
1091
|
delete(conversation) {
|
|
577
|
-
return invoke('
|
|
1092
|
+
return invoke('conversations/delete', conversation);
|
|
578
1093
|
},
|
|
579
1094
|
};
|
|
580
1095
|
const chatDraft = {
|
|
581
1096
|
write(conversation, draft, revision = chatDraftRevision()) {
|
|
582
|
-
return withDraft(conversation, () => ({
|
|
1097
|
+
return withDraft(conversation, (current) => ({
|
|
583
1098
|
conversation,
|
|
584
1099
|
text: draft.text,
|
|
585
|
-
files: [...draft.files],
|
|
586
|
-
turn: null,
|
|
587
1100
|
revision,
|
|
1101
|
+
...(current === undefined ? {} : pending(current)),
|
|
1102
|
+
}));
|
|
1103
|
+
},
|
|
1104
|
+
prepend(conversation, draft) {
|
|
1105
|
+
return withDraft(conversation, (current) => ({
|
|
1106
|
+
conversation,
|
|
1107
|
+
...(current === undefined ? { text: draft.text } : prepended(current, draft)),
|
|
1108
|
+
revision: chatDraftRevision(),
|
|
1109
|
+
...(current === undefined ? {} : pending(current)),
|
|
588
1110
|
}));
|
|
589
1111
|
},
|
|
590
1112
|
discard(conversation) {
|
|
591
|
-
return withDraft(conversation, (current) =>
|
|
1113
|
+
return withDraft(conversation, (current) => current === undefined
|
|
1114
|
+
? undefined
|
|
1115
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
592
1116
|
},
|
|
593
1117
|
};
|
|
594
1118
|
return {
|
|
595
1119
|
surface,
|
|
596
1120
|
chatDraft,
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
messages.set(entry.conversation, [...entry.messages].sort(ordered));
|
|
604
|
-
if (entry.read !== undefined)
|
|
605
|
-
reads.set(entry.conversation, entry.read);
|
|
606
|
-
}
|
|
607
|
-
drafts.clear();
|
|
608
|
-
for (const record of stored)
|
|
609
|
-
drafts.set(record.conversation, record);
|
|
1121
|
+
async opened(local) {
|
|
1122
|
+
const { conversationList, conversationCursor } = await local.facts();
|
|
1123
|
+
if (conversationList === null || listKnown)
|
|
1124
|
+
return;
|
|
1125
|
+
deps.stream()?.hold(conversationCursor);
|
|
1126
|
+
learned();
|
|
610
1127
|
},
|
|
611
1128
|
async reconcile() {
|
|
612
1129
|
if (!listKnown)
|
|
613
1130
|
return;
|
|
614
|
-
const
|
|
1131
|
+
const local = await deps.local();
|
|
1132
|
+
const [held, kept, { conversationList }] = await Promise.all([
|
|
1133
|
+
local.conversations(),
|
|
1134
|
+
local.chatDrafts(),
|
|
1135
|
+
local.facts(),
|
|
1136
|
+
]);
|
|
1137
|
+
const list = conversationList ?? [];
|
|
1138
|
+
const entries = new Map(list.map((entry) => [entry.conversation, entry]));
|
|
1139
|
+
const named = new Set(entries.keys());
|
|
1140
|
+
const reads = new Map(held.map((one) => [one.conversation, one]));
|
|
615
1141
|
const leaving = [
|
|
616
|
-
...new Set([
|
|
617
|
-
|
|
1142
|
+
...new Set([
|
|
1143
|
+
...replicated,
|
|
1144
|
+
...reads.keys(),
|
|
1145
|
+
...kept.map(({ conversation }) => conversation),
|
|
1146
|
+
]),
|
|
1147
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
1148
|
+
!creations.has(conversation) &&
|
|
1149
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
|
|
618
1150
|
for (const conversation of named) {
|
|
619
|
-
if (!replicated.has(conversation)
|
|
1151
|
+
if (!replicated.has(conversation) &&
|
|
1152
|
+
missed(conversation, reads.get(conversation), entries.get(conversation))) {
|
|
620
1153
|
behind.add(conversation);
|
|
1154
|
+
}
|
|
621
1155
|
}
|
|
622
1156
|
replicated = named;
|
|
623
1157
|
handReplicated();
|
|
@@ -625,44 +1159,107 @@ export function createConversationStore(deps) {
|
|
|
625
1159
|
await remove(leaving);
|
|
626
1160
|
},
|
|
627
1161
|
async catchUp(progress) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1162
|
+
if (deps.full()) {
|
|
1163
|
+
if (behind.size > 0)
|
|
1164
|
+
progress.complete = false;
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
1168
|
+
const read = async (conversations) => {
|
|
1169
|
+
if (conversations.length === 0 || deps.closed())
|
|
1170
|
+
return;
|
|
1171
|
+
const failed = await caughtUp(conversations);
|
|
631
1172
|
if (failed?.kind === 'transport-uncertainty')
|
|
632
1173
|
progress.reachable = false;
|
|
633
|
-
}
|
|
1174
|
+
};
|
|
1175
|
+
const asking = [...behind].filter(owed).sort();
|
|
1176
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
1177
|
+
await Promise.all([
|
|
1178
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
1179
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
1180
|
+
]);
|
|
634
1181
|
if (behind.size > 0)
|
|
635
1182
|
progress.complete = false;
|
|
636
1183
|
},
|
|
1184
|
+
async resend(progress) {
|
|
1185
|
+
const local = await deps.local();
|
|
1186
|
+
const tried = new Set();
|
|
1187
|
+
for (const { conversation } of await local.chatDrafts()) {
|
|
1188
|
+
for (;;) {
|
|
1189
|
+
const submission = (await local.chatDraft(conversation))?.submitted?.find((one) => one.turn === null && !sending.has(one.operation) && !tried.has(one.operation));
|
|
1190
|
+
if (submission === undefined)
|
|
1191
|
+
break;
|
|
1192
|
+
if (deps.closed())
|
|
1193
|
+
return;
|
|
1194
|
+
tried.add(submission.operation);
|
|
1195
|
+
const result = await dispatched(conversation, submission);
|
|
1196
|
+
if (result.kind === 'revoked-authorization')
|
|
1197
|
+
return;
|
|
1198
|
+
if (result.kind === 'transport-uncertainty') {
|
|
1199
|
+
progress.reachable = false;
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
1203
|
+
progress.complete = false;
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
637
1209
|
current() {
|
|
638
1210
|
return behind.size === 0;
|
|
639
1211
|
},
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1212
|
+
known() {
|
|
1213
|
+
return listKnown;
|
|
1214
|
+
},
|
|
1215
|
+
reopened() {
|
|
1216
|
+
relisting = null;
|
|
1217
|
+
for (const [operation, dispatch] of sending)
|
|
1218
|
+
if (!dispatch.answered)
|
|
1219
|
+
sending.delete(operation);
|
|
1220
|
+
},
|
|
1221
|
+
lost() {
|
|
1222
|
+
catching.clear();
|
|
643
1223
|
},
|
|
644
1224
|
watch(conversation, options, deliver) {
|
|
645
|
-
const
|
|
646
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
1225
|
+
const asked = askedOf(options);
|
|
1226
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
647
1227
|
const view = views.get(key) ?? {
|
|
648
1228
|
conversation,
|
|
649
|
-
|
|
1229
|
+
asked,
|
|
650
1230
|
watchers: new Set(),
|
|
651
|
-
|
|
1231
|
+
window: null,
|
|
1232
|
+
draft: null,
|
|
1233
|
+
ended: null,
|
|
1234
|
+
marked: [],
|
|
1235
|
+
more: false,
|
|
1236
|
+
total: null,
|
|
652
1237
|
};
|
|
653
1238
|
views.set(key, view);
|
|
654
1239
|
const watcher = { notify: deliver, ready: false };
|
|
655
1240
|
view.watchers.add(watcher);
|
|
656
|
-
|
|
657
|
-
|
|
1241
|
+
const reading = new AbortController();
|
|
1242
|
+
void untilAnswered(() => deps.durably(async (local) => {
|
|
1243
|
+
if (view.window !== null)
|
|
1244
|
+
return;
|
|
1245
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
1246
|
+
view.draft = (await local.chatDraft(conversation)) ?? null;
|
|
1247
|
+
view.ended = await endedIn(conversation);
|
|
1248
|
+
view.window = messages;
|
|
1249
|
+
view.marked = await local.jobMarkers(conversation);
|
|
1250
|
+
view.more = more;
|
|
1251
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1252
|
+
}), reading.signal)
|
|
658
1253
|
.then(() => {
|
|
659
1254
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
660
1255
|
return;
|
|
661
1256
|
watcher.ready = true;
|
|
662
|
-
|
|
1257
|
+
const window = view.window ?? [];
|
|
1258
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
|
|
663
1259
|
})
|
|
664
1260
|
.catch(() => { });
|
|
665
1261
|
return () => {
|
|
1262
|
+
reading.abort();
|
|
666
1263
|
if (!view.watchers.delete(watcher))
|
|
667
1264
|
return;
|
|
668
1265
|
if (view.watchers.size === 0)
|
|
@@ -670,73 +1267,73 @@ export function createConversationStore(deps) {
|
|
|
670
1267
|
};
|
|
671
1268
|
},
|
|
672
1269
|
watchList(deliver) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
676
|
-
deliver(entries());
|
|
677
|
-
});
|
|
1270
|
+
unlisted.add(deliver);
|
|
1271
|
+
learned();
|
|
678
1272
|
return () => {
|
|
1273
|
+
unlisted.delete(deliver);
|
|
679
1274
|
listWatchers.delete(deliver);
|
|
680
1275
|
};
|
|
681
1276
|
},
|
|
682
1277
|
watchAgents(deliver) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
deliver(agents());
|
|
687
|
-
});
|
|
1278
|
+
unnamed.add(deliver);
|
|
1279
|
+
learned();
|
|
1280
|
+
readAgents();
|
|
688
1281
|
return () => {
|
|
1282
|
+
unnamed.delete(deliver);
|
|
689
1283
|
agentWatchers.delete(deliver);
|
|
690
1284
|
};
|
|
691
1285
|
},
|
|
1286
|
+
learned,
|
|
692
1287
|
changed(set) {
|
|
693
1288
|
for (const conversation of set.conversations)
|
|
694
1289
|
emit(conversation);
|
|
695
|
-
const declaration = deps.agents();
|
|
696
|
-
if (declaration !== undefined &&
|
|
697
|
-
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
698
|
-
agentsRead = readAgents().catch(() => {
|
|
699
|
-
agentsRead = null;
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
1290
|
},
|
|
703
1291
|
receive(event) {
|
|
704
1292
|
if (deps.closed())
|
|
705
|
-
return
|
|
706
|
-
if (event.kind === 'list'
|
|
707
|
-
if (event.
|
|
708
|
-
|
|
1293
|
+
return;
|
|
1294
|
+
if (event.kind === 'list') {
|
|
1295
|
+
if (event.whole)
|
|
1296
|
+
relisting ??= new Map();
|
|
1297
|
+
if (relisting !== null) {
|
|
709
1298
|
for (const entry of event.entries)
|
|
710
|
-
|
|
711
|
-
listKnown = true;
|
|
712
|
-
}
|
|
713
|
-
else if (event.kind === 'listed') {
|
|
714
|
-
listing.set(event.entry.conversation, { ...event.entry });
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
listing.delete(event.conversation);
|
|
1299
|
+
relisting.set(entry.conversation, { ...entry });
|
|
718
1300
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1301
|
+
if (event.more && relisting !== null)
|
|
1302
|
+
return;
|
|
1303
|
+
const whole = relisting;
|
|
1304
|
+
relisting = null;
|
|
1305
|
+
listChanged({
|
|
1306
|
+
replacing: whole !== null,
|
|
1307
|
+
set: whole === null ? event.entries : [...whole.values()],
|
|
1308
|
+
gone: null,
|
|
1309
|
+
...(event.more ? { paged: true } : { cursor: event.cursor }),
|
|
1310
|
+
});
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (event.kind === 'listed') {
|
|
1314
|
+
relisting?.set(event.entry.conversation, { ...event.entry });
|
|
1315
|
+
listChanged({ replacing: false, set: [event.entry], gone: null });
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (event.kind === 'unlisted') {
|
|
1319
|
+
relisting?.delete(event.conversation);
|
|
1320
|
+
listChanged({ replacing: false, set: [], gone: event.conversation });
|
|
1321
|
+
return;
|
|
724
1322
|
}
|
|
725
1323
|
heard(event);
|
|
726
|
-
return false;
|
|
727
|
-
},
|
|
728
|
-
messages() {
|
|
729
|
-
return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
|
|
730
|
-
},
|
|
731
|
-
message(conversation, identity) {
|
|
732
|
-
const held = messages.get(conversation)?.find((message) => message.identity === identity);
|
|
733
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
734
1324
|
},
|
|
735
1325
|
close() {
|
|
1326
|
+
closing.abort();
|
|
1327
|
+
if (rowTimer !== null)
|
|
1328
|
+
clearTimeout(rowTimer);
|
|
1329
|
+
rowTimer = null;
|
|
736
1330
|
views.clear();
|
|
737
1331
|
lives.clear();
|
|
1332
|
+
unsent.clear();
|
|
738
1333
|
listWatchers.clear();
|
|
1334
|
+
unlisted.clear();
|
|
739
1335
|
agentWatchers.clear();
|
|
1336
|
+
unnamed.clear();
|
|
740
1337
|
},
|
|
741
1338
|
};
|
|
742
1339
|
}
|