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