@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-cb0eb0d87ff49
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 +164 -99
- package/README.md +13 -3
- package/dist/adapter.d.ts +72 -16
- 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 +17 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +109 -37
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +48 -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 +60 -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 +26 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +62 -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 +21 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +151 -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 +620 -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 +33 -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 +20 -11
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +66 -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 +29 -14
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1262 -377
- package/dist/conversation.d.ts +23 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +304 -136
- 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 +4 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +17 -12
- 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 +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/memory.d.ts +5 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +70 -21
- package/dist/model.d.ts +11 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +11 -0
- 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 +15 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +94 -77
- package/dist/query.d.ts +32 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +5 -31
- package/dist/reach.d.ts +2 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +45 -21
- package/dist/react.d.ts +26 -17
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +8 -6
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +291 -85
- 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 +10 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +43 -38
- package/dist/replica.d.ts +9 -6
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +116 -68
- package/dist/room-draft.d.ts +26 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +132 -23
- package/dist/rows.d.ts +8 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +12 -14
- package/dist/save.d.ts +31 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +376 -45
- package/dist/search.d.ts +31 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +109 -44
- 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 +2 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +66 -17
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +506 -196
- 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 +7 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -2
- 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,7 @@ export function unheldView(conversation) {
|
|
|
19
22
|
chatDraft: null,
|
|
20
23
|
};
|
|
21
24
|
}
|
|
25
|
+
export const SESSION_STARTED = 'Session started';
|
|
22
26
|
function startedTurn(turn, operation) {
|
|
23
27
|
if (operation.op === 'begin')
|
|
24
28
|
return { turn, started: operation.at, parts: [], context: null };
|
|
@@ -75,11 +79,15 @@ function ordered(left, right) {
|
|
|
75
79
|
return left.identity < right.identity ? -1 : 1;
|
|
76
80
|
return 0;
|
|
77
81
|
}
|
|
78
|
-
function
|
|
82
|
+
function windowOf(held, arriving, limit) {
|
|
79
83
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
80
84
|
for (const message of arriving)
|
|
81
85
|
byIdentity.set(message.identity, message);
|
|
82
|
-
|
|
86
|
+
const next = [...byIdentity.values()].sort(ordered);
|
|
87
|
+
return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
|
|
88
|
+
}
|
|
89
|
+
function newestOf(arriving, from) {
|
|
90
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
83
91
|
}
|
|
84
92
|
function acceptedMessage(value) {
|
|
85
93
|
if (typeof value !== 'object' || value === null)
|
|
@@ -152,13 +160,33 @@ function limitOf(options) {
|
|
|
152
160
|
if (limit === undefined)
|
|
153
161
|
return undefined;
|
|
154
162
|
if (!Number.isInteger(limit) || limit < 0) {
|
|
155
|
-
throw
|
|
163
|
+
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
156
164
|
}
|
|
157
165
|
return limit;
|
|
158
166
|
}
|
|
167
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
168
|
+
function unwritten(draft) {
|
|
169
|
+
return draft.text === '' && draft.files.length === 0;
|
|
170
|
+
}
|
|
171
|
+
function pending(draft) {
|
|
172
|
+
return draft.submitted === undefined ? {} : { submitted: draft.submitted };
|
|
173
|
+
}
|
|
174
|
+
function withSubmitted(draft, submitted) {
|
|
175
|
+
const { submitted: _held, ...unsent } = draft;
|
|
176
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
177
|
+
}
|
|
178
|
+
function returned(draft, submission, others) {
|
|
179
|
+
return withSubmitted({
|
|
180
|
+
conversation: draft.conversation,
|
|
181
|
+
text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
|
|
182
|
+
files: [...submission.files, ...draft.files],
|
|
183
|
+
revision: chatDraftRevision(),
|
|
184
|
+
}, others);
|
|
185
|
+
}
|
|
159
186
|
export function createConversationStore(deps) {
|
|
160
|
-
const
|
|
187
|
+
const stored = new Set();
|
|
161
188
|
const reads = new Map();
|
|
189
|
+
const partials = new Set();
|
|
162
190
|
const drafts = new Map();
|
|
163
191
|
const sending = new Map();
|
|
164
192
|
const behind = new Set();
|
|
@@ -168,56 +196,60 @@ export function createConversationStore(deps) {
|
|
|
168
196
|
const listing = new Map();
|
|
169
197
|
const listWatchers = new Set();
|
|
170
198
|
let listKnown = false;
|
|
199
|
+
const agentWatchers = new Set();
|
|
171
200
|
let agentNames = new Map();
|
|
201
|
+
let retiredAgents = new Set();
|
|
172
202
|
let agentsRead = null;
|
|
173
203
|
let replicated = new Set();
|
|
174
204
|
let handedReplicated;
|
|
175
205
|
function liveOf(conversation) {
|
|
176
206
|
return lives.get(conversation) ?? QUIET;
|
|
177
207
|
}
|
|
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)) {
|
|
208
|
+
function viewOf(conversation, window) {
|
|
189
209
|
const draft = drafts.get(conversation);
|
|
190
210
|
const facts = liveOf(conversation);
|
|
191
211
|
return {
|
|
192
212
|
conversation,
|
|
193
|
-
messages:
|
|
213
|
+
messages: window.map(({ identity, turn, text, message }) => ({
|
|
214
|
+
identity,
|
|
215
|
+
turn,
|
|
216
|
+
text,
|
|
217
|
+
message,
|
|
218
|
+
})),
|
|
194
219
|
turn: structuredClone(facts.turn),
|
|
195
220
|
questions: structuredClone(facts.questions),
|
|
196
221
|
process: facts.process === null ? null : { ...facts.process },
|
|
197
222
|
commands: structuredClone(facts.commands),
|
|
198
223
|
options: structuredClone(facts.options),
|
|
199
|
-
chatDraft: draft === undefined || draft
|
|
224
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
200
225
|
? null
|
|
201
|
-
: { text: draft.text, files: [...draft.files] },
|
|
226
|
+
: { text: draft.text, files: [...draft.files], revision: draft.revision },
|
|
202
227
|
};
|
|
203
228
|
}
|
|
204
|
-
function emit(conversation
|
|
229
|
+
function emit(conversation) {
|
|
205
230
|
if (deps.closed())
|
|
206
231
|
return;
|
|
207
232
|
for (const view of views.values()) {
|
|
208
|
-
if (view.conversation !== conversation)
|
|
233
|
+
if (view.conversation !== conversation || view.window === null)
|
|
209
234
|
continue;
|
|
210
|
-
if (messagesChanged)
|
|
211
|
-
view.messages = null;
|
|
212
235
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
213
236
|
if (ready.length === 0)
|
|
214
237
|
continue;
|
|
215
|
-
|
|
216
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
238
|
+
const value = viewOf(conversation, view.window);
|
|
217
239
|
for (const watcher of ready)
|
|
218
240
|
watcher.notify(value);
|
|
219
241
|
}
|
|
220
242
|
}
|
|
243
|
+
function slide(conversation, arriving) {
|
|
244
|
+
for (const view of views.values()) {
|
|
245
|
+
if (view.conversation !== conversation || view.window === null)
|
|
246
|
+
continue;
|
|
247
|
+
view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function changesOf(conversation, arriving) {
|
|
251
|
+
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
252
|
+
}
|
|
221
253
|
function stateOf(conversation) {
|
|
222
254
|
const facts = liveOf(conversation);
|
|
223
255
|
if (facts.questions.length > 0)
|
|
@@ -243,6 +275,21 @@ export function createConversationStore(deps) {
|
|
|
243
275
|
for (const watcher of listWatchers)
|
|
244
276
|
watcher(value);
|
|
245
277
|
}
|
|
278
|
+
function agents() {
|
|
279
|
+
return [...agentNames]
|
|
280
|
+
.filter(([identity]) => !retiredAgents.has(identity))
|
|
281
|
+
.map(([identity, name]) => ({ identity, name }))
|
|
282
|
+
.sort((left, right) => left.name === right.name
|
|
283
|
+
? left.identity.localeCompare(right.identity)
|
|
284
|
+
: left.name.localeCompare(right.name));
|
|
285
|
+
}
|
|
286
|
+
function emitAgents() {
|
|
287
|
+
if (deps.closed() || agentWatchers.size === 0)
|
|
288
|
+
return;
|
|
289
|
+
const value = agents();
|
|
290
|
+
for (const watcher of agentWatchers)
|
|
291
|
+
watcher(value);
|
|
292
|
+
}
|
|
246
293
|
function setLive(conversation, next) {
|
|
247
294
|
const before = stateOf(conversation);
|
|
248
295
|
const began = lives.get(conversation)?.turn?.started;
|
|
@@ -259,20 +306,32 @@ export function createConversationStore(deps) {
|
|
|
259
306
|
const declaration = deps.agents();
|
|
260
307
|
if (declaration === undefined)
|
|
261
308
|
return;
|
|
262
|
-
const
|
|
309
|
+
const held = await deps.documents(declaration);
|
|
310
|
+
if ('pending' in held)
|
|
311
|
+
return;
|
|
312
|
+
const { documents } = held;
|
|
263
313
|
const names = new Map();
|
|
314
|
+
const retired = new Set();
|
|
264
315
|
for (const document of documents) {
|
|
265
316
|
if (document.type !== declaration.type)
|
|
266
317
|
continue;
|
|
267
318
|
const identity = document.fields?.[declaration.identity];
|
|
268
319
|
const name = document.fields?.[declaration.name];
|
|
269
|
-
if (typeof identity
|
|
270
|
-
|
|
320
|
+
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
321
|
+
continue;
|
|
322
|
+
names.set(identity, name);
|
|
323
|
+
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
324
|
+
if (ended !== undefined && ended !== null)
|
|
325
|
+
retired.add(identity);
|
|
271
326
|
}
|
|
272
|
-
|
|
327
|
+
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
328
|
+
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
273
329
|
return;
|
|
274
330
|
agentNames = names;
|
|
275
|
-
|
|
331
|
+
retiredAgents = retired;
|
|
332
|
+
if (named)
|
|
333
|
+
emitList();
|
|
334
|
+
emitAgents();
|
|
276
335
|
}
|
|
277
336
|
function agentsReady() {
|
|
278
337
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -282,7 +341,7 @@ export function createConversationStore(deps) {
|
|
|
282
341
|
}
|
|
283
342
|
function live(conversation, change) {
|
|
284
343
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
285
|
-
emit(conversation
|
|
344
|
+
emit(conversation);
|
|
286
345
|
}
|
|
287
346
|
function relayed(conversation, turn, operation) {
|
|
288
347
|
const held = liveOf(conversation).turn;
|
|
@@ -310,21 +369,25 @@ export function createConversationStore(deps) {
|
|
|
310
369
|
}
|
|
311
370
|
async function remove(leaving) {
|
|
312
371
|
await deps.durably(async (local) => {
|
|
313
|
-
await local.dropConversations(leaving);
|
|
314
372
|
const removed = [];
|
|
315
|
-
const changed = [];
|
|
316
373
|
for (const conversation of leaving) {
|
|
317
|
-
for (const
|
|
318
|
-
removed.push({ conversation, identity:
|
|
374
|
+
for (const identity of await local.messageIdentities(conversation)) {
|
|
375
|
+
removed.push({ conversation, identity, text: null });
|
|
319
376
|
}
|
|
320
|
-
|
|
377
|
+
}
|
|
378
|
+
await local.dropConversations(leaving);
|
|
379
|
+
const changed = [];
|
|
380
|
+
for (const conversation of leaving) {
|
|
381
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
321
382
|
changed.push(conversation);
|
|
322
383
|
}
|
|
323
|
-
|
|
384
|
+
stored.delete(conversation);
|
|
324
385
|
reads.delete(conversation);
|
|
386
|
+
partials.delete(conversation);
|
|
325
387
|
drafts.delete(conversation);
|
|
326
388
|
behind.delete(conversation);
|
|
327
389
|
setLive(conversation, null);
|
|
390
|
+
slide(conversation, null);
|
|
328
391
|
}
|
|
329
392
|
if (changed.length > 0)
|
|
330
393
|
deps.publish(conversationChangeSet(removed, changed));
|
|
@@ -342,30 +405,25 @@ export function createConversationStore(deps) {
|
|
|
342
405
|
const advances = read !== undefined &&
|
|
343
406
|
!behind.has(conversation) &&
|
|
344
407
|
(read === null || message.position > read);
|
|
345
|
-
|
|
408
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
346
409
|
await local.keepMessages({
|
|
347
410
|
conversation,
|
|
348
411
|
messages: [message],
|
|
349
|
-
...(advances ? { read: message.position } : {}),
|
|
350
|
-
sealed,
|
|
412
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
351
413
|
});
|
|
352
|
-
|
|
414
|
+
stored.add(conversation);
|
|
353
415
|
if (advances)
|
|
354
416
|
reads.set(conversation, message.position);
|
|
355
|
-
if (sealed)
|
|
356
|
-
drafts.delete(conversation);
|
|
357
417
|
sealTurns(conversation, new Set([message.turn]));
|
|
358
|
-
|
|
418
|
+
slide(conversation, [message]);
|
|
419
|
+
deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
|
|
359
420
|
});
|
|
360
421
|
}
|
|
361
422
|
async function readHistories(conversations) {
|
|
362
423
|
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
424
|
let asking = conversations.map((conversation) => ({
|
|
367
425
|
conversation,
|
|
368
|
-
after:
|
|
426
|
+
after: reads.get(conversation) ?? undefined,
|
|
369
427
|
}));
|
|
370
428
|
while (asking.length > 0) {
|
|
371
429
|
const result = await deps.call('conversation.history', {
|
|
@@ -377,46 +435,41 @@ export function createConversationStore(deps) {
|
|
|
377
435
|
const pages = historyPages(result);
|
|
378
436
|
if (pages === null)
|
|
379
437
|
return result;
|
|
380
|
-
const asked = new
|
|
438
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
381
439
|
const following = [];
|
|
382
440
|
for (const page of pages) {
|
|
383
441
|
if (!asked.has(page.conversation))
|
|
384
442
|
continue;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
...page.messages,
|
|
388
|
-
]);
|
|
389
|
-
if (page.next === undefined)
|
|
390
|
-
complete.add(page.conversation);
|
|
391
|
-
else
|
|
443
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
444
|
+
if (page.next !== undefined) {
|
|
392
445
|
following.push({ conversation: page.conversation, after: page.next });
|
|
446
|
+
}
|
|
393
447
|
}
|
|
394
448
|
asking = following;
|
|
395
449
|
}
|
|
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
450
|
return null;
|
|
402
451
|
}
|
|
403
|
-
function
|
|
452
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
404
453
|
return deps.durably(async (local) => {
|
|
405
454
|
if (!replicated.has(conversation))
|
|
406
455
|
return;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
await local
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
456
|
+
const whole = next === undefined;
|
|
457
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
458
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
459
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
460
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
461
|
+
stored.add(conversation);
|
|
462
|
+
reads.set(conversation, cursor);
|
|
463
|
+
if (partial)
|
|
464
|
+
partials.add(conversation);
|
|
465
|
+
else
|
|
466
|
+
partials.delete(conversation);
|
|
415
467
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
416
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
468
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
417
469
|
behind.delete(conversation);
|
|
418
|
-
|
|
419
|
-
|
|
470
|
+
slide(conversation, arriving);
|
|
471
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
472
|
+
deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
|
|
420
473
|
}
|
|
421
474
|
});
|
|
422
475
|
}
|
|
@@ -429,7 +482,7 @@ export function createConversationStore(deps) {
|
|
|
429
482
|
if (held === undefined)
|
|
430
483
|
return;
|
|
431
484
|
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
432
|
-
emit(conversation
|
|
485
|
+
emit(conversation);
|
|
433
486
|
return;
|
|
434
487
|
}
|
|
435
488
|
if (event.kind === 'turn') {
|
|
@@ -462,22 +515,103 @@ export function createConversationStore(deps) {
|
|
|
462
515
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
463
516
|
emit(conversation);
|
|
464
517
|
}
|
|
518
|
+
async function kept(local, conversation, following) {
|
|
519
|
+
if (following === null || (unwritten(following) && following.submitted === undefined)) {
|
|
520
|
+
await local.dropChatDraft(conversation);
|
|
521
|
+
drafts.delete(conversation);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
await local.keepChatDraft(following);
|
|
525
|
+
drafts.set(conversation, following);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function sealed(local, conversation, turns) {
|
|
529
|
+
const held = drafts.get(conversation);
|
|
530
|
+
const submitted = held?.submitted ?? [];
|
|
531
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
532
|
+
if (held === undefined || left.length === submitted.length)
|
|
533
|
+
return false;
|
|
534
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
465
537
|
function withDraft(conversation, next) {
|
|
466
538
|
return deps.durably(async (local) => {
|
|
467
|
-
const following = next(drafts.get(conversation));
|
|
539
|
+
const following = await next(drafts.get(conversation), local);
|
|
468
540
|
if (following === undefined)
|
|
469
541
|
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
|
-
}
|
|
542
|
+
await kept(local, conversation, following);
|
|
478
543
|
deps.publish(conversationChangeSet([], [conversation]));
|
|
479
544
|
});
|
|
480
545
|
}
|
|
546
|
+
async function submitted(conversation, operation, draft) {
|
|
547
|
+
let submission;
|
|
548
|
+
let earlier = false;
|
|
549
|
+
await withDraft(conversation, (current) => {
|
|
550
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
551
|
+
earlier = submission !== undefined;
|
|
552
|
+
if (submission !== undefined)
|
|
553
|
+
return undefined;
|
|
554
|
+
const said = draft ?? current;
|
|
555
|
+
submission = {
|
|
556
|
+
operation,
|
|
557
|
+
text: said?.text ?? '',
|
|
558
|
+
files: [...(said?.files ?? [])],
|
|
559
|
+
turn: null,
|
|
560
|
+
};
|
|
561
|
+
return {
|
|
562
|
+
conversation,
|
|
563
|
+
...(draft === undefined || current === undefined
|
|
564
|
+
? { text: '', files: [], revision: chatDraftRevision() }
|
|
565
|
+
: { text: current.text, files: current.files, revision: current.revision }),
|
|
566
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
return { submission: submission, earlier };
|
|
570
|
+
}
|
|
571
|
+
function forgotten(conversation, submission) {
|
|
572
|
+
return withDraft(conversation, (current) => {
|
|
573
|
+
const held = current?.submitted ?? [];
|
|
574
|
+
if (current === undefined || !held.includes(submission))
|
|
575
|
+
return undefined;
|
|
576
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async function dispatched(conversation, submission) {
|
|
580
|
+
const dispatch = {};
|
|
581
|
+
sending.set(submission.operation, dispatch);
|
|
582
|
+
try {
|
|
583
|
+
const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
|
|
584
|
+
const result = await invoke('conversation.send', conversation, {
|
|
585
|
+
text: submission.text,
|
|
586
|
+
files,
|
|
587
|
+
operation_id: submission.operation,
|
|
588
|
+
});
|
|
589
|
+
if (result.kind === 'revoked-authorization')
|
|
590
|
+
return result;
|
|
591
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
592
|
+
deps.unanswered();
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
const turn = turnOf(result);
|
|
596
|
+
await withDraft(conversation, async (current, local) => {
|
|
597
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
598
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
599
|
+
return undefined;
|
|
600
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
601
|
+
if (result.kind !== 'action')
|
|
602
|
+
return returned(current, held, others);
|
|
603
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
604
|
+
return withSubmitted(current, turn === null || arrived
|
|
605
|
+
? others
|
|
606
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
607
|
+
});
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
finally {
|
|
611
|
+
if (sending.get(submission.operation) === dispatch)
|
|
612
|
+
sending.delete(submission.operation);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
481
615
|
function invoke(name, conversation, extra = {}) {
|
|
482
616
|
return deps.call(name, { conversation, ...extra });
|
|
483
617
|
}
|
|
@@ -488,46 +622,25 @@ export function createConversationStore(deps) {
|
|
|
488
622
|
},
|
|
489
623
|
async read(conversation, options = {}) {
|
|
490
624
|
const limit = limitOf(options);
|
|
491
|
-
return deps.durably(() =>
|
|
625
|
+
return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
|
|
492
626
|
},
|
|
493
627
|
create(input) {
|
|
494
628
|
return deps.call('conversation.create', input);
|
|
495
629
|
},
|
|
496
|
-
async send(conversation) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
});
|
|
521
|
-
return result;
|
|
630
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
631
|
+
if (deps.online()) {
|
|
632
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
633
|
+
if (!earlier || !deps.expired(operation))
|
|
634
|
+
return dispatched(conversation, submission);
|
|
635
|
+
await forgotten(conversation, submission);
|
|
636
|
+
return EXPIRED;
|
|
522
637
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
638
|
+
if (draft !== undefined) {
|
|
639
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
640
|
+
? undefined
|
|
641
|
+
: returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
530
642
|
}
|
|
643
|
+
return OFFLINE;
|
|
531
644
|
},
|
|
532
645
|
answer(conversation, answer) {
|
|
533
646
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -542,7 +655,12 @@ export function createConversationStore(deps) {
|
|
|
542
655
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
543
656
|
if (result.kind !== 'action')
|
|
544
657
|
return result;
|
|
545
|
-
await withDraft(conversation, (current) =>
|
|
658
|
+
await withDraft(conversation, (current) => {
|
|
659
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
660
|
+
if (current === undefined || held === undefined)
|
|
661
|
+
return undefined;
|
|
662
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
663
|
+
});
|
|
546
664
|
return result;
|
|
547
665
|
},
|
|
548
666
|
delete(conversation) {
|
|
@@ -550,33 +668,43 @@ export function createConversationStore(deps) {
|
|
|
550
668
|
},
|
|
551
669
|
};
|
|
552
670
|
const chatDraft = {
|
|
553
|
-
write(conversation, draft) {
|
|
554
|
-
return withDraft(conversation, () => ({
|
|
671
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
672
|
+
return withDraft(conversation, (current) => ({
|
|
555
673
|
conversation,
|
|
556
674
|
text: draft.text,
|
|
557
675
|
files: [...draft.files],
|
|
558
|
-
|
|
676
|
+
revision,
|
|
677
|
+
...(current?.submitted === undefined ? {} : { submitted: current.submitted }),
|
|
559
678
|
}));
|
|
560
679
|
},
|
|
561
680
|
discard(conversation) {
|
|
562
|
-
return withDraft(conversation, (current) =>
|
|
681
|
+
return withDraft(conversation, (current) => current === undefined
|
|
682
|
+
? undefined
|
|
683
|
+
: {
|
|
684
|
+
conversation,
|
|
685
|
+
text: '',
|
|
686
|
+
files: [],
|
|
687
|
+
revision: chatDraftRevision(),
|
|
688
|
+
...pending(current),
|
|
689
|
+
});
|
|
563
690
|
},
|
|
564
691
|
};
|
|
565
692
|
return {
|
|
566
693
|
surface,
|
|
567
694
|
chatDraft,
|
|
568
|
-
hydrate(held,
|
|
569
|
-
|
|
695
|
+
hydrate(held, kept) {
|
|
696
|
+
stored.clear();
|
|
570
697
|
reads.clear();
|
|
571
|
-
|
|
572
|
-
view.messages = null;
|
|
698
|
+
partials.clear();
|
|
573
699
|
for (const entry of held) {
|
|
574
|
-
|
|
700
|
+
stored.add(entry.conversation);
|
|
575
701
|
if (entry.read !== undefined)
|
|
576
702
|
reads.set(entry.conversation, entry.read);
|
|
703
|
+
if (entry.partial)
|
|
704
|
+
partials.add(entry.conversation);
|
|
577
705
|
}
|
|
578
706
|
drafts.clear();
|
|
579
|
-
for (const record of
|
|
707
|
+
for (const record of kept)
|
|
580
708
|
drafts.set(record.conversation, record);
|
|
581
709
|
},
|
|
582
710
|
async reconcile() {
|
|
@@ -584,7 +712,7 @@ export function createConversationStore(deps) {
|
|
|
584
712
|
return;
|
|
585
713
|
const named = new Set(listing.keys());
|
|
586
714
|
const leaving = [
|
|
587
|
-
...new Set([...replicated, ...
|
|
715
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
588
716
|
].filter((conversation) => !named.has(conversation));
|
|
589
717
|
for (const conversation of named) {
|
|
590
718
|
if (!replicated.has(conversation))
|
|
@@ -605,6 +733,31 @@ export function createConversationStore(deps) {
|
|
|
605
733
|
if (behind.size > 0)
|
|
606
734
|
progress.complete = false;
|
|
607
735
|
},
|
|
736
|
+
async resend(progress) {
|
|
737
|
+
for (const [conversation, held] of [...drafts]) {
|
|
738
|
+
for (const submission of held.submitted ?? []) {
|
|
739
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
740
|
+
continue;
|
|
741
|
+
if (deps.closed())
|
|
742
|
+
return;
|
|
743
|
+
if (deps.expired(submission.operation)) {
|
|
744
|
+
await forgotten(conversation, submission);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
const result = await dispatched(conversation, submission);
|
|
748
|
+
if (result.kind === 'revoked-authorization')
|
|
749
|
+
return;
|
|
750
|
+
if (result.kind === 'transport-uncertainty') {
|
|
751
|
+
progress.reachable = false;
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
755
|
+
progress.complete = false;
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
},
|
|
608
761
|
current() {
|
|
609
762
|
return behind.size === 0;
|
|
610
763
|
},
|
|
@@ -612,6 +765,9 @@ export function createConversationStore(deps) {
|
|
|
612
765
|
for (const conversation of replicated)
|
|
613
766
|
gap(conversation);
|
|
614
767
|
},
|
|
768
|
+
reopened() {
|
|
769
|
+
sending.clear();
|
|
770
|
+
},
|
|
615
771
|
watch(conversation, options, deliver) {
|
|
616
772
|
const limit = limitOf(options);
|
|
617
773
|
const key = JSON.stringify([conversation, limit ?? null]);
|
|
@@ -619,18 +775,20 @@ export function createConversationStore(deps) {
|
|
|
619
775
|
conversation,
|
|
620
776
|
limit,
|
|
621
777
|
watchers: new Set(),
|
|
622
|
-
|
|
778
|
+
window: null,
|
|
623
779
|
};
|
|
624
780
|
views.set(key, view);
|
|
625
781
|
const watcher = { notify: deliver, ready: false };
|
|
626
782
|
view.watchers.add(watcher);
|
|
627
783
|
void deps
|
|
628
|
-
.durably(() =>
|
|
784
|
+
.durably(async (local) => {
|
|
785
|
+
view.window ??= await local.messageWindow(conversation, limit);
|
|
786
|
+
})
|
|
629
787
|
.then(() => {
|
|
630
788
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
631
789
|
return;
|
|
632
790
|
watcher.ready = true;
|
|
633
|
-
watcher.notify(viewOf(conversation,
|
|
791
|
+
watcher.notify(viewOf(conversation, view.window ?? []));
|
|
634
792
|
})
|
|
635
793
|
.catch(() => { });
|
|
636
794
|
return () => {
|
|
@@ -650,6 +808,16 @@ export function createConversationStore(deps) {
|
|
|
650
808
|
listWatchers.delete(deliver);
|
|
651
809
|
};
|
|
652
810
|
},
|
|
811
|
+
watchAgents(deliver) {
|
|
812
|
+
agentWatchers.add(deliver);
|
|
813
|
+
void agentsReady().then(() => {
|
|
814
|
+
if (!deps.closed() && agentWatchers.has(deliver))
|
|
815
|
+
deliver(agents());
|
|
816
|
+
});
|
|
817
|
+
return () => {
|
|
818
|
+
agentWatchers.delete(deliver);
|
|
819
|
+
};
|
|
820
|
+
},
|
|
653
821
|
changed(set) {
|
|
654
822
|
for (const conversation of set.conversations)
|
|
655
823
|
emit(conversation);
|
|
@@ -686,17 +854,17 @@ export function createConversationStore(deps) {
|
|
|
686
854
|
heard(event);
|
|
687
855
|
return false;
|
|
688
856
|
},
|
|
689
|
-
|
|
690
|
-
return [...
|
|
857
|
+
searchable() {
|
|
858
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
691
859
|
},
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
860
|
+
acquired(conversation) {
|
|
861
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
695
862
|
},
|
|
696
863
|
close() {
|
|
697
864
|
views.clear();
|
|
698
865
|
lives.clear();
|
|
699
866
|
listWatchers.clear();
|
|
867
|
+
agentWatchers.clear();
|
|
700
868
|
},
|
|
701
869
|
};
|
|
702
870
|
}
|