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