@agentshouse/mdruntime 0.1.0-c16fc8caac7ff → 0.1.0-c1a1221068331
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +289 -276
- package/README.md +16 -14
- package/dist/adapter.d.ts +97 -34
- 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/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 -8
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +155 -63
- 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 +31 -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 +3336 -729
- package/dist/conversation.d.ts +30 -9
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +546 -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 +14 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +124 -14
- 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 +52 -20
- 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 +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 +112 -44
- package/dist/replica.d.ts +57 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +295 -149
- 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 +116 -76
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +349 -480
- 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 +17 -18
- 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 -26
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -62
- package/dist/chat/markdown.d.ts +0 -20
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -188
- 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,147 @@ 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 = {};
|
|
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
|
+
if (result.kind === 'revoked-authorization')
|
|
761
|
+
return result;
|
|
762
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
763
|
+
deps.unanswered();
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
const turn = answered(result, 'turn');
|
|
767
|
+
await withDraft(conversation, async (current, local) => {
|
|
768
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
769
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
770
|
+
return undefined;
|
|
771
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
772
|
+
if (result.kind !== 'action')
|
|
773
|
+
return returned(current, held, others);
|
|
774
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
775
|
+
return withSubmitted(current, turn === null || arrived
|
|
776
|
+
? others
|
|
777
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
778
|
+
});
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
finally {
|
|
782
|
+
if (sending.get(submission.operation) === dispatch)
|
|
783
|
+
sending.delete(submission.operation);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
509
786
|
function invoke(name, conversation, extra = {}) {
|
|
510
787
|
return deps.call(name, { conversation, ...extra });
|
|
511
788
|
}
|
|
@@ -515,47 +792,57 @@ export function createConversationStore(deps) {
|
|
|
515
792
|
return entries();
|
|
516
793
|
},
|
|
517
794
|
async read(conversation, options = {}) {
|
|
518
|
-
const
|
|
519
|
-
return deps.durably(() =>
|
|
795
|
+
const asked = askedOf(options);
|
|
796
|
+
return deps.durably(async (local) => {
|
|
797
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
798
|
+
const total = asked.total ? await local.messageCount(conversation) : null;
|
|
799
|
+
return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
|
|
800
|
+
});
|
|
520
801
|
},
|
|
521
|
-
create(input) {
|
|
522
|
-
|
|
802
|
+
async create(input) {
|
|
803
|
+
const result = await deps.call('conversation.create', input);
|
|
804
|
+
const conversation = answered(result, 'conversation');
|
|
805
|
+
if (conversation === null)
|
|
806
|
+
return result;
|
|
807
|
+
if (!listing.has(conversation))
|
|
808
|
+
creations.add(conversation);
|
|
809
|
+
const creation = creationOf(input);
|
|
810
|
+
if (creation !== null)
|
|
811
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
812
|
+
return result;
|
|
523
813
|
},
|
|
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 };
|
|
814
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
815
|
+
if (deps.full())
|
|
816
|
+
return DURABLE_WRITE;
|
|
817
|
+
if (deps.online()) {
|
|
818
|
+
const held = await submitted(conversation, operation, draft).catch((error) => {
|
|
819
|
+
if (refusedForSpace(error))
|
|
820
|
+
return null;
|
|
821
|
+
throw error;
|
|
548
822
|
});
|
|
549
|
-
|
|
823
|
+
if (held === null)
|
|
824
|
+
return DURABLE_WRITE;
|
|
825
|
+
const { submission, earlier } = held;
|
|
826
|
+
if (!earlier || !deps.expired(operation))
|
|
827
|
+
return dispatched(conversation, submission);
|
|
828
|
+
await forgotten(conversation, submission);
|
|
829
|
+
return EXPIRED;
|
|
550
830
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
556
|
-
}
|
|
557
|
-
}
|
|
831
|
+
if (draft !== undefined) {
|
|
832
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
833
|
+
? undefined
|
|
834
|
+
: returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
558
835
|
}
|
|
836
|
+
return OFFLINE;
|
|
837
|
+
},
|
|
838
|
+
async attach(room, file) {
|
|
839
|
+
if (!deps.online())
|
|
840
|
+
return OFFLINE;
|
|
841
|
+
return deps.call('conversation.attach', {
|
|
842
|
+
room,
|
|
843
|
+
...(await declaredFile(file)),
|
|
844
|
+
blob: file.blob,
|
|
845
|
+
});
|
|
559
846
|
},
|
|
560
847
|
answer(conversation, answer) {
|
|
561
848
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -570,7 +857,12 @@ export function createConversationStore(deps) {
|
|
|
570
857
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
571
858
|
if (result.kind !== 'action')
|
|
572
859
|
return result;
|
|
573
|
-
await withDraft(conversation, (current) =>
|
|
860
|
+
await withDraft(conversation, (current) => {
|
|
861
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
862
|
+
if (current === undefined || held === undefined)
|
|
863
|
+
return undefined;
|
|
864
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
865
|
+
});
|
|
574
866
|
return result;
|
|
575
867
|
},
|
|
576
868
|
delete(conversation) {
|
|
@@ -579,33 +871,43 @@ export function createConversationStore(deps) {
|
|
|
579
871
|
};
|
|
580
872
|
const chatDraft = {
|
|
581
873
|
write(conversation, draft, revision = chatDraftRevision()) {
|
|
582
|
-
return withDraft(conversation, () => ({
|
|
874
|
+
return withDraft(conversation, (current) => ({
|
|
583
875
|
conversation,
|
|
584
876
|
text: draft.text,
|
|
585
|
-
files: [...draft.files],
|
|
586
|
-
turn: null,
|
|
587
877
|
revision,
|
|
878
|
+
...(current === undefined ? {} : pending(current)),
|
|
879
|
+
}));
|
|
880
|
+
},
|
|
881
|
+
prepend(conversation, draft) {
|
|
882
|
+
return withDraft(conversation, (current) => ({
|
|
883
|
+
conversation,
|
|
884
|
+
...(current === undefined ? { text: draft.text } : prepended(current, draft)),
|
|
885
|
+
revision: chatDraftRevision(),
|
|
886
|
+
...(current === undefined ? {} : pending(current)),
|
|
588
887
|
}));
|
|
589
888
|
},
|
|
590
889
|
discard(conversation) {
|
|
591
|
-
return withDraft(conversation, (current) =>
|
|
890
|
+
return withDraft(conversation, (current) => current === undefined
|
|
891
|
+
? undefined
|
|
892
|
+
: { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
|
|
592
893
|
},
|
|
593
894
|
};
|
|
594
895
|
return {
|
|
595
896
|
surface,
|
|
596
897
|
chatDraft,
|
|
597
|
-
hydrate(held,
|
|
598
|
-
|
|
898
|
+
hydrate(held, kept) {
|
|
899
|
+
stored.clear();
|
|
599
900
|
reads.clear();
|
|
600
|
-
|
|
601
|
-
view.messages = null;
|
|
901
|
+
partials.clear();
|
|
602
902
|
for (const entry of held) {
|
|
603
|
-
|
|
903
|
+
stored.add(entry.conversation);
|
|
604
904
|
if (entry.read !== undefined)
|
|
605
905
|
reads.set(entry.conversation, entry.read);
|
|
906
|
+
if (entry.partial)
|
|
907
|
+
partials.add(entry.conversation);
|
|
606
908
|
}
|
|
607
909
|
drafts.clear();
|
|
608
|
-
for (const record of
|
|
910
|
+
for (const record of kept)
|
|
609
911
|
drafts.set(record.conversation, record);
|
|
610
912
|
},
|
|
611
913
|
async reconcile() {
|
|
@@ -613,8 +915,10 @@ export function createConversationStore(deps) {
|
|
|
613
915
|
return;
|
|
614
916
|
const named = new Set(listing.keys());
|
|
615
917
|
const leaving = [
|
|
616
|
-
...new Set([...replicated, ...
|
|
617
|
-
].filter((conversation) => !named.has(conversation)
|
|
918
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
919
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
920
|
+
!creations.has(conversation) &&
|
|
921
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
618
922
|
for (const conversation of named) {
|
|
619
923
|
if (!replicated.has(conversation))
|
|
620
924
|
behind.add(conversation);
|
|
@@ -625,41 +929,98 @@ export function createConversationStore(deps) {
|
|
|
625
929
|
await remove(leaving);
|
|
626
930
|
},
|
|
627
931
|
async catchUp(progress) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
932
|
+
if (deps.full()) {
|
|
933
|
+
if (behind.size > 0)
|
|
934
|
+
progress.complete = false;
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
|
|
938
|
+
const read = async (conversations) => {
|
|
939
|
+
if (conversations.length === 0 || deps.closed())
|
|
940
|
+
return;
|
|
941
|
+
const failed = await caughtUp(conversations);
|
|
631
942
|
if (failed?.kind === 'transport-uncertainty')
|
|
632
943
|
progress.reachable = false;
|
|
633
|
-
}
|
|
944
|
+
};
|
|
945
|
+
const asking = [...behind].filter(owed).sort();
|
|
946
|
+
const awaited = asking.filter((conversation) => catching.has(conversation));
|
|
947
|
+
await Promise.all([
|
|
948
|
+
read(asking.filter((conversation) => !catching.has(conversation))),
|
|
949
|
+
Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
|
|
950
|
+
]);
|
|
634
951
|
if (behind.size > 0)
|
|
635
952
|
progress.complete = false;
|
|
636
953
|
},
|
|
954
|
+
async resend(progress) {
|
|
955
|
+
for (const [conversation, held] of [...drafts]) {
|
|
956
|
+
for (const submission of held.submitted ?? []) {
|
|
957
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
958
|
+
continue;
|
|
959
|
+
if (deps.closed())
|
|
960
|
+
return;
|
|
961
|
+
if (deps.expired(submission.operation)) {
|
|
962
|
+
await forgotten(conversation, submission);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const result = await dispatched(conversation, submission);
|
|
966
|
+
if (result.kind === 'revoked-authorization')
|
|
967
|
+
return;
|
|
968
|
+
if (result.kind === 'transport-uncertainty') {
|
|
969
|
+
progress.reachable = false;
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
973
|
+
progress.complete = false;
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
},
|
|
637
979
|
current() {
|
|
638
980
|
return behind.size === 0;
|
|
639
981
|
},
|
|
982
|
+
known() {
|
|
983
|
+
return listKnown;
|
|
984
|
+
},
|
|
640
985
|
reconnected() {
|
|
641
986
|
for (const conversation of replicated)
|
|
642
987
|
gap(conversation);
|
|
643
988
|
},
|
|
989
|
+
reopened() {
|
|
990
|
+
sending.clear();
|
|
991
|
+
},
|
|
992
|
+
lost() {
|
|
993
|
+
catching.clear();
|
|
994
|
+
},
|
|
644
995
|
watch(conversation, options, deliver) {
|
|
645
|
-
const
|
|
646
|
-
const key = JSON.stringify([conversation, limit ?? null]);
|
|
996
|
+
const asked = askedOf(options);
|
|
997
|
+
const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
|
|
647
998
|
const view = views.get(key) ?? {
|
|
648
999
|
conversation,
|
|
649
|
-
|
|
1000
|
+
asked,
|
|
650
1001
|
watchers: new Set(),
|
|
651
|
-
|
|
1002
|
+
window: null,
|
|
1003
|
+
more: false,
|
|
1004
|
+
total: null,
|
|
652
1005
|
};
|
|
653
1006
|
views.set(key, view);
|
|
654
1007
|
const watcher = { notify: deliver, ready: false };
|
|
655
1008
|
view.watchers.add(watcher);
|
|
656
1009
|
void deps
|
|
657
|
-
.durably(() =>
|
|
1010
|
+
.durably(async (local) => {
|
|
1011
|
+
if (view.window !== null)
|
|
1012
|
+
return;
|
|
1013
|
+
const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
|
|
1014
|
+
view.window = messages;
|
|
1015
|
+
view.more = more;
|
|
1016
|
+
view.total = asked.total ? await local.messageCount(conversation) : null;
|
|
1017
|
+
})
|
|
658
1018
|
.then(() => {
|
|
659
1019
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
660
1020
|
return;
|
|
661
1021
|
watcher.ready = true;
|
|
662
|
-
|
|
1022
|
+
const window = view.window ?? [];
|
|
1023
|
+
watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
|
|
663
1024
|
})
|
|
664
1025
|
.catch(() => { });
|
|
665
1026
|
return () => {
|
|
@@ -706,12 +1067,16 @@ export function createConversationStore(deps) {
|
|
|
706
1067
|
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
707
1068
|
if (event.kind === 'list') {
|
|
708
1069
|
listing.clear();
|
|
709
|
-
for (const entry of event.entries)
|
|
1070
|
+
for (const entry of event.entries) {
|
|
710
1071
|
listing.set(entry.conversation, { ...entry });
|
|
1072
|
+
creations.delete(entry.conversation);
|
|
1073
|
+
}
|
|
711
1074
|
listKnown = true;
|
|
1075
|
+
openingEnded();
|
|
712
1076
|
}
|
|
713
1077
|
else if (event.kind === 'listed') {
|
|
714
1078
|
listing.set(event.entry.conversation, { ...event.entry });
|
|
1079
|
+
creations.delete(event.entry.conversation);
|
|
715
1080
|
}
|
|
716
1081
|
else {
|
|
717
1082
|
listing.delete(event.conversation);
|
|
@@ -725,16 +1090,16 @@ export function createConversationStore(deps) {
|
|
|
725
1090
|
heard(event);
|
|
726
1091
|
return false;
|
|
727
1092
|
},
|
|
728
|
-
|
|
729
|
-
return [...
|
|
1093
|
+
searchable() {
|
|
1094
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
730
1095
|
},
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
1096
|
+
acquired(conversation) {
|
|
1097
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
734
1098
|
},
|
|
735
1099
|
close() {
|
|
736
1100
|
views.clear();
|
|
737
1101
|
lives.clear();
|
|
1102
|
+
unsent.clear();
|
|
738
1103
|
listWatchers.clear();
|
|
739
1104
|
agentWatchers.clear();
|
|
740
1105
|
},
|