@agentshouse/mdruntime 0.1.0-cfbf80d8d4644 → 0.1.0-cfcbb238a5fa0
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 +270 -203
- package/README.md +20 -10
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +83 -20
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +15 -2
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +18 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +113 -37
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +49 -16
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +33 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +61 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +27 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +63 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +22 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +155 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +629 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +34 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +25 -12
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +75 -16
- package/dist/collection.d.ts +21 -12
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +271 -23
- package/dist/connection.d.ts +44 -15
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1788 -400
- package/dist/conversation.d.ts +26 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +414 -148
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +18 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +97 -19
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +19 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +11 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +87 -25
- package/dist/model.d.ts +14 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +12 -1
- package/dist/observation.d.ts +5 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +22 -11
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +32 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +42 -1
- package/dist/overlay.d.ts +16 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +118 -77
- package/dist/query.d.ts +40 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +60 -21
- package/dist/reach.d.ts +8 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +60 -24
- package/dist/react.d.ts +31 -18
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +15 -11
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +394 -129
- 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 +197 -0
- package/dist/refresh.d.ts +13 -3
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +49 -38
- package/dist/replica.d.ts +9 -6
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +125 -68
- package/dist/room-draft.d.ts +38 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +134 -25
- package/dist/rows.d.ts +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +89 -49
- package/dist/save.d.ts +31 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +376 -47
- package/dist/search.d.ts +39 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +132 -46
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +6 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +75 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +539 -200
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +141 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +61 -31
- package/package.json +26 -14
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/conversation.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { conversationChangeSet } from './change-set.js';
|
|
2
|
+
import { runtimeError } from './failure.js';
|
|
2
3
|
import { declaredFile } from './file.js';
|
|
4
|
+
import { changeBatchIdentity, chatDraftRevision } from './identity.js';
|
|
5
|
+
import { OFFLINE } from './outcome.js';
|
|
3
6
|
const QUIET = {
|
|
4
7
|
turn: null,
|
|
5
8
|
questions: [],
|
|
@@ -19,6 +22,17 @@ export function unheldView(conversation) {
|
|
|
19
22
|
chatDraft: null,
|
|
20
23
|
};
|
|
21
24
|
}
|
|
25
|
+
export const SESSION_STARTED = 'Session started';
|
|
26
|
+
const NEW_CHAT = 'new:';
|
|
27
|
+
export function newChat(agent) {
|
|
28
|
+
return `${NEW_CHAT}${agent}`;
|
|
29
|
+
}
|
|
30
|
+
function creationOf(input) {
|
|
31
|
+
if (typeof input !== 'object' || input === null)
|
|
32
|
+
return null;
|
|
33
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
34
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
35
|
+
}
|
|
22
36
|
function startedTurn(turn, operation) {
|
|
23
37
|
if (operation.op === 'begin')
|
|
24
38
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -75,11 +89,15 @@ function ordered(left, right) {
|
|
|
75
89
|
return left.identity < right.identity ? -1 : 1;
|
|
76
90
|
return 0;
|
|
77
91
|
}
|
|
78
|
-
function
|
|
92
|
+
function windowOf(held, arriving, limit) {
|
|
79
93
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
80
94
|
for (const message of arriving)
|
|
81
95
|
byIdentity.set(message.identity, message);
|
|
82
|
-
|
|
96
|
+
const next = [...byIdentity.values()].sort(ordered);
|
|
97
|
+
return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
|
|
98
|
+
}
|
|
99
|
+
function newestOf(arriving, from) {
|
|
100
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
83
101
|
}
|
|
84
102
|
function acceptedMessage(value) {
|
|
85
103
|
if (typeof value !== 'object' || value === null)
|
|
@@ -138,86 +156,118 @@ function historyPages(result) {
|
|
|
138
156
|
}
|
|
139
157
|
return pages;
|
|
140
158
|
}
|
|
141
|
-
function
|
|
159
|
+
function answered(result, field) {
|
|
142
160
|
if (result.kind !== 'action')
|
|
143
161
|
return null;
|
|
144
162
|
const answer = result.result;
|
|
145
163
|
if (typeof answer !== 'object' || answer === null)
|
|
146
164
|
return null;
|
|
147
|
-
const
|
|
148
|
-
return typeof
|
|
165
|
+
const value = answer[field];
|
|
166
|
+
return typeof value === 'string' ? value : null;
|
|
149
167
|
}
|
|
150
168
|
function limitOf(options) {
|
|
151
169
|
const { limit } = options;
|
|
152
170
|
if (limit === undefined)
|
|
153
171
|
return undefined;
|
|
154
172
|
if (!Number.isInteger(limit) || limit < 0) {
|
|
155
|
-
throw
|
|
173
|
+
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
156
174
|
}
|
|
157
175
|
return limit;
|
|
158
176
|
}
|
|
177
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
178
|
+
function unwritten(draft) {
|
|
179
|
+
return draft.text === '' && draft.files.length === 0;
|
|
180
|
+
}
|
|
181
|
+
function pending(draft) {
|
|
182
|
+
return {
|
|
183
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
184
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function empty(draft) {
|
|
188
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
189
|
+
}
|
|
190
|
+
function withSubmitted(draft, submitted) {
|
|
191
|
+
const { submitted: _held, ...unsent } = draft;
|
|
192
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
193
|
+
}
|
|
194
|
+
function returned(draft, submission, others) {
|
|
195
|
+
return withSubmitted({
|
|
196
|
+
conversation: draft.conversation,
|
|
197
|
+
text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
|
|
198
|
+
files: [...submission.files, ...draft.files],
|
|
199
|
+
revision: chatDraftRevision(),
|
|
200
|
+
}, others);
|
|
201
|
+
}
|
|
159
202
|
export function createConversationStore(deps) {
|
|
160
|
-
const
|
|
203
|
+
const stored = new Set();
|
|
161
204
|
const reads = new Map();
|
|
205
|
+
const partials = new Set();
|
|
162
206
|
const drafts = new Map();
|
|
163
207
|
const sending = new Map();
|
|
164
208
|
const behind = new Set();
|
|
209
|
+
const catching = new Map();
|
|
165
210
|
const gaps = new Map();
|
|
166
211
|
const lives = new Map();
|
|
167
212
|
const views = new Map();
|
|
168
213
|
const listing = new Map();
|
|
214
|
+
const creations = new Set();
|
|
169
215
|
const listWatchers = new Set();
|
|
170
216
|
let listKnown = false;
|
|
217
|
+
const agentWatchers = new Set();
|
|
171
218
|
let agentNames = new Map();
|
|
219
|
+
let retiredAgents = new Set();
|
|
172
220
|
let agentsRead = null;
|
|
173
221
|
let replicated = new Set();
|
|
174
222
|
let handedReplicated;
|
|
175
223
|
function liveOf(conversation) {
|
|
176
224
|
return lives.get(conversation) ?? QUIET;
|
|
177
225
|
}
|
|
178
|
-
function
|
|
179
|
-
const held = messages.get(conversation) ?? [];
|
|
180
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
181
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
182
|
-
identity,
|
|
183
|
-
turn,
|
|
184
|
-
text,
|
|
185
|
-
message: structuredClone(message),
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
226
|
+
function viewOf(conversation, held) {
|
|
189
227
|
const draft = drafts.get(conversation);
|
|
190
228
|
const facts = liveOf(conversation);
|
|
191
229
|
return {
|
|
192
230
|
conversation,
|
|
193
|
-
messages:
|
|
231
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
232
|
+
identity,
|
|
233
|
+
turn,
|
|
234
|
+
text,
|
|
235
|
+
message,
|
|
236
|
+
})),
|
|
194
237
|
turn: structuredClone(facts.turn),
|
|
195
238
|
questions: structuredClone(facts.questions),
|
|
196
239
|
process: facts.process === null ? null : { ...facts.process },
|
|
197
240
|
commands: structuredClone(facts.commands),
|
|
198
241
|
options: structuredClone(facts.options),
|
|
199
|
-
chatDraft: draft === undefined || draft
|
|
242
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
200
243
|
? null
|
|
201
|
-
: { text: draft.text, files: [...draft.files] },
|
|
244
|
+
: { text: draft.text, files: [...draft.files], revision: draft.revision },
|
|
202
245
|
};
|
|
203
246
|
}
|
|
204
|
-
function emit(conversation
|
|
247
|
+
function emit(conversation) {
|
|
205
248
|
if (deps.closed())
|
|
206
249
|
return;
|
|
207
250
|
for (const view of views.values()) {
|
|
208
|
-
if (view.conversation !== conversation)
|
|
251
|
+
if (view.conversation !== conversation || view.window === null)
|
|
209
252
|
continue;
|
|
210
|
-
if (messagesChanged)
|
|
211
|
-
view.messages = null;
|
|
212
253
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
213
254
|
if (ready.length === 0)
|
|
214
255
|
continue;
|
|
215
|
-
|
|
216
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
256
|
+
const value = viewOf(conversation, view.window);
|
|
217
257
|
for (const watcher of ready)
|
|
218
258
|
watcher.notify(value);
|
|
219
259
|
}
|
|
220
260
|
}
|
|
261
|
+
function slide(conversation, arriving) {
|
|
262
|
+
for (const view of views.values()) {
|
|
263
|
+
if (view.conversation !== conversation || view.window === null)
|
|
264
|
+
continue;
|
|
265
|
+
view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function changesOf(conversation, arriving) {
|
|
269
|
+
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
270
|
+
}
|
|
221
271
|
function stateOf(conversation) {
|
|
222
272
|
const facts = liveOf(conversation);
|
|
223
273
|
if (facts.questions.length > 0)
|
|
@@ -243,6 +293,21 @@ export function createConversationStore(deps) {
|
|
|
243
293
|
for (const watcher of listWatchers)
|
|
244
294
|
watcher(value);
|
|
245
295
|
}
|
|
296
|
+
function agents() {
|
|
297
|
+
return [...agentNames]
|
|
298
|
+
.filter(([identity]) => !retiredAgents.has(identity))
|
|
299
|
+
.map(([identity, name]) => ({ identity, name }))
|
|
300
|
+
.sort((left, right) => left.name === right.name
|
|
301
|
+
? left.identity.localeCompare(right.identity)
|
|
302
|
+
: left.name.localeCompare(right.name));
|
|
303
|
+
}
|
|
304
|
+
function emitAgents() {
|
|
305
|
+
if (deps.closed() || agentWatchers.size === 0)
|
|
306
|
+
return;
|
|
307
|
+
const value = agents();
|
|
308
|
+
for (const watcher of agentWatchers)
|
|
309
|
+
watcher(value);
|
|
310
|
+
}
|
|
246
311
|
function setLive(conversation, next) {
|
|
247
312
|
const before = stateOf(conversation);
|
|
248
313
|
const began = lives.get(conversation)?.turn?.started;
|
|
@@ -259,20 +324,38 @@ export function createConversationStore(deps) {
|
|
|
259
324
|
const declaration = deps.agents();
|
|
260
325
|
if (declaration === undefined)
|
|
261
326
|
return;
|
|
262
|
-
const
|
|
327
|
+
const held = await deps.documents(declaration);
|
|
328
|
+
if ('pending' in held)
|
|
329
|
+
return;
|
|
330
|
+
const { documents } = held;
|
|
263
331
|
const names = new Map();
|
|
332
|
+
const retired = new Set();
|
|
264
333
|
for (const document of documents) {
|
|
265
334
|
if (document.type !== declaration.type)
|
|
266
335
|
continue;
|
|
267
336
|
const identity = document.fields?.[declaration.identity];
|
|
268
337
|
const name = document.fields?.[declaration.name];
|
|
269
|
-
if (typeof identity
|
|
270
|
-
|
|
338
|
+
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
339
|
+
continue;
|
|
340
|
+
names.set(identity, name);
|
|
341
|
+
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
342
|
+
if (ended !== undefined && ended !== null)
|
|
343
|
+
retired.add(identity);
|
|
271
344
|
}
|
|
272
|
-
|
|
345
|
+
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
346
|
+
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
273
347
|
return;
|
|
274
348
|
agentNames = names;
|
|
275
|
-
|
|
349
|
+
retiredAgents = retired;
|
|
350
|
+
if (named)
|
|
351
|
+
emitList();
|
|
352
|
+
emitAgents();
|
|
353
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
354
|
+
if (ended.length > 0)
|
|
355
|
+
await remove(ended);
|
|
356
|
+
}
|
|
357
|
+
function retiredChat(conversation) {
|
|
358
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
276
359
|
}
|
|
277
360
|
function agentsReady() {
|
|
278
361
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -282,7 +365,7 @@ export function createConversationStore(deps) {
|
|
|
282
365
|
}
|
|
283
366
|
function live(conversation, change) {
|
|
284
367
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
285
|
-
emit(conversation
|
|
368
|
+
emit(conversation);
|
|
286
369
|
}
|
|
287
370
|
function relayed(conversation, turn, operation) {
|
|
288
371
|
const held = liveOf(conversation).turn;
|
|
@@ -310,21 +393,25 @@ export function createConversationStore(deps) {
|
|
|
310
393
|
}
|
|
311
394
|
async function remove(leaving) {
|
|
312
395
|
await deps.durably(async (local) => {
|
|
313
|
-
await local.dropConversations(leaving);
|
|
314
396
|
const removed = [];
|
|
315
|
-
const changed = [];
|
|
316
397
|
for (const conversation of leaving) {
|
|
317
|
-
for (const
|
|
318
|
-
removed.push({ conversation, identity:
|
|
398
|
+
for (const identity of await local.messageIdentities(conversation)) {
|
|
399
|
+
removed.push({ conversation, identity, text: null });
|
|
319
400
|
}
|
|
320
|
-
|
|
401
|
+
}
|
|
402
|
+
await local.dropConversations(leaving);
|
|
403
|
+
const changed = [];
|
|
404
|
+
for (const conversation of leaving) {
|
|
405
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
321
406
|
changed.push(conversation);
|
|
322
407
|
}
|
|
323
|
-
|
|
408
|
+
stored.delete(conversation);
|
|
324
409
|
reads.delete(conversation);
|
|
410
|
+
partials.delete(conversation);
|
|
325
411
|
drafts.delete(conversation);
|
|
326
412
|
behind.delete(conversation);
|
|
327
413
|
setLive(conversation, null);
|
|
414
|
+
slide(conversation, null);
|
|
328
415
|
}
|
|
329
416
|
if (changed.length > 0)
|
|
330
417
|
deps.publish(conversationChangeSet(removed, changed));
|
|
@@ -342,30 +429,25 @@ export function createConversationStore(deps) {
|
|
|
342
429
|
const advances = read !== undefined &&
|
|
343
430
|
!behind.has(conversation) &&
|
|
344
431
|
(read === null || message.position > read);
|
|
345
|
-
|
|
432
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
346
433
|
await local.keepMessages({
|
|
347
434
|
conversation,
|
|
348
435
|
messages: [message],
|
|
349
|
-
...(advances ? { read: message.position } : {}),
|
|
350
|
-
sealed,
|
|
436
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
351
437
|
});
|
|
352
|
-
|
|
438
|
+
stored.add(conversation);
|
|
353
439
|
if (advances)
|
|
354
440
|
reads.set(conversation, message.position);
|
|
355
|
-
if (sealed)
|
|
356
|
-
drafts.delete(conversation);
|
|
357
441
|
sealTurns(conversation, new Set([message.turn]));
|
|
358
|
-
|
|
442
|
+
slide(conversation, [message]);
|
|
443
|
+
deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
|
|
359
444
|
});
|
|
360
445
|
}
|
|
361
|
-
async function readHistories(conversations) {
|
|
446
|
+
async function readHistories(conversations, ended) {
|
|
362
447
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
363
|
-
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
364
|
-
const arriving = new Map();
|
|
365
|
-
const complete = new Set();
|
|
366
448
|
let asking = conversations.map((conversation) => ({
|
|
367
449
|
conversation,
|
|
368
|
-
after:
|
|
450
|
+
after: reads.get(conversation) ?? undefined,
|
|
369
451
|
}));
|
|
370
452
|
while (asking.length > 0) {
|
|
371
453
|
const result = await deps.call('conversation.history', {
|
|
@@ -377,46 +459,64 @@ export function createConversationStore(deps) {
|
|
|
377
459
|
const pages = historyPages(result);
|
|
378
460
|
if (pages === null)
|
|
379
461
|
return result;
|
|
380
|
-
const asked = new
|
|
462
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
381
463
|
const following = [];
|
|
382
464
|
for (const page of pages) {
|
|
383
465
|
if (!asked.has(page.conversation))
|
|
384
466
|
continue;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
...page.messages,
|
|
388
|
-
]);
|
|
389
|
-
if (page.next === undefined)
|
|
390
|
-
complete.add(page.conversation);
|
|
391
|
-
else
|
|
467
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
468
|
+
if (page.next !== undefined) {
|
|
392
469
|
following.push({ conversation: page.conversation, after: page.next });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const { conversation } of asking) {
|
|
473
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
474
|
+
ended(conversation);
|
|
393
475
|
}
|
|
394
476
|
asking = following;
|
|
395
477
|
}
|
|
396
|
-
for (const [conversation, held] of arriving) {
|
|
397
|
-
if (!complete.has(conversation))
|
|
398
|
-
continue;
|
|
399
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
400
|
-
}
|
|
401
478
|
return null;
|
|
402
479
|
}
|
|
403
|
-
function
|
|
480
|
+
function caughtUp(conversations) {
|
|
481
|
+
const ends = new Map();
|
|
482
|
+
for (const conversation of conversations) {
|
|
483
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
484
|
+
catching.set(conversation, held);
|
|
485
|
+
void held.then(() => {
|
|
486
|
+
if (catching.get(conversation) === held)
|
|
487
|
+
catching.delete(conversation);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
491
|
+
const read = readHistories(conversations, ended);
|
|
492
|
+
const endAll = () => {
|
|
493
|
+
for (const conversation of conversations)
|
|
494
|
+
ended(conversation);
|
|
495
|
+
};
|
|
496
|
+
read.then(endAll, endAll);
|
|
497
|
+
return read;
|
|
498
|
+
}
|
|
499
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
404
500
|
return deps.durably(async (local) => {
|
|
405
501
|
if (!replicated.has(conversation))
|
|
406
502
|
return;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
await local
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
503
|
+
const whole = next === undefined;
|
|
504
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
505
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
506
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
507
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
508
|
+
stored.add(conversation);
|
|
509
|
+
reads.set(conversation, cursor);
|
|
510
|
+
if (partial)
|
|
511
|
+
partials.add(conversation);
|
|
512
|
+
else
|
|
513
|
+
partials.delete(conversation);
|
|
415
514
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
416
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
515
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
417
516
|
behind.delete(conversation);
|
|
418
|
-
|
|
419
|
-
|
|
517
|
+
slide(conversation, arriving);
|
|
518
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
519
|
+
deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
|
|
420
520
|
}
|
|
421
521
|
});
|
|
422
522
|
}
|
|
@@ -429,7 +529,7 @@ export function createConversationStore(deps) {
|
|
|
429
529
|
if (held === undefined)
|
|
430
530
|
return;
|
|
431
531
|
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
432
|
-
emit(conversation
|
|
532
|
+
emit(conversation);
|
|
433
533
|
return;
|
|
434
534
|
}
|
|
435
535
|
if (event.kind === 'turn') {
|
|
@@ -462,22 +562,126 @@ export function createConversationStore(deps) {
|
|
|
462
562
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
463
563
|
emit(conversation);
|
|
464
564
|
}
|
|
565
|
+
async function kept(local, conversation, following) {
|
|
566
|
+
if (following === null || empty(following)) {
|
|
567
|
+
await local.dropChatDraft(conversation);
|
|
568
|
+
drafts.delete(conversation);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
await local.keepChatDrafts([following]);
|
|
572
|
+
drafts.set(conversation, following);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async function sealed(local, conversation, turns) {
|
|
576
|
+
const held = drafts.get(conversation);
|
|
577
|
+
const submitted = held?.submitted ?? [];
|
|
578
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
579
|
+
if (held === undefined || left.length === submitted.length)
|
|
580
|
+
return false;
|
|
581
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
function moved(agent, operation, to) {
|
|
585
|
+
const from = newChat(agent);
|
|
586
|
+
return deps.durably(async (local) => {
|
|
587
|
+
const held = drafts.get(from);
|
|
588
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
589
|
+
if (earlier.includes(operation))
|
|
590
|
+
return;
|
|
591
|
+
const blank = {
|
|
592
|
+
conversation: from,
|
|
593
|
+
text: '',
|
|
594
|
+
files: [],
|
|
595
|
+
revision: chatDraftRevision(),
|
|
596
|
+
};
|
|
597
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
598
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
599
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
600
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
601
|
+
drafts.set(from, left);
|
|
602
|
+
for (const one of taken)
|
|
603
|
+
drafts.set(to, one);
|
|
604
|
+
deps.publish(conversationChangeSet([], [from, to]));
|
|
605
|
+
});
|
|
606
|
+
}
|
|
465
607
|
function withDraft(conversation, next) {
|
|
466
608
|
return deps.durably(async (local) => {
|
|
467
|
-
const following = next(drafts.get(conversation));
|
|
609
|
+
const following = await next(drafts.get(conversation), local);
|
|
468
610
|
if (following === undefined)
|
|
469
611
|
return;
|
|
470
|
-
|
|
471
|
-
await local.dropChatDraft(conversation);
|
|
472
|
-
drafts.delete(conversation);
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
475
|
-
await local.keepChatDraft(following);
|
|
476
|
-
drafts.set(conversation, following);
|
|
477
|
-
}
|
|
612
|
+
await kept(local, conversation, following);
|
|
478
613
|
deps.publish(conversationChangeSet([], [conversation]));
|
|
479
614
|
});
|
|
480
615
|
}
|
|
616
|
+
async function submitted(conversation, operation, draft) {
|
|
617
|
+
let submission;
|
|
618
|
+
let earlier = false;
|
|
619
|
+
await withDraft(conversation, (current) => {
|
|
620
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
621
|
+
earlier = submission !== undefined;
|
|
622
|
+
if (submission !== undefined)
|
|
623
|
+
return undefined;
|
|
624
|
+
const said = draft ?? current;
|
|
625
|
+
submission = {
|
|
626
|
+
operation,
|
|
627
|
+
text: said?.text ?? '',
|
|
628
|
+
files: [...(said?.files ?? [])],
|
|
629
|
+
turn: null,
|
|
630
|
+
};
|
|
631
|
+
return {
|
|
632
|
+
conversation,
|
|
633
|
+
...(draft === undefined || current === undefined
|
|
634
|
+
? { text: '', files: [], revision: chatDraftRevision() }
|
|
635
|
+
: { text: current.text, files: current.files, revision: current.revision }),
|
|
636
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
637
|
+
};
|
|
638
|
+
});
|
|
639
|
+
return { submission: submission, earlier };
|
|
640
|
+
}
|
|
641
|
+
function forgotten(conversation, submission) {
|
|
642
|
+
return withDraft(conversation, (current) => {
|
|
643
|
+
const held = current?.submitted ?? [];
|
|
644
|
+
if (current === undefined || !held.includes(submission))
|
|
645
|
+
return undefined;
|
|
646
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
async function dispatched(conversation, submission) {
|
|
650
|
+
const dispatch = {};
|
|
651
|
+
sending.set(submission.operation, dispatch);
|
|
652
|
+
try {
|
|
653
|
+
const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
|
|
654
|
+
const result = await invoke('conversation.send', conversation, {
|
|
655
|
+
text: submission.text,
|
|
656
|
+
files,
|
|
657
|
+
operation_id: submission.operation,
|
|
658
|
+
});
|
|
659
|
+
if (result.kind === 'revoked-authorization')
|
|
660
|
+
return result;
|
|
661
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
662
|
+
deps.unanswered();
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
const turn = answered(result, 'turn');
|
|
666
|
+
await withDraft(conversation, async (current, local) => {
|
|
667
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
668
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
669
|
+
return undefined;
|
|
670
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
671
|
+
if (result.kind !== 'action')
|
|
672
|
+
return returned(current, held, others);
|
|
673
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
674
|
+
return withSubmitted(current, turn === null || arrived
|
|
675
|
+
? others
|
|
676
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
677
|
+
});
|
|
678
|
+
return result;
|
|
679
|
+
}
|
|
680
|
+
finally {
|
|
681
|
+
if (sending.get(submission.operation) === dispatch)
|
|
682
|
+
sending.delete(submission.operation);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
481
685
|
function invoke(name, conversation, extra = {}) {
|
|
482
686
|
return deps.call(name, { conversation, ...extra });
|
|
483
687
|
}
|
|
@@ -488,46 +692,34 @@ export function createConversationStore(deps) {
|
|
|
488
692
|
},
|
|
489
693
|
async read(conversation, options = {}) {
|
|
490
694
|
const limit = limitOf(options);
|
|
491
|
-
return deps.durably(() =>
|
|
492
|
-
},
|
|
493
|
-
create(input) {
|
|
494
|
-
return deps.call('conversation.create', input);
|
|
695
|
+
return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
|
|
495
696
|
},
|
|
496
|
-
async
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
sending.set(conversation, pending);
|
|
501
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
502
|
-
}
|
|
503
|
-
try {
|
|
504
|
-
const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
|
|
505
|
-
...(await declaredFile(file)),
|
|
506
|
-
blob: file.blob,
|
|
507
|
-
})));
|
|
508
|
-
const result = await invoke('conversation.send', conversation, {
|
|
509
|
-
text: draft?.text ?? '',
|
|
510
|
-
files,
|
|
511
|
-
});
|
|
512
|
-
const turn = turnOf(result);
|
|
513
|
-
if (turn === null || draft === undefined)
|
|
514
|
-
return result;
|
|
515
|
-
await withDraft(conversation, (current) => {
|
|
516
|
-
if (current !== draft)
|
|
517
|
-
return undefined;
|
|
518
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
519
|
-
return sealed ? null : { ...draft, turn };
|
|
520
|
-
});
|
|
697
|
+
async create(input) {
|
|
698
|
+
const result = await deps.call('conversation.create', input);
|
|
699
|
+
const conversation = answered(result, 'conversation');
|
|
700
|
+
if (conversation === null)
|
|
521
701
|
return result;
|
|
702
|
+
if (!listing.has(conversation))
|
|
703
|
+
creations.add(conversation);
|
|
704
|
+
const creation = creationOf(input);
|
|
705
|
+
if (creation !== null)
|
|
706
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
707
|
+
return result;
|
|
708
|
+
},
|
|
709
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
710
|
+
if (deps.online()) {
|
|
711
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
712
|
+
if (!earlier || !deps.expired(operation))
|
|
713
|
+
return dispatched(conversation, submission);
|
|
714
|
+
await forgotten(conversation, submission);
|
|
715
|
+
return EXPIRED;
|
|
522
716
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
717
|
+
if (draft !== undefined) {
|
|
718
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
719
|
+
? undefined
|
|
720
|
+
: returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
530
721
|
}
|
|
722
|
+
return OFFLINE;
|
|
531
723
|
},
|
|
532
724
|
answer(conversation, answer) {
|
|
533
725
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -542,7 +734,12 @@ export function createConversationStore(deps) {
|
|
|
542
734
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
543
735
|
if (result.kind !== 'action')
|
|
544
736
|
return result;
|
|
545
|
-
await withDraft(conversation, (current) =>
|
|
737
|
+
await withDraft(conversation, (current) => {
|
|
738
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
739
|
+
if (current === undefined || held === undefined)
|
|
740
|
+
return undefined;
|
|
741
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
742
|
+
});
|
|
546
743
|
return result;
|
|
547
744
|
},
|
|
548
745
|
delete(conversation) {
|
|
@@ -550,33 +747,43 @@ export function createConversationStore(deps) {
|
|
|
550
747
|
},
|
|
551
748
|
};
|
|
552
749
|
const chatDraft = {
|
|
553
|
-
write(conversation, draft) {
|
|
554
|
-
return withDraft(conversation, () => ({
|
|
750
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
751
|
+
return withDraft(conversation, (current) => ({
|
|
555
752
|
conversation,
|
|
556
753
|
text: draft.text,
|
|
557
754
|
files: [...draft.files],
|
|
558
|
-
|
|
755
|
+
revision,
|
|
756
|
+
...(current === undefined ? {} : pending(current)),
|
|
559
757
|
}));
|
|
560
758
|
},
|
|
561
759
|
discard(conversation) {
|
|
562
|
-
return withDraft(conversation, (current) =>
|
|
760
|
+
return withDraft(conversation, (current) => current === undefined
|
|
761
|
+
? undefined
|
|
762
|
+
: {
|
|
763
|
+
conversation,
|
|
764
|
+
text: '',
|
|
765
|
+
files: [],
|
|
766
|
+
revision: chatDraftRevision(),
|
|
767
|
+
...pending(current),
|
|
768
|
+
});
|
|
563
769
|
},
|
|
564
770
|
};
|
|
565
771
|
return {
|
|
566
772
|
surface,
|
|
567
773
|
chatDraft,
|
|
568
|
-
hydrate(held,
|
|
569
|
-
|
|
774
|
+
hydrate(held, kept) {
|
|
775
|
+
stored.clear();
|
|
570
776
|
reads.clear();
|
|
571
|
-
|
|
572
|
-
view.messages = null;
|
|
777
|
+
partials.clear();
|
|
573
778
|
for (const entry of held) {
|
|
574
|
-
|
|
779
|
+
stored.add(entry.conversation);
|
|
575
780
|
if (entry.read !== undefined)
|
|
576
781
|
reads.set(entry.conversation, entry.read);
|
|
782
|
+
if (entry.partial)
|
|
783
|
+
partials.add(entry.conversation);
|
|
577
784
|
}
|
|
578
785
|
drafts.clear();
|
|
579
|
-
for (const record of
|
|
786
|
+
for (const record of kept)
|
|
580
787
|
drafts.set(record.conversation, record);
|
|
581
788
|
},
|
|
582
789
|
async reconcile() {
|
|
@@ -584,8 +791,10 @@ export function createConversationStore(deps) {
|
|
|
584
791
|
return;
|
|
585
792
|
const named = new Set(listing.keys());
|
|
586
793
|
const leaving = [
|
|
587
|
-
...new Set([...replicated, ...
|
|
588
|
-
].filter((conversation) => !named.has(conversation)
|
|
794
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
795
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
796
|
+
!creations.has(conversation) &&
|
|
797
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
589
798
|
for (const conversation of named) {
|
|
590
799
|
if (!replicated.has(conversation))
|
|
591
800
|
behind.add(conversation);
|
|
@@ -596,22 +805,64 @@ export function createConversationStore(deps) {
|
|
|
596
805
|
await remove(leaving);
|
|
597
806
|
},
|
|
598
807
|
async catchUp(progress) {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
808
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
809
|
+
const read = async (conversations) => {
|
|
810
|
+
if (conversations.length === 0 || deps.closed())
|
|
811
|
+
return;
|
|
812
|
+
const failed = await caughtUp(conversations);
|
|
602
813
|
if (failed?.kind === 'transport-uncertainty')
|
|
603
814
|
progress.reachable = false;
|
|
604
|
-
}
|
|
815
|
+
};
|
|
816
|
+
const asking = [...behind].filter(owed).sort();
|
|
817
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
818
|
+
await Promise.all([
|
|
819
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
820
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
821
|
+
]);
|
|
605
822
|
if (behind.size > 0)
|
|
606
823
|
progress.complete = false;
|
|
607
824
|
},
|
|
825
|
+
async resend(progress) {
|
|
826
|
+
for (const [conversation, held] of [...drafts]) {
|
|
827
|
+
for (const submission of held.submitted ?? []) {
|
|
828
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
829
|
+
continue;
|
|
830
|
+
if (deps.closed())
|
|
831
|
+
return;
|
|
832
|
+
if (deps.expired(submission.operation)) {
|
|
833
|
+
await forgotten(conversation, submission);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const result = await dispatched(conversation, submission);
|
|
837
|
+
if (result.kind === 'revoked-authorization')
|
|
838
|
+
return;
|
|
839
|
+
if (result.kind === 'transport-uncertainty') {
|
|
840
|
+
progress.reachable = false;
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
844
|
+
progress.complete = false;
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
},
|
|
608
850
|
current() {
|
|
609
851
|
return behind.size === 0;
|
|
610
852
|
},
|
|
853
|
+
known() {
|
|
854
|
+
return listKnown;
|
|
855
|
+
},
|
|
611
856
|
reconnected() {
|
|
612
857
|
for (const conversation of replicated)
|
|
613
858
|
gap(conversation);
|
|
614
859
|
},
|
|
860
|
+
reopened() {
|
|
861
|
+
sending.clear();
|
|
862
|
+
},
|
|
863
|
+
lost() {
|
|
864
|
+
catching.clear();
|
|
865
|
+
},
|
|
615
866
|
watch(conversation, options, deliver) {
|
|
616
867
|
const limit = limitOf(options);
|
|
617
868
|
const key = JSON.stringify([conversation, limit ?? null]);
|
|
@@ -619,18 +870,20 @@ export function createConversationStore(deps) {
|
|
|
619
870
|
conversation,
|
|
620
871
|
limit,
|
|
621
872
|
watchers: new Set(),
|
|
622
|
-
|
|
873
|
+
window: null,
|
|
623
874
|
};
|
|
624
875
|
views.set(key, view);
|
|
625
876
|
const watcher = { notify: deliver, ready: false };
|
|
626
877
|
view.watchers.add(watcher);
|
|
627
878
|
void deps
|
|
628
|
-
.durably(() =>
|
|
879
|
+
.durably(async (local) => {
|
|
880
|
+
view.window ??= await local.messageWindow(conversation, limit);
|
|
881
|
+
})
|
|
629
882
|
.then(() => {
|
|
630
883
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
631
884
|
return;
|
|
632
885
|
watcher.ready = true;
|
|
633
|
-
watcher.notify(viewOf(conversation,
|
|
886
|
+
watcher.notify(viewOf(conversation, view.window ?? []));
|
|
634
887
|
})
|
|
635
888
|
.catch(() => { });
|
|
636
889
|
return () => {
|
|
@@ -650,6 +903,16 @@ export function createConversationStore(deps) {
|
|
|
650
903
|
listWatchers.delete(deliver);
|
|
651
904
|
};
|
|
652
905
|
},
|
|
906
|
+
watchAgents(deliver) {
|
|
907
|
+
agentWatchers.add(deliver);
|
|
908
|
+
void agentsReady().then(() => {
|
|
909
|
+
if (!deps.closed() && agentWatchers.has(deliver))
|
|
910
|
+
deliver(agents());
|
|
911
|
+
});
|
|
912
|
+
return () => {
|
|
913
|
+
agentWatchers.delete(deliver);
|
|
914
|
+
};
|
|
915
|
+
},
|
|
653
916
|
changed(set) {
|
|
654
917
|
for (const conversation of set.conversations)
|
|
655
918
|
emit(conversation);
|
|
@@ -667,12 +930,15 @@ export function createConversationStore(deps) {
|
|
|
667
930
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
668
931
|
if (event.kind === 'list') {
|
|
669
932
|
listing.clear();
|
|
670
|
-
for (const entry of event.entries)
|
|
933
|
+
for (const entry of event.entries) {
|
|
671
934
|
listing.set(entry.conversation, { ...entry });
|
|
935
|
+
creations.delete(entry.conversation);
|
|
936
|
+
}
|
|
672
937
|
listKnown = true;
|
|
673
938
|
}
|
|
674
939
|
else if (event.kind === 'listed') {
|
|
675
940
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
941
|
+
creations.delete(event.entry.conversation);
|
|
676
942
|
}
|
|
677
943
|
else {
|
|
678
944
|
listing.delete(event.conversation);
|
|
@@ -686,17 +952,17 @@ export function createConversationStore(deps) {
|
|
|
686
952
|
heard(event);
|
|
687
953
|
return false;
|
|
688
954
|
},
|
|
689
|
-
|
|
690
|
-
return [...
|
|
955
|
+
searchable() {
|
|
956
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
691
957
|
},
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
958
|
+
acquired(conversation) {
|
|
959
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
695
960
|
},
|
|
696
961
|
close() {
|
|
697
962
|
views.clear();
|
|
698
963
|
lives.clear();
|
|
699
964
|
listWatchers.clear();
|
|
965
|
+
agentWatchers.clear();
|
|
700
966
|
},
|
|
701
967
|
};
|
|
702
968
|
}
|