@agentshouse/mdruntime 0.1.0-c95464765b116 → 0.1.0-c95f0f04f2113
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 +248 -0
- package/dist/adapter.d.ts +153 -84
- 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 +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +2 -0
- package/dist/client.d.ts +32 -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 -3344
- 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 +43 -18
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +762 -299
- 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 +7 -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/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 +31 -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 +11 -12
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -19
- 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 +46 -7
- 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 +27 -185
- 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 +19 -9
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +287 -99
- package/dist/react.d.ts +8 -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 +136 -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 -77
- 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 -149
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +136 -712
- 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 +86 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +217 -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 +407 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +724 -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 +49 -6
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -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 -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 -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 } 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,12 +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
|
}
|
|
25
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
|
+
}
|
|
26
65
|
function startedTurn(turn, operation) {
|
|
27
66
|
if (operation.op === 'begin')
|
|
28
67
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -72,19 +111,25 @@ function appliedTurn(held, operation) {
|
|
|
72
111
|
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
73
112
|
};
|
|
74
113
|
}
|
|
75
|
-
function ordered(left, right) {
|
|
76
|
-
if (left.position !== right.position)
|
|
77
|
-
return left.position < right.position ? -1 : 1;
|
|
78
|
-
if (left.identity !== right.identity)
|
|
79
|
-
return left.identity < right.identity ? -1 : 1;
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
114
|
function windowOf(held, arriving, limit) {
|
|
83
115
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
84
116
|
for (const message of arriving)
|
|
85
117
|
byIdentity.set(message.identity, message);
|
|
86
|
-
const next = [...byIdentity.values()].sort(
|
|
87
|
-
|
|
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
|
+
};
|
|
88
133
|
}
|
|
89
134
|
function newestOf(arriving, from) {
|
|
90
135
|
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
@@ -146,67 +191,103 @@ function historyPages(result) {
|
|
|
146
191
|
}
|
|
147
192
|
return pages;
|
|
148
193
|
}
|
|
149
|
-
function
|
|
194
|
+
function answered(result, field) {
|
|
150
195
|
if (result.kind !== 'action')
|
|
151
196
|
return null;
|
|
152
197
|
const answer = result.result;
|
|
153
198
|
if (typeof answer !== 'object' || answer === null)
|
|
154
199
|
return null;
|
|
155
|
-
const
|
|
156
|
-
return typeof
|
|
200
|
+
const value = answer[field];
|
|
201
|
+
return typeof value === 'string' ? value : null;
|
|
157
202
|
}
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
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)) {
|
|
163
218
|
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
164
219
|
}
|
|
165
|
-
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;
|
|
166
229
|
}
|
|
167
|
-
const
|
|
230
|
+
const DURABLE_WRITE = {
|
|
231
|
+
kind: 'rejected',
|
|
232
|
+
code: 'durable-write',
|
|
233
|
+
fields: {},
|
|
234
|
+
retryable: true,
|
|
235
|
+
};
|
|
168
236
|
function unwritten(draft) {
|
|
169
|
-
return draft.text === ''
|
|
237
|
+
return draft.text === '';
|
|
170
238
|
}
|
|
171
239
|
function pending(draft) {
|
|
172
|
-
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;
|
|
173
247
|
}
|
|
174
248
|
function withSubmitted(draft, submitted) {
|
|
175
249
|
const { submitted: _held, ...unsent } = draft;
|
|
176
250
|
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
177
251
|
}
|
|
252
|
+
function prepended(draft, said) {
|
|
253
|
+
return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
|
|
254
|
+
}
|
|
178
255
|
function returned(draft, submission, others) {
|
|
179
256
|
return withSubmitted({
|
|
180
257
|
conversation: draft.conversation,
|
|
181
|
-
|
|
182
|
-
files: [...submission.files, ...draft.files],
|
|
258
|
+
...prepended(draft, submission),
|
|
183
259
|
revision: chatDraftRevision(),
|
|
184
260
|
}, others);
|
|
185
261
|
}
|
|
186
262
|
export function createConversationStore(deps) {
|
|
187
|
-
const stored = new Set();
|
|
188
|
-
const reads = new Map();
|
|
189
|
-
const partials = new Set();
|
|
190
|
-
const drafts = new Map();
|
|
191
263
|
const sending = new Map();
|
|
192
264
|
const behind = new Set();
|
|
265
|
+
const catching = new Map();
|
|
193
266
|
const gaps = new Map();
|
|
194
267
|
const lives = new Map();
|
|
268
|
+
const unsent = new Map();
|
|
195
269
|
const views = new Map();
|
|
196
|
-
const
|
|
270
|
+
const heardNewest = new Map();
|
|
271
|
+
let relisting = null;
|
|
272
|
+
const creations = new Set();
|
|
197
273
|
const listWatchers = new Set();
|
|
274
|
+
const unlisted = new Set();
|
|
198
275
|
let listKnown = false;
|
|
276
|
+
let listOwed = false;
|
|
277
|
+
let listTelling = false;
|
|
278
|
+
let rowTimer = null;
|
|
279
|
+
const closing = new AbortController();
|
|
199
280
|
const agentWatchers = new Set();
|
|
200
|
-
|
|
201
|
-
let
|
|
202
|
-
let
|
|
281
|
+
const unnamed = new Set();
|
|
282
|
+
let agentsShown = null;
|
|
283
|
+
let listedAgents = null;
|
|
284
|
+
let agentsReading = false;
|
|
203
285
|
let replicated = new Set();
|
|
204
286
|
let handedReplicated;
|
|
205
287
|
function liveOf(conversation) {
|
|
206
288
|
return lives.get(conversation) ?? QUIET;
|
|
207
289
|
}
|
|
208
|
-
function viewOf(conversation, held) {
|
|
209
|
-
const draft = drafts.get(conversation);
|
|
290
|
+
function viewOf(conversation, held, frame, draft, ended, marked) {
|
|
210
291
|
const facts = liveOf(conversation);
|
|
211
292
|
return {
|
|
212
293
|
conversation,
|
|
@@ -216,14 +297,21 @@ export function createConversationStore(deps) {
|
|
|
216
297
|
text,
|
|
217
298
|
message,
|
|
218
299
|
})),
|
|
300
|
+
...frame,
|
|
219
301
|
turn: structuredClone(facts.turn),
|
|
220
302
|
questions: structuredClone(facts.questions),
|
|
221
303
|
process: facts.process === null ? null : { ...facts.process },
|
|
304
|
+
processEnded: ended,
|
|
222
305
|
commands: structuredClone(facts.commands),
|
|
223
306
|
options: structuredClone(facts.options),
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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),
|
|
227
315
|
};
|
|
228
316
|
}
|
|
229
317
|
function emit(conversation) {
|
|
@@ -235,114 +323,307 @@ export function createConversationStore(deps) {
|
|
|
235
323
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
236
324
|
if (ready.length === 0)
|
|
237
325
|
continue;
|
|
238
|
-
const value = viewOf(conversation, view.window);
|
|
326
|
+
const value = viewOf(conversation, view.window, sharedFrame(view, view.window), view.draft, view.ended, view.marked);
|
|
239
327
|
for (const watcher of ready)
|
|
240
328
|
watcher.notify(value);
|
|
241
329
|
}
|
|
242
330
|
}
|
|
243
|
-
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;
|
|
244
337
|
for (const view of views.values()) {
|
|
245
338
|
if (view.conversation !== conversation || view.window === null)
|
|
246
339
|
continue;
|
|
247
|
-
|
|
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
|
+
}
|
|
248
360
|
}
|
|
249
361
|
}
|
|
250
|
-
function changesOf(conversation, arriving) {
|
|
251
|
-
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
252
|
-
}
|
|
253
362
|
function stateOf(conversation) {
|
|
254
363
|
const facts = liveOf(conversation);
|
|
255
364
|
if (facts.questions.length > 0)
|
|
256
365
|
return 'asking';
|
|
257
366
|
return facts.turn === null ? 'idle' : 'working';
|
|
258
367
|
}
|
|
259
|
-
function
|
|
260
|
-
|
|
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) => ({
|
|
261
386
|
conversation: entry.conversation,
|
|
262
|
-
agent: { identity: entry.agent, name:
|
|
387
|
+
agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
|
|
263
388
|
name: entry.name,
|
|
264
389
|
created: entry.created,
|
|
265
390
|
finished: entry.finished,
|
|
266
391
|
outcome: entry.outcome,
|
|
267
|
-
|
|
268
|
-
|
|
392
|
+
waiting: entry.waiting,
|
|
393
|
+
...rowOf(entry.conversation),
|
|
394
|
+
...heldFacts(rows.get(entry.conversation), entry),
|
|
269
395
|
}));
|
|
270
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
|
+
}
|
|
271
512
|
function emitList() {
|
|
272
|
-
if (deps.closed()
|
|
513
|
+
if (deps.closed())
|
|
273
514
|
return;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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 }))
|
|
282
545
|
.sort((left, right) => left.name === right.name
|
|
283
546
|
? left.identity.localeCompare(right.identity)
|
|
284
547
|
: left.name.localeCompare(right.name));
|
|
285
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
|
+
}
|
|
286
566
|
function emitAgents() {
|
|
287
567
|
if (deps.closed() || agentWatchers.size === 0)
|
|
288
568
|
return;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
});
|
|
292
578
|
}
|
|
293
579
|
function setLive(conversation, next) {
|
|
294
|
-
const before =
|
|
295
|
-
const began = lives.get(conversation)?.turn?.started;
|
|
580
|
+
const before = rowOf(conversation);
|
|
296
581
|
if (next === null)
|
|
297
582
|
lives.delete(conversation);
|
|
298
583
|
else
|
|
299
584
|
lives.set(conversation, next);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
585
|
+
const after = rowOf(conversation);
|
|
586
|
+
if (after.state !== before.state || after.started !== before.started)
|
|
303
587
|
emitList();
|
|
588
|
+
else if (after.line !== before.line || after.question !== before.question)
|
|
589
|
+
tellSoon();
|
|
304
590
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if ('pending' in held)
|
|
311
|
-
return;
|
|
312
|
-
const { documents } = held;
|
|
313
|
-
const names = new Map();
|
|
314
|
-
const retired = new Set();
|
|
315
|
-
for (const document of documents) {
|
|
316
|
-
if (document.type !== declaration.type)
|
|
317
|
-
continue;
|
|
318
|
-
const identity = document.fields?.[declaration.identity];
|
|
319
|
-
const name = document.fields?.[declaration.name];
|
|
320
|
-
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
321
|
-
continue;
|
|
322
|
-
names.set(identity, name);
|
|
323
|
-
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
324
|
-
if (ended !== undefined && ended !== null)
|
|
325
|
-
retired.add(identity);
|
|
326
|
-
}
|
|
327
|
-
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
328
|
-
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
329
|
-
return;
|
|
330
|
-
agentNames = names;
|
|
331
|
-
retiredAgents = retired;
|
|
332
|
-
if (named)
|
|
333
|
-
emitList();
|
|
334
|
-
emitAgents();
|
|
335
|
-
}
|
|
336
|
-
function agentsReady() {
|
|
337
|
-
agentsRead ??= readAgents().catch(() => {
|
|
338
|
-
agentsRead = null;
|
|
339
|
-
});
|
|
340
|
-
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);
|
|
341
596
|
}
|
|
342
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);
|
|
343
601
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
344
602
|
emit(conversation);
|
|
345
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
|
+
}
|
|
626
|
+
}
|
|
346
627
|
function relayed(conversation, turn, operation) {
|
|
347
628
|
const held = liveOf(conversation).turn;
|
|
348
629
|
const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
|
|
@@ -369,30 +650,94 @@ export function createConversationStore(deps) {
|
|
|
369
650
|
}
|
|
370
651
|
async function remove(leaving) {
|
|
371
652
|
await deps.durably(async (local) => {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
+
]);
|
|
378
659
|
await local.dropConversations(leaving);
|
|
379
660
|
const changed = [];
|
|
380
661
|
for (const conversation of leaving) {
|
|
381
|
-
if (
|
|
662
|
+
if (kept.has(conversation) || lives.has(conversation))
|
|
382
663
|
changed.push(conversation);
|
|
664
|
+
for (const view of views.values()) {
|
|
665
|
+
if (view.conversation === conversation)
|
|
666
|
+
view.draft = null;
|
|
383
667
|
}
|
|
384
|
-
stored.delete(conversation);
|
|
385
|
-
reads.delete(conversation);
|
|
386
|
-
partials.delete(conversation);
|
|
387
|
-
drafts.delete(conversation);
|
|
388
668
|
behind.delete(conversation);
|
|
669
|
+
heardNewest.delete(conversation);
|
|
670
|
+
unsent.delete(conversation);
|
|
389
671
|
setLive(conversation, null);
|
|
390
|
-
slide(conversation, null);
|
|
672
|
+
await slide(local, conversation, null);
|
|
391
673
|
}
|
|
392
674
|
if (changed.length > 0)
|
|
393
|
-
deps.publish(conversationChangeSet(
|
|
675
|
+
deps.publish(conversationChangeSet(changed));
|
|
394
676
|
});
|
|
395
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
|
+
}
|
|
396
741
|
function gap(conversation) {
|
|
397
742
|
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
398
743
|
behind.add(conversation);
|
|
@@ -401,7 +746,7 @@ export function createConversationStore(deps) {
|
|
|
401
746
|
return deps.durably(async (local) => {
|
|
402
747
|
if (!replicated.has(conversation))
|
|
403
748
|
return;
|
|
404
|
-
const read =
|
|
749
|
+
const read = (await readsOf(local, [conversation])).get(conversation)?.read;
|
|
405
750
|
const advances = read !== undefined &&
|
|
406
751
|
!behind.has(conversation) &&
|
|
407
752
|
(read === null || message.position > read);
|
|
@@ -411,22 +756,22 @@ export function createConversationStore(deps) {
|
|
|
411
756
|
messages: [message],
|
|
412
757
|
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
413
758
|
});
|
|
414
|
-
stored.add(conversation);
|
|
415
|
-
if (advances)
|
|
416
|
-
reads.set(conversation, message.position);
|
|
417
759
|
sealTurns(conversation, new Set([message.turn]));
|
|
418
|
-
slide(conversation, [message]);
|
|
419
|
-
deps.publish(conversationChangeSet(
|
|
760
|
+
await slide(local, conversation, [message]);
|
|
761
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
762
|
+
tellList();
|
|
420
763
|
});
|
|
421
764
|
}
|
|
422
|
-
async function readHistories(conversations) {
|
|
765
|
+
async function readHistories(conversations, ended) {
|
|
423
766
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
424
|
-
|
|
767
|
+
const held = await readsOf(await deps.local(), conversations);
|
|
768
|
+
let waiting = conversations.map((conversation) => ({
|
|
425
769
|
conversation,
|
|
426
|
-
after:
|
|
770
|
+
after: held.get(conversation)?.read ?? undefined,
|
|
427
771
|
}));
|
|
428
|
-
while (
|
|
429
|
-
const
|
|
772
|
+
while (waiting.length > 0) {
|
|
773
|
+
const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
|
|
774
|
+
const result = await deps.history({
|
|
430
775
|
conversations: asking.map(({ conversation, after }) => ({
|
|
431
776
|
conversation,
|
|
432
777
|
...(after === undefined ? {} : { after }),
|
|
@@ -445,32 +790,52 @@ export function createConversationStore(deps) {
|
|
|
445
790
|
following.push({ conversation: page.conversation, after: page.next });
|
|
446
791
|
}
|
|
447
792
|
}
|
|
448
|
-
asking
|
|
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];
|
|
449
798
|
}
|
|
450
799
|
return null;
|
|
451
800
|
}
|
|
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
|
+
}
|
|
452
820
|
function keepPage(conversation, arriving, from, next, generation) {
|
|
453
821
|
return deps.durably(async (local) => {
|
|
454
822
|
if (!replicated.has(conversation))
|
|
455
823
|
return;
|
|
456
824
|
const whole = next === undefined;
|
|
457
825
|
const cursor = next ?? newestOf(arriving, from);
|
|
458
|
-
const
|
|
826
|
+
const held = (await readsOf(local, [conversation])).get(conversation);
|
|
827
|
+
const partial = !whole && (held?.partial === true || held?.read === undefined);
|
|
459
828
|
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
460
829
|
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
461
|
-
stored.add(conversation);
|
|
462
|
-
reads.set(conversation, cursor);
|
|
463
|
-
if (partial)
|
|
464
|
-
partials.add(conversation);
|
|
465
|
-
else
|
|
466
|
-
partials.delete(conversation);
|
|
467
830
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
468
831
|
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
469
832
|
behind.delete(conversation);
|
|
470
|
-
slide(conversation, arriving);
|
|
833
|
+
await slide(local, conversation, arriving);
|
|
471
834
|
if (arriving.length > 0 || sealing || whole) {
|
|
472
|
-
deps.publish(conversationChangeSet(
|
|
835
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
473
836
|
}
|
|
837
|
+
if (arriving.length > 0)
|
|
838
|
+
tellSoon();
|
|
474
839
|
});
|
|
475
840
|
}
|
|
476
841
|
function heard(event) {
|
|
@@ -478,11 +843,8 @@ export function createConversationStore(deps) {
|
|
|
478
843
|
if (event.kind === 'gap') {
|
|
479
844
|
if (replicated.has(conversation))
|
|
480
845
|
gap(conversation);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return;
|
|
484
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
485
|
-
emit(conversation);
|
|
846
|
+
if (lives.has(conversation))
|
|
847
|
+
unsent.set(conversation, new Set(FACTS));
|
|
486
848
|
return;
|
|
487
849
|
}
|
|
488
850
|
if (event.kind === 'turn') {
|
|
@@ -505,28 +867,59 @@ export function createConversationStore(deps) {
|
|
|
505
867
|
live(conversation, { options: structuredClone(event.options) });
|
|
506
868
|
return;
|
|
507
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
|
+
}
|
|
508
886
|
const arriving = acceptedMessage(event.message);
|
|
509
887
|
if (arriving === null)
|
|
510
888
|
return;
|
|
889
|
+
noted(conversation, arriving);
|
|
511
890
|
if (replicated.has(conversation)) {
|
|
512
|
-
|
|
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
|
+
});
|
|
513
899
|
return;
|
|
514
900
|
}
|
|
515
901
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
516
902
|
emit(conversation);
|
|
517
903
|
}
|
|
518
904
|
async function kept(local, conversation, following) {
|
|
519
|
-
|
|
905
|
+
const shown = following === null || empty(following) ? null : following;
|
|
906
|
+
if (shown === null)
|
|
520
907
|
await local.dropChatDraft(conversation);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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;
|
|
526
916
|
}
|
|
527
917
|
}
|
|
918
|
+
async function draftAt(local, conversation) {
|
|
919
|
+
return (await local.chatDraft(conversation)) ?? undefined;
|
|
920
|
+
}
|
|
528
921
|
async function sealed(local, conversation, turns) {
|
|
529
|
-
const held =
|
|
922
|
+
const held = await draftAt(local, conversation);
|
|
530
923
|
const submitted = held?.submitted ?? [];
|
|
531
924
|
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
532
925
|
if (held === undefined || left.length === submitted.length)
|
|
@@ -534,65 +927,73 @@ export function createConversationStore(deps) {
|
|
|
534
927
|
await kept(local, conversation, withSubmitted(held, left));
|
|
535
928
|
return true;
|
|
536
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
|
+
}
|
|
537
954
|
function withDraft(conversation, next) {
|
|
538
955
|
return deps.durably(async (local) => {
|
|
539
|
-
const following = await next(
|
|
956
|
+
const following = await next(await draftAt(local, conversation), local);
|
|
540
957
|
if (following === undefined)
|
|
541
958
|
return;
|
|
542
959
|
await kept(local, conversation, following);
|
|
543
|
-
deps.publish(conversationChangeSet([
|
|
960
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
544
961
|
});
|
|
545
962
|
}
|
|
546
963
|
async function submitted(conversation, operation, draft) {
|
|
547
964
|
let submission;
|
|
548
|
-
let earlier = false;
|
|
549
965
|
await withDraft(conversation, (current) => {
|
|
550
966
|
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
551
|
-
earlier = submission !== undefined;
|
|
552
967
|
if (submission !== undefined)
|
|
553
968
|
return undefined;
|
|
554
969
|
const said = draft ?? current;
|
|
555
|
-
submission = {
|
|
556
|
-
operation,
|
|
557
|
-
text: said?.text ?? '',
|
|
558
|
-
files: [...(said?.files ?? [])],
|
|
559
|
-
turn: null,
|
|
560
|
-
};
|
|
970
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
561
971
|
return {
|
|
562
972
|
conversation,
|
|
563
973
|
...(draft === undefined || current === undefined
|
|
564
|
-
? { text: '',
|
|
565
|
-
: { text: current.text,
|
|
974
|
+
? { text: '', revision: chatDraftRevision() }
|
|
975
|
+
: { text: current.text, revision: current.revision }),
|
|
566
976
|
submitted: [...(current?.submitted ?? []), submission],
|
|
567
977
|
};
|
|
568
978
|
});
|
|
569
|
-
return
|
|
570
|
-
}
|
|
571
|
-
function forgotten(conversation, submission) {
|
|
572
|
-
return withDraft(conversation, (current) => {
|
|
573
|
-
const held = current?.submitted ?? [];
|
|
574
|
-
if (current === undefined || !held.includes(submission))
|
|
575
|
-
return undefined;
|
|
576
|
-
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
577
|
-
});
|
|
979
|
+
return submission;
|
|
578
980
|
}
|
|
579
981
|
async function dispatched(conversation, submission) {
|
|
580
|
-
const dispatch = {};
|
|
982
|
+
const dispatch = { answered: false };
|
|
581
983
|
sending.set(submission.operation, dispatch);
|
|
582
984
|
try {
|
|
583
|
-
const
|
|
584
|
-
const result = await invoke('conversation.send', conversation, {
|
|
585
|
-
text: submission.text,
|
|
586
|
-
files,
|
|
985
|
+
const result = await invoke('conversations/send', conversation, {
|
|
587
986
|
operation_id: submission.operation,
|
|
987
|
+
text: submission.text,
|
|
588
988
|
});
|
|
989
|
+
dispatch.answered = result.kind !== 'transport-uncertainty';
|
|
589
990
|
if (result.kind === 'revoked-authorization')
|
|
590
991
|
return result;
|
|
591
992
|
if (result.kind !== 'action' && result.retryable) {
|
|
592
993
|
deps.unanswered();
|
|
593
994
|
return result;
|
|
594
995
|
}
|
|
595
|
-
const turn =
|
|
996
|
+
const turn = answered(result, 'message_id');
|
|
596
997
|
await withDraft(conversation, async (current, local) => {
|
|
597
998
|
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
598
999
|
if (current === undefined || held === undefined || held.turn !== null)
|
|
@@ -612,59 +1013,80 @@ export function createConversationStore(deps) {
|
|
|
612
1013
|
sending.delete(submission.operation);
|
|
613
1014
|
}
|
|
614
1015
|
}
|
|
615
|
-
function invoke(name, conversation, extra = {}) {
|
|
616
|
-
return deps.call(name, { conversation, ...extra });
|
|
1016
|
+
function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
|
|
1017
|
+
return deps.call(name, { conversation_id: conversation, ...extra });
|
|
617
1018
|
}
|
|
618
1019
|
const surface = {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
+
});
|
|
622
1030
|
},
|
|
623
1031
|
async read(conversation, options = {}) {
|
|
624
|
-
const
|
|
625
|
-
|
|
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
|
+
});
|
|
626
1041
|
},
|
|
627
|
-
create(input) {
|
|
628
|
-
|
|
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;
|
|
629
1055
|
},
|
|
630
1056
|
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
1057
|
+
if (deps.full())
|
|
1058
|
+
return DURABLE_WRITE;
|
|
631
1059
|
if (deps.online()) {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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);
|
|
637
1068
|
}
|
|
638
1069
|
if (draft !== undefined) {
|
|
639
1070
|
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
640
1071
|
? undefined
|
|
641
|
-
: returned(current ?? { conversation, text: '',
|
|
1072
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
642
1073
|
}
|
|
643
1074
|
return OFFLINE;
|
|
644
1075
|
},
|
|
645
1076
|
answer(conversation, answer) {
|
|
646
|
-
return invoke('
|
|
1077
|
+
return invoke('conversations/interaction/answer', conversation, {
|
|
1078
|
+
operation_id: changeBatchIdentity(),
|
|
1079
|
+
...answer,
|
|
1080
|
+
});
|
|
647
1081
|
},
|
|
648
1082
|
interrupt(conversation) {
|
|
649
|
-
return invoke('
|
|
1083
|
+
return invoke('conversations/interrupt', conversation);
|
|
650
1084
|
},
|
|
651
1085
|
kill(conversation) {
|
|
652
|
-
return invoke('
|
|
653
|
-
},
|
|
654
|
-
async cancel(conversation, turn) {
|
|
655
|
-
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
656
|
-
if (result.kind !== 'action')
|
|
657
|
-
return result;
|
|
658
|
-
await withDraft(conversation, (current) => {
|
|
659
|
-
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
660
|
-
if (current === undefined || held === undefined)
|
|
661
|
-
return undefined;
|
|
662
|
-
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
663
|
-
});
|
|
664
|
-
return result;
|
|
1086
|
+
return invoke('conversations/kill', conversation);
|
|
665
1087
|
},
|
|
666
1088
|
delete(conversation) {
|
|
667
|
-
return invoke('
|
|
1089
|
+
return invoke('conversations/delete', conversation);
|
|
668
1090
|
},
|
|
669
1091
|
};
|
|
670
1092
|
const chatDraft = {
|
|
@@ -672,51 +1094,61 @@ export function createConversationStore(deps) {
|
|
|
672
1094
|
return withDraft(conversation, (current) => ({
|
|
673
1095
|
conversation,
|
|
674
1096
|
text: draft.text,
|
|
675
|
-
files: [...draft.files],
|
|
676
1097
|
revision,
|
|
677
|
-
...(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)),
|
|
678
1107
|
}));
|
|
679
1108
|
},
|
|
680
1109
|
discard(conversation) {
|
|
681
1110
|
return withDraft(conversation, (current) => current === undefined
|
|
682
1111
|
? undefined
|
|
683
|
-
: {
|
|
684
|
-
conversation,
|
|
685
|
-
text: '',
|
|
686
|
-
files: [],
|
|
687
|
-
revision: chatDraftRevision(),
|
|
688
|
-
...pending(current),
|
|
689
|
-
});
|
|
1112
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
690
1113
|
},
|
|
691
1114
|
};
|
|
692
1115
|
return {
|
|
693
1116
|
surface,
|
|
694
1117
|
chatDraft,
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (entry.read !== undefined)
|
|
702
|
-
reads.set(entry.conversation, entry.read);
|
|
703
|
-
if (entry.partial)
|
|
704
|
-
partials.add(entry.conversation);
|
|
705
|
-
}
|
|
706
|
-
drafts.clear();
|
|
707
|
-
for (const record of kept)
|
|
708
|
-
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();
|
|
709
1124
|
},
|
|
710
1125
|
async reconcile() {
|
|
711
1126
|
if (!listKnown)
|
|
712
1127
|
return;
|
|
713
|
-
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]));
|
|
714
1138
|
const leaving = [
|
|
715
|
-
...new Set([
|
|
716
|
-
|
|
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)));
|
|
717
1147
|
for (const conversation of named) {
|
|
718
|
-
if (!replicated.has(conversation)
|
|
1148
|
+
if (!replicated.has(conversation) &&
|
|
1149
|
+
missed(conversation, reads.get(conversation), entries.get(conversation))) {
|
|
719
1150
|
behind.add(conversation);
|
|
1151
|
+
}
|
|
720
1152
|
}
|
|
721
1153
|
replicated = named;
|
|
722
1154
|
handReplicated();
|
|
@@ -724,26 +1156,39 @@ export function createConversationStore(deps) {
|
|
|
724
1156
|
await remove(leaving);
|
|
725
1157
|
},
|
|
726
1158
|
async catchUp(progress) {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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);
|
|
730
1169
|
if (failed?.kind === 'transport-uncertainty')
|
|
731
1170
|
progress.reachable = false;
|
|
732
|
-
}
|
|
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
|
+
]);
|
|
733
1178
|
if (behind.size > 0)
|
|
734
1179
|
progress.complete = false;
|
|
735
1180
|
},
|
|
736
1181
|
async resend(progress) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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;
|
|
741
1189
|
if (deps.closed())
|
|
742
1190
|
return;
|
|
743
|
-
|
|
744
|
-
await forgotten(conversation, submission);
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
1191
|
+
tried.add(submission.operation);
|
|
747
1192
|
const result = await dispatched(conversation, submission);
|
|
748
1193
|
if (result.kind === 'revoked-authorization')
|
|
749
1194
|
return;
|
|
@@ -764,37 +1209,54 @@ export function createConversationStore(deps) {
|
|
|
764
1209
|
known() {
|
|
765
1210
|
return listKnown;
|
|
766
1211
|
},
|
|
767
|
-
reconnected() {
|
|
768
|
-
for (const conversation of replicated)
|
|
769
|
-
gap(conversation);
|
|
770
|
-
},
|
|
771
1212
|
reopened() {
|
|
772
|
-
|
|
1213
|
+
relisting = null;
|
|
1214
|
+
for (const [operation, dispatch] of sending)
|
|
1215
|
+
if (!dispatch.answered)
|
|
1216
|
+
sending.delete(operation);
|
|
1217
|
+
},
|
|
1218
|
+
lost() {
|
|
1219
|
+
catching.clear();
|
|
773
1220
|
},
|
|
774
1221
|
watch(conversation, options, deliver) {
|
|
775
|
-
const
|
|
776
|
-
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]);
|
|
777
1224
|
const view = views.get(key) ?? {
|
|
778
1225
|
conversation,
|
|
779
|
-
|
|
1226
|
+
asked,
|
|
780
1227
|
watchers: new Set(),
|
|
781
1228
|
window: null,
|
|
1229
|
+
draft: null,
|
|
1230
|
+
ended: null,
|
|
1231
|
+
marked: [],
|
|
1232
|
+
more: false,
|
|
1233
|
+
total: null,
|
|
782
1234
|
};
|
|
783
1235
|
views.set(key, view);
|
|
784
1236
|
const watcher = { notify: deliver, ready: false };
|
|
785
1237
|
view.watchers.add(watcher);
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
view.window
|
|
789
|
-
|
|
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)
|
|
790
1250
|
.then(() => {
|
|
791
1251
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
792
1252
|
return;
|
|
793
1253
|
watcher.ready = true;
|
|
794
|
-
|
|
1254
|
+
const window = view.window ?? [];
|
|
1255
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
|
|
795
1256
|
})
|
|
796
1257
|
.catch(() => { });
|
|
797
1258
|
return () => {
|
|
1259
|
+
reading.abort();
|
|
798
1260
|
if (!view.watchers.delete(watcher))
|
|
799
1261
|
return;
|
|
800
1262
|
if (view.watchers.size === 0)
|
|
@@ -802,72 +1264,73 @@ export function createConversationStore(deps) {
|
|
|
802
1264
|
};
|
|
803
1265
|
},
|
|
804
1266
|
watchList(deliver) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (!deps.closed() && listWatchers.has(deliver))
|
|
808
|
-
deliver(entries());
|
|
809
|
-
});
|
|
1267
|
+
unlisted.add(deliver);
|
|
1268
|
+
learned();
|
|
810
1269
|
return () => {
|
|
1270
|
+
unlisted.delete(deliver);
|
|
811
1271
|
listWatchers.delete(deliver);
|
|
812
1272
|
};
|
|
813
1273
|
},
|
|
814
1274
|
watchAgents(deliver) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
deliver(agents());
|
|
819
|
-
});
|
|
1275
|
+
unnamed.add(deliver);
|
|
1276
|
+
learned();
|
|
1277
|
+
readAgents();
|
|
820
1278
|
return () => {
|
|
1279
|
+
unnamed.delete(deliver);
|
|
821
1280
|
agentWatchers.delete(deliver);
|
|
822
1281
|
};
|
|
823
1282
|
},
|
|
1283
|
+
learned,
|
|
824
1284
|
changed(set) {
|
|
825
1285
|
for (const conversation of set.conversations)
|
|
826
1286
|
emit(conversation);
|
|
827
|
-
const declaration = deps.agents();
|
|
828
|
-
if (declaration !== undefined &&
|
|
829
|
-
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
830
|
-
agentsRead = readAgents().catch(() => {
|
|
831
|
-
agentsRead = null;
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
1287
|
},
|
|
835
1288
|
receive(event) {
|
|
836
1289
|
if (deps.closed())
|
|
837
|
-
return
|
|
838
|
-
if (event.kind === 'list'
|
|
839
|
-
if (event.
|
|
840
|
-
|
|
1290
|
+
return;
|
|
1291
|
+
if (event.kind === 'list') {
|
|
1292
|
+
if (event.whole)
|
|
1293
|
+
relisting ??= new Map();
|
|
1294
|
+
if (relisting !== null) {
|
|
841
1295
|
for (const entry of event.entries)
|
|
842
|
-
|
|
843
|
-
listKnown = true;
|
|
1296
|
+
relisting.set(entry.conversation, { ...entry });
|
|
844
1297
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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;
|
|
856
1319
|
}
|
|
857
1320
|
heard(event);
|
|
858
|
-
return false;
|
|
859
|
-
},
|
|
860
|
-
searchable() {
|
|
861
|
-
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
862
|
-
},
|
|
863
|
-
acquired(conversation) {
|
|
864
|
-
return reads.has(conversation) && !partials.has(conversation);
|
|
865
1321
|
},
|
|
866
1322
|
close() {
|
|
1323
|
+
closing.abort();
|
|
1324
|
+
if (rowTimer !== null)
|
|
1325
|
+
clearTimeout(rowTimer);
|
|
1326
|
+
rowTimer = null;
|
|
867
1327
|
views.clear();
|
|
868
1328
|
lives.clear();
|
|
1329
|
+
unsent.clear();
|
|
869
1330
|
listWatchers.clear();
|
|
1331
|
+
unlisted.clear();
|
|
870
1332
|
agentWatchers.clear();
|
|
1333
|
+
unnamed.clear();
|
|
871
1334
|
},
|
|
872
1335
|
};
|
|
873
1336
|
}
|