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