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