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