@agentshouse/mdruntime 0.1.0-cede5a74284be → 0.1.0-cee9c8aa4cd86
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 +294 -292
- 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 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -63
- 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 +75 -13
- package/dist/chat/messages.d.ts +15 -1
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +82 -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 +9 -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 +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +718 -0
- package/dist/connection.d.ts +41 -13
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3120 -702
- package/dist/conversation.d.ts +28 -9
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +513 -178
- 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/fulltext.d.ts +27 -0
- package/dist/fulltext.d.ts.map +1 -0
- package/dist/fulltext.js +368 -0
- package/dist/host.d.ts +14 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +102 -13
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +3 -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/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +90 -0
- 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 +39 -28
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +37 -105
- 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 +108 -75
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +345 -450
- package/dist/stored.d.ts +138 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +544 -0
- package/dist/tables.d.ts +332 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +570 -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/dist/words.d.ts +19 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +150 -0
- package/package.json +14 -4
- 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,70 @@ 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
|
+
}
|
|
225
327
|
function stateOf(conversation) {
|
|
226
328
|
const facts = liveOf(conversation);
|
|
227
329
|
if (facts.questions.length > 0)
|
|
@@ -236,6 +338,7 @@ export function createConversationStore(deps) {
|
|
|
236
338
|
created: entry.created,
|
|
237
339
|
finished: entry.finished,
|
|
238
340
|
outcome: entry.outcome,
|
|
341
|
+
waiting: entry.waiting,
|
|
239
342
|
state: stateOf(entry.conversation),
|
|
240
343
|
started: lives.get(entry.conversation)?.turn?.started ?? null,
|
|
241
344
|
}));
|
|
@@ -278,20 +381,30 @@ export function createConversationStore(deps) {
|
|
|
278
381
|
const declaration = deps.agents();
|
|
279
382
|
if (declaration === undefined)
|
|
280
383
|
return;
|
|
281
|
-
const { documents } = await deps.documents(declaration.locator);
|
|
282
384
|
const names = new Map();
|
|
283
385
|
const retired = new Set();
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
386
|
+
let covered = null;
|
|
387
|
+
for (let after;;) {
|
|
388
|
+
const read = await deps.documents(declaration, after);
|
|
389
|
+
if ('pending' in read)
|
|
390
|
+
return;
|
|
391
|
+
const coverage = JSON.stringify(read.coverage);
|
|
392
|
+
if (covered !== null && coverage !== covered)
|
|
393
|
+
return readAgents();
|
|
394
|
+
covered = coverage;
|
|
395
|
+
for (const document of read.documents) {
|
|
396
|
+
const identity = document.fields?.[declaration.identity];
|
|
397
|
+
const name = document.fields?.[declaration.name];
|
|
398
|
+
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
399
|
+
continue;
|
|
400
|
+
names.set(identity, name);
|
|
401
|
+
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
402
|
+
if (ended !== undefined && ended !== null)
|
|
403
|
+
retired.add(identity);
|
|
404
|
+
}
|
|
405
|
+
if (read.next === undefined)
|
|
406
|
+
break;
|
|
407
|
+
after = read.next;
|
|
295
408
|
}
|
|
296
409
|
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
297
410
|
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
@@ -301,6 +414,12 @@ export function createConversationStore(deps) {
|
|
|
301
414
|
if (named)
|
|
302
415
|
emitList();
|
|
303
416
|
emitAgents();
|
|
417
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
418
|
+
if (ended.length > 0)
|
|
419
|
+
await remove(ended);
|
|
420
|
+
}
|
|
421
|
+
function retiredChat(conversation) {
|
|
422
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
304
423
|
}
|
|
305
424
|
function agentsReady() {
|
|
306
425
|
agentsRead ??= readAgents().catch(() => {
|
|
@@ -309,8 +428,33 @@ export function createConversationStore(deps) {
|
|
|
309
428
|
return agentsRead;
|
|
310
429
|
}
|
|
311
430
|
function live(conversation, change) {
|
|
431
|
+
const owed = unsent.get(conversation);
|
|
432
|
+
for (const fact of FACTS.filter((one) => one in change))
|
|
433
|
+
owed?.delete(fact);
|
|
312
434
|
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
313
|
-
emit(conversation
|
|
435
|
+
emit(conversation);
|
|
436
|
+
}
|
|
437
|
+
function openingEnded() {
|
|
438
|
+
const owed = [...unsent];
|
|
439
|
+
unsent.clear();
|
|
440
|
+
for (const [conversation, facts] of owed) {
|
|
441
|
+
const held = lives.get(conversation);
|
|
442
|
+
if (held === undefined || facts.size === 0)
|
|
443
|
+
continue;
|
|
444
|
+
setLive(conversation, {
|
|
445
|
+
turn: held.turn,
|
|
446
|
+
questions: facts.has('questions') ? QUIET.questions : held.questions,
|
|
447
|
+
process: facts.has('process') ? QUIET.process : held.process,
|
|
448
|
+
commands: facts.has('commands') ? QUIET.commands : held.commands,
|
|
449
|
+
options: facts.has('options') ? QUIET.options : held.options,
|
|
450
|
+
starting: facts.has('starting') ? QUIET.starting : held.starting,
|
|
451
|
+
link: facts.has('link') ? QUIET.link : held.link,
|
|
452
|
+
deliveryUnknown: facts.has('deliveryUnknown')
|
|
453
|
+
? QUIET.deliveryUnknown
|
|
454
|
+
: held.deliveryUnknown,
|
|
455
|
+
});
|
|
456
|
+
emit(conversation);
|
|
457
|
+
}
|
|
314
458
|
}
|
|
315
459
|
function relayed(conversation, turn, operation) {
|
|
316
460
|
const held = liveOf(conversation).turn;
|
|
@@ -339,23 +483,22 @@ export function createConversationStore(deps) {
|
|
|
339
483
|
async function remove(leaving) {
|
|
340
484
|
await deps.durably(async (local) => {
|
|
341
485
|
await local.dropConversations(leaving);
|
|
342
|
-
const removed = [];
|
|
343
486
|
const changed = [];
|
|
344
487
|
for (const conversation of leaving) {
|
|
345
|
-
|
|
346
|
-
removed.push({ conversation, identity: message.identity });
|
|
347
|
-
}
|
|
348
|
-
if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
488
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
349
489
|
changed.push(conversation);
|
|
350
490
|
}
|
|
351
|
-
|
|
491
|
+
stored.delete(conversation);
|
|
352
492
|
reads.delete(conversation);
|
|
493
|
+
partials.delete(conversation);
|
|
353
494
|
drafts.delete(conversation);
|
|
354
495
|
behind.delete(conversation);
|
|
496
|
+
unsent.delete(conversation);
|
|
355
497
|
setLive(conversation, null);
|
|
498
|
+
await slide(local, conversation, null);
|
|
356
499
|
}
|
|
357
500
|
if (changed.length > 0)
|
|
358
|
-
deps.publish(conversationChangeSet(
|
|
501
|
+
deps.publish(conversationChangeSet(changed));
|
|
359
502
|
});
|
|
360
503
|
}
|
|
361
504
|
function gap(conversation) {
|
|
@@ -370,30 +513,25 @@ export function createConversationStore(deps) {
|
|
|
370
513
|
const advances = read !== undefined &&
|
|
371
514
|
!behind.has(conversation) &&
|
|
372
515
|
(read === null || message.position > read);
|
|
373
|
-
|
|
516
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
374
517
|
await local.keepMessages({
|
|
375
518
|
conversation,
|
|
376
519
|
messages: [message],
|
|
377
|
-
...(advances ? { read: message.position } : {}),
|
|
378
|
-
sealed,
|
|
520
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
379
521
|
});
|
|
380
|
-
|
|
522
|
+
stored.add(conversation);
|
|
381
523
|
if (advances)
|
|
382
524
|
reads.set(conversation, message.position);
|
|
383
|
-
if (sealed)
|
|
384
|
-
drafts.delete(conversation);
|
|
385
525
|
sealTurns(conversation, new Set([message.turn]));
|
|
386
|
-
|
|
526
|
+
await slide(local, conversation, [message]);
|
|
527
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
387
528
|
});
|
|
388
529
|
}
|
|
389
|
-
async function readHistories(conversations) {
|
|
530
|
+
async function readHistories(conversations, ended) {
|
|
390
531
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
391
|
-
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
392
|
-
const arriving = new Map();
|
|
393
|
-
const complete = new Set();
|
|
394
532
|
let asking = conversations.map((conversation) => ({
|
|
395
533
|
conversation,
|
|
396
|
-
after:
|
|
534
|
+
after: reads.get(conversation) ?? undefined,
|
|
397
535
|
}));
|
|
398
536
|
while (asking.length > 0) {
|
|
399
537
|
const result = await deps.call('conversation.history', {
|
|
@@ -405,46 +543,64 @@ export function createConversationStore(deps) {
|
|
|
405
543
|
const pages = historyPages(result);
|
|
406
544
|
if (pages === null)
|
|
407
545
|
return result;
|
|
408
|
-
const asked = new
|
|
546
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
409
547
|
const following = [];
|
|
410
548
|
for (const page of pages) {
|
|
411
549
|
if (!asked.has(page.conversation))
|
|
412
550
|
continue;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
...page.messages,
|
|
416
|
-
]);
|
|
417
|
-
if (page.next === undefined)
|
|
418
|
-
complete.add(page.conversation);
|
|
419
|
-
else
|
|
551
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
552
|
+
if (page.next !== undefined) {
|
|
420
553
|
following.push({ conversation: page.conversation, after: page.next });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const { conversation } of asking) {
|
|
557
|
+
if (!following.some((next) => next.conversation === conversation))
|
|
558
|
+
ended(conversation);
|
|
421
559
|
}
|
|
422
560
|
asking = following;
|
|
423
561
|
}
|
|
424
|
-
for (const [conversation, held] of arriving) {
|
|
425
|
-
if (!complete.has(conversation))
|
|
426
|
-
continue;
|
|
427
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
428
|
-
}
|
|
429
562
|
return null;
|
|
430
563
|
}
|
|
431
|
-
function
|
|
564
|
+
function caughtUp(conversations) {
|
|
565
|
+
const ends = new Map();
|
|
566
|
+
for (const conversation of conversations) {
|
|
567
|
+
const held = new Promise((end) => ends.set(conversation, end));
|
|
568
|
+
catching.set(conversation, held);
|
|
569
|
+
void held.then(() => {
|
|
570
|
+
if (catching.get(conversation) === held)
|
|
571
|
+
catching.delete(conversation);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
const ended = (conversation) => ends.get(conversation)?.();
|
|
575
|
+
const read = readHistories(conversations, ended);
|
|
576
|
+
const endAll = () => {
|
|
577
|
+
for (const conversation of conversations)
|
|
578
|
+
ended(conversation);
|
|
579
|
+
};
|
|
580
|
+
read.then(endAll, endAll);
|
|
581
|
+
return read;
|
|
582
|
+
}
|
|
583
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
432
584
|
return deps.durably(async (local) => {
|
|
433
585
|
if (!replicated.has(conversation))
|
|
434
586
|
return;
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
await local
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
587
|
+
const whole = next === undefined;
|
|
588
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
589
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
590
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
591
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
592
|
+
stored.add(conversation);
|
|
593
|
+
reads.set(conversation, cursor);
|
|
594
|
+
if (partial)
|
|
595
|
+
partials.add(conversation);
|
|
596
|
+
else
|
|
597
|
+
partials.delete(conversation);
|
|
443
598
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
444
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
599
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
445
600
|
behind.delete(conversation);
|
|
446
|
-
|
|
447
|
-
|
|
601
|
+
await slide(local, conversation, arriving);
|
|
602
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
603
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
448
604
|
}
|
|
449
605
|
});
|
|
450
606
|
}
|
|
@@ -453,11 +609,8 @@ export function createConversationStore(deps) {
|
|
|
453
609
|
if (event.kind === 'gap') {
|
|
454
610
|
if (replicated.has(conversation))
|
|
455
611
|
gap(conversation);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
return;
|
|
459
|
-
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
460
|
-
emit(conversation, false);
|
|
612
|
+
if (lives.has(conversation))
|
|
613
|
+
unsent.set(conversation, new Set(FACTS));
|
|
461
614
|
return;
|
|
462
615
|
}
|
|
463
616
|
if (event.kind === 'turn') {
|
|
@@ -480,6 +633,18 @@ export function createConversationStore(deps) {
|
|
|
480
633
|
live(conversation, { options: structuredClone(event.options) });
|
|
481
634
|
return;
|
|
482
635
|
}
|
|
636
|
+
if (event.kind === 'starting') {
|
|
637
|
+
live(conversation, { starting: event.step });
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (event.kind === 'link') {
|
|
641
|
+
live(conversation, { link: { connected: event.connected } });
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (event.kind === 'deliveries') {
|
|
645
|
+
live(conversation, { deliveryUnknown: [...event.unknown] });
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
483
648
|
const arriving = acceptedMessage(event.message);
|
|
484
649
|
if (arriving === null)
|
|
485
650
|
return;
|
|
@@ -490,22 +655,118 @@ export function createConversationStore(deps) {
|
|
|
490
655
|
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
491
656
|
emit(conversation);
|
|
492
657
|
}
|
|
658
|
+
async function kept(local, conversation, following) {
|
|
659
|
+
if (following === null || empty(following)) {
|
|
660
|
+
await local.dropChatDraft(conversation);
|
|
661
|
+
drafts.delete(conversation);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
await local.keepChatDrafts([following]);
|
|
665
|
+
drafts.set(conversation, following);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function sealed(local, conversation, turns) {
|
|
669
|
+
const held = drafts.get(conversation);
|
|
670
|
+
const submitted = held?.submitted ?? [];
|
|
671
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
672
|
+
if (held === undefined || left.length === submitted.length)
|
|
673
|
+
return false;
|
|
674
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
function moved(agent, operation, to) {
|
|
678
|
+
const from = newChat(agent);
|
|
679
|
+
return deps.durably(async (local) => {
|
|
680
|
+
const held = drafts.get(from);
|
|
681
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
682
|
+
if (earlier.includes(operation))
|
|
683
|
+
return;
|
|
684
|
+
const blank = {
|
|
685
|
+
conversation: from,
|
|
686
|
+
text: '',
|
|
687
|
+
revision: chatDraftRevision(),
|
|
688
|
+
};
|
|
689
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
690
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
691
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
692
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
693
|
+
drafts.set(from, left);
|
|
694
|
+
for (const one of taken)
|
|
695
|
+
drafts.set(to, one);
|
|
696
|
+
deps.publish(conversationChangeSet([from, to]));
|
|
697
|
+
});
|
|
698
|
+
}
|
|
493
699
|
function withDraft(conversation, next) {
|
|
494
700
|
return deps.durably(async (local) => {
|
|
495
|
-
const following = next(drafts.get(conversation));
|
|
701
|
+
const following = await next(drafts.get(conversation), local);
|
|
496
702
|
if (following === undefined)
|
|
497
703
|
return;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
drafts.delete(conversation);
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
await local.keepChatDraft(following);
|
|
504
|
-
drafts.set(conversation, following);
|
|
505
|
-
}
|
|
506
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
704
|
+
await kept(local, conversation, following);
|
|
705
|
+
deps.publish(conversationChangeSet([conversation]));
|
|
507
706
|
});
|
|
508
707
|
}
|
|
708
|
+
async function submitted(conversation, operation, draft) {
|
|
709
|
+
let submission;
|
|
710
|
+
let earlier = false;
|
|
711
|
+
await withDraft(conversation, (current) => {
|
|
712
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
713
|
+
earlier = submission !== undefined;
|
|
714
|
+
if (submission !== undefined)
|
|
715
|
+
return undefined;
|
|
716
|
+
const said = draft ?? current;
|
|
717
|
+
submission = { operation, text: said?.text ?? '', turn: null };
|
|
718
|
+
return {
|
|
719
|
+
conversation,
|
|
720
|
+
...(draft === undefined || current === undefined
|
|
721
|
+
? { text: '', revision: chatDraftRevision() }
|
|
722
|
+
: { text: current.text, revision: current.revision }),
|
|
723
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
return { submission: submission, earlier };
|
|
727
|
+
}
|
|
728
|
+
function forgotten(conversation, submission) {
|
|
729
|
+
return withDraft(conversation, (current) => {
|
|
730
|
+
const held = current?.submitted ?? [];
|
|
731
|
+
if (current === undefined || !held.includes(submission))
|
|
732
|
+
return undefined;
|
|
733
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async function dispatched(conversation, submission) {
|
|
737
|
+
const dispatch = {};
|
|
738
|
+
sending.set(submission.operation, dispatch);
|
|
739
|
+
try {
|
|
740
|
+
const result = await invoke('conversation.send', conversation, {
|
|
741
|
+
text: submission.text,
|
|
742
|
+
operation_id: submission.operation,
|
|
743
|
+
});
|
|
744
|
+
if (result.kind === 'revoked-authorization')
|
|
745
|
+
return result;
|
|
746
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
747
|
+
deps.unanswered();
|
|
748
|
+
return result;
|
|
749
|
+
}
|
|
750
|
+
const turn = answered(result, 'turn');
|
|
751
|
+
await withDraft(conversation, async (current, local) => {
|
|
752
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
753
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
754
|
+
return undefined;
|
|
755
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
756
|
+
if (result.kind !== 'action')
|
|
757
|
+
return returned(current, held, others);
|
|
758
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
759
|
+
return withSubmitted(current, turn === null || arrived
|
|
760
|
+
? others
|
|
761
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
762
|
+
});
|
|
763
|
+
return result;
|
|
764
|
+
}
|
|
765
|
+
finally {
|
|
766
|
+
if (sending.get(submission.operation) === dispatch)
|
|
767
|
+
sending.delete(submission.operation);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
509
770
|
function invoke(name, conversation, extra = {}) {
|
|
510
771
|
return deps.call(name, { conversation, ...extra });
|
|
511
772
|
}
|
|
@@ -515,47 +776,48 @@ export function createConversationStore(deps) {
|
|
|
515
776
|
return entries();
|
|
516
777
|
},
|
|
517
778
|
async read(conversation, options = {}) {
|
|
518
|
-
const
|
|
519
|
-
return deps.durably(() =>
|
|
779
|
+
const asked = askedOf(options);
|
|
780
|
+
return deps.durably(async (local) => {
|
|
781
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
782
|
+
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
783
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
|
|
784
|
+
});
|
|
520
785
|
},
|
|
521
|
-
create(input) {
|
|
522
|
-
|
|
786
|
+
async create(input) {
|
|
787
|
+
const result = await deps.call('conversation.create', input);
|
|
788
|
+
const conversation = answered(result, 'conversation');
|
|
789
|
+
if (conversation === null)
|
|
790
|
+
return result;
|
|
791
|
+
if (!listing.has(conversation))
|
|
792
|
+
creations.add(conversation);
|
|
793
|
+
const creation = creationOf(input);
|
|
794
|
+
if (creation !== null)
|
|
795
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
796
|
+
return result;
|
|
523
797
|
},
|
|
524
|
-
async send(conversation) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
798
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
799
|
+
if (deps.online()) {
|
|
800
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
801
|
+
if (!earlier || !deps.expired(operation))
|
|
802
|
+
return dispatched(conversation, submission);
|
|
803
|
+
await forgotten(conversation, submission);
|
|
804
|
+
return EXPIRED;
|
|
530
805
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
})));
|
|
536
|
-
const result = await invoke('conversation.send', conversation, {
|
|
537
|
-
text: draft?.text ?? '',
|
|
538
|
-
files,
|
|
539
|
-
});
|
|
540
|
-
const turn = turnOf(result);
|
|
541
|
-
if (turn === null || draft === undefined)
|
|
542
|
-
return result;
|
|
543
|
-
await withDraft(conversation, (current) => {
|
|
544
|
-
if (current !== draft)
|
|
545
|
-
return undefined;
|
|
546
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
547
|
-
return sealed ? null : { ...draft, turn };
|
|
548
|
-
});
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
551
|
-
finally {
|
|
552
|
-
if (pending !== undefined && sending.get(conversation) === pending) {
|
|
553
|
-
sending.delete(conversation);
|
|
554
|
-
if (drafts.get(conversation) === draft) {
|
|
555
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
556
|
-
}
|
|
557
|
-
}
|
|
806
|
+
if (draft !== undefined) {
|
|
807
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
808
|
+
? undefined
|
|
809
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
558
810
|
}
|
|
811
|
+
return OFFLINE;
|
|
812
|
+
},
|
|
813
|
+
async attach(room, file) {
|
|
814
|
+
if (!deps.online())
|
|
815
|
+
return OFFLINE;
|
|
816
|
+
return deps.call('conversation.attach', {
|
|
817
|
+
room,
|
|
818
|
+
...(await declaredFile(file)),
|
|
819
|
+
blob: file.blob,
|
|
820
|
+
});
|
|
559
821
|
},
|
|
560
822
|
answer(conversation, answer) {
|
|
561
823
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -570,7 +832,12 @@ export function createConversationStore(deps) {
|
|
|
570
832
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
571
833
|
if (result.kind !== 'action')
|
|
572
834
|
return result;
|
|
573
|
-
await withDraft(conversation, (current) =>
|
|
835
|
+
await withDraft(conversation, (current) => {
|
|
836
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
837
|
+
if (current === undefined || held === undefined)
|
|
838
|
+
return undefined;
|
|
839
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
840
|
+
});
|
|
574
841
|
return result;
|
|
575
842
|
},
|
|
576
843
|
delete(conversation) {
|
|
@@ -579,33 +846,43 @@ export function createConversationStore(deps) {
|
|
|
579
846
|
};
|
|
580
847
|
const chatDraft = {
|
|
581
848
|
write(conversation, draft, revision = chatDraftRevision()) {
|
|
582
|
-
return withDraft(conversation, () => ({
|
|
849
|
+
return withDraft(conversation, (current) => ({
|
|
583
850
|
conversation,
|
|
584
851
|
text: draft.text,
|
|
585
|
-
files: [...draft.files],
|
|
586
|
-
turn: null,
|
|
587
852
|
revision,
|
|
853
|
+
...(current === undefined ? {} : pending(current)),
|
|
854
|
+
}));
|
|
855
|
+
},
|
|
856
|
+
prepend(conversation, draft) {
|
|
857
|
+
return withDraft(conversation, (current) => ({
|
|
858
|
+
conversation,
|
|
859
|
+
...(current === undefined ? { text: draft.text } : prepended(current, draft)),
|
|
860
|
+
revision: chatDraftRevision(),
|
|
861
|
+
...(current === undefined ? {} : pending(current)),
|
|
588
862
|
}));
|
|
589
863
|
},
|
|
590
864
|
discard(conversation) {
|
|
591
|
-
return withDraft(conversation, (current) =>
|
|
865
|
+
return withDraft(conversation, (current) => current === undefined
|
|
866
|
+
? undefined
|
|
867
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
592
868
|
},
|
|
593
869
|
};
|
|
594
870
|
return {
|
|
595
871
|
surface,
|
|
596
872
|
chatDraft,
|
|
597
|
-
hydrate(held,
|
|
598
|
-
|
|
873
|
+
hydrate(held, kept) {
|
|
874
|
+
stored.clear();
|
|
599
875
|
reads.clear();
|
|
600
|
-
|
|
601
|
-
view.messages = null;
|
|
876
|
+
partials.clear();
|
|
602
877
|
for (const entry of held) {
|
|
603
|
-
|
|
878
|
+
stored.add(entry.conversation);
|
|
604
879
|
if (entry.read !== undefined)
|
|
605
880
|
reads.set(entry.conversation, entry.read);
|
|
881
|
+
if (entry.partial)
|
|
882
|
+
partials.add(entry.conversation);
|
|
606
883
|
}
|
|
607
884
|
drafts.clear();
|
|
608
|
-
for (const record of
|
|
885
|
+
for (const record of kept)
|
|
609
886
|
drafts.set(record.conversation, record);
|
|
610
887
|
},
|
|
611
888
|
async reconcile() {
|
|
@@ -613,8 +890,10 @@ export function createConversationStore(deps) {
|
|
|
613
890
|
return;
|
|
614
891
|
const named = new Set(listing.keys());
|
|
615
892
|
const leaving = [
|
|
616
|
-
...new Set([...replicated, ...
|
|
617
|
-
].filter((conversation) => !named.has(conversation)
|
|
893
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
894
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
895
|
+
!creations.has(conversation) &&
|
|
896
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
618
897
|
for (const conversation of named) {
|
|
619
898
|
if (!replicated.has(conversation))
|
|
620
899
|
behind.add(conversation);
|
|
@@ -625,41 +904,93 @@ export function createConversationStore(deps) {
|
|
|
625
904
|
await remove(leaving);
|
|
626
905
|
},
|
|
627
906
|
async catchUp(progress) {
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
907
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
908
|
+
const read = async (conversations) => {
|
|
909
|
+
if (conversations.length === 0 || deps.closed())
|
|
910
|
+
return;
|
|
911
|
+
const failed = await caughtUp(conversations);
|
|
631
912
|
if (failed?.kind === 'transport-uncertainty')
|
|
632
913
|
progress.reachable = false;
|
|
633
|
-
}
|
|
914
|
+
};
|
|
915
|
+
const asking = [...behind].filter(owed).sort();
|
|
916
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
917
|
+
await Promise.all([
|
|
918
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
919
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
920
|
+
]);
|
|
634
921
|
if (behind.size > 0)
|
|
635
922
|
progress.complete = false;
|
|
636
923
|
},
|
|
924
|
+
async resend(progress) {
|
|
925
|
+
for (const [conversation, held] of [...drafts]) {
|
|
926
|
+
for (const submission of held.submitted ?? []) {
|
|
927
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
928
|
+
continue;
|
|
929
|
+
if (deps.closed())
|
|
930
|
+
return;
|
|
931
|
+
if (deps.expired(submission.operation)) {
|
|
932
|
+
await forgotten(conversation, submission);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const result = await dispatched(conversation, submission);
|
|
936
|
+
if (result.kind === 'revoked-authorization')
|
|
937
|
+
return;
|
|
938
|
+
if (result.kind === 'transport-uncertainty') {
|
|
939
|
+
progress.reachable = false;
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
943
|
+
progress.complete = false;
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
},
|
|
637
949
|
current() {
|
|
638
950
|
return behind.size === 0;
|
|
639
951
|
},
|
|
952
|
+
known() {
|
|
953
|
+
return listKnown;
|
|
954
|
+
},
|
|
640
955
|
reconnected() {
|
|
641
956
|
for (const conversation of replicated)
|
|
642
957
|
gap(conversation);
|
|
643
958
|
},
|
|
959
|
+
reopened() {
|
|
960
|
+
sending.clear();
|
|
961
|
+
},
|
|
962
|
+
lost() {
|
|
963
|
+
catching.clear();
|
|
964
|
+
},
|
|
644
965
|
watch(conversation, options, deliver) {
|
|
645
|
-
const
|
|
646
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
966
|
+
const asked = askedOf(options);
|
|
967
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
647
968
|
const view = views.get(key) ?? {
|
|
648
969
|
conversation,
|
|
649
|
-
|
|
970
|
+
asked,
|
|
650
971
|
watchers: new Set(),
|
|
651
|
-
|
|
972
|
+
window: null,
|
|
973
|
+
more: false,
|
|
974
|
+
total: null,
|
|
652
975
|
};
|
|
653
976
|
views.set(key, view);
|
|
654
977
|
const watcher = { notify: deliver, ready: false };
|
|
655
978
|
view.watchers.add(watcher);
|
|
656
979
|
void deps
|
|
657
|
-
.durably(() =>
|
|
980
|
+
.durably(async (local) => {
|
|
981
|
+
if (view.window !== null)
|
|
982
|
+
return;
|
|
983
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
984
|
+
view.window = messages;
|
|
985
|
+
view.more = more;
|
|
986
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
987
|
+
})
|
|
658
988
|
.then(() => {
|
|
659
989
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
660
990
|
return;
|
|
661
991
|
watcher.ready = true;
|
|
662
|
-
|
|
992
|
+
const window = view.window ?? [];
|
|
993
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
663
994
|
})
|
|
664
995
|
.catch(() => { });
|
|
665
996
|
return () => {
|
|
@@ -706,12 +1037,16 @@ export function createConversationStore(deps) {
|
|
|
706
1037
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
707
1038
|
if (event.kind === 'list') {
|
|
708
1039
|
listing.clear();
|
|
709
|
-
for (const entry of event.entries)
|
|
1040
|
+
for (const entry of event.entries) {
|
|
710
1041
|
listing.set(entry.conversation, { ...entry });
|
|
1042
|
+
creations.delete(entry.conversation);
|
|
1043
|
+
}
|
|
711
1044
|
listKnown = true;
|
|
1045
|
+
openingEnded();
|
|
712
1046
|
}
|
|
713
1047
|
else if (event.kind === 'listed') {
|
|
714
1048
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
1049
|
+
creations.delete(event.entry.conversation);
|
|
715
1050
|
}
|
|
716
1051
|
else {
|
|
717
1052
|
listing.delete(event.conversation);
|
|
@@ -725,16 +1060,16 @@ export function createConversationStore(deps) {
|
|
|
725
1060
|
heard(event);
|
|
726
1061
|
return false;
|
|
727
1062
|
},
|
|
728
|
-
|
|
729
|
-
return [...
|
|
1063
|
+
searchable() {
|
|
1064
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
730
1065
|
},
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
1066
|
+
acquired(conversation) {
|
|
1067
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
734
1068
|
},
|
|
735
1069
|
close() {
|
|
736
1070
|
views.clear();
|
|
737
1071
|
lives.clear();
|
|
1072
|
+
unsent.clear();
|
|
738
1073
|
listWatchers.clear();
|
|
739
1074
|
agentWatchers.clear();
|
|
740
1075
|
},
|