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