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