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