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