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