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