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