@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ceb97106cc2fd
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 +273 -175
- package/README.md +21 -11
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +129 -41
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +47 -0
- 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 +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +20 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +114 -37
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +49 -16
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- 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/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -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 +27 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +63 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +22 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +155 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -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 +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +629 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -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 +34 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +28 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +71 -26
- package/dist/collection.d.ts +21 -12
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +271 -23
- package/dist/connection.d.ts +50 -23
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2074 -735
- package/dist/conversation.d.ts +41 -14
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +546 -214
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -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/host.d.ts +24 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +126 -45
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +19 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +17 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +159 -46
- package/dist/model.d.ts +17 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +16 -4
- package/dist/observation.d.ts +6 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +38 -17
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +32 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +42 -1
- package/dist/overlay.d.ts +16 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +118 -77
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -16
- package/dist/query.d.ts +46 -6
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +85 -37
- package/dist/reach.d.ts +45 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +306 -0
- package/dist/react.d.ts +44 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +66 -0
- package/dist/read-side.d.ts +16 -5
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +408 -137
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts +12 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +51 -36
- package/dist/replica.d.ts +10 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +129 -73
- package/dist/room-draft.d.ts +41 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +139 -28
- package/dist/rows.d.ts +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +87 -50
- package/dist/save.d.ts +32 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +465 -92
- package/dist/search.d.ts +37 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +116 -45
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -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 +76 -19
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +539 -200
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +162 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +82 -61
- package/package.json +31 -7
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/conversation.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
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';
|
|
3
6
|
const QUIET = {
|
|
4
7
|
turn: null,
|
|
5
8
|
questions: [],
|
|
@@ -7,12 +10,42 @@ const QUIET = {
|
|
|
7
10
|
commands: [],
|
|
8
11
|
options: [],
|
|
9
12
|
};
|
|
13
|
+
export function unheldView(conversation) {
|
|
14
|
+
return {
|
|
15
|
+
conversation,
|
|
16
|
+
messages: [],
|
|
17
|
+
turn: null,
|
|
18
|
+
questions: [],
|
|
19
|
+
process: null,
|
|
20
|
+
commands: [],
|
|
21
|
+
options: [],
|
|
22
|
+
chatDraft: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export const SESSION_STARTED = 'Session started';
|
|
26
|
+
const NEW_CHAT = 'new:';
|
|
27
|
+
export function newChat(agent) {
|
|
28
|
+
return `${NEW_CHAT}${agent}`;
|
|
29
|
+
}
|
|
30
|
+
function creationOf(input) {
|
|
31
|
+
if (typeof input !== 'object' || input === null)
|
|
32
|
+
return null;
|
|
33
|
+
const { agent_id: agent, operation_id: operation } = input;
|
|
34
|
+
return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
|
|
35
|
+
}
|
|
10
36
|
function startedTurn(turn, operation) {
|
|
37
|
+
if (operation.op === 'begin')
|
|
38
|
+
return { turn, started: operation.at, parts: [], context: null };
|
|
11
39
|
if (operation.op === 'context') {
|
|
12
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
turn,
|
|
42
|
+
started: null,
|
|
43
|
+
parts: [],
|
|
44
|
+
context: { used: operation.used, window: operation.window },
|
|
45
|
+
};
|
|
13
46
|
}
|
|
14
47
|
return operation.op === 'start' && operation.index === 0
|
|
15
|
-
? { turn, parts: [structuredClone(operation.part)], context: null }
|
|
48
|
+
? { turn, started: null, parts: [structuredClone(operation.part)], context: null }
|
|
16
49
|
: null;
|
|
17
50
|
}
|
|
18
51
|
function changedFields(part, operation) {
|
|
@@ -27,6 +60,8 @@ function changedFields(part, operation) {
|
|
|
27
60
|
return { entries: structuredClone(operation.entries) };
|
|
28
61
|
}
|
|
29
62
|
function appliedTurn(held, operation) {
|
|
63
|
+
if (operation.op === 'begin')
|
|
64
|
+
return { ...held, started: operation.at };
|
|
30
65
|
if (operation.op === 'context') {
|
|
31
66
|
return { ...held, context: { used: operation.used, window: operation.window } };
|
|
32
67
|
}
|
|
@@ -54,11 +89,15 @@ function ordered(left, right) {
|
|
|
54
89
|
return left.identity < right.identity ? -1 : 1;
|
|
55
90
|
return 0;
|
|
56
91
|
}
|
|
57
|
-
function
|
|
92
|
+
function windowOf(held, arriving, limit) {
|
|
58
93
|
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
59
94
|
for (const message of arriving)
|
|
60
95
|
byIdentity.set(message.identity, message);
|
|
61
|
-
|
|
96
|
+
const next = [...byIdentity.values()].sort(ordered);
|
|
97
|
+
return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
|
|
98
|
+
}
|
|
99
|
+
function newestOf(arriving, from) {
|
|
100
|
+
return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
|
|
62
101
|
}
|
|
63
102
|
function acceptedMessage(value) {
|
|
64
103
|
if (typeof value !== 'object' || value === null)
|
|
@@ -117,84 +156,215 @@ function historyPages(result) {
|
|
|
117
156
|
}
|
|
118
157
|
return pages;
|
|
119
158
|
}
|
|
120
|
-
function
|
|
159
|
+
function answered(result, field) {
|
|
121
160
|
if (result.kind !== 'action')
|
|
122
161
|
return null;
|
|
123
162
|
const answer = result.result;
|
|
124
163
|
if (typeof answer !== 'object' || answer === null)
|
|
125
164
|
return null;
|
|
126
|
-
const
|
|
127
|
-
return typeof
|
|
165
|
+
const value = answer[field];
|
|
166
|
+
return typeof value === 'string' ? value : null;
|
|
128
167
|
}
|
|
129
168
|
function limitOf(options) {
|
|
130
169
|
const { limit } = options;
|
|
131
170
|
if (limit === undefined)
|
|
132
171
|
return undefined;
|
|
133
172
|
if (!Number.isInteger(limit) || limit < 0) {
|
|
134
|
-
throw
|
|
173
|
+
throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
135
174
|
}
|
|
136
175
|
return limit;
|
|
137
176
|
}
|
|
177
|
+
const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
|
|
178
|
+
function unwritten(draft) {
|
|
179
|
+
return draft.text === '' && draft.files.length === 0;
|
|
180
|
+
}
|
|
181
|
+
function pending(draft) {
|
|
182
|
+
return {
|
|
183
|
+
...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
|
|
184
|
+
...(draft.moves === undefined ? {} : { moves: draft.moves }),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function empty(draft) {
|
|
188
|
+
return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
|
|
189
|
+
}
|
|
190
|
+
function withSubmitted(draft, submitted) {
|
|
191
|
+
const { submitted: _held, ...unsent } = draft;
|
|
192
|
+
return submitted.length === 0 ? unsent : { ...unsent, submitted };
|
|
193
|
+
}
|
|
194
|
+
function returned(draft, submission, others) {
|
|
195
|
+
return withSubmitted({
|
|
196
|
+
conversation: draft.conversation,
|
|
197
|
+
text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
|
|
198
|
+
files: [...submission.files, ...draft.files],
|
|
199
|
+
revision: chatDraftRevision(),
|
|
200
|
+
}, others);
|
|
201
|
+
}
|
|
138
202
|
export function createConversationStore(deps) {
|
|
139
|
-
const
|
|
203
|
+
const stored = new Set();
|
|
140
204
|
const reads = new Map();
|
|
205
|
+
const partials = new Set();
|
|
141
206
|
const drafts = new Map();
|
|
142
207
|
const sending = new Map();
|
|
143
208
|
const behind = new Set();
|
|
144
209
|
const gaps = new Map();
|
|
145
210
|
const lives = new Map();
|
|
146
211
|
const views = new Map();
|
|
212
|
+
const listing = new Map();
|
|
213
|
+
const creations = new Set();
|
|
214
|
+
const listWatchers = new Set();
|
|
215
|
+
let listKnown = false;
|
|
216
|
+
const agentWatchers = new Set();
|
|
217
|
+
let agentNames = new Map();
|
|
218
|
+
let retiredAgents = new Set();
|
|
219
|
+
let agentsRead = null;
|
|
147
220
|
let replicated = new Set();
|
|
148
221
|
let handedReplicated;
|
|
149
222
|
function liveOf(conversation) {
|
|
150
223
|
return lives.get(conversation) ?? QUIET;
|
|
151
224
|
}
|
|
152
|
-
function
|
|
153
|
-
const held = messages.get(conversation) ?? [];
|
|
154
|
-
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
155
|
-
return shown.map(({ identity, turn, text, message }) => ({
|
|
156
|
-
identity,
|
|
157
|
-
turn,
|
|
158
|
-
text,
|
|
159
|
-
message: structuredClone(message),
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
225
|
+
function viewOf(conversation, held) {
|
|
163
226
|
const draft = drafts.get(conversation);
|
|
164
227
|
const facts = liveOf(conversation);
|
|
165
228
|
return {
|
|
166
229
|
conversation,
|
|
167
|
-
messages:
|
|
230
|
+
messages: held.map(({ identity, turn, text, message }) => ({
|
|
231
|
+
identity,
|
|
232
|
+
turn,
|
|
233
|
+
text,
|
|
234
|
+
message,
|
|
235
|
+
})),
|
|
168
236
|
turn: structuredClone(facts.turn),
|
|
169
237
|
questions: structuredClone(facts.questions),
|
|
170
238
|
process: facts.process === null ? null : { ...facts.process },
|
|
171
239
|
commands: structuredClone(facts.commands),
|
|
172
240
|
options: structuredClone(facts.options),
|
|
173
|
-
chatDraft: draft === undefined || draft
|
|
241
|
+
chatDraft: draft === undefined || unwritten(draft)
|
|
174
242
|
? null
|
|
175
|
-
: { text: draft.text, files: [...draft.files] },
|
|
243
|
+
: { text: draft.text, files: [...draft.files], revision: draft.revision },
|
|
176
244
|
};
|
|
177
245
|
}
|
|
178
|
-
function emit(conversation
|
|
246
|
+
function emit(conversation) {
|
|
179
247
|
if (deps.closed())
|
|
180
248
|
return;
|
|
181
249
|
for (const view of views.values()) {
|
|
182
|
-
if (view.conversation !== conversation)
|
|
250
|
+
if (view.conversation !== conversation || view.window === null)
|
|
183
251
|
continue;
|
|
184
|
-
if (messagesChanged)
|
|
185
|
-
view.messages = null;
|
|
186
252
|
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
187
253
|
if (ready.length === 0)
|
|
188
254
|
continue;
|
|
189
|
-
|
|
190
|
-
const value = viewOf(conversation, view.limit, view.messages);
|
|
255
|
+
const value = viewOf(conversation, view.window);
|
|
191
256
|
for (const watcher of ready)
|
|
192
257
|
watcher.notify(value);
|
|
193
258
|
}
|
|
194
259
|
}
|
|
260
|
+
function slide(conversation, arriving) {
|
|
261
|
+
for (const view of views.values()) {
|
|
262
|
+
if (view.conversation !== conversation || view.window === null)
|
|
263
|
+
continue;
|
|
264
|
+
view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function changesOf(conversation, arriving) {
|
|
268
|
+
return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
|
|
269
|
+
}
|
|
270
|
+
function stateOf(conversation) {
|
|
271
|
+
const facts = liveOf(conversation);
|
|
272
|
+
if (facts.questions.length > 0)
|
|
273
|
+
return 'asking';
|
|
274
|
+
return facts.turn === null ? 'idle' : 'working';
|
|
275
|
+
}
|
|
276
|
+
function entries() {
|
|
277
|
+
return [...listing.values()].map((entry) => ({
|
|
278
|
+
conversation: entry.conversation,
|
|
279
|
+
agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
|
|
280
|
+
name: entry.name,
|
|
281
|
+
created: entry.created,
|
|
282
|
+
finished: entry.finished,
|
|
283
|
+
outcome: entry.outcome,
|
|
284
|
+
state: stateOf(entry.conversation),
|
|
285
|
+
started: lives.get(entry.conversation)?.turn?.started ?? null,
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
function emitList() {
|
|
289
|
+
if (deps.closed() || listWatchers.size === 0)
|
|
290
|
+
return;
|
|
291
|
+
const value = entries();
|
|
292
|
+
for (const watcher of listWatchers)
|
|
293
|
+
watcher(value);
|
|
294
|
+
}
|
|
295
|
+
function agents() {
|
|
296
|
+
return [...agentNames]
|
|
297
|
+
.filter(([identity]) => !retiredAgents.has(identity))
|
|
298
|
+
.map(([identity, name]) => ({ identity, name }))
|
|
299
|
+
.sort((left, right) => left.name === right.name
|
|
300
|
+
? left.identity.localeCompare(right.identity)
|
|
301
|
+
: left.name.localeCompare(right.name));
|
|
302
|
+
}
|
|
303
|
+
function emitAgents() {
|
|
304
|
+
if (deps.closed() || agentWatchers.size === 0)
|
|
305
|
+
return;
|
|
306
|
+
const value = agents();
|
|
307
|
+
for (const watcher of agentWatchers)
|
|
308
|
+
watcher(value);
|
|
309
|
+
}
|
|
310
|
+
function setLive(conversation, next) {
|
|
311
|
+
const before = stateOf(conversation);
|
|
312
|
+
const began = lives.get(conversation)?.turn?.started;
|
|
313
|
+
if (next === null)
|
|
314
|
+
lives.delete(conversation);
|
|
315
|
+
else
|
|
316
|
+
lives.set(conversation, next);
|
|
317
|
+
if (!listing.has(conversation))
|
|
318
|
+
return;
|
|
319
|
+
if (stateOf(conversation) !== before || next?.turn?.started !== began)
|
|
320
|
+
emitList();
|
|
321
|
+
}
|
|
322
|
+
async function readAgents() {
|
|
323
|
+
const declaration = deps.agents();
|
|
324
|
+
if (declaration === undefined)
|
|
325
|
+
return;
|
|
326
|
+
const held = await deps.documents(declaration);
|
|
327
|
+
if ('pending' in held)
|
|
328
|
+
return;
|
|
329
|
+
const { documents } = held;
|
|
330
|
+
const names = new Map();
|
|
331
|
+
const retired = new Set();
|
|
332
|
+
for (const document of documents) {
|
|
333
|
+
if (document.type !== declaration.type)
|
|
334
|
+
continue;
|
|
335
|
+
const identity = document.fields?.[declaration.identity];
|
|
336
|
+
const name = document.fields?.[declaration.name];
|
|
337
|
+
if (typeof identity !== 'string' || typeof name !== 'string')
|
|
338
|
+
continue;
|
|
339
|
+
names.set(identity, name);
|
|
340
|
+
const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
|
|
341
|
+
if (ended !== undefined && ended !== null)
|
|
342
|
+
retired.add(identity);
|
|
343
|
+
}
|
|
344
|
+
const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
|
|
345
|
+
if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
|
|
346
|
+
return;
|
|
347
|
+
agentNames = names;
|
|
348
|
+
retiredAgents = retired;
|
|
349
|
+
if (named)
|
|
350
|
+
emitList();
|
|
351
|
+
emitAgents();
|
|
352
|
+
const ended = [...drafts.keys()].filter(retiredChat);
|
|
353
|
+
if (ended.length > 0)
|
|
354
|
+
await remove(ended);
|
|
355
|
+
}
|
|
356
|
+
function retiredChat(conversation) {
|
|
357
|
+
return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
|
|
358
|
+
}
|
|
359
|
+
function agentsReady() {
|
|
360
|
+
agentsRead ??= readAgents().catch(() => {
|
|
361
|
+
agentsRead = null;
|
|
362
|
+
});
|
|
363
|
+
return agentsRead;
|
|
364
|
+
}
|
|
195
365
|
function live(conversation, change) {
|
|
196
|
-
|
|
197
|
-
emit(conversation
|
|
366
|
+
setLive(conversation, { ...liveOf(conversation), ...change });
|
|
367
|
+
emit(conversation);
|
|
198
368
|
}
|
|
199
369
|
function relayed(conversation, turn, operation) {
|
|
200
370
|
const held = liveOf(conversation).turn;
|
|
@@ -206,7 +376,7 @@ export function createConversationStore(deps) {
|
|
|
206
376
|
const held = lives.get(conversation);
|
|
207
377
|
if (held?.turn == null || !turns.has(held.turn.turn))
|
|
208
378
|
return false;
|
|
209
|
-
|
|
379
|
+
setLive(conversation, { ...held, turn: null });
|
|
210
380
|
return true;
|
|
211
381
|
}
|
|
212
382
|
function handReplicated() {
|
|
@@ -220,44 +390,27 @@ export function createConversationStore(deps) {
|
|
|
220
390
|
handedReplicated = named;
|
|
221
391
|
stream.replicate(held);
|
|
222
392
|
}
|
|
223
|
-
async function documented() {
|
|
224
|
-
const declaration = deps.cards();
|
|
225
|
-
if (declaration === undefined)
|
|
226
|
-
return [];
|
|
227
|
-
const { documents } = await deps.documents(declaration.locator);
|
|
228
|
-
return documents.filter((document) => typeof document.type === 'string');
|
|
229
|
-
}
|
|
230
|
-
function carded(documents) {
|
|
231
|
-
const declaration = deps.cards();
|
|
232
|
-
if (declaration === undefined)
|
|
233
|
-
return [];
|
|
234
|
-
const cards = [];
|
|
235
|
-
for (const card of documents) {
|
|
236
|
-
if (card.type !== declaration.type)
|
|
237
|
-
continue;
|
|
238
|
-
const identity = card.fields[declaration.identity];
|
|
239
|
-
if (typeof identity === 'string')
|
|
240
|
-
cards.push({ conversation: identity, card });
|
|
241
|
-
}
|
|
242
|
-
return cards;
|
|
243
|
-
}
|
|
244
393
|
async function remove(leaving) {
|
|
245
394
|
await deps.durably(async (local) => {
|
|
246
|
-
await local.dropConversations(leaving);
|
|
247
395
|
const removed = [];
|
|
248
|
-
const changed = [];
|
|
249
396
|
for (const conversation of leaving) {
|
|
250
|
-
for (const
|
|
251
|
-
removed.push({ conversation, identity:
|
|
397
|
+
for (const identity of await local.messageIdentities(conversation)) {
|
|
398
|
+
removed.push({ conversation, identity, text: null });
|
|
252
399
|
}
|
|
253
|
-
|
|
400
|
+
}
|
|
401
|
+
await local.dropConversations(leaving);
|
|
402
|
+
const changed = [];
|
|
403
|
+
for (const conversation of leaving) {
|
|
404
|
+
if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
254
405
|
changed.push(conversation);
|
|
255
406
|
}
|
|
256
|
-
|
|
407
|
+
stored.delete(conversation);
|
|
257
408
|
reads.delete(conversation);
|
|
409
|
+
partials.delete(conversation);
|
|
258
410
|
drafts.delete(conversation);
|
|
259
411
|
behind.delete(conversation);
|
|
260
|
-
|
|
412
|
+
setLive(conversation, null);
|
|
413
|
+
slide(conversation, null);
|
|
261
414
|
}
|
|
262
415
|
if (changed.length > 0)
|
|
263
416
|
deps.publish(conversationChangeSet(removed, changed));
|
|
@@ -275,30 +428,25 @@ export function createConversationStore(deps) {
|
|
|
275
428
|
const advances = read !== undefined &&
|
|
276
429
|
!behind.has(conversation) &&
|
|
277
430
|
(read === null || message.position > read);
|
|
278
|
-
|
|
431
|
+
await sealed(local, conversation, new Set([message.turn]));
|
|
279
432
|
await local.keepMessages({
|
|
280
433
|
conversation,
|
|
281
434
|
messages: [message],
|
|
282
|
-
...(advances ? { read: message.position } : {}),
|
|
283
|
-
sealed,
|
|
435
|
+
...(advances ? { read: { at: message.position, partial: false } } : {}),
|
|
284
436
|
});
|
|
285
|
-
|
|
437
|
+
stored.add(conversation);
|
|
286
438
|
if (advances)
|
|
287
439
|
reads.set(conversation, message.position);
|
|
288
|
-
if (sealed)
|
|
289
|
-
drafts.delete(conversation);
|
|
290
440
|
sealTurns(conversation, new Set([message.turn]));
|
|
291
|
-
|
|
441
|
+
slide(conversation, [message]);
|
|
442
|
+
deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
|
|
292
443
|
});
|
|
293
444
|
}
|
|
294
445
|
async function readHistories(conversations) {
|
|
295
446
|
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
296
|
-
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
297
|
-
const arriving = new Map();
|
|
298
|
-
const complete = new Set();
|
|
299
447
|
let asking = conversations.map((conversation) => ({
|
|
300
448
|
conversation,
|
|
301
|
-
after:
|
|
449
|
+
after: reads.get(conversation) ?? undefined,
|
|
302
450
|
}));
|
|
303
451
|
while (asking.length > 0) {
|
|
304
452
|
const result = await deps.call('conversation.history', {
|
|
@@ -310,124 +458,244 @@ export function createConversationStore(deps) {
|
|
|
310
458
|
const pages = historyPages(result);
|
|
311
459
|
if (pages === null)
|
|
312
460
|
return result;
|
|
313
|
-
const asked = new
|
|
461
|
+
const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
|
|
314
462
|
const following = [];
|
|
315
463
|
for (const page of pages) {
|
|
316
464
|
if (!asked.has(page.conversation))
|
|
317
465
|
continue;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
...page.messages,
|
|
321
|
-
]);
|
|
322
|
-
if (page.next === undefined)
|
|
323
|
-
complete.add(page.conversation);
|
|
324
|
-
else
|
|
466
|
+
await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
|
|
467
|
+
if (page.next !== undefined) {
|
|
325
468
|
following.push({ conversation: page.conversation, after: page.next });
|
|
469
|
+
}
|
|
326
470
|
}
|
|
327
471
|
asking = following;
|
|
328
472
|
}
|
|
329
|
-
for (const [conversation, held] of arriving) {
|
|
330
|
-
if (!complete.has(conversation))
|
|
331
|
-
continue;
|
|
332
|
-
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
333
|
-
}
|
|
334
473
|
return null;
|
|
335
474
|
}
|
|
336
|
-
function
|
|
475
|
+
function keepPage(conversation, arriving, from, next, generation) {
|
|
337
476
|
return deps.durably(async (local) => {
|
|
338
477
|
if (!replicated.has(conversation))
|
|
339
478
|
return;
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
await local
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
479
|
+
const whole = next === undefined;
|
|
480
|
+
const cursor = next ?? newestOf(arriving, from);
|
|
481
|
+
const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
|
|
482
|
+
const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
|
|
483
|
+
await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
|
|
484
|
+
stored.add(conversation);
|
|
485
|
+
reads.set(conversation, cursor);
|
|
486
|
+
if (partial)
|
|
487
|
+
partials.add(conversation);
|
|
488
|
+
else
|
|
489
|
+
partials.delete(conversation);
|
|
348
490
|
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
349
|
-
if ((gaps.get(conversation) ?? 0) === generation)
|
|
491
|
+
if (whole && (gaps.get(conversation) ?? 0) === generation)
|
|
350
492
|
behind.delete(conversation);
|
|
351
|
-
|
|
352
|
-
|
|
493
|
+
slide(conversation, arriving);
|
|
494
|
+
if (arriving.length > 0 || sealing || whole) {
|
|
495
|
+
deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
|
|
353
496
|
}
|
|
354
497
|
});
|
|
355
498
|
}
|
|
499
|
+
function heard(event) {
|
|
500
|
+
const { conversation } = event;
|
|
501
|
+
if (event.kind === 'gap') {
|
|
502
|
+
if (replicated.has(conversation))
|
|
503
|
+
gap(conversation);
|
|
504
|
+
const held = lives.get(conversation);
|
|
505
|
+
if (held === undefined)
|
|
506
|
+
return;
|
|
507
|
+
setLive(conversation, { ...QUIET, turn: held.turn });
|
|
508
|
+
emit(conversation);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (event.kind === 'turn') {
|
|
512
|
+
relayed(conversation, event.turn, event.operation);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (event.kind === 'questions') {
|
|
516
|
+
live(conversation, { questions: structuredClone(event.questions) });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (event.kind === 'process') {
|
|
520
|
+
live(conversation, { process: { running: event.running } });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (event.kind === 'commands') {
|
|
524
|
+
live(conversation, { commands: structuredClone(event.commands) });
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (event.kind === 'options') {
|
|
528
|
+
live(conversation, { options: structuredClone(event.options) });
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const arriving = acceptedMessage(event.message);
|
|
532
|
+
if (arriving === null)
|
|
533
|
+
return;
|
|
534
|
+
if (replicated.has(conversation)) {
|
|
535
|
+
void keep(conversation, arriving).catch(() => { });
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
539
|
+
emit(conversation);
|
|
540
|
+
}
|
|
541
|
+
async function kept(local, conversation, following) {
|
|
542
|
+
if (following === null || empty(following)) {
|
|
543
|
+
await local.dropChatDraft(conversation);
|
|
544
|
+
drafts.delete(conversation);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
await local.keepChatDrafts([following]);
|
|
548
|
+
drafts.set(conversation, following);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function sealed(local, conversation, turns) {
|
|
552
|
+
const held = drafts.get(conversation);
|
|
553
|
+
const submitted = held?.submitted ?? [];
|
|
554
|
+
const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
|
|
555
|
+
if (held === undefined || left.length === submitted.length)
|
|
556
|
+
return false;
|
|
557
|
+
await kept(local, conversation, withSubmitted(held, left));
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
function moved(agent, operation, to) {
|
|
561
|
+
const from = newChat(agent);
|
|
562
|
+
return deps.durably(async (local) => {
|
|
563
|
+
const held = drafts.get(from);
|
|
564
|
+
const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
|
|
565
|
+
if (earlier.includes(operation))
|
|
566
|
+
return;
|
|
567
|
+
const blank = {
|
|
568
|
+
conversation: from,
|
|
569
|
+
text: '',
|
|
570
|
+
files: [],
|
|
571
|
+
revision: chatDraftRevision(),
|
|
572
|
+
};
|
|
573
|
+
const { moves: _answered, ...unsent } = held ?? blank;
|
|
574
|
+
const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
|
|
575
|
+
const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
|
|
576
|
+
await local.keepChatDrafts([left, ...taken]);
|
|
577
|
+
drafts.set(from, left);
|
|
578
|
+
for (const one of taken)
|
|
579
|
+
drafts.set(to, one);
|
|
580
|
+
deps.publish(conversationChangeSet([], [from, to]));
|
|
581
|
+
});
|
|
582
|
+
}
|
|
356
583
|
function withDraft(conversation, next) {
|
|
357
584
|
return deps.durably(async (local) => {
|
|
358
|
-
const following = next(drafts.get(conversation));
|
|
585
|
+
const following = await next(drafts.get(conversation), local);
|
|
359
586
|
if (following === undefined)
|
|
360
587
|
return;
|
|
361
|
-
|
|
362
|
-
await local.dropChatDraft(conversation);
|
|
363
|
-
drafts.delete(conversation);
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
await local.keepChatDraft(following);
|
|
367
|
-
drafts.set(conversation, following);
|
|
368
|
-
}
|
|
588
|
+
await kept(local, conversation, following);
|
|
369
589
|
deps.publish(conversationChangeSet([], [conversation]));
|
|
370
590
|
});
|
|
371
591
|
}
|
|
592
|
+
async function submitted(conversation, operation, draft) {
|
|
593
|
+
let submission;
|
|
594
|
+
let earlier = false;
|
|
595
|
+
await withDraft(conversation, (current) => {
|
|
596
|
+
submission = current?.submitted?.find((one) => one.operation === operation);
|
|
597
|
+
earlier = submission !== undefined;
|
|
598
|
+
if (submission !== undefined)
|
|
599
|
+
return undefined;
|
|
600
|
+
const said = draft ?? current;
|
|
601
|
+
submission = {
|
|
602
|
+
operation,
|
|
603
|
+
text: said?.text ?? '',
|
|
604
|
+
files: [...(said?.files ?? [])],
|
|
605
|
+
turn: null,
|
|
606
|
+
};
|
|
607
|
+
return {
|
|
608
|
+
conversation,
|
|
609
|
+
...(draft === undefined || current === undefined
|
|
610
|
+
? { text: '', files: [], revision: chatDraftRevision() }
|
|
611
|
+
: { text: current.text, files: current.files, revision: current.revision }),
|
|
612
|
+
submitted: [...(current?.submitted ?? []), submission],
|
|
613
|
+
};
|
|
614
|
+
});
|
|
615
|
+
return { submission: submission, earlier };
|
|
616
|
+
}
|
|
617
|
+
function forgotten(conversation, submission) {
|
|
618
|
+
return withDraft(conversation, (current) => {
|
|
619
|
+
const held = current?.submitted ?? [];
|
|
620
|
+
if (current === undefined || !held.includes(submission))
|
|
621
|
+
return undefined;
|
|
622
|
+
return withSubmitted(current, held.filter((one) => one !== submission));
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
async function dispatched(conversation, submission) {
|
|
626
|
+
const dispatch = {};
|
|
627
|
+
sending.set(submission.operation, dispatch);
|
|
628
|
+
try {
|
|
629
|
+
const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
|
|
630
|
+
const result = await invoke('conversation.send', conversation, {
|
|
631
|
+
text: submission.text,
|
|
632
|
+
files,
|
|
633
|
+
operation_id: submission.operation,
|
|
634
|
+
});
|
|
635
|
+
if (result.kind === 'revoked-authorization')
|
|
636
|
+
return result;
|
|
637
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
638
|
+
deps.unanswered();
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
const turn = answered(result, 'turn');
|
|
642
|
+
await withDraft(conversation, async (current, local) => {
|
|
643
|
+
const held = current?.submitted?.find((one) => one.operation === submission.operation);
|
|
644
|
+
if (current === undefined || held === undefined || held.turn !== null)
|
|
645
|
+
return undefined;
|
|
646
|
+
const others = current.submitted.filter((one) => one !== held);
|
|
647
|
+
if (result.kind !== 'action')
|
|
648
|
+
return returned(current, held, others);
|
|
649
|
+
const arrived = turn !== null && (await local.turnArrived(conversation, turn));
|
|
650
|
+
return withSubmitted(current, turn === null || arrived
|
|
651
|
+
? others
|
|
652
|
+
: current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
|
|
653
|
+
});
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
finally {
|
|
657
|
+
if (sending.get(submission.operation) === dispatch)
|
|
658
|
+
sending.delete(submission.operation);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
372
661
|
function invoke(name, conversation, extra = {}) {
|
|
373
662
|
return deps.call(name, { conversation, ...extra });
|
|
374
663
|
}
|
|
375
664
|
const surface = {
|
|
376
665
|
async list() {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return [];
|
|
380
|
-
const documents = await documented();
|
|
381
|
-
const entries = carded(documents).map(({ conversation, card }) => ({
|
|
382
|
-
conversation,
|
|
383
|
-
card,
|
|
384
|
-
agent: documents.find((document) => document.backlinks.some((backlink) => backlink.path === card.path && backlink.field === declaration.agent)) ?? null,
|
|
385
|
-
}));
|
|
386
|
-
entries.sort((left, right) => (left.conversation < right.conversation ? -1 : 1));
|
|
387
|
-
return entries;
|
|
666
|
+
await agentsReady();
|
|
667
|
+
return entries();
|
|
388
668
|
},
|
|
389
669
|
async read(conversation, options = {}) {
|
|
390
670
|
const limit = limitOf(options);
|
|
391
|
-
return deps.durably(() =>
|
|
671
|
+
return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
|
|
392
672
|
},
|
|
393
|
-
create(input) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const draft = drafts.get(conversation);
|
|
398
|
-
const pending = draft === undefined ? undefined : { draft };
|
|
399
|
-
if (pending !== undefined) {
|
|
400
|
-
sending.set(conversation, pending);
|
|
401
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
402
|
-
}
|
|
403
|
-
try {
|
|
404
|
-
const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
|
|
405
|
-
...(await declaredFile(file)),
|
|
406
|
-
blob: file.blob,
|
|
407
|
-
})));
|
|
408
|
-
const result = await invoke('conversation.send', conversation, {
|
|
409
|
-
text: draft?.text ?? '',
|
|
410
|
-
files,
|
|
411
|
-
});
|
|
412
|
-
const turn = turnOf(result);
|
|
413
|
-
if (turn === null || draft === undefined)
|
|
414
|
-
return result;
|
|
415
|
-
await withDraft(conversation, (current) => {
|
|
416
|
-
if (current !== draft)
|
|
417
|
-
return undefined;
|
|
418
|
-
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
419
|
-
return sealed ? null : { ...draft, turn };
|
|
420
|
-
});
|
|
673
|
+
async create(input) {
|
|
674
|
+
const result = await deps.call('conversation.create', input);
|
|
675
|
+
const conversation = answered(result, 'conversation');
|
|
676
|
+
if (conversation === null)
|
|
421
677
|
return result;
|
|
678
|
+
if (!listing.has(conversation))
|
|
679
|
+
creations.add(conversation);
|
|
680
|
+
const creation = creationOf(input);
|
|
681
|
+
if (creation !== null)
|
|
682
|
+
await moved(creation.agent, creation.operation, conversation);
|
|
683
|
+
return result;
|
|
684
|
+
},
|
|
685
|
+
async send(conversation, operation = changeBatchIdentity(), draft) {
|
|
686
|
+
if (deps.online()) {
|
|
687
|
+
const { submission, earlier } = await submitted(conversation, operation, draft);
|
|
688
|
+
if (!earlier || !deps.expired(operation))
|
|
689
|
+
return dispatched(conversation, submission);
|
|
690
|
+
await forgotten(conversation, submission);
|
|
691
|
+
return EXPIRED;
|
|
422
692
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
deps.publish(conversationChangeSet([], [conversation]));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
693
|
+
if (draft !== undefined) {
|
|
694
|
+
await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
|
|
695
|
+
? undefined
|
|
696
|
+
: returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
|
|
430
697
|
}
|
|
698
|
+
return OFFLINE;
|
|
431
699
|
},
|
|
432
700
|
answer(conversation, answer) {
|
|
433
701
|
return invoke('conversation.answer', conversation, { answer });
|
|
@@ -442,7 +710,12 @@ export function createConversationStore(deps) {
|
|
|
442
710
|
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
443
711
|
if (result.kind !== 'action')
|
|
444
712
|
return result;
|
|
445
|
-
await withDraft(conversation, (current) =>
|
|
713
|
+
await withDraft(conversation, (current) => {
|
|
714
|
+
const held = current?.submitted?.find((one) => one.turn === turn);
|
|
715
|
+
if (current === undefined || held === undefined)
|
|
716
|
+
return undefined;
|
|
717
|
+
return returned(current, held, current.submitted.filter((one) => one !== held));
|
|
718
|
+
});
|
|
446
719
|
return result;
|
|
447
720
|
},
|
|
448
721
|
delete(conversation) {
|
|
@@ -450,40 +723,54 @@ export function createConversationStore(deps) {
|
|
|
450
723
|
},
|
|
451
724
|
};
|
|
452
725
|
const chatDraft = {
|
|
453
|
-
write(conversation, draft) {
|
|
454
|
-
return withDraft(conversation, () => ({
|
|
726
|
+
write(conversation, draft, revision = chatDraftRevision()) {
|
|
727
|
+
return withDraft(conversation, (current) => ({
|
|
455
728
|
conversation,
|
|
456
729
|
text: draft.text,
|
|
457
730
|
files: [...draft.files],
|
|
458
|
-
|
|
731
|
+
revision,
|
|
732
|
+
...(current === undefined ? {} : pending(current)),
|
|
459
733
|
}));
|
|
460
734
|
},
|
|
461
735
|
discard(conversation) {
|
|
462
|
-
return withDraft(conversation, (current) =>
|
|
736
|
+
return withDraft(conversation, (current) => current === undefined
|
|
737
|
+
? undefined
|
|
738
|
+
: {
|
|
739
|
+
conversation,
|
|
740
|
+
text: '',
|
|
741
|
+
files: [],
|
|
742
|
+
revision: chatDraftRevision(),
|
|
743
|
+
...pending(current),
|
|
744
|
+
});
|
|
463
745
|
},
|
|
464
746
|
};
|
|
465
747
|
return {
|
|
466
748
|
surface,
|
|
467
749
|
chatDraft,
|
|
468
|
-
hydrate(held,
|
|
469
|
-
|
|
750
|
+
hydrate(held, kept) {
|
|
751
|
+
stored.clear();
|
|
470
752
|
reads.clear();
|
|
471
|
-
|
|
472
|
-
view.messages = null;
|
|
753
|
+
partials.clear();
|
|
473
754
|
for (const entry of held) {
|
|
474
|
-
|
|
755
|
+
stored.add(entry.conversation);
|
|
475
756
|
if (entry.read !== undefined)
|
|
476
757
|
reads.set(entry.conversation, entry.read);
|
|
758
|
+
if (entry.partial)
|
|
759
|
+
partials.add(entry.conversation);
|
|
477
760
|
}
|
|
478
761
|
drafts.clear();
|
|
479
|
-
for (const record of
|
|
762
|
+
for (const record of kept)
|
|
480
763
|
drafts.set(record.conversation, record);
|
|
481
764
|
},
|
|
482
765
|
async reconcile() {
|
|
483
|
-
|
|
766
|
+
if (!listKnown)
|
|
767
|
+
return;
|
|
768
|
+
const named = new Set(listing.keys());
|
|
484
769
|
const leaving = [
|
|
485
|
-
...new Set([...replicated, ...
|
|
486
|
-
].filter((conversation) => !named.has(conversation)
|
|
770
|
+
...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
|
|
771
|
+
].filter((conversation) => !named.has(conversation) &&
|
|
772
|
+
!creations.has(conversation) &&
|
|
773
|
+
(!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
|
|
487
774
|
for (const conversation of named) {
|
|
488
775
|
if (!replicated.has(conversation))
|
|
489
776
|
behind.add(conversation);
|
|
@@ -503,13 +790,44 @@ export function createConversationStore(deps) {
|
|
|
503
790
|
if (behind.size > 0)
|
|
504
791
|
progress.complete = false;
|
|
505
792
|
},
|
|
793
|
+
async resend(progress) {
|
|
794
|
+
for (const [conversation, held] of [...drafts]) {
|
|
795
|
+
for (const submission of held.submitted ?? []) {
|
|
796
|
+
if (submission.turn !== null || sending.has(submission.operation))
|
|
797
|
+
continue;
|
|
798
|
+
if (deps.closed())
|
|
799
|
+
return;
|
|
800
|
+
if (deps.expired(submission.operation)) {
|
|
801
|
+
await forgotten(conversation, submission);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const result = await dispatched(conversation, submission);
|
|
805
|
+
if (result.kind === 'revoked-authorization')
|
|
806
|
+
return;
|
|
807
|
+
if (result.kind === 'transport-uncertainty') {
|
|
808
|
+
progress.reachable = false;
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (result.kind !== 'action' && result.retryable) {
|
|
812
|
+
progress.complete = false;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
},
|
|
506
818
|
current() {
|
|
507
819
|
return behind.size === 0;
|
|
508
820
|
},
|
|
821
|
+
known() {
|
|
822
|
+
return listKnown;
|
|
823
|
+
},
|
|
509
824
|
reconnected() {
|
|
510
825
|
for (const conversation of replicated)
|
|
511
826
|
gap(conversation);
|
|
512
827
|
},
|
|
828
|
+
reopened() {
|
|
829
|
+
sending.clear();
|
|
830
|
+
},
|
|
513
831
|
watch(conversation, options, deliver) {
|
|
514
832
|
const limit = limitOf(options);
|
|
515
833
|
const key = JSON.stringify([conversation, limit ?? null]);
|
|
@@ -517,18 +835,20 @@ export function createConversationStore(deps) {
|
|
|
517
835
|
conversation,
|
|
518
836
|
limit,
|
|
519
837
|
watchers: new Set(),
|
|
520
|
-
|
|
838
|
+
window: null,
|
|
521
839
|
};
|
|
522
840
|
views.set(key, view);
|
|
523
841
|
const watcher = { notify: deliver, ready: false };
|
|
524
842
|
view.watchers.add(watcher);
|
|
525
843
|
void deps
|
|
526
|
-
.durably(() =>
|
|
844
|
+
.durably(async (local) => {
|
|
845
|
+
view.window ??= await local.messageWindow(conversation, limit);
|
|
846
|
+
})
|
|
527
847
|
.then(() => {
|
|
528
848
|
if (deps.closed() || !view.watchers.has(watcher))
|
|
529
849
|
return;
|
|
530
850
|
watcher.ready = true;
|
|
531
|
-
watcher.notify(viewOf(conversation,
|
|
851
|
+
watcher.notify(viewOf(conversation, view.window ?? []));
|
|
532
852
|
})
|
|
533
853
|
.catch(() => { });
|
|
534
854
|
return () => {
|
|
@@ -538,64 +858,76 @@ export function createConversationStore(deps) {
|
|
|
538
858
|
views.delete(key);
|
|
539
859
|
};
|
|
540
860
|
},
|
|
861
|
+
watchList(deliver) {
|
|
862
|
+
listWatchers.add(deliver);
|
|
863
|
+
void agentsReady().then(() => {
|
|
864
|
+
if (!deps.closed() && listWatchers.has(deliver))
|
|
865
|
+
deliver(entries());
|
|
866
|
+
});
|
|
867
|
+
return () => {
|
|
868
|
+
listWatchers.delete(deliver);
|
|
869
|
+
};
|
|
870
|
+
},
|
|
871
|
+
watchAgents(deliver) {
|
|
872
|
+
agentWatchers.add(deliver);
|
|
873
|
+
void agentsReady().then(() => {
|
|
874
|
+
if (!deps.closed() && agentWatchers.has(deliver))
|
|
875
|
+
deliver(agents());
|
|
876
|
+
});
|
|
877
|
+
return () => {
|
|
878
|
+
agentWatchers.delete(deliver);
|
|
879
|
+
};
|
|
880
|
+
},
|
|
541
881
|
changed(set) {
|
|
542
882
|
for (const conversation of set.conversations)
|
|
543
883
|
emit(conversation);
|
|
884
|
+
const declaration = deps.agents();
|
|
885
|
+
if (declaration !== undefined &&
|
|
886
|
+
set.documents.some((change) => change.locator === declaration.locator)) {
|
|
887
|
+
agentsRead = readAgents().catch(() => {
|
|
888
|
+
agentsRead = null;
|
|
889
|
+
});
|
|
890
|
+
}
|
|
544
891
|
},
|
|
545
892
|
receive(event) {
|
|
546
893
|
if (deps.closed())
|
|
547
|
-
return;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
if (event.kind === 'commands') {
|
|
572
|
-
live(conversation, { commands: structuredClone(event.commands) });
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (event.kind === 'options') {
|
|
576
|
-
live(conversation, { options: structuredClone(event.options) });
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const arriving = acceptedMessage(event.message);
|
|
580
|
-
if (arriving === null)
|
|
581
|
-
return;
|
|
582
|
-
if (replicated.has(conversation)) {
|
|
583
|
-
void keep(conversation, arriving).catch(() => { });
|
|
584
|
-
return;
|
|
894
|
+
return false;
|
|
895
|
+
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
|
|
896
|
+
if (event.kind === 'list') {
|
|
897
|
+
listing.clear();
|
|
898
|
+
for (const entry of event.entries) {
|
|
899
|
+
listing.set(entry.conversation, { ...entry });
|
|
900
|
+
creations.delete(entry.conversation);
|
|
901
|
+
}
|
|
902
|
+
listKnown = true;
|
|
903
|
+
}
|
|
904
|
+
else if (event.kind === 'listed') {
|
|
905
|
+
listing.set(event.entry.conversation, { ...event.entry });
|
|
906
|
+
creations.delete(event.entry.conversation);
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
listing.delete(event.conversation);
|
|
910
|
+
}
|
|
911
|
+
emitList();
|
|
912
|
+
return (event.kind !== 'list' &&
|
|
913
|
+
listKnown &&
|
|
914
|
+
(listing.size !== replicated.size ||
|
|
915
|
+
[...listing.keys()].some((conversation) => !replicated.has(conversation))));
|
|
585
916
|
}
|
|
586
|
-
|
|
587
|
-
|
|
917
|
+
heard(event);
|
|
918
|
+
return false;
|
|
588
919
|
},
|
|
589
|
-
|
|
590
|
-
return [...
|
|
920
|
+
searchable() {
|
|
921
|
+
return [...(listKnown ? listing.keys() : stored)].sort();
|
|
591
922
|
},
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
923
|
+
acquired(conversation) {
|
|
924
|
+
return reads.has(conversation) && !partials.has(conversation);
|
|
595
925
|
},
|
|
596
926
|
close() {
|
|
597
927
|
views.clear();
|
|
598
928
|
lives.clear();
|
|
929
|
+
listWatchers.clear();
|
|
930
|
+
agentWatchers.clear();
|
|
599
931
|
},
|
|
600
932
|
};
|
|
601
933
|
}
|