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