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