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