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