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