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