@agentshouse/mdruntime 0.1.0-cdb8840578a20 → 0.1.0-cdded6c080913
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 +151 -79
- 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 +319 -107
- 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 -16
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -88
- 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 +33 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +98 -11
- package/dist/collection.d.ts +11 -21
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +39 -291
- 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 +7 -129
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +97 -3359
- 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 +43 -18
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +718 -312
- 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 +16 -4
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +5 -2
- 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 +7 -8
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +101 -24
- package/dist/identity.d.ts +0 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -6
- package/dist/index.d.ts +32 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- 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 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +248 -103
- 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 +8 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -18
- 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 +17 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +100 -5
- 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 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +106 -156
- 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 +26 -185
- 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 +19 -9
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +287 -99
- package/dist/react.d.ts +12 -4
- 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 +14 -17
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +2 -8
- 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 +3 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +43 -8
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +143 -52
- 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 +331 -181
- 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 +31 -29
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +59 -116
- 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 -14
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +534 -187
- 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 -78
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -184
- 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 +7 -3
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +119 -66
- 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 +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/fences.d.ts +16 -0
- package/dist/store/fences.d.ts.map +1 -0
- package/dist/store/fences.js +26 -0
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/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 -150
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +136 -713
- 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 +5 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +51 -21
- 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 +50 -6
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +16 -2
- package/package.json +18 -19
- package/AGENTS.md +0 -300
- package/dist/acquisition.d.ts +0 -14
- package/dist/acquisition.d.ts.map +0 -1
- package/dist/acquisition.js +0 -24
- 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 -61
- 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 -27
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -63
- 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 -22
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -155
- 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 -629
- 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 -34
- 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 -25
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -639
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, questionAsked } 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,13 +37,22 @@ 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
|
}
|
|
25
51
|
export const SESSION_STARTED = 'Session started';
|
|
26
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;
|
|
27
56
|
export function newChat(agent) {
|
|
28
57
|
return `${NEW_CHAT}${agent}`;
|
|
29
58
|
}
|
|
@@ -82,19 +111,25 @@ function appliedTurn(held, operation) {
|
|
|
82
111
|
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
83
112
|
};
|
|
84
113
|
}
|
|
85
|
-
function ordered(left, right) {
|
|
86
|
-
if (left.position !== right.position)
|
|
87
|
-
return left.position < right.position ? -1 : 1;
|
|
88
|
-
if (left.identity !== right.identity)
|
|
89
|
-
return left.identity < right.identity ? -1 : 1;
|
|
90
|
-
return 0;
|
|
91
|
-
}
|
|
92
114
|
function windowOf(held, arriving, limit) {
|
|
93
115
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
94
116
|
for (const message of arriving)
|
|
95
117
|
byIdentity.set(message.identity, message);
|
|
96
|
-
const next = [...byIdentity.values()].sort(
|
|
97
|
-
|
|
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
|
+
};
|
|
98
133
|
}
|
|
99
134
|
function newestOf(arriving, from) {
|
|
100
135
|
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
@@ -165,18 +200,41 @@ function answered(result, field) {
|
|
|
165
200
|
const value = answer[field];
|
|
166
201
|
return typeof value === 'string' ? value : null;
|
|
167
202
|
}
|
|
168
|
-
function
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
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)) {
|
|
173
218
|
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
174
219
|
}
|
|
175
|
-
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;
|
|
176
229
|
}
|
|
177
|
-
const
|
|
230
|
+
const DURABLE_WRITE = {
|
|
231
|
+
kind: 'rejected',
|
|
232
|
+
code: 'durable-write',
|
|
233
|
+
fields: {},
|
|
234
|
+
retryable: true,
|
|
235
|
+
};
|
|
178
236
|
function unwritten(draft) {
|
|
179
|
-
return draft.text === ''
|
|
237
|
+
return draft.text === '';
|
|
180
238
|
}
|
|
181
239
|
function pending(draft) {
|
|
182
240
|
return {
|
|
@@ -191,39 +249,45 @@ function withSubmitted(draft, submitted) {
|
|
|
191
249
|
const { submitted: _held, ...unsent } = draft;
|
|
192
250
|
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
193
251
|
}
|
|
252
|
+
function prepended(draft, said) {
|
|
253
|
+
return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
|
|
254
|
+
}
|
|
194
255
|
function returned(draft, submission, others) {
|
|
195
256
|
return withSubmitted({
|
|
196
257
|
conversation: draft.conversation,
|
|
197
|
-
|
|
198
|
-
files: [...submission.files, ...draft.files],
|
|
258
|
+
...prepended(draft, submission),
|
|
199
259
|
revision: chatDraftRevision(),
|
|
200
260
|
}, others);
|
|
201
261
|
}
|
|
202
262
|
export function createConversationStore(deps) {
|
|
203
|
-
const stored = new Set();
|
|
204
|
-
const reads = new Map();
|
|
205
|
-
const partials = new Set();
|
|
206
|
-
const drafts = new Map();
|
|
207
263
|
const sending = new Map();
|
|
208
264
|
const behind = new Set();
|
|
265
|
+
const catching = new Map();
|
|
209
266
|
const gaps = new Map();
|
|
210
267
|
const lives = new Map();
|
|
268
|
+
const unsent = new Map();
|
|
211
269
|
const views = new Map();
|
|
212
|
-
const
|
|
270
|
+
const heardNewest = new Map();
|
|
271
|
+
let relisting = null;
|
|
213
272
|
const creations = new Set();
|
|
214
273
|
const listWatchers = new Set();
|
|
274
|
+
const unlisted = new Set();
|
|
215
275
|
let listKnown = false;
|
|
276
|
+
let listOwed = false;
|
|
277
|
+
let listTelling = false;
|
|
278
|
+
let rowTimer = null;
|
|
279
|
+
const closing = new AbortController();
|
|
216
280
|
const agentWatchers = new Set();
|
|
217
|
-
|
|
218
|
-
let
|
|
219
|
-
let
|
|
281
|
+
const unnamed = new Set();
|
|
282
|
+
let agentsShown = null;
|
|
283
|
+
let listedAgents = null;
|
|
284
|
+
let agentsReading = false;
|
|
220
285
|
let replicated = new Set();
|
|
221
286
|
let handedReplicated;
|
|
222
287
|
function liveOf(conversation) {
|
|
223
288
|
return lives.get(conversation) ?? QUIET;
|
|
224
289
|
}
|
|
225
|
-
function viewOf(conversation, held) {
|
|
226
|
-
const draft = drafts.get(conversation);
|
|
290
|
+
function viewOf(conversation, held, frame, draft, ended, marked) {
|
|
227
291
|
const facts = liveOf(conversation);
|
|
228
292
|
return {
|
|
229
293
|
conversation,
|
|
@@ -233,14 +297,21 @@ export function createConversationStore(deps) {
|
|
|
233
297
|
text,
|
|
234
298
|
message,
|
|
235
299
|
})),
|
|
300
|
+
...frame,
|
|
236
301
|
turn: structuredClone(facts.turn),
|
|
237
302
|
questions: structuredClone(facts.questions),
|
|
238
303
|
process: facts.process === null ? null : { ...facts.process },
|
|
304
|
+
processEnded: ended,
|
|
239
305
|
commands: structuredClone(facts.commands),
|
|
240
306
|
options: structuredClone(facts.options),
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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),
|
|
244
315
|
};
|
|
245
316
|
}
|
|
246
317
|
function emit(conversation) {
|
|
@@ -252,120 +323,310 @@ export function createConversationStore(deps) {
|
|
|
252
323
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
253
324
|
if (ready.length === 0)
|
|
254
325
|
continue;
|
|
255
|
-
const value = viewOf(conversation, view.window);
|
|
326
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window), view.draft, view.ended, view.marked);
|
|
256
327
|
for (const watcher of ready)
|
|
257
328
|
watcher.notify(value);
|
|
258
329
|
}
|
|
259
330
|
}
|
|
260
|
-
function
|
|
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;
|
|
261
337
|
for (const view of views.values()) {
|
|
262
338
|
if (view.conversation !== conversation || view.window === null)
|
|
263
339
|
continue;
|
|
264
|
-
|
|
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
|
+
}
|
|
265
360
|
}
|
|
266
361
|
}
|
|
267
|
-
function changesOf(conversation, arriving) {
|
|
268
|
-
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
269
|
-
}
|
|
270
362
|
function stateOf(conversation) {
|
|
271
363
|
const facts = liveOf(conversation);
|
|
272
364
|
if (facts.questions.length > 0)
|
|
273
365
|
return 'asking';
|
|
274
366
|
return facts.turn === null ? 'idle' : 'working';
|
|
275
367
|
}
|
|
276
|
-
function
|
|
277
|
-
|
|
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) => ({
|
|
278
387
|
conversation: entry.conversation,
|
|
279
|
-
agent: { identity: entry.agent, name:
|
|
388
|
+
agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
|
|
280
389
|
name: entry.name,
|
|
281
390
|
created: entry.created,
|
|
282
391
|
finished: entry.finished,
|
|
283
392
|
outcome: entry.outcome,
|
|
284
|
-
|
|
285
|
-
|
|
393
|
+
waiting: entry.waiting,
|
|
394
|
+
...rowOf(entry.conversation),
|
|
395
|
+
...heldFacts(rows.get(entry.conversation), entry),
|
|
286
396
|
}));
|
|
287
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
|
+
}
|
|
288
513
|
function emitList() {
|
|
289
|
-
if (deps.closed()
|
|
514
|
+
if (deps.closed())
|
|
515
|
+
return;
|
|
516
|
+
tellList();
|
|
517
|
+
emitAgents();
|
|
518
|
+
learned();
|
|
519
|
+
}
|
|
520
|
+
function learned() {
|
|
521
|
+
if (deps.closed())
|
|
522
|
+
return;
|
|
523
|
+
if (unlisted.size > 0)
|
|
524
|
+
tellList();
|
|
525
|
+
if (unnamed.size === 0)
|
|
290
526
|
return;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
watcher
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 }))
|
|
299
546
|
.sort((left, right) => left.name === right.name
|
|
300
547
|
? left.identity.localeCompare(right.identity)
|
|
301
548
|
: left.name.localeCompare(right.name));
|
|
302
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
|
+
}
|
|
303
567
|
function emitAgents() {
|
|
304
568
|
if (deps.closed() || agentWatchers.size === 0)
|
|
305
569
|
return;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
});
|
|
309
579
|
}
|
|
310
580
|
function setLive(conversation, next) {
|
|
311
|
-
const before =
|
|
312
|
-
const began = lives.get(conversation)?.turn?.started;
|
|
581
|
+
const before = rowOf(conversation);
|
|
313
582
|
if (next === null)
|
|
314
583
|
lives.delete(conversation);
|
|
315
584
|
else
|
|
316
585
|
lives.set(conversation, next);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
320
|
-
emitList();
|
|
321
|
-
}
|
|
322
|
-
async function readAgents() {
|
|
323
|
-
const declaration = deps.agents();
|
|
324
|
-
if (declaration === undefined)
|
|
325
|
-
return;
|
|
326
|
-
const held = await deps.documents(declaration);
|
|
327
|
-
if ('pending' in held)
|
|
328
|
-
return;
|
|
329
|
-
const { documents } = held;
|
|
330
|
-
const names = new Map();
|
|
331
|
-
const retired = new Set();
|
|
332
|
-
for (const document of documents) {
|
|
333
|
-
if (document.type !== declaration.type)
|
|
334
|
-
continue;
|
|
335
|
-
const identity = document.fields?.[declaration.identity];
|
|
336
|
-
const name = document.fields?.[declaration.name];
|
|
337
|
-
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
338
|
-
continue;
|
|
339
|
-
names.set(identity, name);
|
|
340
|
-
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
341
|
-
if (ended !== undefined && ended !== null)
|
|
342
|
-
retired.add(identity);
|
|
343
|
-
}
|
|
344
|
-
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
345
|
-
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
346
|
-
return;
|
|
347
|
-
agentNames = names;
|
|
348
|
-
retiredAgents = retired;
|
|
349
|
-
if (named)
|
|
586
|
+
const after = rowOf(conversation);
|
|
587
|
+
if (after.state !== before.state || after.started !== before.started)
|
|
350
588
|
emitList();
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
589
|
+
else if (after.line !== before.line ||
|
|
590
|
+
after.question !== before.question ||
|
|
591
|
+
after.asked !== before.asked)
|
|
592
|
+
tellSoon();
|
|
355
593
|
}
|
|
356
|
-
function retiredChat(conversation) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
agentsRead = null;
|
|
362
|
-
});
|
|
363
|
-
return agentsRead;
|
|
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);
|
|
364
599
|
}
|
|
365
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);
|
|
366
604
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
367
605
|
emit(conversation);
|
|
368
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
|
+
}
|
|
629
|
+
}
|
|
369
630
|
function relayed(conversation, turn, operation) {
|
|
370
631
|
const held = liveOf(conversation).turn;
|
|
371
632
|
const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
|
|
@@ -392,30 +653,94 @@ export function createConversationStore(deps) {
|
|
|
392
653
|
}
|
|
393
654
|
async function remove(leaving) {
|
|
394
655
|
await deps.durably(async (local) => {
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
+
]);
|
|
401
662
|
await local.dropConversations(leaving);
|
|
402
663
|
const changed = [];
|
|
403
664
|
for (const conversation of leaving) {
|
|
404
|
-
if (
|
|
665
|
+
if (kept.has(conversation) || lives.has(conversation))
|
|
405
666
|
changed.push(conversation);
|
|
667
|
+
for (const view of views.values()) {
|
|
668
|
+
if (view.conversation === conversation)
|
|
669
|
+
view.draft = null;
|
|
406
670
|
}
|
|
407
|
-
stored.delete(conversation);
|
|
408
|
-
reads.delete(conversation);
|
|
409
|
-
partials.delete(conversation);
|
|
410
|
-
drafts.delete(conversation);
|
|
411
671
|
behind.delete(conversation);
|
|
672
|
+
heardNewest.delete(conversation);
|
|
673
|
+
unsent.delete(conversation);
|
|
412
674
|
setLive(conversation, null);
|
|
413
|
-
slide(conversation, null);
|
|
675
|
+
await slide(local, conversation, null);
|
|
414
676
|
}
|
|
415
677
|
if (changed.length > 0)
|
|
416
|
-
deps.publish(conversationChangeSet(
|
|
678
|
+
deps.publish(conversationChangeSet(changed));
|
|
417
679
|
});
|
|
418
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
|
+
}
|
|
419
744
|
function gap(conversation) {
|
|
420
745
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
421
746
|
behind.add(conversation);
|
|
@@ -424,7 +749,7 @@ export function createConversationStore(deps) {
|
|
|
424
749
|
return deps.durably(async (local) => {
|
|
425
750
|
if (!replicated.has(conversation))
|
|
426
751
|
return;
|
|
427
|
-
const read =
|
|
752
|
+
const read = (await readsOf(local, [conversation])).get(conversation)?.read;
|
|
428
753
|
const advances = read !== undefined &&
|
|
429
754
|
!behind.has(conversation) &&
|
|
430
755
|
(read === null || message.position > read);
|
|
@@ -434,22 +759,22 @@ export function createConversationStore(deps) {
|
|
|
434
759
|
messages: [message],
|
|
435
760
|
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
436
761
|
});
|
|
437
|
-
stored.add(conversation);
|
|
438
|
-
if (advances)
|
|
439
|
-
reads.set(conversation, message.position);
|
|
440
762
|
sealTurns(conversation, new Set([message.turn]));
|
|
441
|
-
slide(conversation, [message]);
|
|
442
|
-
deps.publish(conversationChangeSet(
|
|
763
|
+
await slide(local, conversation, [message]);
|
|
764
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
765
|
+
tellList();
|
|
443
766
|
});
|
|
444
767
|
}
|
|
445
|
-
async function readHistories(conversations) {
|
|
768
|
+
async function readHistories(conversations, ended) {
|
|
446
769
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
447
|
-
|
|
770
|
+
const held = await readsOf(await deps.local(), conversations);
|
|
771
|
+
let waiting = conversations.map((conversation) => ({
|
|
448
772
|
conversation,
|
|
449
|
-
after:
|
|
773
|
+
after: held.get(conversation)?.read ?? undefined,
|
|
450
774
|
}));
|
|
451
|
-
while (
|
|
452
|
-
const
|
|
775
|
+
while (waiting.length > 0) {
|
|
776
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
777
|
+
const result = await deps.history({
|
|
453
778
|
conversations: asking.map(({ conversation, after }) => ({
|
|
454
779
|
conversation,
|
|
455
780
|
...(after === undefined ? {} : { after }),
|
|
@@ -468,32 +793,52 @@ export function createConversationStore(deps) {
|
|
|
468
793
|
following.push({ conversation: page.conversation, after: page.next });
|
|
469
794
|
}
|
|
470
795
|
}
|
|
471
|
-
asking
|
|
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];
|
|
472
801
|
}
|
|
473
802
|
return null;
|
|
474
803
|
}
|
|
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
|
+
}
|
|
475
823
|
function keepPage(conversation, arriving, from, next, generation) {
|
|
476
824
|
return deps.durably(async (local) => {
|
|
477
825
|
if (!replicated.has(conversation))
|
|
478
826
|
return;
|
|
479
827
|
const whole = next === undefined;
|
|
480
828
|
const cursor = next ?? newestOf(arriving, from);
|
|
481
|
-
const
|
|
829
|
+
const held = (await readsOf(local, [conversation])).get(conversation);
|
|
830
|
+
const partial = !whole && (held?.partial === true || held?.read === undefined);
|
|
482
831
|
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
483
832
|
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
484
|
-
stored.add(conversation);
|
|
485
|
-
reads.set(conversation, cursor);
|
|
486
|
-
if (partial)
|
|
487
|
-
partials.add(conversation);
|
|
488
|
-
else
|
|
489
|
-
partials.delete(conversation);
|
|
490
833
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
491
834
|
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
492
835
|
behind.delete(conversation);
|
|
493
|
-
slide(conversation, arriving);
|
|
836
|
+
await slide(local, conversation, arriving);
|
|
494
837
|
if (arriving.length > 0 || sealing || whole) {
|
|
495
|
-
deps.publish(conversationChangeSet(
|
|
838
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
496
839
|
}
|
|
840
|
+
if (arriving.length > 0)
|
|
841
|
+
tellSoon();
|
|
497
842
|
});
|
|
498
843
|
}
|
|
499
844
|
function heard(event) {
|
|
@@ -501,11 +846,8 @@ export function createConversationStore(deps) {
|
|
|
501
846
|
if (event.kind === 'gap') {
|
|
502
847
|
if (replicated.has(conversation))
|
|
503
848
|
gap(conversation);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
return;
|
|
507
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
508
|
-
emit(conversation);
|
|
849
|
+
if (lives.has(conversation))
|
|
850
|
+
unsent.set(conversation, new Set(FACTS));
|
|
509
851
|
return;
|
|
510
852
|
}
|
|
511
853
|
if (event.kind === 'turn') {
|
|
@@ -528,28 +870,59 @@ export function createConversationStore(deps) {
|
|
|
528
870
|
live(conversation, { options: structuredClone(event.options) });
|
|
529
871
|
return;
|
|
530
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
|
+
}
|
|
531
889
|
const arriving = acceptedMessage(event.message);
|
|
532
890
|
if (arriving === null)
|
|
533
891
|
return;
|
|
892
|
+
noted(conversation, arriving);
|
|
534
893
|
if (replicated.has(conversation)) {
|
|
535
|
-
|
|
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
|
+
});
|
|
536
902
|
return;
|
|
537
903
|
}
|
|
538
904
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
539
905
|
emit(conversation);
|
|
540
906
|
}
|
|
541
907
|
async function kept(local, conversation, following) {
|
|
542
|
-
|
|
908
|
+
const shown = following === null || empty(following) ? null : following;
|
|
909
|
+
if (shown === null)
|
|
543
910
|
await local.dropChatDraft(conversation);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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;
|
|
549
919
|
}
|
|
550
920
|
}
|
|
921
|
+
async function draftAt(local, conversation) {
|
|
922
|
+
return (await local.chatDraft(conversation)) ?? undefined;
|
|
923
|
+
}
|
|
551
924
|
async function sealed(local, conversation, turns) {
|
|
552
|
-
const held =
|
|
925
|
+
const held = await draftAt(local, conversation);
|
|
553
926
|
const submitted = held?.submitted ?? [];
|
|
554
927
|
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
555
928
|
if (held === undefined || left.length === submitted.length)
|
|
@@ -560,85 +933,70 @@ export function createConversationStore(deps) {
|
|
|
560
933
|
function moved(agent, operation, to) {
|
|
561
934
|
const from = newChat(agent);
|
|
562
935
|
return deps.durably(async (local) => {
|
|
563
|
-
const held =
|
|
564
|
-
const earlier =
|
|
936
|
+
const held = await draftAt(local, from);
|
|
937
|
+
const earlier = held?.moves ?? [];
|
|
565
938
|
if (earlier.includes(operation))
|
|
566
939
|
return;
|
|
567
940
|
const blank = {
|
|
568
941
|
conversation: from,
|
|
569
942
|
text: '',
|
|
570
|
-
files: [],
|
|
571
943
|
revision: chatDraftRevision(),
|
|
572
944
|
};
|
|
573
945
|
const { moves: _answered, ...unsent } = held ?? blank;
|
|
574
|
-
const taken =
|
|
946
|
+
const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
|
|
947
|
+
? []
|
|
948
|
+
: [{ ...unsent, conversation: to }];
|
|
575
949
|
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
576
950
|
await local.keepChatDrafts([left, ...taken]);
|
|
577
|
-
|
|
951
|
+
shownDraft(from, left);
|
|
578
952
|
for (const one of taken)
|
|
579
|
-
|
|
580
|
-
deps.publish(conversationChangeSet([
|
|
953
|
+
shownDraft(to, one);
|
|
954
|
+
deps.publish(conversationChangeSet([from, to]));
|
|
581
955
|
});
|
|
582
956
|
}
|
|
583
957
|
function withDraft(conversation, next) {
|
|
584
958
|
return deps.durably(async (local) => {
|
|
585
|
-
const following = await next(
|
|
959
|
+
const following = await next(await draftAt(local, conversation), local);
|
|
586
960
|
if (following === undefined)
|
|
587
961
|
return;
|
|
588
962
|
await kept(local, conversation, following);
|
|
589
|
-
deps.publish(conversationChangeSet([
|
|
963
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
590
964
|
});
|
|
591
965
|
}
|
|
592
966
|
async function submitted(conversation, operation, draft) {
|
|
593
967
|
let submission;
|
|
594
|
-
let earlier = false;
|
|
595
968
|
await withDraft(conversation, (current) => {
|
|
596
969
|
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
597
|
-
earlier = submission !== undefined;
|
|
598
970
|
if (submission !== undefined)
|
|
599
971
|
return undefined;
|
|
600
972
|
const said = draft ?? current;
|
|
601
|
-
submission = {
|
|
602
|
-
operation,
|
|
603
|
-
text: said?.text ?? '',
|
|
604
|
-
files: [...(said?.files ?? [])],
|
|
605
|
-
turn: null,
|
|
606
|
-
};
|
|
973
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
607
974
|
return {
|
|
608
975
|
conversation,
|
|
609
976
|
...(draft === undefined || current === undefined
|
|
610
|
-
? { text: '',
|
|
611
|
-
: { text: current.text,
|
|
977
|
+
? { text: '', revision: chatDraftRevision() }
|
|
978
|
+
: { text: current.text, revision: current.revision }),
|
|
612
979
|
submitted: [...(current?.submitted ?? []), submission],
|
|
613
980
|
};
|
|
614
981
|
});
|
|
615
|
-
return
|
|
616
|
-
}
|
|
617
|
-
function forgotten(conversation, submission) {
|
|
618
|
-
return withDraft(conversation, (current) => {
|
|
619
|
-
const held = current?.submitted ?? [];
|
|
620
|
-
if (current === undefined || !held.includes(submission))
|
|
621
|
-
return undefined;
|
|
622
|
-
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
623
|
-
});
|
|
982
|
+
return submission;
|
|
624
983
|
}
|
|
625
984
|
async function dispatched(conversation, submission) {
|
|
626
|
-
const dispatch = {};
|
|
985
|
+
const dispatch = { answered: false };
|
|
627
986
|
sending.set(submission.operation, dispatch);
|
|
628
987
|
try {
|
|
629
|
-
const
|
|
630
|
-
const result = await invoke('conversation.send', conversation, {
|
|
631
|
-
text: submission.text,
|
|
632
|
-
files,
|
|
988
|
+
const result = await invoke('conversations/send', conversation, {
|
|
633
989
|
operation_id: submission.operation,
|
|
990
|
+
text: submission.text,
|
|
634
991
|
});
|
|
992
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
635
993
|
if (result.kind === 'revoked-authorization')
|
|
636
994
|
return result;
|
|
637
995
|
if (result.kind !== 'action' && result.retryable) {
|
|
638
996
|
deps.unanswered();
|
|
639
997
|
return result;
|
|
640
998
|
}
|
|
641
|
-
const turn = answered(result, '
|
|
999
|
+
const turn = answered(result, 'message_id');
|
|
642
1000
|
await withDraft(conversation, async (current, local) => {
|
|
643
1001
|
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
644
1002
|
if (current === undefined || held === undefined || held.turn !== null)
|
|
@@ -658,68 +1016,80 @@ export function createConversationStore(deps) {
|
|
|
658
1016
|
sending.delete(submission.operation);
|
|
659
1017
|
}
|
|
660
1018
|
}
|
|
661
|
-
function invoke(name, conversation, extra = {}) {
|
|
662
|
-
return deps.call(name, { conversation, ...extra });
|
|
1019
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
1020
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
663
1021
|
}
|
|
664
1022
|
const surface = {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
+
});
|
|
668
1033
|
},
|
|
669
1034
|
async read(conversation, options = {}) {
|
|
670
|
-
const
|
|
671
|
-
|
|
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
|
+
});
|
|
672
1044
|
},
|
|
673
1045
|
async create(input) {
|
|
674
|
-
const result = await deps.call('
|
|
675
|
-
const conversation = answered(result, '
|
|
1046
|
+
const result = await deps.call('conversations/create', input);
|
|
1047
|
+
const conversation = answered(result, 'conversation_id');
|
|
676
1048
|
if (conversation === null)
|
|
677
1049
|
return result;
|
|
678
|
-
|
|
1050
|
+
const listed = (await heldList()) ?? [];
|
|
1051
|
+
if (!listed.some((entry) => entry.conversation === conversation)) {
|
|
679
1052
|
creations.add(conversation);
|
|
1053
|
+
}
|
|
680
1054
|
const creation = creationOf(input);
|
|
681
1055
|
if (creation !== null)
|
|
682
1056
|
await moved(creation.agent, creation.operation, conversation);
|
|
683
1057
|
return result;
|
|
684
1058
|
},
|
|
685
1059
|
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
1060
|
+
if (deps.full())
|
|
1061
|
+
return DURABLE_WRITE;
|
|
686
1062
|
if (deps.online()) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1063
|
+
const held = await submitted(conversation, operation, draft).catch((error) => {
|
|
1064
|
+
if (refusedForSpace(error))
|
|
1065
|
+
return null;
|
|
1066
|
+
throw error;
|
|
1067
|
+
});
|
|
1068
|
+
if (held === null)
|
|
1069
|
+
return DURABLE_WRITE;
|
|
1070
|
+
return dispatched(conversation, held);
|
|
692
1071
|
}
|
|
693
1072
|
if (draft !== undefined) {
|
|
694
1073
|
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
695
1074
|
? undefined
|
|
696
|
-
: returned(current ?? { conversation, text: '',
|
|
1075
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
697
1076
|
}
|
|
698
1077
|
return OFFLINE;
|
|
699
1078
|
},
|
|
700
1079
|
answer(conversation, answer) {
|
|
701
|
-
return invoke('
|
|
1080
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
1081
|
+
operation_id: changeBatchIdentity(),
|
|
1082
|
+
...answer,
|
|
1083
|
+
});
|
|
702
1084
|
},
|
|
703
1085
|
interrupt(conversation) {
|
|
704
|
-
return invoke('
|
|
1086
|
+
return invoke('conversations/interrupt', conversation);
|
|
705
1087
|
},
|
|
706
1088
|
kill(conversation) {
|
|
707
|
-
return invoke('
|
|
708
|
-
},
|
|
709
|
-
async cancel(conversation, turn) {
|
|
710
|
-
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
711
|
-
if (result.kind !== 'action')
|
|
712
|
-
return result;
|
|
713
|
-
await withDraft(conversation, (current) => {
|
|
714
|
-
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
715
|
-
if (current === undefined || held === undefined)
|
|
716
|
-
return undefined;
|
|
717
|
-
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
718
|
-
});
|
|
719
|
-
return result;
|
|
1089
|
+
return invoke('conversations/kill', conversation);
|
|
720
1090
|
},
|
|
721
1091
|
delete(conversation) {
|
|
722
|
-
return invoke('
|
|
1092
|
+
return invoke('conversations/delete', conversation);
|
|
723
1093
|
},
|
|
724
1094
|
};
|
|
725
1095
|
const chatDraft = {
|
|
@@ -727,53 +1097,61 @@ export function createConversationStore(deps) {
|
|
|
727
1097
|
return withDraft(conversation, (current) => ({
|
|
728
1098
|
conversation,
|
|
729
1099
|
text: draft.text,
|
|
730
|
-
files: [...draft.files],
|
|
731
1100
|
revision,
|
|
732
1101
|
...(current === undefined ? {} : pending(current)),
|
|
733
1102
|
}));
|
|
734
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)),
|
|
1110
|
+
}));
|
|
1111
|
+
},
|
|
735
1112
|
discard(conversation) {
|
|
736
1113
|
return withDraft(conversation, (current) => current === undefined
|
|
737
1114
|
? undefined
|
|
738
|
-
: {
|
|
739
|
-
conversation,
|
|
740
|
-
text: '',
|
|
741
|
-
files: [],
|
|
742
|
-
revision: chatDraftRevision(),
|
|
743
|
-
...pending(current),
|
|
744
|
-
});
|
|
1115
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
745
1116
|
},
|
|
746
1117
|
};
|
|
747
1118
|
return {
|
|
748
1119
|
surface,
|
|
749
1120
|
chatDraft,
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (entry.read !== undefined)
|
|
757
|
-
reads.set(entry.conversation, entry.read);
|
|
758
|
-
if (entry.partial)
|
|
759
|
-
partials.add(entry.conversation);
|
|
760
|
-
}
|
|
761
|
-
drafts.clear();
|
|
762
|
-
for (const record of kept)
|
|
763
|
-
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();
|
|
764
1127
|
},
|
|
765
1128
|
async reconcile() {
|
|
766
1129
|
if (!listKnown)
|
|
767
1130
|
return;
|
|
768
|
-
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]));
|
|
769
1141
|
const leaving = [
|
|
770
|
-
...new Set([
|
|
1142
|
+
...new Set([
|
|
1143
|
+
...replicated,
|
|
1144
|
+
...reads.keys(),
|
|
1145
|
+
...kept.map(({ conversation }) => conversation),
|
|
1146
|
+
]),
|
|
771
1147
|
].filter((conversation) => !named.has(conversation) &&
|
|
772
1148
|
!creations.has(conversation) &&
|
|
773
|
-
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
1149
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
|
|
774
1150
|
for (const conversation of named) {
|
|
775
|
-
if (!replicated.has(conversation)
|
|
1151
|
+
if (!replicated.has(conversation) &&
|
|
1152
|
+
missed(conversation, reads.get(conversation), entries.get(conversation))) {
|
|
776
1153
|
behind.add(conversation);
|
|
1154
|
+
}
|
|
777
1155
|
}
|
|
778
1156
|
replicated = named;
|
|
779
1157
|
handReplicated();
|
|
@@ -781,26 +1159,39 @@ export function createConversationStore(deps) {
|
|
|
781
1159
|
await remove(leaving);
|
|
782
1160
|
},
|
|
783
1161
|
async catchUp(progress) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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);
|
|
787
1172
|
if (failed?.kind === 'transport-uncertainty')
|
|
788
1173
|
progress.reachable = false;
|
|
789
|
-
}
|
|
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
|
+
]);
|
|
790
1181
|
if (behind.size > 0)
|
|
791
1182
|
progress.complete = false;
|
|
792
1183
|
},
|
|
793
1184
|
async resend(progress) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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;
|
|
798
1192
|
if (deps.closed())
|
|
799
1193
|
return;
|
|
800
|
-
|
|
801
|
-
await forgotten(conversation, submission);
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
1194
|
+
tried.add(submission.operation);
|
|
804
1195
|
const result = await dispatched(conversation, submission);
|
|
805
1196
|
if (result.kind === 'revoked-authorization')
|
|
806
1197
|
return;
|
|
@@ -821,37 +1212,54 @@ export function createConversationStore(deps) {
|
|
|
821
1212
|
known() {
|
|
822
1213
|
return listKnown;
|
|
823
1214
|
},
|
|
824
|
-
reconnected() {
|
|
825
|
-
for (const conversation of replicated)
|
|
826
|
-
gap(conversation);
|
|
827
|
-
},
|
|
828
1215
|
reopened() {
|
|
829
|
-
|
|
1216
|
+
relisting = null;
|
|
1217
|
+
for (const [operation, dispatch] of sending)
|
|
1218
|
+
if (!dispatch.answered)
|
|
1219
|
+
sending.delete(operation);
|
|
1220
|
+
},
|
|
1221
|
+
lost() {
|
|
1222
|
+
catching.clear();
|
|
830
1223
|
},
|
|
831
1224
|
watch(conversation, options, deliver) {
|
|
832
|
-
const
|
|
833
|
-
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]);
|
|
834
1227
|
const view = views.get(key) ?? {
|
|
835
1228
|
conversation,
|
|
836
|
-
|
|
1229
|
+
asked,
|
|
837
1230
|
watchers: new Set(),
|
|
838
1231
|
window: null,
|
|
1232
|
+
draft: null,
|
|
1233
|
+
ended: null,
|
|
1234
|
+
marked: [],
|
|
1235
|
+
more: false,
|
|
1236
|
+
total: null,
|
|
839
1237
|
};
|
|
840
1238
|
views.set(key, view);
|
|
841
1239
|
const watcher = { notify: deliver, ready: false };
|
|
842
1240
|
view.watchers.add(watcher);
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
view.window
|
|
846
|
-
|
|
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)
|
|
847
1253
|
.then(() => {
|
|
848
1254
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
849
1255
|
return;
|
|
850
1256
|
watcher.ready = true;
|
|
851
|
-
|
|
1257
|
+
const window = view.window ?? [];
|
|
1258
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
|
|
852
1259
|
})
|
|
853
1260
|
.catch(() => { });
|
|
854
1261
|
return () => {
|
|
1262
|
+
reading.abort();
|
|
855
1263
|
if (!view.watchers.delete(watcher))
|
|
856
1264
|
return;
|
|
857
1265
|
if (view.watchers.size === 0)
|
|
@@ -859,75 +1267,73 @@ export function createConversationStore(deps) {
|
|
|
859
1267
|
};
|
|
860
1268
|
},
|
|
861
1269
|
watchList(deliver) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
865
|
-
deliver(entries());
|
|
866
|
-
});
|
|
1270
|
+
unlisted.add(deliver);
|
|
1271
|
+
learned();
|
|
867
1272
|
return () => {
|
|
1273
|
+
unlisted.delete(deliver);
|
|
868
1274
|
listWatchers.delete(deliver);
|
|
869
1275
|
};
|
|
870
1276
|
},
|
|
871
1277
|
watchAgents(deliver) {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
deliver(agents());
|
|
876
|
-
});
|
|
1278
|
+
unnamed.add(deliver);
|
|
1279
|
+
learned();
|
|
1280
|
+
readAgents();
|
|
877
1281
|
return () => {
|
|
1282
|
+
unnamed.delete(deliver);
|
|
878
1283
|
agentWatchers.delete(deliver);
|
|
879
1284
|
};
|
|
880
1285
|
},
|
|
1286
|
+
learned,
|
|
881
1287
|
changed(set) {
|
|
882
1288
|
for (const conversation of set.conversations)
|
|
883
1289
|
emit(conversation);
|
|
884
|
-
const declaration = deps.agents();
|
|
885
|
-
if (declaration !== undefined &&
|
|
886
|
-
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
887
|
-
agentsRead = readAgents().catch(() => {
|
|
888
|
-
agentsRead = null;
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
1290
|
},
|
|
892
1291
|
receive(event) {
|
|
893
1292
|
if (deps.closed())
|
|
894
|
-
return
|
|
895
|
-
if (event.kind === 'list'
|
|
896
|
-
if (event.
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
listKnown = true;
|
|
903
|
-
}
|
|
904
|
-
else if (event.kind === 'listed') {
|
|
905
|
-
listing.set(event.entry.conversation, { ...event.entry });
|
|
906
|
-
creations.delete(event.entry.conversation);
|
|
907
|
-
}
|
|
908
|
-
else {
|
|
909
|
-
listing.delete(event.conversation);
|
|
1293
|
+
return;
|
|
1294
|
+
if (event.kind === 'list') {
|
|
1295
|
+
if (event.whole)
|
|
1296
|
+
relisting ??= new Map();
|
|
1297
|
+
if (relisting !== null) {
|
|
1298
|
+
for (const entry of event.entries)
|
|
1299
|
+
relisting.set(entry.conversation, { ...entry });
|
|
910
1300
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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;
|
|
916
1322
|
}
|
|
917
1323
|
heard(event);
|
|
918
|
-
return false;
|
|
919
|
-
},
|
|
920
|
-
searchable() {
|
|
921
|
-
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
922
|
-
},
|
|
923
|
-
acquired(conversation) {
|
|
924
|
-
return reads.has(conversation) && !partials.has(conversation);
|
|
925
1324
|
},
|
|
926
1325
|
close() {
|
|
1326
|
+
closing.abort();
|
|
1327
|
+
if (rowTimer !== null)
|
|
1328
|
+
clearTimeout(rowTimer);
|
|
1329
|
+
rowTimer = null;
|
|
927
1330
|
views.clear();
|
|
928
1331
|
lives.clear();
|
|
1332
|
+
unsent.clear();
|
|
929
1333
|
listWatchers.clear();
|
|
1334
|
+
unlisted.clear();
|
|
930
1335
|
agentWatchers.clear();
|
|
1336
|
+
unnamed.clear();
|
|
931
1337
|
},
|
|
932
1338
|
};
|
|
933
1339
|
}
|